improve support clipping, change gap offset from number to boolean

This commit is contained in:
Stewart Allen 2026-01-13 00:13:51 -05:00
commit c8c789b723
5 changed files with 45 additions and 17 deletions

View file

@ -74,10 +74,23 @@ const POLYS = {
union, union,
unionFaces, unionFaces,
xor, xor,
verify
}; };
export { POLYS }; export { POLYS };
export function verify(polys) {
polys.forEach(p => {
if (!p.open && p.length < 3) console.trace('SHORT', p);
if (!p.open && p.area() < 0.001) console.trace('SMALL', p);
p.points.forEach(p => {
if (isNaN(p.x) || isNaN(p.y) ||isNaN(p.z)) {
console.trace('NaN', p);
}
});
});
}
export function outer(polys) { export function outer(polys) {
for (let p of polys) { for (let p of polys) {
p.inner = undefined; p.inner = undefined;
@ -136,7 +149,7 @@ export function fromClipperNode(tnode, z) {
export function fromClipperTree(tnode, z, tops, parent, minarea) { export function fromClipperTree(tnode, z, tops, parent, minarea) {
let poly, let poly,
polys = tops || [], polys = tops || [],
min = numOrDefault(minarea, 0.1); min = minarea ?? 0.1;
for (let child of tnode.m_Childs) { for (let child of tnode.m_Childs) {
poly = fromClipperNode(child, z); poly = fromClipperNode(child, z);
@ -150,7 +163,7 @@ export function fromClipperTree(tnode, z, tops, parent, minarea) {
polys.push(poly); polys.push(poly);
} }
if (child.m_Childs) { if (child.m_Childs) {
fromClipperTree(child, z, polys, parent ? null : poly, minarea); fromClipperTree(child, z, polys, parent ? null : poly, min);
} }
} }
@ -641,13 +654,15 @@ export function xor(set, z) {
* @param {Polygon[]} setB mask set * @param {Polygon[]} setB mask set
* @returns {Polygon[]} * @returns {Polygon[]}
*/ */
export function trimTo(setA, setB) { export function trimTo(setA, setB, opt = {}) {
// handle null/empty slices // handle null/empty slices
if (setA === setB || setA === null || setB === null) return null; if (setA === setB || setA === null || setB === null) {
return null;
}
let out = [], tmp; let out = [], tmp;
util.doCombinations(setA, setB, {}, function(a, b) { util.doCombinations(setA, setB, {}, (a, b) => {
if (tmp = a.mask(b)) { if (tmp = a.mask(b, opt.nullEq, opt.minArea)) {
out.appendAll(tmp); out.appendAll(tmp);
} }
}); });

View file

@ -341,7 +341,7 @@ export const conf = {
sliceSupportAngle: 50, sliceSupportAngle: 50,
sliceSupportDensity: 0.1, sliceSupportDensity: 0.1,
sliceSupportExtra: 0, sliceSupportExtra: 0,
sliceSupportGap: 1, sliceSupportGap: true,
sliceSupportNozzle: 0, sliceSupportNozzle: 0,
sliceSupportOffset: 1.0, sliceSupportOffset: 1.0,
sliceSupportOutline: true, sliceSupportOutline: true,

View file

@ -121,12 +121,12 @@ export function menu() {
separator: newBlank({ class:"set-sep", driven }), separator: newBlank({ class:"set-sep", driven }),
sliceSupportAngle: newInput(LANG.sp_angl_s, {title:LANG.sp_angl_l, convert:toFloat, bound:bound(0.0,90.0)}), sliceSupportAngle: newInput(LANG.sp_angl_s, {title:LANG.sp_angl_l, convert:toFloat, bound:bound(0.0,90.0)}),
sliceSupportDensity: newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:toFloat, bound:bound(0.0,1.0)}), sliceSupportDensity: newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:toFloat, bound:bound(0.0,1.0)}),
sliceSupportGap: newInput(LANG.sp_gaps_s, {title:LANG.sp_gaps_l, convert:toInt, bound:bound(0,5)}),
sliceSupportOffset: newInput(LANG.sp_offs_s, {title:LANG.sp_offs_l, convert:toFloat, bound:bound(0.0,200.0)}), sliceSupportOffset: newInput(LANG.sp_offs_s, {title:LANG.sp_offs_l, convert:toFloat, bound:bound(0.0,200.0)}),
sliceSupportExtra: newInput(LANG.sp_xpnd_s, {title:LANG.sp_xpnd_l, convert:toFloat, bound:bound(0.0,10.0)}), sliceSupportExtra: newInput(LANG.sp_xpnd_s, {title:LANG.sp_xpnd_l, convert:toFloat, bound:bound(0.0,10.0)}),
// sliceSupportSpan: newInput(LANG.sp_span_s, {title:LANG.sp_span_l, convert:toFloat, bound:bound(0.0,200.0), show:() => ui.sliceSupportEnable.checked }), // sliceSupportSpan: newInput(LANG.sp_span_s, {title:LANG.sp_span_l, convert:toFloat, bound:bound(0.0,200.0), show:() => ui.sliceSupportEnable.checked }),
separator: newBlank({ class:"set-sep", driven }), separator: newBlank({ class:"set-sep", driven }),
sliceSupportOutline: newBoolean(LANG.sp_outl_s, onBooleanClick, {title:LANG.sp_outl_l, xshow: () => !isTree() }), sliceSupportOutline: newBoolean(LANG.sp_outl_s, onBooleanClick, {title:LANG.sp_outl_l, xshow: () => !isTree() }),
sliceSupportGap: newBoolean(LANG.sp_gaps_s, onBooleanClick, {title:LANG.sp_gaps_l }),
sliceSupportTree: newBoolean(LANG.sp_tree_s, onBooleanClick, {title:LANG.sp_tree_l }), sliceSupportTree: newBoolean(LANG.sp_tree_s, onBooleanClick, {title:LANG.sp_tree_l }),
separator: newBlank({ class:"set-sep", driven, show:manualSupport }), separator: newBlank({ class:"set-sep", driven, show:manualSupport }),
sliceSupportManual: newRow([ sliceSupportManual: newRow([

View file

@ -540,6 +540,9 @@ export function sliceOne(settings, widget, onupdate, ondone) {
if (manual && paint?.length) { if (manual && paint?.length) {
let hpi = Math.PI/2; let hpi = Math.PI/2;
for (let slice of stack) { for (let slice of stack) {
if (!slice.up) {
continue;
}
let polys = []; let polys = [];
for (let rec of paint) { for (let rec of paint) {
let { point, radius } = rec; let { point, radius } = rec;
@ -550,13 +553,21 @@ export function sliceOne(settings, widget, onupdate, ondone) {
polys.push(newPolygon().centerCircle(point, radius, 10)); polys.push(newPolygon().centerCircle(point, radius, 10));
} }
} }
slice.shadow = POLY.union(polys, 0, true); // trim polys to part overhangs
let top = slice.up.topPolys();
let bot = slice.topPolys();
let bridge = [];
let propose = POLY.setZ(POLY.union(polys, 0, true), slice.z);
POLY.subtract(top, bot, bridge, undefined, slice.z, 0, { wasm: true });
propose = POLY.trimTo(propose, bridge, { minArea: 0 });
slice.shadow = propose;
// if (devel) slice.output().setLayer("over", 0x8844aa).addPolys(bridge);
} }
} }
// 1. accumulate / union shadow coverage top down // 1. accumulate / union shadow coverage top down
// 2. trim to area outside slice.clips // 2. trim to area outside slice.clips
let minArea = lineWidth; let minArea = lineWidth * lineWidth;
let shadowSum; let shadowSum;
let length = stack.length; let length = stack.length;
let count = 0; let count = 0;
@ -569,7 +580,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
// perform accumulation top down // perform accumulation top down
for (let slice of stack.reverse()) { for (let slice of stack.reverse()) {
let shadow = slice.shadow ?? []; let shadow = slice.shadow ?? [];
if (devel) slice.output().setLayer("shadow", 0xff0000).addPolys(shadow.clone(true)); if (devel) slice.output().setLayer("shadow", 0xff0000).addPolys(shadow);
if (process.sliceSupportExtra) { if (process.sliceSupportExtra) {
shadow = POLY.offset(shadow, process.sliceSupportExtra); shadow = POLY.offset(shadow, process.sliceSupportExtra);
} }
@ -577,10 +588,10 @@ export function sliceOne(settings, widget, onupdate, ondone) {
if (shadowSum) { if (shadowSum) {
shadow = POLY.union([...shadow, ...shadowSum], minArea, true); shadow = POLY.union([...shadow, ...shadowSum], minArea, true);
} }
// subtract slice.clips areas (widget boundaries) from shadow projection // subtract slice top areas (widget boundaries) from shadow projection
if (true) { if (true) {
let rem = []; let rem = [];
let clips = [ slice.clips ].filter(v => v).flat(); let clips = [ slice.topPolys() ].filter(v => v).flat();
clips = POLY.union(clips, minArea, true); clips = POLY.union(clips, minArea, true);
POLY.subtract(shadow, clips, rem, null, slice.z, minArea, { wasm: false }); POLY.subtract(shadow, clips, rem, null, slice.z, minArea, { wasm: false });
shadow = rem; shadow = rem;
@ -608,11 +619,13 @@ export function sliceOne(settings, widget, onupdate, ondone) {
trackupdate((++count/length), div[1].lo, div[1].hi, "support"); trackupdate((++count/length), div[1].lo, div[1].hi, "support");
} }
// clip shadow to layers above/below clip areas (for part separation) // trim using support part offset value
let gaps = process.sliceSupportGap;
for (let slice of stack.reverse()) { for (let slice of stack.reverse()) {
let clips = [ let clips = [
slice.up?.clips, slice.clips,
slice.down?.clips gaps ? slice.up?.clips : undefined,
gaps ? slice.down?.clips : undefined
].filter(v => v).flat(); ].filter(v => v).flat();
if (clips.length) { if (clips.length) {
let rem = []; let rem = [];

View file

@ -472,7 +472,7 @@ self.lang['en-us'] = {
sp_offs_s: "part offset", sp_offs_s: "part offset",
sp_offs_l: ["offset from part","in millimeters"], sp_offs_l: ["offset from part","in millimeters"],
sp_gaps_s: "layer gap", sp_gaps_s: "layer gap",
sp_gaps_l: ["number of layers","offset from part"], sp_gaps_l: ["add a layer gap between support and part"],
sp_span_s: "max span", sp_span_s: "max span",
sp_span_l: ["unsupported span that causes","a new support to be generated","in millimeters"], sp_span_l: ["unsupported span that causes","a new support to be generated","in millimeters"],
sp_angl_s: "angle", sp_angl_s: "angle",