From f703f0cef3ca0ae92f0824c390cff870124800ae Mon Sep 17 00:00:00 2001 From: bakedpotatolord Date: Thu, 22 May 2025 12:32:21 -0600 Subject: [PATCH] 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 d00fbbbe..ca606429 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 e5c9cbe9..99ccec03 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;