From 4d963cc06c6c54db74f5f9a1a437b0b203ea5819 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 6 Jan 2026 14:06:12 -0500 Subject: [PATCH] reimplement angle auto support, remove dead code, disable unused params --- src/kiri/app/conf/defaults.js | 2 +- src/kiri/core/widget.js | 8 +- src/kiri/mode/fdm/init-menu.js | 2 +- src/kiri/mode/fdm/post.js | 2 + src/kiri/mode/fdm/slice.js | 284 ++++++--------------------------- web/kiri/lang/en.js | 4 +- 6 files changed, 58 insertions(+), 244 deletions(-) diff --git a/src/kiri/app/conf/defaults.js b/src/kiri/app/conf/defaults.js index c178c642..dbdf5b7c 100644 --- a/src/kiri/app/conf/defaults.js +++ b/src/kiri/app/conf/defaults.js @@ -338,7 +338,7 @@ export const conf = { sliceSolidify: false, sliceSolidMinArea: 1, sliceSupportAngle: 50, - sliceSupportArea: 0.1, + // sliceSupportArea: 0.1, sliceSupportDensity: 0.1, // sliceSupportEnable: false, sliceSupportExtra: 0, diff --git a/src/kiri/core/widget.js b/src/kiri/core/widget.js index ff23f145..5d11b2df 100644 --- a/src/kiri/core/widget.js +++ b/src/kiri/core/widget.js @@ -711,6 +711,11 @@ class Widget { return this; } + /** + * @param {number} z height for shadow computation + * @param {number | undefined} pocket normal value to match faces + * @returns {Polygon[]} + */ async shadowAt(z, pocket) { let shadows = this.cache.shadows; if (!shadows) { @@ -762,7 +767,8 @@ class Widget { const b = new THREE.Vector3(geo[ip++], geo[ip++], geo[ip++]); const c = new THREE.Vector3(geo[ip++], geo[ip++], geo[ip++]); const n = THREE.computeFaceNormal(a, b, c); - if ((pocket && n.z > -0.001) || (!pocket && n.z < 0.001)) { + // todo: use pocket to match normal values when set + if ((pocket && n.z > -pocket) || (!pocket && n.z < 0.001)) { continue; } const minZ = Math.floor(Math.min(a.z, b.z, c.z)); diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index 9dc84e2f..f8622836 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -123,7 +123,7 @@ export function menu() { // sliceSupportSize: newInput(LANG.sp_size_s, {title:LANG.sp_size_l, convert:toFloat, bound:bound(1.0,200.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)}), - sliceSupportArea: newInput(LANG.sp_area_s, {title:LANG.sp_area_l, convert:toFloat, bound:bound(0.0,200.0)}), + // sliceSupportArea: newInput(LANG.sp_area_s, {title:LANG.sp_area_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)}), // sliceSupportGrow: newInput(LANG.sp_grow_s, {title:LANG.sp_grow_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 }), diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 299c189c..68ac94d2 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -23,6 +23,7 @@ export function slicePost(data, options) { const { compInner, compOuter, pump } = post_args; const { clipOffset, fillOffset, shellOffset } = post_args; + // pumping the shells will round sharp edges if (pump) { let nugroups = []; let pump = shellOffset / 10; @@ -86,6 +87,7 @@ export function slicePost(data, options) { useAssembly })); } + // create a fast clip offset polygon based on a simplified part outline data.clip = clipOffset ? POLY.offset(nutops.map(t => t.simple), clipOffset) : undefined; data.tops = nutops; delete data.groups; diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index b202d042..b5734d85 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -24,7 +24,7 @@ let tracker = util.pwait, infill: { check: 0x3322bb, face: 0x3322bb, line: 0x3322bb, opacity, lopacity, fat }, inset: { line: 0xaaaaaa, check: 0xaaaaaa, face: 0 }, shell: { check: 0x0077bb, face: 0x0077bb, line: 0x0077bb, opacity, lopacity, fat }, - support: { check: 0xaa5533, face: 0xaa5533, line: 0xaa5533, opacity, lopacity, fat }, + support: { check: 0xbbbb00, face: 0xbbbb00, line: 0xbbbb00, opacity, lopacity, fat }, part: { line: 0x333333, check: 0x333333 }, thin: { check: 0xbb8800, face: 0xbb8800, line: 0xbb8800, opacity, lopacity, fat }, }, @@ -239,8 +239,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) { compInner: sliceCompInner, compOuter: sliceCompOuter, shellOffset, - fillOffset, - clipOffset, + fillOffset, // distance: top inset for sparse/solid infill + clipOffset, // distance: create simpler clip offset for supports lineWidth, vaseMode, process, @@ -362,49 +362,31 @@ export function fdm_slice(settings, widget, onupdate, ondone) { return onSliceDone(slices); }).then(ondone); - // shadow used to clip supports in non-belt mode - async function doShadow(slices) { - if (widget.shadow) { - return; - } - let root = widget.group[0]; - if (root.shadow) { - widget.shadow = root.shadow; - return; - } - // console.log({ doShadow: widget, slices }); - // create shadow for clipping supports - let alltops = widget.group - .filter(w => !w.track.synth) // no supports in shadow - .map(w => w.slices).flat() - .map(s => s.tops).flat().map(t => t.simple); - let shadow = isConcurrent ? - await minions.union(alltops, 0.1) : - POLY.union(alltops, 0.1, true); - // expand shadow when requested (support clipping) - if (process.sliceSupportExtra) { - shadow = POLY.offset(shadow, process.sliceSupportExtra); - } - widget.shadow = root.shadow = POLY.setZ(shadow, 0); - // slices[0].output() - // .setLayer('shadow', { line: 0xff0000, check: 0xff0000 }) - // .addPolys(shadow); - } - async function onSliceDone(slices) { // new auto support demonstrator using cast shadow - if (false) { - let indices = slices.map(s => s.z);//.sort((a,b) => b-a); + if (process.sliceSupportType === 'automatic') { + let stack = slices.slice(); + let indices = stack.map(s => s.z); + let zAngNorm = Math.sin(process.sliceSupportAngle * Math.PI / 180); - await widget.computeShadowStack(indices, prog => {}, true); + await widget.computeShadowStack(indices, progress => {}, zAngNorm); - for (let slice of slices.slice().sort((a,b) => a.z - b.z)) { + if (process.sliceSupportTree) { + stack.sort((a,b) => a.z - b.z); // deltas only + } else { + stack.sort((a,b) => b.z - a.z); // accumulate deltas + } + + for (let slice of stack) { let shadow = await widget.shadowAt(slice.z, true); - // slice.supports = shadow; - slice.output() - .setLayer('shadow', 0xff0000) - .addPolys(shadow); + if (process.sliceSupportExtra) { + shadow = POLY.offset(shadow, process.sliceSupportExtra); + } + slice.supports = shadow; + // slice.output() + // .setLayer('shadow', 0xff0000) + // .addPolys(shadow); } } @@ -442,11 +424,6 @@ export function fdm_slice(settings, widget, onupdate, ondone) { slice.params = getRangeParameters(process, slice.index); } - // create shadow for non-belt supports - if (!isBelt && supportDensity && process.sliceSupportEnable) { - await doShadow(slices); - } - // calculate % complete and call onupdate() function doupdate(index, from, to, msg) { trackupdate(index / slices.length, from, to, msg); @@ -792,22 +769,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { } } - // auto support generation - if (!isBelt && supportDensity && process.sliceSupportEnable) { - doShadow(slices); - profileStart("support"); - let promises = []; - forSlices(0.7, 0.75, slice => { - promises.push(doSupport(slice, process, widget.shadow, { })); - }, "support"); - await tracker(promises, (i, t) => { - trackupdate(i / t, 0.75, 0.8); - }); - profileEnd(); - } - // fill all supports (auto and manual) - // if (!isBelt && supportDensity) { if (supportDensity) { profileStart("support-fill"); let promises = false && isConcurrent ? [] : undefined; @@ -815,10 +777,15 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let params = slice.params || process; let density = params.sliceSupportDensity; doSupportFill({ - promises, slice, lineWidth, density, - minArea: process.sliceSupportArea, isBelt, angle: process.sliceSupportFill, - outline: process.sliceSupportOutline !== false + density, + gap: process.sliceSupportGap, + isBelt, + lineWidth, + minArea: 0, + outline: process.sliceSupportOutline !== false, + promises, + slice, }); }, "support"); if (promises) { @@ -1457,14 +1424,11 @@ function doSolidsFill(slice, spacing, angle, minArea, fillQ) { } if (tofill.length > 0) { doFillArea(fillQ, tofill, angle, spacing, newfill); - // top.fill_lines_norm = {angle:angle,spacing:spacing}; } if (angfill.length > 0) { top.fill_lines_ang = {spacing:spacing,list:[],poly:[]}; for (let af of angfill) { doFillArea(fillQ, [af], af.fillang.angle + 45, spacing, newfill); - // top.fill_lines_ang.list.push(af.fillang.angle + 45); - // top.fill_lines_ang.poly.push(af.clone()); } } } @@ -1478,175 +1442,11 @@ function doFillArea(fillQ, polys, angle, spacing, output, minLen, maxLen) { } } -/** - * calculate external overhangs requiring support - */ -async function doSupport(slice, proc, shadow, opt = {}) { - let { minions } = self.kiri_worker, - maxBridge = proc.sliceSupportSpan || 5, - minArea = proc.supportMinArea || 0.1, - pillarSize = proc.sliceSupportSize, - offset = proc.sliceSupportOffset || 0, - gap = proc.sliceSupportGap, - size = (pillarSize || 1), - tops = slice.topPolys(), - trimTo = tops; - - let traces = POLY.flatten(slice.topShells().clone(true)), - fill = slice.topFill(), - points = [], - down = slice.down, - down_tops = down ? down.topPolys() : null, - down_traces = down ? POLY.flatten(down.topShells().clone(true)) : null; - - if (opt.exp && down_tops) { - let points = down_tops.map(p => p.deepLength).reduce((a,v)=>a+v); - if (points > 200) { - // use de-rez'd top shadow instead - down_tops = down.topSimples(); - // de-rez trace polys because it's not that important for supports - down_traces = down_traces.map(p => p.clean(true, undefined, config.clipper / 10)); - } - } - - // DEBUG code - let SDBG = false; - let cks = SDBG ? [] : undefined; - let pip = SDBG ? [] : undefined; - let pcl = SDBG ? [] : undefined; - - // check if point is supported by layer below - function checkPointSupport(point) { - if (SDBG) cks.push(point); // DEBUG - // skip points close to other support points - for (let i=0; i= maxBridge) { - let slope = p1.slopeTo(p2).factor(1/dist), - segs = Math.floor(dist / maxBridge) + 1, - seglen = dist / segs; - while (i < segs) { - checkPointSupport(p1.projectOnSlope(slope, i++ * seglen)); - } - } - if (poly) checkPointSupport(p2); - } - - let supports = []; - - // generate support polys from unsupported points - if (slice.down) (function() { - // check trace line support needs - traces.forEach(function(trace) { - trace.forEachSegment(function(p1, p2) { checkLineSupport(p1, p2, true) }); - }); - - // add offset solids to supports (or fill depending) - fill.forEachPair(function(p1,p2) { checkLineSupport(p1, p2, false) }); - - // skip the rest if no points or supports - if (!(points.length || supports.length)) return; - - let pillars = []; - - // for each point, create a bounding rectangle - points.forEach(function(point) { - pillars.push(newPolygon().centerRectangle(point, size/2, size/2)); - }); - - supports.appendAll(POLY.union(pillars, null, true, { wasm: false })); - // merge pillars and replace with convex hull of outer points (aka smoothing) - pillars = POLY.union(pillars, null, true, { wasm: false }).forEach(function(pillar) { - supports.push(newPolygon().createConvexHull(pillar.points)); - }); - })(); - - // DEBUG code - if (SDBG && down_traces) slice.output() - .setLayer('cks', { line: 0xee5533, check: 0xee5533 }) - .addPolys(cks.map(p => newPolygon().centerRectangle(p, 0.25, 0.25))) - .setLayer('pip', { line: 0xdd4422, check: 0xdd4422 }) - .addPolys(pip.map(p => newPolygon().centerRectangle(p, 0.4, 0.4))) - .setLayer('pcl', { line: 0xcc3311, check: 0xcc3311 }) - .addPolys(pcl.map(p => newPolygon().centerRectangle(p, 0.3, 0.3))) - .setLayer('pts', { line: 0xdd33dd, check: 0xdd33dd }) - .addPolys(points.map(p => newPolygon().centerRectangle(p, 0.8, 0.8))) - .setLayer('dtr', { line: 0x0, check: 0x0 }) - .addPolys(POLY.setZ(down_traces.clone(true),slice.z)); - ; - - if (supports.length === 0) { - return; - } - - // then union supports - if (supports.length > 10) { - supports = await minions.union(supports); - } else { - supports = POLY.union(supports, null, true, { wasm: false }); - } - - // clip to top polys - supports = POLY.trimTo(supports, shadow); - - let depth = 0; - while (down && supports.length > 0) { - down.supports = down.supports || []; - - let trimmed = [], culled = []; - - // culled = supports; - // clip supports to shell offsets - POLY.subtract(supports, down.topSimples(), trimmed, null, slice.z, minArea, { wasm: false }); - - // set depth hint on support polys for infill density - trimmed.forEach(function(trim) { - if (trim.area() < minArea) return; - culled.push(trim.setZ(down.z)); - }); - - // exit when no more support polys exist - if (culled.length === 0) break; - - // new bridge polys for next pass (skip first layer below) - if (depth >= gap) { - down.supports.appendAll(culled); - } - - supports = culled; - down = down.down; - depth++; - } - -} - function doSupportFill(args) { - const { promises, slice, lineWidth, density, minArea, isBelt, angle, outline } = args; + const { promises, slice, lineWidth, density, minArea, isBelt, angle, outline, gap } = args; let supports = slice.supports, - nsB = [], - nsC = [], - min = minArea || 0.1; + min = minArea || 0.1, + sub; if (!supports) return; @@ -1654,13 +1454,19 @@ function doSupportFill(args) { supports = POLY.setZ(POLY.union(supports, undefined, true, { wasm: false }), slice.z); // clip supports to slice clip offset (or shell if none) - POLY.subtract(supports, slice.clips, nsB, null, slice.z, min, { wasm: false }); - supports = nsB; + POLY.subtract(supports, slice.clips, sub = [], null, slice.z, min, { wasm: false }); + supports = sub; - // also trim to lower offsets, if they exist - if (slice.down && slice.down.clips) { - POLY.subtract(nsB, slice.down.clips, nsC, null, slice.z, min, { wasm: false }); - supports = nsC; + // trim to upper offsets, if they exist + if (gap && slice?.up?.clips) { + POLY.subtract(supports, slice.up.clips, sub = [], null, slice.z, min, { wasm: false }); + supports = sub; + } + + // trim to lower offsets, if they exist + if (gap && slice?.down?.clips) { + POLY.subtract(supports, slice.down.clips, sub = [], null, slice.z, min, { wasm: false }); + supports = sub; } if (supports) { diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 56a8f511..c477fda1 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -478,8 +478,8 @@ self.lang['en-us'] = { sp_span_l: ["unsupported span that causes","a new support to be generated","in millimeters"], sp_angl_s: "angle", sp_angl_l: ["max overhang angle before","supporting pillar is generated","normal values 45-60","belt values 0-10"], - sp_area_s: "min area", - sp_area_l: ["minimum area for","a support column","in millimeters","smaller values will","slow detection"], + // sp_area_s: "min area", + // sp_area_l: ["minimum area for","a support column","in millimeters","smaller values will","slow detection"], sp_xpnd_s: "expand", sp_xpnd_l: ["expand support area","beyond part boundary","in millimeters"], // sp_grow_s: "grow",