Merge branch 'rel-4.0'
This commit is contained in:
commit
13248bf395
9 changed files with 200 additions and 70 deletions
|
|
@ -879,7 +879,7 @@ class Polygon {
|
|||
* @returns {Polygon}
|
||||
*/
|
||||
clone(deep) {
|
||||
let np = newPolygon().copyZ(this.z),
|
||||
let np = newPolygon().copyZ(this.getZ()),
|
||||
ln = this.length,
|
||||
i = 0;
|
||||
|
||||
|
|
@ -938,9 +938,7 @@ class Polygon {
|
|||
ln = ar.length,
|
||||
i = 0;
|
||||
while (i < ln) ar[i++].z = z;
|
||||
if (this.inner) this.inner.forEach(function(c) {
|
||||
c.setZ(z)
|
||||
});
|
||||
if (this.inner) this.inner.forEach(c => c.setZ(z));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -2050,22 +2048,136 @@ class Polygon {
|
|||
return faces;
|
||||
}
|
||||
|
||||
// for turning a poly with an inner offset into a
|
||||
// 3d mesh if and only if the inner has the same
|
||||
// circularity and <= num points. used to make chamfers
|
||||
ribbonMesh(swap) {
|
||||
if (!(this.inner && this.inner.length === 1)) {
|
||||
return undefined;
|
||||
}
|
||||
let outer = this.clone().setClockwise();
|
||||
let inner = this.inner[0].clone().setClockwise();
|
||||
let c0 = outer.circularity();
|
||||
let c1 = inner.circularity();
|
||||
let n0 = outer.points.length;
|
||||
let n1 = inner.points.length;
|
||||
let p0 = outer.points.slice();
|
||||
let p1 = inner.points.slice();
|
||||
let min = { d: Infinity, i:0, j:0 };
|
||||
for (let i=0; i<p0.length; i++) {
|
||||
for (let j=0; j<p1.length; j++) {
|
||||
let d = p0[i].distTo2D(p1[j]);
|
||||
if (d < min.d) {
|
||||
min = { d, i, j };
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p0.slice(min.i).concat(p0.slice(0, min.i)); p0.push(p0[0]);
|
||||
p1 = p1.slice(min.j).concat(p1.slice(0, min.j)); p1.push(p1[0]);
|
||||
// walk both arrays moving to the next poly + point that forms
|
||||
// the shortest line segment between the two polys
|
||||
let faces = [];
|
||||
let pi0 = 0;
|
||||
let pi1 = 0;
|
||||
let pp0 = p0[pi0];
|
||||
let pp1 = p1[pi1];
|
||||
for (;;) {
|
||||
let pn0 = p0[pi0 + 1];
|
||||
let pn1 = p1[pi1 + 1];
|
||||
if ((!pn0 && pn1) || (pn1 && pp0.distTo2D(pn1) < pp1.distTo2D(pn0))) {
|
||||
// emit and increment bottom
|
||||
faces.push(pp0.x, pp0.y, pp0.z);
|
||||
if (swap) {
|
||||
faces.push(pp1.x, pp1.y, pp1.z);
|
||||
faces.push(pn1.x, pn1.y, pn1.z);
|
||||
} else {
|
||||
faces.push(pn1.x, pn1.y, pn1.z);
|
||||
faces.push(pp1.x, pp1.y, pp1.z);
|
||||
}
|
||||
pi1++;
|
||||
pp1 = p1[pi1];
|
||||
} else if (pn0) {
|
||||
// emit and increment top
|
||||
faces.push(pp0.x, pp0.y, pp0.z);
|
||||
if (swap) {
|
||||
faces.push(pp1.x, pp1.y, pp1.z);
|
||||
faces.push(pn0.x, pn0.y, pn0.z);
|
||||
} else {
|
||||
faces.push(pn0.x, pn0.y, pn0.z);
|
||||
faces.push(pp1.x, pp1.y, pp1.z);
|
||||
}
|
||||
pi0++;
|
||||
pp0 = p0[pi0];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return faces;
|
||||
}
|
||||
|
||||
// extrude poly (with inner voids) into 3d mesh
|
||||
extrude(z = 1, zadd = 0) {
|
||||
let obj = [];
|
||||
let earcut = this.earcut();
|
||||
for (let poly of earcut) {
|
||||
extrude(z = 1, opt = {}) {
|
||||
let chamfer = opt.chamfer || 0;
|
||||
let chamfer_top = opt.chamfer_top || chamfer;
|
||||
let chamfer_bottom = opt.chamfer_bottom || chamfer;
|
||||
let zadd = (typeof opt === 'number' ? opt : opt.zadd || 0); // z bottom
|
||||
let earcut = this.earcut(); // array of 3-point polygons
|
||||
let obj = []; // flat output vertex array (float-x,float-y,float-z,...)
|
||||
let top_face = earcut;
|
||||
let bottom_face = earcut;
|
||||
let z_top = z + zadd;
|
||||
let z_bottom = zadd;
|
||||
let z_side_top = z;
|
||||
let z_side_bottom = z_bottom;
|
||||
|
||||
// create chamfers (when defined)
|
||||
if (chamfer_top) {
|
||||
let inset = this.offset(chamfer_top);
|
||||
if (inset.length === 1) {
|
||||
inset[0].setZ(z_top);
|
||||
top_face = inset[0].earcut();
|
||||
z_side_top -= chamfer_top;
|
||||
let renest = POLY.renest([this.clone(true).setZ(z_side_top), inset[0]]);
|
||||
for (let rnpoly of renest) {
|
||||
obj.appendAll(rnpoly.ribbonMesh(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chamfer_bottom) {
|
||||
let inset = this.offset(chamfer_bottom);
|
||||
if (inset.length === 1) {
|
||||
inset[0].setZ(0);
|
||||
bottom_face = inset[0].earcut();
|
||||
z_side_top -= chamfer_top;
|
||||
z_side_bottom += chamfer_top;
|
||||
let renest = POLY.renest([this.clone(true).setZ(z_side_bottom), inset[0]]);
|
||||
for (let rnpoly of renest) {
|
||||
obj.appendAll(rnpoly.ribbonMesh(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let poly of top_face) {
|
||||
for (let point of poly.points) {
|
||||
obj.push(point.x, point.y, z + zadd);
|
||||
obj.push(point.x, point.y, z_top);
|
||||
}
|
||||
}
|
||||
|
||||
// bottom face (reversed to reverse normals)
|
||||
for (let poly of bottom_face) {
|
||||
for (let point of poly.points.reverse()) {
|
||||
obj.push(point.x, point.y, zadd);
|
||||
obj.push(point.x, point.y, z_bottom);
|
||||
}
|
||||
}
|
||||
obj.appendAll(this.ribbonZ(z, zadd));
|
||||
|
||||
// outside wall
|
||||
obj.appendAll(this.ribbonZ(z_side_top, z_side_bottom));
|
||||
for (let inner of this.inner || []) {
|
||||
obj.appendAll(inner.ribbonZ(z, zadd, true));
|
||||
// inside wall(s)
|
||||
obj.appendAll(inner.ribbonZ(z_side_top, z_side_bottom, true));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const POLYS = base.polygons = {
|
|||
expand,
|
||||
points,
|
||||
length,
|
||||
renest,
|
||||
route,
|
||||
union,
|
||||
inset,
|
||||
|
|
@ -193,6 +194,13 @@ function points(polys) {
|
|||
return polys.length ? polys.map(p => p.deepLength).reduce((a,v) => a+v) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* redo nesting of polygons that might already have inners
|
||||
*/
|
||||
function renest(polygons, deep) {
|
||||
return nest(flatten(polygons, [], true), deep);
|
||||
}
|
||||
|
||||
/**
|
||||
* todo use clipper polytree?
|
||||
*
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function toMesh(text, opt = {}) {
|
|||
// copper pads
|
||||
const mid = [];
|
||||
for (let poly of flat.filter(p => p.depth === 1)) {
|
||||
mid.push(poly.extrude(z1, z0 - z1));
|
||||
mid.push(poly.extrude(z1, { zadd: z0 - z1 }));
|
||||
}
|
||||
progress(20);
|
||||
debug({ mid });
|
||||
|
|
@ -103,7 +103,7 @@ function toMesh(text, opt = {}) {
|
|||
// and (optionally) remove from the bottom for making vias
|
||||
const low = [];
|
||||
for (let poly of flat.filter(p => p.depth === 2)) {
|
||||
low.push(poly.extrude(z1, z0 - z1));
|
||||
low.push(poly.extrude(z1, { zadd: z0 - z1 }));
|
||||
}
|
||||
progress(30);
|
||||
|
||||
|
|
|
|||
|
|
@ -321,14 +321,18 @@ let add = {
|
|||
},
|
||||
|
||||
gear(opt = {}) {
|
||||
function gengear(teeth, module, angle, twist, shaft) {
|
||||
function gengear(bound) {
|
||||
const { _teeth, _module, _angle, _twist, _shaft, _offset, _height, _chamfer } = bound;
|
||||
api.modal.hide();
|
||||
worker.model_gen_gear({
|
||||
teeth: parseInt(teeth),
|
||||
module: parseFloat(module),
|
||||
angle: parseFloat(angle),
|
||||
twist: parseFloat(twist),
|
||||
shaft: parseFloat(shaft),
|
||||
teeth: parseInt(_teeth.value),
|
||||
module: parseFloat(_module.value),
|
||||
angle: parseFloat(_angle.value),
|
||||
twist: parseFloat(_twist.value),
|
||||
shaft: parseFloat(_shaft.value),
|
||||
offset: parseFloat(_offset.value),
|
||||
height: parseFloat(_height.value),
|
||||
chamfer: parseFloat(_chamfer.value),
|
||||
}).then(gear => {
|
||||
const nmdl = new mesh.model({ file: "gear", mesh: gear.toFloat32() });
|
||||
const ngrp = group.new([ nmdl ]);
|
||||
|
|
@ -339,27 +343,29 @@ let add = {
|
|||
title: "gear generator",
|
||||
body: [ h.div({ class: "addgear" }, [
|
||||
h.label('number of teeth'),
|
||||
h.input({ value: opt.teeth || 20, size: 5, id: "gteeth" }),
|
||||
h.label('module (diam / teeth)'),
|
||||
h.input({ value: opt.module || 3, size: 5, id: "gmodule" }),
|
||||
h.label('pressure angle'),
|
||||
h.input({ value: opt.angle || 20, size: 5, id: "gangle" }),
|
||||
h.label('twist angle'),
|
||||
h.input({ value: opt.twist || 0, size: 5, id: "gtwist" }),
|
||||
h.input({ value: opt.teeth || 20, size: 5, id: "_teeth" }),
|
||||
h.label('shaft diameter'),
|
||||
h.input({ value: opt.shaft || 5, size: 5, id: "gshaft" }),
|
||||
h.button({ _: "create", onclick() {
|
||||
gengear(
|
||||
api.modal.bound.gteeth.value,
|
||||
api.modal.bound.gmodule.value,
|
||||
api.modal.bound.gangle.value,
|
||||
api.modal.bound.gtwist.value,
|
||||
api.modal.bound.gshaft.value,
|
||||
)
|
||||
} })
|
||||
h.input({ value: opt.shaft || 5, size: 5, id: "_shaft" }),
|
||||
h.label('z height'),
|
||||
h.input({ value: opt.height || 15, size: 5, id: "_height" }),
|
||||
h.label('chamfer'),
|
||||
h.input({ value: opt.chamfer || 0, size: 5, id: "_chamfer" }),
|
||||
h.hr(),
|
||||
h.code("all other settings must"),
|
||||
h.code("match for gears to mesh"),
|
||||
h.hr(),
|
||||
h.label('module (diam / teeth)'),
|
||||
h.input({ value: opt.module || 3, size: 5, id: "_module" }),
|
||||
h.label('pressure angle'),
|
||||
h.input({ value: opt.angle || 20, size: 5, id: "_angle" }),
|
||||
h.label('twist angle (helix)'),
|
||||
h.input({ value: opt.twist || 0, size: 5, id: "_twist" }),
|
||||
h.label('tooth offset (play)'),
|
||||
h.input({ value: opt.offset || 0.1, size: 5, id: "_offset" }),
|
||||
h.button({ _: "create", onclick() { gengear(api.modal.bound) } })
|
||||
]) ]
|
||||
});
|
||||
api.modal.bound.gteeth.focus();
|
||||
api.modal.bound._teeth.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ function ui_build() {
|
|||
h.div({ class: "pop"}, [
|
||||
h.button({ _: 'cylinder', onclick: add.cylinder }),
|
||||
h.button({ _: 'cube', onclick: add.cube }),
|
||||
h.button({ _: 'gear', onclick: add.gear }),
|
||||
h.button({ _: 'gear', onclick() { add.gear() } }),
|
||||
devel ? h.button({ _: 'input', onclick: add.input }) : undefined
|
||||
])
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -740,7 +740,7 @@ mesh.tool = class MeshTool {
|
|||
|
||||
// returns points array for a polygon
|
||||
// extrusion and twist is handled in work.js
|
||||
generateGear(numTeeth, module, pressureAngle) {
|
||||
generateGear(numTeeth, module, pressureAngle, offset) {
|
||||
// Adapted from: Public Domain Parametric Involute Spur Gear by Leemon Baird, 2011, Leemon@Leemon.com http://www.thingiverse.com/thing:5505
|
||||
// see also http://grabcad.com/questions/tutorial-how-to-model-involute-gears-in-solidworks-and-show-design-intent
|
||||
|
||||
|
|
@ -787,10 +787,11 @@ mesh.tool = class MeshTool {
|
|||
|
||||
// gear parameter setup
|
||||
let mm_per_tooth = module * pi; // mm size of one gear tooth
|
||||
let clearance = 0.1; // freedom between two gear centers
|
||||
let backlash = 0.1; // freedom between two gear contact points
|
||||
let clearance = offset/2 || 0; // freedom between two gear centers
|
||||
let backlash = offset || 0; // freedom between two gear contact points
|
||||
let pressure_angle = degrees_to_radians(pressureAngle);
|
||||
let gear = [];
|
||||
let pitch;
|
||||
|
||||
// involute gear maker
|
||||
{
|
||||
|
|
@ -800,11 +801,12 @@ mesh.tool = class MeshTool {
|
|||
let r = p - (c - p) - clearance; // radius of root circle
|
||||
let t = mm_per_tooth / 2 - backlash / 2; // tooth thickness at pitch circle
|
||||
let k = -iang(b, p) - t / 2 / p; // angle where involute meets base circle on side of tooth
|
||||
let f = 10 + ((p/5) | 0); // increase number of tooth curve points on larger gears
|
||||
let points = [ // [x,y] points for a single gear tooth
|
||||
polar(r, -pi / numTeeth),
|
||||
polar(r, r < b ? k : -pi / numTeeth),
|
||||
...qf(10, r, b, c, k, 1),
|
||||
...qf(10, r, b, c, k, -1).reverse(),
|
||||
...qf(f, r, b, c, k, 1),
|
||||
...qf(f, r, b, c, k, -1).reverse(),
|
||||
polar(r, r < b ? -k : pi / numTeeth),
|
||||
// polar(r, 3.142 / numTeeth) // omit b/c overlaps start when rotated
|
||||
];
|
||||
|
|
@ -814,10 +816,11 @@ mesh.tool = class MeshTool {
|
|||
gear.appendAll(rotate(points, -i * 2 * pi / numTeeth));
|
||||
}
|
||||
|
||||
mesh.log(`gear pitch radius: ${p.round(3)}`);
|
||||
pitch = p.round(3);
|
||||
mesh.log(`gear pitch radius: ${pitch}`);
|
||||
}
|
||||
|
||||
return gear;
|
||||
return { gear, pitch };
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -401,31 +401,27 @@ let model = {
|
|||
},
|
||||
|
||||
gen_gear(data, send) {
|
||||
const { teeth, module, angle, twist, shaft, z } = data;
|
||||
let points = new mesh.tool()
|
||||
.generateGear(teeth, module, angle)
|
||||
.map(v => [...v, 0]).flat();
|
||||
let poly = newPolygon().addVerts(points);
|
||||
const { teeth, module, angle, twist, shaft, offset, height, chamfer } = data;
|
||||
const { gear, pitch } = new mesh.tool().generateGear(teeth, module, angle, offset);
|
||||
const points = gear.map(v => [...v, 0]).flat();
|
||||
const poly = newPolygon().addVerts(points);
|
||||
if (shaft) {
|
||||
poly.addInner(
|
||||
newPolygon().centerCircle({
|
||||
x:0, y:0, z:0
|
||||
}, shaft / 2, Math.min(20 + shaft,100))
|
||||
);
|
||||
const nump = Math.min(30 + shaft, 150);
|
||||
const srad = shaft / 2;
|
||||
poly.addInner( newPolygon().centerCircle({ x:0, y:0, z:0 }, srad, nump) );
|
||||
}
|
||||
let verts = poly.extrude(z || 15);
|
||||
const zh = height || 15;
|
||||
const verts = poly.extrude(zh, { chamfer });
|
||||
if (twist) {
|
||||
let rad = base.util.toRadians(twist);
|
||||
const rad = base.util.toRadians(twist);
|
||||
for (let i=0; i<verts.length; i += 3) {
|
||||
if (Math.abs(verts[i+2]) < 0.001) {
|
||||
let [x, y] = base.util.rotate(
|
||||
verts[i],
|
||||
verts[i+1],
|
||||
rad
|
||||
);
|
||||
verts[i] = x;
|
||||
verts[i+1] = y;
|
||||
}
|
||||
let [x, y] = base.util.rotate(
|
||||
verts[i],
|
||||
verts[i+1],
|
||||
rad * (verts[i+2] / zh)
|
||||
);
|
||||
verts[i] = x;
|
||||
verts[i+1] = y;
|
||||
}
|
||||
}
|
||||
send.done(verts);
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ gapp.overlay(root, {
|
|||
|
||||
// add common element types
|
||||
[
|
||||
"a", "i", "hr", "div", "span", "label", "input",
|
||||
"a", "i", "hr", "div", "pre", "code", "span", "label", "input",
|
||||
"button", "svg", "textarea", "select", "option"
|
||||
].forEach(type => {
|
||||
h[type] = (attr, innr) => {
|
||||
|
|
|
|||
|
|
@ -235,8 +235,13 @@ button:hover {
|
|||
grid-template-columns: 1fr 50px;
|
||||
}
|
||||
|
||||
.addgear button {
|
||||
column-span: all;
|
||||
.addgear hr {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.addgear button, .addgear hr, .addgear code {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-import {
|
||||
|
|
@ -477,7 +482,7 @@ button.selected:hover {
|
|||
flex-direction: column;
|
||||
font-family: monospace;
|
||||
font-size: smaller;
|
||||
max-height: 5em;
|
||||
max-height: 8em;
|
||||
min-height: 1em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue