285 lines
11 KiB
JavaScript
285 lines
11 KiB
JavaScript
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
import { CamOp } from './op.js';
|
|
import { Tool } from './tool.js';
|
|
import { generate as Topo } from './topo3.js';
|
|
import { newPolygon } from '../../../geo/polygon.js';
|
|
import { newSlice } from '../../core/slice.js';
|
|
import { polygons as POLY } from '../../../geo/polygons.js';
|
|
import { util as base_util } from '../../../geo/base.js';
|
|
import { calc_normal, calc_vertex } from '../../../geo/paths.js';
|
|
import { CAM } from './driver-be.js';
|
|
|
|
const DEG2RAG = Math.PI / 180;
|
|
|
|
class OpPocket extends CamOp {
|
|
constructor(state, op) {
|
|
super(state, op);
|
|
}
|
|
|
|
async slice(progress) {
|
|
const pocket = this;
|
|
const debug = false;
|
|
let { op, state } = this;
|
|
let { tool, rate, down, plunge, expand, contour, smooth, tolerance } = op;
|
|
let { ov_botz, ov_conv } = op;
|
|
let { settings, widget, addSlices, zBottom, zThru, tabs, color } = state;
|
|
let { updateToolDiams, cutTabs, healPolys, shadowAt, workarea } = state;
|
|
let { process } = settings;
|
|
zBottom = ov_botz ? workarea.bottom_stock + ov_botz : zBottom;
|
|
// generate tracing offsets from chosen features
|
|
let sliceOut;
|
|
let pockets = this.pockets = [];
|
|
let camTool = new Tool(settings, tool);
|
|
let toolDiam = camTool.fluteDiameter();
|
|
let toolOver = toolDiam * op.step;
|
|
let cutdir = ov_conv;
|
|
let engrave = contour && op.engrave;
|
|
let zTop = workarea.top_z;
|
|
if (contour) {
|
|
down = 0;
|
|
this.topo = await Topo({
|
|
// onupdate: (update, msg) => {
|
|
onupdate: (index, total, msg) => {
|
|
progress((index / total) * 0.9, msg);
|
|
},
|
|
ondone: (slices) => {
|
|
// console.log({ contour: slices });
|
|
},
|
|
contour: {
|
|
tool,
|
|
tolerance,
|
|
inside: true,
|
|
axis: "-"
|
|
},
|
|
state: state
|
|
});
|
|
}
|
|
updateToolDiams(toolDiam);
|
|
if (tabs) {
|
|
tabs.forEach(tab => {
|
|
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
|
});
|
|
}
|
|
function newPocket() {
|
|
pockets.push(sliceOut = []);
|
|
}
|
|
function newSliceOut(z) {
|
|
let slice = newSlice(z);
|
|
sliceOut.push(slice);
|
|
return slice;
|
|
}
|
|
function clearZ(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 ];
|
|
if (engrave) {
|
|
toolDiam = toolOver;
|
|
}
|
|
if (contour) {
|
|
expand = engrave ? 0 : expand;
|
|
} else if (expand) {
|
|
polys = POLY.offset(polys, expand);
|
|
}
|
|
let zpro = 0, zinc = 1 / (polys.length * zs.length);
|
|
for (let poly of polys) {
|
|
newPocket();
|
|
for (let z of zs) {
|
|
let clip = [], shadow;
|
|
if (contour) {
|
|
if (smooth) {
|
|
clip = POLY.offset(POLY.offset([ poly ], smooth), -smooth);
|
|
} else {
|
|
clip = [ poly ];
|
|
}
|
|
} else {
|
|
shadow = shadowAt(z);
|
|
if (smooth) {
|
|
shadow = POLY.setZ(POLY.offset(POLY.offset(shadow, smooth), -smooth), 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 = engrave ? 1 : 999;
|
|
slice.camTrace = { tool, rate, plunge };
|
|
if (toolDiam) {
|
|
const offs = contour ?
|
|
[ expand || (-0.02), -toolOver ] :
|
|
[ -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);
|
|
if (contour) {
|
|
slice.camLines = pocket.conform(slice.camLines, op.refine, engrave, pct => {
|
|
progress(0.9 + (zpro + zinc * pct) * 0.1, "conform");
|
|
});
|
|
}
|
|
slice.output()
|
|
.setLayer("pocket", {line: color}, false)
|
|
.addPolys(slice.camLines)
|
|
if (debug && shadow) slice.output()
|
|
.setLayer("pocket shadow", {line: 0xff8811}, false)
|
|
.addPolys(shadow)
|
|
if (!contour) {
|
|
progress(zpro, "pocket");
|
|
}
|
|
zpro += zinc;
|
|
addSlices(slice);
|
|
}
|
|
}
|
|
}
|
|
let surfaces = op.surfaces[widget.id] || [];
|
|
let vert = widget.getGeoVertices({ unroll: true, translate: true }).map(v => v.round(4));
|
|
// let vert = widget.getVertices().array.map(v => v.round(4));
|
|
let outline = [];
|
|
let faces = CAM.surface_find(widget, surfaces, (op.follow || 5) * DEG2RAG);
|
|
let zmin = Infinity;
|
|
let j=0, k=faces.length;
|
|
for (let face of faces) {
|
|
let i = face * 9;
|
|
outline.push(newPolygon()
|
|
.add(vert[i++], vert[i++], zmin = Math.min(zmin, vert[i++]))
|
|
.add(vert[i++], vert[i++], zmin = Math.min(zmin, vert[i++]))
|
|
.add(vert[i++], vert[i++], zmin = Math.min(zmin, vert[i++]))
|
|
);
|
|
}
|
|
zmin = Math.max(zBottom, zmin);
|
|
outline = POLY.union(outline, 0.0001, true);
|
|
outline = POLY.setWinding(outline, cutdir, false);
|
|
outline = healPolys(outline);
|
|
if (smooth) {
|
|
outline = POLY.offset(POLY.offset(outline, smooth), -smooth);
|
|
}
|
|
if (outline.length) {
|
|
// option to skip interior features (holes, pillars)
|
|
if (op.outline) {
|
|
POLY.clearInner(outline);
|
|
}
|
|
if (debug) newSliceOut(zmin).output()
|
|
.setLayer("pocket area", {line: 0x1188ff}, false)
|
|
.addPolys(outline)
|
|
clearZ(outline, zmin + 0.0001, down);
|
|
progress(1, "pocket");
|
|
}
|
|
}
|
|
|
|
// mold cam output lines to the surface of the topo offset by tool geometry
|
|
conform(camLines, refine, engrave, progress) {
|
|
const topo = this.topo;
|
|
// re-segment polygon to a higher resolution
|
|
const hirez = camLines.map(p => p.segment(topo.tolerance * 2));
|
|
// walk points and offset from surface taking into account tool geometry
|
|
let steps = hirez.length;
|
|
let iter = 0;
|
|
for (let poly of hirez) {
|
|
for (let point of poly.points) {
|
|
point.z = engrave ? topo.zAtXY(point.x, point.y) : topo.toolAtXY(point.x, point.y);
|
|
}
|
|
progress((iter++ / steps) * 0.8);
|
|
}
|
|
steps = steps * refine;
|
|
iter = 0;
|
|
// walk points noting z deltas and smoothing z sawtooth patterns
|
|
for (let j=0; j<refine; j++) {
|
|
for (let poly of hirez) {
|
|
const points = poly.points, length = points.length;
|
|
let sn = []; // segment normals
|
|
for (let i=0; i<length; i++) {
|
|
let p1 = points[i];
|
|
let p2 = points[(i + 1) % length];
|
|
sn.push(calc_normal(p1, p2));
|
|
}
|
|
let vn = []; // vertex normals
|
|
for (let i=0; i<length; i++) {
|
|
let n1 = sn[(i + length - 1) % length];
|
|
let n2 = sn[i];
|
|
let vi = calc_vertex(n1, n2, 1);
|
|
vn.push(vi);
|
|
let vl = Math.abs(1 - vi.vl).round(2);
|
|
// vl should be close to zero on smooth / continuous curves
|
|
// factoring out hard turns, we smooth the z using the weighted
|
|
// z values of the points before and after the current point
|
|
if (vl === 0) {
|
|
let p0 = points[(i + length - 1) % length];
|
|
let p1 = points[i];
|
|
let p2 = points[(i + 1) % length];
|
|
p1.z = (p0.z + p2.z + p1.z) / 3;
|
|
}
|
|
}
|
|
|
|
progress((iter++ / steps) * 0.2 + 0.8);
|
|
}
|
|
}
|
|
// return hirez.map(p => p.midpoints(topo.tolerance * 8));
|
|
return hirez;
|
|
}
|
|
|
|
prepare(ops, progress) {
|
|
let { op, state, pockets } = this;
|
|
let { setTool, setSpindle, setTolerance, sliceOutput, getPrintPoint } = ops;
|
|
let { process } = state.settings;
|
|
|
|
setTool(op.tool, op.rate);
|
|
setSpindle(op.spindle);
|
|
|
|
if (this.topo) {
|
|
setTolerance(this.topo.tolerance);
|
|
}
|
|
|
|
// 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;
|
|
sliceOutput(min.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")
|
|
});
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export { OpPocket };
|