From 46724db7bf783b7f866c4789f7fdd265c75ef4c7 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 6 Sep 2025 22:53:11 -0400 Subject: [PATCH 01/24] fix trace recontour by making it a negative down option --- src/kiri/mode/cam/op-trace.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kiri/mode/cam/op-trace.js b/src/kiri/mode/cam/op-trace.js index 7b759316..52481169 100644 --- a/src/kiri/mode/cam/op-trace.js +++ b/src/kiri/mode/cam/op-trace.js @@ -38,7 +38,7 @@ class OpTrace extends CamOp { let cutdir = ov_conv; let polys = []; let reContour = false; - let canRecontour = offset !== 'none' && down === 0; + let canRecontour = offset !== 'none' && down < 0; let stockRect = stock.center && stock.x && stock.y ? newPolygon().centerRectangle({x:0,y:0}, stock.x, stock.y) : undefined; updateToolDiams(toolDiam); @@ -57,6 +57,7 @@ class OpTrace extends CamOp { let max = Math.max(...zs); if (max - min > 0.0001 && canRecontour) { reContour = true; + down = 0; } } if (false) newSliceOut(0).output() From 183c2bc79103a76c3b5960e28c1977b955863aac Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 8 Sep 2025 17:23:40 -0400 Subject: [PATCH 02/24] fix missing sliceAll from fdm driver --- src/kiri/mode/fdm/driver-be.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kiri/mode/fdm/driver-be.js b/src/kiri/mode/fdm/driver-be.js index 397670e2..f1164acc 100644 --- a/src/kiri/mode/fdm/driver-be.js +++ b/src/kiri/mode/fdm/driver-be.js @@ -1,7 +1,7 @@ /** Copyright Stewart Allen -- All Rights Reserved */ import { util } from '../../../geo/base.js'; -import { fdm_slice } from './slice.js'; +import { fdm_slice, sliceAll } from './slice.js'; import { fdm_prepare } from './prepare.js'; import { fdm_export } from './export.js'; @@ -39,6 +39,7 @@ export const FDM = { extrudePerMM, extrudeMM, slice: fdm_slice, + sliceAll: sliceAll, prepare: fdm_prepare, export: fdm_export }; From b1c73b5e4645e2cef90b491d786725909351a325 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 8 Sep 2025 17:40:33 -0400 Subject: [PATCH 03/24] fix import of taper tool dependencies --- src/kiri/mode/cam/tools.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kiri/mode/cam/tools.js b/src/kiri/mode/cam/tools.js index 9552fccb..8c6b43d2 100644 --- a/src/kiri/mode/cam/tools.js +++ b/src/kiri/mode/cam/tools.js @@ -4,6 +4,7 @@ import { $ } from '../../../moto/webui.js'; import { api } from '../../core/api.js'; import { consts } from '../../core/consts.js'; import { settings as setconf } from '../../core/settings.js'; +import { calcTaperLength, calcTaperAngle } from './tool.js'; const DEG2RAD = Math.PI / 180; @@ -44,7 +45,7 @@ function selectTool(tool) { ui.toolMetric.checked = tool.metric; ui.toolType.selectedIndex = toolNames.indexOf(tool.type); if (tool === 'tapermill') { - ui.toolTaperAngle.value = kiri.driver.CAM.calcTaperAngle( + ui.toolTaperAngle.value = calcTaperAngle( (tool.flute_diam - tool.taper_tip) / 2, tool.flute_len ).round(1); } else if(tool === 'drill'){ @@ -208,16 +209,15 @@ export function updateTool(ev) { selectedTool.metric = ui.toolMetric.checked; selectedTool.type = toolNames[ui.toolType.selectedIndex]; if (selectedTool.type === 'tapermill') { - const CAM = kiri.driver.CAM; const rad = (selectedTool.flute_diam - selectedTool.taper_tip) / 2; if (ev && ev.target === ui.toolTaperAngle) { const angle = parseFloat(ev.target.value); - const len = CAM.calcTaperLength(rad, angle * DEG2RAD); + const len = calcTaperLength(rad, angle * DEG2RAD); selectedTool.flute_len = len; ui.toolTaperAngle.value = angle.round(1); ui.toolFluteLen.value = selectedTool.flute_len.round(4); } else { - ui.toolTaperAngle.value = CAM.calcTaperAngle(rad, selectedTool.flute_len).round(1); + ui.toolTaperAngle.value = calcTaperAngle(rad, selectedTool.flute_len).round(1); } } else { ui.toolTaperAngle.value = 0; From d0bcaa28da34fe195cf4cb36f1979d790309f31f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 8 Sep 2025 18:33:48 -0400 Subject: [PATCH 04/24] fix gcode import: remove unused mov obj --- src/kiri/core/print.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/kiri/core/print.js b/src/kiri/core/print.js index 848229f3..d0487b52 100644 --- a/src/kiri/core/print.js +++ b/src/kiri/core/print.js @@ -362,14 +362,11 @@ class Print { // if( axis == 'I' || axis == "J") return if (abs) { pos[axis] = val; - if (axis == "X") point.x = factor * pos.X + xoff.x + off.x else if (axis == "Y") point.y = factor * pos.Y + xoff.y + off.y else if (axis == "Z") point.z = factor * pos.Z + xoff.z + off.z + dz - - } else { - mov[axis] = val; + // mov[axis] = val; pos[axis] += val; } // console.log("position updated",structuredClone(pos)) @@ -393,7 +390,6 @@ class Print { center = center.add(prevPoint); center.setZ((prevPoint.z+point.z)/2+dz); } - return { center, point, @@ -402,10 +398,8 @@ class Print { } function outputPoint(point,lastP,emit,{center,arcPoints,retract}) { - // non-move in a new plane means burp out // the old sequence and start a new one - if (newlayer || (autolayer && seq.z != point.z)) { newlayer = false; let dz = point.z - seq.z; From 96beba67e8f276cbbfc26f5634260764a8902451 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 9 Sep 2025 00:22:24 -0400 Subject: [PATCH 05/24] add rad points to shell inset arrays --- src/kiri/mode/fdm/post.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index bbc088fc..a2c95565 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -167,6 +167,7 @@ function thin_type_3(params) { top.thin_fill = []; top.fill_sparse = []; + // 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; @@ -180,6 +181,7 @@ function thin_type_3(params) { let pointMap = new Map(); let lineMap = new Map(); + // convert point to deduplicated / unique point record function pointToRec(point) { let key = `${(point.x*100)|0}|${(point.y*100)|0}`; let rec = pointMap.get(key); @@ -196,6 +198,7 @@ function thin_type_3(params) { return rec; } + // convert line to unique line record. matches reversed and duplicate lines function pointsToLine(p0, p1) { p0 = pointToRec(p0); p1 = pointToRec(p1); @@ -294,6 +297,7 @@ function thin_type_3(params) { let showCross = false; let showRad = true; + // divide a segment/redius into 1 or more equal subsegments function div(cr) { if (cr <= maxR) { return [ cr ]; @@ -315,6 +319,8 @@ function thin_type_3(params) { if (showRad) { top.shells = []; } + // ranked points by inset distance + let inset = []; for (let chain of chains.filter(c => c.total >= minR)) { // chain reduction by merging short segments let end = chain[0]; @@ -364,18 +370,26 @@ function thin_type_3(params) { let sx = cx + yo; let sy = cy - xo; let m = 1; + let indx = 0; for (let r of pop) { + indx = Math.min(indx, pop.length - 1 - indx); + let inset_arr = inset[indx]; + if (!inset_arr) { + inset_arr = inset[indx] = []; + } sx -= (r/cr) * yo * m; sy += (r/cr) * xo * m; circ.push(newPolygon().centerCircle({ x:sx, y:sy }, r, 10)); m = 2; + inset_arr.push({ x:sx, y:sy, r }); + indx++; } - // console.log(pop); } } } } + // console.log({ inset }); return { last, gaps }; } From 57ed55ca77dbb8270a3e0f392a10d8aca4f9128c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 9 Sep 2025 16:31:48 -0400 Subject: [PATCH 06/24] fixes #423 --- src/kiri/run/engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/run/engine.js b/src/kiri/run/engine.js index 00824cbe..5b9d7008 100644 --- a/src/kiri/run/engine.js +++ b/src/kiri/run/engine.js @@ -156,7 +156,7 @@ class Engine { } slice() { - this.widget.setTopZ(this.settings.stock.z - (this.topOffset || 0)); + this.widget.setTopZ((this.settings?.stock?.z || 0) - (this.topOffset || 0)); return new Promise((accept, reject) => { client.clear(); client.sync([this.widget]); From eb63ebb1d08a6b6032f08683f0d4c1122353904f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 9 Sep 2025 22:55:30 -0400 Subject: [PATCH 07/24] form chains from radii points --- src/kiri/mode/fdm/post.js | 86 ++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index a2c95565..66c015a7 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -2,7 +2,7 @@ import '../../../ext/jspoly.js'; import { base } from '../../../geo/base.js'; -import { newPolygon } from '../../../geo/polygon.js'; +import { newPolygon, Polygon } from '../../../geo/polygon.js'; import { polygons as POLY, fillArea } from '../../../geo/polygons.js'; import { getRangeParameters } from './driver.js'; import { slicer } from '../../../geo/slicer.js'; @@ -169,7 +169,7 @@ 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.shells = noodle; top.gaps = last = remain; let thin = top.thin_fill; @@ -293,9 +293,16 @@ function thin_type_3(params) { } } - let showMid = false; - let showCross = false; - let showRad = true; + let showNoodle = false; + let showMid = false; + let showCross = false; + let showRad = true; + let showShells = true; + + // render inset "noodle" + if (showNoodle) { + top.shells.appendAll(noodle); + } // divide a segment/redius into 1 or more equal subsegments function div(cr) { @@ -315,12 +322,8 @@ function thin_type_3(params) { } } - // add chains to thinwall output for visualization - if (showRad) { - top.shells = []; - } - // ranked points by inset distance - let inset = []; + // gather point offsets into shells + let shells = { count: 0 }; for (let chain of chains.filter(c => c.total >= minR)) { // chain reduction by merging short segments let end = chain[0]; @@ -364,32 +367,75 @@ function thin_type_3(params) { thin.push(new Point(cx + yo, cy - xo)); thin.push(new Point(cx - yo, cy + xo)); } - if (showRad) { + if (showRad || showShells) { let circ = top.shells; let pop = div(cr); let sx = cx + yo; let sy = cy - xo; let m = 1; - let indx = 0; + let index = 0; + let plen = pop.length; + let mid = plen / 2; for (let r of pop) { - indx = Math.min(indx, pop.length - 1 - indx); - let inset_arr = inset[indx]; + let shell = index < mid ? index + 1 : -(pop.length - index); + let inset_arr = shells[shell]; if (!inset_arr) { - inset_arr = inset[indx] = []; + inset_arr = shells[shell] = []; } sx -= (r/cr) * yo * m; sy += (r/cr) * xo * m; - circ.push(newPolygon().centerCircle({ x:sx, y:sy }, r, 10)); m = 2; - inset_arr.push({ x:sx, y:sy, r }); - indx++; + if (showRad) { + circ.push(newPolygon().centerCircle({ x:sx, y:sy }, r, 10)); + } + inset_arr.push(new Point(sx, sy, r)); + index++; + shells.count = Math.max(shells.count, Math.abs(shell)); } } } } } - // console.log({ inset }); + // follow shells and output lines + console.log({ shells }); + if (showShells) + for (let shell = 1; shell <= 1; shell++) { + let poly = new Polygon().setOpen(); + let arr = shells[shell]; + let lp = arr[0]; + poly.push(lp); + let find; + for (;;) { + find = { dist: Infinity }; + // find next closest point + for (let i=1; i 1) { + top.shells.push(poly); + poly = new Polygon().setOpen(); + } + arr[find.pi] = undefined; + poly.push(lp = find.pt); + } else { + break; + } + } + console.log({ poly }); + if (poly.length > 1) { + top.shells.push(poly); + } + } + return { last, gaps }; } From a5c3c3019081e361a6287f609b48c27b059a08e1 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 10 Sep 2025 14:22:09 -0400 Subject: [PATCH 08/24] append base64js for production build --- bin/esbuild.config.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/esbuild.config.mjs b/bin/esbuild.config.mjs index 4881ab00..0cb973bb 100644 --- a/bin/esbuild.config.mjs +++ b/bin/esbuild.config.mjs @@ -12,7 +12,9 @@ const MESH_OUTFILE = 'src/pack/mesh-main.js'; const MESH_EXTRAS = [ ]; const KIRI_OUTFILE = 'src/pack/kiri-main.js'; -const KIRI_EXTRAS = [ ]; +const KIRI_EXTRAS = [ + 'src/ext/base64.js' +]; async function appendExtraModules(extras, outfile, minify = false) { const files = await glob(extras); From 23eb8d275870301ab4926592cb8fe55c9294477d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Wed, 10 Sep 2025 20:50:48 -0400 Subject: [PATCH 09/24] updated notes w fdm todo --- notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/notes.md b/notes.md index a7036e77..5efbac1a 100644 --- a/notes.md +++ b/notes.md @@ -31,6 +31,10 @@ * `B` multi-extruder rendering of raft fails to offset the rest of the print * `B` multi-extruder purge blocks fail to generate properly for rafts +* `F` min layer time +* `F` separate bridging speed / fan control +* `F` elephant foot compensation +* `F` inner / outer perimeter compensation * `F` better thin-wall (like arachne) * `F` add scarf seams * `F` add 'random' and 'seam' start point options From 3991daadd2dca8119c0b4c09d137433286799114 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 11 Sep 2025 09:47:10 -0400 Subject: [PATCH 10/24] thin3 notes --- src/kiri/mode/fdm/post.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 66c015a7..f818aef0 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -293,11 +293,15 @@ function thin_type_3(params) { } } + // todo: merge chains at next of 3 or more preferencing + // merging longer chains. then shorten remaining nexus chain + // intersections by minR + let showNoodle = false; - let showMid = false; + let showMid = true; let showCross = false; let showRad = true; - let showShells = true; + let showShells = false; // render inset "noodle" if (showNoodle) { @@ -323,7 +327,9 @@ function thin_type_3(params) { } // gather point offsets into shells + // todo: order outer shell innersection to inner let shells = { count: 0 }; + // filter chains that are too short from consideration for (let chain of chains.filter(c => c.total >= minR)) { // chain reduction by merging short segments let end = chain[0]; @@ -373,11 +379,10 @@ function thin_type_3(params) { let sx = cx + yo; let sy = cy - xo; let m = 1; - let index = 0; + let index = 1; let plen = pop.length; - let mid = plen / 2; for (let r of pop) { - let shell = index < mid ? index + 1 : -(pop.length - index); + let shell = Math.min(index, plen - index + 1); let inset_arr = shells[shell]; if (!inset_arr) { inset_arr = shells[shell] = []; @@ -389,8 +394,8 @@ function thin_type_3(params) { circ.push(newPolygon().centerCircle({ x:sx, y:sy }, r, 10)); } inset_arr.push(new Point(sx, sy, r)); + shells.count = Math.max(shells.count, shell); index++; - shells.count = Math.max(shells.count, Math.abs(shell)); } } } From 76d028bd151cd508e01c660f5770ec783bdcbae2 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 14 Sep 2025 12:48:20 -0400 Subject: [PATCH 11/24] separate chain filter logic --- src/kiri/mode/fdm/post.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index f818aef0..8abceaf6 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -326,11 +326,8 @@ function thin_type_3(params) { } } - // gather point offsets into shells - // todo: order outer shell innersection to inner - let shells = { count: 0 }; // filter chains that are too short from consideration - for (let chain of chains.filter(c => c.total >= minR)) { + chains = chains.filter(c => c.total >= minR).map(chain => { // chain reduction by merging short segments let end = chain[0]; let nuchain = [ end ]; @@ -342,7 +339,17 @@ function thin_type_3(params) { end.len += point.len; } } - chain = nuchain; + return nuchain; + }); + + // connect chains end to end following nexus branches that result + // in the longest final merged chain (open or closed) + console.log(chains); + + // gather point offsets into shells + // todo: order outer shell innersection to inner + let shells = { count: 0 }; + for (let chain of chains) { // emit chain with subdivision for long segments for (let i=0; i Date: Sun, 14 Sep 2025 14:29:23 -0400 Subject: [PATCH 12/24] checkpoint thin3 work --- src/geo/polygon.js | 3 +- src/kiri/mode/fdm/post.js | 245 +++++++++++++++++++++++--------------- 2 files changed, 152 insertions(+), 96 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 38957f46..e0226946 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -259,8 +259,9 @@ export class Polygon { noodle(width) { let clone = this.clone(true); let ins = clone.offset(width); + let remain = ins.clone(true); let nood = POLY.nest(POLY.flatten([ clone, ...ins ], [], true)); - return { noodle: nood, remain: ins }; + return { noodle: nood, remain }; } // generate center crossing point cloud diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 8abceaf6..00b85f98 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -173,17 +173,23 @@ function thin_type_3(params) { top.gaps = last = remain; let thin = top.thin_fill; - let scale = 5000; + let scale = 1000; let minR = offsetN / 2; let maxR = minR * 1.5; let minSpur = 0; // offsetN * 3; + let mergeClosePoints = true; + let interpolateShortSpur = true; let pointMap = new Map(); let lineMap = new Map(); + function point2key(point) { + return `${(point.x*100)|0}|${(point.y*100)|0}`; + } + // convert point to deduplicated / unique point record function pointToRec(point) { - let key = `${(point.x*100)|0}|${(point.y*100)|0}`; + let key = point2key(point); let rec = pointMap.get(key); if (!rec) { rec = { @@ -222,6 +228,9 @@ function thin_type_3(params) { let chains = []; + // 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 }); @@ -234,63 +243,75 @@ function thin_type_3(params) { for (let { point0, point1 } of ma) { pointsToLine(point0, point1); } - for (let seg of lineMap.values()) { - let { p0, p1 } = seg; - let len = seg.len = Math.hypot(p0.x-p1.x, p0.y-p1.y); - // 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 (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); - } else { - lineMap.delete(seg.key); - continue; - } + } + + // connect segments into chains + // console.log({ seg: [...lineMap.values()].sort((s1,s2) => s2.len - s1.len) }); + for (let seg of lineMap.values()) { + let { p0, p1 } = seg; + let len = seg.len = Math.hypot(p0.x-p1.x, p0.y-p1.y); + // 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 (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); + } else { + // both points eliminated + pointMap.delete(p0.key); + pointMap.delete(p1.key); + lineMap.delete(seg.key); + continue; } - // for each line segment either add it to a chain or start a new chain - let add; - for (let chain of chains) { - if (p0.count === 2 && p0.key === chain[0].key) { - chain.splice(0,0,{...p1, len}); - } else if (p0.count === 2 && p0.key === chain.peek().key) { - chain.peek().len = len; - chain.push({...p1}); - add = true; - } else if (p1.count === 2 && p1.key === chain[0].key) { - chain.splice(0,0,{...p0, len}); - add = true; - } else if (p1.count === 2 && p1.key === chain.peek().key) { - chain.peek().len = len; - chain.push({...p0}); - add = true; - } - if (add) { - chain.total += len; - break; - } + } + // for each line segment either add it to a chain or start a new chain + let add; + for (let chain of chains) { + if (p0.count === 2 && p0.key === chain[0].key) { + // prefix chain with p1 + chain.splice(0,0,{...p1, len}); + add = true; + } else if (p0.count === 2 && p0.key === chain.peek().key) { + // append chain with p1 + chain.peek().len = len; + chain.push({...p1}); + add = true; + } else if (p1.count === 2 && p1.key === chain[0].key) { + // prefix chain with p0 + chain.splice(0,0,{...p0, len}); + add = true; + } else if (p1.count === 2 && p1.key === chain.peek().key) { + // append chain with p0 + chain.peek().len = len; + chain.push({...p0}); + add = true; } - if (!add) { - chains.push([ {...p0 ,len}, {...p1} ]); - chains.peek().total = len; + if (add) { + chain.total += len; + break; } } + if (!add) { + chains.push([ {...p0 ,len}, {...p1} ]); + chains.peek().total = len; + } } // todo: merge chains at next of 3 or more preferencing @@ -300,7 +321,7 @@ function thin_type_3(params) { let showNoodle = false; let showMid = true; let showCross = false; - let showRad = true; + let showRad = false; let showShells = false; // render inset "noodle" @@ -327,10 +348,16 @@ function thin_type_3(params) { } // filter chains that are too short from consideration - chains = chains.filter(c => c.total >= minR).map(chain => { + chains = chains.filter(c => c.total >= minR); + chains.sort((c1,c2) => c2.total - c1.total); + + // merge chain points that are too close + if (mergeClosePoints) + chains = chains.map(chain => { // chain reduction by merging short segments let end = chain[0]; let nuchain = [ end ]; + nuchain.total = chain.total; for (let point of chain.slice(1)) { if (point.len === undefined || point.len + end.len >= minR) { nuchain.push(point); @@ -342,9 +369,37 @@ function thin_type_3(params) { return nuchain; }); + // map chains to nexus via endpoints + let nexus = {}; + function addEp(point, chain) { + let key = point.key; + let rec = nexus[key]; + if (!rec) rec = nexus[key] = []; + if (rec.indexOf(chain) < 0) rec.push(chain); + } + for (let chain of chains) { + addEp(chain[0], chain); + addEp(chain.peek(), chain); + } + + // mark spurs + for (let [key,val] of Object.entries(nexus)) { + if (val.length === 1) { + val[0].spur = true; + delete nexus[key]; + } + } + + // detach spurs from nexus and shorten by ? + for (let [key,val] of Object.entries(nexus)) { + nexus[key] = val.filter(c => !c.spur); + } + + console.log({ chains, nexus }); + // connect chains end to end following nexus branches that result // in the longest final merged chain (open or closed) - console.log(chains); + // map chain endpoints to nexus points // gather point offsets into shells // todo: order outer shell innersection to inner @@ -410,43 +465,43 @@ function thin_type_3(params) { } // follow shells and output lines - console.log({ shells }); - if (showShells) - for (let shell = 1; shell <= 1; shell++) { - let poly = new Polygon().setOpen(); - let arr = shells[shell]; - let lp = arr[0]; - poly.push(lp); - let find; - for (;;) { - find = { dist: Infinity }; - // find next closest point - for (let i=1; i 1) { - top.shells.push(poly); - poly = new Polygon().setOpen(); - } - arr[find.pi] = undefined; - poly.push(lp = find.pt); - } else { - break; - } - } - console.log({ poly }); - if (poly.length > 1) { - top.shells.push(poly); - } - } + // console.log({ shells }); + // if (showShells) + // for (let shell = 1; shell <= 1; shell++) { + // let poly = new Polygon().setOpen(); + // let arr = shells[shell]; + // let lp = arr[0]; + // poly.push(lp); + // let find; + // for (;;) { + // find = { dist: Infinity }; + // // find next closest point + // for (let i=1; i 1) { + // top.shells.push(poly); + // poly = new Polygon().setOpen(); + // } + // arr[find.pi] = undefined; + // poly.push(lp = find.pt); + // } else { + // break; + // } + // } + // console.log({ poly }); + // if (poly.length > 1) { + // top.shells.push(poly); + // } + // } return { last, gaps }; } From 5fe02937c8f4e9f23a36798a87b3a415024455d4 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 14 Sep 2025 19:08:13 -0400 Subject: [PATCH 13/24] fix thin3 cross interp --- src/kiri/mode/fdm/post.js | 134 +++++++++++--------------------------- 1 file changed, 37 insertions(+), 97 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 00b85f98..40c5a15d 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -163,22 +163,29 @@ function thin_type_2(params) { } function thin_type_3(params) { + // offsetN usually = extrusion width (nozzle diameter) let { z, top, count, offsetN, last, gaps } = params; - top.thin_fill = []; - top.fill_sparse = []; // 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 thin = top.thin_fill = []; + let sparse = top.fill_sparse = []; let scale = 1000; let minR = offsetN / 2; let maxR = minR * 1.5; - let minSpur = 0; // offsetN * 3; - let mergeClosePoints = true; + let minSpur = offsetN * 3; + let interpolateShortSpur = true; + let showChainInterpPoints = true; + let showChainRawPoints = false; + let showNoodle = false; + let showMid = true; + let showCross = true; + let showRad = true; let pointMap = new Map(); let lineMap = new Map(); @@ -246,7 +253,6 @@ function thin_type_3(params) { } // connect segments into chains - // console.log({ seg: [...lineMap.values()].sort((s1,s2) => s2.len - s1.len) }); for (let seg of lineMap.values()) { let { p0, p1 } = seg; let len = seg.len = Math.hypot(p0.x-p1.x, p0.y-p1.y); @@ -318,12 +324,6 @@ function thin_type_3(params) { // merging longer chains. then shorten remaining nexus chain // intersections by minR - let showNoodle = false; - let showMid = true; - let showCross = false; - let showRad = false; - let showShells = false; - // render inset "noodle" if (showNoodle) { top.shells.appendAll(noodle); @@ -351,24 +351,6 @@ function thin_type_3(params) { chains = chains.filter(c => c.total >= minR); chains.sort((c1,c2) => c2.total - c1.total); - // merge chain points that are too close - if (mergeClosePoints) - chains = chains.map(chain => { - // chain reduction by merging short segments - let end = chain[0]; - let nuchain = [ end ]; - nuchain.total = chain.total; - for (let point of chain.slice(1)) { - if (point.len === undefined || point.len + end.len >= minR) { - nuchain.push(point); - end = point; - } else { - end.len += point.len; - } - } - return nuchain; - }); - // map chains to nexus via endpoints let nexus = {}; function addEp(point, chain) { @@ -403,8 +385,11 @@ function thin_type_3(params) { // gather point offsets into shells // todo: order outer shell innersection to inner - let shells = { count: 0 }; for (let chain of chains) { + if (showChainRawPoints) + for (let pt of chain) { + top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); + } // emit chain with subdivision for long segments for (let i=0; i 1) { - // top.shells.push(poly); - // poly = new Polygon().setOpen(); - // } - // arr[find.pi] = undefined; - // poly.push(lp = find.pt); - // } else { - // break; - // } - // } - // console.log({ poly }); - // if (poly.length > 1) { - // top.shells.push(poly); - // } - // } + POLY.setZ([...thin, ...top.shells], z); return { last, gaps }; } From ad6d1139ed947eba290365e0b6ffd77fea2c1be3 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 14 Sep 2025 23:27:58 -0400 Subject: [PATCH 14/24] add optional chain merge --- src/kiri/mode/fdm/post.js | 71 ++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 40c5a15d..0bbb3185 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -179,13 +179,14 @@ function thin_type_3(params) { let maxR = minR * 1.5; let minSpur = offsetN * 3; + let mergeChains = false; let interpolateShortSpur = true; let showChainInterpPoints = true; let showChainRawPoints = false; let showNoodle = false; let showMid = true; let showCross = true; - let showRad = true; + let showRad = false; let pointMap = new Map(); let lineMap = new Map(); @@ -356,32 +357,77 @@ function thin_type_3(params) { function addEp(point, chain) { let key = point.key; let rec = nexus[key]; - if (!rec) rec = nexus[key] = []; - if (rec.indexOf(chain) < 0) rec.push(chain); + if (!rec) rec = nexus[key] = { point, chains: [] }; + if (rec.chains.indexOf(chain) < 0) rec.chains.push(chain); } + // since chains are sorted longest to shortest, + // they will appear in the nexus record longest to shortest for (let chain of chains) { addEp(chain[0], chain); addEp(chain.peek(), chain); } // mark spurs - for (let [key,val] of Object.entries(nexus)) { - if (val.length === 1) { - val[0].spur = true; + for (let [key, rec] of Object.entries(nexus)) { + if (rec.chains.length === 1) { + rec.chains[0].spur = true; delete nexus[key]; + // console.log({ delete_nexus: key }); } } // detach spurs from nexus and shorten by ? - for (let [key,val] of Object.entries(nexus)) { - nexus[key] = val.filter(c => !c.spur); - } + // for (let [key,val] of Object.entries(nexus)) { + // nexus[key].chains = val.chains.filter(c => !c.spur); + // } console.log({ chains, nexus }); + function merge1chain() { + let clen = chains.length; + for (let i=0; i chain); + console.log({ chains }); // gather point offsets into shells // todo: order outer shell innersection to inner @@ -400,12 +446,13 @@ function thin_type_3(params) { // compute medial axis segment cross section let p0 = chain[i]; let p1 = chain[i+1]; + let len = Math.hypot(p0.x-p1.x, p0.y-p1.y); let dr = (p1.r - p0.r); let dx = (p1.x - p0.x); let dy = (p1.y - p0.y); - let ndx = dx / p0.len; - let ndy = dy / p0.len; - let offs = p0.len <= offsetN ? [ 0.5 ] : base.util.lerp(0, p0.len, offsetN, true); + let ndx = dx / len; + let ndy = dy / len; + let offs = len <= offsetN ? [ 0.5 ] : base.util.lerp(0, len, offsetN, true); let indx = 0; let step = 1 / offs.length; // interpolate across the length of the chain segment From edb60e66df526216ec5f477acd7c73ab826bdea6 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 14 Sep 2025 23:57:42 -0400 Subject: [PATCH 15/24] use objects instead of keys for comparison --- src/kiri/mode/fdm/post.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 0bbb3185..951ff447 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -179,7 +179,7 @@ function thin_type_3(params) { let maxR = minR * 1.5; let minSpur = offsetN * 3; - let mergeChains = false; + let mergeChains = true; let interpolateShortSpur = true; let showChainInterpPoints = true; let showChainRawPoints = false; @@ -291,23 +291,21 @@ function thin_type_3(params) { // for each line segment either add it to a chain or start a new chain let add; for (let chain of chains) { - if (p0.count === 2 && p0.key === chain[0].key) { + if (p0.count === 2 && p0 === chain[0]) { // prefix chain with p1 - chain.splice(0,0,{...p1, len}); + chain.splice(0,0,p1); add = true; - } else if (p0.count === 2 && p0.key === chain.peek().key) { + } else if (p0.count === 2 && p0 === chain.peek()) { // append chain with p1 - chain.peek().len = len; - chain.push({...p1}); + chain.push(p1); add = true; - } else if (p1.count === 2 && p1.key === chain[0].key) { + } else if (p1.count === 2 && p1 === chain[0]) { // prefix chain with p0 - chain.splice(0,0,{...p0, len}); + chain.splice(0,0,p0); add = true; - } else if (p1.count === 2 && p1.key === chain.peek().key) { + } else if (p1.count === 2 && p1 === chain.peek()) { // append chain with p0 - chain.peek().len = len; - chain.push({...p0}); + chain.push(p0); add = true; } if (add) { @@ -316,7 +314,7 @@ function thin_type_3(params) { } } if (!add) { - chains.push([ {...p0 ,len}, {...p1} ]); + chains.push([ p0, p1 ]); chains.peek().total = len; } } @@ -391,25 +389,25 @@ function thin_type_3(params) { for (let j=i+1; j Date: Mon, 15 Sep 2025 00:38:16 -0400 Subject: [PATCH 16/24] mark closed chains --- src/kiri/mode/fdm/post.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 951ff447..568b793b 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -191,6 +191,10 @@ function thin_type_3(params) { let pointMap = new Map(); let lineMap = new Map(); + function pointDist(p0, p1) { + return Math.hypot(p0.x-p1.x, p0.y-p1.y); + } + function point2key(point) { return `${(point.x*100)|0}|${(point.y*100)|0}`; } @@ -256,7 +260,7 @@ function thin_type_3(params) { // connect segments into chains for (let seg of lineMap.values()) { let { p0, p1 } = seg; - let len = seg.len = Math.hypot(p0.x-p1.x, p0.y-p1.y); + 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. @@ -427,6 +431,11 @@ function thin_type_3(params) { chains = chains.filter(chain => chain); console.log({ chains }); + // mark closed chains + for (let chain of chains) { + chain.closed = pointDist(chain[0], chain.peek()) <= offsetN; + } + // gather point offsets into shells // todo: order outer shell innersection to inner for (let chain of chains) { @@ -444,7 +453,7 @@ function thin_type_3(params) { // compute medial axis segment cross section let p0 = chain[i]; let p1 = chain[i+1]; - let len = Math.hypot(p0.x-p1.x, p0.y-p1.y); + let len = pointDist(p0, p1); let dr = (p1.r - p0.r); let dx = (p1.x - p0.x); let dy = (p1.y - p0.y); From 32d4b7e62a37782c6543b74711dc8fb629c34cbb Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 15 Sep 2025 17:11:56 -0400 Subject: [PATCH 17/24] fix thin3 chain marking and shortening --- src/geo/polygon.js | 2 +- src/kiri/mode/fdm/post.js | 123 ++++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 39 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index e0226946..846515f9 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -258,7 +258,7 @@ export class Polygon { // of the polygon with the noodle removed (for the next pass) noodle(width) { let clone = this.clone(true); - let ins = clone.offset(width); + let ins = clone.offset(width) ?? []; let remain = ins.clone(true); let nood = POLY.nest(POLY.flatten([ clone, ...ins ], [], true)); return { noodle: nood, remain }; diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 568b793b..b9818762 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -182,11 +182,13 @@ function thin_type_3(params) { let mergeChains = true; let interpolateShortSpur = true; let showChainInterpPoints = true; + let showCross = false; + let showPoint = true; let showChainRawPoints = false; let showNoodle = false; let showMid = true; - let showCross = true; let showRad = false; + let showNexus = false; let pointMap = new Map(); let lineMap = new Map(); @@ -354,37 +356,6 @@ function thin_type_3(params) { chains = chains.filter(c => c.total >= minR); chains.sort((c1,c2) => c2.total - c1.total); - // map chains to nexus via endpoints - let nexus = {}; - function addEp(point, chain) { - let key = point.key; - let rec = nexus[key]; - if (!rec) rec = nexus[key] = { point, chains: [] }; - if (rec.chains.indexOf(chain) < 0) rec.chains.push(chain); - } - // since chains are sorted longest to shortest, - // they will appear in the nexus record longest to shortest - for (let chain of chains) { - addEp(chain[0], chain); - addEp(chain.peek(), chain); - } - - // mark spurs - for (let [key, rec] of Object.entries(nexus)) { - if (rec.chains.length === 1) { - rec.chains[0].spur = true; - delete nexus[key]; - // console.log({ delete_nexus: key }); - } - } - - // detach spurs from nexus and shorten by ? - // for (let [key,val] of Object.entries(nexus)) { - // nexus[key].chains = val.chains.filter(c => !c.spur); - // } - - console.log({ chains, nexus }); - function merge1chain() { let clen = chains.length; for (let i=0; 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; + } + } + } + + // add chains + + // shorten chains terminating at nexus by midR + 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 (showNexus) + for (let rec of Object.values(nexus)) { + let { point, chains } = rec; + // console.log(rec); + top.shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); } // gather point offsets into shells - // todo: order outer shell innersection to inner + let zi = z; for (let chain of chains) { + // zi += 0.1; if (showChainRawPoints) for (let pt of chain) { top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); @@ -447,8 +493,8 @@ function thin_type_3(params) { for (let i=0; i Date: Mon, 15 Sep 2025 17:23:29 -0400 Subject: [PATCH 18/24] clarify thin3 test var naming --- src/kiri/mode/fdm/post.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index b9818762..2d286191 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -179,16 +179,16 @@ function thin_type_3(params) { let maxR = minR * 1.5; let minSpur = offsetN * 3; + let showNoodle = false; let mergeChains = true; let interpolateShortSpur = true; - let showChainInterpPoints = true; - let showCross = false; - let showPoint = true; let showChainRawPoints = false; - let showNoodle = false; - let showMid = true; - let showRad = false; - let showNexus = false; + let showChainInterpPoints = true; + let showMidNormals = true; + let showMidPoints = true; + let showMidline = true; + let showMidRadii = false; + let showNexuses = false; let pointMap = new Map(); let lineMap = new Map(); @@ -474,7 +474,7 @@ function thin_type_3(params) { } } - if (showNexus) + if (showNexuses) for (let rec of Object.values(nexus)) { let { point, chains } = rec; // console.log(rec); @@ -492,7 +492,7 @@ function thin_type_3(params) { // emit chain with subdivision for long segments for (let i=0; i Date: Mon, 15 Sep 2025 21:24:14 -0400 Subject: [PATCH 19/24] thin3 adjust chain nexus normals --- src/kiri/mode/fdm/post.js | 87 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 2d286191..d3a19198 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -184,10 +184,10 @@ function thin_type_3(params) { let interpolateShortSpur = true; let showChainRawPoints = false; let showChainInterpPoints = true; - let showMidNormals = true; - let showMidPoints = true; let showMidline = true; - let showMidRadii = false; + let showMidPoints = true; + let showMidNormals = true; + let showMidRadii = true; let showNexuses = false; let pointMap = new Map(); @@ -489,29 +489,84 @@ function thin_type_3(params) { for (let pt of chain) { top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10)); } - // emit chain with subdivision for long segments - for (let i=0; i Date: Tue, 16 Sep 2025 09:30:56 -0400 Subject: [PATCH 20/24] thin3 add point/normal fn --- src/kiri/mode/fdm/post.js | 67 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index d3a19198..1554ad4d 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -183,11 +183,11 @@ function thin_type_3(params) { let mergeChains = true; let interpolateShortSpur = true; let showChainRawPoints = false; - let showChainInterpPoints = true; + let showChainInterpPoints = false; let showMidline = true; let showMidPoints = true; let showMidNormals = true; - let showMidRadii = true; + let showMidRadii = false; let showNexuses = false; let pointMap = new Map(); @@ -481,6 +481,29 @@ function thin_type_3(params) { top.shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); } + function renderPointNormal(pt, ndx, ndy) { + if (showMidNormals) { + 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) { + 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)); + acc += r * 2; + } + } +} + // gather point offsets into shells let zi = z; for (let chain of chains) { @@ -510,7 +533,7 @@ function thin_type_3(params) { let dy = (p1.y - p0.y); let ndx = dx / len; let ndy = dy / len; - segs.push({ p0, p1, len, offs, step, dr, dx, dy, ndx, ndy }); + segs.push({ p0, p1, offs, step, dr, dx, dy, ndx, ndy }); } console.log({ segs }); // compute chain subdivisions @@ -518,7 +541,7 @@ function thin_type_3(params) { for (let si=0; si Date: Tue, 16 Sep 2025 10:22:22 -0400 Subject: [PATCH 21/24] thin3 use point vs dist comparison for closed chains --- src/kiri/mode/fdm/post.js | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index 1554ad4d..a2222402 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -177,17 +177,17 @@ function thin_type_3(params) { let scale = 1000; let minR = offsetN / 2; let maxR = minR * 1.5; - let minSpur = offsetN * 3; + let minSpur = offsetN * 2; let showNoodle = false; let mergeChains = true; let interpolateShortSpur = true; let showChainRawPoints = false; - let showChainInterpPoints = false; + let showChainInterpPoints = true; let showMidline = true; - let showMidPoints = true; - let showMidNormals = true; - let showMidRadii = false; + let showMidPoints = false; + let showMidNormals = false; + let showMidRadii = true; let showNexuses = false; let pointMap = new Map(); @@ -365,25 +365,25 @@ function thin_type_3(params) { let cj = chains[j]; if (!cj) continue; if (ci[0] === cj[0]) { - ci.reverse().appendAll(cj.slice(1)) + ci.reverse().appendAll(cj) ci.total += cj.total; cj.merged = true; chains[j] = undefined; return true; } else if (ci.peek() === cj.peek()) { - ci.appendAll(cj.reverse().slice(1)); + ci.appendAll(cj.reverse()); ci.total += cj.total; cj.merged = true; chains[j] = undefined; return true; } else if (ci.peek() === cj[0]) { - ci.appendAll(cj.slice(1)); + ci.appendAll(cj); ci.total += cj.total; cj.merged = true; chains[j] = undefined; return true; } else if (ci[0] === cj.peek()) { - cj.appendAll(ci.slice(1)); + cj.appendAll(ci); cj.total += ci.total; ci.merged = true; chains[i] = undefined; @@ -394,8 +394,6 @@ function thin_type_3(params) { return false; } - // console.log({ chains }); - // map chains to nexus via endpoints let nexus = {}; function addEp(point, chain) { @@ -411,24 +409,27 @@ function thin_type_3(params) { addEp(chain.peek(), chain); } - // console.log({ chains, nexus, np: Object.values(nexus).length }); - // connect chains end to end following nexus branches that result // in the longest final merged chain (open or closed) // map chain endpoints to nexus points while (mergeChains && merge1chain()) ; + // filter out null chains that were merged into other chains chains = chains.filter(chain => chain); - console.log({ chains }); + + // 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 for (let chain of chains) { - chain.closed = pointDist(chain[0], chain.peek()) <= offsetN; - // console.log(chain.closed); + chain.closed = (chain[0] === chain.peek()); + console.log(chain.closed ? 'closed' : 'open'); + 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]; @@ -454,8 +455,6 @@ function thin_type_3(params) { } } - // add chains - // shorten chains terminating at nexus by midR for (let rec of Object.values(nexus)) { let { point } = rec; @@ -476,8 +475,7 @@ function thin_type_3(params) { if (showNexuses) for (let rec of Object.values(nexus)) { - let { point, chains } = rec; - // console.log(rec); + let { point } = rec; top.shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts)); } From 1501d33c700e59d6c750d6f99b60b66345a8f079 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 17:13:57 -0400 Subject: [PATCH 22/24] fixes #427 now scale prevented below threshold --- src/kiri/core/init.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/kiri/core/init.js b/src/kiri/core/init.js index 36a68da0..02dd0a55 100644 --- a/src/kiri/core/init.js +++ b/src/kiri/core/init.js @@ -1323,7 +1323,8 @@ function init_one() { yr = ((tl && yc) || (!tl && yt) ? ra : 1), zr = ((tl && zc) || (!tl && zt) ? ra : 1); // prevent null scale - if (xr * yr * zr === 0) { + if (xv * xr < 0.1 || yv * yr < 0.1 || zv * zr < 0.1) { + api.alerts.show('invalid scale value'); return; } selection.scale(xr,yr,zr); @@ -1350,7 +1351,11 @@ function init_one() { xr = ((tl && xc) || (!tl && xt) ? ra : 1), yr = ((tl && yc) || (!tl && yt) ? ra : 1), zr = ((tl && zc) || (!tl && zt) ? ra : 1); - + // prevent null scale + if (xv * xr < 0.1 || yv * yr < 0.1 || zv * zr < 0.1) { + api.alerts.show('invalid scale value'); + return; + } selection.scale(xr,yr,zr); ui.scaleX.was = ui.scaleX.value = xv * xr; ui.scaleY.was = ui.scaleY.value = yv * yr; @@ -1362,6 +1367,7 @@ function init_one() { e.target.value = val; } + // on enter but not on blur space.event.onEnterKey([ ui.scaleX, selectionScale, ui.scaleY, selectionScale, @@ -1369,6 +1375,9 @@ function init_one() { ui.sizeX, selectionSize, ui.sizeY, selectionSize, ui.sizeZ, selectionSize, + ]); + // on enter and blur + space.event.onEnterKey([ ui.toolName, updateTool, ui.toolNum, updateTool, ui.toolFluteDiam, updateTool, From fc9b5b668b0ecf5ea097faddcf591217e991be6c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 17:23:04 -0400 Subject: [PATCH 23/24] fixes #426 by applying proper camZBottom value --- src/kiri/mode/cam/cl-tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri/mode/cam/cl-tab.js b/src/kiri/mode/cam/cl-tab.js index 8237065e..0cda4f12 100644 --- a/src/kiri/mode/cam/cl-tab.js +++ b/src/kiri/mode/cam/cl-tab.js @@ -61,7 +61,7 @@ export function createTabBox(iw, ic, n) { const { camZBottom, camStockIndexed } = process; const sz = stock.z || bounds.max.z; const zto = sz - iw.track.top; - const zp = (env.camZBottom || camStockIndexed ? env.camZBottom : sz - track.box.d - zto) + (camTabsMidline ? 0 : camTabsHeight / 2); + const zp = (camZBottom || camStockIndexed ? camZBottom : sz - track.box.d - zto) + (camTabsMidline ? 0 : camTabsHeight / 2); ic.x += n.x * camTabsDepth / 2; // offset from part ic.z -= n.y * camTabsDepth / 2; // offset swap z,y ic.y = zp; // offset swap in world space y,z From 65c2978f43f092d5199a14bb82e4abac2080199c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 21 Sep 2025 17:35:44 -0400 Subject: [PATCH 24/24] fixes #421 by removing all non-extended-thru cuts when step down is greater than full cut depth of part --- src/kiri/mode/cam/op-outline.js | 5 +++++ src/kiri/mode/cam/op-rough.js | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/kiri/mode/cam/op-outline.js b/src/kiri/mode/cam/op-outline.js index 270f4c64..c9b7e275 100644 --- a/src/kiri/mode/cam/op-outline.js +++ b/src/kiri/mode/cam/op-outline.js @@ -100,6 +100,11 @@ class OpOutline extends CamOp { // extend cut thru (only when z bottom is 0) if (workarea.bottom_z < 0) { let last = slices[slices.length-1]; + // when step down > full cut depth, clear slices and + // leave only the cut-thru pass(es) + if (op.down > workarea.top_stock - workarea.bottom_part) { + slices.length = 0; + } for (let zneg of base_util.lerp(0, -workarea.bottom_cut, op.down)) { if (!last) continue; let add = last.clone(true); diff --git a/src/kiri/mode/cam/op-rough.js b/src/kiri/mode/cam/op-rough.js index 341f56d8..beb24827 100644 --- a/src/kiri/mode/cam/op-rough.js +++ b/src/kiri/mode/cam/op-rough.js @@ -32,11 +32,12 @@ class OpRough extends CamOp { let roughStock = op.all && isIndexed; let toolDiam = new Tool(settings, op.tool).fluteDiameter(); let trueShadow = process.camTrueShadow === true; + let cutThruBypass = op.down > workarea.top_stock - workarea.bottom_part; updateToolDiams(toolDiam); // clear the stock above the area to be roughed out - if (workarea.top_z > workarea.top_part) { + if (workarea.top_z > workarea.top_part && !cutThruBypass) { let shadow = state.shadow.base.clone(); let step = toolDiam * op.step; let inset = roughStock ? @@ -258,6 +259,12 @@ class OpRough extends CamOp { let last = slices[slices.length-1]; + // when step down > full cut depth, clear slices and + // leave only the cut-thru pass(es) + if (cutThruBypass) { + slices.length = 0; + } + // add cut thru passes if (workarea.bottom_z < 0) for (let zneg of base_util.lerp(0, -workarea.bottom_cut, op.down)) { if (!last) continue;