for area offsets, disable wasm (lacks features) and use round joins for offsets (better tool paths for cnc)
This commit is contained in:
parent
c9a4c31578
commit
d5b6d4c080
7 changed files with 66 additions and 46 deletions
|
|
@ -29,8 +29,8 @@ class Point {
|
||||||
|
|
||||||
toClipper() {
|
toClipper() {
|
||||||
return {
|
return {
|
||||||
X: this.x * config.clipper,
|
X: (this.x * config.clipper) | 0,
|
||||||
Y: this.y * config.clipper
|
Y: (this.y * config.clipper) | 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2070,11 +2070,13 @@ export class Polygon {
|
||||||
* @returns {?Polygon[]}
|
* @returns {?Polygon[]}
|
||||||
*/
|
*/
|
||||||
diff(poly) {
|
diff(poly) {
|
||||||
let fillang = this.fillang && this.area() > poly.area() ? this.fillang : poly.fillang,
|
let clip = new Clipper(),
|
||||||
clip = new Clipper(),
|
|
||||||
tree = new PolyTree(),
|
tree = new PolyTree(),
|
||||||
sp1 = this.toClipper(),
|
sp1 = this.toClipper(),
|
||||||
sp2 = poly.toClipper();
|
sp2 = poly.toClipper(),
|
||||||
|
fillang = this.fillang
|
||||||
|
&& this.area() > poly.area()
|
||||||
|
? this.fillang : poly.fillang;
|
||||||
|
|
||||||
clip.AddPaths(sp1, PathSubject, true);
|
clip.AddPaths(sp1, PathSubject, true);
|
||||||
clip.AddPaths(sp2, PathClip, true);
|
clip.AddPaths(sp2, PathClip, true);
|
||||||
|
|
@ -2116,11 +2118,14 @@ export class Polygon {
|
||||||
* @returns {?Polygon[]}
|
* @returns {?Polygon[]}
|
||||||
*/
|
*/
|
||||||
mask(poly, nullOnEquiv, minarea) {
|
mask(poly, nullOnEquiv, minarea) {
|
||||||
let fillang = this.fillang && this.area() > poly.area() ? this.fillang : poly.fillang,
|
let clip = new Clipper(),
|
||||||
clip = new Clipper(),
|
|
||||||
tree = new PolyTree(),
|
tree = new PolyTree(),
|
||||||
sp1 = this.toClipper(),
|
sp1 = this.toClipper(),
|
||||||
sp2 = poly.toClipper();
|
sp2 = poly.toClipper(),
|
||||||
|
fillang = this.fillang
|
||||||
|
&& this.area() > poly.area()
|
||||||
|
? this.fillang : poly.fillang;
|
||||||
|
|
||||||
clip.AddPaths(sp1, PathSubject, true);
|
clip.AddPaths(sp1, PathSubject, true);
|
||||||
clip.AddPaths(sp2, PathClip, true);
|
clip.AddPaths(sp2, PathClip, true);
|
||||||
|
|
||||||
|
|
@ -2258,28 +2263,23 @@ export class Polygon {
|
||||||
union(poly, min, all) {
|
union(poly, min, all) {
|
||||||
if (!this.overlaps(poly)) return null;
|
if (!this.overlaps(poly)) return null;
|
||||||
|
|
||||||
let fillang = this.fillang && this.area() > poly.area() ? this.fillang : poly.fillang,
|
let clip = new Clipper(),
|
||||||
clip = new Clipper(),
|
|
||||||
tree = new PolyTree(),
|
tree = new PolyTree(),
|
||||||
sp1 = this.toClipper(),
|
sp1 = this.toClipper(),
|
||||||
sp2 = poly.toClipper(),
|
sp2 = poly.toClipper(),
|
||||||
minarea = min ?? 0.1;
|
fillang = this.fillang
|
||||||
|
&& this.area() >= poly.area()
|
||||||
|
? this.fillang : poly.fillang;
|
||||||
|
|
||||||
clip.AddPaths(sp1, PathSubject, true);
|
clip.AddPaths(sp1, PathSubject, true);
|
||||||
clip.AddPaths(sp2, PathClip, true);
|
clip.AddPaths(sp2, PathClip, true);
|
||||||
|
|
||||||
if (clip.Execute(ClipUnion, tree, FillEvenOdd, FillEvenOdd)) {
|
if (clip.Execute(ClipUnion, tree, FillEvenOdd, FillEvenOdd)) {
|
||||||
let union = POLY.fromClipperTreeUnion(tree, poly.getZ(), minarea);
|
let union = POLY.fromClipperTreeUnion(tree, poly.getZ(), min ?? 0);
|
||||||
|
let length = union.length;
|
||||||
if (all) {
|
if (all) {
|
||||||
if (union.length === 2) {
|
return length === 2 ? null : union;
|
||||||
return null;
|
} else if (length === 1) {
|
||||||
// if (this.unionMatch(union) || poly.unionMatch(union)) {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
return union;
|
|
||||||
}
|
|
||||||
if (union.length === 1) {
|
|
||||||
union = union[0];
|
union = union[0];
|
||||||
union.fillang = fillang;
|
union.fillang = fillang;
|
||||||
return union;
|
return union;
|
||||||
|
|
|
||||||
|
|
@ -441,11 +441,10 @@ export function union(polys, minarea, all, opt = {}) {
|
||||||
let lpre = length(polys);
|
let lpre = length(polys);
|
||||||
|
|
||||||
if (opt.wasm && geo.wasm) {
|
if (opt.wasm && geo.wasm) {
|
||||||
let min = minarea ?? 0.01;
|
|
||||||
// let deepLength = polys.map(p => p.deepLength).reduce((a,v) => a+v);
|
|
||||||
// if (deepLength < 15000)
|
|
||||||
try {
|
try {
|
||||||
let out = geo.wasm.js.union(polys, polys[0].getZ()).filter(p => p.area() > min);
|
console.log({ wasm_union: polys, minarea });
|
||||||
|
if (minarea) polys = polys.filter(p => p.area() >= minarea);
|
||||||
|
let out = geo.wasm.js.union(polys, polys[0].getZ());
|
||||||
opt.changes = length(out) - lpre;
|
opt.changes = length(out) - lpre;
|
||||||
return out;
|
return out;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -461,10 +460,10 @@ export function union(polys, minarea, all, opt = {}) {
|
||||||
if (!out[j]) continue;
|
if (!out[j]) continue;
|
||||||
union = out[i].union(out[j], minarea, all);
|
union = out[i].union(out[j], minarea, all);
|
||||||
if (union && union.length) {
|
if (union && union.length) {
|
||||||
if (opt.onmerge) {
|
// if (opt.onmerge) {
|
||||||
a = out[i];
|
// a = out[i];
|
||||||
b = out[j];
|
// b = out[j];
|
||||||
}
|
// }
|
||||||
out[i] = null;
|
out[i] = null;
|
||||||
out[j] = null;
|
out[j] = null;
|
||||||
if (all) {
|
if (all) {
|
||||||
|
|
@ -472,9 +471,9 @@ export function union(polys, minarea, all, opt = {}) {
|
||||||
} else {
|
} else {
|
||||||
out.push(union);
|
out.push(union);
|
||||||
}
|
}
|
||||||
if (opt.onmerge) {
|
// if (opt.onmerge) {
|
||||||
opt.onmerge(a, b, union);
|
// opt.onmerge(a, b, union);
|
||||||
}
|
// }
|
||||||
continue outer;
|
continue outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -629,7 +628,7 @@ export function offset(polys, dist, opts = {}) {
|
||||||
mina = numOrDefault(opts.minArea, 0.1),
|
mina = numOrDefault(opts.minArea, 0.1),
|
||||||
zed = opts.z || 0;
|
zed = opts.z || 0;
|
||||||
|
|
||||||
if (opts.wasm && geo.wasm) {
|
if (opts.wasm && geo.wasm && join === JoinType.jtMiter && fill === FillNonZero && type === EndType.etClosedPolygon) {
|
||||||
try {
|
try {
|
||||||
polys = geo.wasm.js.offset(polys, offs, zed, clean ? config.clipperClean : 0, simple ? 1 : 0);
|
polys = geo.wasm.js.offset(polys, offs, zed, clean ? config.clipperClean : 0, simple ? 1 : 0);
|
||||||
if (open.length) polys.appendAll(open);
|
if (open.length) polys.appendAll(open);
|
||||||
|
|
@ -651,7 +650,7 @@ export function offset(polys, dist, opts = {}) {
|
||||||
coff.AddPaths(poly, join, type);
|
coff.AddPaths(poly, join, type);
|
||||||
}
|
}
|
||||||
// perform offset
|
// perform offset
|
||||||
coff.Execute(tree, offs * config.clipper);
|
coff.Execute(tree, (offs * config.clipper) | 0);
|
||||||
// convert back from clipper output format
|
// convert back from clipper output format
|
||||||
polys = fromClipperTree(tree, zed, null, null, mina);
|
polys = fromClipperTree(tree, zed, null, null, mina);
|
||||||
}
|
}
|
||||||
|
|
@ -660,10 +659,14 @@ export function offset(polys, dist, opts = {}) {
|
||||||
// if specified, perform offset gap analysis
|
// if specified, perform offset gap analysis
|
||||||
if (opts.gaps && polys.length) {
|
if (opts.gaps && polys.length) {
|
||||||
let oneg = offset(polys, -offs, {
|
let oneg = offset(polys, -offs, {
|
||||||
fill: opts.fill, join: opts.join, type: opts.type, z: opts.z, minArea: mina
|
fill: opts.fill,
|
||||||
|
join: opts.join,
|
||||||
|
type: opts.type,
|
||||||
|
z: opts.z,
|
||||||
|
minArea: mina
|
||||||
});
|
});
|
||||||
let suba = [];
|
let suba = [];
|
||||||
let diff = subtract(orig, oneg, suba, null, zed);
|
subtract(orig, oneg, suba, null, zed);
|
||||||
opts.gaps.append(suba, opts.flat);
|
opts.gaps.append(suba, opts.flat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { base } from '../../geo/base.js';
|
import { base } from '../../geo/base.js';
|
||||||
import { avgc } from './utils.js';
|
import { avgc } from './utils.js';
|
||||||
import { util as mesh_util } from '../../mesh/util.js';
|
import { util as mesh_util } from '../../mesh/util.js';
|
||||||
|
import { tool as mesh_tool } from '../../mesh/tool.js';
|
||||||
import { verticesToPoints } from '../../geo/points.js';
|
import { verticesToPoints } from '../../geo/points.js';
|
||||||
import { newPoint } from '../../geo/point.js';
|
import { newPoint } from '../../geo/point.js';
|
||||||
import { newPolygon } from '../../geo/polygon.js';
|
import { newPolygon } from '../../geo/polygon.js';
|
||||||
|
|
@ -158,7 +159,12 @@ class Widget {
|
||||||
* @param {Float32Array} vertices
|
* @param {Float32Array} vertices
|
||||||
* @returns {Widget}
|
* @returns {Widget}
|
||||||
*/
|
*/
|
||||||
loadVertices(data, options = { index: false }) {
|
loadVertices(data, options = { normalize: false }) {
|
||||||
|
if (options.normalize) {
|
||||||
|
console.time('mesh normalize')
|
||||||
|
data = new mesh_tool({ precision: 0.001 }).normalizeVertices(data).toFloat32();
|
||||||
|
console.timeEnd('mesh normalize');
|
||||||
|
}
|
||||||
// console.trace({ loadVertices: this.id, worker: this.inWorker, data });
|
// console.trace({ loadVertices: this.id, worker: this.inWorker, data });
|
||||||
let vertices,
|
let vertices,
|
||||||
autoscale = false;
|
autoscale = false;
|
||||||
|
|
@ -859,9 +865,9 @@ class Widget {
|
||||||
// find closest shadow above and use to speed up delta shadow gen
|
// find closest shadow above and use to speed up delta shadow gen
|
||||||
let zover = Object.keys(shadows).map(v => parseFloat(v)).filter(v => v > z);
|
let zover = Object.keys(shadows).map(v => parseFloat(v)).filter(v => v > z);
|
||||||
let minZabove = Math.min(Infinity, ...zover);
|
let minZabove = Math.min(Infinity, ...zover);
|
||||||
let shadow = this.#computeShadowAt(z, minZabove);
|
let shadow = this.#computeShadowAt(z - 0.01, minZabove);
|
||||||
if (minZabove < Infinity) {
|
if (minZabove < Infinity) {
|
||||||
shadow = POLY.union([...shadow, ...shadows[minZabove]], 0.001, true);
|
shadow = POLY.union([...shadow, ...shadows[minZabove]], 0.001, true, { wasm: false });
|
||||||
}
|
}
|
||||||
return shadows[z] = POLY.setZ(shadow, z);
|
return shadows[z] = POLY.setZ(shadow, z);
|
||||||
}
|
}
|
||||||
|
|
@ -953,7 +959,7 @@ class Widget {
|
||||||
.add(a[1].x, a[1].y, a[1].z)
|
.add(a[1].x, a[1].y, a[1].z)
|
||||||
.add(a[2].x, a[2].y, a[2].z);
|
.add(a[2].x, a[2].y, a[2].z);
|
||||||
});
|
});
|
||||||
polys = POLY.union(polys, 0, true);
|
polys = POLY.union(polys, 0, true, { wasm: false });
|
||||||
|
|
||||||
// for a more perfect union, pump shadows to merge very close lines
|
// for a more perfect union, pump shadows to merge very close lines
|
||||||
// todo: create clipper only version that avoids round trip thru geo classes
|
// todo: create clipper only version that avoids round trip thru geo classes
|
||||||
|
|
|
||||||
|
|
@ -246,8 +246,8 @@ export function createPopOps() {
|
||||||
leavez: UC.newInput(LANG.cr_lstz_s, { title: LANG.cr_lstz_l, convert: toFloat, bound: UC.bound(0, 10), units }),
|
leavez: UC.newInput(LANG.cr_lstz_s, { title: LANG.cr_lstz_l, convert: toFloat, bound: UC.bound(0, 10), units }),
|
||||||
sep: UC.newBlank({ class: "pop-sep" }),
|
sep: UC.newBlank({ class: "pop-sep" }),
|
||||||
all: UC.newBoolean(LANG.cr_clst_s, undefined, { title: LANG.cr_clst_l, show: () => !env.poppedRec.inside || env.poppedRec.all }),
|
all: UC.newBoolean(LANG.cr_clst_s, undefined, { title: LANG.cr_clst_l, show: () => !env.poppedRec.inside || env.poppedRec.all }),
|
||||||
inside: UC.newBoolean(LANG.cr_olin_s, undefined, { title: LANG.cr_olin_l, show: () => !env.poppedRec.all || env.poppedRec.inside }),
|
|
||||||
flats: UC.newBoolean(LANG.cr_clrf_s, undefined, { title: LANG.cr_clrf_l }),
|
flats: UC.newBoolean(LANG.cr_clrf_s, undefined, { title: LANG.cr_clrf_l }),
|
||||||
|
inside: UC.newBoolean(LANG.cr_olin_s, undefined, { title: LANG.cr_olin_l, show: () => !env.poppedRec.all || env.poppedRec.inside }),
|
||||||
omitthru: UC.newBoolean(LANG.co_omit_s, undefined, { title: LANG.co_omit_l }),
|
omitthru: UC.newBoolean(LANG.co_omit_s, undefined, { title: LANG.co_omit_l }),
|
||||||
sep: UC.newBlank({ class: "pop-sep" }),
|
sep: UC.newBlank({ class: "pop-sep" }),
|
||||||
exp: UC.newExpand("overrides"),
|
exp: UC.newExpand("overrides"),
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,13 @@ class OpArea extends CamOp {
|
||||||
for (let area of polys) {
|
for (let area of polys) {
|
||||||
let bounds = area.getBounds3D();
|
let bounds = area.getBounds3D();
|
||||||
let ts_off = toolDiam / 2 - ts_eps;
|
let ts_off = toolDiam / 2 - ts_eps;
|
||||||
|
let offopt = {
|
||||||
|
join: ClipperLib.JoinType.jtRound,
|
||||||
|
clean: true,
|
||||||
|
simple: false,
|
||||||
|
cleanDist: 10,
|
||||||
|
minArea: 0.01,
|
||||||
|
};
|
||||||
|
|
||||||
if (outline) {
|
if (outline) {
|
||||||
// remove inner voids when processing outline only
|
// remove inner voids when processing outline only
|
||||||
|
|
@ -143,8 +150,8 @@ class OpArea extends CamOp {
|
||||||
let layers = slice.output();
|
let layers = slice.output();
|
||||||
let shadow = await shadowAt(z);
|
let shadow = await shadowAt(z);
|
||||||
let tool_shadow = [
|
let tool_shadow = [
|
||||||
...POLY.offset(shadow, [ ts_off ], { count: 1, z }),
|
...POLY.offset(shadow, [ ts_off ], { count: 1, z, ...offopt }),
|
||||||
...POLY.offset(shadow, [ -ts_off ], { count: 1, z }),
|
...POLY.offset(shadow, [ -ts_off ], { count: 1, z, ...offopt }),
|
||||||
];
|
];
|
||||||
// for roughing/outline backward compatability
|
// for roughing/outline backward compatability
|
||||||
if (op.omitthru) {
|
if (op.omitthru) {
|
||||||
|
|
@ -156,7 +163,7 @@ class OpArea extends CamOp {
|
||||||
let firstOff = -(toolDiam / 2 + (op.leave_xy ?? 0));
|
let firstOff = -(toolDiam / 2 + (op.leave_xy ?? 0));
|
||||||
POLY.subtract([ area ], shadow, clip, undefined, undefined, 0);
|
POLY.subtract([ area ], shadow, clip, undefined, undefined, 0);
|
||||||
POLY.offset(clip, [ firstOff, -toolOver ], {
|
POLY.offset(clip, [ firstOff, -toolOver ], {
|
||||||
count: op.steps ?? 999, outs, flat: true, z: z - zMov, minArea: 0
|
count: op.steps ?? 999, outs, flat: true, z: z - zMov, ...offopt
|
||||||
});
|
});
|
||||||
// if we see no offsets, re-check the mesh bottom Z then exit
|
// if we see no offsets, re-check the mesh bottom Z then exit
|
||||||
if (outs.length === 0) {
|
if (outs.length === 0) {
|
||||||
|
|
@ -261,8 +268,8 @@ class OpArea extends CamOp {
|
||||||
slice.tool_shadow = [
|
slice.tool_shadow = [
|
||||||
area,
|
area,
|
||||||
...shadow,
|
...shadow,
|
||||||
...POLY.offset(shadow, [ ts_off ], { count: 1, z }),
|
...POLY.offset(shadow, [ ts_off ], { count: 1, z, ...offopt }),
|
||||||
...POLY.offset(shadow, [ -ts_off ], { count: 1, z }),
|
...POLY.offset(shadow, [ -ts_off ], { count: 1, z, ...offopt }),
|
||||||
];
|
];
|
||||||
// add tabs to travel boundaries
|
// add tabs to travel boundaries
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ class MeshTool {
|
||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeVertices(vertices, opt) {
|
||||||
|
return this.generateFaces(vertices, opt).unrolled();
|
||||||
|
}
|
||||||
|
|
||||||
getIndex() {
|
getIndex() {
|
||||||
if (!this.indexed) {
|
if (!this.indexed) {
|
||||||
throw "missing index";
|
throw "missing index";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue