first pass refactor of cnc path routing

This commit is contained in:
Stewart Allen 2025-11-30 18:03:18 -05:00
commit f46bcc06f1
13 changed files with 286 additions and 341 deletions

View file

@ -287,7 +287,7 @@ export function opRender() {
oplist.forEach((rec, i) => {
let title = '';
let clock = rec.type === '|';
let label = clock ? `` : rec.mode ?? rec.type;
let label = clock ? `` : (rec.mode ? rec.type + ' ' + rec.mode : rec.type);
let clazz = notime ? ["draggable", "notime"] : ["draggable"];
let notable = rec.note ? rec.note.split(' ').filter(v => v.charAt(0) === '#') : undefined;
if (clock) { clazz.push('clock'); title = ` title="end of ops chain\ndrag/drop like an op\nops after this are disabled"` }

View file

@ -85,6 +85,7 @@ class OpContour extends CamOp {
let { color, addSlices, settings, updateToolDiams } = state;
let filter = createFilter(op, settings.origin, op.axis.toLowerCase());
let toolDiam = this.toolDiam = new Tool(settings, op.tool).fluteDiameter();
updateToolDiams(toolDiam);
// we need topo for safe travel moves when roughing and outlining
// not generated when drilling-only. then all z moves use bounds max.
@ -107,6 +108,7 @@ class OpContour extends CamOp {
contour: op,
state: state
});
// computed if set to 0
this.tolerance = topo.tolerance;
}
@ -116,10 +118,10 @@ class OpContour extends CamOp {
let { settings } = state;
let { process } = settings;
let { setTolerance, setTool, setSpindle, setPrintPoint } = ops;
let { camOut, newLayer, printPoint, lastPoint } = ops;
let { bounds, zmax } = ops;
let { setTolerance, setTool, setSpindle } = ops;
let { widget, camOut, newLayer, zmax } = ops;
let bounds = widget.getBoundingBox();
let toolDiam = this.toolDiam;
let stepover = toolDiam * op.step * 2;
let depthFirst = process.camDepthFirst;
@ -129,7 +131,7 @@ class OpContour extends CamOp {
setSpindle(op.spindle);
setTolerance(this.tolerance);
printPoint = newPoint(bounds.min.x, bounds.min.y, zmax);
let printPoint = newPoint(bounds.min.x, bounds.min.y, zmax);
for (let slice of sliceOut) {
// ignore debug slices
@ -158,20 +160,17 @@ class OpContour extends CamOp {
}
if (depthFirst) {
printPoint = tip2tipEmit(depthData, printPoint, function (el, point, count) {
tip2tipEmit(depthData, printPoint, function (el, point, count) {
let poly = el.poly;
if (poly.last() === point) {
poly.reverse();
}
poly.forEachPoint(function (point, pidx) {
camOut(point.clone().annotate({ slice: poly.slice }), pidx > 0, { moveLen: stepover });
camOut(printPoint = point.clone().annotate({ slice: poly.slice }), pidx > 0, { moveLen: stepover });
}, false);
newLayer();
return lastPoint();
});
}
setPrintPoint(printPoint);
}
}

View file

@ -29,38 +29,42 @@ class OpDrill extends CamOp {
if (!drill.selected) {
return
}
let slice = newSlice(0);
if (op.mark) {
// replace depth with single down peck
drill.depth = op.down
}
drill.zBottom = drill.z - drill.depth;
// for thru holes, follow z thru when set
if ((op.thru > 0)) {
drill.zBottom -= op.thru;
}
const poly = newPolygon()
poly.points.push(newPoint(drill.x, drill.y, drill.z))
poly.points.push(newPoint(drill.x, drill.y, drill.zBottom))
// poly.points.pop();
slice.camTrace = { tool: op.tool, rate: op.feed, plunge: op.rate };
slice.camLines = [poly];
slice.output()
.setLayer(state.layername, { face: color, line: color })
.addPolys(slice.camLines);
addSlices(slice);
sliceOut.push(slice);
});
}
prepare(ops, progress) {
let { op } = this;
let { op, sliceOut } = this;
let { setTool, setSpindle, setDrill, emitDrills } = ops;
setTool(op.tool, undefined, op.rate);
setDrill(op.down, op.lift, op.dwell);
setSpindle(op.spindle);
emitDrills(this.sliceOut.map(slice => slice.camLines).flat());
emitDrills(sliceOut.map(slice => slice.camLines).flat());
}
}

View file

@ -240,20 +240,13 @@ export class OpHelical extends CamOp {
}
async prepare(ops, progress) {
let {
setTool,
setSpindle,
polyEmit,
setPrintPoint,
getPrintPoint,
} = ops;
let { polyEmit, printPoint, setTool, setSpindle } = ops;
let { tool, spindle, rate, feed } = this.op;
setTool(tool, feed, rate);
setSpindle(spindle);
let [polys] = this.sliceOut.map((s) => s.camLines);
let pp = getPrintPoint();
let [ polys ] = this.sliceOut.map((s) => s.camLines);
polys = polys.slice();
for (;;) {
@ -265,7 +258,7 @@ export class OpHelical extends CamOp {
for (let i = 0; i < polys.length; i++) {
if (!polys[i]) continue;
if ((dist = polys[i].first().distTo2D(pp)) < closestDist) {
if ((dist = polys[i].first().distTo2D(printPoint)) < closestDist) {
closestDist = dist;
closest = polys[i];
closestI = i;
@ -275,9 +268,7 @@ export class OpHelical extends CamOp {
if (!closest) break;
poly = polys[closestI]
polys[closestI] = null;
pp = polyEmit(poly, 0, 1, poly.points[0])
printPoint = polyEmit(poly, 0, 1, poly.points[0])
}
setPrintPoint(pp);
}
}

View file

@ -25,16 +25,12 @@ export class OpIndex extends CamOp {
}
prepare(ops, progress) {
const { lastPoint, zmax, zclear, camOut, stock, newLayer } = ops;
let last = lastPoint();
if (last) {
// max point of stock corner radius when rotating (safe z when indexing)
const rzmax = (Math.max(stock.y, stock.z) * Math.sqrt(2)) / 2 + zclear;
const zmove = Math.max(rzmax, zmax);
// move above rotating stock
camOut(last = last.clone().setZ(zmove), 0);
// issue rotation command
camOut(last = last.clone().setZ(zmove).setA(this.degrees), 0);
}
const { zmax, zsafe, camOut } = ops;
// max point of stock corner radius when rotating (safe z when indexing)
const ztop = Math.max(zsafe, zmax);
// move above rotating stock
camOut(last = last.clone().setZ(ztop), 0);
// issue rotation command
camOut(last = last.clone().setZ(ztop).setA(this.degrees), 0);
}
}

View file

@ -57,7 +57,7 @@ class OpLathe extends CamOp {
let { op, state, slices, topo } = this;
let { settings } = state;
let { setTool, setSpindle, setPrintPoint } = ops;
let { setTool, setSpindle } = ops;
let { camOut, newLayer, printPoint } = ops;
let { zmax } = ops;
@ -111,8 +111,6 @@ class OpLathe extends CamOp {
// camOut(last = last.clone().setA(amax), 0);
newLayer();
ops.addGCode([`G0 Z${zmax.round(2)}`, `G0 A${amax}`, "G92 A0"]);
setPrintPoint(last);
}
}

View file

@ -14,12 +14,12 @@ class OpLevel extends CamOp {
async slice(progress) {
let { op, state } = this;
let { settings, addSlices, updateToolDiams } = state;
let { zMax, ztOff, color, tshadow } = state;
let { addSlices, color, settings, tshadow, updateToolDiams, zMax, ztOff } = state;
let { stock } = settings;
let toolDiam = new Tool(settings, op.tool).fluteDiameter();
let stepOver = this.stepOver = toolDiam * op.step;
let wpos = state.widget.track.pos;
let zTop = zMax + ztOff;
let zBot = zTop - op.down;
let zList = op.stepz ? util.lerp(zTop, zBot, op.stepz) : [ zBot ];
@ -28,7 +28,7 @@ class OpLevel extends CamOp {
let points = [];
let clear = op.stock ?
[ newPolygon().centerRectangle({x:0,y:0,z:0}, stock.x + toolDiam/2, stock.y) ] :
[ newPolygon().centerRectangle({x:-wpos.x,y:-wpos.y,z:wpos.z}, stock.x + toolDiam/2, stock.y) ] :
POLY.outer(POLY.offset(tshadow, toolDiam * (op.inset || 0)));
POLY.fillArea(clear, 1090, stepOver, points);
@ -50,7 +50,7 @@ class OpLevel extends CamOp {
prepare(ops, progress) {
let { op, layers, stepOver } = this;
let { setTool, setSpindle, printPoint, setPrintPoint } = ops;
let { setTool, setSpindle, printPoint } = ops;
let { newLayer, tip2tipEmit, camOut } = ops;
setTool(op.tool, op.rate);
@ -68,7 +68,6 @@ class OpLevel extends CamOp {
});
newLayer();
}
setPrintPoint(printPoint);
}
}

View file

@ -231,24 +231,22 @@ class OpOutline extends CamOp {
prepare(ops, progress) {
let { op, state, sliceOut } = this;
let { setTool, setSpindle, setPrintPoint } = ops;
let { polyEmit, depthOutlinePath } = ops;
let { newLayer, printPoint } = ops;
let { settings } = state;
let { process } = settings;
let { depthOutlinePath, newLayer, polyEmit, printPoint, setTool, setSpindle } = ops;
let easeDown = process.camEaseDown;
let toolDiam = this.toolDiam;
let cutdir = op.ov_conv;
let depthFirst = process.camDepthFirst;
let depthData = [];
let easeDown = process.camEaseDown;
let toolDiam = this.toolDiam;
setTool(op.tool, op.rate, op.plunge);
setSpindle(op.spindle);
// 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);
// printPoint becomes NaN in engine mode
if (Object.values(printPoint).some(v => Number.isNaN(v))) {
printPoint = newPoint(0, 0, 0);
}
for (let slice of sliceOut) {
@ -308,8 +306,6 @@ class OpOutline extends CamOp {
printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, true, ease);
}
}
setPrintPoint(printPoint);
}
}

View file

@ -242,7 +242,7 @@ class OpPocket extends CamOp {
prepare(ops, progress) {
let { op, state, pockets } = this;
let { getPrintPoint , pocket, setTool, setSpindle, setTolerance } = ops;
let { pocket, setTool, setSpindle, setTolerance } = ops;
let { process } = state.settings;
setTool(op.tool, op.rate);
@ -255,36 +255,15 @@ class OpPocket extends CamOp {
// eliminate empty pockets
pockets = pockets.filter(p => p.length);
// pockets is an [ array of an [ array of slices ] ]
// each top level array is a pocket containing a [ z layer array of slices ]
// follow each pocket to the next closest one from previous exit
for (;;) {
let printPoint = getPrintPoint();
let min = {
dist: Infinity,
pocket: undefined
};
for (let pocket of pockets.filter(p => !p.used)) {
let poly = pocket[0].camLines.slice().sort((a,b) => b.area() - a.area())[0];
if (!poly) continue;
let find = poly.findClosestPointTo(printPoint);
if (find.distance < min.dist) {
min.pocket = pocket;
min.dist = find.distance;
}
}
if (min.pocket) {
min.pocket.used = true;
pocket({
cutdir: op.ov_conv,
depthFirst: process.camDepthFirst && !state.isIndexed,
easeDown: op.down && process.easeDown ? op.down : 0,
progress: (n,m) => progress(n/m, "pocket"),
slices: min.pocket
});
} else {
break;
}
// naive output order
for (let slices of pockets) {
pocket({
cutdir: op.ov_conv,
depthFirst: process.camDepthFirst && !state.isIndexed,
easeDown: op.down && process.easeDown ? op.down : 0,
progress: (n,m) => progress(n/m, "pocket"),
slices
});
}
}
}

View file

@ -14,7 +14,7 @@ class OpRegister extends CamOp {
async slice(progress) {
let { op, state } = this;
let { settings, widget, bounds, addSlices, zMax, zThru, color } = state;
let { addSlices, bounds, color, settings, widget, zThru } = state;
let { updateToolDiams } = state;
let tool = new Tool(settings, op.tool);
@ -23,21 +23,22 @@ class OpRegister extends CamOp {
updateToolDiams(tool.fluteDiameter());
let { stock } = settings,
toolZ = widget.track.pos.z,
boundMinX = bounds.min.x,
boundMaxX = bounds.max.x,
boundMinY = bounds.min.y,
boundMaxY = bounds.max.y,
toolOffset = tool.fluteDiameter() * 2,
centerX = (boundMinX + boundMaxX) / 2,
centerY = (boundMinY + boundMaxY) / 2,
cutDepth = op.thru || zThru || 0,
pathPoints = [],
stockToSurfaceOffset = stock.z - bounds.max.z,
startZ = bounds.max.z + stockToSurfaceOffset + toolZ,
endZ = toolZ - cutDepth,
cutOffset = op.offset;
let { stock } = settings;
let { pos } = widget.track;
let toolZ = pos.z,
boundMinX = bounds.min.x,
boundMaxX = bounds.max.x,
boundMinY = bounds.min.y,
boundMaxY = bounds.max.y,
toolOffset = tool.fluteDiameter() * 2,
centerX = (boundMinX + boundMaxX) / 2,
centerY = (boundMinY + boundMaxY) / 2,
cutDepth = op.thru || zThru || 0,
pathPoints = [],
stockToSurfaceOffset = stock.z - bounds.max.z,
startZ = bounds.max.z + stockToSurfaceOffset + toolZ,
endZ = toolZ - cutDepth,
cutOffset = op.offset;
if (!(stock.x && stock.y && stock.z)) {
return;
@ -146,7 +147,7 @@ class OpRegister extends CamOp {
prepare(ops, progress) {
let { op } = this;
let { setTool, setSpindle, setDrill, emitDrills } = ops;
let { emitDrills, setDrill, setSpindle, setTool } = ops;
if (op.axis === '-' || op.axis === '=') {
setTool(op.tool, op.feed, op.rate);

View file

@ -296,7 +296,7 @@ class OpRough extends CamOp {
prepare(ops, progress) {
let { op, state, sliceOut, camFaces } = this;
let { setTool, setSpindle, setPrintPoint, pocket, polyEmit, newLayer, printPoint } = ops;
let { setTool, setSpindle, pocket, polyEmit, newLayer, printPoint } = ops;
let { settings } = state;
let { process } = settings;
@ -329,7 +329,6 @@ class OpRough extends CamOp {
}
// output the roughing passes
setPrintPoint(printPoint);
pocket({
cutdir,
depthFirst,

View file

@ -18,28 +18,44 @@ const { toRadians } = util
* @param {Number} [index] into widget array
* @param {Object} [firstPoint] starting point
*/
export async function cam_prepare(widall, settings, update) {
const widgets = widall.filter(w => !w.track.ignore && !w.meta.disabled);
const count = widgets.length;
export async function cam_prepare(widgets, settings, update) {
const active = widgets
.filter(w => !w.isSynth() && !w.track.ignore && !w.meta.disabled)
.filter(w => w?.camops.length)
;
const count = active.length;
const weight = 1 / count;
const print = self.kiri_worker.current.print = newPrint(settings, widgets);
const print = self.kiri_worker.current.print = newPrint(settings, active);
const { origin } = settings;
// cam-specific storage
print.output = [];
let point;
widgets.filter(w => !w.isSynth()).forEach((widget, index) => {
point = prepEach(widget, settings, print, point, (progress, msg) => {
// sort output by distance to origin
if (origin) {
let point = newPoint().move(origin);
active.sort((w0,w1) =>
newPoint().move(w0.track.pos).distTo2D(point) -
newPoint().move(w1.track.pos).distTo2D(point)
);
}
let startPoint;
active.forEach((widget, index) => {
startPoint = prepare_one(widget, settings, print, startPoint, (progress, msg) => {
update((index * weight + progress * weight) * 0.75, msg || "prepare");
});
});
// prune empty levels
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);
}, {
// compute path display
return render.path(
output,
(progress, layer) => {
update(0.75 + progress * 0.25, "render", layer);
}, {
thin: true,
print: 0,
move: 0x557799,
@ -49,82 +65,75 @@ export async function cam_prepare(widall, settings, update) {
action: "milling",
maxspeed: settings.process.camFastFeed || 6000
}
);
);
};
export function prepEach(widget, settings, print, firstPoint, update) {
if (widget.camops.length === 0 || widget.meta.disabled) return;
// process `prepare` paths for a single widget
export function prepare_one(widget, settings, print, firstPoint, update) {
let { device, process } = settings,
isIndexed = process.camStockIndexed,
startCenter = process.camOriginCenter,
alignTop = settings.controller.alignTop,
{ alignTop } = settings.controller,
{ camArcEnabled, camArcResolution, camArcTolerance } = process,
{ camDepthFirst, camEaseAngle, camEaseDown } = process,
{ camForceZMax, camFullEngage, camInnerFirst, camOriginCenter } = process,
{ camOriginOffX, camOriginOffY, camOriginOffZ } = process,
{ camStockOffset, camStockIndexed, camZClearance } = process,
stock = settings.stock || {},
stockz = stock.z * (isIndexed ? 0.5 : 1),
outer = settings.bounds || widget.getPositionBox(),
outerz = outer.max.z,
zclear = (process.camZClearance || 1),
zmax_force = process.camForceZMax || false,
zmax_outer = stockz + zclear,
wztop = widget.track.top,
ztOff = stockz - wztop,
stockZ = stock.z * (camStockIndexed ? 0.5 : 1),
outer = settings.bounds || widget.getPositionBox(), // dup of bounds?
outerZ = outer.max.z, // dup of boundsZ?
stockZClear = stockZ + camZClearance,
widgetTrackTop = widget.track.top,
widgetTopToStock = stockZ - widgetTrackTop,
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 || 0),
zsafe = isIndexed ? Math.hypot(bounds.dim.y, bounds.dim.z) / 2 + zclear : zmax,
boundsZ = camStockIndexed ? stock.z / 2 : bounds.max.z + widgetTopToStock,
zMax = outerZ + camZClearance + (camOriginOffZ || 0) + widgetTopToStock,
zSafe = camStockIndexed ? Math.hypot(bounds.dim.y, bounds.dim.z) / 2 + camZClearance : zMax,
wmpos = widget.track.pos,
wmx = wmpos.x,
wmy = wmpos.y,
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),
easeDown = process.camEaseDown,
easeAngle = process.camEaseAngle,
depthFirst = process.camDepthFirst,
innerFirst = process.camInnerFirst,
engageFactor = process.camFullEngage,
arcTolerance = process.camArcTolerance,
arcRes = toRadians(process.camArcResolution),
arcEnabled = process.camArcEnabled && arcTolerance > 0 && arcRes > 0,
tolerance = 0,
wmz = !camStockIndexed ? stock.z - boundsZ : alignTop ? outerZ - boundsZ : 0,
originx = (camOriginCenter ? 0 : -stock.x / 2) + (camOriginOffX || 0),
originy = (camOriginCenter ? 0 : -stock.y / 2) + (camOriginOffY || 0),
origin = newPoint(originx, originy, zMax),
arcRadians = toRadians(camArcResolution),
arcEnabled = camArcEnabled && camArcTolerance > 0 && arcRadians > 0,
currentOp,
drillDown = 0,
drillLift = 0,
drillDwell = 0,
lasering = false,
laserPower = 0,
newOutput = print.output || [],
layerOut = [],
printPoint,
feedRate,
isPocket,
isContour,
isRough,
isLathe,
isIndex,
layerOut = [],
lastOp,
lastTool,
lastPush,
lasering = false,
laserPower = 0,
maxToolDiam = widget.maxToolDiam,
newOutput = print.output || [],
plungeRate = process.camFastFeedZ,
printPoint,
tool,
toolType,
toolDiam,
toolDiamMove,
plungeRate = process.camFastFeedZ,
feedRate,
lastOp,
lastTool,
lastPush,
lastPoint,
currentOp,
nextIsMove = true,
synthPlunge = false,
spindle = 0,
spindleMax = device.spindleMax,
maxToolDiam = widget.maxToolDiam,
terrain = widget.terrain ? widget.terrain.map(data => {
return {
z: data.z,
tops: data.tops,
};
}) : zmax;
}) : zMax,
tolerance = 0;
console.log({ zSafe, stockZ, outerZ, boundsZ, wmz });
function newLayer(op) {
if (layerOut.length || layerOut.mode) {
newOutput.push(layerOut);
@ -158,10 +167,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
}
}
function setPrintPoint(point) {
ops.printPoint = printPoint = point;
}
function setSpindle(speed) {
spindle = Math.min(speed, spindleMax);
}
@ -209,7 +214,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
if (!closest) return;
polys[closestI] = null;
printPoint = closest.first();
emitDrill(closest, drillDown, drillLift, drillDwell);
}
}
@ -240,7 +244,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
break;
}
}
camOut(point.clone().setZ(zmax_outer), 0);
camOut(point.clone().setZ(stockZClear), 0);
points.forEach(function (point, index) {
camOut(point, 1);
if (index > 0 && index < points.length - 1) {
@ -248,7 +252,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
if (lift) camOut(point.clone().setZ(point.z + lift), 0);
}
})
camOut(point.clone().setZ(zmax_outer), 0);
camOut(point.clone().setZ(stockZClear), 0);
newLayer();
}
@ -270,7 +274,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
let power = emit ? laserPower : 0;
if (emit && lasering.adapt) {
let { minz, maxz, minp, maxp, adaptrp } = lasering;
maxz = maxz || wztop;
maxz = maxz || widgetTrackTop;
let deltaz = maxz - minz;
let { z } = point;
if (adaptrp) {
@ -288,13 +292,14 @@ export function prepEach(widget, settings, print, firstPoint, update) {
}
}
if (lasering.flat) {
point.z = (stock && stock.z ? stock.z : wztop) + lasering.flatz;
point.z = (stock && stock.z ? stock.z : widgetTrackTop) + lasering.flatz;
}
print.addOutput(layerOut, point, power, speed, tool, { type: 'laser' });
} else {
print.addOutput(layerOut, point, emit, speed, tool, { type, center, arcPoints });
}
lastPush = { point, emit, speed, tool };
printPoint = point ?? printPoint;
return point;
}
@ -316,8 +321,17 @@ export function prepEach(widget, settings, print, firstPoint, update) {
return newPoint(
p.x + wmx,
p.y + wmy,
p.z + zadd
).setA(p.a ?? lastPoint?.a).annotate({ slice: p.slice });
p.z + wmz
)
.setA(p.a ?? printPoint?.a)
.annotate({ slice: p.slice });
}
/**
* wrapper: translate point into workspace coordinates and call `camOut`
*/
function camOut(point, emit = 1, opts) {
_camOut(applyWidgetMovement(point), emit, opts);
}
/**
@ -329,7 +343,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
* @param {number} opts.moveLen typically = tool diameter used to trigger terrain detection
* @param {number} opts.factor speed scale factor
*/
function camOut(point, emit = 1, opts) {
function _camOut(point, emit, opts) {
let lop = lastOp;
lastOp = currentOp;
@ -337,17 +351,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// 1. move to safe z of current point preserving angle
// 2. move to safe z of new point preserving old angle
// 3. move to safe z of new point with new angle
if (lop !== currentOp && lastPoint) {
// compensate for applyWidgetMovement() applied to lastPoint
let lpo = lastPoint.clone().move({ x: -wmx, y: -wmy, z: -zadd });
camOut(lpo.clone().setZ(zsafe).setA(lastPoint.a), 0);
camOut(point.clone().setZ(zsafe).setA(lastPoint.a), 0);
camOut(point.clone().setZ(zsafe), 0);
if (lop !== currentOp) {
_camOut(printPoint.clone().setZ(zSafe).setA(printPoint.a), 0);
_camOut(point.clone().setZ(zSafe).setA(printPoint.a), 0);
_camOut(point.clone().setZ(zSafe), 0);
}
if (lop?.type === 'index' && lop !== currentOp ) {
// console.log('post index first point', point);
camOut(point.clone().setZ(lastPoint.z).setA(lastPoint.a), 1);
_camOut(point.clone().setZ(printPoint.z).setA(printPoint.a), 1);
}
let {
@ -361,9 +373,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
const isArc = emit == 2 || emit == 3;
const pointA = point.a;
// apply widget movement pos
point = applyWidgetMovement(point);
if (nextIsMove) {
emit = 0;
nextIsMove = false;
@ -372,13 +381,13 @@ export function prepEach(widget, settings, print, firstPoint, update) {
let rate = feedRate * factor;
// carry rotation forward when not overridden
if (lastPoint && pointA !== undefined && lastPoint.a !== undefined) {
let DA = lastPoint.a - pointA;
let MZ = Math.max(lastPoint.z, point.z)
if (pointA !== undefined && printPoint.a !== undefined) {
let DA = printPoint.a - pointA;
let MZ = Math.max(printPoint.z, point.z)
// find arc length
let AL = (Math.abs(DA) / 360) * (2 * Math.PI * MZ);
if (AL >= 1) {
let lerp = base.util.lerp(lastPoint.a, pointA, 1);
let lerp = base.util.lerp(printPoint.a, pointA, 1);
// create interpolated point set for rendering and animation
// console.log({ DA, MZ, AL }, lerp.length);
for (let a of lerp) {
@ -395,39 +404,31 @@ export function prepEach(widget, settings, print, firstPoint, update) {
}
}
// before first point, move cutting head to point above it
// then set that new point as the lastPoint
if (!lastPoint) {
let above = point.clone().setZ(stockz + zclear);
// let above = point.clone().setZ(zmax + zadd + ztOff);
lastPoint = layerPush(above, 0, 0, tool);
}
// measure deltas to last point in XY and Z
let deltaXY = lastPoint.distTo2D(point),
deltaZ = point.z - lastPoint.z,
let deltaXY = printPoint.distTo2D(point),
deltaZ = point.z - printPoint.z,
absDeltaZ = Math.abs(deltaZ),
hasDelta = deltaXY >= 0.001 && absDeltaZ >= 0,
isMove = emit == 0;
// when rapid pluge could cut thru stock, rapid to just above stock
// then continue plunge as a plunge cut
if (deltaZ < 0 && lastPoint.z > stockz && point.z < stockz && emit === 0) {
// console.log('detected plunge cut as rapid move', lastPoint.z, stockz, point.z);
layerPush(point.clone().setZ(stockz + 1), 0, 0, tool);
if (deltaZ < 0 && printPoint.z > stockZ && point.z < stockZ && emit === 0) {
// console.log('detected plunge cut as rapid move', printPoint.z, stockZ, point.z);
layerPush(point.clone().setZ(stockZ + 1), 0, 0, tool);
// change to cutting move for remainder of plunge
emit = 1;
isMove = false;
}
// drop points too close together
if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z && point.a === lastPoint.a) {
// console.trace(["drop dup",lastPoint,point]);
if (!isLathe && !isArc && deltaXY < 0.001 && point.z === printPoint.z && point.a === printPoint.a) {
// console.trace(["drop dup",printPoint,point]);
return;
}
// no jump moves in contour mode to adjacent slice points
let steady = lastPoint && currentOp.type === 'contour' && Math.abs(point.slice - lastPoint.slice) < 4;
let steady = currentOp.type === 'contour' && Math.abs(point.slice - printPoint.slice) < 4;
// convert short planar moves to cuts in some cases
if (hasDelta && !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) {
@ -439,15 +440,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
isMove = false;
} else if (deltaZ <= -tolerance) {
// move over before descending
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
layerPush(point.clone().setZ(printPoint.z), 0, 0, tool);
// new pos for plunge calc
deltaXY = 0;
}
} else if (isMove && isLathe) {
if (point.z > lastPoint.z) {
layerPush(lastPoint.clone().setZ(point.z), 0, 0, tool);
} else if (point.z < lastPoint.z) {
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
if (point.z > printPoint.z) {
layerPush(printPoint.clone().setZ(point.z), 0, 0, tool);
} else if (point.z < printPoint.z) {
layerPush(point.clone().setZ(printPoint.z), 0, 0, tool);
}
} else if (isMove && !steady) {
// for longer moves, check the terrain to see if we need to go up and over
@ -458,40 +459,40 @@ export function prepEach(widget, settings, print, firstPoint, update) {
if (bigXY || bigZ || midZ) {
let maxz = getZClearPath(
terrain,
lastPoint.x - wmx,
lastPoint.y - wmy,
point.x - wmx,
point.y - wmy,
Math.max(point.z, lastPoint.z),
zadd,
printPoint.x,
printPoint.y,
point.x,
point.y,
Math.max(point.z, printPoint.z),
0, // zadd,
maxToolDiam / 2,
zclear
camZClearance
),
maxZdelta = Math.max(maxz - point.z, maxz - lastPoint.z),
maxZdelta = Math.max(maxz - point.z, maxz - printPoint.z),
mustGoUp = maxZdelta >= tolerance,
clearz = maxz;
let zIsBelow = point.z <= maxz;
if (zmax_force) {
clearz = maxz = zmax + zadd;
if (camForceZMax) {
clearz = maxz = zMax;// + zadd;
zIsBelow = true;
}
// up if any point between higher than start/outline, go up first
if (mustGoUp || zIsBelow) {
const zClearance = clearz + (isIndexed ? 0 : ztOff);
const zClearance = clearz + (camStockIndexed ? 0 : widgetTopToStock);
if (zIsBelow) {
layerPush(lastPoint.clone().setZ(zClearance), 0, 0, tool);
layerPush(printPoint.clone().setZ(zClearance), 0, 0, tool);
}
layerPush(point.clone().setZ(zClearance), 0, 0, tool);
// new pos for plunge calc
deltaXY = 0;
// if plunge goes below stock, convert to cut
if (emit === 0 && point.z < stockz) {
lastPoint = layerPush(point.clone().setZ(stockz + 1), 0, 0, tool);
if (emit === 0 && point.z < stockZ) {
layerPush(point.clone().setZ(stockZ + 1), 0, 0, tool);
emit = 1;
}
}
} else if (isRough && deltaZ < 0) {
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
layerPush(point.clone().setZ(printPoint.z), 0, 0, tool);
}
}
@ -526,7 +527,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
if (isArc) {
layerOut.mode = currentOp;
layerOut.spindle = spindle;
lastPoint = layerPush(
layerPush(
point,
clockwise ? 2 : 3,
tmprate ?? rate,
@ -541,7 +542,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// TODO: synthesize move speed from feed / plunge accordingly
layerOut.mode = currentOp;
layerOut.spindle = spindle;
lastPoint = layerPush(
layerPush(
point,
emit,
tmprate ?? rate,
@ -581,10 +582,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
depthData.push(polys);
} else {
// if not depth first, output the polys in slice order
printPoint = poly2polyEmit(polys, printPoint, function (poly, index, count) {
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, undefined, count === 1 ? camFullEngage : 1);
}, poly.isClosed(), index);
}, { swapdir: false });
newLayer();
@ -608,8 +609,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
let otops = outs.map(level => {
return POLY.nest(level.filter(poly => poly.depth === 0).clone());
});
printPoint = depthRoughPath(printPoint, 0, ins, itops, polyEmit, false, easeDown);
printPoint = depthRoughPath(printPoint, 0, outs, otops, polyEmit, false, easeDown);
depthRoughPath(printPoint, 0, ins, itops, polyEmit, false, easeDown);
depthRoughPath(printPoint, 0, outs, otops, polyEmit, false, easeDown);
}
}
/**
@ -677,73 +678,12 @@ export function prepEach(widget, settings, print, firstPoint, update) {
return touch;
}
// coming from a previous widget, use previous last point
lastPoint = firstPoint;
// make top start offset configurable
printPoint = firstPoint || origin;
let ops = {
addGCode,
bounds,
camOut,
depthOutlinePath,
depthRoughPath,
emitDrills,
emitTrace,
getPrintPoint() { return printPoint },
lastPoint: () => { return lastPoint },
newLayer,
pocket,
poly2polyEmit,
polyEmit,
printPoint,
setDrill,
setLasering,
setPrintPoint,
setSpindle,
setTolerance,
setTool,
stock,
tip2tipEmit,
zclear,
zmax,
zsafe,
};
let opSum = 0;
let opTot = widget.camops.map(op => op.weight()).reduce((a, v) => a + v);
for (let op of widget.camops) {
setTolerance(0);
nextIsMove = true;
currentOp = op.op;
isIndex = currentOp.type === 'index';
isLathe = currentOp.type === 'lathe';
isRough = currentOp.type === 'rough';
isPocket = currentOp.type === 'pocket';
isContour = currentOp.type === 'contour' || (isPocket && currentOp.contour);
let weight = op.weight();
newLayer(op.op);
op.prepare(ops, (progress, message) => {
update((opSum + (progress * weight)) / opTot, message || op.type(), message);
});
opSum += weight;
if (tool && lastPoint) {
newLayer();
if (!isIndex) {
layerPush(printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
newLayer();
}
}
}
function emitTrace(slice) {
let { tool, rate, plunge } = slice.camTrace;
setTool(tool, rate, plunge);
printPoint = poly2polyEmit(slice.camLines, printPoint, polyEmit, {
poly2polyEmit(slice.camLines, printPoint, polyEmit, {
swapdir: false,
weight: innerFirst
weight: camInnerFirst
});
newLayer();
}
@ -765,11 +705,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
* @returns {Point} - the last point of the polygon
*/
function polyEmit(poly, index, count, fromPoint, ops) {
let {
cutFromLast,
} = ops ?? {
cutFromLast: false
};
let { cutFromLast } = ops ?? { cutFromLast: false };
let arcQ = [],
arcMax = Infinity, // no max arc radius
lineTolerance = 0.001, // do not consider points under 0.001mm for arcs
@ -778,24 +714,21 @@ export function prepEach(widget, settings, print, firstPoint, update) {
fromPoint = fromPoint || printPoint;
arcQ.angle = []
let lastPoint = fromPoint;
let startIndex = index;
// 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;
let scale = ((isRough || isPocket) && count === 1) ? camFullEngage : 1;
let startIndex = index;
let polyLastPoint = fromPoint;
// easeDown only allowed on closed polys (that we can continue around indefinitly)
if (easeDown && poly.isClosed()) {
if (camEaseDown && poly.isClosed()) {
let closest = poly.findClosestPointTo(fromPoint);
lastPoint = closest.point;
polyLastPoint = closest.point;
startIndex = closest.index;
let last = generateEaseDown((point, offset) => { // generate ease-down points
if (offset == 0) camOut(point.clone(), 0, { factor: engageFactor });
if (offset == 0) camOut(point.clone(), 0, { factor: camFullEngage });
camOut(point.clone(), 1, { factor: scale }); // and pass them to camOut
}, poly, fromPoint, easeAngle);
lastPoint = poly.points[last];
}, poly, fromPoint, camEaseAngle);
polyLastPoint = poly.points[last];
startIndex = last;
}
@ -806,11 +739,11 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// console.log({pointA, pointB, indexA, indexB,startIndex})
if (indexA == startIndex) {
//if cutFromLast is true, emit a 1 for a cutting move
camOut(pointA.clone(), cutFromLast ? 1 : 0, { factor: engageFactor });
camOut(pointA.clone(), cutFromLast ? 1 : 0, { factor: camFullEngage });
// if first point, move to and call export function
if (arcEnabled) arcQ.push(pointA);
}
lastPoint = arcExport(pointB, pointA);
polyLastPoint = arcExport(pointB, pointA);
}, !poly.isClosed(), startIndex);
// console.log("at end of arcExport",structuredClone(arcQ));
@ -844,7 +777,7 @@ export 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) > arcRes; // enforce arcRes(olution)
radFault = Math.abs(angle) > arcRadians; // enforce arcRadians(olution)
} else {
// console.log("too much angle")
radFault = true;
@ -873,10 +806,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// check if first angles should have caused radFault
let angle = toRadians(arcQ[0].slopeTo(cc).angleDiff(arcQ[1].slopeTo(cc)).angle)
radFault = Math.abs(angle) > arcRes
radFault = Math.abs(angle) > arcRadians
if (radFault) {
// if so, remove first point
console.log("secondary radfault,",structuredClone(arcQ),{angle,arcRes,a,b})
console.log("secondary radfault,",structuredClone(arcQ),{angle,arcRadians,a,b})
camOut(arcQ.shift(), 1)
}
@ -891,9 +824,9 @@ export function prepEach(widget, settings, print, firstPoint, update) {
}
// if new point is off the arc
// if point is off-center, or too far from center, or too large of a radius
if (dc * arcQ.center.length / arcQ.rSum > arcTolerance || dist > cc.r || cc.r > arcMax || radFault || zFault) {
// let debug = [ dc * arcQ.center.length / arcQ.rSum > arcTolerance, dist > cc.r, cc.r > arcMax, radFault];
// console.log("point off the arc,",structuredClone(arcQ),radFault,zFault,[dc * arcQ.center.length / arcQ.rSum > arcTolerance , dist > cc.r , cc.r > arcMax]);
if (dc * arcQ.center.length / arcQ.rSum > camArcTolerance || dist > cc.r || cc.r > arcMax || radFault || zFault) {
// let debug = [ dc * arcQ.center.length / arcQ.rSum > camArcTolerance, dist > cc.r, cc.r > arcMax, radFault];
// console.log("point off the arc,",structuredClone(arcQ),radFault,zFault,[dc * arcQ.center.length / arcQ.rSum > camArcTolerance , dist > cc.r , cc.r > arcMax]);
if (arcQ.length === 4) {
// not enough points for an arc, drop first point and recalc center
camOut(arcQ.shift(), 1);
@ -904,7 +837,7 @@ export 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) > arcRes) { // enforce arcRes on initial angle
if (Math.abs(angle) > arcRadians) { // enforce arcRadians on initial angle
camOut(arcQ.shift(), 1);
}
}
@ -949,7 +882,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// console.trace("draining")
let arcPreviewRes = 64
if (!arcTolerance) {
if (!camArcTolerance) {
return;
}
@ -978,42 +911,36 @@ export function prepEach(widget, settings, print, firstPoint, update) {
// generate circle
// console.log("circle",{from, to,center});
to = forceCircle? from.clone().setZ(to.z) : from
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center })
.map(applyWidgetMovement);
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center });
// console.log({arcPoints})
camOut(from, 1);
camOut(to, gc, { center: center.sub(from), clockwise, arcPoints });
} else {
//if a non-circle arc
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center })
.map(applyWidgetMovement);
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, gc, { center: center.sub(from), clockwise, arcPoints });
lastPoint = to.clone();
polyLastPoint = to.clone();
}
} else {
//if q too short, emit as lines
for (let rec of arcQ) {
camOut(rec, 1);
}
lastPoint = arcQ.peek().clone();
polyLastPoint = arcQ.peek().clone();
}
arcQ.length = 0;
arcQ.center = undefined;
}
if (depthFirst) {
if (camDepthFirst) {
newLayer();
}
return lastPoint;
return polyLastPoint;
}
function depthRoughPath(start, depth, levels, tops, emitter, fit, ease) {
@ -1093,26 +1020,82 @@ export function prepEach(widget, settings, print, firstPoint, update) {
fromPoint = depthOutlinePath(fromPoint, depth + 1, levels, radius, emitter, !dir, ease);
return fromPoint;
}, {
weight: innerFirst,
weight: camInnerFirst,
swapdir: false
});
return start;
}
// last layer/move is to zmax
// re-inject that point into the last layer generated
if (lastPoint && newOutput.length) {
let lastLayer = newOutput.filter(layer => Array.isArray(layer)).peek();
if (Array.isArray(lastLayer)) {
printPoint = lastPoint.clone();
if (printPoint.z < zmax_outer) printPoint.setZ(zmax_outer);
print.addOutput(lastLayer, printPoint, 0, 0, tool);
// print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
console.log({ prep: widget, firstPoint: firstPoint?.clone(), wmx, wmy, printPoint, origin: origin.clone() });
// coming from a previous widget, use previous last point as starting point
// make top start offset configurable
printPoint = firstPoint || origin;
let ops = {
addGCode,
camOut,
depthOutlinePath,
depthRoughPath,
emitDrills,
emitTrace,
newLayer,
pocket,
poly2polyEmit,
polyEmit,
printPoint,
setDrill,
setLasering,
setSpindle,
setTolerance,
setTool,
tip2tipEmit,
widget,
zMax,
zSafe,
};
let opSum = 0;
let opTot = widget.camops.map(op => op.weight()).reduce((a, v) => a + v);
for (let op of widget.camops) {
setTolerance(0);
nextIsMove = true;
currentOp = op.op;
isIndex = currentOp.type === 'index';
isLathe = currentOp.type === 'lathe';
isRough = currentOp.type === 'rough';
isPocket = currentOp.type === 'pocket';
isContour = currentOp.type === 'contour' || (isPocket && currentOp.contour);
let weight = op.weight();
newLayer(op.op);
op.prepare(ops, (progress, message) => {
update((opSum + (progress * weight)) / opTot, message || op.type(), message);
});
opSum += weight;
if (tool && printPoint) {
newLayer();
if (!isIndex) {
layerPush(printPoint.clone().setZ(stockZClear), 0, 0, tool);
newLayer();
}
}
}
// last layer/move is to zMax
// re-inject that point into the last layer generated
if (printPoint && newOutput.length) {
let lastLayer = newOutput.filter(layer => Array.isArray(layer)).peek();
if (Array.isArray(lastLayer)) {
if (printPoint.z < stockZClear) printPoint.setZ(stockZClear);
print.addOutput(lastLayer, printPoint, 0, 0, tool);
}
}
// console.log("prepare output", newOutput);
// replace output single flattened layer with all points
print.output = newOutput;
return printPoint;
};

View file

@ -296,7 +296,7 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
opTot += opList.peek().weight();
}
// determing # of steps and step weighting for progress bar
// determine # of steps and step weighting for progress bar
for (let op of activeOps) {
if (op.type === '|') {
break;