From 6ede24eebb06fd52c783b04797fce615b9cee6e0 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 16 Sep 2025 11:13:30 -0400 Subject: [PATCH 01/49] clean up radii terms --- src/kiri/mode/fdm/post.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index a2222402..6b926c8f 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -169,16 +169,17 @@ function thin_type_3(params) { // produce trace from outside of poly inward no more than max inset let { noodle, remain } = top.poly.noodle(offsetN * count); - // top.shells = noodle; - top.gaps = last = remain; - let thin = top.thin_fill = []; let sparse = top.fill_sparse = []; let scale = 1000; - let minR = offsetN / 2; - let maxR = minR * 1.5; + let midR = offsetN * 0.5; + let minR = midR * 0.75; + let maxR = midR * 1.5; let minSpur = offsetN * 2; + // top.shells = noodle; + top.gaps = last = remain; + let showNoodle = false; let mergeChains = true; let interpolateShortSpur = true; @@ -341,14 +342,14 @@ function thin_type_3(params) { } else if (cr <= maxR * 2) { return [ cr / 2, cr / 2 ]; } else if (cr <= maxR * 3) { - let rem = cr - minR * 2; - if (rem < maxR) { - return [ minR, rem, minR ]; + if (cr - maxR * 2 >= minR) { + return [ maxR, cr - maxR * 2, maxR ]; } else { - return [ minR, rem / 2, rem / 2, minR ]; + return [ cr / 3, cr / 3, cr / 3 ]; } } else { - return [ minR, ...div(cr - minR * 2), minR ]; + let divs = (cr / midR) | 0; + return new Array(divs).fill(cr / divs); } } @@ -524,7 +525,7 @@ function thin_type_3(params) { thin.push(new Point(p1.x, p1.y, zi)); } // compute medial axis segment normal - let offs = len <= offsetN ? [ 0 ] : base.util.lerp(0, len, offsetN, true); + let offs = len <= midR ? [ 0 ] : base.util.lerp(0, len, midR, true); let step = 1 / offs.length; let dr = (p1.r - p0.r); let dx = (p1.x - p0.x); @@ -533,7 +534,7 @@ function thin_type_3(params) { let ndy = dy / len; segs.push({ p0, p1, offs, step, dr, dx, dy, ndx, ndy }); } - console.log({ segs }); + // compute chain subdivisions let slen = segs.length; for (let si=0; si Date: Tue, 16 Sep 2025 14:49:30 -0400 Subject: [PATCH 02/49] add micro offsetting for visualization --- src/kiri/mode/fdm/post.js | 65 +++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 6b926c8f..f91f6b14 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -169,19 +169,46 @@ function thin_type_3(params) { // produce trace from outside of poly inward no more than max inset let { noodle, remain } = top.poly.noodle(offsetN * count); + top.gaps = remain; + let thin = top.thin_fill = []; + let shells = top.shells; let sparse = top.fill_sparse = []; let scale = 1000; let midR = offsetN * 0.5; let minR = midR * 0.75; let maxR = midR * 1.5; - let minSpur = offsetN * 2; + let minSpur = offsetN * 1; - // top.shells = noodle; - top.gaps = last = remain; + if (false) { + let dtotl = 0; + let dstep = minR / 2; + let inset = POLY.expand(noodle, -dstep); + while (inset && inset.length) { + dtotl += dstep; + let ps = POLY.flatten(inset); + for (let poly of ps) { + for (let p of poly.segment(minR).points) { + let d = dtotl * 0+0.05; + // let z = dtotl * 10; + shells.append( newPolygon().centerCircle(p, d, 10).setZ(z) ); + } + } + POLY.setZ(inset, z); + shells.appendAll(inset); + if (dtotl >= maxR) { + break; + } + inset = POLY.expand(inset, -dstep); + } + } + + // shells.appendAll(noodle); + // return { last, gaps }; let showNoodle = false; let mergeChains = true; + let shortenAtNexus = false; let interpolateShortSpur = true; let showChainRawPoints = false; let showChainInterpPoints = true; @@ -332,7 +359,7 @@ function thin_type_3(params) { // render inset "noodle" if (showNoodle) { - top.shells.appendAll(noodle); + shells.appendAll(noodle); } // divide a segment/redius into 1 or more equal subsegments @@ -457,6 +484,7 @@ function thin_type_3(params) { } // shorten chains terminating at nexus by midR + if (shortenAtNexus) for (let rec of Object.values(nexus)) { let { point } = rec; rec.shorts = 0; @@ -477,7 +505,7 @@ function thin_type_3(params) { if (showNexuses) for (let rec of Object.values(nexus)) { let { point } = rec; - top.shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); + shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); } function renderPointNormal(pt, ndx, ndy) { @@ -490,14 +518,13 @@ function thin_type_3(params) { if (showMidRadii) { const Sx = pt.x + ndy * pt.r; const Sy = pt.y - ndx * pt.r; - const circ = top.shells; const pop = div(pt.r); let acc = 0; for (let r of pop) { const t = acc + r; const px = Sx + -ndy * t; const py = Sy + ndx * t; - circ.push(newPolygon().centerCircle({x:px, y:py}, r, 10)); + shells.push(newPolygon().centerCircle({x:px, y:py}, r, 10)); acc += r * 2; } } @@ -509,7 +536,7 @@ function thin_type_3(params) { // zi += 0.1; if (showChainRawPoints) for (let pt of chain) { - top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); + shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); } // draw medial axis chain let { closed, length } = chain; @@ -525,14 +552,14 @@ function thin_type_3(params) { thin.push(new Point(p1.x, p1.y, zi)); } // compute medial axis segment normal - let offs = len <= midR ? [ 0 ] : base.util.lerp(0, len, midR, true); - let step = 1 / offs.length; + let steps = Math.ceil(len / midR) + 1; + let step = 1 / steps; let dr = (p1.r - p0.r); let dx = (p1.x - p0.x); let dy = (p1.y - p0.y); let ndx = dx / len; let ndy = dy / len; - segs.push({ p0, p1, offs, step, dr, dx, dy, ndx, ndy }); + segs.push({ p0, p1, steps, step, dr, dx, dy, ndx, ndy }); } // compute chain subdivisions @@ -540,7 +567,7 @@ function thin_type_3(params) { for (let si=0; si Date: Wed, 17 Sep 2025 00:55:09 -0400 Subject: [PATCH 03/49] add more splitting / intersection visualizations --- src/geo/polygons.js | 2 +- src/kiri/mode/fdm/post.js | 158 ++++++++++++++++++++++++++++++-------- 2 files changed, 129 insertions(+), 31 deletions(-) diff --git a/src/geo/polygons.js b/src/geo/polygons.js index 6d4d8128..fcbd1303 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -645,7 +645,7 @@ export function offset(polys, dist, opts = {}) { for (let poly of polys) { // convert to clipper format poly = poly.toClipper(); - if (clean) poly = CleanPolygons(poly, config.clipperClean); + if (clean) poly = CleanPolygons(poly, opts.cleanDist ?? config.clipperClean); if (simple) poly = SimplifyPolygons(poly, fill); coff.AddPaths(poly, join, type); } diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index f91f6b14..96be3140 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -171,6 +171,23 @@ function thin_type_3(params) { top.gaps = remain; + let mergeChains = false; + let simplifyChain = false; + let shortenAtNexus = false; + let shortenEdgeSpur = false; + let intersectChains = true; + + let showNoodle = false; + let showInsetPoints = false; + let showChainRawPoints = false; + let showChainInterpPoints = true; + let showNuMedLine = false; + let showMedLine = true; + let showMedPoints = false; + let showMedNormals = false; + let showMedRadii = false; + let showNexuses = false; + let thin = top.thin_fill = []; let shells = top.shells; let sparse = top.fill_sparse = []; @@ -180,10 +197,16 @@ function thin_type_3(params) { let maxR = midR * 1.5; let minSpur = offsetN * 1; - if (false) { + let inset; + let dstep = minR / 4; + top.gaps = remain; + + if (true) { let dtotl = 0; - let dstep = minR / 2; - let inset = POLY.expand(noodle, -dstep); + inset = POLY.offset(noodle, -dstep, { + join: ClipperLib.JoinType.jtRound, + arc: (1/dstep) * 4 + }); while (inset && inset.length) { dtotl += dstep; let ps = POLY.flatten(inset); @@ -191,12 +214,13 @@ function thin_type_3(params) { for (let p of poly.segment(minR).points) { let d = dtotl * 0+0.05; // let z = dtotl * 10; + if (showInsetPoints) shells.append( newPolygon().centerCircle(p, d, 10).setZ(z) ); } } POLY.setZ(inset, z); shells.appendAll(inset); - if (dtotl >= maxR) { + if (true || dtotl >= maxR) { break; } inset = POLY.expand(inset, -dstep); @@ -206,18 +230,6 @@ function thin_type_3(params) { // shells.appendAll(noodle); // return { last, gaps }; - let showNoodle = false; - let mergeChains = true; - let shortenAtNexus = false; - let interpolateShortSpur = true; - let showChainRawPoints = false; - let showChainInterpPoints = true; - let showMidline = true; - let showMidPoints = false; - let showMidNormals = false; - let showMidRadii = true; - let showNexuses = false; - let pointMap = new Map(); let lineMap = new Map(); @@ -294,7 +306,7 @@ function thin_type_3(params) { // filter out lines where both points' radii are under the // minR threshold. shorten and fixup lines where minR falls // somewhere along the gradient between ends. - if (interpolateShortSpur) + if (shortenEdgeSpur) if (p0.r < minR || p1.r < minR) { if (p0.r > minR && len > minSpur) { // move p1 toward p0 until minR met @@ -451,9 +463,9 @@ function thin_type_3(params) { // mark closed chains // todo use exact point match by eliminating slice above // then later removing repeated points in the resulting chain from merges + if (mergeChains) for (let chain of chains) { chain.closed = (chain[0] === chain.peek()); - console.log(chain.closed ? 'closed' : 'open'); if (chain.closed) chain.pop(); } @@ -508,14 +520,39 @@ function thin_type_3(params) { shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); } + // Keep endpoints; drop interior points closer than R from the last kept point. + function dropClosePoints(chain, R) { + if (!chain || chain.length <= 2 || R <= 0) { + return chain; + } + let last = chain[0]; + let out = [ last ]; + let R2 = R * R; + for (let i = 1; i < chain.length - 1; i++) { + let dx = chain[i].x - last.x, + dy = chain[i].y - last.y; + if (dx * dx + dy * dy >= R2) { + out.push(chain[i]); + last = chain[i]; + } + } + out.push(chain[chain.length - 1]); + return out; + } + + // drop chain points < minR apart + if (simplifyChain) { + chains = chains.map(chain => dropClosePoints(chain, minR)); + } + function renderPointNormal(pt, ndx, ndy) { - if (showMidNormals) { + if (showMedNormals) { let xo = ndx * pt.r; let yo = ndy * pt.r; thin.push(new Point(pt.x + yo, pt.y - xo)); thin.push(new Point(pt.x - yo, pt.y + xo)); } - if (showMidRadii) { + if (showMedRadii) { const Sx = pt.x + ndy * pt.r; const Sy = pt.y - ndx * pt.r; const pop = div(pt.r); @@ -532,7 +569,10 @@ function thin_type_3(params) { // gather point offsets into shells let zi = z; + let nuchains = []; for (let chain of chains) { + let nuchain = []; + nuchains.push(nuchain); // zi += 0.1; if (showChainRawPoints) for (let pt of chain) { @@ -547,7 +587,7 @@ function thin_type_3(params) { let p1 = chain[(i+1) % length]; let len = pointDist(p0, p1); // medial axis segment - if (showMidline) { + if (showMedLine) { thin.push(new Point(p0.x, p0.y, zi)); thin.push(new Point(p1.x, p1.y, zi)); } @@ -571,23 +611,26 @@ function thin_type_3(params) { let first = si === 0; let last = si === slen - 1; let mid = !(first || last); + if (first || mid) { + nuchain.push(p0); + } // for length 2, first and last segment are the same - if (showMidPoints) { + if (showMedPoints) { if (first) { // todo: handle closed - shells.push(newPolygon().centerCircle(p0, 0.08, 10)); + shells.push(newPolygon().centerCircle(p0, p0.r/2, 10)); } if (mid) { // mid segments - shells.push(newPolygon().centerCircle(p0, 0.08, 10)); + shells.push(newPolygon().centerCircle(p0, p0.r/2, 10)); } if (last) { // todo: handle closed - shells.push(newPolygon().centerCircle(p0, 0.08, 10)); + shells.push(newPolygon().centerCircle(p0, p0.r/2, 10)); if (closed) { console.log('closed'); } else { - shells.push(newPolygon().centerCircle(p1, 0.08, 10)); + shells.push(newPolygon().centerCircle(p1, p1.r/2, 10)); } } } @@ -604,7 +647,6 @@ function thin_type_3(params) { } } // interpolate across the length of the chain segment - if (showChainInterpPoints) for (let indx = 1; indx < steps; indx++) { const inc = indx * step; const cr = p0.r + dr * inc; @@ -612,11 +654,67 @@ function thin_type_3(params) { const cy = p0.y + dy * inc; const xo = ndx * cr; const yo = ndy * cr; - if (showMidPoints) { - top.shells.push(newPolygon().centerCircle({ x:cx, y:cy }, 0.05, 10)); + if (showChainInterpPoints) { + if (showMedPoints) { + shells.push(newPolygon().centerCircle({ x:cx, y:cy }, cr/2, 10)); + } + renderPointNormal({ x:cx, y:cy, r:cr }, ndx, ndy); } - renderPointNormal({ x:cx, y:cy, r:cr }, ndx, ndy); + nuchain.push({ x:cx, y:cy, r:cr }); } + if (last) { + nuchain.push(p1); + } + } + if (showNuMedLine) { + for (let i=0; i { + let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; + let len = pointDist(p1, p2); + let dx = (p1.x - p2.x) / len; + let dy = (p1.y - p2.y) / len; + let np2 = { x: np1.x + dy * 10, y: np1.y - dx * 10 }; + let min = { dist: Infinity }; + for (let chain of nuchains) { + for (let i=0; i Date: Wed, 17 Sep 2025 01:21:31 -0400 Subject: [PATCH 04/49] add single (odd) wall claim system for median intercepts --- src/kiri/mode/fdm/post.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 96be3140..e1990c43 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -181,6 +181,7 @@ function thin_type_3(params) { let showInsetPoints = false; let showChainRawPoints = false; let showChainInterpPoints = true; + let showChainIntersect = false; let showNuMedLine = false; let showMedLine = true; let showMedPoints = false; @@ -192,9 +193,10 @@ function thin_type_3(params) { let shells = top.shells; let sparse = top.fill_sparse = []; let scale = 1000; - let midR = offsetN * 0.5; - let minR = midR * 0.75; - let maxR = midR * 1.5; + let midR = offsetN * 0.5; // nominal extrusion width + let minR = midR * 0.75; // smallest interior fill or single wall width + let maxR = midR * 1.5; // max double wall extrusion width + let maxF = midR * 2; // max interior extrusion width let minSpur = offsetN * 1; let inset; @@ -678,7 +680,6 @@ function thin_type_3(params) { // project inset segment normals onto closest chain if (intersectChains) { - console.log({ chains, nuchains }); let { intersect } = base.util; let { SEGINT } = base.key; inset = POLY.flatten(inset, []); @@ -701,14 +702,21 @@ function thin_type_3(params) { } } if (min.p1) { - thin.push(new Point(np1.x, np1.y)); - thin.push(new Point(min.x, min.y)); - let mr = Math.min(min.p1.r, min.p2.r); + let { p1, p2 } = min; + if (showChainIntersect) { + thin.push(new Point(np1.x, np1.y)); + thin.push(new Point(min.x, min.y)); + } + let mr = Math.min(p1.r, p2.r); if (mr < minR) return; let pop = div(mr); - // todo decide which side has priority to emit single wall - // same is true for odd midlines: 3, 5, etc - if (pop.length === 1) return; + // implement first-to-intersect claim system + // for odd wall counts so that they're not emitted twice + // todo extend to all odd counts + if (pop.length === 1 && p1.claimed) { + return; + } + p1.claimed = true; shells.push(newPolygon().centerCircle({ x: np1.x + dy * (pop[0] - dstep), y: np1.y - dx * (pop[0] - dstep) From ee2d6753798eeff2139ecad2d1d03caf2f51bee7 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 17 Sep 2025 01:33:59 -0400 Subject: [PATCH 05/49] maintain better nominal outer wall width --- src/kiri/mode/fdm/post.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index e1990c43..21fae9a5 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -383,10 +383,11 @@ function thin_type_3(params) { } else if (cr <= maxR * 2) { return [ cr / 2, cr / 2 ]; } else if (cr <= maxR * 3) { - if (cr - maxR * 2 >= minR) { - return [ maxR, cr - maxR * 2, maxR ]; - } else { + let rem = (cr - midR * 2); + if (rem < minR) { return [ cr / 3, cr / 3, cr / 3 ]; + } else { + return [ midR, rem, midR ]; } } else { let divs = (cr / midR) | 0; From 4b64c4b8b0bd355c40a355504c7381a1593bc27f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 17 Sep 2025 10:21:00 -0400 Subject: [PATCH 06/49] place single wall exactly on medial axis vs inter --- src/kiri/mode/fdm/post.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 21fae9a5..4dbb5117 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -718,10 +718,19 @@ function thin_type_3(params) { return; } p1.claimed = true; - shells.push(newPolygon().centerCircle({ - x: np1.x + dy * (pop[0] - dstep), - y: np1.y - dx * (pop[0] - dstep) - }, pop[0], 10)); + if (pop.length === 1) { + // for single wall place it exactly on medial axis + shells.push(newPolygon().centerCircle({ + x: min.x, + y: min.y + }, mr, 12)); + } else { + // otherwise use divisions + shells.push(newPolygon().centerCircle({ + x: np1.x + dy * (pop[0] - dstep), + y: np1.y - dx * (pop[0] - dstep) + }, pop[0], 12)); + } } }); } From 2c8925002b85a741ae736b66d5789db1053102c6 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 17 Sep 2025 17:13:34 -0400 Subject: [PATCH 07/49] calculate new inset after first pass extrusion. possible to use new inset for another pass vs recycling medial axis --- src/kiri/mode/fdm/post.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 4dbb5117..95da06cb 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -178,6 +178,8 @@ function thin_type_3(params) { let intersectChains = true; let showNoodle = false; + let showExtrusion = true; + let showExtrudeInset = false; let showInsetPoints = false; let showChainRawPoints = false; let showChainInterpPoints = true; @@ -685,6 +687,7 @@ function thin_type_3(params) { let { SEGINT } = base.key; inset = POLY.flatten(inset, []); for (let poly of inset) { + let nupoly = new Polygon(); poly.segment(minR).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); @@ -711,28 +714,41 @@ function thin_type_3(params) { let mr = Math.min(p1.r, p2.r); if (mr < minR) return; let pop = div(mr); + let odd = (pop.length % 2 === 1); + let len = Math.ceil(pop.length / 2); + if (odd && p1.claimed) len--; // implement first-to-intersect claim system // for odd wall counts so that they're not emitted twice // todo extend to all odd counts - if (pop.length === 1 && p1.claimed) { + if (len === 0) { return; } - p1.claimed = true; if (pop.length === 1) { + nupoly.push(new Point(min.x, min.y)); // for single wall place it exactly on medial axis + if (showExtrusion) shells.push(newPolygon().centerCircle({ x: min.x, y: min.y }, mr, 12)); } else { + let off = -dstep; // otherwise use divisions - shells.push(newPolygon().centerCircle({ - x: np1.x + dy * (pop[0] - dstep), - y: np1.y - dx * (pop[0] - dstep) - }, pop[0], 12)); + for (let i=0; i<1; i++) { + off += (pop[i] * (i ? 2 : 1)); + if (showExtrusion) + shells.push(newPolygon().centerCircle({ + x: np1.x + dy * off, + y: np1.y - dx * off + }, pop[0], 12)); + } + off += dstep / 2; + nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2)); } + p1.claimed = true; } }); + if (showExtrudeInset) shells.push(nupoly); } } From a2a5e4d457b0877f618eb43ef28764beb1859077 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 17 Sep 2025 19:05:30 -0400 Subject: [PATCH 08/49] another optimization to maintain nominal wall thickness --- src/kiri/mode/fdm/post.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 95da06cb..52631439 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -383,17 +383,21 @@ function thin_type_3(params) { if (cr <= maxR) { return [ cr ]; } else if (cr <= maxR * 2) { - return [ cr / 2, cr / 2 ]; + let rem = (cr - midR * 2); + if (rem < minR) { + return [ cr / 2, cr / 2 ]; + } + return [ midR, rem, midR ]; } else if (cr <= maxR * 3) { let rem = (cr - midR * 2); if (rem < minR) { return [ cr / 3, cr / 3, cr / 3 ]; - } else { - return [ midR, rem, midR ]; } + return [ midR, rem, midR ]; } else { - let divs = (cr / midR) | 0; - return new Array(divs).fill(cr / divs); + return [ midR, cr - midR * 2, midR ]; + // let divs = (cr / midR) | 0; + // return new Array(divs).fill(cr / divs); } } From 7b2e723fbaeb3ebb19f89021aa745974b65f5a6e Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 18 Sep 2025 09:59:12 -0400 Subject: [PATCH 09/49] clean and simplify inset extrude poly --- src/kiri/mode/fdm/post.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 52631439..415e9633 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -185,7 +185,7 @@ function thin_type_3(params) { let showChainInterpPoints = true; let showChainIntersect = false; let showNuMedLine = false; - let showMedLine = true; + let showMedLine = false; let showMedPoints = false; let showMedNormals = false; let showMedRadii = false; @@ -752,7 +752,9 @@ function thin_type_3(params) { p1.claimed = true; } }); - if (showExtrudeInset) shells.push(nupoly); + if (showExtrudeInset) { + shells.push(...nupoly.clean().simplify()); + } } } From 43a083a93a38f412403292951d91229b2e6f70b8 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 18 Sep 2025 14:42:41 -0400 Subject: [PATCH 10/49] fix ray/line. add max inset visalization --- src/geo/base.js | 12 +++--- src/geo/polygon.js | 8 +--- src/geo/polygons.js | 1 + src/kiri/mode/fdm/post.js | 85 +++++++++++++++++++++++++++++++-------- 4 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/geo/base.js b/src/geo/base.js index d7ea18bf..5406d5ab 100644 --- a/src/geo/base.js +++ b/src/geo/base.js @@ -91,11 +91,11 @@ function area2(p1, p2, p3) { } function isCloseTo(v1, v2, dist) { - return Math.abs(v1 - v2) <= (dist || base.config.precision_merge); + return Math.abs(v1 - v2) <= (dist ?? base.config.precision_merge); } -function inCloseRange(val, min, max) { - return (isCloseTo(val, min) || val >= min) && (isCloseTo(val, max) || val <= max); +function inCloseRange(val, min, max, precisiom) { + return (isCloseTo(val, min, precisiom) || val >= min) && (isCloseTo(val, max, precisiom) || val <= max); } /** @@ -232,7 +232,7 @@ function intersect(p1, p2, p3, p4, test, parallelok) { /** * used by {@link rayIntersect} and {@link Polygon.trace} */ -function intersectRayLine(ro, s1, p1, p2, infinite) { +function intersectRayLine(ro, s1, p1, p2) { let keys = base.key, p1x = ro.x, p1y = ro.y, @@ -259,11 +259,11 @@ function intersectRayLine(ro, s1, p1, p2, infinite) { a = n1 / d; b = n2 / d; - if (infinite || (inCloseRange(b, 0, 1) && a >= 0)) { + if (inCloseRange(b, 0, 1, 0.00) && a >= 0) { let ip = newPoint( p1x + (a * s1x), p1y + (a * s1y), - p2.z || ro.z, + p2.z ?? ro.z, keys.NONE ); ip.dist = a; diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 846515f9..e5a3d9a6 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -2042,13 +2042,9 @@ export class Polygon { // use expand / deflate technique instead if (opt.pump) { - let p2 = POLY.offset([this], opt.pump, { - z - }); + let p2 = POLY.offset([this], opt.pump, { z }); if (p2) { - p2 = POLY.offset(p2, -opt.pump, { - z - }); + p2 = POLY.offset(p2, -opt.pump, { z }); return p2; } return null; diff --git a/src/geo/polygons.js b/src/geo/polygons.js index fcbd1303..1d5c5212 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -871,6 +871,7 @@ export function fillArea(polys, angle, spacing, output, minLen, maxLen) { * @returns {Point[]} */ export function rayIntersect(start, slope, polygons, for_fill) { + throw "deprecated"; let i = 0, flat = [], points = [], diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 415e9633..650eb3c6 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -175,7 +175,8 @@ function thin_type_3(params) { let simplifyChain = false; let shortenAtNexus = false; let shortenEdgeSpur = false; - let intersectChains = true; + let intersectChains = false; + let maxInsetBypass = true; let showNoodle = false; let showExtrusion = true; @@ -185,7 +186,7 @@ function thin_type_3(params) { let showChainInterpPoints = true; let showChainIntersect = false; let showNuMedLine = false; - let showMedLine = false; + let showMedLine = true; let showMedPoints = false; let showMedNormals = false; let showMedRadii = false; @@ -201,38 +202,90 @@ function thin_type_3(params) { let maxF = midR * 2; // max interior extrusion width let minSpur = offsetN * 1; + let insets = []; let inset; let dstep = minR / 4; + let dtotl = 0; top.gaps = remain; - if (true) { - let dtotl = 0; + if (maxInsetBypass || intersectChains) { inset = POLY.offset(noodle, -dstep, { join: ClipperLib.JoinType.jtRound, arc: (1/dstep) * 4 }); while (inset && inset.length) { dtotl += dstep; - let ps = POLY.flatten(inset); - for (let poly of ps) { - for (let p of poly.segment(minR).points) { - let d = dtotl * 0+0.05; - // let z = dtotl * 10; - if (showInsetPoints) - shells.append( newPolygon().centerCircle(p, d, 10).setZ(z) ); - } + for (let p of POLY.flatten(inset, [])) { + p.dtotl = dtotl; } + insets.push(inset); + let ps = POLY.flatten(inset); POLY.setZ(inset, z); shells.appendAll(inset); - if (true || dtotl >= maxR) { + if (intersectChains) { break; } - inset = POLY.expand(inset, -dstep); + // if (dtotl >= maxR * 3) { + // break; + // } + inset = POLY.offset(inset, -dstep, { + // join: ClipperLib.JoinType.jtRound, + // arc: (1/dstep) * 4 + minArea: 0 + }); } } - // shells.appendAll(noodle); - // return { last, gaps }; + // project from first inset and find greated offset intersection + if (maxInsetBypass) { + // shells.appendAll(noodle); + // return { last, gaps }; + let { intersectRayLine } = base.util; + let tests = insets.slice(1); + let source = POLY.flatten(insets[0], []); + console.log({ source, tests }); + for (let poly of source) { + // segment the smallest inset and test against all other insets + poly.segment(minR).forEachSegment((p1, p2) => { + let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; + let len = pointDist(p1, p2); + let dx = (p1.x - p2.x) / len; + let dy = (p1.y - p2.y) / len; + let np2 = { x: np1.x + dy * 10, y: np1.y - dx * 10 }; + let max = { dtotl: poly.dtotl, dist: Infinity, maxd: Infinity }; + let term = false; + outer: for (let test of tests) { + for (let tpoly of POLY.flatten(test, [])) { + tpoly.forEachSegment((tp1, tp2) => { + let int = intersectRayLine(np1, { dx: dy, dy: -dx }, tp1, tp2); + if (int && int.dist > int.p1.poly.dtotl) { + return; + } + if (int && int.dist <= max.maxd && int.p1.poly.dtotl > max.dtotl) { + max.dtotl = int.p1.poly.dtotl; + max.dist = int.dist; + max.int = int; + } else if (int && int.dist <= max.maxd && int.p1.poly.dtotl === max.dtotl && int.dist < max.dist) { + max.dtotl = int.p1.poly.dtotl; + max.dist = int.dist; + max.int = int; + } + }); + if (term) { + break outer; + } + } + } + if (max.int) { + thin.push(new Point(np1.x, np1.y, z)); + thin.push(new Point(max.int.x, max.int.y, z)); + } + }); + } + + shells.appendAll(noodle); + return { last, gaps }; + } let pointMap = new Map(); let lineMap = new Map(); From afcacc89b3c88f1188cb9ff2a34822af9f1f50aa Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 18 Sep 2025 16:39:28 -0400 Subject: [PATCH 11/49] use intersect offset instead of approximate medial axis radius --- src/kiri/mode/fdm/post.js | 52 ++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 650eb3c6..44f40217 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -175,18 +175,18 @@ function thin_type_3(params) { let simplifyChain = false; let shortenAtNexus = false; let shortenEdgeSpur = false; - let intersectChains = false; - let maxInsetBypass = true; + let intersectChains = true; + let maxInsetBypass = false; let showNoodle = false; + let showMedLine = false; + let showInset = false; let showExtrusion = true; let showExtrudeInset = false; - let showInsetPoints = false; let showChainRawPoints = false; let showChainInterpPoints = true; let showChainIntersect = false; let showNuMedLine = false; - let showMedLine = true; let showMedPoints = false; let showMedNormals = false; let showMedRadii = false; @@ -221,16 +221,13 @@ function thin_type_3(params) { insets.push(inset); let ps = POLY.flatten(inset); POLY.setZ(inset, z); - shells.appendAll(inset); + if (showInset) { + shells.appendAll(inset); + } if (intersectChains) { break; } - // if (dtotl >= maxR * 3) { - // break; - // } inset = POLY.offset(inset, -dstep, { - // join: ClipperLib.JoinType.jtRound, - // arc: (1/dstep) * 4 minArea: 0 }); } @@ -238,8 +235,6 @@ function thin_type_3(params) { // project from first inset and find greated offset intersection if (maxInsetBypass) { - // shells.appendAll(noodle); - // return { last, gaps }; let { intersectRayLine } = base.util; let tests = insets.slice(1); let source = POLY.flatten(insets[0], []); @@ -739,12 +734,15 @@ function thin_type_3(params) { } // project inset segment normals onto closest chain - if (intersectChains) { - let { intersect } = base.util; - let { SEGINT } = base.key; - inset = POLY.flatten(inset, []); - for (let poly of inset) { - let nupoly = new Polygon(); + if (intersectChains) for (let insetp of inset) { + let { intersectRayLine } = base.util; + let parent = new Polygon(); + let inner = []; + for (let poly of insetp.flattenTo([])) { + let nupoly = poly.parent ? new Polygon() : parent; + if (poly.parent) { + inner.push(nupoly); + } poly.segment(minR).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); @@ -756,8 +754,8 @@ function thin_type_3(params) { for (let i=0; i Date: Thu, 18 Sep 2025 18:28:53 -0400 Subject: [PATCH 12/49] skip chains and use medial axis segments directly --- src/kiri/mode/fdm/post.js | 40 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 44f40217..06ca29c1 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -186,7 +186,7 @@ function thin_type_3(params) { let showChainRawPoints = false; let showChainInterpPoints = true; let showChainIntersect = false; - let showNuMedLine = false; + let showNuMedLine = true; let showMedPoints = false; let showMedNormals = false; let showMedRadii = false; @@ -627,6 +627,7 @@ function thin_type_3(params) { // gather point offsets into shells let zi = z; let nuchains = []; + let nusegs = []; for (let chain of chains) { let nuchain = []; nuchains.push(nuchain); @@ -727,6 +728,7 @@ function thin_type_3(params) { for (let i=0; i Date: Thu, 18 Sep 2025 19:49:06 -0400 Subject: [PATCH 13/49] add alt seg generator. prepare to drop chains --- src/kiri/mode/fdm/post.js | 295 ++++---------------------------------- 1 file changed, 24 insertions(+), 271 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 06ca29c1..3555c3c9 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -171,12 +171,9 @@ function thin_type_3(params) { top.gaps = remain; - let mergeChains = false; - let simplifyChain = false; - let shortenAtNexus = false; - let shortenEdgeSpur = false; let intersectChains = true; let maxInsetBypass = false; + let altNuSegs = true; let showNoodle = false; let showMedLine = false; @@ -184,13 +181,8 @@ function thin_type_3(params) { let showExtrusion = true; let showExtrudeInset = false; let showChainRawPoints = false; - let showChainInterpPoints = true; let showChainIntersect = false; - let showNuMedLine = true; - let showMedPoints = false; - let showMedNormals = false; - let showMedRadii = false; - let showNexuses = false; + let showNuMedLine = false; let thin = top.thin_fill = []; let shells = top.shells; @@ -200,7 +192,6 @@ function thin_type_3(params) { let minR = midR * 0.75; // smallest interior fill or single wall width let maxR = midR * 1.5; // max double wall extrusion width let maxF = midR * 2; // max interior extrusion width - let minSpur = offsetN * 1; let insets = []; let inset; @@ -333,6 +324,7 @@ function thin_type_3(params) { } let chains = []; + let nusegs = []; // for each standalone noodle, construct medial axis // map medial axis points into de-duplicated segments @@ -355,37 +347,22 @@ function thin_type_3(params) { for (let seg of lineMap.values()) { let { p0, p1 } = seg; let len = seg.len = pointDist(p0, p1); - // filter out lines where both points' radii are under the - // minR threshold. shorten and fixup lines where minR falls - // somewhere along the gradient between ends. - if (shortenEdgeSpur) - if (p0.r < minR || p1.r < minR) { - if (p0.r > minR && len > minSpur) { - // move p1 toward p0 until minR met - let pct = (minR - p1.r) / (p0.r - p1.r); - let dx = p1.x - p0.x; - let dy = p1.y - p0.y; - p1.x -= dx * pct; - p1.y -= dy * pct; - p1.r = minR; - pointMap.delete(p1.key); - } else if (p1.r > minR && len > minSpur) { - // move p0 toward p1 until minR met - let pct = (minR - p0.r) / (p1.r - p0.r); - let dx = p0.x - p1.x; - let dy = p0.y - p1.y; - p0.x -= dx * pct; - p0.y -= dy * pct; - p0.r = minR; - pointMap.delete(p0.key); + if (altNuSegs) { + // add to nusegs + if (len <= minR) { + nusegs.push({ p0, p1 }); } else { - // both points eliminated - pointMap.delete(p0.key); - pointMap.delete(p1.key); - lineMap.delete(seg.key); - continue; + let steps = Math.ceil(len / minR) + 1; + let dx = (p1.x - p0.x) / steps; + let dy = (p1.y - p0.y) / steps; + for (let i=0; i c.total >= minR); - chains.sort((c1,c2) => c2.total - c1.total); - - function merge1chain() { - let clen = chains.length; - for (let i=0; i chain); - - // filter dup points from connecting ends - chains = chains.map(chain => chain.filter((v, i) => i === 0 || v !== chain[i - 1])); - - // mark closed chains - // todo use exact point match by eliminating slice above - // then later removing repeated points in the resulting chain from merges - if (mergeChains) - for (let chain of chains) { - chain.closed = (chain[0] === chain.peek()); - if (chain.closed) chain.pop(); - } - - // shorten chain length by minR distance by popping or moving points - function shorten(chain) { - for (let rem=0, i=chain.length-1; i>=1 && rem < minR; i--) { - let c1 = chain[i]; - let c2 = chain[i-1]; - let d = pointDist(c1,c2); - if (rem + d <= minR) { - chain.pop(); - rem += d; - } else { - let diff = minR - rem; - // move c1 toward c2 - let pct = diff / d; - let dx = c1.x - c2.x; - let dy = c1.y - c2.y; - chain[i] = { - x: c1.x - dx * pct, - y: c1.y - dy * pct, - r: c1.r - }; - rem = 0; - break; - } - } - } - - // shorten chains terminating at nexus by midR - if (shortenAtNexus) - for (let rec of Object.values(nexus)) { - let { point } = rec; - rec.shorts = 0; - if (rec.chains.length < 3) continue; - for (let chain of chains.filter(c => !c.closed)) { - if (pointDist(chain[0], point) < minR) { - chain.reverse(); - shorten(chain); - chain.reverse(); - rec.shorts++; - } else if (chain.peek() === point) { - shorten(chain); - rec.shorts++; - } - } - } - - if (showNexuses) - for (let rec of Object.values(nexus)) { - let { point } = rec; - shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); - } - - // Keep endpoints; drop interior points closer than R from the last kept point. - function dropClosePoints(chain, R) { - if (!chain || chain.length <= 2 || R <= 0) { - return chain; - } - let last = chain[0]; - let out = [ last ]; - let R2 = R * R; - for (let i = 1; i < chain.length - 1; i++) { - let dx = chain[i].x - last.x, - dy = chain[i].y - last.y; - if (dx * dx + dy * dy >= R2) { - out.push(chain[i]); - last = chain[i]; - } - } - out.push(chain[chain.length - 1]); - return out; - } - - // drop chain points < minR apart - if (simplifyChain) { - chains = chains.map(chain => dropClosePoints(chain, minR)); - } - - function renderPointNormal(pt, ndx, ndy) { - if (showMedNormals) { - let xo = ndx * pt.r; - let yo = ndy * pt.r; - thin.push(new Point(pt.x + yo, pt.y - xo)); - thin.push(new Point(pt.x - yo, pt.y + xo)); - } - if (showMedRadii) { - const Sx = pt.x + ndy * pt.r; - const Sy = pt.y - ndx * pt.r; - const pop = div(pt.r); - let acc = 0; - for (let r of pop) { - const t = acc + r; - const px = Sx + -ndy * t; - const py = Sy + ndx * t; - shells.push(newPolygon().centerCircle({x:px, y:py}, r, 10)); - acc += r * 2; - } - } -} + // chains = chains.filter(c => c.total >= minR); // gather point offsets into shells let zi = z; let nuchains = []; - let nusegs = []; + if (!altNuSegs) for (let chain of chains) { let nuchain = []; nuchains.push(nuchain); @@ -672,63 +473,25 @@ function thin_type_3(params) { if (first || mid) { nuchain.push(p0); } - // for length 2, first and last segment are the same - if (showMedPoints) { - if (first) { - // todo: handle closed - shells.push(newPolygon().centerCircle(p0, p0.r/2, 10)); - } - if (mid) { - // mid segments - shells.push(newPolygon().centerCircle(p0, p0.r/2, 10)); - } - if (last) { - // todo: handle closed - shells.push(newPolygon().centerCircle(p0, p0.r/2, 10)); - if (closed) { - console.log('closed'); - } else { - shells.push(newPolygon().centerCircle(p1, p1.r/2, 10)); - } - } - } - { - // todo: handle closed - if (first) { - renderPointNormal(p0, ndx, ndy); - } - if (mid) { - renderPointNormal(p0, ((ndx + segp.ndx)/2), ((ndy + segp.ndy)/2)); - } - if (last) { - renderPointNormal(p1, ndx, ndy); - } - } // interpolate across the length of the chain segment for (let indx = 1; indx < steps; indx++) { const inc = indx * step; const cr = p0.r + dr * inc; const cx = p0.x + dx * inc; const cy = p0.y + dy * inc; - const xo = ndx * cr; - const yo = ndy * cr; - if (showChainInterpPoints) { - if (showMedPoints) { - shells.push(newPolygon().centerCircle({ x:cx, y:cy }, cr/2, 10)); - } - renderPointNormal({ x:cx, y:cy, r:cr }, ndx, ndy); - } nuchain.push({ x:cx, y:cy, r:cr }); } if (last) { nuchain.push(p1); } } - if (showNuMedLine) { - for (let i=0; i Date: Thu, 18 Sep 2025 21:34:44 -0400 Subject: [PATCH 14/49] extract and somewhat generalize trace_noodle --- src/kiri/mode/fdm/post.js | 263 +++++++++++--------------------------- 1 file changed, 75 insertions(+), 188 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 3555c3c9..1f3cbb0d 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -167,69 +167,78 @@ function thin_type_3(params) { let { z, top, count, offsetN, last, gaps } = params; // produce trace from outside of poly inward no more than max inset - let { noodle, remain } = top.poly.noodle(offsetN * count); + let noodleWidth = offsetN * count; + let { noodle, remain } = top.poly.noodle(noodleWidth); top.gaps = remain; - let intersectChains = true; - let maxInsetBypass = false; - let altNuSegs = true; - - let showNoodle = false; - let showMedLine = false; - let showInset = false; - let showExtrusion = true; - let showExtrudeInset = false; - let showChainRawPoints = false; - let showChainIntersect = false; - let showNuMedLine = false; + // render inset "noodle" + // shells.appendAll(noodle); let thin = top.thin_fill = []; let shells = top.shells; let sparse = top.fill_sparse = []; - let scale = 1000; + let midR = offsetN * 0.5; // nominal extrusion width let minR = midR * 0.75; // smallest interior fill or single wall width let maxR = midR * 1.5; // max double wall extrusion width let maxF = midR * 2; // max interior extrusion width + let { lines, polys } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, { + brute: false, + showExtrusion: true, + showExtrudeInset: true, + showChainIntersect: false + }); + + console.log({ lines, polys }); + + POLY.setZ([...lines, ...polys], z); + thin.push(...lines); + shells.push(...polys); + + return { last, gaps }; +} + +// trace a single extrusion line around the inside of the noodle +function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { + brute, + showExtrusion, + showExtrudeInset, + showChainIntersect, +}) { + let scale = 1000; + + let polys = []; + let lines = []; let insets = []; - let inset; let dstep = minR / 4; let dtotl = 0; - top.gaps = remain; - if (maxInsetBypass || intersectChains) { - inset = POLY.offset(noodle, -dstep, { - join: ClipperLib.JoinType.jtRound, - arc: (1/dstep) * 4 - }); - while (inset && inset.length) { - dtotl += dstep; - for (let p of POLY.flatten(inset, [])) { - p.dtotl = dtotl; - } - insets.push(inset); - let ps = POLY.flatten(inset); - POLY.setZ(inset, z); - if (showInset) { - shells.appendAll(inset); - } - if (intersectChains) { - break; - } - inset = POLY.offset(inset, -dstep, { - minArea: 0 - }); + let inset = POLY.offset(noodle, -dstep, { + join: ClipperLib.JoinType.jtRound, + arc: (1/dstep) * 4 + }); + while (inset && inset.length) { + dtotl += dstep; + for (let p of POLY.flatten(inset, [])) { + p.dtotl = dtotl; } + insets.push(inset); + polys.appendAll(inset); + if (!brute) { + break; + } + inset = POLY.offset(inset, -dstep, { + minArea: 0 + }); } // project from first inset and find greated offset intersection - if (maxInsetBypass) { + if (brute) { let { intersectRayLine } = base.util; let tests = insets.slice(1); let source = POLY.flatten(insets[0], []); - console.log({ source, tests }); for (let poly of source) { // segment the smallest inset and test against all other insets poly.segment(minR).forEachSegment((p1, p2) => { @@ -263,14 +272,12 @@ function thin_type_3(params) { } } if (max.int) { - thin.push(new Point(np1.x, np1.y, z)); - thin.push(new Point(max.int.x, max.int.y, z)); + lines.push(new Point(np1.x, np1.y)); + lines.push(new Point(max.int.x, max.int.y)); } }); } - - shells.appendAll(noodle); - return { last, gaps }; + return { lines, polys }; } let pointMap = new Map(); @@ -323,12 +330,10 @@ function thin_type_3(params) { return rec; } - let chains = []; let nusegs = []; // for each standalone noodle, construct medial axis // map medial axis points into de-duplicated segments - // which will be re-connected into chains for (let poly of noodle) { // scale the polygon (including inners) so the medial axis code works properly poly = poly.clone(true).scale({ x:scale, y:scale, z:scale }); @@ -343,60 +348,23 @@ function thin_type_3(params) { } } - // connect segments into chains + // break down long segments for (let seg of lineMap.values()) { let { p0, p1 } = seg; let len = seg.len = pointDist(p0, p1); - if (altNuSegs) { - // add to nusegs - if (len <= minR) { + // add to nusegs + if (len <= minR) { + nusegs.push({ p0, p1 }); + } else { + let steps = Math.ceil(len / minR) + 1; + let dx = (p1.x - p0.x) / steps; + let dy = (p1.y - p0.y) / steps; + for (let i=0; i c.total >= minR); - - // gather point offsets into shells - let zi = z; - let nuchains = []; - if (!altNuSegs) - for (let chain of chains) { - let nuchain = []; - nuchains.push(nuchain); - // zi += 0.1; - if (showChainRawPoints) - for (let pt of chain) { - shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); - } - // draw medial axis chain - let { closed, length } = chain; - let term = closed ? length : length - 1; - let segs = []; - for (let i=0; i Date: Thu, 18 Sep 2025 22:07:08 -0400 Subject: [PATCH 15/49] further abstract trace_noodle --- src/kiri/mode/fdm/post.js | 43 ++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 1f3cbb0d..ba17e491 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -168,9 +168,7 @@ function thin_type_3(params) { // produce trace from outside of poly inward no more than max inset let noodleWidth = offsetN * count; - let { noodle, remain } = top.poly.noodle(noodleWidth); - - top.gaps = remain; + let { noodle,} = top.poly.noodle(noodleWidth); // render inset "noodle" // shells.appendAll(noodle); @@ -184,10 +182,8 @@ function thin_type_3(params) { let maxR = midR * 1.5; // max double wall extrusion width let maxF = midR * 2; // max interior extrusion width - let { lines, polys } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, { + let { lines, polys, remain, trace } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, { brute: false, - showExtrusion: true, - showExtrudeInset: true, showChainIntersect: false }); @@ -197,18 +193,26 @@ function thin_type_3(params) { thin.push(...lines); shells.push(...polys); + // show extrusion + for (let point of trace) { + shells.push(newPolygon().centerCircle(point, point.r, 12)); + } + + // show remaining noodle after single trace + shells.push(...remain); + return { last, gaps }; } // trace a single extrusion line around the inside of the noodle function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { brute, - showExtrusion, - showExtrudeInset, showChainIntersect, }) { let scale = 1000; + let remain = []; + let trace = []; let polys = []; let lines = []; let insets = []; @@ -385,8 +389,6 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { return [ midR, rem, midR ]; } else { return [ midR, cr - midR * 2, midR ]; - // let divs = (cr / midR) | 0; - // return new Array(divs).fill(cr / divs); } } @@ -436,24 +438,21 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { nupoly.push(new Point(min.x, min.y)); return; } + let op; if (pop.length === 1) { nupoly.push(new Point(min.x, min.y)); // for single wall place it exactly on medial axis - if (showExtrusion) - polys.push(newPolygon().centerCircle({ - x: min.x, - y: min.y - }, mr, 12)); + trace.push(op = { x: min.x, y: min.y, r: mr }) } else { let off = -dstep; // otherwise use divisions for (let i=0; i<1; i++) { off += (pop[i] * (i ? 2 : 1)); - if (showExtrusion) - polys.push(newPolygon().centerCircle({ + trace.push(op = { x: np1.x + dy * off, - y: np1.y - dx * off - }, pop[0], 12)); + y: np1.y - dx * off, + r: pop[0] + }) } off += dstep / 2; nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2)); @@ -465,12 +464,10 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { }); } // subtract inner extrusion offset from parent - if (showExtrudeInset) { - polys.push(...POLY.subtract([ parent ], inner, [])); - } + remain.push(...POLY.subtract([ parent ], inner, [])); } - return { lines, polys }; + return { lines, polys, remain, trace }; } /** From a78ec87fb72cf12505dfaa9256b5ef45e7824f0b Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 19 Sep 2025 10:36:27 -0400 Subject: [PATCH 16/49] fix post-noodle-trace subtraction --- src/kiri/mode/fdm/post.js | 71 +++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index ba17e491..dba0aa69 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -182,31 +182,42 @@ function thin_type_3(params) { let maxR = midR * 1.5; // max double wall extrusion width let maxF = midR * 2; // max interior extrusion width - let { lines, polys, remain, trace } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, { - brute: false, - showChainIntersect: false - }); + for (let i=0; i<1; i++) { + console.log({ noodle }); - console.log({ lines, polys }); + let { lines, polys, remain, trace } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, { + brute: false, + showMedialAxis: i > 0, + showChainIntersect: i > 0 + }); - POLY.setZ([...lines, ...polys], z); - thin.push(...lines); - shells.push(...polys); + console.log({ lines, polys, remain, trace }); - // show extrusion - for (let point of trace) { - shells.push(newPolygon().centerCircle(point, point.r, 12)); + POLY.setZ([...lines, ...polys], z); + thin.push(...lines); + shells.push(...polys); + + // show extrusion + if (i === 0) + for (let point of trace) { + shells.push(newPolygon().centerCircle(point, point.r, 12)); + } + + // show remaining noodle after single trace + shells.push(...POLY.flatten(remain)); + + noodle = [ POLY.nest(remain)[3] ]; + // break; } - // show remaining noodle after single trace - shells.push(...remain); - return { last, gaps }; } -// trace a single extrusion line around the inside of the noodle +// trace a single extrusion line around the inside of the noodle poly function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { brute, + showInset, + showMedialAxis, showChainIntersect, }) { let scale = 1000; @@ -229,7 +240,9 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { p.dtotl = dtotl; } insets.push(inset); - polys.appendAll(inset); + if (showInset) { + polys.appendAll(inset); + } if (!brute) { break; } @@ -359,6 +372,9 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { // add to nusegs if (len <= minR) { nusegs.push({ p0, p1 }); + if (showMedialAxis) { + lines.push(new Point(p0.x, p0.y), new Point(p1.x, p1.y)); + } } else { let steps = Math.ceil(len / minR) + 1; let dx = (p1.x - p0.x) / steps; @@ -366,6 +382,9 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { for (let i=0; i { + return p.clean().simplify(); + }).flat() + ); + remain.push(...ret); } return { lines, polys, remain, trace }; From c39576ce37ffc4bcbec6ace0e27b612a5f79f5a4 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 19 Sep 2025 11:06:58 -0400 Subject: [PATCH 17/49] filter remaining trace polygons to >= minArea --- src/kiri/mode/fdm/post.js | 78 +++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index dba0aa69..33b176d1 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -182,51 +182,67 @@ function thin_type_3(params) { let maxR = midR * 1.5; // max double wall extrusion width let maxF = midR * 2; // max interior extrusion width - for (let i=0; i<1; i++) { - console.log({ noodle }); + let lines = []; + let polys = []; + let trace = []; - let { lines, polys, remain, trace } = trace_noodle(noodle, noodleWidth, minR, midR, maxR, { + for (let i=0; i 0, - showChainIntersect: i > 0 + showMedialAxis: false, + showChainIntersect: false }); - console.log({ lines, polys, remain, trace }); - - POLY.setZ([...lines, ...polys], z); - thin.push(...lines); - shells.push(...polys); - - // show extrusion - if (i === 0) - for (let point of trace) { - shells.push(newPolygon().centerCircle(point, point.r, 12)); - } + console.log({ remain }); // show remaining noodle after single trace shells.push(...POLY.flatten(remain)); - noodle = [ POLY.nest(remain)[3] ]; - // break; + noodle = remain; } + console.log({ lines, polys, trace }); + + // show extrusion + for (let point of trace) { + shells.push(newPolygon().centerCircle(point, point.r, 12)); + } + + POLY.setZ([...lines, ...polys], z); + thin.push(...lines); + shells.push(...polys); + return { last, gaps }; } // trace a single extrusion line around the inside of the noodle poly -function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { - brute, - showInset, - showMedialAxis, - showChainIntersect, -}) { - let scale = 1000; - - let remain = []; - let trace = []; - let polys = []; - let lines = []; +function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { + let { + brute, + showInset, + showMedialAxis, + showChainIntersect, + minArea = midR * midR, + lines = [], + polys = [], + remain = [], + trace = [], + } = opt; let insets = []; + let scale = 1000; let dstep = minR / 4; let dtotl = 0; @@ -498,7 +514,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, { POLY.flatten(setA, [], true).map(p => { return p.clean().simplify(); }).flat() - ); + ).filter(p => p.area() >= minArea); remain.push(...ret); } From e55a8328e34d37585ae011884e2a027fd32df1bc Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 19 Sep 2025 12:13:02 -0400 Subject: [PATCH 18/49] re-add gaps visualization. remove unused inset iteration for single intersections --- src/kiri/mode/fdm/post.js | 48 ++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 33b176d1..80955d23 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -164,11 +164,14 @@ function thin_type_2(params) { function thin_type_3(params) { // offsetN usually = extrusion width (nozzle diameter) - let { z, top, count, offsetN, last, gaps } = params; + let { z, top, count, offsetN } = params; // produce trace from outside of poly inward no more than max inset let noodleWidth = offsetN * count; - let { noodle,} = top.poly.noodle(noodleWidth); + let { noodle, remain } = top.poly.noodle(noodleWidth); + + // remaining area for infill after shells are extruded + let gaps = top.gaps = remain; // render inset "noodle" // shells.appendAll(noodle); @@ -189,6 +192,7 @@ function thin_type_3(params) { for (let i=0; i Date: Fri, 19 Sep 2025 12:50:42 -0400 Subject: [PATCH 19/49] start passing back thin wall data to slice --- src/kiri/core/slice.js | 3 ++- src/kiri/mode/fdm/post.js | 28 ++++++++++++++-------------- src/kiri/mode/fdm/slice.js | 16 ++++++++++------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/kiri/core/slice.js b/src/kiri/core/slice.js index 934f7394..6982d2cf 100644 --- a/src/kiri/core/slice.js +++ b/src/kiri/core/slice.js @@ -144,7 +144,8 @@ class Slice { } else { // create top object from object bundle passed back by slicePost() let top = new Top(data.poly); - top.thin_fill = data.thin_fill ? data.thin_fill.map(p => newPoint(p.x,p.y,p.z)) : undefined; + top.thin_fill = data.thin_fill?.map(p => newPoint(p.x,p.y,p.z)); + top.thin_wall = data.thin_wall; top.fill_lines = data.fill_lines; top.fill_sparse = data.fill_sparse; top.fill_off = data.fill_off; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 80955d23..e26bbc4f 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -170,15 +170,14 @@ function thin_type_3(params) { let noodleWidth = offsetN * count; let { noodle, remain } = top.poly.noodle(noodleWidth); - // remaining area for infill after shells are extruded - let gaps = top.gaps = remain; - // render inset "noodle" // shells.appendAll(noodle); - let thin = top.thin_fill = []; - let shells = top.shells; - let sparse = top.fill_sparse = []; + let debugRemain = false; + let debugExtrusion = false; + + let shells = top.shells; // only used for debug + let trace = top.thin_wall = []; let midR = offsetN * 0.5; // nominal extrusion width let minR = midR * 0.75; // smallest interior fill or single wall width @@ -187,7 +186,6 @@ function thin_type_3(params) { let lines = []; let polys = []; - let trace = []; for (let i=0; i newPolygon().centerCircle(p, p.r, 12).setZ(slice.z)), vopt({ offset, height })); + + if (top.gaps?.length && devel) output .setLayer("gaps", Color.gaps) .addPolys(top.gaps, vopt({ offset, height, thin: true })); - if (isThin && devel && top.fill_off && top.fill_off.length) { + if (isThin && devel && top.fill_off?.length) { slice.output() .setLayer('fill inset', Color.inset) .addPolys(top.fill_off); From b1eee58c1e74e8a339897b6f270cff39c75f59bd Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 19 Sep 2025 20:26:34 -0400 Subject: [PATCH 20/49] annotate thinwall points with shell number --- src/kiri/mode/fdm/post.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index e26bbc4f..e3ae6d93 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -199,6 +199,7 @@ function thin_type_3(params) { i === 0 ? midR : midR * 1.25, i === 0 ? maxR : maxF, { + shell: 0, lines, polys, remain, @@ -242,6 +243,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { polys = [], remain = [], trace = [], + shell = 0 } = opt; let insets = []; let scale = 1000; @@ -437,6 +439,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { let parent = new Polygon(); let inner = []; for (let poly of insetp.flattenTo([])) { + let sno = poly.parent ? shell : -shell; // shell number (- for inner/hole) let nupoly = poly.parent ? new Polygon() : parent; if (poly.parent) { inner.push(nupoly); @@ -481,7 +484,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { if (pop.length === 1) { // for single wall place it exactly on medial axis nupoly.push(new Point(min.x, min.y)); - trace.push(op = { x: min.x, y: min.y, r: mr }) + trace.push(op = { x: min.x, y: min.y, r: mr, sno }) } else { // otherwise use first division // compensate for minimal inset @@ -489,7 +492,8 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { trace.push(op = { x: np1.x + dy * off, y: np1.y - dx * off, - r: pop[0] + r: pop[0], + sno }) // trace inset by full diameter nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2)); From d5b9d5d8f731fc4b9ae3701c1035d42702c50ae1 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 01:13:13 -0400 Subject: [PATCH 21/49] test output using point radius for extrusion width --- src/kiri/mode/fdm/prepare.js | 21 +++++++++++++++++++-- src/kiri/mode/fdm/slice.js | 10 +++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index c7a90638..455f5250 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -3,7 +3,7 @@ import { base, util } from '../../../geo/base.js'; import { poly2polyEmit, tip2tipEmit } from '../../../geo/paths.js'; import { newBounds } from '../../../geo/bounds.js'; -import { newPoint } from '../../../geo/point.js'; +import { newPoint, Point } from '../../../geo/point.js'; import { newPolygon, Polygon } from '../../../geo/polygon.js'; import { polygons as POLY, fillArea } from '../../../geo/polygons.js'; import { newPrint } from '../../core/print.js'; @@ -834,7 +834,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { z = slice.z, lastPoly; - // support alternating shell order + // support alternating shell order if (Math.abs(shellOrder) > 1 && slice.index % 2 === 1) { shellOrder = -shellOrder; } @@ -1089,6 +1089,20 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } } + function outputAdaptiveWalls(thin, opt = {}) { + let np; + for (let pt of thin) { + let { lastPoint, lastEmit } = print; + let { x, y, r } = pt; + np = new Point(x, y, z); + if (lastPoint && lastPoint.distTo2D(np) > retractDist) { + print.addOutput(preout, np, 0, moveSpeed); + } else { + print.addOutput(preout, np, (r * 2) / nozzleSize, printSpeed); + } + } + } + /** * output sparse infill * @param {Polygon[]} polys @@ -1444,6 +1458,9 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { // raft if (top.traces) outputTraces(top.traces); + // thin wall v3 + if (top.thin_wall) outputAdaptiveWalls(top.thin_wall); + // innermost shells let inner = next.innerShells() || []; let shells = next.shells || []; diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index fb854b9b..32cb2f52 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -997,10 +997,14 @@ function doRender(slice, isSynth, params, opt = {}) { .setLayer('part', Color.part) .addPolys([top.poly]); - output + if (top.shells?.length) output .setLayer(isSynth ? "support" : "shells", isSynth ? Color.support : Color.shell) .addPolys(top.shells || [], vopt({ offset, height, clean: true })); + if (top.thin_wall?.length) output + .setLayer("walls", Color.thin) + .addPolys(top.thin_wall.map(p => newPolygon().centerCircle(p, p.r, 12).setZ(slice.z)), vopt({ offset, height })); + if (top.fill_lines?.length) output .setLayer("solid fill", isSynth ? Color.support : Color.fill) .addLines(top.fill_lines || [], vopt({ offset: offset * solidWidth, height, z:slice.z })); @@ -1017,10 +1021,6 @@ function doRender(slice, isSynth, params, opt = {}) { .setLayer("thin fill", Color.thin) .addLines(top.thin_fill, vopt({ offset, height })); - if (top.thin_wall?.length) output - .setLayer("shells", Color.thin) - .addPolys(top.thin_wall.map(p => newPolygon().centerCircle(p, p.r, 12).setZ(slice.z)), vopt({ offset, height })); - if (top.gaps?.length && devel) output .setLayer("gaps", Color.gaps) .addPolys(top.gaps, vopt({ offset, height, thin: true })); From 8dfd40bf692f5a5ad830757888f7a15d947acd5b Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 10:57:11 -0400 Subject: [PATCH 22/49] apply thin wall shell extrusion multiplier --- src/kiri/mode/fdm/prepare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 455f5250..4069e7a0 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1098,7 +1098,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { if (lastPoint && lastPoint.distTo2D(np) > retractDist) { print.addOutput(preout, np, 0, moveSpeed); } else { - print.addOutput(preout, np, (r * 2) / nozzleSize, printSpeed); + print.addOutput(preout, np, ((r * 2) / nozzleSize) * shellMult, printSpeed); } } } From 82a429c13ab05649c7c476544009d5e62033f15f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 14:56:56 -0400 Subject: [PATCH 23/49] add shell width stepping --- src/kiri/mode/fdm/post.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index e3ae6d93..ecd892ca 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -239,12 +239,15 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { showMedialAxis, showChainIntersect, minArea = midR * midR, + shellStep = 1 / 30, // diameter step as % of nozzle lines = [], polys = [], remain = [], trace = [], shell = 0 } = opt; + + let sstep = 1 / shellStep; let insets = []; let scale = 1000; let dstep = minR / 4; @@ -482,17 +485,20 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { } let op; if (pop.length === 1) { + // let rad_step = ((pop[0] * sstep) | 0) / sstep; // for single wall place it exactly on medial axis nupoly.push(new Point(min.x, min.y)); trace.push(op = { x: min.x, y: min.y, r: mr, sno }) } else { + // radius rounded to nearest step + let rad_step = ((pop[0] * sstep) | 0) / sstep; // otherwise use first division // compensate for minimal inset - let off = -dstep + pop[0]; + let off = -dstep + rad_step; trace.push(op = { x: np1.x + dy * off, y: np1.y - dx * off, - r: pop[0], + r: rad_step, sno }) // trace inset by full diameter From e2c5fc4a988d46d28deb6968095acc1b507ffa9d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 15:20:26 -0400 Subject: [PATCH 24/49] repeeat last point of trace if close enough to close --- src/kiri/core/render.js | 3 +-- src/kiri/mode/fdm/post.js | 7 +++++++ src/kiri/mode/fdm/slice.js | 6 +++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/kiri/core/render.js b/src/kiri/core/render.js index 0567d53d..a322958d 100644 --- a/src/kiri/core/render.js +++ b/src/kiri/core/render.js @@ -192,7 +192,7 @@ async function path(levels, update, opts = {}) { // Math.abs(op.y - lp.y), // Math.abs(op.z - lp.z)); // if (moved < 0.0001) return; - if( is_cam() && (out.emit == 2 || out.emit == 3 )) { // cam arc emit + if (is_cam() && (out.emit == 2 || out.emit == 3 )) { // cam arc emit // checks if a new poly should be started if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) { current = newPolygon().setOpen(); @@ -210,7 +210,6 @@ async function path(levels, update, opts = {}) { } else if (out.emit) { // explicity G1 in CAM mode // just a non-move in other modes - // checks if a new poly should be started if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) { current = newPolygon().setOpen(); diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index ecd892ca..742df723 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -447,6 +447,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { if (poly.parent) { inner.push(nupoly); } + let fpo; poly.segment(minR).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); @@ -507,8 +508,14 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { if (odd && !mp1.claimed) { mp1.claimed = true; } + // store first trace out for this poly to repeat at the end + fpo = fpo ?? trace.peek(); } }); + // repeat first point of trace (if close enough to be closed) + if (fpo && pointDist(fpo, trace.peek()) <= maxR) { + trace.push(fpo); + } } // subtract inner extrusion offsets from parent extrusion offset diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index 32cb2f52..62c5d7ca 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -1001,10 +1001,14 @@ function doRender(slice, isSynth, params, opt = {}) { .setLayer(isSynth ? "support" : "shells", isSynth ? Color.support : Color.shell) .addPolys(top.shells || [], vopt({ offset, height, clean: true })); - if (top.thin_wall?.length) output + if (isThin && top.thin_wall?.length) output .setLayer("walls", Color.thin) .addPolys(top.thin_wall.map(p => newPolygon().centerCircle(p, p.r, 12).setZ(slice.z)), vopt({ offset, height })); + if (!isThin && top.thin_wall?.length) output + .setLayer("walls", Color.thin) + .addPolys(top.thin_wall.map(p => newPolygon().centerCircle(p, p.r, 3).setZ(slice.z)), vopt({ offset, height })); + if (top.fill_lines?.length) output .setLayer("solid fill", isSynth ? Color.support : Color.fill) .addLines(top.fill_lines || [], vopt({ offset: offset * solidWidth, height, z:slice.z })); From 07213ddc790748f326c91098dcb81fc4b994c510 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 16:24:33 -0400 Subject: [PATCH 25/49] annotate thin shells for output ordering --- src/geo/polygon.js | 8 ++++++ src/kiri/core/slice.js | 1 + src/kiri/mode/fdm/post.js | 51 ++++++++++++++++++++++++++---------- src/kiri/mode/fdm/prepare.js | 11 +++++--- src/kiri/mode/fdm/slice.js | 10 +++++-- 5 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index e5a3d9a6..f81ebbb6 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1104,6 +1104,14 @@ export class Polygon { return this.first().isEqual(this.last()); } + closeIf(dist = 1) { + if (this.first().distTo2D(this.last()) <= dist) { + return this.setClosed(); + } else { + return this.setOpen(); + } + } + fixClosed() { if (this.appearsClosed()) { this.points.pop(); diff --git a/src/kiri/core/slice.js b/src/kiri/core/slice.js index 6982d2cf..d684d046 100644 --- a/src/kiri/core/slice.js +++ b/src/kiri/core/slice.js @@ -146,6 +146,7 @@ class Slice { let top = new Top(data.poly); top.thin_fill = data.thin_fill?.map(p => newPoint(p.x,p.y,p.z)); top.thin_wall = data.thin_wall; + top.thin_sort = data.thin_sort; top.fill_lines = data.fill_lines; top.fill_sparse = data.fill_sparse; top.fill_off = data.fill_off; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 742df723..40d3ebcc 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -177,7 +177,7 @@ function thin_type_3(params) { let debugExtrusion = false; let shells = top.shells; // only used for debug - let trace = top.thin_wall = []; + let traces = top.thin_wall = []; let midR = offsetN * 0.5; // nominal extrusion width let minR = midR * 0.75; // smallest interior fill or single wall width @@ -199,11 +199,11 @@ function thin_type_3(params) { i === 0 ? midR : midR * 1.25, i === 0 ? maxR : maxF, { - shell: 0, + shell: i + 1, lines, polys, remain, - trace, + traces, brute: false, showMedialAxis: false, showChainIntersect: false @@ -228,7 +228,12 @@ function thin_type_3(params) { if (lines.length) top.thin_fill = lines; if (polys.length) shells.push(...polys); - return { trace, last: remain }; + // create shell depth annotation that will survive serialization + top.thin_sort = traces.map(a => a.shell); + + console.log({ this: (self.kiri_minion?.name ?? self.kiri_worker), traces }); + + return { traces, last: remain }; } // trace a single extrusion line around the inside of the noodle poly @@ -243,7 +248,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { lines = [], polys = [], remain = [], - trace = [], + traces = [], shell = 0 } = opt; @@ -447,7 +452,9 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { if (poly.parent) { inner.push(nupoly); } - let fpo; + let ltpo; + let trace = []; + trace.shell = sno; poly.segment(minR).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); @@ -489,7 +496,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { // let rad_step = ((pop[0] * sstep) | 0) / sstep; // for single wall place it exactly on medial axis nupoly.push(new Point(min.x, min.y)); - trace.push(op = { x: min.x, y: min.y, r: mr, sno }) + trace.push(op = { x: min.x, y: min.y, r: mr }) } else { // radius rounded to nearest step let rad_step = ((pop[0] * sstep) | 0) / sstep; @@ -499,8 +506,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { trace.push(op = { x: np1.x + dy * off, y: np1.y - dx * off, - r: rad_step, - sno + r: rad_step }) // trace inset by full diameter nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2)); @@ -508,13 +514,29 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { if (odd && !mp1.claimed) { mp1.claimed = true; } - // store first trace out for this poly to repeat at the end - fpo = fpo ?? trace.peek(); + // detect trace jump and start new trace + if (ltpo && pointDist(ltpo, trace.peek()) > maxR) { + // start new trace removing jump point + // and adding that to the new trace + traces.push(trace); + let nutrace = [ trace.pop() ]; + nutrace.shell = sno; + // close last trace if endpoints near enough + if (pointDist(ltpo, trace[0] <= maxR)) { + trace.push(trace[0]); + } + trace = nutrace; + } + // update last trace point out + ltpo = trace.peek(); } }); // repeat first point of trace (if close enough to be closed) - if (fpo && pointDist(fpo, trace.peek()) <= maxR) { - trace.push(fpo); + if (trace[0] && pointDist(trace[0], trace.peek()) <= maxR) { + trace.push(trace[0]); + } + if (trace.length) { + traces.push(trace); } } @@ -534,10 +556,11 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { return p.clean().simplify(); }).flat() ).filter(p => p.area() >= minArea); + remain.push(...ret); } - return { lines, polys, remain, trace }; + return { lines, polys, remain, traces }; } /** diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 4069e7a0..03f5f6cd 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1089,9 +1089,14 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } } - function outputAdaptiveWalls(thin, opt = {}) { + function outputAdaptiveWalls(thin, sort, opt = {}) { + // restore thin shell order annotation and sort on it + thin.forEach((a,i) => a.shell = sort[i]); + thin.sort((a,b) => (a.shell - b.shell) * shellOrder); + // perform nearest poly to poly algorithm on shells let np; - for (let pt of thin) { + for (let trace of thin) + for (let pt of trace) { let { lastPoint, lastEmit } = print; let { x, y, r } = pt; np = new Point(x, y, z); @@ -1459,7 +1464,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { if (top.traces) outputTraces(top.traces); // thin wall v3 - if (top.thin_wall) outputAdaptiveWalls(top.thin_wall); + if (top.thin_wall) outputAdaptiveWalls(top.thin_wall, top.thin_sort); // innermost shells let inner = next.innerShells() || []; diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index 62c5d7ca..291dcf24 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -1003,11 +1003,17 @@ function doRender(slice, isSynth, params, opt = {}) { if (isThin && top.thin_wall?.length) output .setLayer("walls", Color.thin) - .addPolys(top.thin_wall.map(p => newPolygon().centerCircle(p, p.r, 12).setZ(slice.z)), vopt({ offset, height })); + .addPolys(top.thin_wall.map(a => a.map(p => newPolygon().centerCircle(p, p.r, 12).setZ(slice.z))).flat(), vopt({ offset, height })); if (!isThin && top.thin_wall?.length) output .setLayer("walls", Color.thin) - .addPolys(top.thin_wall.map(p => newPolygon().centerCircle(p, p.r, 3).setZ(slice.z)), vopt({ offset, height })); + .addPolys(top.thin_wall.map(a => + a.length === 1 ? + newPolygon().centerCircle(a[0], a[0].r, 6) : + newPolygon(a.map(p => newPoint(p.x, p.y))) + .setZ(slice.z) + .closeIf(height) + ), vopt({ offset, height })); if (top.fill_lines?.length) output .setLayer("solid fill", isSynth ? Color.support : Color.fill) From 6a44654714e8026d7bf5260753aabb2e2ebc90ee Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 18:17:56 -0400 Subject: [PATCH 26/49] update single width extrusion claim system. does not require splitting medial axis segments --- src/geo/point.js | 7 ++++- src/geo/polygon.js | 7 +++-- src/kiri/mode/fdm/post.js | 54 +++++++++++++-------------------------- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/geo/point.js b/src/geo/point.js index a29b98c4..add35a8c 100644 --- a/src/geo/point.js +++ b/src/geo/point.js @@ -114,11 +114,16 @@ class Point { return this.key; } - clone() { + clone(keys) { let p = newPoint(this.x, this.y, this.z, this._key); if (this.a !== undefined) { p.a = this.a; } + if (keys) { + for (let key of keys) { + p[key] = this[key]; + } + } return p; } diff --git a/src/geo/polygon.js b/src/geo/polygon.js index f81ebbb6..7eee6ad0 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -2322,7 +2322,7 @@ export class Polygon { // split long straight lines into segments no longer than max // and return a new polygon - segment(max = 1) { + segment(max = 1, mark) { const newp = []; const points = this.points; const length = points.length; @@ -2334,6 +2334,7 @@ export class Polygon { const dy = p2.y - p1.y; const dl = Math.sqrt(dx * dx + dy * dy); newp.push(p1); + if (mark) p1.segment = p1; if (dl < max) { continue; } @@ -2348,10 +2349,12 @@ export class Polygon { newp.push(newPoint(ox, oy, (p1.z + p2.z) / 2)); ox += ix; oy += iy; + // mark new point with first point of originating segment + if (mark) newp.peek().segment = p1; } } if (newp.length > length) { - return newPolygon().addPoints(newp.map(p => p.clone())).setOpenValue(this.open); + return newPolygon().addPoints(newp.map(p => p.clone(['segment']))).setOpenValue(this.open); } return this; } diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 40d3ebcc..d27f27dd 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -218,11 +218,14 @@ function thin_type_3(params) { // console.log({ lines, polys, trace }); // show extrusion + let zo = z + 0.1; if (debugExtrusion) + for (let trace of traces) for (let point of trace) { - polys.push(newPolygon().centerCircle(point, point.r, 12).setZ(z)); + polys.push(newPolygon().centerCircle(point, point.r, 12).setZ(zo = zo + 0.005)); } + if (!debugExtrusion) POLY.setZ([...lines, ...polys], z); if (lines.length) top.thin_fill = lines; @@ -279,7 +282,11 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { }); } - // project from first inset and find greated offset intersection + // project from first inset and find greated offset intersection. + // this examples show how to brute force create a medial axis from + // micro stepping insets. the same intersect technique used with + // medial axis can be applied to this structure to produce an + // equivalent output. not as computationally efficient, but much simpler. if (brute) { let { intersectRayLine } = base.util; let tests = insets.slice(1); @@ -375,8 +382,6 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { return rec; } - let nusegs = []; - // for each standalone noodle, construct medial axis // map medial axis points into de-duplicated segments for (let poly of noodle) { @@ -393,31 +398,6 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { } } - // break down long segments - for (let seg of lineMap.values()) { - let { p0, p1 } = seg; - let len = seg.len = pointDist(p0, p1); - // add to nusegs - if (len <= minR) { - nusegs.push({ p0, p1 }); - if (showMedialAxis) { - lines.push(new Point(p0.x, p0.y), new Point(p1.x, p1.y)); - } - } else { - let steps = Math.ceil(len / minR) + 1; - let dx = (p1.x - p0.x) / steps; - let dy = (p1.y - p0.y) / steps; - for (let i=0; i { + poly.segment(minR, true).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); let dx = (p1.x - p2.x) / len; let dy = (p1.y - p2.y) / len; let min = { dist: Infinity }; - for (let seg of nusegs) { + for (let seg of segs) { let { p0, p1 } = seg; let int = intersectRayLine(np1, { dx: dy, dy: -dx }, p0, p1); if (int?.dist < min.dist && int?.dist < noodleWidth) { @@ -486,24 +469,23 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { let len = Math.ceil(pop.length / 2); // check if segment was claimed by a different originating segment // for odd wall counts so that they're not emitted twice - if (odd && mp1.claimed) len--; + if (odd && mp1.claimed && mp1.claimed !== p1.segment) len--; if (len === 0) { nupoly.push(new Point(min.x, min.y)); return; } - let op; if (pop.length === 1) { // let rad_step = ((pop[0] * sstep) | 0) / sstep; // for single wall place it exactly on medial axis nupoly.push(new Point(min.x, min.y)); - trace.push(op = { x: min.x, y: min.y, r: mr }) + trace.push({ x: min.x, y: min.y, r: mr }) } else { // radius rounded to nearest step let rad_step = ((pop[0] * sstep) | 0) / sstep; // otherwise use first division // compensate for minimal inset let off = -dstep + rad_step; - trace.push(op = { + trace.push({ x: np1.x + dy * off, y: np1.y - dx * off, r: rad_step @@ -512,7 +494,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2)); } if (odd && !mp1.claimed) { - mp1.claimed = true; + mp1.claimed = p1.segment; } // detect trace jump and start new trace if (ltpo && pointDist(ltpo, trace.peek()) > maxR) { From 7277eed1118c24836bd393b44a7048ad6f393b71 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 21 Sep 2025 17:38:45 -0600 Subject: [PATCH 27/49] helical bugfixes --- src/kiri/mode/cam/op-helical.js | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/kiri/mode/cam/op-helical.js b/src/kiri/mode/cam/op-helical.js index 82fc7dbc..3c3aed6a 100644 --- a/src/kiri/mode/cam/op-helical.js +++ b/src/kiri/mode/cam/op-helical.js @@ -138,14 +138,17 @@ export class OpHelical extends CamOp { ); // console.log({down,toBottom,ofFull,ofCircle,startAngle,finalAngle,finalEnd}); - //circle down to final point - poly.addPoints( - arcToPath(startPoint.setZ(currentZ), finalEnd, numSegs , { - clockwise, - radius, - center, - }) - ); + //if not already very close to final point + if( ofCircle >= 0.001 ){ + //circle down to final point + poly.addPoints( + arcToPath(startPoint.setZ(currentZ), finalEnd, numSegs , { + clockwise, + radius, + center, + }) + ); + } //if starting at the bottom, reverse poly if ( reverse ){ @@ -169,9 +172,9 @@ export class OpHelical extends CamOp { let lineOut = radius - entryOffset; let centerOffset = (entryOffset**2 + 2*entryOffset*lineOut) / (2*(entryOffset + lineOut)); //add entry points - entry.push(... + entry.push( newPoint(0,-lineOut,0), - arcToPath( + ...arcToPath( newPoint(0,-lineOut,0), newPoint(radius,0,0), 128, @@ -186,12 +189,15 @@ export class OpHelical extends CamOp { } } //for each point + //rotate by start angle + // add to hole center + let invert = ((clockwise && !reverse ) || (!clockwise && reverse) ) ? -1 : 1 + let entryAngle = reverse ? finalAngle : startAngle entry = entry.map(p => { - //rotate by start angle - // add to hole center // TODO: there's totally a way to do this with a matrix - p.y *= clockwise ? 1 : -1 - p.rotate(startAngle) + // logical XOR + p.y *= invert + p.rotate(entryAngle) return p.add(entryCenter) }) //append to start of poly From 813ae0d9e67ba16b03b3b426e172bb11158c7724 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 21:20:36 -0400 Subject: [PATCH 28/49] add proper shell output ordering with next closest algorithm --- src/kiri/mode/fdm/post.js | 5 +--- src/kiri/mode/fdm/prepare.js | 54 +++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index d27f27dd..fbdbe115 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -205,7 +205,6 @@ function thin_type_3(params) { remain, traces, brute: false, - showMedialAxis: false, showChainIntersect: false }); @@ -234,8 +233,7 @@ function thin_type_3(params) { // create shell depth annotation that will survive serialization top.thin_sort = traces.map(a => a.shell); - console.log({ this: (self.kiri_minion?.name ?? self.kiri_worker), traces }); - + // console.log({ this: (self.kiri_minion?.name ?? self.kiri_worker), traces }); return { traces, last: remain }; } @@ -244,7 +242,6 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { let { brute, showInset, - showMedialAxis, showChainIntersect, minArea = midR * midR, shellStep = 1 / 30, // diameter step as % of nozzle diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 03f5f6cd..b5bdab6d 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1089,21 +1089,49 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } } - function outputAdaptiveWalls(thin, sort, opt = {}) { + function minThinDist(np, trace) { + return Math.min( + Math.hypot(np.x - trace[0].x, np.y - trace[0].y), + Math.hypot(np.x - trace.peek().x, np.y - trace.peek().y), + ); + } + + function outputAdaptiveWalls(thin, sort) { // restore thin shell order annotation and sort on it - thin.forEach((a,i) => a.shell = sort[i]); - thin.sort((a,b) => (a.shell - b.shell) * shellOrder); - // perform nearest poly to poly algorithm on shells + thin.forEach((a,i) => a.shell = sort[i] * shellOrder); + thin.forEach(t => t.shell = t.shell < 0 ? Math.abs(t.shell) + 0.5 : t.shell); + thin.sort((a,b) => a.shell - b.shell); let np; - for (let trace of thin) - for (let pt of trace) { - let { lastPoint, lastEmit } = print; - let { x, y, r } = pt; - np = new Point(x, y, z); - if (lastPoint && lastPoint.distTo2D(np) > retractDist) { - print.addOutput(preout, np, 0, moveSpeed); - } else { - print.addOutput(preout, np, ((r * 2) / nozzleSize) * shellMult, printSpeed); + // perform nearest poly to poly algorithm on shells + for (;;) { + let min = { dist: Infinity }; + for (let trace of thin) { + if (!min.trace) { + min.trace = trace; + } else if (np && min.trace.shell === trace.shell) { + let dist = minThinDist(np, trace); + if (dist < min.dist) min = { dist, trace }; + } + } + if (!min.trace) { + return; + } + let { trace } = min; + let fp = trace[0]; + let lp = trace.peek(); + // reverse trace if far end closer + if (np && Math.hypot(np.x - lp.x, np.y - lp.y) < Math.hypot(np.x - fp.x, np.y - fp.y)) { + trace.reverse(); + } + // remove emitted trace + thin = thin.filter(t => t !== trace); + for (let pt of trace) { + np = new Point(pt.x, pt.y, z); + if (pt === trace[0]) { + print.addOutput(preout, np, 0, moveSpeed); + } else { + print.addOutput(preout, np, ((pt.r * 2) / nozzleSize) * shellMult, printSpeed); + } } } } From cc2615db713b4e3471569b43ebccba843fea77b4 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 23:00:14 -0400 Subject: [PATCH 29/49] fix infill offsetting. implement point reduction. fix move to start on layer up --- src/kiri/mode/fdm/post.js | 28 ++++++++++++++++++++-------- src/kiri/mode/fdm/prepare.js | 3 ++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index fbdbe115..21b7593c 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -170,6 +170,9 @@ function thin_type_3(params) { let noodleWidth = offsetN * count; let { noodle, remain } = top.poly.noodle(noodleWidth); + // re-expand inner offset so fill offsets align properly with expectation + remain = POLY.offset(remain, offsetN / 2); + // render inset "noodle" // shells.appendAll(noodle); @@ -188,7 +191,7 @@ function thin_type_3(params) { let polys = []; for (let i=0; i 1 && lpo.s === npo.s && lpo.r === npo.r) { + trace.pop(); + } + trace.push(npo); if (odd && !mp1.claimed) { mp1.claimed = p1.segment; } diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index b5bdab6d..8c1b7499 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1101,7 +1101,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { thin.forEach((a,i) => a.shell = sort[i] * shellOrder); thin.forEach(t => t.shell = t.shell < 0 ? Math.abs(t.shell) + 0.5 : t.shell); thin.sort((a,b) => a.shell - b.shell); - let np; + let np = print.lastPoint; // perform nearest poly to poly algorithm on shells for (;;) { let min = { dist: Infinity }; @@ -1114,6 +1114,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } } if (!min.trace) { + startPoint = np; return; } let { trace } = min; From 9567a99fd9e4792d1cdf5b4daeca0fd93b4889f0 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 00:09:41 -0400 Subject: [PATCH 30/49] fix thin wall retraction. remove point markers to allow codec between minion n worker --- src/kiri/mode/fdm/post.js | 12 +++++++++--- src/kiri/mode/fdm/prepare.js | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 21b7593c..655a569d 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -430,14 +430,13 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { let parent = new Polygon(); let inner = []; for (let poly of insetp.flattenTo([])) { - let sno = poly.parent ? shell : -shell; // shell number (- for inner/hole) let nupoly = poly.parent ? new Polygon() : parent; if (poly.parent) { inner.push(nupoly); } let ltpo; let trace = []; - trace.shell = sno; + trace.shell = poly.parent ? shell : -shell; // shell number (- for inner/hole) poly.segment(minR, true).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); @@ -511,7 +510,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { // and adding that to the new trace traces.push(trace); let nutrace = [ trace.pop() ]; - nutrace.shell = sno; + nutrace.shell = trace.shell; // close last trace if endpoints near enough if (pointDist(ltpo, trace[0] <= maxR)) { trace.push(trace[0]); @@ -551,6 +550,13 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { remain.push(...ret); } + // remove point.segment markers for codec encoding between minion and worker + for (let trace of traces) { + for (let pt of trace) { + delete pt.s; + } + } + return { lines, polys, remain, traces }; } diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 8c1b7499..d2bb8897 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1126,6 +1126,10 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { } // remove emitted trace thin = thin.filter(t => t !== trace); + // retract if moving > retractDist + if (np && np.distTo2D(new Point(trace[0].x, trace[0].y)) > retractDist) { + retract(); + } for (let pt of trace) { np = new Point(pt.x, pt.y, z); if (pt === trace[0]) { From 6021fe0ffb40e8a69b5c635fba9855241d21239d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 01:57:56 -0400 Subject: [PATCH 31/49] move shell/fill overlap to shell menu. allow micro backlash. increase thin resolution stepping --- src/kiri/mode/fdm/init-menu.js | 5 +++-- src/kiri/mode/fdm/post.js | 2 +- src/kiri/mode/fdm/prepare.js | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index bb34b97f..73b93a1f 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -82,11 +82,12 @@ export function menu() { sliceDetectThin: newSelect(LANG.ad_thin_s, { title: LANG.ad_thin_l, action: thinWallSave }, "thin"), outputAlternating: newBoolean(LANG.ad_altr_s, onBooleanClick, {title:LANG.ad_altr_l}), sliceZInterleave: newBoolean(LANG.ad_zint_s, onBooleanClick, {title:LANG.ad_zint_l, show:zIntShow}), + separator: newBlank({ class:"set-sep", driven }), + sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0)}), _____: newGroup(LANG.fi_menu, $('fdm-fill'), { modes:FDM, driven, hideable, separator, group:"fdm-fill" }), sliceFillType: newSelect(LANG.fi_type, {trigger}, "infill"), sliceFillSparse: newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:toFloat, bound:bound(0.0,1.0), show:hasInfill}), sliceFillRepeat: newInput(LANG.fi_rept_s, {title:LANG.fi_rept_l, convert:toInt, bound:bound(1,10), show:fillIsLinear}), - sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0), show:hasInfill}), separator: newBlank({ class:"set-sep", driven }), sliceFillRate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt, bound:bound(0,500)}), sliceSolidRate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt, bound:bound(0,500)}), @@ -178,7 +179,7 @@ export function menu() { outputCoastDist: newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, convert:toFloat, bound:bound(0.0,10)}), zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}), arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}), - antiBacklash: newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, convert:toInt, bound:bound(0,3)}), + antiBacklash: newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, convert:toFloat, bound:bound(0,3)}), outputLoops: newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, convert:toInt, bound:bound(-1,1000), show:isBelt}), outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}), diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 655a569d..3ff454d7 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -247,7 +247,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { showInset, showChainIntersect, minArea = midR * midR, - shellStep = 1 / 30, // diameter step as % of nozzle + shellStep = 1 / 100, // diameter step as % of nozzle lines = [], polys = [], remain = [], diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index d2bb8897..55808023 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -1130,12 +1130,13 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { if (np && np.distTo2D(new Point(trace[0].x, trace[0].y)) > retractDist) { retract(); } + let outputSpeed = trace.shell === 1 || trace.shell === 1.5 ? finishSpeed : printSpeed; for (let pt of trace) { np = new Point(pt.x, pt.y, z); if (pt === trace[0]) { print.addOutput(preout, np, 0, moveSpeed); } else { - print.addOutput(preout, np, ((pt.r * 2) / nozzleSize) * shellMult, printSpeed); + print.addOutput(preout, np, ((pt.r * 2) / nozzleSize) * shellMult, outputSpeed); } } } @@ -1237,10 +1238,10 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { start = 0, skip = false, lastIndex = -1, - raft = opt.raft || false, + raft = opt.raft ?? false, flow = opt.flow || 1, - near = opt.near || (antiBacklash ? false : true), - fast = opt.fast || false, // support infill only! + near = opt.near ?? (antiBacklash ? false : true), + fast = opt.fast ?? false, // support infill only! fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow, thinDist = near ? thinWall : thinWall; From cbfdde341a6a5db685eac4ca0c0f5e4bc626b7d4 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 02:19:31 -0400 Subject: [PATCH 32/49] rename thin wall implementations --- src/kiri/core/consts.js | 6 +++--- src/kiri/mode/fdm/post.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/kiri/core/consts.js b/src/kiri/core/consts.js index a332391e..748fa4e4 100644 --- a/src/kiri/core/consts.js +++ b/src/kiri/core/consts.js @@ -115,9 +115,9 @@ const LISTS = { ], thin: [ { name: "off" }, - { name: "type 1" }, - { name: "type 2" }, - { name: "type 3" } + { name: "adaptive" }, + { name: "legacy 1" }, + { name: "legacy 2" } ] }; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 3ff454d7..7c321bf6 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -601,13 +601,13 @@ export function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = { } }); let ret = { last, gaps }; switch (opt.thinType) { - case "type 1": + case "legacy 1": ret = thin_type_1({ z, top, count, top_poly, offsetN, fillOffset }); break; - case "type 2": + case "legacy 2": ret = thin_type_2({ z, top, count, top_poly, offset1, offsetN, fillOffset, gaps, wasm }); break; - case "type 3": + case "adaptive": ret = thin_type_3({ z, top, count, offsetN }); break; default: From e8d5c9e743da31b6bdbca8b35060c4bead338a54 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 09:48:25 -0400 Subject: [PATCH 33/49] bump rev 4.3.1 and fix thinwall closed poly --- package.json | 2 +- src/geo/polygon.js | 6 ++- src/kiri/mode/fdm/post.js | 88 +++++++++++++++++++------------------- src/kiri/mode/fdm/slice.js | 2 +- src/moto/license.js | 4 +- web/kiri/manifest.json | 2 +- 6 files changed, 54 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 5e9bdcc9..82e76bb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.3.0", + "version": "4.3.1", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 7eee6ad0..2a2be748 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1105,7 +1105,11 @@ export class Polygon { } closeIf(dist = 1) { - if (this.first().distTo2D(this.last()) <= dist) { + let closeDist = this.first().distTo2D(this.last()); + if (closeDist < 0.001) { + this.points.pop(); + return this.setClosed(); + } else if (closeDist <= dist) { return this.setClosed(); } else { return this.setOpen(); diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 7c321bf6..8bf789ff 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -287,50 +287,50 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { // micro stepping insets. the same intersect technique used with // medial axis can be applied to this structure to produce an // equivalent output. not as computationally efficient, but much simpler. - if (brute) { - let { intersectRayLine } = base.util; - let tests = insets.slice(1); - let source = POLY.flatten(insets[0], []); - for (let poly of source) { - // segment the smallest inset and test against all other insets - poly.segment(minR).forEachSegment((p1, p2) => { - let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; - let len = pointDist(p1, p2); - let dx = (p1.x - p2.x) / len; - let dy = (p1.y - p2.y) / len; - let np2 = { x: np1.x + dy * 10, y: np1.y - dx * 10 }; - let max = { dtotl: poly.dtotl, dist: Infinity, maxd: Infinity }; - let term = false; - outer: for (let test of tests) { - for (let tpoly of POLY.flatten(test, [])) { - tpoly.forEachSegment((tp1, tp2) => { - let int = intersectRayLine(np1, { dx: dy, dy: -dx }, tp1, tp2); - if (int && int.dist > int.p1.poly.dtotl) { - return; - } - if (int && int.dist <= max.maxd && int.p1.poly.dtotl > max.dtotl) { - max.dtotl = int.p1.poly.dtotl; - max.dist = int.dist; - max.int = int; - } else if (int && int.dist <= max.maxd && int.p1.poly.dtotl === max.dtotl && int.dist < max.dist) { - max.dtotl = int.p1.poly.dtotl; - max.dist = int.dist; - max.int = int; - } - }); - if (term) { - break outer; - } - } - } - if (max.int) { - lines.push(new Point(np1.x, np1.y)); - lines.push(new Point(max.int.x, max.int.y)); - } - }); - } - return { lines, polys }; - } + // if (brute) { + // let { intersectRayLine } = base.util; + // let tests = insets.slice(1); + // let source = POLY.flatten(insets[0], []); + // for (let poly of source) { + // // segment the smallest inset and test against all other insets + // poly.segment(minR).forEachSegment((p1, p2) => { + // let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; + // let len = pointDist(p1, p2); + // let dx = (p1.x - p2.x) / len; + // let dy = (p1.y - p2.y) / len; + // let np2 = { x: np1.x + dy * 10, y: np1.y - dx * 10 }; + // let max = { dtotl: poly.dtotl, dist: Infinity, maxd: Infinity }; + // let term = false; + // outer: for (let test of tests) { + // for (let tpoly of POLY.flatten(test, [])) { + // tpoly.forEachSegment((tp1, tp2) => { + // let int = intersectRayLine(np1, { dx: dy, dy: -dx }, tp1, tp2); + // if (int && int.dist > int.p1.poly.dtotl) { + // return; + // } + // if (int && int.dist <= max.maxd && int.p1.poly.dtotl > max.dtotl) { + // max.dtotl = int.p1.poly.dtotl; + // max.dist = int.dist; + // max.int = int; + // } else if (int && int.dist <= max.maxd && int.p1.poly.dtotl === max.dtotl && int.dist < max.dist) { + // max.dtotl = int.p1.poly.dtotl; + // max.dist = int.dist; + // max.int = int; + // } + // }); + // if (term) { + // break outer; + // } + // } + // } + // if (max.int) { + // lines.push(new Point(np1.x, np1.y)); + // lines.push(new Point(max.int.x, max.int.y)); + // } + // }); + // } + // return { lines, polys }; + // } let pointMap = new Map(); let lineMap = new Map(); diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index 291dcf24..41472f5d 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -1009,7 +1009,7 @@ function doRender(slice, isSynth, params, opt = {}) { .setLayer("walls", Color.thin) .addPolys(top.thin_wall.map(a => a.length === 1 ? - newPolygon().centerCircle(a[0], a[0].r, 6) : + newPolygon().centerCircle(a[0], a[0].r/2, 12) : newPolygon(a.map(p => newPoint(p.x, p.y))) .setZ(slice.z) .closeIf(height) diff --git a/src/moto/license.js b/src/moto/license.js index ae5ed98c..21ca7569 100644 --- a/src/moto/license.js +++ b/src/moto/license.js @@ -3,9 +3,9 @@ const terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: "4.3.0" + VERSION: "4.3.1" }; -export const beta = 1; +export const beta = 0; export const license = terms; export const version = terms.VERSION; diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index ce4522be..051f5f4c 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.3.0", + "name": "Kiri:Moto 4.3.1", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/", From 3a4aa465da7ab2ac507e61fff227f3b66ed1430d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 12:09:49 -0400 Subject: [PATCH 34/49] fixup for origin point finder for high res meshes --- src/kiri/mode/cam/cl-origin.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/cam/cl-origin.js b/src/kiri/mode/cam/cl-origin.js index 5b63f8ac..c694f078 100644 --- a/src/kiri/mode/cam/cl-origin.js +++ b/src/kiri/mode/cam/cl-origin.js @@ -25,6 +25,16 @@ export function originSelect() { env.hover = pointHover; env.hoverUp = pointHoverUp; + // less precise point key for hi-res meshes that leads to dups + function updateKey(pt) { + pt._key = [ + ((pt.x * 1000) | 0), + ((pt.y * 1000) | 0), + ((pt.z * 1000) | 0) + ].join(' '); + return pt; + } + CAM.traces((ids) => { api.hide.alert(alert); alert = api.show.alert("select origin
[esc] to cancel", 1000); @@ -53,14 +63,14 @@ export function originSelect() { }); for (let poly of polys) { if (poly.circularity() >= 0.99) { - let center = poly.calcCircleCenter(); + let center = updateKey(poly.calcCircleCenter()); if (points[center.key]) { continue; } addpoint(widget, center); } else if (poly.isOpen()) { - incpoint(widget, poly.first()); - incpoint(widget, poly.last()); + incpoint(widget, updateKey(poly.first())); + incpoint(widget, updateKey(poly.last())); } } }); From dfa2308224f4895f74195f9af10744fba9ca3981 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 12:27:56 -0400 Subject: [PATCH 35/49] add todo/note for single wall noodle and claim system --- src/kiri/mode/fdm/post.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 8bf789ff..6f002388 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -468,6 +468,10 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { let len = Math.ceil(pop.length / 2); // check if segment was claimed by a different originating segment // for odd wall counts so that they're not emitted twice + // todo: failing for noodle width single wall on circular objects + // update claim system to eliminate overlapping close singles + // and/or extend segment definition to curve within angle deviation + // instead of strict point/point segments if (odd && mp1.claimed && mp1.claimed !== p1.segment) len--; if (len === 0) { nupoly.push(new Point(min.x, min.y)); From 6bf15373b6960303f229fd29a38d2f509f17d51b Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 18:07:35 -0400 Subject: [PATCH 36/49] add slice bridge annotations to slow print. fix thin shell depth for identifying outside faces --- src/kiri/mode/fdm/post.js | 47 +++++++++++++++++++++++------------- src/kiri/mode/fdm/prepare.js | 10 ++++++-- src/kiri/mode/fdm/slice.js | 13 +++++++++- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 6f002388..9a9d43e4 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -169,13 +169,15 @@ function thin_type_3(params) { // produce trace from outside of poly inward no more than max inset let noodleWidth = offsetN * count; let { noodle, remain } = top.poly.noodle(noodleWidth); + for (let n of POLY.flatten(noodle).sort((a,b) => a.depth - b.depth)) { + n.shell = [1,-1][Math.floor(n.depth / 2)]; + // console.log('map', n.depth, n.shell, n.length, n.area().round(3)); + } // re-expand inner offset so fill offsets align properly with expectation remain = POLY.offset(remain, offsetN / 2); - // render inset "noodle" - // shells.appendAll(noodle); - + let debugNoodle = false; let debugRemain = false; let debugExtrusion = false; @@ -190,11 +192,14 @@ function thin_type_3(params) { let lines = []; let polys = []; - for (let i=0; i b.area() - a.area())) trace_noodle( [ n ], noodleWidth, @@ -202,7 +207,7 @@ function thin_type_3(params) { i === 0 ? midR : midR * 1.25, i === 0 ? maxR : maxF, { - shell: i + 1, + shell: n.shell, lines, polys, remain: next, @@ -234,9 +239,17 @@ function thin_type_3(params) { if (polys.length) shells.push(...polys); // create shell depth annotation that will survive serialization - top.thin_sort = traces.map(a => a.shell); + top.thin_sort = traces.map(t => t.shell); - // console.log({ this: (self.kiri_minion?.name ?? self.kiri_worker), traces }); + // remove point.segment markers for codec encoding between minion and worker + for (let trace of traces) { + for (let pt of trace) { + delete pt.s; + } + } + + // console.log({ traces: traces.map(t => [t.length, t.shell]) }); + // console.log({ this: (self.kiri_minion?.name ?? self.kiri_worker), traces, polys }); return { traces, last: remain }; } @@ -255,6 +268,8 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { shell = 0 } = opt; + // increment shell with direction (1=outside, -1=inside) + let shellAdd = shell > 0 ? 1 : -1; let sstep = 1 / shellStep; let insets = []; let scale = 1000; @@ -265,6 +280,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { join: ClipperLib.JoinType.jtRound, arc: (1/dstep) * 4 }); + while (inset && inset.length) { dtotl += dstep; for (let p of POLY.flatten(inset, [])) { @@ -434,9 +450,10 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { if (poly.parent) { inner.push(nupoly); } + let sadd = shellAdd > 0 ? (poly.parent ? shellAdd : 0) : (poly.parent ? 0 : shellAdd); let ltpo; let trace = []; - trace.shell = poly.parent ? shell : -shell; // shell number (- for inner/hole) + trace.shell = shell + sadd; poly.segment(minR, true).forEachSegment((p1, p2) => { let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let len = pointDist(p1, p2); @@ -548,19 +565,15 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) { let ret = POLY.nest( POLY.flatten(setA, [], true).map(p => { return p.clean().simplify(); - }).flat() + }).flat().map(p => { + p.shell = shell + shellAdd; + return p; + }) ).filter(p => p.area() >= minArea); remain.push(...ret); } - // remove point.segment markers for codec encoding between minion and worker - for (let trace of traces) { - for (let pt of trace) { - delete pt.s; - } - } - return { lines, polys, remain, traces }; } diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 55808023..96e65cdb 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -805,10 +805,10 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { shellOrder = {"out-in":-1,"in-out":1,"alternate":-2}[process.sliceShellOrder] || -1, sparseMult = process.outputSparseMult, coastDist = process.outputCoastDist || 0, - finishSpeed = opt.speed || process.outputFinishrate, firstShellSpeed = process.firstLayerRate, firstFillSpeed = process.firstLayerFillRate, firstPrintMult = process.firstLayerPrintMult, + finishSpeed = firstLayer ? firstShellSpeed : (opt.speed || process.outputFinishrate), printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate), fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate), infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed, @@ -834,15 +834,21 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { z = slice.z, lastPoly; - // support alternating shell order + // support alternating shell order if (Math.abs(shellOrder) > 1 && slice.index % 2 === 1) { shellOrder = -shellOrder; } + // update fill speed when solids detected if (slice.finishSolids) { fillSpeed = process.sliceSolidRate || finishSpeed; } + // override: adapt bridge layer speed + if (slice.isBridgeLayer) { + fillSpeed /= 2; + } + // apply first layer extrusion multipliers if (firstLayer) { fillMult *= firstPrintMult; diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index 41472f5d..8fa865d5 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -498,6 +498,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let spaceMult = slice.index === 0 ? process.firstLayerLineMult || 1 : 1; let isBottom = slice.index < bottomLayers; let isTop = topLayers && slice.index > slices.length - topLayers - 1; + let isTopBase = isTop && slice.index === slices.length - topLayers; let isDense = range.sliceFillSparse > 0.995; let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth; let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0; @@ -505,6 +506,10 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let fillSpace = fillSpacing * spaceMult * solidWidth; doSolidLayerFill(slice, fillSpace, sliceFillAngle); } + if (isTopBase) { + // mark the first top solid supporting layer as a bridge + slice.isBridgeLayer = true; + } sliceFillAngle = (sliceFillAngle + 90.0) % 360; }, "solid layers"); @@ -1386,7 +1391,10 @@ export function projectFlats(slice, count, expand) { export function projectBridges(slice, count) { if (!slice.up || !slice.bridges) return; // these flats are marked for finishing print speed - if (slice.bridges.length) slice.finishSolids = true; + if (slice.bridges.length) { + slice.finishSolids = true; + slice.isBridgeLayer = true; + } projectSolid(slice, slice.bridges, count, true, true); }; @@ -1746,6 +1754,9 @@ function fillSupportPolys(args) { * @returns {*} */ function projectSolid(slice, polys, count, up, first) { + if (slice && !up && count === 1) { + slice.isBridgeLayer = true; + } if (!slice || count <= 0) { return; } From 92cfe7f2bd548c9384bc337c7f977f1e73a53b89 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 19:22:03 -0400 Subject: [PATCH 37/49] slow down shells on flats layers --- src/kiri/mode/fdm/prepare.js | 5 +++++ src/kiri/mode/fdm/slice.js | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 96e65cdb..bd676ec2 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -844,6 +844,11 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) { fillSpeed = process.sliceSolidRate || finishSpeed; } + // on flats layers also slow down shell print speed + if (slice.isFlatsLayer) { + printSpeed = finishSpeed || printSpeed; + } + // override: adapt bridge layer speed if (slice.isBridgeLayer) { fillSpeed /= 2; diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index 8fa865d5..f0bc89e2 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -506,6 +506,9 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let fillSpace = fillSpacing * spaceMult * solidWidth; doSolidLayerFill(slice, fillSpace, sliceFillAngle); } + if (slice.index === slices.length - 1) { + slice.isFlatsLayer = true; + } if (isTopBase) { // mark the first top solid supporting layer as a bridge slice.isBridgeLayer = true; @@ -1378,8 +1381,9 @@ function addSolidFills(slice, polys) { export function projectFlats(slice, count, expand) { if (!slice.down || !slice.flats) return; // these flats are marked for finishing print speed - if (slice.flats.length) slice.finishSolids = true; - if (slice && slice.flats && slice.flats.length) { + if (slice.flats?.length) { + slice.finishSolids = true; + slice.isFlatsLayer = true; const flats = expand ? POLY.expand(slice.flats, expand) : slice.flats; projectSolid(slice, flats, count, false, true); } @@ -1391,7 +1395,7 @@ export function projectFlats(slice, count, expand) { export function projectBridges(slice, count) { if (!slice.up || !slice.bridges) return; // these flats are marked for finishing print speed - if (slice.bridges.length) { + if (slice.bridges?.length) { slice.finishSolids = true; slice.isBridgeLayer = true; } From d976461ed46cb15b6faed75599a985b94b30a880 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 21:38:02 -0400 Subject: [PATCH 38/49] code formatting --- src/kiri/mode/cam/cl-helical.js | 104 +++++++++++++-------------- src/kiri/mode/cam/slice.js | 124 ++++++++++++++++---------------- src/mesh/tool.js | 57 +++++++-------- 3 files changed, 142 insertions(+), 143 deletions(-) diff --git a/src/kiri/mode/cam/cl-helical.js b/src/kiri/mode/cam/cl-helical.js index 6894f0a6..f8102e94 100644 --- a/src/kiri/mode/cam/cl-helical.js +++ b/src/kiri/mode/cam/cl-helical.js @@ -5,59 +5,59 @@ import { clearPops, env } from "./client.js"; let alert, lastWidget; export let helicalOn = false; export function selectHelical() { - if (helicalOn) { - return helicalDone(); - } - clearPops(); - alert = api.show.alert("analyzing surfaces...", 1000); - let cylinders = env.poppedRec.cylinders; + if (helicalOn) { + return helicalDone(); + } + clearPops(); + alert = api.show.alert("analyzing surfaces...", 1000); + let cylinders = env.poppedRec.cylinders; - CAM.surface_prep(false, () => { - api.hide.alert(alert); - alert = api.show.alert("[esc] cancels surface selection"); - for (let [wid, arr] of Object.entries(cylinders)) { - let widget = api.widgets.forid(wid); - if ( widget && arr?.length ) - for (let faceid of arr) { - CAM.cylinderToggle(widget, faceid, (faceids) => { - // cylinders[widget.id] = faceids; - }); + CAM.surface_prep(false, () => { + api.hide.alert(alert); + alert = api.show.alert("[esc] cancels surface selection"); + for (let [wid, arr] of Object.entries(cylinders)) { + let widget = api.widgets.forid(wid); + if (widget && arr?.length) + for (let faceid of arr) { + CAM.cylinderToggle(widget, faceid, (faceids) => { + // cylinders[widget.id] = faceids; + }); + } + } + }); + helicalOn = env.hoveredOp; + helicalOn.classList.add("editing"); + api.feature.on_mouse_up = (obj, ev) => { + let { face } = obj; + let min = Math.min(face.a, face.b, face.c); + let faceid = min / 3; + let widget = (lastWidget = obj.object.widget); + CAM.cylinderToggle(widget, faceid, ({ faces, error }) => { + if (error) { + api.show.alert(error, 3000); + console.log(error); + return; + } + cylinders[widget.id] = faces; + }); + }; +}; + +export function helicalDone() { + if (!(helicalOn && env.poppedRec && env.poppedRec.cylinders)) { + return; + } + let { cylinders } = env.poppedRec; + for (let wid of Object.keys(cylinders)) { + let widget = api.widgets.forid(wid); + if (widget) { + CAM.cylinderClear(widget); + } else { + delete cylinders[wid]; } } - }); - helicalOn = env.hoveredOp; - helicalOn.classList.add("editing"); - api.feature.on_mouse_up = (obj, ev) => { - let { face } = obj; - let min = Math.min(face.a, face.b, face.c); - let faceid = min / 3; - let widget = (lastWidget = obj.object.widget); - CAM.cylinderToggle(widget, faceid, ({ faces, error }) => { - if ( error ) { - api.show.alert(error, 3000); - console.log( error ); - return; - } - cylinders[widget.id] = faces; - }); - }; -}; - -export function helicalDone(){ - if (!( helicalOn && env.poppedRec && env.poppedRec.cylinders )) { - return; - } - let { cylinders } = env.poppedRec; - for (let wid of Object.keys(cylinders)) { - let widget = api.widgets.forid(wid); - if (widget) { - CAM.cylinderClear(widget); - } else { - delete cylinders[wid]; - } - } - api.hide.alert(alert); - api.feature.on_mouse_up = undefined; - helicalOn.classList.remove("editing"); - helicalOn = false; + api.hide.alert(alert); + api.feature.on_mouse_up = undefined; + helicalOn.classList.remove("editing"); + helicalOn = false; }; diff --git a/src/kiri/mode/cam/slice.js b/src/kiri/mode/cam/slice.js index cf77b042..0d8f884d 100644 --- a/src/kiri/mode/cam/slice.js +++ b/src/kiri/mode/cam/slice.js @@ -510,8 +510,7 @@ export async function traces(settings, widget) { widget.traces = polylines; return true; -}; - +} /** * Returns an array of arrays of perpindicular triangles in the mesh. @@ -523,110 +522,109 @@ export async function traces(settings, widget) { * @param {object} opts - options object * @return {array} - array of arrays of triangle data */ -export async function cylinders(settings, widget, opts){ - - let {} = opts ?? {} - - let {array:verts} = widget.mesh.geometry.attributes.position - let perpTriangles = [] +export async function cylinders(settings, widget, opts) { + let { } = opts ?? {}; + let { array: verts } = widget.mesh.geometry.attributes.position; + let perpTriangles = []; //iterate over all triangles - for(let i = 0; i < verts.length; i+=9){ - let a = [verts[i], verts[i+1], verts[i+2]], - b = [verts[i+3], verts[i+4], verts[i+5]], - c = [verts[i+6], verts[i+7], verts[i+8]]; + for (let i = 0; i < verts.length; i += 9) { + let a = [verts[i], verts[i + 1], verts[i + 2]], + b = [verts[i + 3], verts[i + 4], verts[i + 5]], + c = [verts[i + 6], verts[i + 7], verts[i + 8]]; //calculate normal - let normal = new THREE.Vector3(a[0]-b[0], a[1]-b[1], a[2]-b[2]) - .cross(new THREE.Vector3(b[0]-c[0], b[1]-c[1], b[2]-c[2])) - .normalize() + let normal = new THREE.Vector3(a[0] - b[0], a[1] - b[1], a[2] - b[2]) + .cross(new THREE.Vector3(b[0] - c[0], b[1] - c[1], b[2] - c[2])) + .normalize(); // if perpindicular normal, and at least 2 Zs are the same - if(normal.z.round(5) == 0 && ! (a[2] != b[2] && b[2] != c[2])){ + if (normal.z.round(5) == 0 && !(a[2] != b[2] && b[2] != c[2])) { let minZ = Math.min(a[2], b[2], c[2]), maxZ = Math.max(a[2], b[2], c[2]); - if(minZ == maxZ){ // all Zs are the same indicated malformed geometry + if (minZ == maxZ) { // all Zs are the same indicated malformed geometry continue; } - perpTriangles.push({...[a,b,c], normal,minZ,maxZ,i:index}) + perpTriangles.push({ ...[a, b, c], normal, minZ, maxZ, i: index }); } } //map where zmax, zmin -> triangleData - let cylinderTriangles = new Map() - for(let t of perpTriangles){ - let hash = `${t.minZ.round(5)},${t.maxZ.round(5)}` - if(!cylinderTriangles.has(hash)){ - cylinderTriangles.set(hash,[]) + let cylinderTriangles = new Map(); + for (let t of perpTriangles) { + let hash = `${t.minZ.round(5)},${t.maxZ.round(5)}`; + if (!cylinderTriangles.has(hash)) { + cylinderTriangles.set(hash, []); } - cylinderTriangles.get(hash).push(t) + cylinderTriangles.get(hash).push(t); } - - return Array.from(cylinderTriangles.values()) + + return Array.from(cylinderTriangles.values()); } - - /** - * Find all triangles that are part of the same cylinder as the given triangle - * @param {Widget} widget - widget object - * @param {number} face - index of face in triangle vertex data - * @return {array} - array of all triangle data that belong to the same cylinder - * @requires surface_prep must be called first - */ +/** + * Find all triangles that are part of the same cylinder as the given triangle + * @param {Widget} widget - widget object + * @param {number} face - index of face in triangle vertex data + * @return {array} - array of all triangle data that belong to the same cylinder + * @requires surface_prep must be called first + */ export function cylinder_find (widget, face){ CAM.surface_prep(widget,false); return widget.tool.findCylinderSurface(face); } -export function cylinder_poly_find(widget, face){ +export function cylinder_poly_find(widget, face) { let faces = cylinder_find(widget, face); let vert = widget.getGeoVertices({ unroll: true, translate: true }).map(v => v.round(4)); - let slicer = new cam_slicer(widget,{}) - let opts = { - dedup:false, - edges: false, - over:true - } - let firstOffset = faces[0]*9 - let [x1,y1,z1,x2,y2,z2,x3,y3,z3]= Array.from(vert.subarray(firstOffset,firstOffset+9)) - let zs = [z1,z2,z3].map(z => z.round(5)) + let slicer = new cam_slicer(widget, {}); + let firstOffset = faces[0] * 9; + let [x1, y1, z1, x2, y2, z2, x3, y3, z3] = Array.from(vert.subarray(firstOffset, firstOffset + 9)); + let zs = [z1, z2, z3].map(z => z.round(5)); let zmin = Math.min(...zs); let zmax = Math.max(...zs); - let zmid = (zmin+zmax)/2 - let cylVerts = new Float32Array(faces.length*9) - + let zmid = (zmin + zmax) / 2; + let cylVerts = new Float32Array(faces.length * 9); + let opts = { + dedup: false, + edges: false, + over: true + }; + let i = 0 //for each cylinder face index - for(let index of faces){ - let off = index*9 + for (let index of faces) { + let off = index * 9; //copy the 9 triangle vertices floats in - for(let j = 0;j < 9;j++){ - cylVerts[i] = vert[off+j] - i++ + for (let j = 0; j < 9; j++) { + cylVerts[i] = vert[off + j]; + i++; } } + //slice the poly at the midpoint - let poly = slicer.sliceZ(zmid, cylVerts,opts)?.polys?.at(0) - if(!poly) throw "slicing returned no poly" + let poly = slicer.sliceZ(zmid, cylVerts, opts)?.polys?.at(0); + if (!poly) throw "slicing returned no poly"; + let circular = poly.circularity() > 0.98; + //throw error if not circular - if(!circular) throw "faces must be circular" - + if (!circular) throw "faces must be circular"; + //calculate circle properties let area = poly.area(), - diam = Math.sqrt(area / Math.PI)* 2, + diam = Math.sqrt(area / Math.PI) * 2, center = poly.calcCircleCenter(); - - //find direction using normal to center point + //find direction using normal to center point let point = newPoint(x1, y1, z1), delta = point.setZ(center.z).sub(center), - normal = new THREE.Vector3(x1-x2, y1-y2, z1-z2) - .cross(new THREE.Vector3(x2-x3, y2-y3, z2-z3)), + normal = new THREE.Vector3(x1 - x2, y1 - y2, z1 - z2) + .cross(new THREE.Vector3(x2 - x3, y2 - y3, z2 - z3)), dotProd = normal.dot(delta), - interior = dotProd < 0 + interior = dotProd < 0 - return {faces, zmin, zmax, poly, circular, area, diam, center,interior} + return { faces, zmin, zmax, poly, circular, area, diam, center, interior } } /** diff --git a/src/mesh/tool.js b/src/mesh/tool.js index 41db2f8b..bc80aa6a 100644 --- a/src/mesh/tool.js +++ b/src/mesh/tool.js @@ -241,13 +241,12 @@ class MeshTool { }; } -/** - * Retrieves the adjacent faces for a given face index. - * - * @param {number} face - The index of the face for which adjacent faces are to be found. - * @returns {number[]} An array of face indices that are adjacent to the given face. - */ - + /** + * Retrieves the adjacent faces for a given face index. + * + * @param {number} face - The index of the face for which adjacent faces are to be found. + * @returns {number[]} An array of face indices that are adjacent to the given face. + */ getAdjacentFaces(face) { const { faces, sides } = this.getIndex(); const foff = face * 6; @@ -319,31 +318,31 @@ class MeshTool { * 3. Filtering faces with normals perpendicular to Z axis * 4. Recursively adding adjacent faces with the same Z values and normal perpendicular to Z axis */ - findCylinderSurface(face){ - face = Number(face) - - const radianTolerance = 8 * (Math.PI/180); + findCylinderSurface(face) { + face = Number(face); + const radianTolerance = 8 * (Math.PI / 180); const vertexTolerance = 1e-4; - //helper function - const dissimilar = (a,b,c) => Math.abs(a-b) > vertexTolerance && - Math.abs(b-c) > vertexTolerance && - Math.abs(a-c) > vertexTolerance; + + // helper function + const dissimilar = (a, b, c) => Math.abs(a - b) > vertexTolerance && + Math.abs(b - c) > vertexTolerance && + Math.abs(a - c) > vertexTolerance; let found = {}, out = [face], - {vertices} = this, - {faces} = this.getIndex(), - faceOffset = face*9, - zs = [2,5,8].map(i => vertices[faceOffset+i].round(5)), //get zs from face vertices + { vertices } = this, + { faces } = this.getIndex(), + faceOffset = face * 9, + zs = [2, 5, 8].map(i => vertices[faceOffset + i].round(5)), //get zs from face vertices lowZ = Math.min(zs), highZ = Math.max(zs), checked = {}, check = [face]; - - //check for errors, and throw if found - if(dissimilar(...zs)){ + + // check for errors, and throw if found + if (dissimilar(...zs)) { throw `face must have only 2 Z values. found: ${zs.join(", ")}` - }else if(Math.abs( faces[face * 6 + 2] > 1e-6 )){ + } else if (Math.abs(faces[face * 6 + 2] > 1e-6)) { throw "face's normal must be perpendicular to Z axis" } @@ -352,11 +351,12 @@ class MeshTool { const face = check.shift(); const froot = face * 6; const vroot = face * 9 - + // filter faces to those perpendicular to +z axis - if ( Math.abs( faces[froot + 2] > 1e-6 )) continue; - //filter faces with verts don't share the z values of the first face - if( + if (Math.abs(faces[froot + 2] > 1e-6)) continue; + + // filter faces with verts don't share the z values of the first face + if ( !(vertices[vroot + 2] !== lowZ || vertices[vroot + 2] !== highZ) && !(vertices[vroot + 5] !== lowZ || vertices[vroot + 5] !== highZ) && !(vertices[vroot + 8] !== lowZ || vertices[vroot + 8] !== highZ) @@ -369,7 +369,7 @@ class MeshTool { } const aroot = f * 6; let sum = 0; - for (let i=0; i<3; i++) { + for (let i = 0; i < 3; i++) { sum += Math.pow(faces[froot + i] - faces[aroot + i], 2); } const fn = Math.sqrt(sum); @@ -381,6 +381,7 @@ class MeshTool { } } } + return out; } From cb0344fb7022e6db3a4ceb6b5a83ceab0be6a88a Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 23:18:08 -0400 Subject: [PATCH 39/49] fix LANG api ref for extruders --- src/kiri/core/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/core/settings.js b/src/kiri/core/settings.js index 1a5b65b0..5efa6a30 100644 --- a/src/kiri/core/settings.js +++ b/src/kiri/core/settings.js @@ -286,7 +286,7 @@ function updateFieldsFromSettings(setrec, uirec = api.ui, opt = {}) { function updateExtruderFields(device) { const { ui } = api; - const { LANG } = api.const; + const { current: LANG } = api.const.LANG; if (device.extruders && device.extruders[device.internal]) { updateFieldsFromSettings(device.extruders[device.internal]); ui.extruder.innerHTML = From bcadc9dfb9270019c64040c5778ef2d9bbc52a9a Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 22 Sep 2025 23:39:45 -0400 Subject: [PATCH 40/49] reorder fdm menus, add solid fill sub-menu --- src/kiri/core/conf.js | 2 ++ src/kiri/mode/fdm/init-menu.js | 15 +++++++-------- web/kiri/index.html | 1 + web/kiri/lang/en.js | 21 ++++++++++++--------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index d8f38f30..3153c01f 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -949,7 +949,9 @@ export const conf = { hidden: { "fdm-base": true, "fdm-cool": true, + "fdm-heat": true, "fdm-fill": true, + "fdm-solid": true, "fdm-supp": true, "fdm-xprt": true }, diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index 73b93a1f..5320c367 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -82,8 +82,11 @@ export function menu() { sliceDetectThin: newSelect(LANG.ad_thin_s, { title: LANG.ad_thin_l, action: thinWallSave }, "thin"), outputAlternating: newBoolean(LANG.ad_altr_s, onBooleanClick, {title:LANG.ad_altr_l}), sliceZInterleave: newBoolean(LANG.ad_zint_s, onBooleanClick, {title:LANG.ad_zint_l, show:zIntShow}), - separator: newBlank({ class:"set-sep", driven }), + _____: newGroup(LANG.fs_menu, $('fdm-solid'), { modes:FDM, driven, hideable, separator, group:"fdm-solid" }), + sliceFillAngle: newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:toFloat}), sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0)}), + sliceFillGrow: newInput(LANG.fi_grow_s, {title:LANG.fi_grow_l, convert:toFloat}), + sliceSolidMinArea: newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:toFloat}), _____: newGroup(LANG.fi_menu, $('fdm-fill'), { modes:FDM, driven, hideable, separator, group:"fdm-fill" }), sliceFillType: newSelect(LANG.fi_type, {trigger}, "infill"), sliceFillSparse: newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:toFloat, bound:bound(0.0,1.0), show:hasInfill}), @@ -91,9 +94,6 @@ export function menu() { separator: newBlank({ class:"set-sep", driven }), sliceFillRate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt, bound:bound(0,500)}), sliceSolidRate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt, bound:bound(0,500)}), - separator: newBlank({ class:"set-sep", driven }), - sliceFillGrow: newInput(LANG.fi_grow_s, {title:LANG.fi_grow_l, convert:toFloat}), - sliceFillAngle: newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:toFloat}), _____: newGroup(LANG.fh_menu, $('fdm-heat'), { modes:FDM, driven, hideable, separator, group:"fdm-heat" }), outputTemp: newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:toInt}), outputBedTemp: newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:toInt}), @@ -172,16 +172,15 @@ export function menu() { outputBeltFirst: newBoolean(LANG.ad_lbir_s, onBooleanClick, {title:LANG.ad_lbir_l, show:isBelt}), _____: newGroup(LANG.ad_menu, $('fdm-expert'), { modes:FDM, driven, hideable, separator, group:"fdm-xprt" }), sliceAngle: newInput(LANG.sl_angl_s, {title:LANG.sl_angl_l, convert:toFloat, show:isBelt}), + antiBacklash: newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, convert:toFloat, bound:bound(0,3)}), + arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}), outputRetractDwell: newInput(LANG.ad_rdwl_s, {title:LANG.ad_rdwl_l, convert:toInt}), - sliceSolidMinArea: newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:toFloat}), outputMinSpeed: newInput(LANG.ad_mins_s, {title:LANG.ad_mins_l, convert:toFloat, bound:bound(1,200)}), + outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}), outputShortPoly: newInput(LANG.ad_spol_s, {title:LANG.ad_spol_l, convert:toFloat, bound:bound(0,10000)}), outputCoastDist: newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, convert:toFloat, bound:bound(0.0,10)}), zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}), - arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}), - antiBacklash: newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, convert:toFloat, bound:bound(0,3)}), outputLoops: newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, convert:toInt, bound:bound(-1,1000), show:isBelt}), - outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}), fdmRanges: $('fdm-ranges'), diff --git a/web/kiri/index.html b/web/kiri/index.html index 91431e16..a72a261a 100644 --- a/web/kiri/index.html +++ b/web/kiri/index.html @@ -337,6 +337,7 @@
+
diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index fdaa35b3..a9b61cf5 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -332,14 +332,14 @@ self.lang['en-us'] = { se_save: "save", // FDM LAYERS - sl_menu: "paths", + sl_menu: "layers", sl_angl_s: "slice angle", sl_angl_l: ["slice angle bias in degrees"], sl_lahi_s: "layer height", sl_lahi_l: ["height of each slice","layer in millimeters"], ad_minl_s: "layer minimum", ad_minl_l: ["adaptive min layer height","in millimeters","must be non-zero"], - ad_adap_s: "adaptive", + ad_adap_s: "adaptive height", ad_adap_l: ["use adaptive layer heights","with 'layer height' as max","and 'layer min' as the min"], sl_ltop_s: "top layers", sl_ltop_l: ["number of solid layers at the top of the print or on flat areas facing up"], @@ -363,6 +363,15 @@ self.lang['en-us'] = { sl_strt_s: "layer start", sl_strt_l: ["layer starting point","last = last layer end","center = part center","origin = device origin"], + // FDM SOLID + fs_menu: "solid fill", + fi_over_s: "shell overlap", + fi_over_l: ["overlap with shell and other fill","for improved extrusion bonding","as fraction of nozzle diameter","0.0 - 2.0"], + fi_angl_s: "start angle", + fi_angl_l: ["starting angle in degrees","90 degrees added to ","each following layer","applies only to solid layers"], + fi_grow_s: "area expand", + fi_grow_l: ["expand projected solids","can help with solid areas","over sparse infill areas","units in millimeters"], + // FDM FILL fi_menu: "sparse fill", fi_type: "fill type", @@ -370,12 +379,6 @@ self.lang['en-us'] = { fi_pcnt_l: ["fill density values","0.0 - 1.0"], fi_rept_s: "fill repeat", fi_rept_l: ["number of times to repeat","unchanged fill layers"], - fi_angl_s: "solid angle", - fi_angl_l: ["starting angle in degrees","90 degrees added to ","each following layer","applies only to solid layers"], - fi_grow_s: "solid expand", - fi_grow_l: ["expand projected solids","can help with solid areas","over sparse infill areas","units in millimeters"], - fi_over_s: "fill overlap", - fi_over_l: ["overlap with shell and other fill","for improved extrusion bonding","as fraction of nozzle diameter","0.0 - 2.0"], // fi_rate_s: "print speed", fi_rate_l: ["extrusion speed for infill","set to 0 to use default","output print speeds"], @@ -855,7 +858,7 @@ self.lang['en-us'] = { ad_rdwl_l: ["time between re-engaging","filament and movement","in milliseconds"], ad_scst_s: "shell coast", ad_scst_l: ["non-printing end","of perimeter shells","in millimeters"], - ad_msol_s: "min solid", + ad_msol_s: "min area", ad_msol_l: ["minimum area (mm^2)","required to keep solid","must be > 0.1"], ad_mins_s: "min speed", ad_mins_l: ["minimum speed","for short segments"], From 9138e219ea83ed227bf61776f1e992931ec2c67f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 23 Sep 2025 18:33:03 -0400 Subject: [PATCH 41/49] fixes #429 outline refresh and stock size issues --- src/kiri/core/widget.js | 5 +++++ src/kiri/mode/cam/slice.js | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/kiri/core/widget.js b/src/kiri/core/widget.js index 3e340433..783598ca 100644 --- a/src/kiri/core/widget.js +++ b/src/kiri/core/widget.js @@ -360,6 +360,10 @@ class Widget { this.setOpacity(opacity ?? 1); } + refreshVisualState() { + this.setVisualState(this.getVisualState()); + } + restoreVisualState() { if (this.cache.vizstate) { this.setVisualState(this.cache.vizstate); @@ -613,6 +617,7 @@ class Widget { this.roto = []; this.center(); this.setModified(); + this.refreshVisualState(); } mirror() { diff --git a/src/kiri/mode/cam/slice.js b/src/kiri/mode/cam/slice.js index 0d8f884d..2c9f454f 100644 --- a/src/kiri/mode/cam/slice.js +++ b/src/kiri/mode/cam/slice.js @@ -132,15 +132,17 @@ export async function cam_slice(settings, widget, onupdate, ondone) { } if (stock.x && stock.y && stock.z && !isIndexed) { - if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) { + let maxDelta = 1e-3; + + if (stock.x + maxDelta < (bounds.max.x - bounds.min.x)) { return error('stock X too small for part. resize stock or use offset stock'); } - if (stock.y + 0.00001 < bounds.max.y - bounds.min.y) { + if (stock.y + maxDelta < (bounds.max.y - bounds.min.y)) { return error('stock Y too small for part. resize stock or use offset stock'); } - if (stock.z + 0.00001 < bounds.max.z - bounds.min.z) { + if (stock.z + maxDelta < (bounds.max.z - bounds.min.z)) { return error('stock Z too small for part. resize stock or use offset stock'); } } From 90c29d1aa74f0e645865f6cb892fc3e20e60ba03 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 23 Sep 2025 18:48:05 -0400 Subject: [PATCH 42/49] fixes #430 by providing an enclosing element height and aspect ratio --- web/kiri/index.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/kiri/index.css b/web/kiri/index.css index bb06d38e..5b39165b 100644 --- a/web/kiri/index.css +++ b/web/kiri/index.css @@ -1636,8 +1636,10 @@ details[open] summary::after { background-color: rgba(255,255,255,0.5); position: absolute; z-index: 30; - bottom: -20px; + bottom: calc(-4px - 1em); right: 0; + height: 1em; + aspect-ratio: 1 / 1 !important; } .mod-x svg { aspect-ratio: 1 / 1 !important; From 457c03766c69ced8b58f5c329d0fe4436adb6eea Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 23 Sep 2025 20:20:34 -0400 Subject: [PATCH 43/49] fix adding supports from underneath platform looking up --- src/kiri/mode/fdm/client.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/kiri/mode/fdm/client.js b/src/kiri/mode/fdm/client.js index a7471a5d..7bb6d5cf 100644 --- a/src/kiri/mode/fdm/client.js +++ b/src/kiri/mode/fdm/client.js @@ -426,7 +426,7 @@ export function init() { let idn = new Raycaster(rp, dn) .intersectObjects(targets, false) .filter(t => !t.object.pillar); - if (idn.length && idn.length % 2 === 0) { + if (idn.length) { iw = iup[0].object.widget || iw; let wa = api.widgets.annotate(iw.id); let ws = (wa.support = wa.support || []); @@ -471,10 +471,11 @@ export function init() { iw = null; } p1 = point; + // looking up by default (assumes platform intersect) let dir = new Vector3(0,1,0) let ray = new Raycaster(point, dir); let rz = int && int.face ? Math.atan2(int.face.normal.y, int.face.normal.x) : 0; - // when on object, project down on downward faces + // when intersecting underside of object, project down if (int && int.face && int.face.normal.z < -0.1) { dir.y = -1; } @@ -482,7 +483,7 @@ export function init() { let i2 = ray.intersectObjects(targets, false) .filter(t => !t.object.pillar) // eliminate other pillars .filter(i => i.distance > 0.1); // false matches close to origin of ray - if (i2.length && i2.length % 2 === 0) { + if (i2.length) { p2 = i2[0].point; iw = i2[0].object.widget || iw; let p1y = Math.max(0, p1.y); From f8180fe79a6c5f15d44c988570fe60eb79dac13a Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 24 Sep 2025 01:18:19 -0400 Subject: [PATCH 44/49] mock up basic thinwall w/out medial axis --- src/kiri/core/print.js | 10 ++++++---- src/kiri/mode/fdm/post.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index d0487b52..7b6c642c 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -138,16 +138,17 @@ class Print { } poly.forEachPoint((point, pos, points, count) => { - if (first) { - if (options.onfirst) { + if (first || point.skip) { + // if (point.skip) console.log({ skip: point }); + if (first && options.onfirst) { options.onfirst(point); } // move to first output point on poly let out = scope.addOutput(output, point, 0, moveSpeed, tool); - if (options.onfirstout) { + if (first && options.onfirstout) { options.onfirstout(out); } - first = false; + if (first) first = false; } else { let seglen = last.distTo2D(point); if (coastDist && shellMult && perimeter - seglen <= coastDist) { @@ -158,6 +159,7 @@ class Print { shellMult = 0; } perimeter -= seglen; + // to increase shellMult when point.inc set for collapsed points scope.addOutput(output, point, shellMult, printSpeed, tool); } last = point; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 9a9d43e4..7a86ea94 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -99,6 +99,37 @@ function offset_default(params) { } } ); + // look for close points on segmented polys + if (false) + for (let poly of POLY.flatten(last)) { + let ps = poly.segment(offset1, true); + let pp = ps.points; + let moved = false; + pp.push(pp[0]); + for (let i=0; i Date: Wed, 24 Sep 2025 20:34:11 -0400 Subject: [PATCH 45/49] extend approach. need to switch to segment comparison w/ angles --- src/geo/polygon.js | 5 +- src/kiri/mode/fdm/post.js | 106 ++++++++++++++++++++++++++++---------- 2 files changed, 84 insertions(+), 27 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 2a2be748..806fbd9b 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -2326,7 +2326,7 @@ export class Polygon { // split long straight lines into segments no longer than max // and return a new polygon - segment(max = 1, mark) { + segment(max = 1, mark, wrap) { const newp = []; const points = this.points; const length = points.length; @@ -2358,6 +2358,9 @@ export class Polygon { } } if (newp.length > length) { + if (wrap) { + newp.push(newp[0]); + } return newPolygon().addPoints(newp.map(p => p.clone(['segment']))).setOpenValue(this.open); } return this; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 7a86ea94..8da695bb 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -70,6 +70,9 @@ export function slicePost(data, options) { function offset_default(params) { let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps } = params; + + let first; + // standard wall offsetting strategy POLY.offset( top_poly, @@ -81,6 +84,7 @@ function offset_default(params) { outs: top.shells, flat: true, call: (polys, onCount) => { + first = first || polys; last = polys; // mark each poly with depth (offset #) starting at 0 for (let p of polys) { @@ -99,37 +103,87 @@ function offset_default(params) { } } ); - // look for close points on segmented polys + + // look for close points on adjacent segmented polys and merge + let test_polys = first !== last ? [ ...first, ...last ] : last; + let thin_test = POLY.flatten(test_polys).map(poly => { + return { + poly, + points: poly.segment(offset1, true).points + } + }); + if (false) - for (let poly of POLY.flatten(last)) { - let ps = poly.segment(offset1, true); - let pp = ps.points; - let moved = false; - pp.push(pp[0]); - for (let i=0; i Date: Wed, 24 Sep 2025 23:59:31 -0400 Subject: [PATCH 46/49] add basic thin wall handling and default to on --- src/kiri/core/conf.js | 2 +- src/kiri/core/consts.js | 1 + src/kiri/core/print.js | 17 ++++++---- src/kiri/mode/fdm/post.js | 71 +++++++++++++++++---------------------- 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index 3153c01f..febd09c0 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -338,7 +338,7 @@ export const conf = { outputLoops: 0, outputInvertX: false, outputInvertY: false, - sliceDetectThin: "off", + sliceDetectThin: "basic", sliceMinHeight: 0, sliceAdaptive: false, zHopDistance: 0.2, diff --git a/src/kiri/core/consts.js b/src/kiri/core/consts.js index 748fa4e4..fa73787f 100644 --- a/src/kiri/core/consts.js +++ b/src/kiri/core/consts.js @@ -115,6 +115,7 @@ const LISTS = { ], thin: [ { name: "off" }, + { name: "basic" }, { name: "adaptive" }, { name: "legacy 1" }, { name: "legacy 2" } diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index 7b6c642c..c343540b 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -137,18 +137,21 @@ class Print { printSpeed = minSpeed + (printSpeed - minSpeed) * (perimeter / process.outputShortPoly); } + let lpo; poly.forEachPoint((point, pos, points, count) => { - if (first || point.skip) { + if (point.skip && lpo?.skip) { + scope.addOutput(output, point, 0, moveSpeed, tool); + } else if (first) { // if (point.skip) console.log({ skip: point }); - if (first && options.onfirst) { + if (options.onfirst) { options.onfirst(point); } // move to first output point on poly let out = scope.addOutput(output, point, 0, moveSpeed, tool); - if (first && options.onfirstout) { + if (options.onfirstout) { options.onfirstout(out); } - if (first) first = false; + first = false; } else { let seglen = last.distTo2D(point); if (coastDist && shellMult && perimeter - seglen <= coastDist) { @@ -159,10 +162,12 @@ class Print { shellMult = 0; } perimeter -= seglen; + // increase mult by % of point moved relative to nozzle radius + let multOut = shellMult + (point.moved ?? 0); // to increase shellMult when point.inc set for collapsed points - scope.addOutput(output, point, shellMult, printSpeed, tool); + scope.addOutput(output, point, multOut, printSpeed, tool); } - last = point; + last = lpo = point; }, close, closest.index); this.lastPoly = poly; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 8da695bb..68da6933 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -7,6 +7,7 @@ import { polygons as POLY, fillArea } from '../../../geo/polygons.js'; import { getRangeParameters } from './driver.js'; import { slicer } from '../../../geo/slicer.js'; import { Point } from '../../../geo/point.js'; +import { Slope } from '../../../geo/slope.js'; slicer.slicePost.FDM = slicePost; @@ -69,7 +70,7 @@ export function slicePost(data, options) { }; function offset_default(params) { - let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps } = params; + let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps, thin } = params; let first; @@ -104,16 +105,31 @@ function offset_default(params) { } ); + if (!thin) { + return { last, gaps }; + } + + const deltaBig = 160; + const deltSmall = 20; + // look for close points on adjacent segmented polys and merge let test_polys = first !== last ? [ ...first, ...last ] : last; - let thin_test = POLY.flatten(test_polys).map(poly => { + let test_flats = POLY.flatten(test_polys); + + // pre-compute segment angles + for (let poly of test_flats) + poly.forEachSegment((p0, p1) => { + p0.angle = new Slope(p0, p1); + }); + + // segment each poly to be tested + let thin_test = test_flats.map(poly => { return { poly, points: poly.segment(offset1, true).points } }); - if (false) for (let pi=0, pil=thin_test.length; pi deltSmall && diff < deltaBig) continue; // merge points marking point offset and skip let mid = p0.midPointTo(p1); - let inc = p0.distTo2D(mid); + let inc = p0.distTo2D(mid) / offset1; moved = p0.moved = p1.moved = inc; p1.skip = true; p0.x = p1.x = mid.x; @@ -154,36 +171,6 @@ function offset_default(params) { } } - // for (let poly of poly.segment(offset1, true)) { - // let ps = poly.segment(offset1, true); - // let pp = ps.points; - // let moved = false; - // pp.push(pp[0]); - // for (let i=0; i Date: Thu, 25 Sep 2025 00:11:40 -0400 Subject: [PATCH 47/49] filter unmodified subsegments used for proximity testing --- src/kiri/mode/fdm/post.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 68da6933..826c2566 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -167,7 +167,12 @@ function offset_default(params) { for (let rec of thin_test) { if (rec.moved) { - rec.poly.points = rec.points; + let last_seg; + rec.poly.points = rec.points.filter((p,i) => { + let keep = (last_seg !== p.segment || p.moved || p.skip); + last_seg = p.segment; + return keep; + }); } } From 90a9f21ab3135c4a3eff397b3d3a62d4987378aa Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 25 Sep 2025 18:43:38 -0600 Subject: [PATCH 48/49] fix helical operator mistake --- src/kiri/mode/cam/op-helical.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/mode/cam/op-helical.js b/src/kiri/mode/cam/op-helical.js index 3c3aed6a..a8f19e78 100644 --- a/src/kiri/mode/cam/op-helical.js +++ b/src/kiri/mode/cam/op-helical.js @@ -58,7 +58,7 @@ export class OpHelical extends CamOp { } let { zmin, zmax, diam, center, interior, faces } = res; // console.log({ zmin, zmax, diam, center, interior }); - let radius = diam >> 1; + let radius = diam /2; zmax = fromTop ? stock.z: zmax; let radAdd, From f5d234da3145fe166571a23dc66bb9015677d688 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 25 Sep 2025 21:31:54 -0400 Subject: [PATCH 49/49] revert new thin default until codec updated --- src/kiri/core/conf.js | 2 +- src/kiri/mode/fdm/post.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index febd09c0..3153c01f 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -338,7 +338,7 @@ export const conf = { outputLoops: 0, outputInvertX: false, outputInvertY: false, - sliceDetectThin: "basic", + sliceDetectThin: "off", sliceMinHeight: 0, sliceAdaptive: false, zHopDistance: 0.2, diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 826c2566..34458f49 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -114,7 +114,7 @@ function offset_default(params) { // look for close points on adjacent segmented polys and merge let test_polys = first !== last ? [ ...first, ...last ] : last; - let test_flats = POLY.flatten(test_polys); + let test_flats = POLY.flatten(test_polys ?? []); // pre-compute segment angles for (let poly of test_flats)