From c8c789b723c0a26b600629bf677217d2f0f156de Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 13 Jan 2026 00:13:51 -0500 Subject: [PATCH] improve support clipping, change gap offset from number to boolean --- src/geo/polygons.js | 27 +++++++++++++++++++++------ src/kiri/app/conf/defaults.js | 2 +- src/kiri/mode/fdm/app/init-menu.js | 2 +- src/kiri/mode/fdm/work/slice.js | 29 +++++++++++++++++++++-------- web/kiri/lang/en.js | 2 +- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/geo/polygons.js b/src/geo/polygons.js index 5f06696f..649813eb 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -74,10 +74,23 @@ const POLYS = { union, unionFaces, xor, + verify }; 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) { for (let p of polys) { p.inner = undefined; @@ -136,7 +149,7 @@ export function fromClipperNode(tnode, z) { export function fromClipperTree(tnode, z, tops, parent, minarea) { let poly, polys = tops || [], - min = numOrDefault(minarea, 0.1); + min = minarea ?? 0.1; for (let child of tnode.m_Childs) { poly = fromClipperNode(child, z); @@ -150,7 +163,7 @@ export function fromClipperTree(tnode, z, tops, parent, minarea) { polys.push(poly); } 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 * @returns {Polygon[]} */ -export function trimTo(setA, setB) { +export function trimTo(setA, setB, opt = {}) { // 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; - util.doCombinations(setA, setB, {}, function(a, b) { - if (tmp = a.mask(b)) { + util.doCombinations(setA, setB, {}, (a, b) => { + if (tmp = a.mask(b, opt.nullEq, opt.minArea)) { out.appendAll(tmp); } }); diff --git a/src/kiri/app/conf/defaults.js b/src/kiri/app/conf/defaults.js index 0b9d6559..b4e6ed56 100644 --- a/src/kiri/app/conf/defaults.js +++ b/src/kiri/app/conf/defaults.js @@ -341,7 +341,7 @@ export const conf = { sliceSupportAngle: 50, sliceSupportDensity: 0.1, sliceSupportExtra: 0, - sliceSupportGap: 1, + sliceSupportGap: true, sliceSupportNozzle: 0, sliceSupportOffset: 1.0, sliceSupportOutline: true, diff --git a/src/kiri/mode/fdm/app/init-menu.js b/src/kiri/mode/fdm/app/init-menu.js index 262f84e7..1675037f 100644 --- a/src/kiri/mode/fdm/app/init-menu.js +++ b/src/kiri/mode/fdm/app/init-menu.js @@ -121,12 +121,12 @@ export function menu() { 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)}), 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)}), 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 }), separator: newBlank({ class:"set-sep", driven }), 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 }), separator: newBlank({ class:"set-sep", driven, show:manualSupport }), sliceSupportManual: newRow([ diff --git a/src/kiri/mode/fdm/work/slice.js b/src/kiri/mode/fdm/work/slice.js index 37978a8d..b5a29d55 100644 --- a/src/kiri/mode/fdm/work/slice.js +++ b/src/kiri/mode/fdm/work/slice.js @@ -540,6 +540,9 @@ export function sliceOne(settings, widget, onupdate, ondone) { if (manual && paint?.length) { let hpi = Math.PI/2; for (let slice of stack) { + if (!slice.up) { + continue; + } let polys = []; for (let rec of paint) { let { point, radius } = rec; @@ -550,13 +553,21 @@ export function sliceOne(settings, widget, onupdate, ondone) { 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 // 2. trim to area outside slice.clips - let minArea = lineWidth; + let minArea = lineWidth * lineWidth; let shadowSum; let length = stack.length; let count = 0; @@ -569,7 +580,7 @@ export function sliceOne(settings, widget, onupdate, ondone) { // perform accumulation top down for (let slice of stack.reverse()) { 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) { shadow = POLY.offset(shadow, process.sliceSupportExtra); } @@ -577,10 +588,10 @@ export function sliceOne(settings, widget, onupdate, ondone) { if (shadowSum) { 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) { let rem = []; - let clips = [ slice.clips ].filter(v => v).flat(); + let clips = [ slice.topPolys() ].filter(v => v).flat(); clips = POLY.union(clips, minArea, true); POLY.subtract(shadow, clips, rem, null, slice.z, minArea, { wasm: false }); shadow = rem; @@ -608,11 +619,13 @@ export function sliceOne(settings, widget, onupdate, ondone) { 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()) { let clips = [ - slice.up?.clips, - slice.down?.clips + slice.clips, + gaps ? slice.up?.clips : undefined, + gaps ? slice.down?.clips : undefined ].filter(v => v).flat(); if (clips.length) { let rem = []; diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 58546d42..a74666b9 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -472,7 +472,7 @@ self.lang['en-us'] = { sp_offs_s: "part offset", sp_offs_l: ["offset from part","in millimeters"], 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_l: ["unsupported span that causes","a new support to be generated","in millimeters"], sp_angl_s: "angle",