From 6ede24eebb06fd52c783b04797fce615b9cee6e0 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 16 Sep 2025 11:13:30 -0400 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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 813ae0d9e67ba16b03b3b426e172bb11158c7724 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 21:20:36 -0400 Subject: [PATCH 27/31] 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 28/31] 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 29/31] 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 30/31] 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 31/31] 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: