From f703f0cef3ca0ae92f0824c390cff870124800ae Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 22 May 2025 12:32:21 -0600 Subject: [PATCH 01/73] move easeDown to prepare function --- src/geo/polygon.js | 69 +--------- src/kiri-mode/cam/prepare.js | 252 +++++++++++++++++++++++++++++++---- 2 files changed, 230 insertions(+), 91 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index d00fbbbee..ca606429a 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1188,74 +1188,7 @@ class Polygon { return false; } - /** - * Ease down along the polygonal path. - * - * 1. Travel from fromPoint to closest point on polygon, to rampZ above that that point, - * 2. ease-down starts, following the polygonal path, decreasing Z at a fixed slope until target Z is hit, - * 3. then the rest of the path is completed and repeated at target Z until touchdown point is reached. - * 4. this function should probably move to CAM prepare since it's only called from there - */ - forEachPointEaseDown(fn, fromPoint, degrees = 45) { - let index = this.findClosestPointTo(fromPoint).index, - fromZ = fromPoint.z, - offset = 0, - points = this.points, - length = points.length, - touch = -1, // first point to touch target z - targetZ = points[0].z, - dist2next, - last, - next, - done; - - // Slope for computations. - const slope = Math.tan((degrees * Math.PI) / 180); - // Z height above polygon Z from which to start the ease-down. - // Machine will travel from "fromPoint" to "nearest point x, y, z' => with z' = point z + rampZ", - // then start the ease down along path. - const rampZ = 2.0; - while (true) { - next = points[index % length]; - if (last && next.z < fromZ) { - // When "in Ease-Down" (ie. while target Z not yet reached) - follow path while slowly decreasing Z. - let deltaZ = fromZ - next.z; - dist2next = last.distTo2D(next); - let deltaZFullMove = dist2next * slope; - - if (deltaZFullMove > deltaZ) { - // Too long: easing along full path would overshoot depth, synth intermediate point at target Z. - // - // XXX: please check my super basic trig - this should follow from `last` to `next` up until the - // intersect at the target Z distance. - fn(last.followTo(next, dist2next * deltaZ / deltaZFullMove).setZ(next.z), offset++); - } else { - // Ok: execute full move at desired slope. - next = next.clone().setZ(fromZ - deltaZFullMove); - } - - fromZ = next.z; - } else if (offset === 0 && next.z < fromZ) { - // First point, move to rampZ height above next. - let deltaZ = fromZ - next.z; - fromZ = next.z + Math.min(deltaZ, rampZ) - next = next.clone().setZ(fromZ); - } - last = next; - fn(next, offset++); - if ((index % length) === touch) { - break; - } - if (touch < 0 && next.z <= targetZ) { - // Save touch-down index so as to be able to "complete" the full cut at target Z, - // i.e. keep following the path loop until the touch down point is reached again. - touch = ((index + length) % length); - } - index++; - } - - return last; - } + forEachPoint(fn, close, start) { let index = start || 0, diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index e5c9cbe9d..99ccec034 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -252,7 +252,7 @@ function prepEach(widget, settings, print, firstPoint, update) { if (lift) camOut(point.clone().setZ(point.z + lift), 0); } }) - camOut(point.clone().setZ(zmax)); + camOut(point.clone().setZ(zmax),0); newLayer(); } @@ -311,12 +311,22 @@ function prepEach(widget, settings, print, firstPoint, update) { /** * emit a cut or move operation from the current location to a new location - * @param {*} point destination for move - * @param {*} cut 1/true = cut, 0/false = move - * @param {*} moveLen typically = tool diameter used to trigger terrain detection - * @param {*} factor speed scale factor + * @param {Point} point destination for move + * @param {1|2} cut 1/true = cut, 0/false = move + * @param {number} opts.radius arc radius; truthy values for arc move + * @param {boolean} opts.clockwise arc direction + * @param {number} opts.moveLen typically = tool diameter used to trigger terrain detection + * @param {number} opts.factor speed scale factor */ - function camOut(point, cut, moveLen = toolDiamMove, factor = 1) { + function camOut(point, cut,opts) { + + let { + radius = null, + clockwise = true, + moveLen = toolDiamMove, + factor = 1, + } = opts ?? {} + point = point.clone(); point.x += wmx; point.y += wmy; @@ -463,6 +473,7 @@ function prepEach(widget, settings, print, firstPoint, update) { /** * output an array of slices that form a pocket + * used by rough and pocket ops, does not support arcs * * @param {Slice[]} top-down Z stack of slices * @param {*} opts @@ -494,7 +505,7 @@ function prepEach(widget, settings, print, firstPoint, update) { printPoint = poly2polyEmit(polys, printPoint, function(poly, index, count) { poly.forEachPoint(function(point, pidx, points, offset) { // scale speed of first cutting poly since it engages the full bit - camOut(point.clone(), offset !== 0, undefined, count === 1 ? engageFactor : 1); + camOut(point.clone(), offset !== 0,{ factor: count === 1 ? engageFactor : 1}); }, poly.isClosed(), index); }, { swapdir: false }); newLayer(); @@ -520,6 +531,71 @@ function prepEach(widget, settings, print, firstPoint, update) { printPoint = depthRoughPath(printPoint, 0, outs, otops, polyEmit, false, easeDown); } } + /** + * Ease down along the polygonal path. + * + * 1. Travel from fromPoint to closest point on polygon, to rampZ above that that point, + * 2. ease-down starts, following the polygonal path, decreasing Z at a fixed slope until target Z is hit, + */ + + function generateEaseDown(fn,poly, fromPoint, degrees = 45){ + let index = poly.findClosestPointTo(fromPoint).index, + fromZ = fromPoint.z, + offset = 0, + points = poly.points, + length = points.length, + touch = -1, // first point to touch target z + targetZ = points[0].z, + dist2next, + last, + next, + done; + + // Slope for computations. + const slope = Math.tan((degrees * Math.PI) / 180); + // Z height above polygon Z from which to start the ease-down. + // Machine will travel from "fromPoint" to "nearest point x, y, z' => with z' = point z + rampZ", + // then start the ease down along path. + const rampZ = 2.0; + while (true) { + next = points[index % length]; + if (last && next.z < fromZ) { + // When "in Ease-Down" (ie. while target Z not yet reached) - follow path while slowly decreasing Z. + let deltaZ = fromZ - next.z; + dist2next = last.distTo2D(next); + let deltaZFullMove = dist2next * slope; + + if (deltaZFullMove > deltaZ) { + // Too long: easing along full path would overshoot depth, synth intermediate point at target Z. + // + // XXX: please check my super basic trig - this should follow from `last` to `next` up until the + // intersect at the target Z distance. + fn(last.followTo(next, dist2next * deltaZ / deltaZFullMove).setZ(next.z), offset++); + } else { + // Ok: execute full move at desired slope. + next = next.clone().setZ(fromZ - deltaZFullMove); + } + + fromZ = next.z; + } else if (offset === 0 && next.z < fromZ) { + // First point, move to rampZ height above next. + let deltaZ = fromZ - next.z; + fromZ = next.z + Math.min(deltaZ, rampZ) + next = next.clone().setZ(fromZ); + } + last = next; + fn(next, offset++); + if (touch < 0 && next.z <= targetZ) { + // Save touch-down index so as to be able to "complete" the full cut at target Z, + // i.e. keep following the path loop until the touch down point is reached again. + touch = ((index + length) % length); + break; //break after touch + } + index++; + } + + return touch; + } // coming from a previous widget, use previous last point lastPoint = firstPoint; @@ -603,26 +679,156 @@ function prepEach(widget, settings, print, firstPoint, update) { newLayer(); } - // prepare - // sliceOutput - // depthRoughPath - // poly2polyEmit - // roughTopEmit - // poly2polyEmit + + + + /** + * Output a single polygon as gcode. The polygon is walked in either the + * clockwise or counter-clockwise direction depending on the winding of the + * polygon. The first point of the polygon is assumed to be the starting + * point, and the last point is assumed to be the ending point. If the + * polygon is closed, the starting and ending points are the same. The + * function will automatically output a rapid move to the first point of + * the polygon if that point is not the current position. + * + * @param {Polygon} poly - the polygon to output + * @param {number} index - the index of the polygon in its containing array + * @param {number} count - the total number of polygons in the array + * @param {Point} fromPoint - the point to rapid move from + * @returns {Point} - the last point of the polygon + */ function polyEmit(poly, index, count, fromPoint) { + + console.log('polyEmit', poly, index, count); + + // if (fromPoint && out.emit) { + // if (arcDist) { + // let rec = {e:out.emit, x, y, z, dist, emitPerMM, speedMMM}; + // arcQ.push(rec); + // let deem = false; // do arcQ[0] and rec have differing emit values? + // let depm = false; // do arcQ[0] and rec have differing emit speeds? + // let desp = false; // do arcQ[0] and rec have differing move speeds? + // if (arcQ.length > 1) { + // let el = arcQ.length; + // deem = arcQ[0].e !== rec.e; + // depm = arcQ[0].emitPerMM !== rec.emitPerMM; + // desp = arcQ[0].speedMMM !== rec.speedMMM; + // } + // // ondebug({arcQ}); + // if (arcQ.length > 2) { + // let el = arcQ.length; + // let e1 = arcQ[0]; // first in arcQ + // let e2 = arcQ[Math.floor(el/2)]; // mid in arcQ + // let e3 = arcQ[el-1]; // last in arcQ + // let e4 = arcQ[el-2]; // second last in arcQ + // let e5 = arcQ[el-3]; // third last in arcQ + // let cc = util.center2d(e1, e2, e3, 1); // find center + // let lr = util.center2d(e3, e4, e5, 1); // find local radius + // let dc = 0; + + // let radFault = false; + // if (lr) { + // let angle = 2 * Math.asin(dist/(2*lr.r)); + // radFault = Math.abs(angle) > Math.PI * 2 / arcRes; // enforce arcRes(olution) + // // if (arcQ.center) { + // // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); + // // let avg = arcQ.rSum / arcQ.center.length; + // // radFault = radFault || Math.abs(avg - lr.r) / avg > arcDev; // eliminate sharps and flats when local rad is out of arcDev(iation) + // // } + // } else { + // radFault = true; + // } + + // if (cc) { + // if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { + // console.log({cc, e1, e2, e3}); + // } + // if (arcQ.length === 3) { + // arcQ.center = [ cc ]; + // arcQ.xSum = cc.x; + // arcQ.ySum = cc.y; + // arcQ.rSum = cc.r; + // } else { + // // check center point delta + // arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 ); + // arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 ); + // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); + // let dx = cc.x - arcQ.xSum / arcQ.center.length; + // let dy = cc.y - arcQ.ySum / arcQ.center.length; + // dc = Math.sqrt(dx * dx + dy * dy); + // } + + // // if new point is off the arc + // // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { + // if (deem || depm || desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) { + // // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; + // if (arcQ.length === 4) { + // // not enough points for an arc, drop first point and recalc center + // emitQrec(arcQ.shift()); + // let tc = util.center2d(arcQ[0], arcQ[1], arcQ[2], 1); + // // the new center is invalid as well. drop the first point + // if (!tc) { + // emitQrec(arcQ.shift()); + // } else { + // arcQ.center = [ tc ]; + // let angle = 2 * Math.asin(arcQ[1].dist/(2*tc.r)); + // if (Math.abs(angle) > Math.PI * 2 / arcRes) { // enforce arcRes on initial angle + // emitQrec(arcQ.shift()); + // } + // } + // } else { + // // enough to consider an arc, emit and start new arc + // let defer = arcQ.pop(); + // drainQ(); + // // re-add point that was off the last arc + // arcQ.push(defer); + // } + // } else { + // // new point is on the arc + // arcQ.center.push(cc); + // } + // } else { + // // drainQ on invalid center + // drainQ(); + // } + // } + // } else { + // // emitMM = emitPerMM * out.emit * dist; + // emitMM = extrudeMM(dist, emitPerMM, out.emit); + // moveTo({x:x, y:y, e:emitMM}, speedMMM); + // emitted += emitMM; + // } + // } else { + // drainQ(); + // moveTo({x:x, y:y}, seekMMM); + // // TODO disabling out of plane z moves until a better mechanism + // // can be built that doesn't rely on computed zpos from layer heights... + // // when making z moves (like polishing) allow slowdown vs fast seek + // // let moveSpeed = (lastp && lastp.z !== z) ? speedMMM : seekMMM; + // // moveTo({x:x, y:y, z:z}, moveSpeed); + // } + + + + let last = null; // scale speed of first cutting poly since it engages the full bit let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; + if (easeDown && poly.isClosed()) { - last = poly.forEachPointEaseDown(function(point, offset) { - camOut(point.clone(), offset > 0, undefined, scale); - }, fromPoint, easeAngle); - } else { - poly.forEachPoint(function(point, pidx, points, offset) { - last = point; - camOut(point.clone(), offset !== 0, undefined, scale); - }, poly.isClosed(), index); - } + last = generateEaseDown((point, pidx, points, offset)=>{ + + camOut(point.clone(), 1, {factor:scale}); + }, poly, fromPoint, easeAngle); + } + console.log(last,last) + + poly.forEachPoint(function(point, pidx, points, offset) { + last = point; + camOut(point.clone(), offset !== 0, {factor:scale}); + }, poly.isClosed(), last); + + newLayer(); return last; } @@ -715,7 +921,7 @@ function prepEach(widget, settings, print, firstPoint, update) { print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool); } } - + console.log("prepare output", newOutput); // replace output single flattened layer with all points print.output = newOutput; return printPoint; From 234ce115be9f0244ae2dc21a1f584cffb3dc2dc1 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 22 May 2025 12:32:28 -0600 Subject: [PATCH 02/73] add jsDoc --- src/kiri-mode/cam/export.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 6f825ab17..1de3989fe 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -118,6 +118,16 @@ CAM.export = function(print, online) { } } + /** + * Take an array of lines and emit them after: + * - splitting a string into an array + * - stripping comments if !isRML and stripComments + * - applying constant replacements + * - applying Inch/Millimeter conversions if G20 or G21 are found + * + * @param {string|Array} array - an array of lines or a single string + * @param {Object} consts - a dictionary of constants to replace + */ function filterEmit(array, consts) { if (!array) { return; From bdf131109c2471a3be45762bb396d58f63ca2b94 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Fri, 23 May 2025 21:59:32 -0600 Subject: [PATCH 03/73] get first version of the arc que operational --- src/kiri-mode/cam/prepare.js | 302 +++++++++++++++++++++-------------- 1 file changed, 186 insertions(+), 116 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 99ccec034..d2d441933 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -11,7 +11,7 @@ gapp.register("kiri-mode.cam.prepare", (root, exports) => { const { base, kiri } = root; -const { paths, polygons, newPoint } = base; +const { paths, polygons, newPoint, util} = base; const { tip2tipEmit, poly2polyEmit } = paths; const { driver, render } = kiri; const { CAM } = driver; @@ -312,7 +312,7 @@ function prepEach(widget, settings, print, firstPoint, update) { /** * emit a cut or move operation from the current location to a new location * @param {Point} point destination for move - * @param {1|2} cut 1/true = cut, 0/false = move + * @param {1|0|boolean} cut 1/true = cut, 0/false = move * @param {number} opts.radius arc radius; truthy values for arc move * @param {boolean} opts.clockwise arc direction * @param {number} opts.moveLen typically = tool diameter used to trigger terrain detection @@ -699,115 +699,182 @@ function prepEach(widget, settings, print, firstPoint, update) { */ function polyEmit(poly, index, count, fromPoint) { - console.log('polyEmit', poly, index, count); - - // if (fromPoint && out.emit) { - // if (arcDist) { - // let rec = {e:out.emit, x, y, z, dist, emitPerMM, speedMMM}; - // arcQ.push(rec); - // let deem = false; // do arcQ[0] and rec have differing emit values? - // let depm = false; // do arcQ[0] and rec have differing emit speeds? - // let desp = false; // do arcQ[0] and rec have differing move speeds? - // if (arcQ.length > 1) { - // let el = arcQ.length; - // deem = arcQ[0].e !== rec.e; - // depm = arcQ[0].emitPerMM !== rec.emitPerMM; - // desp = arcQ[0].speedMMM !== rec.speedMMM; - // } - // // ondebug({arcQ}); - // if (arcQ.length > 2) { - // let el = arcQ.length; - // let e1 = arcQ[0]; // first in arcQ - // let e2 = arcQ[Math.floor(el/2)]; // mid in arcQ - // let e3 = arcQ[el-1]; // last in arcQ - // let e4 = arcQ[el-2]; // second last in arcQ - // let e5 = arcQ[el-3]; // third last in arcQ - // let cc = util.center2d(e1, e2, e3, 1); // find center - // let lr = util.center2d(e3, e4, e5, 1); // find local radius - // let dc = 0; - - // let radFault = false; - // if (lr) { - // let angle = 2 * Math.asin(dist/(2*lr.r)); - // radFault = Math.abs(angle) > Math.PI * 2 / arcRes; // enforce arcRes(olution) - // // if (arcQ.center) { - // // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); - // // let avg = arcQ.rSum / arcQ.center.length; - // // radFault = radFault || Math.abs(avg - lr.r) / avg > arcDev; // eliminate sharps and flats when local rad is out of arcDev(iation) - // // } - // } else { - // radFault = true; - // } - - // if (cc) { - // if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { - // console.log({cc, e1, e2, e3}); - // } - // if (arcQ.length === 3) { - // arcQ.center = [ cc ]; - // arcQ.xSum = cc.x; - // arcQ.ySum = cc.y; - // arcQ.rSum = cc.r; - // } else { - // // check center point delta - // arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 ); - // arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 ); - // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); - // let dx = cc.x - arcQ.xSum / arcQ.center.length; - // let dy = cc.y - arcQ.ySum / arcQ.center.length; - // dc = Math.sqrt(dx * dx + dy * dy); - // } - - // // if new point is off the arc - // // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { - // if (deem || depm || desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) { - // // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; - // if (arcQ.length === 4) { - // // not enough points for an arc, drop first point and recalc center - // emitQrec(arcQ.shift()); - // let tc = util.center2d(arcQ[0], arcQ[1], arcQ[2], 1); - // // the new center is invalid as well. drop the first point - // if (!tc) { - // emitQrec(arcQ.shift()); - // } else { - // arcQ.center = [ tc ]; - // let angle = 2 * Math.asin(arcQ[1].dist/(2*tc.r)); - // if (Math.abs(angle) > Math.PI * 2 / arcRes) { // enforce arcRes on initial angle - // emitQrec(arcQ.shift()); - // } - // } - // } else { - // // enough to consider an arc, emit and start new arc - // let defer = arcQ.pop(); - // drainQ(); - // // re-add point that was off the last arc - // arcQ.push(defer); - // } - // } else { - // // new point is on the arc - // arcQ.center.push(cc); - // } - // } else { - // // drainQ on invalid center - // drainQ(); - // } - // } - // } else { - // // emitMM = emitPerMM * out.emit * dist; - // emitMM = extrudeMM(dist, emitPerMM, out.emit); - // moveTo({x:x, y:y, e:emitMM}, speedMMM); - // emitted += emitMM; - // } - // } else { - // drainQ(); - // moveTo({x:x, y:y}, seekMMM); - // // TODO disabling out of plane z moves until a better mechanism - // // can be built that doesn't rely on computed zpos from layer heights... - // // when making z moves (like polishing) allow slowdown vs fast seek - // // let moveSpeed = (lastp && lastp.z !== z) ? speedMMM : seekMMM; - // // moveTo({x:x, y:y, z:z}, moveSpeed); - // } + const arcDist = 1, + arcRes = 2, //2 degs max + arcMax = Infinity; // no max arc radius + // console.log('polyEmit', poly, index, count); + + let arcQ = []; + + function arcExport(point,lastp){ + + let dist = lastp? point.distTo2D(lastp) : 0; + + if (lastp) { + if (arcDist) { + let rec = Object.assign(point,{dist}); + arcQ.push(rec); + let desp = false; // do arcQ[0] and rec have differing move speeds? + if (arcQ.length > 1) { + let el = arcQ.length; + desp = arcQ[0].speedMMM !== rec.speedMMM; + } + // ondebug({arcQ}); + if (arcQ.length > 2) { + let el = arcQ.length; + let e1 = arcQ[0]; // first in arcQ + let e2 = arcQ[Math.floor(el/2)]; // mid in arcQ + let e3 = arcQ[el-1]; // last in arcQ + let e4 = arcQ[el-2]; // second last in arcQ + let e5 = arcQ[el-3]; // third last in arcQ + let cc = util.center2d(e1, e2, e3, 1); // find center + let lr = util.center2d(e3, e4, e5, 1); // find local radius + let dc = 0; + + let radFault = false; + if (lr) { + let angle = 2 * Math.asin(dist/(2*lr.r)); + radFault = Math.abs(angle) > Math.PI * 2 / arcRes; // enforce arcRes(olution) + // if (arcQ.center) { + // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); + // let avg = arcQ.rSum / arcQ.center.length; + // radFault = radFault || Math.abs(avg - lr.r) / avg > arcDev; // eliminate sharps and flats when local rad is out of arcDev(iation) + // } + } else { + radFault = true; + } + + if (cc) { + if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { + console.log({cc, e1, e2, e3}); + } + if (arcQ.length === 3) { + arcQ.center = [ cc ]; + arcQ.xSum = cc.x; + arcQ.ySum = cc.y; + arcQ.rSum = cc.r; + } else { + // check center point delta + arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 ); + arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 ); + arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); + let dx = cc.x - arcQ.xSum / arcQ.center.length; + let dy = cc.y - arcQ.ySum / arcQ.center.length; + dc = Math.sqrt(dx * dx + dy * dy); + } + + // if new point is off the arc + // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { + if ( desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault ) { + // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; + console.log("point off the arc,",structuredClone(arcQ)); + if (arcQ.length === 4) { + // not enough points for an arc, drop first point and recalc center + camOut(arcQ.shift(),1); + let tc = util.center2d(arcQ[0], arcQ[1], arcQ[2], 1); + // the new center is invalid as well. drop the first point + if (!tc) { + camOut(arcQ.shift(),1); + } else { + arcQ.center = [ tc ]; + let angle = 2 * Math.asin(arcQ[1].dist/(2*tc.r)); + if (Math.abs(angle) > Math.PI * 2 / arcRes) { // enforce arcRes on initial angle + camOut(arcQ.shift(),1); + } + } + } else { + // enough to consider an arc, emit and start new arc + let defer = arcQ.pop(); + drainQ(); + // re-add point that was off the last arc + arcQ.push(defer); + } + } else { + // new point is on the arc + arcQ.center.push(cc); + } + } else { + // drainQ on invalid center + drainQ(); + } + } + } else { + // emitMM = emitPerMM * out.emit * dist; + emitMM = extrudeMM(dist, emitPerMM, out.emit); + camOut({x, y, e:emitMM}, true,); + emitted += emitMM; + } + } else { + // if no last point, emit and set + drainQ(); + camOut({x, y},1 ); + // TODO disabling out of plane z moves until a better mechanism + // can be built that doesn't rely on computed zpos from layer heights... + // when making z moves (like polishing) allow slowdown vs fast seek + // let moveSpeed = (lastp && lastp.z !== z) ? speedMMM : seekMMM; + // moveTo({x:x, y:y, z:z}, moveSpeed); + } + return point; + } + + + + function drainQ() { + + console.log("drainQ called",structuredClone(arcQ)); + + if (!arcDist) { + return; + } + if (arcQ.length > 4) { + // ondebug({arcQ}); + let vec1 = new THREE.Vector2(arcQ[1].x - arcQ[0].x, arcQ[1].y - arcQ[0].y); + let vec2 = new THREE.Vector2(arcQ.center[0].x - arcQ[0].x, arcQ.center[0].y - arcQ[0].y); + let clockwise = vec1.cross(vec2) < 0 ? 'G2' : 'G3'; + let from = arcQ[0]; + let to = arcQ.peek(); + arcQ.xSum = arcQ.center.reduce( (t, v) => t + v.x , 0 ); + arcQ.ySum = arcQ.center.reduce( (t, v) => t + v.y , 0 ); + arcQ.rSum = arcQ.center.reduce( (t, v) => t + v.r , 0 ); + let cl = arcQ.center.length; + let cc; + + let angle = util.thetaDiff( + Math.atan2((from.y - arcQ.ySum / cl), (from.x - arcQ.xSum / cl)), + Math.atan2((to.y - arcQ.ySum / cl), (to.x - arcQ.xSum / cl)), + clockwise + ); + + if (Math.abs(angle) <= 3 * Math.PI / 4) { + cc = util.center2pr(from, to, arcQ.rSum / cl, !clockwise); + } + + if (!cc) { + cc = {x:arcQ.xSum/cl, y:arcQ.ySum/cl, z:arcQ[0].z, r:arcQ.rSum/cl}; + } + + // first arc point + camOut(from,true); + // rest of arc to final point + + // XYR form + // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} `; + camOut(to,1,{radius:cc.r, clockwise}); + // XYIJ form + // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`; + + // let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; + // append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`); + } else { + //if q too short, emit as lines + for (let rec of arcQ) { + camOut(rec,1); + } + } + arcQ.length = 0; + arcQ.center = undefined; + } @@ -815,19 +882,22 @@ function prepEach(widget, settings, print, firstPoint, update) { // scale speed of first cutting poly since it engages the full bit let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; - if (easeDown && poly.isClosed()) { - last = generateEaseDown((point, pidx, points, offset)=>{ - - camOut(point.clone(), 1, {factor:scale}); + if (easeDown && poly.isClosed()) { //if doing ease-down + last = generateEaseDown((point, )=>{ //generate ease-down points + camOut(point.clone(), 1, {factor:scale}); // and pass them to camOut }, poly, fromPoint, easeAngle); } - console.log(last,last) poly.forEachPoint(function(point, pidx, points, offset) { + arcExport(point,last) + // camOut(point.clone(), offset !== 0, {factor:scale}); last = point; - camOut(point.clone(), offset !== 0, {factor:scale}); }, poly.isClosed(), last); + // console.log("at end of arcExport",structuredClone(arcQ)); + drainQ(); + // console.log("at end of arcExport",structuredClone(arcQ)); + newLayer(); return last; From 1510d38c8222f666c494712d4a739146c27c5d51 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sat, 24 May 2025 12:09:47 -0600 Subject: [PATCH 04/73] fix last point detection --- src/kiri-mode/cam/prepare.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index d2d441933..839385276 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -889,9 +889,8 @@ function prepEach(widget, settings, print, firstPoint, update) { } poly.forEachPoint(function(point, pidx, points, offset) { - arcExport(point,last) + last =arcExport(point,last) // camOut(point.clone(), offset !== 0, {factor:scale}); - last = point; }, poly.isClosed(), last); // console.log("at end of arcExport",structuredClone(arcQ)); From 47048616331ac4d791aa727fc6e87c1bc00071af Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 27 May 2025 12:49:27 -0600 Subject: [PATCH 05/73] seperate arc generation from g2g3 func --- src/kiri/print.js | 94 ++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/src/kiri/print.js b/src/kiri/print.js index 7a80a0dc2..333109378 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -277,7 +277,6 @@ class Print { seq = [], autolayer = true, newlayer = false, - arcdivs = Math.PI / 24, hasmoved = false, lastG = 'G1'; @@ -288,61 +287,57 @@ class Print { console.log(...[...arguments].map(o => Object.clone(o))); } - function G2G3(g2, line, index) { - const rec = {}; - - line.forEach(tok => { - rec[tok.charAt(0)] = parseFloat(tok.substring(1)); - }); + function arcToPoints( radius, clockwise, start, end,arcdivs=Math.PI / 24) { + console.log({radius, clockwise, start, end, arcdivs}); let center = { x:0, y:0, r:0 }; - if (rec.X === undefined && rec.X === rec.Y) { + if (end.x === undefined && end.x === end.y) { // bambu generates loop z or wipe loop arcs in place // console.log({ skip_empty_arc: rec }); return; } - // G0G1(false, [`X${rec.X}`, `Y${rec.Y}`, `E1`]);return; - - if (rec.I !== undefined && rec.J !== undefined) { - center.x = pos.X + rec.I; - center.y = pos.Y + rec.J; - center.r = Math.sqrt(rec.I*rec.I + rec.J*rec.J); - } else if (rec.R !== undefined) { - let pd = { x: rec.X - pos.X, y: rec.Y - pos.Y }; - let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; + // G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]);return; + + if (end.I !== undefined && end.J !== undefined) { + center.x = start.x + end.I; + center.y = start.y + end.J; + center.r = Math.sqrt(end.I*end.I + end.J*end.J); + } else if (radius !== undefined) { + let pd = { x: end.x - start.x, y: end.y - start.y }; //position delta + let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; // distance let pr2; - if (Math.abs(dst - rec.R) < 0.001) { + if (Math.abs(dst - radius) < 0.001) { // center point radius - pr2 = { x: (rec.X + pos.X) / 2, y: (rec.Y + pos.Y) / 2}; + pr2 = { x: (end.x + start.x) / 2, y: (end.y + start.y) / 2}; } else { // triangulate pr2 = base.util.center2pr({ - x: pos.X, - y: pos.Y + x: start.x, + y: start.y }, { - x: rec.X, - y: rec.Y - }, rec.R, g2); + x: end.x, + y: end.y + }, radius, clockwise); } center.x = pr2.x; center.y = pr2.y; - center.r = rec.R; + center.r = radius; } else { - console.log({malfomed_arc: line}); + console.log({malfomed_arc: {radius, clockwise, start, end}}); } // line angles - let a1 = Math.atan2(center.y - pos.Y, center.x - pos.X) + Math.PI; - let a2 = Math.atan2(center.y - rec.Y, center.x - rec.X) + Math.PI; - let ad = base.util.thetaDiff(a1, a2, g2); + let a1 = Math.atan2(center.y - start.y, center.x - start.x) + Math.PI; + let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI; + let ad = base.util.thetaDiff(a1, a2, clockwise); let steps = Math.max(Math.floor(Math.abs(ad) / arcdivs), 3); let step = (Math.abs(ad) > 0.001 ? ad : Math.PI * 2) / steps; let rot = a1 + step; let da = Math.abs(a1 - a2); - let dx = pos.X - rec.X; - let dy = pos.Y - rec.Y; + let dx = start.x - end.x; + let dy = start.y - end.y; let dd = Math.sqrt(dx * dx + dy * dy); // LOG({index, da, dd, first: pos, last: rec, center, a1, a2, ad, step, steps, rot, line}); @@ -350,24 +345,41 @@ class Print { // under 1 degree arc and 5mm, convert to straight line if (da < 0.005 && dd < 5) { - G0G1(false, [`X${rec.X}`, `Y${rec.Y}`, `E1`]); + G0G1(false, [`X${end.x}`, `Y${end.y}`, `E1`]); return; } - let pc = { X: pos.X, Y: pos.Y }; + let arr = [] // point accumulator for (let i=0; i<=steps-2; i++) { - let np = { - X: center.x + Math.cos(rot) * center.r, - Y: center.y + Math.sin(rot) * center.r - }; + arr.push(newPoint( + center.x + Math.cos(rot) * center.r, + center.y + Math.sin(rot) * center.r)); rot += step; - G0G1(false, [`X${np.X}`, `Y${np.Y}`, `E1`]); } + return arr + } + + function G2G3(g2, line, index) { + + console.log({index, g2, line}); + + const rec = {}; + + line.forEach(tok => { + rec[tok.charAt(0).toLowerCase()] = parseFloat(tok.substring(1)); + }); + + pos.x = pos.X; + pos.y = pos.Y; + + arcToPoints(rec.r, g2, pos, rec, 0.02).forEach(np => { + G0G1(false, [`X${np.x}`, `Y${np.y}`, `E1`]); + }) - G0G1(false, [`X${rec.X}`, `Y${rec.Y}`, `E1`]); + G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]); - pos.X = rec.X; - pos.Y = rec.Y; + pos.X = rec.x; + pos.Y = rec.y; } function G0G1(g0, line) { From 9d11874002e3776d2ac40d7640dbdf7391fd5a24 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 27 May 2025 12:58:18 -0600 Subject: [PATCH 06/73] move arcToPath to paths file --- src/geo/paths.js | 89 ++++++++++++++++++++++++++++++++++++++++++++++- src/kiri/print.js | 89 +++++------------------------------------------ 2 files changed, 96 insertions(+), 82 deletions(-) diff --git a/src/geo/paths.js b/src/geo/paths.js index d0de1a1ed..da277d1d2 100644 --- a/src/geo/paths.js +++ b/src/geo/paths.js @@ -6,10 +6,11 @@ // dep: geo.base // dep: geo.point + gapp.register("geo.paths", [], (root, exports) => { const { base } = root; -const { util, config } = base; +const { util, config, newPoint } = base; const { sqr, numOrDefault } = util; const DEG2RAD = Math.PI / 180; @@ -526,6 +527,91 @@ function shapeToPath(shape, points, closed) { return {index, faces}; } +/** + * Generate a list of points approximating a circular arc. + * + * @param {number} radius - the radius of the arc. If undefined, will use the + * start and end points to infer the radius. + * @param {boolean} clockwise - whether the arc is clockwise or counter-clockwise. + * @param {Point} start - the starting point of the arc. + * @param {Point} end - the ending point of the arc. + * @param {number} [arcdivs=Math.PI / 24] - the angular increment to use when + * generating the points. + * + * @return {Array} an array of points representing the arc. + */ +function arcToPath( radius, clockwise, start, end,arcdivs=Math.PI / 24) { + + console.log({radius, clockwise, start, end, arcdivs}); + let center = { x:0, y:0, r:0 }; + + if (end.x === undefined && end.x === end.y) { + // bambu generates loop z or wipe loop arcs in place + // console.log({ skip_empty_arc: rec }); + return; + } + // G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]);return; + + if (end.I !== undefined && end.J !== undefined) { + center.x = start.x + end.I; + center.y = start.y + end.J; + center.r = Math.sqrt(end.I*end.I + end.J*end.J); + } else if (radius !== undefined) { + let pd = { x: end.x - start.x, y: end.y - start.y }; //position delta + let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; // distance + let pr2; + if (Math.abs(dst - radius) < 0.001) { + // center point radius + pr2 = { x: (end.x + start.x) / 2, y: (end.y + start.y) / 2}; + } else { + // triangulate + pr2 = base.util.center2pr({ + x: start.x, + y: start.y + }, { + x: end.x, + y: end.y + }, radius, clockwise); + } + center.x = pr2.x; + center.y = pr2.y; + center.r = radius; + } else { + console.log({malfomed_arc: {radius, clockwise, start, end}}); + } + + // line angles + let a1 = Math.atan2(center.y - start.y, center.x - start.x) + Math.PI; + let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI; + let ad = base.util.thetaDiff(a1, a2, clockwise); + let steps = Math.max(Math.floor(Math.abs(ad) / arcdivs), 3); + let step = (Math.abs(ad) > 0.001 ? ad : Math.PI * 2) / steps; + let rot = a1 + step; + + let da = Math.abs(a1 - a2); + let dx = start.x - end.x; + let dy = start.y - end.y; + let dd = Math.sqrt(dx * dx + dy * dy); + + // LOG({index, da, dd, first: pos, last: rec, center, a1, a2, ad, step, steps, rot, line}); + // G0G1(false, [`X${center.x}`, `Y${center.y}`, `E1`]); + + // under 1 degree arc and 5mm, convert to straight line + if (da < 0.005 && dd < 5) { + G0G1(false, [`X${end.x}`, `Y${end.y}`, `E1`]); + return; + } + + let arr = [] // point accumulator + for (let i=0; i<=steps-2; i++) { + arr.push(newPoint( + center.x + Math.cos(rot) * center.r, + center.y + Math.sin(rot) * center.r)); + rot += step; + } + return arr +} + class FloatPacker { constructor(size, factor) { this.size = size; @@ -565,6 +651,7 @@ base.paths = { shapeToPath, pointsToPath, pathTo3D, + arcToPath, vertexNormal: calc_vertex, segmentNormal: calc_normal }; diff --git a/src/kiri/print.js b/src/kiri/print.js index 333109378..32f8e2adf 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -11,6 +11,7 @@ gapp.register("kiri.print", [], (root, evets) => { const { base, kiri } = self; const { paths, util, newPoint } = base; +const { arcToPath } = paths; const { numOrDefault } = util; const { beltfact } = kiri.consts; const XAXIS = new THREE.Vector3(1,0,0); @@ -287,97 +288,23 @@ class Print { console.log(...[...arguments].map(o => Object.clone(o))); } - function arcToPoints( radius, clockwise, start, end,arcdivs=Math.PI / 24) { - - console.log({radius, clockwise, start, end, arcdivs}); - let center = { x:0, y:0, r:0 }; - - if (end.x === undefined && end.x === end.y) { - // bambu generates loop z or wipe loop arcs in place - // console.log({ skip_empty_arc: rec }); - return; - } - // G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]);return; - - if (end.I !== undefined && end.J !== undefined) { - center.x = start.x + end.I; - center.y = start.y + end.J; - center.r = Math.sqrt(end.I*end.I + end.J*end.J); - } else if (radius !== undefined) { - let pd = { x: end.x - start.x, y: end.y - start.y }; //position delta - let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; // distance - let pr2; - if (Math.abs(dst - radius) < 0.001) { - // center point radius - pr2 = { x: (end.x + start.x) / 2, y: (end.y + start.y) / 2}; - } else { - // triangulate - pr2 = base.util.center2pr({ - x: start.x, - y: start.y - }, { - x: end.x, - y: end.y - }, radius, clockwise); - } - center.x = pr2.x; - center.y = pr2.y; - center.r = radius; - } else { - console.log({malfomed_arc: {radius, clockwise, start, end}}); - } - - // line angles - let a1 = Math.atan2(center.y - start.y, center.x - start.x) + Math.PI; - let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI; - let ad = base.util.thetaDiff(a1, a2, clockwise); - let steps = Math.max(Math.floor(Math.abs(ad) / arcdivs), 3); - let step = (Math.abs(ad) > 0.001 ? ad : Math.PI * 2) / steps; - let rot = a1 + step; - - let da = Math.abs(a1 - a2); - let dx = start.x - end.x; - let dy = start.y - end.y; - let dd = Math.sqrt(dx * dx + dy * dy); - - // LOG({index, da, dd, first: pos, last: rec, center, a1, a2, ad, step, steps, rot, line}); - // G0G1(false, [`X${center.x}`, `Y${center.y}`, `E1`]); - - // under 1 degree arc and 5mm, convert to straight line - if (da < 0.005 && dd < 5) { - G0G1(false, [`X${end.x}`, `Y${end.y}`, `E1`]); - return; - } - - let arr = [] // point accumulator - for (let i=0; i<=steps-2; i++) { - arr.push(newPoint( - center.x + Math.cos(rot) * center.r, - center.y + Math.sin(rot) * center.r)); - rot += step; - } - return arr - } - + /** + * Handles G2 and G3 arcs, which are circular arcs. + * @param {boolean} g2 - Whether this is a G2 or G3 arc. G2 is a clockwise arc, G3 is a counter-clockwise arc. + * @param {string[]} line - The line of the g-code file that contains the G2 or G3 command. + * @param {number} index - The line number of the g-code file that contains the G2 or G3 command. + */ function G2G3(g2, line, index) { - - console.log({index, g2, line}); - const rec = {}; - line.forEach(tok => { rec[tok.charAt(0).toLowerCase()] = parseFloat(tok.substring(1)); }); - pos.x = pos.X; pos.y = pos.Y; - - arcToPoints(rec.r, g2, pos, rec, 0.02).forEach(np => { + arcToPath(rec.r, g2, pos, rec, 0.02).forEach(np => { G0G1(false, [`X${np.x}`, `Y${np.y}`, `E1`]); }) - G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]); - pos.X = rec.x; pos.Y = rec.y; } From 81d5a35d58511f7e08f50252eb5eac15d589edf3 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 27 May 2025 12:59:36 -0600 Subject: [PATCH 07/73] remove redundant variable rewriting --- src/geo/paths.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/geo/paths.js b/src/geo/paths.js index da277d1d2..eef719de3 100644 --- a/src/geo/paths.js +++ b/src/geo/paths.js @@ -565,13 +565,7 @@ function arcToPath( radius, clockwise, start, end,arcdivs=Math.PI / 24) { pr2 = { x: (end.x + start.x) / 2, y: (end.y + start.y) / 2}; } else { // triangulate - pr2 = base.util.center2pr({ - x: start.x, - y: start.y - }, { - x: end.x, - y: end.y - }, radius, clockwise); + pr2 = base.util.center2pr(start, end, radius, clockwise); } center.x = pr2.x; center.y = pr2.y; From 08e93dc625de1dbe04ac6f0b5397b7f3ff6cf2bb Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 29 May 2025 21:39:35 -0600 Subject: [PATCH 08/73] fixed empty drainQ bug --- src/kiri-mode/cam/prepare.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 839385276..34f726ff1 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -259,8 +259,8 @@ function prepEach(widget, settings, print, firstPoint, update) { /** * @param {Point} point * @param {number} emit (0=move, !0=filament emit/laser on/cut mode) - * @param {number} [speed] speed - * @param {number} [tool] tool + * @param {number} [speed] feed/plunge rate in mm/min + * @param {number} [tool] tool number */ function layerPush(point, emit, speed, tool, type) { const dz = (point && lastPush && lastPush.point) ? point.z - lastPush.point.z : 0; @@ -808,7 +808,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } else { // if no last point, emit and set drainQ(); - camOut({x, y},1 ); + camOut(point,1 ); // TODO disabling out of plane z moves until a better mechanism // can be built that doesn't rely on computed zpos from layer heights... // when making z moves (like polishing) allow slowdown vs fast seek @@ -817,13 +817,9 @@ function prepEach(widget, settings, print, firstPoint, update) { } return point; } - - function drainQ() { - console.log("drainQ called",structuredClone(arcQ)); - if (!arcDist) { return; } From b073cb13aad898288d53b5d00daff24f723e3e59 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 29 May 2025 21:39:45 -0600 Subject: [PATCH 09/73] add more jsdocs --- src/kiri/print.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/kiri/print.js b/src/kiri/print.js index 32f8e2adf..985fe2775 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -73,8 +73,26 @@ class Print { } output.appendAll(input); } - - // fdm & laser + + /** + * Prints a polygon to a given output array, possibly with a given extrude factor, + * and starting from a given point. The last point is returned. + * used for FDM and laser + * @param {Polygon} poly - the polygon to print + * @param {Point} startPoint - the point to start printing from + * @param {Array} output - the array to print to + * @param {Object} [options] - optional parameters + * @param {boolean} [options.ccw] - set the polygon to be counter-clockwise + * @param {number} [options.extrude] - extrude factor for the polygon + * @param {number} [options.rate] - print speed in mm/s + * @param {number} [options.coast] - distance to coast at the end of the polygon + * @param {number} [options.simple] - if true, use the first point of the polygon + * @param {number} [options.open] - if true, don't close the polygon + * @param {number} [options.tool] - the tool to use + * @param {function} [options.onfirst] - called with the first point of the polygon + * @param {function} [options.onfirstout] - called with the first output point + * @returns {Point} the last point of the polygon + */ polyPrintPath(poly, startPoint, output, options = {}) { if (options.ccw) { poly.setCounterClockwise(); @@ -536,7 +554,7 @@ class Print { } class Output { - constructor(point, emit, speed, tool, type) { + constructor(point, emit, speed, tool, type, radius) { this.point = point; // point to emit this.emit = emit; // emit (feed for printers, power for lasers, cut for cam) this.speed = speed; From 47e312d4865399325d569910ce5012c3d647db59 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sat, 31 May 2025 18:40:14 -0600 Subject: [PATCH 10/73] get prepare displaying G2s --- src/kiri-mode/cam/export.js | 28 +++++++----- src/kiri-mode/cam/prepare.js | 87 +++++++++++++++++++++++------------- src/kiri-mode/fdm/prepare.js | 10 ++--- src/kiri/print.js | 31 +++++++++++-- src/kiri/render.js | 34 +++++++++++++- 5 files changed, 139 insertions(+), 51 deletions(-) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 1de3989fe..5f3a1d5ed 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -22,6 +22,8 @@ CAM.export = function(print, online) { const widget = print.widgets[0]; if (!widget) return; + console.log(print); + const { settings } = print; const { device, tools } = settings; @@ -164,6 +166,7 @@ CAM.export = function(print, online) { } } + function moveTo(out, opt = {}) { let laser = out.type === 'laser'; let newpos = out.point; @@ -251,18 +254,23 @@ CAM.export = function(print, online) { points--; return; } + console.log(out) + let gn; + if ( out.emit >=0 && out.emit <= 3) gn = `G${out.emit}`; + else throw new Error(`malformed emit type ${out.emit}`); let speed = out.speed, - gn = speed ? 'G1' : 'G0', - nl = (compact_output && lastGn === gn) ? [] : [gn], - dx = opt.dx || newpos.x - pos.x, - dy = opt.dy || newpos.y - pos.y, - dz = opt.dz || newpos.z - pos.z, - da = newpos.a != pos.a, - maxf = dz ? maxZd : maxXYd, - feed = Math.min(speed || maxf, maxf), - dist = Math.sqrt(dx * dx + dy * dy + dz * dz), - newFeed = feed && feed !== pos.f; + arc = out.emit == 2 || out.emit == 3, + nl = (compact_output && lastGn === gn) ? [] : [gn], + dx = opt.dx || newpos.x - pos.x, + dy = opt.dy || newpos.y - pos.y, + dz = opt.dz || newpos.z - pos.z, + da = newpos.a != pos.a, + maxf = dz ? maxZd : maxXYd, + feed = Math.min(speed || maxf, maxf), + dist = Math.sqrt(dx * dx + dy * dy + dz * dz), + newFeed = feed && feed !== pos.f; + // drop dup points (all deltas are 0) if (!(dx || dy || dz || da)) { diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 34f726ff1..d858da178 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -7,6 +7,7 @@ // dep: geo.polygons // dep: kiri.render // dep: kiri-mode.cam.driver +// dep: kiri.print // use: kiri-mode.cam.ops gapp.register("kiri-mode.cam.prepare", (root, exports) => { @@ -18,6 +19,7 @@ const { CAM } = driver; const POLY = polygons; + /** * DRIVER PRINT CONTRACT * @@ -43,18 +45,21 @@ CAM.prepare = async function(widall, settings, update) { const output = print.output.filter(level => Array.isArray(level)); if (render) // allows it to run from CLI - return render.path(output, (progress, layer) => { - update(0.75 + progress * 0.25, "render", layer); - }, { - thin: true, - print: 0, - move: 0x557799, - speed: false, - moves: true, - other: "moving", - action: "milling", - maxspeed: settings.process.camFastFeed || 6000 - }); + return render.path( + output, + (progress, layer) => { + update(0.75 + progress * 0.25, "render", layer); + }, { + thin: true, + print: 0, + move: 0x557799, + speed: false, + moves: true, + other: "moving", + action: "milling", + maxspeed: settings.process.camFastFeed || 6000 + } + ); }; function prepEach(widget, settings, print, firstPoint, update) { @@ -258,11 +263,12 @@ function prepEach(widget, settings, print, firstPoint, update) { /** * @param {Point} point - * @param {number} emit (0=move, !0=filament emit/laser on/cut mode) + * @param {number} emit (0=move, 1=/laser on/cut mode, 2/3= G2/G3 arc) * @param {number} [speed] feed/plunge rate in mm/min * @param {number} [tool] tool number */ - function layerPush(point, emit, speed, tool, type) { + function layerPush(point, emit, speed, tool, options) { + const { type, radius, arcPoints } = options ?? {}; const dz = (point && lastPush && lastPush.point) ? point.z - lastPush.point.z : 0; if (dz < 0 && speed > plungeRate) { speed = plungeRate; @@ -292,9 +298,9 @@ function prepEach(widget, settings, print, firstPoint, update) { if (lasering.flat) { point.z = (stock && stock.z ? stock.z : wztop) + lasering.flatz; } - print.addOutput(layerOut, point, power, speed, tool, 'laser'); + print.addOutput(layerOut, point, power, speed, tool, {type:'laser'}); } else { - print.addOutput(layerOut, point, emit, speed, tool, type); + print.addOutput(layerOut, point, emit, speed, tool, {type, radius, arcPoints}); } lastPush = { point, emit, speed, tool }; return point; @@ -321,12 +327,15 @@ function prepEach(widget, settings, print, firstPoint, update) { function camOut(point, cut,opts) { let { - radius = null, + radius = 0, clockwise = true, + arcPoints = [], moveLen = toolDiamMove, factor = 1, } = opts ?? {} + const isArc = radius > 0; + point = point.clone(); point.x += wmx; point.y += wmy; @@ -360,7 +369,7 @@ function prepEach(widget, settings, print, firstPoint, update) { cut ? 1 : 0, rate, tool, - "lerp" + {type:"lerp"}, ); } } @@ -378,7 +387,7 @@ function prepEach(widget, settings, print, firstPoint, update) { let deltaXY = lastPoint.distTo2D(point), deltaZ = point.z - lastPoint.z, absDeltaZ = Math.abs(deltaZ), - isMove = !cut; + isMove = !(cut || isArc); // drop points too close together if (!isLathe && deltaXY < 0.001 && point.z === lastPoint.z) { @@ -460,15 +469,32 @@ function prepEach(widget, settings, print, firstPoint, update) { } } - // todo synthesize move speed from feed / plunge accordingly - layerOut.mode = currentOp; - layerOut.spindle = spindle; - lastPoint = layerPush( - point, - cut ? 1 : 0, - rate, - tool - ); + + if(isArc) { + console.log("arc recieved"); + layerPush( + point, + clockwise ? 2 : 3, + rate, + tool, + { + radius, + arcPoints + } + ); + }else{ + // for g1 moves + + // TODO: synthesize move speed from feed / plunge accordingly + layerOut.mode = currentOp; + layerOut.spindle = spindle; + lastPoint = layerPush( + point, + cut ? 1 : 0, + rate, + tool + ); + } } /** @@ -768,7 +794,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { if ( desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault ) { // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; - console.log("point off the arc,",structuredClone(arcQ)); + // console.log("point off the arc,",structuredClone(arcQ)); if (arcQ.length === 4) { // not enough points for an arc, drop first point and recalc center camOut(arcQ.shift(),1); @@ -856,10 +882,9 @@ function prepEach(widget, settings, print, firstPoint, update) { // XYR form // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} `; - camOut(to,1,{radius:cc.r, clockwise}); + camOut(to,1,{radius:cc.r, clockwise, arcPoints:[...arcQ]}); // XYIJ form // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`; - // let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; // append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`); } else { diff --git a/src/kiri-mode/fdm/prepare.js b/src/kiri-mode/fdm/prepare.js index 7236542ea..42ffa6cd3 100644 --- a/src/kiri-mode/fdm/prepare.js +++ b/src/kiri-mode/fdm/prepare.js @@ -739,7 +739,7 @@ FDM.prepare = async function(widgets, settings, update) { // outside brim if (firstLayerBrim && !brimHalf) { print.addOutput(tmpout, newPoint(maxx + b, y, z), 0, outputSeekrate, tool); - print.addOutput(tmpout, newPoint(maxx + g, y, z), emit, firstLayerRate, tool).retract = true; + print.addOutput(tmpout, newPoint(maxx + g, y, z), emit, firstLayerRate, tool,{retract: true}) } // inside brim if (firstLayerBrimIn && pads.length > 1) { @@ -752,20 +752,20 @@ FDM.prepare = async function(widgets, settings, update) { if (x1 - x0 > bi * 2) { // over 2x brim so emit two segments print.addOutput(tmpout, newPoint(x1 - g, y, z), 0, outputSeekrate, tool); - print.addOutput(tmpout, newPoint(x1 - bi, y, z), emit, firstLayerRate, tool).retract = true; + print.addOutput(tmpout, newPoint(x1 - bi, y, z), emit, firstLayerRate, tool, {retract: true}); print.addOutput(tmpout, newPoint(x0 + bi, y, z), 0, outputSeekrate, tool); - print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool).retract = true; + print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool, {retract: true}); } else if (x1 - x0 > bi / 3) { // over 1/3rd brim length emit single segment print.addOutput(tmpout, newPoint(x1 - g, y, z), 0, outputSeekrate, tool); - print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool).retract = true; + print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool, {retract: true}); } } } // outside brim if (firstLayerBrim) { print.addOutput(tmpout, newPoint(minx - b, y, z), 0, outputSeekrate, tool); - print.addOutput(tmpout, newPoint(minx - g, y, z), emit, firstLayerRate, tool).retract = false; + print.addOutput(tmpout, newPoint(minx - g, y, z), emit, firstLayerRate, tool, {retract: true}); } // when there is a printing move between layers // inject non-printing move to after brim to prevent erroneous extrusion diff --git a/src/kiri/print.js b/src/kiri/print.js index 985fe2775..bc1237fd9 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -42,7 +42,8 @@ class Print { this.widget = widget; } - addOutput(array, point, emit, speed, tool, type) { + addOutput(array, point, emit, speed, tool, opts) { + const { type, retract, radius, arcPoints} = opts ?? {}; let { lastPoint, lastEmit, lastOut } = this; // drop duplicates (usually intruced by FDM bisections) if (lastPoint && point && type !== 'lerp') { @@ -55,10 +56,15 @@ class Print { // if (emit && emit < 1) console.log(emit); this.lastPoint = point; this.lastEmit = emit; - this.lastOut = lastOut = new Output(point, emit, speed, tool, type || this.nextType); + this.lastOut = lastOut = new Output(point, emit, speed, tool, { + type: type ?? this.nextType, + radius, + arcPoints, + }); if (tool !== undefined) { this.tools[tool] = true; } + lastOut.retract = retract; lastOut.widget = this.widget; array.push(lastOut); this.nextType = undefined; @@ -554,12 +560,31 @@ class Print { } class Output { - constructor(point, emit, speed, tool, type, radius) { + /** + * Construct a new output element. + * + * in cam, emit is the G code number (G0, G1, G2, G3) + * + * @param {Point} point point to emit, with x, y, and z properties + * @param {number} emit emit (feed for printers, power for lasers, cut for cam) + * @param {number} speed speed in mm/min + * @param {number} tool tool id + * @param {Object} options options object + * @param {string} [options.type] type of point + * @param {number} [options.radius] radius of arc + * @param {Point[]} [options.arcPoints] point based approximation of arc + */ + constructor(point, emit, speed, tool, options) { + + const { type, radius, arcPoints } = (options ?? {}); + //speed, tool, type, radius this.point = point; // point to emit this.emit = emit; // emit (feed for printers, power for lasers, cut for cam) this.speed = speed; this.tool = tool; this.type = type; + this.radius = radius; + this.arcPoints = arcPoints; // this.where = new Error().stack.split("\n"); } diff --git a/src/kiri/render.js b/src/kiri/render.js index 20ef141dc..20a1a65ae 100644 --- a/src/kiri/render.js +++ b/src/kiri/render.js @@ -44,6 +44,13 @@ function rate_to_color(rate, max) { } }; + /** + * Generate visual representation of a gcode program. + * @param {Output[][]} levels - Array of arrays containing `Output` objects. + * @param {function} update - Called with a completion percentage and the rendered layer. + * @param {{tools: Object, flat: Boolean, thin: Boolean, speed: Boolean, lineWidth: Number, toolMode: Boolean, z: Number, action: String, other: String}} [opts] - Optional parameters. + * @returns {Promise.kiri.Layers[]} - Array of layers. + */ async function path(levels, update, opts = {}) { levels = levels.filter(level => level.length); if (levels.length === 0) { @@ -183,7 +190,8 @@ async function path(levels, update, opts = {}) { return; } - if (lastOut) { + if (lastOut) { // if last outpoint is defined + if (arrowAll || lastOut.emit !== out.emit) { heads.push({p1: lastOutPoint, p2: outPoint}); } @@ -193,7 +201,29 @@ async function path(levels, update, opts = {}) { // Math.abs(op.y - lp.y), // Math.abs(op.z - lp.z)); // if (moved < 0.0001) return; - if (out.emit) { + if( is_cam() && (out.emit == 2 || out.emit == 3 )) { + // cam arc emit + console.log('cam arc render woohoo!',out); + + // checks if a new poly should be started + if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) { + current = newPolygon().setOpen(); + current.push(lastOutPoint); + current.color = color(out); + pushPrint(out.tool, current); + } + + out.arcPoints.forEach(p => { + current.push(p); + }) + + current.push(outPoint); + + } 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(); current.push(lastOutPoint); From 5f7b0a3380c73be4ea1af890b43bb6fcd9047b48 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 1 Jun 2025 15:10:57 -0600 Subject: [PATCH 11/73] add arc R axis --- src/kiri-mode/cam/export.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 5f3a1d5ed..d86af3f78 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -45,7 +45,7 @@ CAM.export = function(print, online) { cmdToolChange = device.gcodeChange || [ "M6 T{tool}" ], cmdSpindle = device.gcodeSpindle || [ "M3 S{speed}" ], cmdDwell = device.gcodeDwell || [ "G4 P{time}" ], - axis = { X: 'X', Y: 'Y', Z: 'Z', A: 'A', F: 'F'}, + axis = { X: 'X', Y: 'Y', Z: 'Z', A: 'A', F: 'F', R: 'R' }, dev = settings.device, spro = settings.process, maxZd = spro.camFastFeedZ, @@ -257,10 +257,9 @@ CAM.export = function(print, online) { console.log(out) let gn; if ( out.emit >=0 && out.emit <= 3) gn = `G${out.emit}`; - else throw new Error(`malformed emit type ${out.emit}`); - let speed = out.speed, arc = out.emit == 2 || out.emit == 3, + radius = out.radius, nl = (compact_output && lastGn === gn) ? [] : [gn], dx = opt.dx || newpos.x - pos.x, dy = opt.dy || newpos.y - pos.y, @@ -305,6 +304,9 @@ CAM.export = function(print, online) { pos.f = feed; nl.append(space).append(axis.F).append(add0(consts.feed = feed * factor, true)); } + if(arc){ + nl.append(space).append(axis.R).append(add0(radius * factor, true)); + } // temp hack to support RML1 dialect from a file extensions trigger if (isRML) { From da18a4d580704d5b2b56ef5e8b952c474804f399 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 1 Jun 2025 15:11:12 -0600 Subject: [PATCH 12/73] remove console log --- src/kiri/render.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/kiri/render.js b/src/kiri/render.js index 20a1a65ae..0e0c10902 100644 --- a/src/kiri/render.js +++ b/src/kiri/render.js @@ -201,10 +201,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 - console.log('cam arc render woohoo!',out); - + 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(); From b9d32be21485de0cd00c268113780c0bff15d683 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 1 Jun 2025 15:12:02 -0600 Subject: [PATCH 13/73] extensive debugging- IT WORKS NOW! --- src/kiri-mode/cam/prepare.js | 76 +++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index d858da178..c1ea1cebd 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -325,7 +325,7 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} opts.factor speed scale factor */ function camOut(point, cut,opts) { - + console.log({camOut: point, cut, opts}) let { radius = 0, clockwise = true, @@ -334,6 +334,7 @@ function prepEach(widget, settings, print, firstPoint, update) { factor = 1, } = opts ?? {} + // console.log({radius}) const isArc = radius > 0; point = point.clone(); @@ -471,17 +472,18 @@ function prepEach(widget, settings, print, firstPoint, update) { if(isArc) { - console.log("arc recieved"); - layerPush( - point, - clockwise ? 2 : 3, - rate, - tool, - { - radius, - arcPoints - } - ); + layerOut.mode = currentOp; + layerOut.spindle = spindle; + lastPoint = layerPush( + point, + clockwise ? 2 : 3, + rate, + tool, + { + radius, + arcPoints + } + ); }else{ // for g1 moves @@ -725,8 +727,8 @@ function prepEach(widget, settings, print, firstPoint, update) { */ function polyEmit(poly, index, count, fromPoint) { - const arcDist = 1, - arcRes = 2, //2 degs max + const arcDist = 0.1, // minimum dist for arc + arcRes = 4, //4 degs max arcMax = Infinity; // no max arc radius // console.log('polyEmit', poly, index, count); @@ -734,11 +736,10 @@ function prepEach(widget, settings, print, firstPoint, update) { let arcQ = []; function arcExport(point,lastp){ - + console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; - if (lastp) { - if (arcDist) { + if (dist >arcDist) { let rec = Object.assign(point,{dist}); arcQ.push(rec); let desp = false; // do arcQ[0] and rec have differing move speeds? @@ -757,7 +758,7 @@ function prepEach(widget, settings, print, firstPoint, update) { let cc = util.center2d(e1, e2, e3, 1); // find center let lr = util.center2d(e3, e4, e5, 1); // find local radius let dc = 0; - + let radFault = false; if (lr) { let angle = 2 * Math.asin(dist/(2*lr.r)); @@ -770,7 +771,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } else { radFault = true; } - + if (cc) { if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { console.log({cc, e1, e2, e3}); @@ -826,15 +827,13 @@ function prepEach(widget, settings, print, firstPoint, update) { } } } else { - // emitMM = emitPerMM * out.emit * dist; - emitMM = extrudeMM(dist, emitPerMM, out.emit); - camOut({x, y, e:emitMM}, true,); - emitted += emitMM; + // if dist to small, output as a cut + camOut(point, 1); } } else { - // if no last point, emit and set - drainQ(); - camOut(point,1 ); + // if first point, emit and set + + arcExport(point,point); // TODO disabling out of plane z moves until a better mechanism // can be built that doesn't rely on computed zpos from layer heights... // when making z moves (like polishing) allow slowdown vs fast seek @@ -853,7 +852,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // ondebug({arcQ}); let vec1 = new THREE.Vector2(arcQ[1].x - arcQ[0].x, arcQ[1].y - arcQ[0].y); let vec2 = new THREE.Vector2(arcQ.center[0].x - arcQ[0].x, arcQ.center[0].y - arcQ[0].y); - let clockwise = vec1.cross(vec2) < 0 ? 'G2' : 'G3'; + let clockwise = vec1.cross(vec2) < 0 let from = arcQ[0]; let to = arcQ.peek(); arcQ.xSum = arcQ.center.reduce( (t, v) => t + v.x , 0 ); @@ -861,28 +860,32 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.rSum = arcQ.center.reduce( (t, v) => t + v.r , 0 ); let cl = arcQ.center.length; let cc; - + let radius = arcQ.rSum / cl; + + // calculate angle between first and last point, relative to arc center let angle = util.thetaDiff( Math.atan2((from.y - arcQ.ySum / cl), (from.x - arcQ.xSum / cl)), Math.atan2((to.y - arcQ.ySum / cl), (to.x - arcQ.xSum / cl)), clockwise ); - + + // if angle is less than 135 degrees (3PI/4) if (Math.abs(angle) <= 3 * Math.PI / 4) { - cc = util.center2pr(from, to, arcQ.rSum / cl, !clockwise); + cc = util.center2pr(from, to, radius, clockwise); + cc.r = radius; } if (!cc) { - cc = {x:arcQ.xSum/cl, y:arcQ.ySum/cl, z:arcQ[0].z, r:arcQ.rSum/cl}; + cc = {x:arcQ.xSum/cl, y:arcQ.ySum/cl, z:arcQ[0].z,}; } - // first arc point camOut(from,true); // rest of arc to final point // XYR form // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} `; - camOut(to,1,{radius:cc.r, clockwise, arcPoints:[...arcQ]}); + + camOut(to,1,{radius, clockwise, arcPoints:[...arcQ]}); // XYIJ form // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`; // let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; @@ -893,6 +896,7 @@ function prepEach(widget, settings, print, firstPoint, update) { camOut(rec,1); } } + last = arcQ.peek(); arcQ.length = 0; arcQ.center = undefined; } @@ -915,9 +919,9 @@ function prepEach(widget, settings, print, firstPoint, update) { }, poly.isClosed(), last); // console.log("at end of arcExport",structuredClone(arcQ)); - drainQ(); - // console.log("at end of arcExport",structuredClone(arcQ)); - + while (arcQ.length){ + camOut(arcQ.shift(),1); + } newLayer(); return last; From 07002f8c707d53880a497837d858f2b08f8db12b Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 1 Jun 2025 15:58:26 -0600 Subject: [PATCH 14/73] remove console logs --- src/kiri-mode/cam/prepare.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index c1ea1cebd..069f61f63 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -325,7 +325,7 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} opts.factor speed scale factor */ function camOut(point, cut,opts) { - console.log({camOut: point, cut, opts}) + // console.log({camOut: point, cut, opts}) let { radius = 0, clockwise = true, @@ -736,7 +736,7 @@ function prepEach(widget, settings, print, firstPoint, update) { let arcQ = []; function arcExport(point,lastp){ - console.log("start",point,lastp) + // console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; if (lastp) { if (dist >arcDist) { From 425f8eb6d464c58c34cbee948ea52319727216a0 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 1 Jun 2025 15:58:40 -0600 Subject: [PATCH 15/73] destructure arcs for animate --- src/kiri-mode/cam/animate.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/kiri-mode/cam/animate.js b/src/kiri-mode/cam/animate.js index 113cc43ae..0c7cda85e 100644 --- a/src/kiri-mode/cam/animate.js +++ b/src/kiri-mode/cam/animate.js @@ -350,6 +350,21 @@ kiri.load(() => { stock = settings.stock; rez = 1/Math.sqrt(density/(stock.x * stock.y)); + + //destructure arcs into path points + path = path.map(o=> + o.arcPoints + ? o.arcPoints.map(point=> + ({ + ...o, + point + }) + ) + : o + ) + .flat(); + + const step = rez; const stepsX = Math.floor(stock.x / step); const stepsY = Math.floor(stock.y / step); From b74c5e781ba146ded1d07deedf67b44f877c1e90 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 1 Jun 2025 18:57:31 -0600 Subject: [PATCH 16/73] remove console log --- src/kiri-mode/cam/export.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index d86af3f78..792520768 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -254,7 +254,6 @@ CAM.export = function(print, online) { points--; return; } - console.log(out) let gn; if ( out.emit >=0 && out.emit <= 3) gn = `G${out.emit}`; let speed = out.speed, From 4270c81fdfbf814c77fa4ce867772ba89a5c55a8 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Mon, 2 Jun 2025 20:35:30 -0600 Subject: [PATCH 17/73] add centers --- src/kiri-mode/cam/export.js | 7 +-- src/kiri-mode/cam/ops.js | 6 +-- src/kiri-mode/cam/prepare.js | 97 ++++++++++++++++-------------------- src/kiri/print.js | 12 ++--- 4 files changed, 56 insertions(+), 66 deletions(-) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 792520768..96601e0ad 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -45,7 +45,7 @@ CAM.export = function(print, online) { cmdToolChange = device.gcodeChange || [ "M6 T{tool}" ], cmdSpindle = device.gcodeSpindle || [ "M3 S{speed}" ], cmdDwell = device.gcodeDwell || [ "G4 P{time}" ], - axis = { X: 'X', Y: 'Y', Z: 'Z', A: 'A', F: 'F', R: 'R' }, + axis = { X: 'X', Y: 'Y', Z: 'Z', A: 'A', F: 'F', R: 'R', I: 'I', J: 'J' }, dev = settings.device, spro = settings.process, maxZd = spro.camFastFeedZ, @@ -258,7 +258,7 @@ CAM.export = function(print, online) { if ( out.emit >=0 && out.emit <= 3) gn = `G${out.emit}`; let speed = out.speed, arc = out.emit == 2 || out.emit == 3, - radius = out.radius, + center = out.center, nl = (compact_output && lastGn === gn) ? [] : [gn], dx = opt.dx || newpos.x - pos.x, dy = opt.dy || newpos.y - pos.y, @@ -304,7 +304,8 @@ CAM.export = function(print, online) { nl.append(space).append(axis.F).append(add0(consts.feed = feed * factor, true)); } if(arc){ - nl.append(space).append(axis.R).append(add0(radius * factor, true)); + nl.append(space).append(axis.I).append(add0(center.x * factor, true)) + .append(space).append(axis.J).append(add0(center.y * factor, true)); } // temp hack to support RML1 dialect from a file extensions trigger diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index 7df5ac1ba..fe2ef3963 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -737,11 +737,11 @@ class OpOutline extends CamOp { Object.entries(orderSplit) //split the polys by order .sort((a,b) => -(a[0] - b[0] )) //sort by order (highest first) .forEach(([order, orderPolys]) => { // emit based on closest for each order + let polyLast; // console.log({order, orderPolys}); printPoint = poly2polyEmit(orderPolys, printPoint, function(poly, index, count) { - poly.forEachPoint(function(point, pidx, points, offset) { - camOut(point.clone(), offset !== 0); - }, poly.isClosed(), index); + + polyLast = polyEmit(poly, index, count, polyLast); }, { swapdir: false }); }) newLayer(); diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 069f61f63..247a50a11 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -268,7 +268,7 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} [tool] tool number */ function layerPush(point, emit, speed, tool, options) { - const { type, radius, arcPoints } = options ?? {}; + const { type, center, arcPoints } = options ?? {}; const dz = (point && lastPush && lastPush.point) ? point.z - lastPush.point.z : 0; if (dz < 0 && speed > plungeRate) { speed = plungeRate; @@ -300,7 +300,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } print.addOutput(layerOut, point, power, speed, tool, {type:'laser'}); } else { - print.addOutput(layerOut, point, emit, speed, tool, {type, radius, arcPoints}); + print.addOutput(layerOut, point, emit, speed, tool, {type, center, arcPoints}); } lastPush = { point, emit, speed, tool }; return point; @@ -327,7 +327,7 @@ function prepEach(widget, settings, print, firstPoint, update) { function camOut(point, cut,opts) { // console.log({camOut: point, cut, opts}) let { - radius = 0, + center = {}, clockwise = true, arcPoints = [], moveLen = toolDiamMove, @@ -335,7 +335,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } = opts ?? {} // console.log({radius}) - const isArc = radius > 0; + const isArc = arcPoints.length > 0; point = point.clone(); point.x += wmx; @@ -472,18 +472,19 @@ function prepEach(widget, settings, print, firstPoint, update) { if(isArc) { - layerOut.mode = currentOp; - layerOut.spindle = spindle; - lastPoint = layerPush( - point, - clockwise ? 2 : 3, - rate, - tool, - { - radius, - arcPoints - } - ); + layerOut.mode = currentOp; + layerOut.spindle = spindle; + lastPoint = layerPush( + point, + clockwise ? 2 : 3, + rate, + tool, + { + center, + arcPoints + } + ); + console.log({center, arcPoints}); }else{ // for g1 moves @@ -726,11 +727,13 @@ function prepEach(widget, settings, print, firstPoint, update) { * @returns {Point} - the last point of the polygon */ function polyEmit(poly, index, count, fromPoint) { - - const arcDist = 0.1, // minimum dist for arc - arcRes = 4, //4 degs max + console.log('polyEmit', poly ); + const arcDist = 0.001, // minimum dist for arc + arcRes = 8, //8 degs max arcMax = Infinity; // no max arc radius + fromPoint = fromPoint || printPoint; + // console.log('polyEmit', poly, index, count); let arcQ = []; @@ -744,7 +747,6 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.push(rec); let desp = false; // do arcQ[0] and rec have differing move speeds? if (arcQ.length > 1) { - let el = arcQ.length; desp = arcQ[0].speedMMM !== rec.speedMMM; } // ondebug({arcQ}); @@ -790,12 +792,11 @@ function prepEach(widget, settings, print, firstPoint, update) { let dy = cc.y - arcQ.ySum / arcQ.center.length; dc = Math.sqrt(dx * dx + dy * dy); } - // if new point is off the arc // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { if ( desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault ) { // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; - // console.log("point off the arc,",structuredClone(arcQ)); + console.log("point off the arc,",structuredClone(arcQ),); if (arcQ.length === 4) { // not enough points for an arc, drop first point and recalc center camOut(arcQ.shift(),1); @@ -828,12 +829,13 @@ function prepEach(widget, settings, print, firstPoint, update) { } } else { // if dist to small, output as a cut + console.log('point too small', point); camOut(point, 1); } } else { // if first point, emit and set - arcExport(point,point); + camOut(point, 1); // TODO disabling out of plane z moves until a better mechanism // can be built that doesn't rely on computed zpos from layer heights... // when making z moves (like polishing) allow slowdown vs fast seek @@ -859,37 +861,16 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum = arcQ.center.reduce( (t, v) => t + v.y , 0 ); arcQ.rSum = arcQ.center.reduce( (t, v) => t + v.r , 0 ); let cl = arcQ.center.length; - let cc; let radius = arcQ.rSum / cl; + let center = newPoint( + arcQ.xSum / cl, + arcQ.ySum / cl, + ) - // calculate angle between first and last point, relative to arc center - let angle = util.thetaDiff( - Math.atan2((from.y - arcQ.ySum / cl), (from.x - arcQ.xSum / cl)), - Math.atan2((to.y - arcQ.ySum / cl), (to.x - arcQ.xSum / cl)), - clockwise - ); - - // if angle is less than 135 degrees (3PI/4) - if (Math.abs(angle) <= 3 * Math.PI / 4) { - cc = util.center2pr(from, to, radius, clockwise); - cc.r = radius; - } - - if (!cc) { - cc = {x:arcQ.xSum/cl, y:arcQ.ySum/cl, z:arcQ[0].z,}; - } // first arc point - camOut(from,true); + camOut(from,1); // rest of arc to final point - - // XYR form - // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} `; - - camOut(to,1,{radius, clockwise, arcPoints:[...arcQ]}); - // XYIJ form - // let pre = `${clockwise? 'G2' : 'G3'} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`; - // let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; - // append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`); + camOut(to,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); } else { //if q too short, emit as lines for (let rec of arcQ) { @@ -914,13 +895,21 @@ function prepEach(widget, settings, print, firstPoint, update) { } poly.forEachPoint(function(point, pidx, points, offset) { - last =arcExport(point,last) - // camOut(point.clone(), offset !== 0, {factor:scale}); + + if(offset ==0 ) last = camOut(point.clone(), 0, {factor:scale}); + else last =arcExport(point,last ?? point); }, poly.isClosed(), last); // console.log("at end of arcExport",structuredClone(arcQ)); - while (arcQ.length){ - camOut(arcQ.shift(),1); + if(arcQ.length <= 3){ + //if few points left, emit as lines + while (arcQ.length){ + camOut(arcQ.shift(),1); + } + }else{ + console.log("force draining",structuredClone(arcQ)); + drainQ(); + // camOut(arcQ.shift(),1); } newLayer(); diff --git a/src/kiri/print.js b/src/kiri/print.js index bc1237fd9..720afe092 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -43,7 +43,7 @@ class Print { } addOutput(array, point, emit, speed, tool, opts) { - const { type, retract, radius, arcPoints} = opts ?? {}; + const { type, retract, center, arcPoints} = opts ?? {}; let { lastPoint, lastEmit, lastOut } = this; // drop duplicates (usually intruced by FDM bisections) if (lastPoint && point && type !== 'lerp') { @@ -58,7 +58,7 @@ class Print { this.lastEmit = emit; this.lastOut = lastOut = new Output(point, emit, speed, tool, { type: type ?? this.nextType, - radius, + center, arcPoints, }); if (tool !== undefined) { @@ -571,19 +571,19 @@ class Output { * @param {number} tool tool id * @param {Object} options options object * @param {string} [options.type] type of point - * @param {number} [options.radius] radius of arc + * @param {Point} [options.center] the center of the arc * @param {Point[]} [options.arcPoints] point based approximation of arc */ constructor(point, emit, speed, tool, options) { - const { type, radius, arcPoints } = (options ?? {}); - //speed, tool, type, radius + const { type, center, arcPoints } = (options ?? {}); + //speed, tool, type, center, arcPoints this.point = point; // point to emit this.emit = emit; // emit (feed for printers, power for lasers, cut for cam) this.speed = speed; this.tool = tool; this.type = type; - this.radius = radius; + this.center = center; this.arcPoints = arcPoints; // this.where = new Error().stack.split("\n"); } From e230358ede1a27681d63f08c6d73f457068e5642 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 3 Jun 2025 09:23:27 -0600 Subject: [PATCH 18/73] add jsdoc --- src/geo/polygon.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index ca606429a..4e1982bed 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1190,7 +1190,13 @@ class Polygon { - forEachPoint(fn, close, start) { + /** + * calls function for each point in this polygon + * @param {Function} fn to call for each point + * @param {boolean} [close=true] whether to close the loop (last point to first) + * @param {number} [start=0] starting index + */ + forEachPoint(fn, close, start) { let index = start || 0, points = this.points, length = points.length, From 5b6b5fc674fc1e70e1d3c441a5af5b185c933452 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 3 Jun 2025 09:29:48 -0600 Subject: [PATCH 19/73] finish circle prepare --- src/kiri-mode/cam/prepare.js | 48 +++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 247a50a11..b3d328790 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -325,7 +325,7 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} opts.factor speed scale factor */ function camOut(point, cut,opts) { - // console.log({camOut: point, cut, opts}) + console.trace({camOut: point, cut, opts}) let { center = {}, clockwise = true, @@ -391,13 +391,13 @@ function prepEach(widget, settings, print, firstPoint, update) { isMove = !(cut || isArc); // drop points too close together - if (!isLathe && deltaXY < 0.001 && point.z === lastPoint.z) { + if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z) { // console.trace(["drop dup",lastPoint,point]); return; } // convert short planar moves to cuts in some cases - if (!isRough && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) { + if (!isRough&& !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering ) { let iscontour = tolerance > 0; let isflat = absDeltaZ < 0.001; // restrict this to contouring @@ -742,7 +742,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; if (lastp) { - if (dist >arcDist) { + if (dist >arcDist && lastp) { let rec = Object.assign(point,{dist}); arcQ.push(rec); let desp = false; // do arcQ[0] and rec have differing move speeds? @@ -829,8 +829,8 @@ function prepEach(widget, settings, print, firstPoint, update) { } } else { // if dist to small, output as a cut - console.log('point too small', point); - camOut(point, 1); + console.log('point too small', point,lastp,dist); + // camOut(point, 1); } } else { // if first point, emit and set @@ -867,10 +867,20 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum / cl, ) - // first arc point - camOut(from,1); - // rest of arc to final point - camOut(to,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + if(arcQ.length == poly.points.length){ + //if is a circle + let arcStart = arcQ[0] + camOut(arcStart,1); + camOut(arcStart,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + }else{ + //if a non-circle arc + + // first arc point + camOut(from,1); + // rest of arc to final point + camOut(to,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + } + } else { //if q too short, emit as lines for (let rec of arcQ) { @@ -881,24 +891,27 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.length = 0; arcQ.center = undefined; } - - let last = null; + let {point:lastPoint, index:startIdx} = poly.findClosestPointTo(fromPoint); + let last = index; // scale speed of first cutting poly since it engages the full bit let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; if (easeDown && poly.isClosed()) { //if doing ease-down last = generateEaseDown((point, )=>{ //generate ease-down points camOut(point.clone(), 1, {factor:scale}); // and pass them to camOut - }, poly, fromPoint, easeAngle); + }, poly, lastPoint, easeAngle); } - poly.forEachPoint(function(point, pidx, points, offset) { - if(offset ==0 ) last = camOut(point.clone(), 0, {factor:scale}); - else last =arcExport(point,last ?? point); - }, poly.isClosed(), last); + poly.forEachPoint( ( point, pidx, points, offset) => { + // if(offset == 0) console.log("forEachPoint",point,pidx,points) + if(offset ==0 ) arcExport(point,lastPoint); + else arcExport(point, lastPoint); + last = pidx; + lastPoint = point; + }, poly.isClosed(), startIdx); // console.log("at end of arcExport",structuredClone(arcQ)); if(arcQ.length <= 3){ @@ -909,6 +922,7 @@ function prepEach(widget, settings, print, firstPoint, update) { }else{ console.log("force draining",structuredClone(arcQ)); drainQ(); + console.log("after draining",structuredClone(arcQ)); // camOut(arcQ.shift(),1); } From 6f96e6c07a60ec73595700d848737a9fe83817cf Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 3 Jun 2025 09:39:52 -0600 Subject: [PATCH 20/73] allow dup points for arcs --- src/kiri-mode/cam/export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 96601e0ad..2a9e4b83c 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -271,7 +271,7 @@ CAM.export = function(print, online) { // drop dup points (all deltas are 0) - if (!(dx || dy || dz || da)) { + if (!arc && !(dx || dy || dz || da)) { return; } From 28765d8db023fb21e983366a075ee03dc5399bed Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 3 Jun 2025 11:45:59 -0600 Subject: [PATCH 21/73] add ease-down support --- src/kiri-mode/cam/prepare.js | 104 +++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index b3d328790..c2b83301a 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -325,7 +325,6 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} opts.factor speed scale factor */ function camOut(point, cut,opts) { - console.trace({camOut: point, cut, opts}) let { center = {}, clockwise = true, @@ -463,10 +462,11 @@ function prepEach(widget, settings, print, firstPoint, update) { modifier = threshold / absDeltaZ; if (synthPlunge && threshold && modifier && deltaXY > tolerance) { // use modifier to speed up long XY move plunge rates + console.log('modifier', modifier); rate = Math.round(plungeRate + ((feedRate - plungeRate) * modifier)); cut = 1; } else { - rate = Math.min(feedRate, plungeRate); + rate = 1 / Math.hypot(deltaXY / feedRate , absDeltaZ / plungeRate ); } } @@ -484,7 +484,6 @@ function prepEach(widget, settings, print, firstPoint, update) { arcPoints } ); - console.log({center, arcPoints}); }else{ // for g1 moves @@ -727,17 +726,60 @@ function prepEach(widget, settings, print, firstPoint, update) { * @returns {Point} - the last point of the polygon */ function polyEmit(poly, index, count, fromPoint) { - console.log('polyEmit', poly ); const arcDist = 0.001, // minimum dist for arc arcRes = 8, //8 degs max arcMax = Infinity; // no max arc radius fromPoint = fromPoint || printPoint; - // console.log('polyEmit', poly, index, count); + console.log('polyEmit', poly, index, count); let arcQ = []; + let closest = poly.findClosestPointTo(fromPoint); + let lastPoint = closest.point; + let startIndex = closest.index; + + // scale speed of first cutting poly since it engages the full bit + let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; + + + if (easeDown && poly.isClosed()) { //if doing ease-down + + let last = generateEaseDown((point,offset )=>{ //generate ease-down points + if(offset == 0) camOut(point.clone(), 0, {factor:scale}); + camOut(point.clone(), 1, {factor:scale}); // and pass them to camOut + }, poly, fromPoint, easeAngle); + lastPoint = poly.points[last]; + startIndex = last; + } + + + poly.forEachPoint( ( point, pidx, points, offset) => { + // if(offset == 0) console.log("forEachPoint",point,pidx,points) + if(offset == 0){ + // if first point, move to and call export function + console.log("offset 0") + camOut(point.clone(), 0, {factor:scale}); + quePush(point); + } + else arcExport(point, lastPoint); + lastPoint = point; + }, poly.isClosed(), startIndex); + + // console.log("at end of arcExport",structuredClone(arcQ)); + if(arcQ.length > 3){ + //if few points left, emit as lines + drainQ(); + } + while (arcQ.length){ + camOut(arcQ.shift(),1); + } + + function quePush(point){ + arcQ.push(point); + } + function arcExport(point,lastp){ // console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; @@ -772,6 +814,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // } } else { radFault = true; + console.log("too much angle") } if (cc) { @@ -796,7 +839,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { if ( desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault ) { // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; - console.log("point off the arc,",structuredClone(arcQ),); + // console.log("point off the arc,",structuredClone(arcQ),); if (arcQ.length === 4) { // not enough points for an arc, drop first point and recalc center camOut(arcQ.shift(),1); @@ -829,8 +872,8 @@ function prepEach(widget, settings, print, firstPoint, update) { } } else { // if dist to small, output as a cut - console.log('point too small', point,lastp,dist); - // camOut(point, 1); + console.trace('point too small', point,lastp,dist); + camOut(point, 1); } } else { // if first point, emit and set @@ -861,7 +904,6 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum = arcQ.center.reduce( (t, v) => t + v.y , 0 ); arcQ.rSum = arcQ.center.reduce( (t, v) => t + v.r , 0 ); let cl = arcQ.center.length; - let radius = arcQ.rSum / cl; let center = newPoint( arcQ.xSum / cl, arcQ.ySum / cl, @@ -869,9 +911,9 @@ function prepEach(widget, settings, print, firstPoint, update) { if(arcQ.length == poly.points.length){ //if is a circle - let arcStart = arcQ[0] - camOut(arcStart,1); - camOut(arcStart,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + camOut(from,1); + camOut(from,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + lastPoint = from.clone(); }else{ //if a non-circle arc @@ -879,6 +921,7 @@ function prepEach(widget, settings, print, firstPoint, update) { camOut(from,1); // rest of arc to final point camOut(to,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + lastPoint = to.clone(); } } else { @@ -886,48 +929,17 @@ function prepEach(widget, settings, print, firstPoint, update) { for (let rec of arcQ) { camOut(rec,1); } + lastPoint = arcQ.peek().clone(); } - last = arcQ.peek(); arcQ.length = 0; arcQ.center = undefined; } - let {point:lastPoint, index:startIdx} = poly.findClosestPointTo(fromPoint); - let last = index; - // scale speed of first cutting poly since it engages the full bit - let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; - - if (easeDown && poly.isClosed()) { //if doing ease-down - last = generateEaseDown((point, )=>{ //generate ease-down points - camOut(point.clone(), 1, {factor:scale}); // and pass them to camOut - }, poly, lastPoint, easeAngle); - } - - - poly.forEachPoint( ( point, pidx, points, offset) => { - // if(offset == 0) console.log("forEachPoint",point,pidx,points) - if(offset ==0 ) arcExport(point,lastPoint); - else arcExport(point, lastPoint); - last = pidx; - lastPoint = point; - }, poly.isClosed(), startIdx); - - // console.log("at end of arcExport",structuredClone(arcQ)); - if(arcQ.length <= 3){ - //if few points left, emit as lines - while (arcQ.length){ - camOut(arcQ.shift(),1); - } - }else{ - console.log("force draining",structuredClone(arcQ)); - drainQ(); - console.log("after draining",structuredClone(arcQ)); - // camOut(arcQ.shift(),1); - } + newLayer(); - return last; + return lastPoint; } function depthRoughPath(start, depth, levels, tops, emitter, fit, ease) { From 389b088ce73d10533aa373c00c8aa48b69f88fcc Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 15:34:29 -0600 Subject: [PATCH 22/73] remove console logs and increace arc tolerance --- src/kiri-mode/cam/prepare.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index c2b83301a..ea2b6d8ba 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -726,7 +726,7 @@ function prepEach(widget, settings, print, firstPoint, update) { * @returns {Point} - the last point of the polygon */ function polyEmit(poly, index, count, fromPoint) { - const arcDist = 0.001, // minimum dist for arc + const arcDist = 0.05, // allowable distance between circle centers arcRes = 8, //8 degs max arcMax = Infinity; // no max arc radius @@ -759,7 +759,6 @@ function prepEach(widget, settings, print, firstPoint, update) { // if(offset == 0) console.log("forEachPoint",point,pidx,points) if(offset == 0){ // if first point, move to and call export function - console.log("offset 0") camOut(point.clone(), 0, {factor:scale}); quePush(point); } @@ -833,7 +832,7 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); let dx = cc.x - arcQ.xSum / arcQ.center.length; let dy = cc.y - arcQ.ySum / arcQ.center.length; - dc = Math.sqrt(dx * dx + dy * dy); + dc = Math.hypot(dx, dy); // delta center distance } // if new point is off the arc // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { From bc8e6decc842163f2ea6f02eb3f8d9b230c078e2 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 15:55:30 -0600 Subject: [PATCH 23/73] add arcTolerance configuration option --- src/cli/kiri-cam-process.json | 1 + src/kiri-mode/cam/prepare.js | 15 ++++++++------- src/kiri/conf.js | 1 + src/kiri/init.js | 1 + 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/cli/kiri-cam-process.json b/src/cli/kiri-cam-process.json index e9be91ab4..b1457afa0 100644 --- a/src/cli/kiri-cam-process.json +++ b/src/cli/kiri-cam-process.json @@ -97,6 +97,7 @@ "top": false }], "camTrueShadow": false, + "camArcTolerance": 0.01, "camDrillMark": true, "camPocketSpindle": 1000, "camPocketTool": 1665454124874, diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index ea2b6d8ba..a464282c1 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -96,6 +96,7 @@ function prepEach(widget, settings, print, firstPoint, update) { easeAngle = process.camEaseAngle, depthFirst = process.camDepthFirst, engageFactor = process.camFullEngage, + arcTolerance = process.camArcTolerance, tolerance = 0, drillDown = 0, drillLift = 0, @@ -726,8 +727,8 @@ function prepEach(widget, settings, print, firstPoint, update) { * @returns {Point} - the last point of the polygon */ function polyEmit(poly, index, count, fromPoint) { - const arcDist = 0.05, // allowable distance between circle centers - arcRes = 8, //8 degs max + //arcTolerance is the allowable distance between circle centers + let arcRes = 8, //8 degs max arcMax = Infinity; // no max arc radius fromPoint = fromPoint || printPoint; @@ -783,7 +784,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; if (lastp) { - if (dist >arcDist && lastp) { + if (dist >arcTolerance && lastp) { let rec = Object.assign(point,{dist}); arcQ.push(rec); let desp = false; // do arcQ[0] and rec have differing move speeds? @@ -835,9 +836,9 @@ function prepEach(widget, settings, print, firstPoint, update) { dc = Math.hypot(dx, dy); // delta center distance } // if new point is off the arc - // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { - if ( desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault ) { - // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault]; + // if (deem || depm || desp || dc > arcTolerance || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { + if ( desp || dc * arcQ.center.length / arcQ.rSum > arcTolerance || dist > cc.r || cc.r > arcMax || radFault ) { + // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcTolerance, dist > cc.r, cc.r > arcMax, radFault]; // console.log("point off the arc,",structuredClone(arcQ),); if (arcQ.length === 4) { // not enough points for an arc, drop first point and recalc center @@ -889,7 +890,7 @@ function prepEach(widget, settings, print, firstPoint, update) { function drainQ() { - if (!arcDist) { + if (!arcTolerance) { return; } if (arcQ.length > 4) { diff --git a/src/kiri/conf.js b/src/kiri/conf.js index 1830281f1..3fcb2bb6b 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -578,6 +578,7 @@ const conf = exports({ outputInvertY: false, camExpertFast: false, camTrueShadow: false, + camArcTolerance: 0.05, camForceZMax: false, camFirstZMax: false, camToolInit: true, diff --git a/src/kiri/init.js b/src/kiri/init.js index 5efc145e9..2742aae16 100644 --- a/src/kiri/init.js +++ b/src/kiri/init.js @@ -1281,6 +1281,7 @@ gapp.register("kiri.init", (root, exports) => { _____: newGroup(LANG.op_xprt_s, $('cam-expert'), { group:"cam_expert", modes:CAM, marker: false, driven, separator }), camExpertFast: newBoolean(LANG.cx_fast_s, onBooleanClick, {title:LANG.cx_fast_l, show: () => !ui.camTrueShadow.checked }), camTrueShadow: newBoolean(LANG.cx_true_s, onBooleanClick, {title:LANG.cx_true_l, show: () => !ui.camExpertFast.checked }), + camArcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,100)}), /** LASER/DRAG/WJET/WEDM cut tool Settings */ From d0a1c73bf0b5a60342658e367edc5881ba1d659d Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 16:05:45 -0600 Subject: [PATCH 24/73] add lineTolerance config --- src/kiri-mode/cam/prepare.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index a464282c1..f69bf6b06 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -729,7 +729,8 @@ function prepEach(widget, settings, print, firstPoint, update) { function polyEmit(poly, index, count, fromPoint) { //arcTolerance is the allowable distance between circle centers let arcRes = 8, //8 degs max - arcMax = Infinity; // no max arc radius + arcMax = Infinity, // no max arc radius + lineTolerance = 0.01; // do not consider points under 0.05mm for lines fromPoint = fromPoint || printPoint; @@ -784,7 +785,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; if (lastp) { - if (dist >arcTolerance && lastp) { + if (dist >lineTolerance && lastp) { let rec = Object.assign(point,{dist}); arcQ.push(rec); let desp = false; // do arcQ[0] and rec have differing move speeds? From b301a25155e5fe9921561a49d1e9b7a09915197d Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 17:11:48 -0600 Subject: [PATCH 25/73] add arc support to Rough --- src/kiri-mode/cam/ops.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index fe2ef3963..eb22e26f2 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -431,7 +431,7 @@ class OpRough extends CamOp { prepare(ops, progress) { let { op, state, sliceOut, camFaces } = this; - let { setTool, setSpindle, setPrintPoint, sliceOutput } = ops; + let { setTool, setSpindle, setPrintPoint, sliceOutput, polyEmit } = ops; let { camOut, newLayer, printPoint } = ops; let { settings } = state; let { process } = settings; @@ -457,12 +457,9 @@ class OpRough extends CamOp { } // set winding specified in output POLY.setWinding(level, cutdir, false); - printPoint = poly2polyEmit(level, printPoint, function(poly, index, count) { - const factor = count === 1 ? 0.5 : 1; - poly.forEachPoint(function(point, pidx, points, offset) { - camOut(point.clone(), offset !== 0, undefined, factor); - }, true, index); - }); + poly2polyEmit(level, printPoint, (poly, index, count) => { + printPoint = polyEmit(poly, index, count, printPoint); + }); newLayer(); } From 1c1a190b542bbaa5f4ec9544cea2b6bd47c59097 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 17:12:18 -0600 Subject: [PATCH 26/73] switch sliceOutput to using PolyEmit --- src/kiri-mode/cam/prepare.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index f69bf6b06..d2f843f62 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -531,11 +531,8 @@ function prepEach(widget, settings, print, firstPoint, update) { if (depthFirst) { depthData.push(polys); } else { - printPoint = poly2polyEmit(polys, printPoint, function(poly, index, count) { - poly.forEachPoint(function(point, pidx, points, offset) { - // scale speed of first cutting poly since it engages the full bit - camOut(point.clone(), offset !== 0,{ factor: count === 1 ? engageFactor : 1}); - }, poly.isClosed(), index); + poly2polyEmit(polys, printPoint, (poly, index, count)=> { + printPoint =polyEmit(poly, index, count,printPoint); }, { swapdir: false }); newLayer(); } @@ -724,6 +721,7 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} index - the index of the polygon in its containing array * @param {number} count - the total number of polygons in the array * @param {Point} fromPoint - the point to rapid move from + * @param {Object} camOutOpts - optional parameters to pass to camOut * @returns {Point} - the last point of the polygon */ function polyEmit(poly, index, count, fromPoint) { @@ -749,7 +747,7 @@ function prepEach(widget, settings, print, firstPoint, update) { if (easeDown && poly.isClosed()) { //if doing ease-down let last = generateEaseDown((point,offset )=>{ //generate ease-down points - if(offset == 0) camOut(point.clone(), 0, {factor:scale}); + if(offset == 0) camOut(point.clone(), 0, {factor:engageFactor}); camOut(point.clone(), 1, {factor:scale}); // and pass them to camOut }, poly, fromPoint, easeAngle); lastPoint = poly.points[last]; @@ -761,7 +759,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // if(offset == 0) console.log("forEachPoint",point,pidx,points) if(offset == 0){ // if first point, move to and call export function - camOut(point.clone(), 0, {factor:scale}); + camOut(point.clone(), 0, {factor:engageFactor}); quePush(point); } else arcExport(point, lastPoint); From d3052cf4fb93262bd425615d99a1af3b568fdb3d Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 17:37:12 -0600 Subject: [PATCH 27/73] remove console log --- src/kiri-mode/cam/prepare.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index d2f843f62..cc437261f 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -731,9 +731,6 @@ function prepEach(widget, settings, print, firstPoint, update) { lineTolerance = 0.01; // do not consider points under 0.05mm for lines fromPoint = fromPoint || printPoint; - - console.log('polyEmit', poly, index, count); - let arcQ = []; let closest = poly.findClosestPointTo(fromPoint); From 9b4e048adec6a039eb2022e74213c2c5481c160c Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 4 Jun 2025 18:12:54 -0600 Subject: [PATCH 28/73] edit arc tolerance to use units --- src/cli/kiri-cam-process.json | 2 +- src/kiri-mode/cam/prepare.js | 2 +- src/kiri/conf.js | 2 +- src/kiri/init.js | 2 +- web/kiri/lang/en.js | 2 ++ 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cli/kiri-cam-process.json b/src/cli/kiri-cam-process.json index b1457afa0..3a9bd700d 100644 --- a/src/cli/kiri-cam-process.json +++ b/src/cli/kiri-cam-process.json @@ -97,7 +97,7 @@ "top": false }], "camTrueShadow": false, - "camArcTolerance": 0.01, + "camArcTolerance": 0.15, "camDrillMark": true, "camPocketSpindle": 1000, "camPocketTool": 1665454124874, diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index cc437261f..76e8a9e80 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -728,7 +728,7 @@ function prepEach(widget, settings, print, firstPoint, update) { //arcTolerance is the allowable distance between circle centers let arcRes = 8, //8 degs max arcMax = Infinity, // no max arc radius - lineTolerance = 0.01; // do not consider points under 0.05mm for lines + lineTolerance = 0.001; // do not consider points under 0.001mm for lines fromPoint = fromPoint || printPoint; let arcQ = []; diff --git a/src/kiri/conf.js b/src/kiri/conf.js index 3fcb2bb6b..c7e2d37e6 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -578,7 +578,7 @@ const conf = exports({ outputInvertY: false, camExpertFast: false, camTrueShadow: false, - camArcTolerance: 0.05, + camArcTolerance: 0.15, camForceZMax: false, camFirstZMax: false, camToolInit: true, diff --git a/src/kiri/init.js b/src/kiri/init.js index 2742aae16..da39d075d 100644 --- a/src/kiri/init.js +++ b/src/kiri/init.js @@ -1281,7 +1281,7 @@ gapp.register("kiri.init", (root, exports) => { _____: newGroup(LANG.op_xprt_s, $('cam-expert'), { group:"cam_expert", modes:CAM, marker: false, driven, separator }), camExpertFast: newBoolean(LANG.cx_fast_s, onBooleanClick, {title:LANG.cx_fast_l, show: () => !ui.camTrueShadow.checked }), camTrueShadow: newBoolean(LANG.cx_true_s, onBooleanClick, {title:LANG.cx_true_l, show: () => !ui.camExpertFast.checked }), - camArcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,100)}), + camArcTolerance: newInput(LANG.cx_arct_s, {title:LANG.cx_arct_l, convert:toFloat, units, bound:bound(0,100)}), /** LASER/DRAG/WJET/WEDM cut tool Settings */ diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 72a841345..e041035b1 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -856,6 +856,8 @@ self.kiri.lang['en-us'] = { cx_fast_l: ["disable overhang detection","can be faster and use less","memory with complex models","but fails with overhangs","try enabling if slicing","hangs during shadowing"], cx_true_s: "true shadow", cx_true_l: ["computationally correct shadow","will be slower but produce","better cuts for complex parts"], + cx_arct_s: "arc tolerance", + cx_arct_l: ["convert cicrular paths to arcs;","center point drift tolerance","when matching arc points","consider values around 0.15","in mm, or 0.006 inches;","High values may cause","unexpected behavior","0 to disable"], // FDM GCODE ag_menu: "gcode", From ee77501e1f61d8f3c7c890b1aeb8fa5d7e75a6a8 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 8 Jun 2025 09:20:52 -0600 Subject: [PATCH 29/73] fix arcToPath --- src/geo/paths.js | 67 ++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/geo/paths.js b/src/geo/paths.js index eef719de3..81085d57b 100644 --- a/src/geo/paths.js +++ b/src/geo/paths.js @@ -529,33 +529,34 @@ function shapeToPath(shape, points, closed) { /** * Generate a list of points approximating a circular arc. - * - * @param {number} radius - the radius of the arc. If undefined, will use the - * start and end points to infer the radius. - * @param {boolean} clockwise - whether the arc is clockwise or counter-clockwise. * @param {Point} start - the starting point of the arc. * @param {Point} end - the ending point of the arc. - * @param {number} [arcdivs=Math.PI / 24] - the angular increment to use when + * @param {number} [arcdivs= 24] - the number of lines to use to represent PI radians + * @param {number} opts.radius - the radius of the arc. If undefined, will use the + * start and end points to infer the radius. + * @param {boolean} opts.clockwise - whether the arc is clockwise or counter-clockwise. * generating the points. * * @return {Array} an array of points representing the arc. */ -function arcToPath( radius, clockwise, start, end,arcdivs=Math.PI / 24) { +function arcToPath( start, end,arcdivs=24,opts) { + + let { clockwise, center, radius } = opts; - console.log({radius, clockwise, start, end, arcdivs}); - let center = { x:0, y:0, r:0 }; + // @type {Point} + - if (end.x === undefined && end.x === end.y) { + if (end.x === undefined && end.x === undefined && center === undefined) { // bambu generates loop z or wipe loop arcs in place // console.log({ skip_empty_arc: rec }); return; + } - // G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]);return; - if (end.I !== undefined && end.J !== undefined) { - center.x = start.x + end.I; - center.y = start.y + end.J; - center.r = Math.sqrt(end.I*end.I + end.J*end.J); + if (center) { + // center = center.add(start); + center.r = center.distTo2D(start); + } else if (radius !== undefined) { let pd = { x: end.x - start.x, y: end.y - start.y }; //position delta let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; // distance @@ -571,38 +572,54 @@ function arcToPath( radius, clockwise, start, end,arcdivs=Math.PI / 24) { center.y = pr2.y; center.r = radius; } else { - console.log({malfomed_arc: {radius, clockwise, start, end}}); + console.log({malfomed_arc: {radius,center, clockwise, start, end}}); } + //deltas + let dx = start.x - end.x; + let dy = start.y - end.y; + let dz = start.z - end.z; + // line angles let a1 = Math.atan2(center.y - start.y, center.x - start.x) + Math.PI; let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI; - let ad = base.util.thetaDiff(a1, a2, clockwise); - let steps = Math.max(Math.floor(Math.abs(ad) / arcdivs), 3); - let step = (Math.abs(ad) > 0.001 ? ad : Math.PI * 2) / steps; + let ad = base.util.thetaDiff(a1, a2, clockwise); // angle difference in radians + let samePoint = Math.abs(ad) < 0.001 + let steps = samePoint? arcdivs : Math.max(Math.floor(( arcdivs) * (ad)),4); + let step = (samePoint? (Math.PI*2) : ad) / steps; + let numPoints = steps/ step; + let zStart = start.z; + let zStep = dz / numPoints; let rot = a1 + step; + // console.log({ad,samePoint,steps,step}) + //unused deltas let da = Math.abs(a1 - a2); - let dx = start.x - end.x; - let dy = start.y - end.y; let dd = Math.sqrt(dx * dx + dy * dy); // LOG({index, da, dd, first: pos, last: rec, center, a1, a2, ad, step, steps, rot, line}); // G0G1(false, [`X${center.x}`, `Y${center.y}`, `E1`]); // under 1 degree arc and 5mm, convert to straight line - if (da < 0.005 && dd < 5) { - G0G1(false, [`X${end.x}`, `Y${end.y}`, `E1`]); - return; - } + // if (da < 0.005 && dd < 5) { + // G0G1(false, [`X${end.x}`, `Y${end.y}`, `E1`]); + // return ; + // } let arr = [] // point accumulator for (let i=0; i<=steps-2; i++) { + if (isNaN(center.r) || isNaN(center.x) || isNaN(center.y)) { + console.log({malfomed_arc: {radius, clockwise, start, end}}); + } arr.push(newPoint( center.x + Math.cos(rot) * center.r, - center.y + Math.sin(rot) * center.r)); + center.y + Math.sin(rot) * center.r, + zStart, + )); + zStart += zStep; rot += step; } + // console.log(arr,start,end); return arr } From e222bf85a8ea06d2aa46ad12bcea508d2cf2fa52 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 8 Jun 2025 09:22:31 -0600 Subject: [PATCH 30/73] I've been fighting with this code for 3 hours --- src/kiri/print.js | 134 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 32 deletions(-) diff --git a/src/kiri/print.js b/src/kiri/print.js index 720afe092..92f7d6077 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -42,11 +42,25 @@ class Print { this.widget = widget; } + /** + * addOutput - add a new point to the output gcode array + * @param {any[]} array - the output gcode array + * @param {Point} point - the new point + * @param {number} emit - the extrusion value + * @param {number} speed - the feed rate + * @param {string} tool - the tool id + * @param {"lerp"|string} opts.type - the output type + * @param {Point} opts.center - the center of the arc + * @param {Point[]} opts.arcPoints - point based approximation of arc used for rendering + * @param {unknown} opts.retract - the retraction value used for FDM + * @return {Output} - the new output object + */ addOutput(array, point, emit, speed, tool, opts) { const { type, retract, center, arcPoints} = opts ?? {}; let { lastPoint, lastEmit, lastOut } = this; + let arc = emit == 2 || emit == 3; // drop duplicates (usually intruced by FDM bisections) - if (lastPoint && point && type !== 'lerp') { + if (lastPoint && point && !arc && type !== 'lerp') { // nested due to uglify confusing browser const { x, y, z } = lastPoint; if (point.x == x && point.y == y && point.z == z && lastEmit == emit) { @@ -67,6 +81,7 @@ class Print { lastOut.retract = retract; lastOut.widget = this.widget; array.push(lastOut); + console.log(structuredClone({lastOut,array})) this.nextType = undefined; return lastOut; } @@ -313,52 +328,104 @@ class Print { } /** - * Handles G2 and G3 arcs, which are circular arcs. - * @param {boolean} g2 - Whether this is a G2 or G3 arc. G2 is a clockwise arc, G3 is a counter-clockwise arc. - * @param {string[]} line - The line of the g-code file that contains the G2 or G3 command. - * @param {number} index - The line number of the g-code file that contains the G2 or G3 command. + * @function processLine + * @description parses a line of g-code into individual axis movements + * @param {string[]} line - the line of g-code as an array of strings, + * each representing a single axis movement + * @param {Object} axes - an object to store the axis values + * @returns {Object} an object containing the current and previous points */ - function G2G3(g2, line, index) { - const rec = {}; - line.forEach(tok => { - rec[tok.charAt(0).toLowerCase()] = parseFloat(tok.substring(1)); - }); - pos.x = pos.X; - pos.y = pos.Y; - arcToPath(rec.r, g2, pos, rec, 0.02).forEach(np => { - G0G1(false, [`X${np.x}`, `Y${np.y}`, `E1`]); - }) - G0G1(false, [`X${rec.x}`, `Y${rec.y}`, `E1`]); - pos.X = rec.x; - pos.Y = rec.y; - } - - function G0G1(g0, line) { - const mov = {}; - const axes = {}; + function processLine(line, axes) { + const prevPoint = newPoint( + factor * pos.X + xoff.X, + factor * pos.Y + xoff.Y, + factor * pos.Z + xoff.Z + dz + ); - lastG = g0 ? 'G0' : 'G1'; + const point = prevPoint.clone() + + line.forEach(tok => { - let axis = tok.charAt(0); + let axis = tok.charAt(0).toUpperCase(); if (morph && belt) { axis = beltaxis[axis]; } + console.log("position updated",structuredClone(pos)) + let val = parseFloat(tok.substring(1)); axes[axis] = val; + // if( axis == 'I' || axis == "J") return if (abs) { pos[axis] = val; + + if (axis == "X") point.x = factor * pos.X + xoff.X + else if (axis == "Y") point.y = factor * pos.Y + xoff.Y + else if (axis == "Z") point.z = factor * pos.Z + xoff.Z+dz + + } else { mov[axis] = val; pos[axis] += val; } + console.log("position updated",structuredClone(pos)) }); - const point = newPoint( - factor * pos.X + xoff.X, - factor * pos.Y + xoff.Y, - factor * pos.Z + xoff.Z + dz - ); + let center; + if(axes.I !== undefined && axes.J !== undefined) { + center = newPoint( + factor* axes.I+ xoff.X, + factor* axes.J+ xoff.Y, + 0, + ); + }else if(axes.R !== undefined) { + center = newPoint( + factor* Math.cos(axes.R * DEG2RAD), + factor* Math.sin(axes.R * DEG2RAD), + 0, + ); + } + if(center){ + center = center.add(prevPoint); + center.setZ((prevPoint.z+point.z)/2+dz); + console.log("center z update",center) + } + + return { + center, + point, + prevPoint + }; + } + + /** + * Handles G2 and G3 arcs, which are circular arcs. + * @param {boolean} g2 - Whether this is a G2 or G3 arc. G2 is a clockwise arc, G3 is a counter-clockwise arc. + * @param {string[]} line - The line of the g-code file that contains the G2 or G3 command. + * @param {number} index - The line number of the g-code file that contains the G2 or G3 command. + */ + function G2G3(g2, line, index) { + const axes = {}; + const {point, prevPoint, center} = processLine(line,axes); + + console.log(structuredClone({point,prevPoint,center})); + + let arcPoints = arcToPath( prevPoint, point, 24,{ clockwise:g2,center}) ?? [] + let emit = g2 ? 2 : 3; + + console.log("clone point",structuredClone({point,prevPoint,center,arcPoints,emit})); + console.log("pointer point",{point,prevPoint,center,arcPoints,emit}); + + scope.addOutput(seq, point, emit, pos.F, tool,{arcPoints}); + scope.lastPos = Object.assign({}, pos); + } + + function G0G1(g0, line) { + const mov = {}; + const axes = {}; + + lastG = g0 ? 'G0' : 'G1'; + const {point} = processLine(line,axes); if (morph && belt) { point.y -= point.z * beltfact; @@ -389,6 +456,7 @@ class Print { // always add moves to the current sequence if (moving) { + console.log("move",structuredClone(point)) scope.addOutput(seq, point, false, pos.F, tool).retract = retract; scope.lastPos = Object.assign({}, pos); return; @@ -439,6 +507,7 @@ class Print { } } // add point to current sequence + console.log("cut",structuredClone(point)) scope.addOutput(seq, point, true, pos.F, tool).retract = retract; scope.lastPos = Object.assign({}, pos); scope.lastPosE = pos.E; @@ -515,10 +584,10 @@ class Print { case 'G11': break; case 'G0': - G0G1(true, line); + G0G1(1, line); break; case 'G1': - G0G1(false, line); + G0G1(0, line); break; case 'G2': // turn arc into a series of points @@ -555,6 +624,7 @@ class Print { console.log({ bounds, print_time: time.round(2) }); } + console.log(scope.output) done({ output: scope.output }); } } From 825d556e71d9650098405909ec316f229e2d1017 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 12 Jun 2025 12:06:54 -0600 Subject: [PATCH 31/73] fix line number calculation math --- src/geo/paths.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geo/paths.js b/src/geo/paths.js index 81085d57b..134a63f34 100644 --- a/src/geo/paths.js +++ b/src/geo/paths.js @@ -585,13 +585,13 @@ function arcToPath( start, end,arcdivs=24,opts) { let a2 = Math.atan2(center.y - end.y, center.x - end.x) + Math.PI; let ad = base.util.thetaDiff(a1, a2, clockwise); // angle difference in radians let samePoint = Math.abs(ad) < 0.001 - let steps = samePoint? arcdivs : Math.max(Math.floor(( arcdivs) * (ad)),4); + let ofFull = Math.abs(ad)/(2*Math.PI); + let steps = samePoint? arcdivs : Math.max(Math.floor( arcdivs * ofFull),4); let step = (samePoint? (Math.PI*2) : ad) / steps; let numPoints = steps/ step; let zStart = start.z; let zStep = dz / numPoints; let rot = a1 + step; - // console.log({ad,samePoint,steps,step}) //unused deltas let da = Math.abs(a1 - a2); From c37946f51c1109382feadb4bfee0f8d145f7b082 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 12 Jun 2025 12:08:47 -0600 Subject: [PATCH 32/73] fix the offsetting issue --- src/kiri/print.js | 170 +++++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 86 deletions(-) diff --git a/src/kiri/print.js b/src/kiri/print.js index 92f7d6077..61f90e55e 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -81,7 +81,7 @@ class Print { lastOut.retract = retract; lastOut.widget = this.widget; array.push(lastOut); - console.log(structuredClone({lastOut,array})) + // console.log("addOutput Called", structuredClone({lastOut,array})) this.nextType = undefined; return lastOut; } @@ -294,14 +294,14 @@ class Print { E: 0.0 }, off = { - X: offset ? offset.x || 0 : 0, - Y: offset ? offset.y || 0 : 0, - Z: offset ? offset.z || 0 : 0 + x: offset ? offset.x || 0 : 0, + y: offset ? offset.y || 0 : 0, + z: offset ? offset.z || 0 : 0 }, xoff = { - X: 0, - Y: 0, - Z: 0 + x: 0, + y: 0, + z: 0 }; let dz = 0, @@ -337,21 +337,31 @@ class Print { */ function processLine(line, axes) { const prevPoint = newPoint( - factor * pos.X + xoff.X, - factor * pos.Y + xoff.Y, - factor * pos.Z + xoff.Z + dz - ); + factor * pos.X , + factor * pos.Y , + factor * pos.Z + dz + ) + .add(xoff) + .add(off); + + // apply origin offset + // for (let layer of output) { + // for (let rec of layer) { + // let point = rec.point; + // point.x += off.X; + // point.y += off.Y; + // point.z += off.Z; + // } + // } const point = prevPoint.clone() - - line.forEach(tok => { let axis = tok.charAt(0).toUpperCase(); if (morph && belt) { axis = beltaxis[axis]; } - console.log("position updated",structuredClone(pos)) + // console.log("position updated",structuredClone(pos)) let val = parseFloat(tok.substring(1)); axes[axis] = val; @@ -359,23 +369,23 @@ class Print { if (abs) { pos[axis] = val; - if (axis == "X") point.x = factor * pos.X + xoff.X - else if (axis == "Y") point.y = factor * pos.Y + xoff.Y - else if (axis == "Z") point.z = factor * pos.Z + xoff.Z+dz + 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; pos[axis] += val; } - console.log("position updated",structuredClone(pos)) + // console.log("position updated",structuredClone(pos)) }); let center; if(axes.I !== undefined && axes.J !== undefined) { center = newPoint( - factor* axes.I+ xoff.X, - factor* axes.J+ xoff.Y, + factor* axes.I+ xoff.x, + factor* axes.J+ xoff.y, 0, ); }else if(axes.R !== undefined) { @@ -388,7 +398,6 @@ class Print { if(center){ center = center.add(prevPoint); center.setZ((prevPoint.z+point.z)/2+dz); - console.log("center z update",center) } return { @@ -398,6 +407,48 @@ 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; + let nh = dz > 0 ? dz : defh; + seq = []; + seq.height = height = nh; + if (fdm) dz = -height / 2; + output.push(seq); + } + + if (!hasmoved) { + seq.height = seq.z = pos.Z; + hasmoved = true; + } + + // debug extrusion rate + const lastPos = scope.lastPos; + if (scope.debugE && fdm && lastPos && pos.E) { + // extruder move + let dE = (absE ? pos.E - scope.lastPosE : pos.E); + // distance moved in XY + let dV = point.distTo2D(lastP); + // debug print time + time += (dV * pos.F) / 1000; + // filament per mm + let dR = (dE / dV); + if (dV > 2 && dE > 0.001) { + let lab = (absE ? 'aA' : 'rR')[scope.debugE++ % 2]; + console.log(lab, height.toFixed(2), dV.toFixed(2), dE.toFixed(3), dR.toFixed(4), pos.F.toFixed(0)); + } + } + // add point to current sequence + scope.addOutput(seq, point, emit, pos.F, tool,{retract,arcPoints}); + scope.lastPos = Object.assign({}, pos); + scope.lastPosE = pos.E; + } + /** * Handles G2 and G3 arcs, which are circular arcs. * @param {boolean} g2 - Whether this is a G2 or G3 arc. G2 is a clockwise arc, G3 is a counter-clockwise arc. @@ -408,16 +459,16 @@ class Print { const axes = {}; const {point, prevPoint, center} = processLine(line,axes); - console.log(structuredClone({point,prevPoint,center})); + // console.log(structuredClone({point,prevPoint,center})); - let arcPoints = arcToPath( prevPoint, point, 24,{ clockwise:g2,center}) ?? [] + let arcPoints = arcToPath( prevPoint, point, 64,{ clockwise:g2,center}) ?? [] let emit = g2 ? 2 : 3; - console.log("clone point",structuredClone({point,prevPoint,center,arcPoints,emit})); - console.log("pointer point",{point,prevPoint,center,arcPoints,emit}); + // console.log("clone point",structuredClone({point,prevPoint,center,arcPoints,emit})); + // console.log("pointer point",{point,prevPoint,center,arcPoints,emit}); - scope.addOutput(seq, point, emit, pos.F, tool,{arcPoints}); - scope.lastPos = Object.assign({}, pos); + outputPoint(point,prevPoint,emit,{center,arcPoints}); + // scope.addOutput(seq, point, emit, pos.F, tool,{center,arcPoints}); } function G0G1(g0, line) { @@ -425,7 +476,7 @@ class Print { const axes = {}; lastG = g0 ? 'G0' : 'G1'; - const {point} = processLine(line,axes); + const {point, prevPoint} = processLine(line,axes); if (morph && belt) { point.y -= point.z * beltfact; @@ -456,61 +507,19 @@ class Print { // always add moves to the current sequence if (moving) { - console.log("move",structuredClone(point)) - scope.addOutput(seq, point, false, pos.F, tool).retract = retract; + // console.log("move",structuredClone(point)) + scope.addOutput(seq, point, 0, pos.F, tool,{retract}) scope.lastPos = Object.assign({}, pos); return; } - - if (seq.Z === undefined) { - seq.Z = pos.Z; + if (seq.z === undefined) { + seq.z = point.z; } - if (fdm && height === 0) { seq.height = defh = height = pos.Z; } - // non-move in a new plane means burp out - // the old sequence and start a new one - if (newlayer || (autolayer && seq.Z != pos.Z)) { - newlayer = false; - let dz = pos.Z - seq.Z; - let nh = dz > 0 ? dz : defh; - seq = []; - seq.height = height = nh; - if (fdm) dz = -height / 2; - output.push(seq); - } - - if (!hasmoved && !moving) { - seq.height = seq.Z = pos.Z; - hasmoved = true; - } - - // debug extrusion rate - const lastPos = scope.lastPos; - if (scope.debugE && fdm && lastPos && pos.E) { - // extruder move - let dE = (absE ? pos.E - scope.lastPosE : pos.E); - // distance moved in XY - let dV = Math.sqrt( - (Math.pow(pos.X - lastPos.X, 2)) + - (Math.pow(pos.Y - lastPos.Y, 2)) - ); - // debug print time - time += (dV * pos.F) / 1000; - // filament per mm - let dR = (dE / dV); - if (dV > 2 && dE > 0.001) { - let lab = (absE ? 'aA' : 'rR')[scope.debugE++ % 2]; - console.log(lab, height.toFixed(2), dV.toFixed(2), dE.toFixed(3), dR.toFixed(4), pos.F.toFixed(0)); - } - } - // add point to current sequence - console.log("cut",structuredClone(point)) - scope.addOutput(seq, point, true, pos.F, tool).retract = retract; - scope.lastPos = Object.assign({}, pos); - scope.lastPosE = pos.E; + outputPoint(point,prevPoint,1,{retract}) } const linemod = cam ? Math.ceil(lines.length / 2500) : 0; @@ -602,17 +611,6 @@ class Print { break; } }); - - // apply origin offset - for (let layer of output) { - for (let rec of layer) { - let point = rec.point; - point.x += off.X; - point.y += off.Y; - point.z += off.Z; - } - } - scope.imported = gcode; scope.lines = lines.length; scope.bytes = gcode.length; From 02f34a9c1480519af498addbdc55531d277edc95 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 12 Jun 2025 12:27:33 -0600 Subject: [PATCH 33/73] generate arcPoints with arcToPath and add emit to camOut --- src/kiri-mode/cam/prepare.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 76e8a9e80..14877dcdf 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -13,7 +13,7 @@ gapp.register("kiri-mode.cam.prepare", (root, exports) => { const { base, kiri } = root; const { paths, polygons, newPoint, util} = base; -const { tip2tipEmit, poly2polyEmit } = paths; +const { tip2tipEmit, poly2polyEmit, arcToPath } = paths; const { driver, render } = kiri; const { CAM } = driver; @@ -317,15 +317,15 @@ function prepEach(widget, settings, print, firstPoint, update) { } /** - * emit a cut or move operation from the current location to a new location + * emit a cut, arc, or move operation from the current location to a new location * @param {Point} point destination for move - * @param {1|0|boolean} cut 1/true = cut, 0/false = move + * @param {0|1|2|3} emit G0, G1, G2, G3 * @param {number} opts.radius arc radius; truthy values for arc move * @param {boolean} opts.clockwise arc direction * @param {number} opts.moveLen typically = tool diameter used to trigger terrain detection * @param {number} opts.factor speed scale factor */ - function camOut(point, cut,opts) { + function camOut(point, emit,opts) { let { center = {}, clockwise = true, @@ -335,7 +335,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } = opts ?? {} // console.log({radius}) - const isArc = arcPoints.length > 0; + const isArc = emit == 2 || emit == 3; point = point.clone(); point.x += wmx; @@ -344,7 +344,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // console.log(point.z); if (nextIsMove) { - cut = 0; + emit = 0; nextIsMove = false; } @@ -367,7 +367,7 @@ function prepEach(widget, settings, print, firstPoint, update) { // console.log(lp.a, lp.x, lp.y, lp.z); lastPoint = layerPush( lp, - cut ? 1 : 0, + emit, rate, tool, {type:"lerp"}, @@ -388,7 +388,7 @@ function prepEach(widget, settings, print, firstPoint, update) { let deltaXY = lastPoint.distTo2D(point), deltaZ = point.z - lastPoint.z, absDeltaZ = Math.abs(deltaZ), - isMove = !(cut || isArc); + isMove = emit == 0; // drop points too close together if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z) { @@ -493,7 +493,7 @@ function prepEach(widget, settings, print, firstPoint, update) { layerOut.spindle = spindle; lastPoint = layerPush( point, - cut ? 1 : 0, + emit, rate, tool ); @@ -886,6 +886,8 @@ function prepEach(widget, settings, print, firstPoint, update) { function drainQ() { + let arcPreviewRes = 64 + if (!arcTolerance) { return; } @@ -907,16 +909,19 @@ function prepEach(widget, settings, print, firstPoint, update) { if(arcQ.length == poly.points.length){ //if is a circle + // generate circle + let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}); camOut(from,1); - camOut(from,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + camOut(from,1,{ center:center.sub(from), clockwise, arcPoints}); lastPoint = from.clone(); }else{ //if a non-circle arc + let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}); // first arc point camOut(from,1); // rest of arc to final point - camOut(to,1,{ center:center.sub(from), clockwise, arcPoints:[...arcQ]}); + camOut(to,1,{ center:center.sub(from), clockwise, arcPoints}); lastPoint = to.clone(); } From 63d12bacf8e39fff09b1001edfb44dcd78af51ab Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 12 Jun 2025 13:07:13 -0600 Subject: [PATCH 34/73] complete gcode parsing --- src/kiri-mode/cam/prepare.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 14877dcdf..fcce08942 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -402,7 +402,7 @@ function prepEach(widget, settings, print, firstPoint, update) { let isflat = absDeltaZ < 0.001; // restrict this to contouring if (isflat || (iscontour && absDeltaZ <= tolerance)) { - cut = 1; + emit = 1; isMove = false; } else if (deltaZ <= -tolerance) { // move over before descending @@ -783,10 +783,7 @@ function prepEach(widget, settings, print, firstPoint, update) { if (dist >lineTolerance && lastp) { let rec = Object.assign(point,{dist}); arcQ.push(rec); - let desp = false; // do arcQ[0] and rec have differing move speeds? - if (arcQ.length > 1) { - desp = arcQ[0].speedMMM !== rec.speedMMM; - } + // ondebug({arcQ}); if (arcQ.length > 2) { let el = arcQ.length; @@ -833,7 +830,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } // if new point is off the arc // if (deem || depm || desp || dc > arcTolerance || cc.r < arcMin || cc.r > arcMax || dist > cc.r) { - if ( desp || dc * arcQ.center.length / arcQ.rSum > arcTolerance || dist > cc.r || cc.r > arcMax || radFault ) { + if ( dc * arcQ.center.length / arcQ.rSum > arcTolerance || dist > cc.r || cc.r > arcMax || radFault ) { // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcTolerance, dist > cc.r, cc.r > arcMax, radFault]; // console.log("point off the arc,",structuredClone(arcQ),); if (arcQ.length === 4) { @@ -896,8 +893,10 @@ function prepEach(widget, settings, print, firstPoint, update) { let vec1 = new THREE.Vector2(arcQ[1].x - arcQ[0].x, arcQ[1].y - arcQ[0].y); let vec2 = new THREE.Vector2(arcQ.center[0].x - arcQ[0].x, arcQ.center[0].y - arcQ[0].y); let clockwise = vec1.cross(vec2) < 0 + let gc = clockwise ? 2 :3 let from = arcQ[0]; let to = arcQ.peek(); + let delta = from.distTo2D(to) arcQ.xSum = arcQ.center.reduce( (t, v) => t + v.x , 0 ); arcQ.ySum = arcQ.center.reduce( (t, v) => t + v.y , 0 ); arcQ.rSum = arcQ.center.reduce( (t, v) => t + v.r , 0 ); @@ -907,21 +906,22 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum / cl, ) - if(arcQ.length == poly.points.length){ + if(arcQ.length == poly.points.length && poly.circularity() > 0.98){ //if is a circle // generate circle - let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}); + // console.log("circle",{from, to,center}); + let arcPoints = arcToPath( from, from, arcPreviewRes,{ clockwise,center}); camOut(from,1); - camOut(from,1,{ center:center.sub(from), clockwise, arcPoints}); + camOut(from,gc,{ center:center.sub(from), clockwise, arcPoints}); lastPoint = from.clone(); }else{ //if a non-circle arc let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}); - + // console.log("arc") // first arc point camOut(from,1); // rest of arc to final point - camOut(to,1,{ center:center.sub(from), clockwise, arcPoints}); + camOut(to,gc,{ center:center.sub(from), clockwise, arcPoints}); lastPoint = to.clone(); } @@ -936,10 +936,7 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.center = undefined; } - - - - newLayer(); + // newLayer(); return lastPoint; } From 569ab2931be7304ce728261201abc9cc355326f7 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 17 Jun 2025 09:04:56 -0600 Subject: [PATCH 35/73] remove cutdir bug --- src/kiri-mode/cam/ops.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index 408ded971..54af68a97 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -700,10 +700,6 @@ class OpOutline extends CamOp { setTool(op.tool, op.rate, op.plunge); setSpindle(op.spindle); - if (!process.camOutlinePocket) { - cutdir = !cutdir; - } - // printpoint becomes NaN in engine mode. not sure why but this fixes it if(Object.values(printPoint).some(v=>Number.isNaN(v))){ printPoint = newPoint(0,0,0); From d5677b2d80c325a6556e36eaae275c8877bc0710 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 16:19:34 -0600 Subject: [PATCH 36/73] remove camOutlinePocket rename --- src/kiri/conf.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/kiri/conf.js b/src/kiri/conf.js index 1830281f1..25a93daf1 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -215,7 +215,6 @@ const renamed = { drillDwell: "camDrillDwell", drillLift: "camDrillLift", drillingOn: "camDrillingOn", - camPocketOnlyFinish: "camOutlinePocket", camWideCutout: "camOutlineWide", outputClockwise: "camConventional" }; From a764cdaafc26d4a67a1cecaae1d561b58aab826f Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 16:32:11 -0600 Subject: [PATCH 37/73] add comments to sliceOutput --- src/kiri-mode/cam/prepare.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index e5c9cbe9d..c463b2804 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -491,6 +491,7 @@ function prepEach(widget, settings, print, firstPoint, update) { if (depthFirst) { depthData.push(polys); } else { + // if not depth first, output the polys in slice order printPoint = poly2polyEmit(polys, printPoint, function(poly, index, count) { poly.forEachPoint(function(point, pidx, points, offset) { // scale speed of first cutting poly since it engages the full bit @@ -508,10 +509,12 @@ function prepEach(widget, settings, print, firstPoint, update) { } if (depthFirst) { + // get inside vals (the positive ones) let ins = depthData.map(a => a.filter(p => !isNeg(p.depth))); let itops = ins.map(level => { return POLY.nest(level.filter(poly => poly.depth === 0).clone()); }); + // get outside vals (the negative ones) let outs = depthData.map(a => a.filter(p => isNeg(p.depth))); let otops = outs.map(level => { return POLY.nest(level.filter(poly => poly.depth === 0).clone()); From 7494eedc21b67af3a26a8634dadb47aa0d9c5242 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 16:52:58 -0600 Subject: [PATCH 38/73] start working on radFault work --- src/kiri-mode/cam/prepare.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index fcce08942..a412b926d 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -12,7 +12,8 @@ gapp.register("kiri-mode.cam.prepare", (root, exports) => { const { base, kiri } = root; -const { paths, polygons, newPoint, util} = base; +const { paths, polygons, newPoint, util } = base; +const { toRadians} = util const { tip2tipEmit, poly2polyEmit, arcToPath } = paths; const { driver, render } = kiri; const { CAM } = driver; @@ -726,7 +727,7 @@ function prepEach(widget, settings, print, firstPoint, update) { */ function polyEmit(poly, index, count, fromPoint) { //arcTolerance is the allowable distance between circle centers - let arcRes = 8, //8 degs max + let arcRes = toRadians(5), //5 degs max arcMax = Infinity, // no max arc radius lineTolerance = 0.001; // do not consider points under 0.001mm for lines @@ -799,7 +800,11 @@ function prepEach(widget, settings, print, firstPoint, update) { let radFault = false; if (lr) { let angle = 2 * Math.asin(dist/(2*lr.r)); - radFault = Math.abs(angle) > Math.PI * 2 / arcRes; // enforce arcRes(olution) + radFault = Math.abs(angle) > arcRes; // enforce arcRes(olution) + if(radFault){ + console.log({angle, arcRes, dist, lr,radFault}); + + } // if (arcQ.center) { // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); // let avg = arcQ.rSum / arcQ.center.length; @@ -906,7 +911,7 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum / cl, ) - if(arcQ.length == poly.points.length && poly.circularity() > 0.98){ + if(arcQ.length == poly.points.length){ //if is a circle // generate circle // console.log("circle",{from, to,center}); From f62a9970b24fb601b73dfd65b27f4129d34d32b6 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 17:59:28 -0600 Subject: [PATCH 39/73] fix animate bug --- src/kiri-mode/cam/animate.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/kiri-mode/cam/animate.js b/src/kiri-mode/cam/animate.js index 0c7cda85e..7a5de7e42 100644 --- a/src/kiri-mode/cam/animate.js +++ b/src/kiri-mode/cam/animate.js @@ -354,12 +354,15 @@ kiri.load(() => { //destructure arcs into path points path = path.map(o=> o.arcPoints - ? o.arcPoints.map(point=> + ? [ + ...o.arcPoints.map(point=> ({ ...o, point }) - ) + ), + o + ] : o ) .flat(); From 1ac1dd922847c24a81177f2cbec83c0873e46184 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 18:08:09 -0600 Subject: [PATCH 40/73] apply widget movement to arc points --- src/kiri-mode/cam/prepare.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index a412b926d..38f6b0f80 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -317,6 +317,20 @@ function prepEach(widget, settings, print, firstPoint, update) { ); } + + /** + * Move a point by the widget's movement offset. + * @param {Point} p - point to move + * @return {Point} new point with offset applied + */ + function applyWidgetMovement(p){ + return newPoint( + p.x + wmx, + p.y + wmy, + p.z + zadd + ) + } + /** * emit a cut, arc, or move operation from the current location to a new location * @param {Point} point destination for move @@ -338,10 +352,8 @@ function prepEach(widget, settings, print, firstPoint, update) { // console.log({radius}) const isArc = emit == 2 || emit == 3; - point = point.clone(); - point.x += wmx; - point.y += wmy; - point.z += zadd; + //apply widget movement pos + point = applyWidgetMovement(point); // console.log(point.z); if (nextIsMove) { @@ -848,13 +860,14 @@ function prepEach(widget, settings, print, firstPoint, update) { } else { arcQ.center = [ tc ]; let angle = 2 * Math.asin(arcQ[1].dist/(2*tc.r)); - if (Math.abs(angle) > Math.PI * 2 / arcRes) { // enforce arcRes on initial angle + if (Math.abs(angle) > arcRes) { // enforce arcRes on initial angle camOut(arcQ.shift(),1); } } } else { // enough to consider an arc, emit and start new arc let defer = arcQ.pop(); + console.log("draining",defer, structuredClone(arcQ),arcQ.length); drainQ(); // re-add point that was off the last arc arcQ.push(defer); @@ -915,13 +928,15 @@ function prepEach(widget, settings, print, firstPoint, update) { //if is a circle // generate circle // console.log("circle",{from, to,center}); - let arcPoints = arcToPath( from, from, arcPreviewRes,{ clockwise,center}); + let arcPoints = arcToPath( from, from, arcPreviewRes,{ clockwise,center}) + .map(applyWidgetMovement); camOut(from,1); camOut(from,gc,{ center:center.sub(from), clockwise, arcPoints}); lastPoint = from.clone(); }else{ //if a non-circle arc - let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}); + let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}) + .map(applyWidgetMovement); // console.log("arc") // first arc point camOut(from,1); From 1071e07b99dea452ed6daaaaf7db5f6e658b6eae Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 20:14:48 -0600 Subject: [PATCH 41/73] make arc generation more robust, and give up fighting with sliceOutput --- src/kiri-mode/cam/prepare.js | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 38f6b0f80..53cf3a754 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -341,6 +341,8 @@ function prepEach(widget, settings, print, firstPoint, update) { * @param {number} opts.factor speed scale factor */ function camOut(point, emit,opts) { + + // console.log({point, emit, opts}) let { center = {}, clockwise = true, @@ -544,8 +546,11 @@ function prepEach(widget, settings, print, firstPoint, update) { if (depthFirst) { depthData.push(polys); } else { - poly2polyEmit(polys, printPoint, (poly, index, count)=> { - printPoint =polyEmit(poly, index, count,printPoint); + printPoint = poly2polyEmit(polys, printPoint, function(poly, index, count) { + poly.forEachPoint(function(point, pidx, points, offset) { + // scale speed of first cutting poly since it engages the full bit + camOut(point.clone(), offset !== 0, undefined, count === 1 ? engageFactor : 1); + }, poly.isClosed(), index); }, { swapdir: false }); newLayer(); } @@ -739,12 +744,13 @@ function prepEach(widget, settings, print, firstPoint, update) { */ function polyEmit(poly, index, count, fromPoint) { //arcTolerance is the allowable distance between circle centers - let arcRes = toRadians(5), //5 degs max + let arcRes = toRadians(2), //5 degs max arcMax = Infinity, // no max arc radius lineTolerance = 0.001; // do not consider points under 0.001mm for lines fromPoint = fromPoint || printPoint; let arcQ = []; + arcQ.angle = [] let closest = poly.findClosestPointTo(fromPoint); let lastPoint = closest.point; @@ -790,7 +796,6 @@ function prepEach(widget, settings, print, firstPoint, update) { } function arcExport(point,lastp){ - // console.log("start",point,lastp) let dist = lastp? point.distTo2D(lastp) : 0; if (lastp) { if (dist >lineTolerance && lastp) { @@ -813,19 +818,12 @@ function prepEach(widget, settings, print, firstPoint, update) { if (lr) { let angle = 2 * Math.asin(dist/(2*lr.r)); radFault = Math.abs(angle) > arcRes; // enforce arcRes(olution) - if(radFault){ - console.log({angle, arcRes, dist, lr,radFault}); - } - // if (arcQ.center) { - // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 ); - // let avg = arcQ.rSum / arcQ.center.length; - // radFault = radFault || Math.abs(avg - lr.r) / avg > arcDev; // eliminate sharps and flats when local rad is out of arcDev(iation) - // } } else { radFault = true; console.log("too much angle") } + if (cc) { if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { @@ -836,6 +834,17 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.xSum = cc.x; arcQ.ySum = cc.y; arcQ.rSum = cc.r; + + // check if first angles should have caused radFault + let a = toRadians(arcQ[0].slopeTo(cc).angle) + let b = toRadians(arcQ[1].slopeTo(cc).angle) + let angle = Math.abs(b-a) + + if( Math.abs(angle) > arcRes){ + // if so, remove first point + camOut(arcQ.shift(), 1) + radFault = true + } } else { // check center point delta arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 ); @@ -867,7 +876,6 @@ function prepEach(widget, settings, print, firstPoint, update) { } else { // enough to consider an arc, emit and start new arc let defer = arcQ.pop(); - console.log("draining",defer, structuredClone(arcQ),arcQ.length); drainQ(); // re-add point that was off the last arc arcQ.push(defer); @@ -924,7 +932,7 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum / cl, ) - if(arcQ.length == poly.points.length){ + if(arcQ.length == poly.points.length ){ //if is a circle // generate circle // console.log("circle",{from, to,center}); From 7f12f929e55de91d1cd467edc63f0e146ea6d618 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 18 Jun 2025 20:15:34 -0600 Subject: [PATCH 42/73] add typecheck to avoid dangerous code that can cause infinite looping --- src/geo/base.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/geo/base.js b/src/geo/base.js index 1e64af865..e851e8039 100644 --- a/src/geo/base.js +++ b/src/geo/base.js @@ -405,6 +405,10 @@ function center2pr(p1, p2, r, clockwise) { // find angle difference between 0 and 2pi from n1 to n2 (signed depending on clock direction) function thetaDiff(n1, n2, clockwise) { let diff = n2 - n1; + if(typeof n1 != 'number' || typeof n2 != 'number') { + throw ("n1 and n2 must be numbers"); + // this check is here because this causes an infinite loop when other value are provided + } while (diff < -Math.PI) diff += Math.PI * 2; while (diff > Math.PI) diff -= Math.PI * 2; if (clockwise && diff > 0) diff -= Math.PI * 2; From 32fca846a0fb57f458595975d0f5783687bbec03 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 19 Jun 2025 10:20:27 -0600 Subject: [PATCH 43/73] switch polyEmit to segment-based --- src/kiri-mode/cam/prepare.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index fcce08942..84e2f54d3 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -737,6 +737,8 @@ function prepEach(widget, settings, print, firstPoint, update) { let lastPoint = closest.point; let startIndex = closest.index; + let circleComplete = false; + // scale speed of first cutting poly since it engages the full bit let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; @@ -751,17 +753,19 @@ function prepEach(widget, settings, print, firstPoint, update) { startIndex = last; } - - poly.forEachPoint( ( point, pidx, points, offset) => { + console.log(poly,poly.isClosed(),startIndex) + + //A is first point of segment, B is last + poly.forEachSegment( ( pointA, pointB, indexA, indexB) => { // if(offset == 0) console.log("forEachPoint",point,pidx,points) - if(offset == 0){ + if(indexA == startIndex){ + camOut(pointA.clone(), 0, {factor:engageFactor}); // if first point, move to and call export function - camOut(point.clone(), 0, {factor:engageFactor}); - quePush(point); + console.log("start") + quePush(pointA); } - else arcExport(point, lastPoint); - lastPoint = point; - }, poly.isClosed(), startIndex); + lastPoint = arcExport(pointB, pointA); + }, !poly.isClosed(), startIndex); // console.log("at end of arcExport",structuredClone(arcQ)); if(arcQ.length > 3){ @@ -913,7 +917,8 @@ function prepEach(widget, settings, print, firstPoint, update) { let arcPoints = arcToPath( from, from, arcPreviewRes,{ clockwise,center}); camOut(from,1); camOut(from,gc,{ center:center.sub(from), clockwise, arcPoints}); - lastPoint = from.clone(); + // lastPoint = to.clone(); + circleComplete = true }else{ //if a non-circle arc let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}); From aaa45d1fc11b834162dcf3580eac6b052d31df7e Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 21 Jun 2025 19:14:11 -0400 Subject: [PATCH 44/73] start refactor of trace to use detected edges instead of flat slice polys --- src/kiri-mode/cam/slice.js | 148 ++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 50 deletions(-) diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index f67adaf76..ef4795f28 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -2,6 +2,7 @@ "use strict"; +// dep: geo.line // dep: geo.point // dep: geo.polygons // dep: kiri.slice @@ -14,7 +15,7 @@ gapp.register("kiri-mode.cam.slice", (root, exports) => { const { base, kiri } = root; const { util } = base; const { driver, newSlice, setSliceTracker } = kiri; -const { polygons, newPoint, newPolygon } = base; +const { polygons, newLine, newPoint, newPolygon } = base; const { CAM } = driver; const { OPS } = CAM; @@ -379,60 +380,107 @@ CAM.addDogbones = function(poly, dist, reverse) { }; CAM.traces = async function(settings, widget, single) { - if (widget.traces && widget.trace_single === single) { - // do no work if cached + if (widget.traces) { return false; } - let slicer = new kiri.cam_slicer(widget); - let zees = Object.assign({}, slicer.zFlat, slicer.zLine); - let indices = [...new Set(Object.keys(zees) - .map(kv => parseFloat(kv).round(5)) - .appendAll(Object.entries(single ? slicer.zLine : {}).map(ze => { - let [ zk, zv ] = ze; - return zv > 1 ? parseFloat(zk).round(5) : null; - }) - .filter(v => v !== null)) - )] - .sort((a,b) => b - a); - let traces = []; - // find and trim polys (including open) to shadow - let oneach = data => { - if (single) { - for (let line of data.lines) { - if (line.p1.distTo2D(line.p2) > 1) { - traces.push(newPolygon().append(line.p1).append(line.p2).setOpen()); - } - } - } else - base.polygons.flatten(data.tops).forEach(poly => { - poly.inner = null; - poly.parent = null; - let z = poly.getZ(); - for (let i=0, il=traces.length; i 0.01) { - continue; - } - // do not add duplicates - if (traces[i].isEquivalent(poly) && dz < 0.1) { - // console.log({ dup: poly }); - return; - } + + // --- points → line segments --- + let edges = new THREE.EdgesGeometry(widget.mesh.geometry, 20); + let array = edges.attributes.position.array; + let pcache = {}; + let points = new Array(2); + let lines = new Array(points.length / 2); + for (let i=0, j=0, k=0, l=array.length; i (v * 10000) | 0).join(','); + let point = pcache[key]; + if (!point) { + point = newPoint(ps[0], ps[1], ps[2], key); + point.lines = []; + pcache[key] = point; + } + points[j++ % 2] = point; + if (j % 2 === 0) { + let [ p0, p1 ] = points; + let line = lines[k++] = newLine(p0, p1); + p0.lines.push(line); + p1.lines.push(line); + } + } + + // --- segments → chains (bidirectional walk) --- + let chains = []; + { + const segs = lines.slice(); // shallow copy + segs.forEach(s => (s.visited = false)); + + function step(dirPoint, prevSeg, pushFront, chain) { + let curr = dirPoint; + let prev = prevSeg; + + while (curr.lines.length === 2) { // stay inside the arc + const next = curr.lines.find(l => !l.visited && l !== prev); + if (!next) break; // should not happen + + next.visited = true; + if (pushFront) chain.unshift(next); // prepend or append? + else chain.push(next); + + curr = next.p1 === curr ? next.p2 : next.p1; + prev = next; } - traces.push(poly); - }); - }; + } + for (const seed of segs) { + if (seed.visited) continue; + + seed.visited = true; // always include the seed + const chain = [seed]; + + // grow backwards from p1 (prepend) + step(seed.p1, seed, true, chain); + + // grow forwards from p2 (append) + step(seed.p2, seed, false, chain); + + chains.push(chain); + } + } + + // --- chains → polylines --- + const polylines = []; + + for (const chain of chains) { + const poly = newPolygon().setOpen(); + + // choose any node whose degree !== 2; if none, it’s a closed loop + let start = null; + for (const s of chain) { + if (s.p1.lines.length !== 2) { start = s.p1; break; } + if (s.p2.lines.length !== 2) { start = s.p2; break; } + } + if (!start) start = chain[0].p1; // closed loop + + let curr = start, prevSeg = null; + poly.push(curr); // first point + + while (true) { + const nextSeg = curr.lines.find( + l => l !== prevSeg && chain.includes(l) + ); + if (!nextSeg) break; // open end reached + + curr = nextSeg.p1 === curr ? nextSeg.p2 : nextSeg.p1; + poly.push(curr); // << push *before* test + if (curr === start) break; // loop closed + + prevSeg = nextSeg; + } + polylines.push(poly); + } + + widget.traces = polylines; - let opts = { each: oneach, over: false, flatoff: 0, edges: true, openok: true, lines: true }; - await slicer.slice(indices, opts); - // pick up bottom features - opts.over = true; - await slicer.slice(indices, opts); - widget.traces = traces; - widget.trace_single = single; return true; }; From 220dc7c439a2a631017494a75193247f50a25a86 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 21 Jun 2025 21:54:58 -0400 Subject: [PATCH 45/73] quick port of pocket clearZ to trace clearZnew --- src/geo/polygon.js | 4 +++ src/kiri-mode/cam/ops.js | 60 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index d00fbbbee..2bd7580f6 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1002,6 +1002,10 @@ class Polygon { return this.z !== undefined ? this.z : this.points[i || 0]?.z || 0; } + minZ() { + return Math.min(...this.points.map(p => p.z)); + } + /** */ render(layer, color, recursive, open) { diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index 555e469fd..ab54b4f76 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -976,11 +976,12 @@ class OpTrace extends CamOp { } async slice(progress) { + const debug = false; let { op, state } = this; let { tool, rate, down, plunge, offset, offover, thru } = op; let { ov_conv } = op; let { settings, widget, addSlices, zThru, tabs, workarea } = state; - let { updateToolDiams, cutTabs, cutPolys, healPolys, color } = state; + let { updateToolDiams, cutTabs, cutPolys, healPolys, color, shadowAt } = state; let { process, stock } = settings; let { camStockClipTo } = process; if (state.isIndexed) { @@ -1042,6 +1043,59 @@ class OpTrace extends CamOp { .setLayer("trace follow", {line: color}, false) .addPolys(slice.camLines) } + + function clearZnew(polys, z, down) { + if (down) { + // adjust step down to a value <= down that + // ends on the lowest z specified + let diff = zTop - z; + down = diff / Math.ceil(diff / down); + } + let zs = down ? base.util.lerp(zTop, z, down) : [ z ]; + let zpro = 0, zinc = 1 / (polys.length * zs.length); + for (let poly of polys) { + // newPocket(); + for (let z of zs) { + let clip = [], shadow; + shadow = shadowAt(z); + POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0); + if (op.outline) { + POLY.clearInner(clip); + } + if (clip.length === 0) { + continue; + } + let slice = newSliceOut(z); + let count = 999; + slice.camTrace = { tool, rate, plunge }; + if (toolDiam) { + const offs = [ -toolDiam / 2, -toolOver ]; + POLY.offset(clip, offs, { + count, outs: slice.camLines = [], flat:true, z, minArea: 0 + }); + } else { + // when engraving with a 0 width tip + slice.camLines = clip; + } + if (tabs) { + slice.camLines = cutTabs(tabs, POLY.flatten(slice.camLines, null, true), z); + } else { + slice.camLines = POLY.flatten(slice.camLines, null, true); + } + POLY.setWinding(slice.camLines, cutdir, false); + slice.output() + .setLayer("trace", {line: color}, false) + .addPolys(slice.camLines) + if (debug && shadow) slice.output() + .setLayer("trace shadow", {line: 0xff8811}, false) + .addPolys(shadow) + progress(zpro, "trace"); + zpro += zinc; + addSlices(slice); + } + } + } + function clearZ(polys, z, down) { let zs = down ? base.util.lerp(zTop, z, down) : [ z ]; let nested = POLY.nest(polys); @@ -1176,7 +1230,7 @@ class OpTrace extends CamOp { const zbo = widget.track.top - widget.track.box.d; let zmap = {}; for (let poly of polys) { - let z = minZ(poly.getZ()); + let z = minZ(poly.minZ()); if (offover) { let pnew = POLY.offset([poly], -offover, { minArea: 0, open: true }); if (pnew) { @@ -1190,7 +1244,7 @@ class OpTrace extends CamOp { (zmap[z] = zmap[z] || []).appendAll(poly); } for (let [zv, polys] of Object.entries(zmap)) { - clearZ(polys, parseFloat(zv), down); + clearZnew(polys, parseFloat(zv), down); } } } From 05a9bca48544ac430ed2e31b7b4f7dadf097a2f6 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 21 Jun 2025 22:22:37 -0400 Subject: [PATCH 46/73] fix ctrl click to select planar matching lines --- src/geo/polygon.js | 13 +++++++++++++ src/kiri-mode/cam/client.js | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 2bd7580f6..5d5fa4783 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1006,6 +1006,19 @@ class Polygon { return Math.min(...this.points.map(p => p.z)); } + avgZ() { + return [...this.points.map(p => p.z)].reduce((a,v) => a+v) / this.points.length; + } + + /** + * @param {*} z value target + * @param {*} epsilon allowed Z variance + * @returns {boolean} true if all points Z within epsilon of value + */ + onZ(z, epsilon = 10e-4) { + return Math.max(...this.points.map(p => Math.abs(z - p.z))) < epsilon; + } + /** */ render(layer, color, recursive, open) { diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 539930154..e2ff19f05 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1128,8 +1128,9 @@ CAM.init = function(kiri, api) { if (ev.metaKey || ev.ctrlKey) { let { selected } = object; let { widget, poly } = object.trace; + let avgZ = poly.avgZ(); for (let add of widget.adds) { - if (add.trace && add.selected !== selected && add.trace.poly.getZ() === poly.getZ()) { + if (add.trace && add.selected !== selected && add.trace.poly.onZ(avgZ)) { func.traceToggle(add); } } From 9be674b61cba26a7ad95a8fc6da7dbbbe456ba45 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 21 Jun 2025 22:38:53 -0400 Subject: [PATCH 47/73] set visual state for trace and restore on trace done --- src/kiri-mode/cam/client.js | 3 ++- src/kiri/widget.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index e2ff19f05..368b2f3c7 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1008,7 +1008,6 @@ CAM.init = function(kiri, api) { CAM.traces((ids) => { api.hide.alert(alert); alert = api.show.alert("[esc] cancels trace editing"); - kiri.api.widgets.opacity(0.8); kiri.api.widgets.for(widget => { if (ids.indexOf(widget.id) >= 0) { unselectTraces(widget, true); @@ -1042,6 +1041,7 @@ CAM.init = function(kiri, api) { }); // ensure appropriate traces are toggled matching current record kiri.api.widgets.for(widget => { + widget.setVisualState({ opacity: 0.25 }); let areas = (poppedRec.areas[widget.id] || []); let stack = widget.trace_stack; stack.meshes.forEach(mesh => { @@ -1070,6 +1070,7 @@ CAM.init = function(kiri, api) { api.feature.hover = false; api.feature.hoverAdds = false; kiri.api.widgets.for(widget => { + widget.restoreVisualState(); if (widget.trace_stack) { widget.trace_stack.hide(); widget.adds.removeAll(widget.trace_stack.meshes); diff --git a/src/kiri/widget.js b/src/kiri/widget.js index 61eb8ddfe..8cb812bb5 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -355,6 +355,27 @@ class Widget { } } + getVisualState() { + return { + edges: this.outline ? true : false, + wires: this.wire ? true : false, + opacity: this.getMaterial().opacity + }; + } + + setVisualState({ edges, wires, opacity}) { + this.cache.vizstate = this.getVisualState(); + this.setEdges(edges ?? false); + this.setWireframe(wires ?? false); + this.setOpacity(opacity ?? 1); + } + + restoreVisualState() { + if (this.cache.vizstate) { + this.setVisualState(this.cache.vizstate); + } + } + /** * @param {number} value */ From fbbbf476c09e57121cb4b255aaf17cff8b56889c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 22 Jun 2025 01:21:49 -0400 Subject: [PATCH 48/73] fix trace toggle --- src/kiri-mode/cam/client.js | 42 +++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 368b2f3c7..7b476dfb9 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1097,17 +1097,15 @@ CAM.init = function(kiri, api) { color.r = colorSave.r; color.g = colorSave.g; color.b = colorSave.b; - lastTrace.position.z -= 0.01; } + lastTrace = null; if (data.type === 'platform') { - lastTrace = null; return; } if (!data.int.object.trace) { return; } lastTrace = data.int.object; - lastTrace.position.z += 0.01; if (lastTrace.selected) { let event = data.event; let target = event.target; @@ -1116,11 +1114,8 @@ CAM.init = function(kiri, api) { } let material = lastTrace.material[0] || lastTrace.material; let color = material.color; - let {r, g, b} = color; - material.colorSave = {r, g, b}; - color.r = 0; - color.g = 0; - color.b = 1; + material.colorSave = color.clone(); + color.setHex(isDark() ? 0x0066ff : 0x0000ff); }; func.traceHoverUp = function(int, ev) { if (!int) return; @@ -1149,23 +1144,17 @@ CAM.init = function(kiri, api) { let wlist = areas[widget.id] = areas[widget.id] || []; obj.selected = !obj.selected; if (!colorSave) { - colorSave = material.colorSave = { - r: color.r, - g: color.g, - b: color.b - }; + colorSave = material.colorSave = color.clone(); } if (obj.selected) { - obj.position.z += 0.01; - color.r = colorSave.r = 0.9; - color.g = colorSave.g = 0; - color.b = colorSave.b = 0.1; + color.setHex(isDark() ? 0xdd0011 : 0xff0033); + colorSave.r = color.r; + colorSave.g = color.g; + colorSave.b = color.b; if (!skip) wlist.push(poly._trace); } else { - obj.position.z -= 0.01; - color.r = colorSave.r = 0xaa/255; - color.g = colorSave.g = 0xaa/255; - color.b = colorSave.b = 0x55/255; + color.setHex(0xaaaa55); + colorSave.setHex(0xaaaa55); if (!skip) wlist.remove(poly._trace); } API.conf.save(); @@ -1194,9 +1183,6 @@ CAM.init = function(kiri, api) { func.hover = func.selectHolesHover; func.hoverUp = func.selectHolesHoverUp; - - - const widgets = kiri.api.widgets.all() /** * creates a mesh for a hole and adds it to a widget @@ -2162,16 +2148,18 @@ function validateTools(tools) { } } +function isDark() { return API.space.is_dark() }; + function addbox() { return FDM.addbox(...arguments)}; function delbox() { return FDM.delbox(...arguments)}; function boxColor() { - return API.space.is_dark() ? 0x00ddff : 0x0000dd; + return isDark() ? 0x00ddff : 0x0000dd; } function boxOpacity() { - return API.space.is_dark() ? 0.75 : 0.6; + return isDark() ? 0.75 : 0.6; } function animate() { @@ -2254,7 +2242,7 @@ function updateStock() { camStock.position.z = center.z; camStock.rotation.x = currentIndex || 0; camStock.lines.material.color = - new THREE.Color(API.space.is_dark() ? 0x555555 : 0xaaaaaa); + new THREE.Color(isDark() ? 0x555555 : 0xaaaaaa); } else if (camStock) { SPACE.world.remove(camStock); camStock = null; From 23ad260a2666af628bd07602c99fc1eadd2d96da Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 22 Jun 2025 01:30:52 -0400 Subject: [PATCH 49/73] remove old code / unused trace select mode --- src/kiri-mode/cam/client.js | 2 -- src/kiri-mode/cam/ops.js | 25 ------------------------- 2 files changed, 27 deletions(-) diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 7b476dfb9..a678f0381 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1700,13 +1700,11 @@ CAM.init = function(kiri, api) { dogbone: 'camTraceDogbone', revbone: 'camTraceDogbone', merge: 'camTraceMerge', - select: 'camTraceMode', ov_topz: 0, ov_botz: 0, ov_conv: '~camConventional', }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), - select: UC.newSelect(LANG.cc_sele_s, {title:LANG.cc_sele_l}, "select"), mode: UC.newSelect(LANG.cu_type_s, {title:LANG.cu_type_l}, "trace"), offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l, show:() => (poppedRec.mode === 'follow')}, "traceoff"), sep: UC.newBlank({class:"pop-sep"}), diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index ab54b4f76..b025431df 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -1043,7 +1043,6 @@ class OpTrace extends CamOp { .setLayer("trace follow", {line: color}, false) .addPolys(slice.camLines) } - function clearZnew(polys, z, down) { if (down) { // adjust step down to a value <= down that @@ -1095,30 +1094,6 @@ class OpTrace extends CamOp { } } } - - function clearZ(polys, z, down) { - let zs = down ? base.util.lerp(zTop, z, down) : [ z ]; - let nested = POLY.nest(polys); - for (let poly of nested) { - for (let z of zs) { - let slice = newSliceOut(z); - slice.camTrace = { tool, rate, plunge }; - POLY.offset([ poly ], [ -toolDiam/2, -toolOver ], { - count:999, outs: slice.camLines = [], flat:true, z, - minArea: 0 - }); - if (tabs) { - slice.camLines = cutTabs(tabs, POLY.flatten(slice.camLines, null, true), z); - } else { - slice.camLines = POLY.flatten(slice.camLines, null, true); - } - POLY.setWinding(slice.camLines, cutdir, false); - slice.output() - .setLayer("trace clear", {line: color}, false) - .addPolys(slice.camLines) - } - } - } function similar(v1, v2, epsilon = 0.01) { return Math.abs(v1-v2) <= epsilon; } From c6506f0f6083887dbe17f075cbc15a7412115982 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 22 Jun 2025 15:19:28 -0400 Subject: [PATCH 50/73] bump rev 4.2.0 --- package.json | 2 +- src/moto/license.js | 2 +- web/kiri/manifest.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 337d9075b..b2659a6fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.1.9", + "version": "4.2.0", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", diff --git a/src/moto/license.js b/src/moto/license.js index 060f58963..768c94d46 100644 --- a/src/moto/license.js +++ b/src/moto/license.js @@ -5,7 +5,7 @@ let terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: (is_self ? self : this).debug_version || "4.1.9" + VERSION: (is_self ? self : this).debug_version || "4.2.0" }; if (typeof(module) === 'object') { diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index 1f310125c..661bb6d5f 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.1.9", + "name": "Kiri:Moto 4.2.0", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/", From c728b878aceabed65df8340e9706199e0b09d83d Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 22 Jun 2025 16:13:00 -0400 Subject: [PATCH 51/73] catch/report async errors in slice --- src/kiri/widget.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kiri/widget.js b/src/kiri/widget.js index 8cb812bb5..79e95bb29 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -836,7 +836,8 @@ class Widget { let drv = driver[settings.mode.toUpperCase()]; if (drv) { - drv.slice(settings, widget, catchupdate, catchdone); + drv.slice(settings, widget, catchupdate, catchdone) + .catch(error => ondone(error)); } else { console.log('invalid mode: '+settings.mode); ondone('invalid mode: '+settings.mode); From 7b1f230ded0b8888beac0a7505b67461c91340c7 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 22 Jun 2025 22:09:02 -0400 Subject: [PATCH 52/73] fix numerous bounds issues with indexed stock fixes #340 fix state variable updates with indexex stock fix large tool animation fixes #352 --- src/kiri-mode/cam/animate2.js | 4 +- src/kiri-mode/cam/client.js | 16 ++-- src/kiri-mode/cam/ops.js | 12 ++- src/kiri-mode/cam/slice.js | 138 ++++++++++++++++++++-------------- src/kiri-mode/cam/tool.js | 2 +- src/kiri/slice.js | 3 - src/kiri/widget.js | 1 + 7 files changed, 104 insertions(+), 72 deletions(-) diff --git a/src/kiri-mode/cam/animate2.js b/src/kiri-mode/cam/animate2.js index 9cbc4dddc..c2d2618bb 100644 --- a/src/kiri-mode/cam/animate2.js +++ b/src/kiri-mode/cam/animate2.js @@ -540,7 +540,7 @@ kiri.load(() => { const sd = Math.sqrt(mx*mx + my*my + Math.min(1,mz*mz) + dr*dr); const moves = []; for (let i=0, x=lp.x, y=lp.y, z=lp.z; i { if (!pos) { throw `no pos @ ${index} of ${moves.length}`; } + const { dx, dy, dz } = pos; toolMove(pos); // console.log('renderMoves', {id, moves, seed}); let subs = 0; + if (dx || dy || dz < 0) for (let slice of stockSlices) { if (slice.bounds.intersectsBox(toolMesh.bounds)) { slice.subtractTool(toolMesh); diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index db3b83b8b..8edaee87e 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -474,13 +474,11 @@ CAM.init = function(kiri, api) { return; } let index = 0; - let indexing = false; for (let op of oplist) { if (op.type === '|') { break; } if (op.type === 'index' && !op.disabled) { - indexing = true; if (op.absolute) { index = op.degrees } else { @@ -2190,6 +2188,8 @@ function updateStock() { return; } + api.platform.update_bounds(); + const settings = API.conf.get(); const widgets = API.widgets.all(); @@ -2197,9 +2197,13 @@ function updateStock() { const { x, y, z, center } = stock; UI.func.animate.classList.add('disabled'); + if (camStock) { + SPACE.world.remove(camStock); + camStock = null; + } if (x && y && z) { UI.func.animate.classList.remove('disabled'); - if (!camStock) { + { let geo = new THREE.BoxGeometry(1, 1, 1); let mat = new THREE.MeshBasicMaterial({ color: 0x777777, @@ -2231,21 +2235,17 @@ function updateStock() { let lines = new THREE.LineSegments(ligeo, limat); camStock.lines = lines; camStock.add(lines); - SPACE.world.add(camStock); } + // fight z fighting in threejs camStock.scale.x = x + 0.005; camStock.scale.y = y + 0.005; camStock.scale.z = z + 0.005; camStock.position.x = center.x; camStock.position.y = center.y; camStock.position.z = center.z; - camStock.rotation.x = currentIndex || 0; camStock.lines.material.color = new THREE.Color(isDark() ? 0x555555 : 0xaaaaaa); - } else if (camStock) { - SPACE.world.remove(camStock); - camStock = null; } SPACE.world.remove(camZTop); diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index b025431df..9caff5b77 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -45,12 +45,17 @@ class OpIndex extends CamOp { super(state, op); } - slice() { + async slice() { let { op, state } = this; if (!state.isIndexed) { throw 'index op requires indexed stock'; } - this.degrees = state.setAxisIndex(op.degrees, op.absolute); + let { widget, updateSlicer, computeShadows, setAxisIndex } = state; + this.degrees = setAxisIndex(op.degrees, op.absolute); + // force recompute of topo + widget.topo = undefined; + updateSlicer(); + await computeShadows(); } prepare(ops, progress) { @@ -483,8 +488,7 @@ class OpOutline extends CamOp { async slice(progress) { let { op, state } = this; let { settings, widget, slicer, addSlices, tshadow, thruHoles, unsafe, color } = state; - let { updateToolDiams, tabs, cutTabs, cutPolys, workarea } = state; - let { zMax } = state; + let { updateToolDiams, tabs, cutTabs, cutPolys, workarea, zMax } = state; let { process, stock } = settings; if (op.down <= 0) { diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index ef4795f28..5843c98c4 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -32,7 +32,6 @@ const POLY = polygons; CAM.slice = async function(settings, widget, onupdate, ondone) { let proc = settings.process, sliceAll = widget.slices = [], - // slices = widget.slices = [], camOps = widget.camops = [], isIndexed = proc.camStockIndexed; @@ -48,8 +47,17 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { // allow recomputing later if widget or settings changes const var_compute = () => { - stock = settings.stock || {}; + let { camStockX, camStockY, camStockZ, camStockOffset } = proc; bounds = widget.getBoundingBox(); + stock = camStockOffset ? { + x: bounds.dim.x + camStockX, + y: bounds.dim.y + camStockY, + z: bounds.dim.z + camStockZ, + } : { + x: camStockX, + y: camStockY, + z: camStockZ + } track = widget.track; ({ camZTop, camZBottom, camZThru } = proc); wztop = track.top; @@ -73,7 +81,7 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { bottom_part = 0; bottom_stock = -bottom_gap; bottom_thru = zThru; - bottom_z = Math.max( + bottom_z = isIndexed ? zBottom : Math.max( (camZBottom ? bottom_stock + camZBottom : bottom_part) - bottom_thru, (camZBottom ? bottom_stock + camZBottom : bottom_stock - bottom_thru) ); @@ -145,15 +153,55 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { } let mark = Date.now(); - let slicer = new kiri.cam_slicer(widget); + let opList = []; + let opSum = 0; + let opTot = 0; + let shadows = {}; + let slicer; + let state = { + settings, + widget, + bounds, + tabs, + cutTabs, + cutPolys, + healPolys, + shadowAt, + slicer, + addSlices, + isIndexed, + setAxisIndex, + updateToolDiams, + updateSlicer, + computeShadows, + zBottom, + zThru, + ztOff, + zMax, + zTop, + unsafe, + color, + dark, + ops: opList + }; + let tracker = setSliceTracker({ rotation: 0 }); + + function updateSlicer() { + slicer = state.slicer = new kiri.cam_slicer(widget); + } + + async function computeShadows() { + shadows = {}; + await new CAM.OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => { + // console.log('reshadow', progress.round(3)); + }); + } function updateToolDiams(toolDiam) { minToolDiam = Math.min(minToolDiam, toolDiam); maxToolDiam = Math.max(maxToolDiam, toolDiam); } - let shadows = {}; - function shadowAt(z) { let cached = shadows[z]; if (cached) { @@ -216,44 +264,22 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { } } - let state = { - settings, - widget, - bounds, - tabs, - cutTabs, - cutPolys, - healPolys, - shadowAt, - slicer, - addSlices, - isIndexed, - setAxisIndex, - updateToolDiams, - zBottom, - zThru, - ztOff, - zMax, - zTop, - unsafe, - color, - dark, - }; - - let opList = [ - // silently preface op list with OpShadow - new CAM.OPS.shadow(state, { type: "shadow", silent: true }) - ]; - if (false) { opList.push(new CAM.OPS.xray(state, { type: "xray" })); } - let opSum = 0; - let opTot = opList.length ? opList.map(op => op.weight()).reduce((a,v) => a + v) : 0; + let activeOps = proc.ops.filter(op => !op.disabled); + + // silently preface op list with OpShadow + if (isIndexed) { + if (activeOps.length === 0 || activeOps[0].type !== 'index') + opList.push(new CAM.OPS.index(state, { type: "index", index: 0 })); + } else { + opList.push(new CAM.OPS.shadow(state, { type: "shadow", silent: true })); + } // determing # of steps and step weighting for progress bar - for (let op of proc.ops.filter(op => !op.disabled)) { + for (let op of activeOps) { if (op.type === '|') { break; } @@ -265,39 +291,41 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { } } - // give ops access to entire sequence - state.ops = opList; - // call slice() function on all ops in order - let tracker = setSliceTracker({ rotation: 0 }); setAxisIndex(); + updateSlicer(); for (let op of opList) { let weight = op.weight(); // apply operation override vars let workover = var_compute(); let valz = op.op; if (valz.ov_topz) { - workover.top_z = bottom_stock + valz.ov_topz; + workover.top_z = isIndexed ? valz.ov_topz : bottom_stock + valz.ov_topz; } if (valz.ov_botz) { - workover.bottom_z = bottom_stock + valz.ov_botz; + workover.bottom_z = isIndexed ? valz.ov_botz : bottom_stock + valz.ov_botz; workover.bottom_cut = Math.max(workover.bottom_z, -zThru); } - state.workarea = workover; + // state.workarea = workover; + Object.assign(state, { + zBottom, + zThru, + ztOff, + zMax, + zTop, + workarea: workover + }); + // console.log({ + // op, + // workover, + // bounds: structuredClone(bounds), + // stock: structuredClone(stock) + // }); await op.slice((progress, message) => { onupdate((opSum + (progress * weight)) / opTot, message || op.type()); }); + // update tracker rotation for next slice output() visualization tracker.rotation = isIndexed ? axisRotation : 0; - // setup new state when indexing the workspace - if (op.op.type === "index") { - widget.topo = undefined; - // let points = base.verticesToPoints(); - slicer = state.slicer = new kiri.cam_slicer(widget); - shadows = {}; - await new CAM.OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => { - // console.log('reshadow', progress.round(3)); - }); - } camOps.push(op); opSum += weight; } diff --git a/src/kiri-mode/cam/tool.js b/src/kiri-mode/cam/tool.js index 4ef46085d..a7a8d9fa9 100644 --- a/src/kiri-mode/cam/tool.js +++ b/src/kiri-mode/cam/tool.js @@ -137,7 +137,7 @@ class Tool { drill_tip_length = this.drillTipLength(), shaft_offset = this.fluteLength(), flute_diameter = this.fluteDiameter(), - shaft_diameter = this.shaftDiameter(), + shaft_diameter = Math.max(flute_diameter, this.shaftDiameter()), max_diameter = Math.max(flute_diameter, shaft_diameter), shaft_pix_float = max_diameter / resolution, shaft_pix_int = Math.round(shaft_pix_float), diff --git a/src/kiri/slice.js b/src/kiri/slice.js index 612bcde6b..c858c0824 100644 --- a/src/kiri/slice.js +++ b/src/kiri/slice.js @@ -7,11 +7,8 @@ gapp.register("kiri.slice", [], (root, exports) => { const { base, kiri } = root; -const { key, polygons } = base; -const { util } = base; const POLY = base.polygons; -const NOKEY = key.NONE; let tracker; diff --git a/src/kiri/widget.js b/src/kiri/widget.js index 79e95bb29..4500ff2d2 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -472,6 +472,7 @@ class Widget { let rad = deg * (Math.PI / 180); if (rad !== this.track.indexRad) { this.track.indexRad = rad; + this.setModified(); this._updateMeshPosition(); } } From 24e99495e92fb1933e1877c989050afead2fbe08 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 23 Jun 2025 09:22:04 -0400 Subject: [PATCH 53/73] handle drives with async or sync slice fns --- src/kiri/widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kiri/widget.js b/src/kiri/widget.js index 4500ff2d2..c55df9038 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -837,8 +837,8 @@ class Widget { let drv = driver[settings.mode.toUpperCase()]; if (drv) { - drv.slice(settings, widget, catchupdate, catchdone) - .catch(error => ondone(error)); + let promise = drv.slice(settings, widget, catchupdate, catchdone); + if (promise) promise.catch(error => ondone(error)); } else { console.log('invalid mode: '+settings.mode); ondone('invalid mode: '+settings.mode); From 3930c41ce76825ef1c56a5f768fbeda532e0879d Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 10:30:42 -0600 Subject: [PATCH 54/73] formatting updates, and fix depth option --- src/geo/polygons.js | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/geo/polygons.js b/src/geo/polygons.js index 4a56a5140..dcc59489a 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -330,6 +330,13 @@ function setContains(setA, poly) { return false; } +/** + * Flatten an array of polygons into a single array of polygons. + * @param {Polygon[]} polys - input array of polygons + * @param {Polygon[]} [to] - output array. if omitted, a new array will be created + * @param {boolean} [crush] - if true, remove the inner array after flattening + * @returns {Polygon[]} - the flattened array + */ function flatten(polys, to, crush) { to = to || []; for (let poly of polys) { @@ -613,14 +620,18 @@ function offset(polys, dist, opts = {}) { } } + let { + count = 1, + depth = 0, + fill = FillNonZero, + join = JoinType.jtMiter, + type = EndType.etClosedPolygon, + } = opts; + let orig = polys, - count = numOrDefault(opts.count, 1), - depth = numOrDefault(opts.depth, 0), clean = opts.clean !== false, simple = opts.simple !== false, - fill = numOrDefault(opts.fill, FillNonZero), - join = numOrDefault(opts.join, JoinType.jtMiter), - type = numOrDefault(opts.type, EndType.etClosedPolygon), + // if dist is array with values, shift out next offset offs = Array.isArray(dist) ? (dist.length > 1 ? dist.shift() : dist[0]) : dist, mina = numOrDefault(opts.minArea, 0.1), @@ -656,11 +667,7 @@ function offset(polys, dist, opts = {}) { // if specified, perform offset gap analysis if (opts.gaps && polys.length) { - let oneg = offset(polys, -offs, { - fill: opts.fill, join: opts.join, type: opts.type, z: opts.z, minArea: mina - }); let suba = []; - let diff = subtract(orig, oneg, suba, null, zed); opts.gaps.append(suba, opts.flat); } @@ -671,6 +678,14 @@ function offset(polys, dist, opts = {}) { // if specified, perform up to *count* successive offsets if (polys.length) { + // decrement count, increment depth + opts.count = --count; + opts.depth = ++depth; + + //set poly depth + polys.forEach(p => { + p.depth = depth; + }); // ensure opts has offset accumulator array opts.outs = opts.outs || []; // store polys in accumulator @@ -681,9 +696,6 @@ function offset(polys, dist, opts = {}) { } // check for more offsets if (count > 1) { - // decrement count, increment depth - opts.count = count - 1; - opts.depth = depth + 1; // call next offset offset(polys, dist, opts); } From 8c3e1abebf508248f9f52ad2597be903f61c7350 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 11:14:33 -0600 Subject: [PATCH 55/73] set arc defaults --- src/cli/kiri-cam-process.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/kiri-cam-process.json b/src/cli/kiri-cam-process.json index 3a9bd700d..7055d6292 100644 --- a/src/cli/kiri-cam-process.json +++ b/src/cli/kiri-cam-process.json @@ -97,7 +97,8 @@ "top": false }], "camTrueShadow": false, - "camArcTolerance": 0.15, + "camArcTolerance": 0.006, + "camArcResolution": 5, "camDrillMark": true, "camPocketSpindle": 1000, "camPocketTool": 1665454124874, From 3ba5ab7a76bf7ab9574f36d52dab78fcbf020379 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 11:14:49 -0600 Subject: [PATCH 56/73] set cam arc defaults --- src/kiri/conf.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kiri/conf.js b/src/kiri/conf.js index c7e2d37e6..ee6f73549 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -578,7 +578,8 @@ const conf = exports({ outputInvertY: false, camExpertFast: false, camTrueShadow: false, - camArcTolerance: 0.15, + camArcTolerance: 0.006, + camArcResolution: 5, camForceZMax: false, camFirstZMax: false, camToolInit: true, From f7997de9e44ef8f0e34fb9ad63573122777a4938 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 11:15:05 -0600 Subject: [PATCH 57/73] add arc resolution option --- src/kiri/init.js | 2 ++ web/kiri/lang/en.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/kiri/init.js b/src/kiri/init.js index da39d075d..d489ed21b 100644 --- a/src/kiri/init.js +++ b/src/kiri/init.js @@ -1281,7 +1281,9 @@ gapp.register("kiri.init", (root, exports) => { _____: newGroup(LANG.op_xprt_s, $('cam-expert'), { group:"cam_expert", modes:CAM, marker: false, driven, separator }), camExpertFast: newBoolean(LANG.cx_fast_s, onBooleanClick, {title:LANG.cx_fast_l, show: () => !ui.camTrueShadow.checked }), camTrueShadow: newBoolean(LANG.cx_true_s, onBooleanClick, {title:LANG.cx_true_l, show: () => !ui.camExpertFast.checked }), + separator: newBlank({ class:"set-sep", driven }), camArcTolerance: newInput(LANG.cx_arct_s, {title:LANG.cx_arct_l, convert:toFloat, units, bound:bound(0,100)}), + camArcResolution: newInput(LANG.cx_arcr_s, {title:LANG.cx_arcr_l, convert:toFloat, bound:bound(0,180), show:() => ui.camArcTolerance.value > 0}), /** LASER/DRAG/WJET/WEDM cut tool Settings */ diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index e041035b1..eefe5848a 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -858,6 +858,8 @@ self.kiri.lang['en-us'] = { cx_true_l: ["computationally correct shadow","will be slower but produce","better cuts for complex parts"], cx_arct_s: "arc tolerance", cx_arct_l: ["convert cicrular paths to arcs;","center point drift tolerance","when matching arc points","consider values around 0.15","in mm, or 0.006 inches;","High values may cause","unexpected behavior","0 to disable"], + cx_arcr_s: "arc resolution", + cx_arcr_l: ["change in line direction","aceptible in an arc","consider values around 5","High values may cause","unexpected behavior",], // FDM GCODE ag_menu: "gcode", From 6fb589e63d9107efb03e8be560f89807d3c0990a Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 11:16:01 -0600 Subject: [PATCH 58/73] remove console logs --- src/kiri-mode/cam/prepare.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 8c0019b6f..21b9b8860 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -98,6 +98,7 @@ function prepEach(widget, settings, print, firstPoint, update) { depthFirst = process.camDepthFirst, engageFactor = process.camFullEngage, arcTolerance = process.camArcTolerance, + arcRes = toRadians(process.camArcResolution), tolerance = 0, drillDown = 0, drillLift = 0, @@ -576,12 +577,11 @@ function prepEach(widget, settings, print, firstPoint, update) { } } /** - * Ease down along the polygonal path. - * - * 1. Travel from fromPoint to closest point on polygon, to rampZ above that that point, - * 2. ease-down starts, following the polygonal path, decreasing Z at a fixed slope until target Z is hit, - */ - + * Ease down along the polygonal path. + * + * 1. Travel from fromPoint to closest point on polygon, to rampZ above that that point, + * 2. ease-down starts, following the polygonal path, decreasing Z at a fixed slope until target Z is hit, + */ function generateEaseDown(fn,poly, fromPoint, degrees = 45){ let index = poly.findClosestPointTo(fromPoint).index, fromZ = fromPoint.z, @@ -736,17 +736,16 @@ function prepEach(widget, settings, print, firstPoint, update) { * the polygon if that point is not the current position. * * @param {Polygon} poly - the polygon to output - * @param {number} index - the index of the polygon in its containing array - * @param {number} count - the total number of polygons in the array + * @param {number} index - unused + * @param {number} count - 1 to set engage factor * @param {Point} fromPoint - the point to rapid move from * @param {Object} camOutOpts - optional parameters to pass to camOut * @returns {Point} - the last point of the polygon */ function polyEmit(poly, index, count, fromPoint) { - //arcTolerance is the allowable distance between circle centers - let arcRes = toRadians(2), //5 degs max - arcMax = Infinity, // no max arc radius - lineTolerance = 0.001; // do not consider points under 0.001mm for lines + + let arcMax = Infinity, // no max arc radius + lineTolerance = 0.001; // do not consider points under 0.001mm for arcs fromPoint = fromPoint || printPoint; let arcQ = []; @@ -756,7 +755,7 @@ function prepEach(widget, settings, print, firstPoint, update) { let lastPoint = closest.point; let startIndex = closest.index; - let circleComplete = false; + // console.log({poly, index, count, fromPoint,startIndex}) // scale speed of first cutting poly since it engages the full bit let scale = ((isRough || isPocket) && count === 1) ? engageFactor : 1; @@ -772,15 +771,15 @@ function prepEach(widget, settings, print, firstPoint, update) { startIndex = last; } - console.log(poly,poly.isClosed(),startIndex) + // console.log(poly,poly.isClosed(),startIndex) //A is first point of segment, B is last poly.forEachSegment( ( pointA, pointB, indexA, indexB) => { // if(offset == 0) console.log("forEachPoint",point,pidx,points) + // console.log({pointA, pointB, indexA, indexB,startIndex}) if(indexA == startIndex){ camOut(pointA.clone(), 0, {factor:engageFactor}); // if first point, move to and call export function - console.log("start") quePush(pointA); } lastPoint = arcExport(pointB, pointA); @@ -895,7 +894,7 @@ function prepEach(widget, settings, print, firstPoint, update) { } } else { // if dist to small, output as a cut - console.trace('point too small', point,lastp,dist); + // console.trace('point too small', point,lastp,dist); camOut(point, 1); } } else { @@ -936,6 +935,8 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum / cl, ) + console.log("draining") + if(arcQ.length == poly.points.length ){ //if is a circle // generate circle @@ -945,7 +946,6 @@ function prepEach(widget, settings, print, firstPoint, update) { camOut(from,1); camOut(from,gc,{ center:center.sub(from), clockwise, arcPoints}); // lastPoint = to.clone(); - circleComplete = true }else{ //if a non-circle arc let arcPoints = arcToPath( from, to, arcPreviewRes,{ clockwise,center}) From 453c201255048be0f9ec188d38f9888ba6618601 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 24 Jun 2025 13:40:31 -0400 Subject: [PATCH 59/73] fix kiri engine cam var settings --- src/kiri-mode/cam/export.js | 2 +- src/kiri-mode/cam/prepare.js | 6 +++--- src/kiri-run/engine.js | 12 +++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 6f825ab17..a8772cfdf 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -371,7 +371,7 @@ CAM.export = function(print, online) { append(`; Generated by Kiri:Moto ${kiri.version}`); append(`; ${new Date().toString()}`); filterEmit(["; Bed left:{left} right:{right} top:{top} bottom:{bottom}"], consts); - append(`; Stock X:${stock.x.round(2)} Y:${stock.y.round(2)} Z:${stock.z.round(2)}`); + append(`; Stock X:${stock.x?.round(2)} Y:${stock.y?.round(2)} Z:${stock.z?.round(2)}`); append(`; Target: ${settings.filter[settings.mode]}`); append("; --- process ---"); for (let pk in spro) { diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index e5c9cbe9d..8e141aae2 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -79,12 +79,12 @@ function prepEach(widget, settings, print, firstPoint, update) { bounds = widget.getBoundingBox(), boundsz = isIndexed ? stock.z / 2 : bounds.max.z + ztOff, zadd = !isIndexed ? stock.z - boundsz : alignTop ? outerz - boundsz : 0, - zmax = outerz + zclear + process.camOriginOffZ, + zmax = outerz + zclear + (process.camOriginOffZ || 0), wmpos = widget.track.pos, wmx = wmpos.x, wmy = wmpos.y, - originx = (startCenter ? 0 : -stock.x / 2) + process.camOriginOffX, - originy = (startCenter ? 0 : -stock.y / 2) + process.camOriginOffY, + originx = (startCenter ? 0 : -stock.x / 2) + (process.camOriginOffX || 0), + originy = (startCenter ? 0 : -stock.y / 2) + (process.camOriginOffY || 0), origin = newPoint(originx + wmx, originy + wmy, zmax), output = print.output, easeDown = process.camEaseDown, diff --git a/src/kiri-run/engine.js b/src/kiri-run/engine.js index 17e9ed077..d0350a17f 100644 --- a/src/kiri-run/engine.js +++ b/src/kiri-run/engine.js @@ -8,6 +8,7 @@ // dep: kiri.client // dep: kiri.widget // use: add.three +// use: add.array gapp.register("kiri-run.engine", [], (root, exports) => { const { kiri } = root; @@ -105,10 +106,19 @@ class Engine { } setStock(stock) { - this.settings.stock = stock; + let { settings } = this; + let { process } = settings; + settings.stock = stock; + process.camStockX = stock.x; + process.camStockY = stock.y; + process.camStockZ = stock.z; return this; } + setOrigin(x, y, z) { + this.settings.origin = { x, y, z }; + return this; + } moveTo(x, y, z) { this.widget.move(x, y, z, true); From 6bb1df2bf063802dfa25671b69861dd4bdf7b075 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 12:10:21 -0600 Subject: [PATCH 60/73] revert changes --- src/geo/polygons.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/geo/polygons.js b/src/geo/polygons.js index dcc59489a..86d866a62 100644 --- a/src/geo/polygons.js +++ b/src/geo/polygons.js @@ -620,18 +620,14 @@ function offset(polys, dist, opts = {}) { } } - let { - count = 1, - depth = 0, - fill = FillNonZero, - join = JoinType.jtMiter, - type = EndType.etClosedPolygon, - } = opts; - let orig = polys, + count = numOrDefault(opts.count, 1), + depth = numOrDefault(opts.depth, 0), clean = opts.clean !== false, simple = opts.simple !== false, - + fill = numOrDefault(opts.fill, FillNonZero), + join = numOrDefault(opts.join, JoinType.jtMiter), + type = numOrDefault(opts.type, EndType.etClosedPolygon), // if dist is array with values, shift out next offset offs = Array.isArray(dist) ? (dist.length > 1 ? dist.shift() : dist[0]) : dist, mina = numOrDefault(opts.minArea, 0.1), @@ -667,7 +663,11 @@ function offset(polys, dist, opts = {}) { // if specified, perform offset gap analysis if (opts.gaps && polys.length) { + let oneg = offset(polys, -offs, { + fill: opts.fill, join: opts.join, type: opts.type, z: opts.z, minArea: mina + }); let suba = []; + let diff = subtract(orig, oneg, suba, null, zed); opts.gaps.append(suba, opts.flat); } @@ -678,14 +678,6 @@ function offset(polys, dist, opts = {}) { // if specified, perform up to *count* successive offsets if (polys.length) { - // decrement count, increment depth - opts.count = --count; - opts.depth = ++depth; - - //set poly depth - polys.forEach(p => { - p.depth = depth; - }); // ensure opts has offset accumulator array opts.outs = opts.outs || []; // store polys in accumulator @@ -696,6 +688,9 @@ function offset(polys, dist, opts = {}) { } // check for more offsets if (count > 1) { + // decrement count, increment depth + opts.count = count - 1; + opts.depth = depth + 1; // call next offset offset(polys, dist, opts); } From 2a4b1f96bf43224b06745ccbdd3888056eb75940 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Tue, 24 Jun 2025 12:19:02 -0600 Subject: [PATCH 61/73] comment out all console logs --- src/kiri-mode/cam/prepare.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 21b9b8860..8971655b9 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -479,7 +479,7 @@ function prepEach(widget, settings, print, firstPoint, update) { modifier = threshold / absDeltaZ; if (synthPlunge && threshold && modifier && deltaXY > tolerance) { // use modifier to speed up long XY move plunge rates - console.log('modifier', modifier); + // console.log('modifier', modifier); rate = Math.round(plungeRate + ((feedRate - plungeRate) * modifier)); cut = 1; } else { @@ -824,13 +824,13 @@ function prepEach(widget, settings, print, firstPoint, update) { } else { radFault = true; - console.log("too much angle") + // console.log("too much angle") } if (cc) { if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) { - console.log({cc, e1, e2, e3}); + // console.log({cc, e1, e2, e3}); } if (arcQ.length === 3) { arcQ.center = [ cc ]; @@ -935,7 +935,7 @@ function prepEach(widget, settings, print, firstPoint, update) { arcQ.ySum / cl, ) - console.log("draining") + // console.log("draining") if(arcQ.length == poly.points.length ){ //if is a circle @@ -1061,7 +1061,7 @@ function prepEach(widget, settings, print, firstPoint, update) { print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool); } } - console.log("prepare output", newOutput); + // console.log("prepare output", newOutput); // replace output single flattened layer with all points print.output = newOutput; return printPoint; From 797da325a653892a94f1382695d701b0e45d6254 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 24 Jun 2025 22:03:09 -0400 Subject: [PATCH 62/73] tweak to improve thru hole handling in roughing --- src/kiri-mode/cam/ops.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index 742007907..3d3b98a38 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -307,7 +307,7 @@ class OpRough extends CamOp { thruHoles.forEach(hole => { shadow = shadow.map(p => { if (p.isEquivalent(hole)) { - let po = POLY.offset([p], -(toolDiam / 2 + roughLeave + 0.01)); + let po = POLY.offset([p], -(toolDiam / 2 + roughLeave + 0.05)); return po ? po[0] : undefined; } else { return p; From 82e2742b8dc280a806a3c6f4decfceb67ee3d57a Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 27 Jun 2025 01:02:12 -0400 Subject: [PATCH 63/73] add trace re-contouring when poly z varies, offset present, no step down --- src/kiri-mode/cam/driver.js | 11 +++++------ src/kiri-mode/cam/ops.js | 11 +++++++++++ src/kiri-mode/cam/slice.js | 37 ++++++++++++++++++++++++------------- src/kiri/conf.js | 2 +- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/kiri-mode/cam/driver.js b/src/kiri-mode/cam/driver.js index 44220e296..9d92527ef 100644 --- a/src/kiri-mode/cam/driver.js +++ b/src/kiri-mode/cam/driver.js @@ -82,7 +82,6 @@ kiri.load(api => { }); }; - CAM.holes = function(indiv,rec,onProgress,onDone) { kiri.client.sync(); const settings = api.conf.get(); @@ -93,13 +92,13 @@ kiri.load(api => { //if a progress message, onProgress(out.progress,out.msg) }else{ - api.hide.alert(alert) - onDone(out) - res(out) + api.hide.alert(alert); + onDone(out); + res(out); } }); }) - } + }; } if (kiri.worker) { @@ -167,7 +166,7 @@ kiri.load(api => { const { settings, indiv, rec } = data; const widgets = Object.values(kiri.worker.cache); const fresh = []; - + for (let [i,widget] of widgets.entries() ) { if (await CAM.holes(settings, widget, indiv, rec, ( prog, msg )=>{ send.data({progress: (i/widgets.length)+(prog/widgets.length),msg})} diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index 63c845540..28f6a546f 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -995,6 +995,8 @@ class OpTrace extends CamOp { let traceOffset = camTool.traceOffset() let cutdir = ov_conv; let polys = []; + let reContour = false; + 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); @@ -1008,6 +1010,12 @@ class OpTrace extends CamOp { let poly = newPolygon().fromArray(arr); POLY.setWinding([ poly ], cutdir, false); polys.push(poly); + let zs = poly.points.map(p => p.z); + let min = Math.min(...zs); + let max = Math.max(...zs); + if (max - min > 0.0001 && canRecontour) { + reContour = true; + } } if (false) newSliceOut(0).output() .setLayer("polys", {line: 0xaaaa00}, false) @@ -1036,6 +1044,9 @@ class OpTrace extends CamOp { if (camStockClipTo && stockRect) { slice.camLines = cutPolys([stockRect], slice.camLines, z, true); } + if (reContour) { + state.contourPolys(widget, slice.camLines); + } slice.output() .setLayer("trace follow", {line: color}, false) .addPolys(slice.camLines) diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index 5843c98c4..ab743b78f 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -165,6 +165,7 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { tabs, cutTabs, cutPolys, + contourPolys, healPolys, shadowAt, slicer, @@ -365,8 +366,6 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { ondone(); }; - - CAM.addDogbones = function(poly, dist, reverse) { if (Array.isArray(poly)) { return poly.forEach(p => CAM.addDogbones(p, dist)); @@ -512,16 +511,16 @@ CAM.traces = async function(settings, widget, single) { return true; }; - /** - * Generate a list of holes in the model based on the given diameter. - * - * @param {Object} settings - settings object - * @param {Object} widget - widget object - * @param {boolean} individual - if true, drill holes individually - * @param {Object} rec - DrillOp record - * @param {Function} onProgress - callback function to report progress - * @returns {Array} list of hole centers as objects with `x`, `y`, `z`, `depth`, and `selected` properties. - */ +/** + * Generate a list of holes in the model based on the given diameter. + * + * @param {Object} settings - settings object + * @param {Object} widget - widget object + * @param {boolean} individual - if true, drill holes individually + * @param {Object} rec - DrillOp record + * @param {Function} onProgress - callback function to report progress + * @returns {Array} list of hole centers as objects with `x`, `y`, `z`, `depth`, and `selected` properties. + */ CAM.holes = async function(settings, widget, individual, rec,onProgress) { let {tool,mark,precision} = rec //TODO: display some visual difference if mark is selected @@ -603,7 +602,6 @@ CAM.holes = async function(settings, widget, individual, rec,onProgress) { // //if on the same xy point, if (dist <= centerDiff ) { - // console.log("overlap",center,circle); circle.overlapping.push(center); // if overlapping, don't add and continue @@ -669,6 +667,19 @@ function cutPolys(polys, offset, z, inter) { return healPolys(noff); } +function contourPolys(widget, polys) { + const raycaster = new THREE.Raycaster(); + raycaster.ray.direction.set(0, 0, -1); // ray pointing down Z + for (let poly of polys) { + for (let point of poly.points) { + raycaster.ray.origin.set(point.x, point.y, 10000); + const intersects = raycaster.intersectObject(widget.mesh, true); + const firstHit = intersects[0] || null; + point.z = firstHit.point.z; + } + } +} + function healPolys(noff) { if (noff.length > 1) { let heal = 0; diff --git a/src/kiri/conf.js b/src/kiri/conf.js index ec097ffb8..abea4b436 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -493,7 +493,7 @@ const conf = exports({ camTracePlunge: 200, camTraceOffOver: 0, camTraceDogbone: false, - camTraceMerge: true, + camTraceMerge: false, camTraceLines: false, camTraceZTop: 0, camTraceZBottom: 0, From d133a4f07686f1ad76635c820f5bfd2eb64e1ab2 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 27 Jun 2025 01:09:58 -0400 Subject: [PATCH 64/73] raycaster optimized b/c no children in widget mesh --- src/kiri-mode/cam/slice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index ab743b78f..9569a0663 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -673,7 +673,7 @@ function contourPolys(widget, polys) { for (let poly of polys) { for (let point of poly.points) { raycaster.ray.origin.set(point.x, point.y, 10000); - const intersects = raycaster.intersectObject(widget.mesh, true); + const intersects = raycaster.intersectObject(widget.mesh, false); const firstHit = intersects[0] || null; point.z = firstHit.point.z; } From ed5d463e0764beb0155301d816566b7069b822e9 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 27 Jun 2025 01:21:01 -0400 Subject: [PATCH 65/73] add TrueNAS install instructions in readme --- readme.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/readme.md b/readme.md index b810a2c8e..9c7d24978 100644 --- a/readme.md +++ b/readme.md @@ -92,6 +92,22 @@ npm run setup npm run dev ``` +## TrueNAS Via YAML + +`Apps > Discover Apps > Install via YAML` + +``` +services: + kirimoto: + build: + context: https://github.com/GridSpace/grid-apps.git#refs/tags/latest + dockerfile: ./src/dock/Dockerfile + ports: + - "8080:8080" +``` + +## For any default install... + Then open a browser to [localhost:8080/kiri](http://localhost:8080/kiri) # Windows Developers From 8292bac7911ee90ca9d1f35a51bd70b3da7ca9de Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 27 Jun 2025 13:32:17 -0400 Subject: [PATCH 66/73] trace uses system edgeangle --- src/kiri-mode/cam/slice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index 9569a0663..f7c79fdff 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -412,7 +412,7 @@ CAM.traces = async function(settings, widget, single) { } // --- points → line segments --- - let edges = new THREE.EdgesGeometry(widget.mesh.geometry, 20); + let edges = new THREE.EdgesGeometry(widget.mesh.geometry, settings.controller.edgeangle ?? 20); let array = edges.attributes.position.array; let pcache = {}; let points = new Array(2); From 87137e44d1dbbeb4cd2291a840dea61ddaab9cc4 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 27 Jun 2025 16:49:04 -0400 Subject: [PATCH 67/73] add maybe future code for loading workers from blobs --- app.js | 44 +++++++++++++++++++++++--------------------- src/kiri/client.js | 4 ++++ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app.js b/app.js index b42b158f0..ccaea7709 100644 --- a/app.js +++ b/app.js @@ -681,11 +681,12 @@ function generateDevices() { function prepareScripts() { generateDevices(); for (let key of Object.keys(script)) { - code[key] = concatCode(script[key]); + code[key] = concatCode(key); } } -function concatCode(array) { +function concatCode(key) { + let array = script[key]; let code = []; let direct = array.filter(f => f.charAt(0) !== '@'); let inject = array.filter(f => f.charAt(0) === '@').map(f => f.substring(1)); @@ -695,11 +696,11 @@ function concatCode(array) { // in debug mode, the script should load dependent // scripts instead of serving a complete bundle if (debug) { - const code = [ + code.push(...[ oversion ? `self.debug_version='${oversion}';self.enable_service=${serviceWorker};` : '', 'self.debug=true;', '(function() { let load = [ ' - ]; + ]); direct.forEach(file => { const vers = cachever[file] || oversion || dversion || version; code.push(`"/${file.replace(/\\/g,'/')}?${vers}",`); @@ -719,24 +720,25 @@ function concatCode(array) { inject.forEach(key => { code.push(synth[key]); }); - return code.join('\n'); - } - - direct.forEach(file => { - let cached = getCachedFile(file, path => { - return minify(PATH.join(dir,file)); + code = code.join('\n'); + } else { + direct.forEach(file => { + let cached = getCachedFile(file, path => { + return minify(PATH.join(dir,file)); + }); + if (oversion) { + cached = `self.debug_version='${oversion}';self.enable_service=${serviceWorker};` + cached; + } + code.push(cached); }); - if (oversion) { - cached = `self.debug_version='${oversion}';self.enable_service=${serviceWorker};` + cached; - } - code.push(cached); - }); - - inject.forEach(key => { - code.push(synth[key]); - }); - - return code.join(''); + inject.forEach(key => { + code.push(synth[key]); + }); + code = code.join(''); + } + // console.log(`SETTING synth[${key}]`); + // synth[key] = `self.${key} = "${Buffer.from(code).toString('base64')}";\n`; + return code; } function getCachedFile(file, fn) { diff --git a/src/kiri/client.js b/src/kiri/client.js index 82157c719..b9e6e2bb8 100644 --- a/src/kiri/client.js +++ b/src/kiri/client.js @@ -70,6 +70,10 @@ const client = exports({ newWorker() { if (self.createWorker) { return self.createWorker(); + } else if (self.kiri_work) { + const work = atob(self.kiri_work); + const blob = new Blob([ work ], { type: 'application/javascript' }); + return new Worker(URL.createObjectURL(blob)); } else { let _ = debug ? '_' : ''; return new Worker(`/code/kiri_work.js?${_}${gapp.version}`); From bc0f51cc7b9110dfc4584a26d75d835894a2d4b7 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 27 Jun 2025 17:25:18 -0400 Subject: [PATCH 68/73] experimentally load worker into engine as base64 blob payload --- app.js | 15 ++++++++------- src/geo/csg.js | 6 +++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app.js b/app.js index ccaea7709..e2cceb2e0 100644 --- a/app.js +++ b/app.js @@ -495,6 +495,7 @@ const script = { "&main/kiri", ], engine : [ + "@kiri_work", "&kiri-run/engine", "&main/kiri", ], @@ -696,6 +697,9 @@ function concatCode(key) { // in debug mode, the script should load dependent // scripts instead of serving a complete bundle if (debug) { + inject.forEach(key => { + code.push(synth[key]); + }); code.push(...[ oversion ? `self.debug_version='${oversion}';self.enable_service=${serviceWorker};` : '', 'self.debug=true;', @@ -709,6 +713,7 @@ function concatCode(key) { ']; function load_next() {', 'let file = load.shift();', 'if (!file) return;', + // 'console.log("loading", file);', 'if (!self.document) { importScripts(file); return load_next() }', 'let s = document.createElement("script");', 's.type = "text/javascript";', @@ -717,11 +722,11 @@ function concatCode(key) { 'document.head.appendChild(s);', '} load_next(); })();' ].join('\n')); + code = code.join('\n'); + } else { inject.forEach(key => { code.push(synth[key]); }); - code = code.join('\n'); - } else { direct.forEach(file => { let cached = getCachedFile(file, path => { return minify(PATH.join(dir,file)); @@ -731,13 +736,9 @@ function concatCode(key) { } code.push(cached); }); - inject.forEach(key => { - code.push(synth[key]); - }); code = code.join(''); + synth[key] = `self.${key} = "${Buffer.from(code).toString('base64')}";\n`; } - // console.log(`SETTING synth[${key}]`); - // synth[key] = `self.${key} = "${Buffer.from(code).toString('base64')}";\n`; return code; } diff --git a/src/geo/csg.js b/src/geo/csg.js index 2a098c269..e93fa19de 100644 --- a/src/geo/csg.js +++ b/src/geo/csg.js @@ -126,11 +126,15 @@ let Instance; ext.manifold.then(mod => { mod.default({ - locateFile() { return "/wasm/manifold.wasm" } + locateFile(a,b,c) { + return "/wasm/manifold.wasm"; + } }).then(inst => { inst.setup(); Instance = inst; CSG.Instance = () => { return Instance }; + }).catch(error => { + console.log('manifold load error', error); }); }); From f1f584bbd1e4db1118ce15984cfd8a5021271012 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sat, 28 Jun 2025 10:51:58 -0600 Subject: [PATCH 69/73] first pass at helical pop op --- src/kiri-mode/cam/client.js | 28 ++++++++++++++++++++++++++++ src/kiri/consts.js | 8 ++++++++ web/kiri/index.html | 1 + 3 files changed, 37 insertions(+) diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 8edaee87e..a79b2d196 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -337,6 +337,7 @@ CAM.init = function(kiri, api) { case "drill": return func.opAddDrill(); case "trace": return func.opAddTrace(); case "pocket": return func.opAddPocket(); + case "helical": return func.opAddHelical(); case "flip": // only one flip op permitted for (let op of current.process.ops) { @@ -419,6 +420,10 @@ CAM.init = function(kiri, api) { func.opAdd(popOp.flip.new()); }; + func.opAddHelical = () => { + func.opAdd(popOp.helical.new()); + }; + // TAB/TRACE BUTTON HANDLERS api.event.on("button.click", target => { let process = API.conf.get().process; @@ -1830,6 +1835,29 @@ CAM.init = function(kiri, api) { sep: UC.newBlank({class:"pop-sep"}), thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}), }; + + createPopOp('helical', { + tool: 'camHelicalTool', + mode: 'camHelicalMode', + offset: 'camHelicalOffset', + spindle: 'camHelicalSpindle', + down: 'camHelicalDown', + rate: 'camHelicalDownSpeed', + feed: 'camHelicalSpeed', + thru: 'camRegisterThru' + }).inputs = { + tool: UC.newSelect(LANG.cc_tool, {}, "tools"), + mode: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l,}, "helicalmode"), + offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l,}, "helicaloff"), + sep: UC.newBlank({class:"pop-sep"}), + spindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, show:hasSpindle}), + rate: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toInt, units:true}), + feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toInt, units:true}), + sep: UC.newBlank({class:"pop-sep"}), + down: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, units:true}), + sep: UC.newBlank({class:"pop-sep"}), + thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}), + }; createPopOp('flip', { axis: 'camFlipAxis', diff --git a/src/kiri/consts.js b/src/kiri/consts.js index 5ba2d52f6..07ae3e123 100644 --- a/src/kiri/consts.js +++ b/src/kiri/consts.js @@ -89,11 +89,19 @@ const LISTS = { { name: "follow" }, { name: "clear" } ], + helicalmode: [ + { name: "clear" }, + { name: "threadCutting" } + ], traceoff: [ { name: "none" }, { name: "inside" }, { name: "outside" } ], + helicaloff:[ + { name: "inside" }, + { name: "outside" }, + ], zanchor: [ { name: "top" }, { name: "middle" }, diff --git a/web/kiri/index.html b/web/kiri/index.html index 3eb610ab5..1db287da7 100644 --- a/web/kiri/index.html +++ b/web/kiri/index.html @@ -435,6 +435,7 @@
outline
contour
pocket
+
helical
From 1fcc2405a099fceb50409c2e00f470c779a01744 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sun, 29 Jun 2025 12:21:14 -0400 Subject: [PATCH 70/73] fix open but really closed traced, prevent slice on null stock --- src/kiri-mode/cam/slice.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index f7c79fdff..7e97fc32d 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -134,6 +134,10 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { return error('no processes specified'); } + if (stock.x === 0 || stock.y === 0 || stock.z === 0) { + return error("one or more stock dimensions is zero
offset stock or set to non zero value"); + } + if (stock.x && stock.y && stock.z && !isIndexed) { if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) { return error('stock X too small for part. resize stock or use offset stock'); @@ -681,6 +685,12 @@ function contourPolys(widget, polys) { } function healPolys(noff) { + for (let p of noff) { + if (p.appearsClosed()) { + p.points.pop(); + p.setClosed(); + } + } if (noff.length > 1) { let heal = 0; // heal/rejoin open segments that share endpoints From b014d694b2114feda649904b9807fd8044127009 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Sun, 29 Jun 2025 12:45:13 -0600 Subject: [PATCH 71/73] added helical to UIs --- src/cli/kiri-cam-process.json | 10 ++++++++++ src/kiri-mode/cam/client.js | 29 +++++++++++++++++++---------- src/kiri/conf.js | 10 ++++++++++ src/kiri/consts.js | 5 +---- src/kiri/ui.js | 13 +++++++++++++ web/kiri/lang/en.js | 15 +++++++++++++++ 6 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/cli/kiri-cam-process.json b/src/cli/kiri-cam-process.json index 7055d6292..aafaa030e 100644 --- a/src/cli/kiri-cam-process.json +++ b/src/cli/kiri-cam-process.json @@ -54,6 +54,16 @@ "camDrillLift": 2, "camDrillingOn": false, "camRegisterSpeed": 1000, + "camHelicalTool": 1000, + "camHelicalSpindle": 1000, + "camHelicalDownSpeed": 250, + "camHelicalSpeed": 1000, + "camHelicalDown": 5, + "camHelicalThru": 0, + "camHelicalOffset": "auto", + "camHelicalForceStartAngle": false, + "camHelicalStartAngle": 0, + "camHelicalOffsetOverride": 0, "camFlipAxis": "X", "camFlipOther": "", "camTabsWidth": 20, diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index a79b2d196..1c7032cdc 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1835,28 +1835,37 @@ CAM.init = function(kiri, api) { sep: UC.newBlank({class:"pop-sep"}), thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}), }; - + createPopOp('helical', { tool: 'camHelicalTool', - mode: 'camHelicalMode', - offset: 'camHelicalOffset', spindle: 'camHelicalSpindle', - down: 'camHelicalDown', rate: 'camHelicalDownSpeed', feed: 'camHelicalSpeed', - thru: 'camRegisterThru' + down: 'camHelicalDown', + thru: 'camHelicalThru', + offset: 'camHelicalOffset', + forceStartAng:'camHelicalForceStartAngle', + startAng:'camHelicalStartAngle', + offOver: 'camHelicalOffsetOverride', + fromTop: 'camHelicalFromStockTop', }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), - mode: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l,}, "helicalmode"), offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l,}, "helicaloff"), sep: UC.newBlank({class:"pop-sep"}), spindle: UC.newInput(LANG.cc_spnd_s, {title:LANG.cc_spnd_l, convert:UC.toInt, show:hasSpindle}), - rate: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toInt, units:true}), - feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toInt, units:true}), - sep: UC.newBlank({class:"pop-sep"}), - down: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, units:true}), + rate: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toFloat, units:true}), + feed: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toFloat, units:true}), + down: UC.newInput(LANG.ch_sdwn_s, {title:LANG.ch_sdwn_l, convert:UC.toFloat, units:true}), + startAng: UC.newInput(LANG.ch_stra_s, {title:LANG.ch_stra_l, convert:UC.toDegsFloat, bound:UC.bound(-360,360),show:() => poppedRec.forceStartAng}), + offOver: UC.newInput(LANG.cc_offd_s, {title:LANG.cc_offd_l, convert:UC.toFloat, units:true, bound:UC.bound(0,Infinity)}), + sep: UC.newBlank({class:"pop-sep"}), + forceStartAng: UC.newBoolean(LANG.ch_fsta_s, undefined, {title:LANG.ch_fsta_l, }), + fromTop: UC.newBoolean(LANG.cd_ftop_s,undefined, {title:LANG.cd_ftop_l}), sep: UC.newBlank({class:"pop-sep"}), thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}), + actions: UC.newRow([ + UC.newButton(LANG.select, ()=>func.selectHoles(true), {title:LANG.cd_seli_l}), + ], {class:"ext-buttons f-col"}) }; createPopOp('flip', { diff --git a/src/kiri/conf.js b/src/kiri/conf.js index abea4b436..36dfb3cfc 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -525,6 +525,16 @@ const conf = exports({ camDrillingOn: false, camRegisterSpeed: 1000, camRegisterThru: 5, + camHelicalTool: 1000, + camHelicalSpindle: 1000, + camHelicalDownSpeed: 250, + camHelicalSpeed: 1000, + camHelicalDown: 5, + camHelicalThru: 0, + camHelicalOffset: "auto", + camHelicalForceStartAngle: false, + camHelicalStartAngle: 0, + camHelicalOffsetOverride: 0, camFlipAxis: "X", camFlipOther: "", camLaserEnable: ["M321"], diff --git a/src/kiri/consts.js b/src/kiri/consts.js index 07ae3e123..0a3cebac0 100644 --- a/src/kiri/consts.js +++ b/src/kiri/consts.js @@ -89,16 +89,13 @@ const LISTS = { { name: "follow" }, { name: "clear" } ], - helicalmode: [ - { name: "clear" }, - { name: "threadCutting" } - ], traceoff: [ { name: "none" }, { name: "inside" }, { name: "outside" } ], helicaloff:[ + { name: "auto" }, { name: "inside" }, { name: "outside" }, ], diff --git a/src/kiri/ui.js b/src/kiri/ui.js index bb18c9e62..b534d4ffa 100644 --- a/src/kiri/ui.js +++ b/src/kiri/ui.js @@ -45,6 +45,7 @@ gapp.register("kiri.ui", [], (root, exports) => { bound, toInt, toFloat, + toDegsFloat, isSticky, setSticky, newBoolean, @@ -342,6 +343,18 @@ gapp.register("kiri.ui", [], (root, exports) => { return nv; } + function toDegsFloat(){ + let nv = this.value !== '' ? parseFloat(this.value) : null; + if (nv !== null && this.bound) nv = this.bound(nv); + nv = (nv+360)% 360; // bound the val to 0-359.99 + if (this.setv) { + return this.setv(nv); + } else { + this.value = nv; + } + return nv; + } + function raw() { return this.value !== '' ? this.value : null; } diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 0e90a13a5..f60a8030c 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -608,6 +608,17 @@ self.kiri.lang['en-us'] = { cd_sela_s: "Select Matching Diameter", cd_sela_l: ["select all holes that", "match selected tool diameter"], + // CNC HELICAL tolpath + ch_menu: "helical", + ch_sdwn_s: "step down (pitch)", + ch_sdwn_l: ["z-distance","per revolution","in workspace units","(inverse of threads per unit)", "0 to disable"], + ch_fsta_s: "force start angle", + ch_fsta_l: ["override start angle","defaults to closest point to tool"], + ch_stra_s: "start angle", + ch_stra_l: ["start angle of helix","in degrees ccw of +x axis"], + ch_selc_l: ["select a cylinder face", "inner holes that are too small","will be ignored."], + + // CNC FLIP cf_menu: "flip", cf_nvrt_s: "z bottom", @@ -643,6 +654,10 @@ self.kiri.lang['en-us'] = { cl_maxz_s: "max z height", cl_maxz_l: ["highest Z band position. when set to 0, uses the highest Z point of the workspace"], + + + + // CNC CUTOUT TABS ct_menu: "tabs", ct_angl_s: "angle", From e5d9712181d49d036419fb5126c8aa7a3c41dc70 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Mon, 30 Jun 2025 20:46:58 -0600 Subject: [PATCH 72/73] set up cylinder route --- src/kiri-mode/cam/client.js | 25 ++++++++++++++++++++- src/kiri-mode/cam/driver.js | 42 +++++++++++++++++++++++++++++++++++ src/kiri-mode/cam/ops.js | 44 +++++++++++++++++++++++++++++++++++++ src/kiri-mode/cam/slice.js | 6 +++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 1c7032cdc..ff3418190 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -1355,6 +1355,29 @@ CAM.init = function(kiri, api) { }); }; + func.selectHelical = () => { + + console.log("select helical") + + + CAM.cylinderFaces((progress,msg)=>{ + api.show.progress(progress,msg) + },(data)=>{ + + console.log(data) + + func.selectHelicalDone(data); + }) + + + + } + + func.selectHelicalDone = () =>{ + + console.log("select helical done") + } + // COMMON TAB/TRACE EVENT HANDLERS api.event.on("slice.begin", () => { if (isCamMode) { @@ -1864,7 +1887,7 @@ CAM.init = function(kiri, api) { sep: UC.newBlank({class:"pop-sep"}), thru: UC.newInput(LANG.cd_thru_s, {title:LANG.cd_thru_l, convert:UC.toFloat, units:true}), actions: UC.newRow([ - UC.newButton(LANG.select, ()=>func.selectHoles(true), {title:LANG.cd_seli_l}), + UC.newButton(LANG.select, func.selectHelical, {title:LANG.cd_seli_l}), ], {class:"ext-buttons f-col"}) }; diff --git a/src/kiri-mode/cam/driver.js b/src/kiri-mode/cam/driver.js index 9d92527ef..6b164df26 100644 --- a/src/kiri-mode/cam/driver.js +++ b/src/kiri-mode/cam/driver.js @@ -99,6 +99,29 @@ kiri.load(api => { }); }) }; + + CAM.cylinderFaces = function(opProgress,onDone){ + + kiri.client.sync(); + const settings = api.conf.get(); + return new Promise((res,rej)=>{ + + console.log("messaging worker") + + kiri.client.send("cam_cylinder_faces", { settings }, output => { + let out = kiri.codec.decode(output) + if(out.progress != undefined){ + //if a progress message, + opProgress(out.progress,out.msg) + }else{ + api.hide.alert(alert); + onDone(out); + res(out); + } + }); + }) + + } } if (kiri.worker) { @@ -181,6 +204,25 @@ kiri.load(api => { shadowed:widget.shadowedDrills } } ))); } + + kiri.worker.cam_cylinder_faces = async function(data,send){ + const { settings, rec } = data; + const widgets = Object.values(kiri.worker.cache); + + console.log("hi from the worker") + + for (let [i,widget] of widgets.entries() ) { + if (await CAM.cylinders(settings, widget,)) { + + let {traces} = widget; + + console.log("traces",traces) + + traces.filter(t=>t) + } + + } + } } }); diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index 28f6a546f..ac518af44 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -1726,6 +1726,49 @@ class OpRegister extends CamOp { } } +class OpHelical extends CamOp { + constructor(state, op) { + super(state, op); + } + + async slice(progress){ + let { op, state } = this; + let { settings, addSlices, widget, updateToolDiams } = state; + let { zBottom, zThru, thruHoles, color } = state; + let { drills, drillThrough} = op + + let drillTool = new CAM.Tool(settings, op.tool), + drillToolDiam = drillTool.fluteDiameter(), + sliceOut = this.sliceOut = []; + + updateToolDiams(drillToolDiam); + + + + + let slice = newSlice(0); + + let poly = newPolygon(); + + + + + slice.camTrace = { tool: op.tool, rate: op.feed, plunge: op.rate }; + slice.camLines = [poly]; + + slice.output() + .setLayer("Helical", {face: color, line: color}) + .addPolys(slice.camLines); + addSlices(slice); + sliceOut.push(slice); + } + + + async prepare(ops, progress){ + + } +} + class OpXRay extends CamOp { constructor(state, op) { super(state, op); @@ -1958,6 +2001,7 @@ CAM.OPS = CamOp.MAP = { "trace": OpTrace, "drill": OpDrill, "register": OpRegister, + "helical": OpHelical, "laser on": OpLaserOn, "laser off": OpLaserOff, "gcode": OpGCode, diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index 7e97fc32d..aa24628c1 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -515,6 +515,12 @@ CAM.traces = async function(settings, widget, single) { return true; }; + +CAM.cylinders = async (settings, widget) => { + + //this is where the migic is gonna happen +} + /** * Generate a list of holes in the model based on the given diameter. * From f153d24ae18a7d381570a2a93bbb70d29e12c839 Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Wed, 2 Jul 2025 17:10:32 -0600 Subject: [PATCH 73/73] finish cam cylinders function --- src/kiri-mode/cam/slice.js | 51 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index aa24628c1..472d57d73 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -516,9 +516,56 @@ CAM.traces = async function(settings, widget, single) { }; -CAM.cylinders = async (settings, widget) => { + /** + * Returns an array of arrays of perpindicular triangles in the mesh. + * Each sub-array is a list of triangle data that are part of the same + * cylinder. + * + * @param {object} settings - settings object + * @param {object} widget - widget object + * @param {object} opts - options object + * @return {array} - array of arrays of triangle data + */ +CAM.cylinders = async (settings, widget, opts) => { + + let {} = opts ?? {} + + let {array:verts} = widget.mesh.geometry.attributes.position + let perpTriangles = [] + + //iterate over all triangles + for(let i = 0; i < verts.length; i+=9){ + let a = [verts[i], verts[i+1], verts[i+2]], + b = [verts[i+3], verts[i+4], verts[i+5]], + c = [verts[i+6], verts[i+7], verts[i+8]]; + + //calculate normal + let normal = new THREE.Vector3(a[0]-b[0], a[1]-b[1], a[2]-b[2]) + .cross(new THREE.Vector3(b[0]-c[0], b[1]-c[1], b[2]-c[2])) + .normalize() + + // if perpindicular normal, and at least 2 Zs are the same + if(normal.z.round(5) == 0 && ! (a[2] != b[2] && b[2] != c[2])){ + let minZ = Math.min(a[2], b[2], c[2]), + maxZ = Math.max(a[2], b[2], c[2]); + if(minZ == maxZ){ // all Zs are the same indicated malformed geometry + continue; + } + perpTriangles.push({...[a,b,c], normal,minZ,maxZ,i:index}) + } + + } + //map where zmax, zmin -> triangleData + let cylinderTriangles = new Map() + for(let t of perpTriangles){ + let hash = `${t.minZ.round(5)},${t.maxZ.round(5)}` + if(!cylinderTriangles.has(hash)){ + cylinderTriangles.set(hash,[]) + } + cylinderTriangles.get(hash).push(t) + } - //this is where the migic is gonna happen + return Array.from(cylinderTriangles.values()) } /**