2020-11-05 15:41:52 -05:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
|
|
|
|
let KIRI = self.kiri,
|
|
|
|
|
BASE = self.base,
|
|
|
|
|
UTIL = BASE.util,
|
|
|
|
|
CAM = KIRI.driver.CAM,
|
|
|
|
|
PRO = CAM.process,
|
|
|
|
|
POLY = BASE.polygons,
|
|
|
|
|
newSlice = KIRI.newSlice,
|
|
|
|
|
newPoint = BASE.newPoint,
|
|
|
|
|
newPolygon = BASE.newPolygon;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DRIVER SLICE CONTRACT
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} settings
|
|
|
|
|
* @param {Widget} widget
|
|
|
|
|
* @param {Function} output
|
|
|
|
|
*/
|
|
|
|
|
CAM.slice = function(settings, widget, onupdate, ondone) {
|
|
|
|
|
let conf = settings,
|
|
|
|
|
proc = conf.process,
|
2020-11-14 11:12:12 -05:00
|
|
|
stock = settings.stock || {},
|
|
|
|
|
hasStock = stock.x && stock.y && stock.z && proc.camStockOn,
|
2020-11-05 15:41:52 -05:00
|
|
|
sliceAll = widget.slices = [],
|
|
|
|
|
unitsName = settings.controller.units,
|
|
|
|
|
roughTool = new CAM.Tool(conf, proc.camRoughTool),
|
|
|
|
|
roughToolDiam = roughTool.fluteDiameter(),
|
|
|
|
|
drillTool = new CAM.Tool(conf, proc.camDrillTool),
|
|
|
|
|
drillToolDiam = drillTool.fluteDiameter(),
|
2020-11-14 11:12:12 -05:00
|
|
|
procFacing = proc.camRoughOn && proc.camZTopOffset && hasStock,
|
2020-11-05 15:41:52 -05:00
|
|
|
procRough = proc.camRoughOn && proc.camRoughDown,
|
2020-11-19 14:35:29 -05:00
|
|
|
procRoughIn = proc.camRoughIn,
|
2020-11-28 19:20:21 -05:00
|
|
|
procOutlineWide = proc.camOutlineWide,
|
|
|
|
|
procOutlineOut = proc.camOutlineOut,
|
2020-11-05 15:41:52 -05:00
|
|
|
procOutlineIn = proc.camOutlineIn,
|
|
|
|
|
procOutlineOn = proc.camOutlineOn,
|
|
|
|
|
procOutline = procOutlineOn && proc.camOutlineDown,
|
|
|
|
|
procContourX = proc.camContourXOn && proc.camOutlinePlunge,
|
|
|
|
|
procContourY = proc.camContourYOn && proc.camOutlinePlunge,
|
|
|
|
|
procContour = procContourX || procContourY,
|
|
|
|
|
procDrill = proc.camDrillingOn && proc.camDrillDown && proc.camDrillDownSpeed,
|
|
|
|
|
procDrillReg = proc.camDrillReg,
|
|
|
|
|
procTrace = proc.camTraceOn,
|
|
|
|
|
roughDown = procRough ? proc.camRoughDown : Infinity,
|
|
|
|
|
outlineDown = procOutline ? proc.camOutlineDown : Infinity,
|
|
|
|
|
sliceDepth = Math.max(0.1, Math.min(roughDown, outlineDown) / 3),
|
|
|
|
|
bounds = widget.getBoundingBox(),
|
|
|
|
|
mesh = widget.mesh,
|
|
|
|
|
zBottom = proc.camZBottom,
|
|
|
|
|
zMin = Math.max(bounds.min.z, zBottom),
|
|
|
|
|
zMax = bounds.max.z,
|
|
|
|
|
zThru = zBottom === 0 ? (proc.camZThru || 0) : 0,
|
2020-11-14 11:12:12 -05:00
|
|
|
ztOff = hasStock ? proc.camZTopOffset : 0,
|
2020-11-05 15:41:52 -05:00
|
|
|
camRoughStock = proc.camRoughStock,
|
|
|
|
|
camRoughDown = proc.camRoughDown,
|
2020-11-14 21:44:11 -05:00
|
|
|
minStepDown = Math.min(1, roughDown/3, outlineDown/3),
|
2020-11-05 15:41:52 -05:00
|
|
|
maxToolDiam = 0,
|
2020-11-25 22:50:44 -05:00
|
|
|
thruHoles,
|
|
|
|
|
tabs = settings.widget[widget.id].tab;
|
|
|
|
|
|
|
|
|
|
if (tabs) {
|
|
|
|
|
// make tab polygons
|
|
|
|
|
tabs.forEach(tab => {
|
|
|
|
|
let zero = BASE.newPoint(0,0,0);
|
|
|
|
|
let point = BASE.newPoint(tab.pos.x, tab.pos.y, tab.pos.z);
|
|
|
|
|
let poly = BASE.newPolygon().centerRectangle(zero, tab.dim.x, tab.dim.y);
|
|
|
|
|
let tslice = KIRI.newSlice(0);
|
|
|
|
|
let m4 = new THREE.Matrix4().makeRotationFromQuaternion(
|
|
|
|
|
new THREE.Quaternion(tab.rot._x, tab.rot._y, tab.rot._z, tab.rot._w)
|
|
|
|
|
);
|
|
|
|
|
poly.points = poly.points
|
|
|
|
|
.map(p => new THREE.Vector3(p.x,p.y,p.z).applyMatrix4(m4))
|
|
|
|
|
.map(v => BASE.newPoint(v.x, v.y, v.z));
|
|
|
|
|
poly.move(point);
|
|
|
|
|
tab.poly = poly;
|
|
|
|
|
// tslice.output().setLayer("tabs", 0xff0000).addPoly(poly);
|
|
|
|
|
// sliceAll.push(tslice);
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
|
2020-11-13 22:09:08 -05:00
|
|
|
if (stock.x && stock.y && stock.z) {
|
|
|
|
|
if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) {
|
|
|
|
|
return ondone('stock X too small for part. disable stock or use offset stock');
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
|
2020-11-13 22:09:08 -05:00
|
|
|
if (stock.y + 0.00001 < bounds.max.y - bounds.min.y) {
|
|
|
|
|
return ondone('stock Y too small for part. disable stock or use offset stock');
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
|
2020-11-13 22:09:08 -05:00
|
|
|
if (stock.z + 0.00001 < bounds.max.z - bounds.min.z) {
|
|
|
|
|
return ondone('stock Z too small for part. disable stock or use offset stock');
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sliceDepth <= 0.05) {
|
|
|
|
|
return ondone(`invalid slice depth (${sliceDepth.toFixed(2)} ${unitsName})`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(procFacing || procRough || procOutline || procContour || procDrill || procDrillReg)) {
|
|
|
|
|
return ondone("no processes selected");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (zMin >= bounds.max.z) {
|
|
|
|
|
return ondone(`invalid z bottom >= bounds z max ${bounds.max.z}`);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-06 19:59:18 -05:00
|
|
|
// allows progress output to me weighted and matched to processes
|
|
|
|
|
let ops = [ [ "mapping", 1.5 ] ];
|
|
|
|
|
if (procRough) ops.push([ "roughing", 1 ]);
|
|
|
|
|
if (procRough) ops.push([ "rough offset", 1 ]);
|
|
|
|
|
if (procOutline) ops.push([ "outline", 0.5 ]);
|
|
|
|
|
if (procContour) ops.push([ "contour", 4 ]);
|
|
|
|
|
let opsTot = ops.map(op => op[1]).reduce((a,v) => a + v);
|
|
|
|
|
let opSum = 0;
|
|
|
|
|
let opTot;
|
|
|
|
|
let opOn;
|
|
|
|
|
|
|
|
|
|
function nextOp() {
|
|
|
|
|
if (opOn) opSum += opOn[1];
|
|
|
|
|
opOn = ops.shift();
|
|
|
|
|
opTot = opOn[1] / opsTot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateOp(index, total, msg) {
|
|
|
|
|
onupdate((opSum/opsTot) + (index/total) * opTot, msg || opOn[0]);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
// TODO pass widget.isModified() on slice and re-use cache if false
|
|
|
|
|
// TODO pre-slice in background from client signal
|
|
|
|
|
// TODO same applies to topo map generation
|
2020-11-06 19:59:18 -05:00
|
|
|
nextOp();
|
2020-11-05 15:41:52 -05:00
|
|
|
let slicer = new KIRI.slicer2(widget.getPoints(), {
|
|
|
|
|
zlist: true,
|
|
|
|
|
zline: true
|
|
|
|
|
});
|
|
|
|
|
let tslices = [];
|
|
|
|
|
let tshadow = [];
|
2020-11-21 18:54:07 -05:00
|
|
|
let tzindex = slicer.interval(minStepDown, { fit: true, off: 0.01, down: true, flats: true });
|
2020-11-27 14:42:55 -05:00
|
|
|
let fakeTerrain = false;
|
|
|
|
|
if (tzindex.length > 100) {
|
|
|
|
|
fakeTerrain = tzindex[0];
|
|
|
|
|
tzindex = [ tzindex.pop() ];
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
let terrain = slicer.slice(tzindex, { each: (data, index, total) => {
|
|
|
|
|
tshadow = POLY.union(tshadow.appendAll(data.tops), 0.01, true);
|
|
|
|
|
tslices.push(data.slice);
|
2020-11-28 10:35:46 -05:00
|
|
|
if (false) {
|
2020-11-17 00:45:34 -05:00
|
|
|
const slice = data.slice;
|
|
|
|
|
sliceAll.push(slice);
|
|
|
|
|
slice.output()
|
2020-11-15 17:02:52 -05:00
|
|
|
.setLayer("debug", {line: 0x888800, thin: true })
|
|
|
|
|
.addPolys(POLY.setZ(tshadow.clone(true), data.z), { thin: true });
|
|
|
|
|
}
|
|
|
|
|
updateOp(index, total);
|
2020-11-05 15:41:52 -05:00
|
|
|
}, genso: true });
|
2020-11-27 14:42:55 -05:00
|
|
|
if (fakeTerrain) {
|
|
|
|
|
// to avoid compute for highly detailed maps, force all
|
|
|
|
|
// moves above part top by synthesizing a single terrain slice
|
|
|
|
|
terrain[0].z = fakeTerrain.z;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
let shadowTop = terrain[terrain.length - 1];
|
2020-11-21 20:52:52 -05:00
|
|
|
let center = tshadow[0].bounds.center();
|
2020-11-05 15:41:52 -05:00
|
|
|
|
|
|
|
|
if (procDrillReg) {
|
|
|
|
|
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
|
|
|
|
sliceDrillReg(settings, sliceAll, zThru);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// identify through holes
|
|
|
|
|
thruHoles = tshadow.map(p => p.inner || []).flat();
|
|
|
|
|
|
|
|
|
|
// create facing slices
|
2020-11-20 08:03:10 -05:00
|
|
|
if (procFacing || proc.camRoughTop) {
|
2020-11-21 18:54:07 -05:00
|
|
|
let shadow = tshadow.clone();
|
2020-11-19 14:35:29 -05:00
|
|
|
let inset = POLY.offset(shadow, (roughToolDiam / (procRoughIn ? 2 : 1)));
|
2020-11-05 15:41:52 -05:00
|
|
|
let facing = POLY.offset(inset, -(roughToolDiam * proc.camRoughOver), { count: 999, flat: true });
|
|
|
|
|
let zdiv = ztOff / roughDown;
|
|
|
|
|
let zstep = (zdiv % 1 > 0) ? ztOff / (Math.floor(zdiv) + 1) : roughDown;
|
2020-11-20 08:03:10 -05:00
|
|
|
if (proc.camRoughTop && ztOff === 0) {
|
2020-11-19 13:54:55 -05:00
|
|
|
// compensate for lack of z top offset in this scenario
|
|
|
|
|
ztOff = zstep;
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
for (let z = zMax + ztOff - zstep; z >= zMax; z -= zstep) {
|
|
|
|
|
let slice = shadowTop.slice.clone(false);
|
|
|
|
|
slice.z = z;
|
|
|
|
|
slice.camMode = PRO.LEVEL;
|
2020-11-08 16:44:32 -05:00
|
|
|
slice.camLines = POLY.setZ(facing.clone(true), slice.z);
|
2020-11-07 14:55:15 -05:00
|
|
|
slice.output()
|
|
|
|
|
.setLayer("facing", {face: 0, line: 0})
|
2020-11-08 16:44:32 -05:00
|
|
|
.addPolys(slice.camLines);
|
2020-11-05 15:41:52 -05:00
|
|
|
sliceAll.push(slice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create roughing slices
|
|
|
|
|
if (procRough) {
|
2020-11-06 19:59:18 -05:00
|
|
|
nextOp();
|
2020-11-05 15:41:52 -05:00
|
|
|
maxToolDiam = Math.max(maxToolDiam, roughToolDiam);
|
2020-11-17 20:01:29 -05:00
|
|
|
|
|
|
|
|
let flats = [];
|
2020-11-05 15:41:52 -05:00
|
|
|
let shadow = [];
|
|
|
|
|
let slices = [];
|
2020-11-24 17:35:39 -05:00
|
|
|
let indices = slicer.interval(roughDown, { down: true, min: zBottom, fit: true, off: 0.01 });
|
2020-11-24 19:21:30 -05:00
|
|
|
// shift out first (top-most) slice
|
|
|
|
|
indices.shift();
|
2020-11-15 15:32:15 -05:00
|
|
|
if (proc.camRoughFlat) {
|
2020-11-15 15:45:14 -05:00
|
|
|
let flats = Object.keys(slicer.zFlat)
|
|
|
|
|
.map(v => parseFloat(v).round(4))
|
|
|
|
|
.filter(v => v >= zBottom);
|
2020-11-15 15:32:15 -05:00
|
|
|
flats.forEach(v => {
|
|
|
|
|
if (!indices.contains(v)) {
|
|
|
|
|
indices.push(v);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
indices = indices.sort((a,b) => { return b - a });
|
|
|
|
|
// if layer is not on a flat and next one is,
|
|
|
|
|
// then move this layer up to mid-point to previous layer
|
|
|
|
|
// this is not perfect. the best method is to interpolate
|
|
|
|
|
// between flats so that each step is < step down. on todo list
|
|
|
|
|
for (let i=1; i<indices.length-1; i++) {
|
|
|
|
|
const prev = indices[i-1];
|
|
|
|
|
const curr = indices[i];
|
|
|
|
|
const next = indices[i+1];
|
|
|
|
|
if (!flats.contains(curr) && flats.contains(next)) {
|
2020-11-16 22:19:14 -05:00
|
|
|
// console.log('move',curr,'up toward',prev,'b/c next',next,'is flat');
|
2020-11-15 15:32:15 -05:00
|
|
|
indices[i] = next + ((prev - next) / 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-17 20:01:29 -05:00
|
|
|
} else {
|
|
|
|
|
// add flats to shadow
|
|
|
|
|
flats = Object.keys(slicer.zFlat)
|
|
|
|
|
.map(v => (parseFloat(v) - 0.01).round(5))
|
|
|
|
|
.filter(v => v > 0 && indices.indexOf(v) < 0);
|
|
|
|
|
indices = indices.appendAll(flats).sort((a,b) => b-a);
|
2020-11-15 15:32:15 -05:00
|
|
|
}
|
2020-11-21 20:31:37 -05:00
|
|
|
|
2020-11-15 17:02:52 -05:00
|
|
|
// console.log('indices', ...indices, {zBottom});
|
2020-11-15 15:05:45 -05:00
|
|
|
slicer.slice(indices, { each: (data, index, total) => {
|
2020-11-05 15:41:52 -05:00
|
|
|
shadow = POLY.union(shadow.appendAll(data.tops), 0.01, true);
|
2020-11-17 20:01:29 -05:00
|
|
|
if (flats.indexOf(data.z) >= 0) {
|
|
|
|
|
// exclude flats injected to complete shadow
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
data.shadow = shadow.clone(true);
|
|
|
|
|
data.slice.camMode = PRO.ROUGH;
|
|
|
|
|
data.slice.shadow = data.shadow;
|
|
|
|
|
// data.slice.tops[0].inner = data.shadow;
|
|
|
|
|
// data.slice.tops[0].inner = POLY.setZ(tshadow.clone(true), data.z);
|
|
|
|
|
slices.push(data.slice);
|
2020-11-06 19:59:18 -05:00
|
|
|
updateOp(index, total);
|
2020-11-05 15:41:52 -05:00
|
|
|
}, genso: true });
|
|
|
|
|
|
|
|
|
|
shadow = POLY.union(shadow.appendAll(shadowTop.tops), 0.01, true);
|
|
|
|
|
|
|
|
|
|
// inset or eliminate thru holes from shadow
|
|
|
|
|
shadow = POLY.flatten(shadow.clone(true), [], true);
|
|
|
|
|
thruHoles.forEach(hole => {
|
|
|
|
|
shadow = shadow.map(p => {
|
|
|
|
|
if (p.isEquivalent(hole)) {
|
|
|
|
|
// eliminate thru holes when roughing voids enabled
|
|
|
|
|
if (proc.camRoughVoid) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
2020-11-28 10:52:54 -05:00
|
|
|
// let po = POLY.offset([p], -(roughToolDiam + camRoughStock));
|
|
|
|
|
let po = POLY.offset([p], -(roughToolDiam / 2 + camRoughStock + 0.001));
|
2020-11-05 15:41:52 -05:00
|
|
|
return po ? po[0] : undefined;
|
|
|
|
|
} else {
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
}).filter(p => p);
|
|
|
|
|
});
|
|
|
|
|
shadow = POLY.nest(shadow);
|
|
|
|
|
|
|
|
|
|
// expand shadow by half tool diameter + stock to leave
|
2020-11-28 10:52:54 -05:00
|
|
|
// const sadd = procRoughIn ? roughToolDiam / 4 : roughToolDiam / 2;
|
|
|
|
|
const sadd = procRoughIn ? roughToolDiam / 2 : roughToolDiam / 2;
|
2020-11-11 00:41:04 -05:00
|
|
|
const shell = POLY.offset(shadow, sadd + camRoughStock);
|
2020-11-05 15:41:52 -05:00
|
|
|
|
2020-11-06 19:59:18 -05:00
|
|
|
nextOp();
|
|
|
|
|
slices.forEach((slice, index) => {
|
2020-11-11 00:41:04 -05:00
|
|
|
let offset = [shell.clone(true),slice.shadow.clone(true)].flat();
|
2020-11-05 15:41:52 -05:00
|
|
|
let flat = POLY.flatten(offset, [], true);
|
|
|
|
|
let nest = POLY.setZ(POLY.nest(flat), slice.z);
|
|
|
|
|
|
|
|
|
|
// inset offset array by 1/2 diameter then by tool overlap %
|
|
|
|
|
offset = POLY.offset(nest, [-(roughToolDiam / 2 + camRoughStock), -roughToolDiam * proc.camRoughOver], {
|
|
|
|
|
z: slice.z,
|
|
|
|
|
count: 999,
|
|
|
|
|
flat: true,
|
|
|
|
|
call: (polys, count, depth) => {
|
|
|
|
|
// used in depth-first path creation
|
|
|
|
|
polys.forEach(p => {
|
|
|
|
|
p.depth = depth;
|
|
|
|
|
if (p.inner) {
|
|
|
|
|
p.inner.forEach(p => p.depth = depth);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-11-15 17:02:52 -05:00
|
|
|
}) || [];
|
2020-11-11 15:38:14 -05:00
|
|
|
|
2020-11-11 00:41:04 -05:00
|
|
|
// add outside pass if not inside only
|
2020-11-19 14:35:29 -05:00
|
|
|
if (!procRoughIn) {
|
2020-11-15 14:43:22 -05:00
|
|
|
const outside = POLY.offset(shadow.clone(), roughToolDiam * proc.camRoughOver, {z: slice.z});
|
2020-11-11 00:41:04 -05:00
|
|
|
if (outside) {
|
|
|
|
|
offset.appendAll(outside);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-28 00:07:38 -05:00
|
|
|
if (tabs) {
|
|
|
|
|
tabs.forEach(tab => {
|
|
|
|
|
tab.off = POLY.expand([tab.poly], roughToolDiam / 2).flat();
|
|
|
|
|
});
|
|
|
|
|
offset = cutTabs(tabs, offset, slice.z);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-09 13:57:18 -05:00
|
|
|
if (!offset) return;
|
|
|
|
|
|
2020-11-28 10:52:54 -05:00
|
|
|
// elimate double inset on inners
|
|
|
|
|
offset.forEach(op => {
|
|
|
|
|
if (op.inner) {
|
|
|
|
|
let operim = op.perimeter();
|
|
|
|
|
let newinner = [];
|
|
|
|
|
op.inner.forEach(oi => {
|
|
|
|
|
if (Math.abs(oi.perimeter() - operim) > 0.01) {
|
|
|
|
|
newinner.push(oi);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
op.inner = newinner;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2020-11-08 16:44:32 -05:00
|
|
|
slice.camLines = offset;
|
2020-11-15 15:05:45 -05:00
|
|
|
if (true) slice.output()
|
|
|
|
|
.setLayer("slice", {line: 0xaaaa00}, true)
|
|
|
|
|
.addPolys(slice.topPolys())
|
|
|
|
|
// .setLayer("top shadow", {line: 0x0000aa})
|
|
|
|
|
// .addPolys(tshadow)
|
|
|
|
|
// .setLayer("rough shadow", {line: 0x00aa00})
|
|
|
|
|
// .addPolys(shadow)
|
|
|
|
|
// .setLayer("rough shell", {line: 0xaa0000})
|
|
|
|
|
// .addPolys(shell);
|
2020-11-07 14:55:15 -05:00
|
|
|
slice.output()
|
|
|
|
|
.setLayer("roughing", {face: 0, line: 0})
|
|
|
|
|
.addPolys(offset);
|
2020-11-06 19:59:18 -05:00
|
|
|
updateOp(index, slices.length);
|
2020-11-05 15:41:52 -05:00
|
|
|
});
|
2020-11-06 14:27:26 -05:00
|
|
|
|
2020-11-09 13:57:18 -05:00
|
|
|
sliceAll.appendAll(slices.filter(slice => slice.camLines));
|
2020-11-05 15:41:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create outline slices
|
|
|
|
|
if (procOutline) {
|
2020-11-06 19:59:18 -05:00
|
|
|
nextOp();
|
2020-11-05 15:41:52 -05:00
|
|
|
let outlineTool = new CAM.Tool(conf, proc.camOutlineTool);
|
|
|
|
|
let outlineToolDiam = outlineTool.fluteDiameter();
|
|
|
|
|
maxToolDiam = Math.max(maxToolDiam, outlineToolDiam);
|
|
|
|
|
|
|
|
|
|
let shadow = [];
|
|
|
|
|
let slices = [];
|
2020-11-24 17:35:39 -05:00
|
|
|
let indices = slicer.interval(outlineDown, { down: true, min: zBottom, fit: true, off: 0.01 });
|
2020-11-24 19:21:30 -05:00
|
|
|
// shift out first (top-most) slice
|
|
|
|
|
indices.shift();
|
2020-11-17 20:01:29 -05:00
|
|
|
// add flats to shadow
|
|
|
|
|
const flats = Object.keys(slicer.zFlat)
|
|
|
|
|
.map(v => (parseFloat(v) - 0.01).round(5))
|
|
|
|
|
.filter(v => v > 0 && indices.indexOf(v) < 0);
|
|
|
|
|
indices = indices.appendAll(flats).sort((a,b) => b-a);
|
2020-11-17 00:45:34 -05:00
|
|
|
// console.log('indices', ...indices, {zBottom, slicer});
|
2020-11-15 15:05:45 -05:00
|
|
|
slicer.slice(indices, { each: (data, index, total) => {
|
2020-11-05 15:41:52 -05:00
|
|
|
shadow = POLY.union(shadow.appendAll(data.tops), 0.01, true);
|
2020-11-17 20:01:29 -05:00
|
|
|
if (flats.indexOf(data.z) >= 0) {
|
|
|
|
|
// exclude flats injected to complete shadow
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
data.shadow = shadow.clone(true);
|
|
|
|
|
data.slice.camMode = PRO.OUTLINE;
|
|
|
|
|
data.slice.shadow = data.shadow;
|
|
|
|
|
// data.slice.tops[0].inner = data.shadow;
|
|
|
|
|
// data.slice.tops[0].inner = POLY.setZ(tshadow.clone(true), data.z);
|
|
|
|
|
slices.push(data.slice);
|
2020-11-17 00:45:34 -05:00
|
|
|
// data.slice.xray();
|
2020-11-06 19:59:18 -05:00
|
|
|
// onupdate(0.2 + (index/total) * 0.1, "outlines");
|
|
|
|
|
updateOp(index, total);
|
2020-11-05 15:41:52 -05:00
|
|
|
}, genso: true });
|
|
|
|
|
shadow = POLY.union(shadow.appendAll(shadowTop.tops), 0.01, true);
|
|
|
|
|
|
|
|
|
|
// extend cut thru (only when z bottom is 0)
|
|
|
|
|
if (zThru) {
|
|
|
|
|
let last = slices[slices.length-1];
|
|
|
|
|
let add = last.clone(true);
|
|
|
|
|
add.camMode = last.camMode;
|
|
|
|
|
add.tops.forEach(top => {
|
|
|
|
|
top.poly.setZ(add.z);
|
|
|
|
|
});
|
|
|
|
|
add.shadow = last.shadow.clone(true);
|
|
|
|
|
add.z -= zThru;
|
|
|
|
|
slices.push(add);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slices.forEach(slice => {
|
2020-11-17 20:01:29 -05:00
|
|
|
let tops = slice.shadow;
|
2020-11-28 19:20:21 -05:00
|
|
|
|
|
|
|
|
// outside only (use tshadow for entire cut)
|
|
|
|
|
if (procOutlineOut) {
|
|
|
|
|
tops = tshadow;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
let offset = POLY.expand(tops, outlineToolDiam / 2, slice.z);
|
2020-11-17 00:45:34 -05:00
|
|
|
if (!(offset && offset.length)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
// when pocket only, drop first outer poly
|
|
|
|
|
// if it matches the shell and promote inner polys
|
|
|
|
|
if (procOutlineIn) {
|
|
|
|
|
let shell = POLY.expand(tops.clone(), outlineToolDiam / 2);
|
|
|
|
|
offset = POLY.filter(offset, [], function(poly) {
|
|
|
|
|
if (poly.area() < 1) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
for (let sp=0; sp<shell.length; sp++) {
|
|
|
|
|
// eliminate shell only polys
|
|
|
|
|
if (poly.isEquivalent(shell[sp])) {
|
|
|
|
|
if (poly.inner) return poly.inner;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return poly;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
if (procOutlineWide) {
|
|
|
|
|
offset.slice().forEach(op => {
|
2020-11-06 07:26:58 -05:00
|
|
|
// clone removes inners but the real solution is
|
|
|
|
|
// to limit expanded shells to through holes
|
2020-11-30 00:19:28 -05:00
|
|
|
POLY.expand([op.clone(true)], outlineToolDiam * 0.5, slice.z, offset, 1);
|
2020-11-05 15:41:52 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-25 22:50:44 -05:00
|
|
|
if (tabs) {
|
|
|
|
|
tabs.forEach(tab => {
|
|
|
|
|
tab.off = POLY.expand([tab.poly], outlineToolDiam / 2).flat();
|
|
|
|
|
});
|
|
|
|
|
offset = cutTabs(tabs, offset, slice.z);
|
2020-11-05 15:41:52 -05:00
|
|
|
}
|
2020-11-17 00:45:34 -05:00
|
|
|
|
2020-11-10 19:56:34 -05:00
|
|
|
// offset.xout(`slice ${slice.z}`);
|
2020-11-08 16:44:32 -05:00
|
|
|
slice.camLines = offset;
|
2020-11-15 15:05:45 -05:00
|
|
|
if (true) slice.output()
|
|
|
|
|
.setLayer("slice", {line: 0xaaaa00}, true)
|
|
|
|
|
.addPolys(slice.topPolys())
|
2020-11-07 14:55:15 -05:00
|
|
|
slice.output()
|
|
|
|
|
.setLayer("outline", {face: 0, line: 0})
|
|
|
|
|
.addPolys(offset);
|
2020-11-05 15:41:52 -05:00
|
|
|
});
|
2020-11-06 14:27:26 -05:00
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
sliceAll.appendAll(slices);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we need topo for safe travel moves when roughing and outlining
|
|
|
|
|
// not generated when drilling-only. then all z moves use bounds max.
|
|
|
|
|
// also generates x and y contouring when selected
|
|
|
|
|
if (procContour) {
|
2020-11-06 19:59:18 -05:00
|
|
|
nextOp();
|
2020-11-05 15:41:52 -05:00
|
|
|
new CAM.Topo(widget, settings, {
|
2020-11-06 19:59:18 -05:00
|
|
|
// onupdate: (update, msg) => {
|
|
|
|
|
onupdate: (index, total, msg) => {
|
|
|
|
|
updateOp(index, total, msg);
|
|
|
|
|
// onupdate(0.30 + update * 0.50, msg || "create topo");
|
2020-11-05 15:41:52 -05:00
|
|
|
},
|
|
|
|
|
ondone: (slices) => {
|
|
|
|
|
sliceAll.appendAll(slices);
|
2020-11-11 23:27:58 -05:00
|
|
|
},
|
2020-11-21 20:52:52 -05:00
|
|
|
shadow: tshadow,
|
2020-11-25 22:50:44 -05:00
|
|
|
center: center,
|
|
|
|
|
tabs
|
2020-11-05 15:41:52 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-30 18:01:32 -05:00
|
|
|
// generate tracing offsets from chosen features
|
2020-11-05 15:41:52 -05:00
|
|
|
if (procTrace) {
|
2020-11-30 18:01:32 -05:00
|
|
|
// todo
|
|
|
|
|
// slice.camMode = PRO.TRACE;
|
2020-11-05 15:41:52 -05:00
|
|
|
}
|
|
|
|
|
|
2020-11-07 14:55:15 -05:00
|
|
|
if (procDrill) {
|
|
|
|
|
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
|
|
|
|
sliceDrill(drillTool, tslices, sliceAll);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-08 16:44:32 -05:00
|
|
|
sliceAll.forEach((slice, index) => slice.index = index);
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
// used in printSetup()
|
2020-11-28 00:07:38 -05:00
|
|
|
// used in CAM.prepare.getZClearPath()
|
|
|
|
|
// add tabs to terrain tops so moves avoid them
|
|
|
|
|
if (tabs) {
|
|
|
|
|
terrain.forEach(slab => {
|
|
|
|
|
tabs.forEach(tab => {
|
|
|
|
|
if (tab.pos.z + tab.dim.z/2 >= slab.z) {
|
|
|
|
|
let all = [...slab.tops, tab.poly];
|
|
|
|
|
slab.tops = POLY.union(all, 0, true);
|
|
|
|
|
// slab.slice.output()
|
|
|
|
|
// .setLayer("debug-tabs", {line: 0x880088, thin: true })
|
|
|
|
|
// .addPolys(POLY.setZ(slab.tops.clone(true), slab.z), { thin: true });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-11-05 15:41:52 -05:00
|
|
|
widget.terrain = terrain;
|
|
|
|
|
widget.maxToolDiam = maxToolDiam;
|
|
|
|
|
|
|
|
|
|
ondone();
|
|
|
|
|
};
|
|
|
|
|
|
2020-11-30 18:01:32 -05:00
|
|
|
CAM.traces = function computeTraces(settings, widget) {
|
|
|
|
|
let slicer = new KIRI.slicer2(widget.getPoints(), {
|
|
|
|
|
zlist: true,
|
|
|
|
|
zline: true
|
|
|
|
|
});
|
|
|
|
|
let proc = settings.process;
|
|
|
|
|
let traceTool = new CAM.Tool(settings, proc.camTraceTool);
|
|
|
|
|
let traceToolDiam = traceTool.fluteDiameter();
|
|
|
|
|
// widget.maxToolDiam = Math.max(maxToolDiam, traceToolDiam);
|
|
|
|
|
// unique merge of z flats and z line indices
|
|
|
|
|
let indices = [...new Set(Object.keys(slicer.zFlat)
|
|
|
|
|
.map(kv => parseFloat(kv).round(5))
|
|
|
|
|
.appendAll(Object.entries(slicer.zLine).map(ze => {
|
|
|
|
|
let [ zk, zv ] = ze;
|
|
|
|
|
return zv > 0 ? parseFloat(zk).round(5) : null;
|
|
|
|
|
})
|
|
|
|
|
.filter(v => v !== null)))]
|
|
|
|
|
.sort((a,b) => b - a);
|
|
|
|
|
let traces = [];
|
|
|
|
|
slicer.slice(indices, { each: (data, index, total) => {
|
2020-12-01 00:35:25 -05:00
|
|
|
// create separate slice for each poly (and inner)
|
|
|
|
|
BASE.polygons.flatten(data.tops,null,true).forEach(poly => {
|
|
|
|
|
let trace = KIRI.newSlice(data.z);
|
|
|
|
|
trace.output('trace')
|
|
|
|
|
.setLayer("trace", {line: 0x88aa55}, false)
|
|
|
|
|
.addPoly(poly);
|
|
|
|
|
traces.push(trace);
|
|
|
|
|
});
|
|
|
|
|
}, flatoff: 0, edges: true, openok: true });
|
2020-11-30 18:01:32 -05:00
|
|
|
widget.traces = traces;
|
|
|
|
|
};
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
// drilling op
|
|
|
|
|
function sliceDrill(tool, slices, output) {
|
|
|
|
|
let drills = [],
|
2020-11-07 14:55:15 -05:00
|
|
|
drillToolDiam = tool.fluteDiameter(),
|
2020-11-05 15:41:52 -05:00
|
|
|
centerDiff = drillToolDiam * 0.1,
|
|
|
|
|
area = (drillToolDiam/2) * (drillToolDiam/2) * Math.PI,
|
|
|
|
|
areaDelta = area * 0.05;
|
|
|
|
|
|
|
|
|
|
// for each slice, look for polygons with 98.5% circularity whose
|
|
|
|
|
// area is within the tolerance of a circle matching the tool diameter
|
|
|
|
|
slices.forEach(function(slice) {
|
2020-11-07 15:18:43 -05:00
|
|
|
let inner = slice.topPolyInners([]);
|
2020-11-05 15:41:52 -05:00
|
|
|
inner.forEach(function(poly) {
|
|
|
|
|
if (poly.circularity() >= 0.985 && Math.abs(poly.area() - area) <= areaDelta) {
|
|
|
|
|
let center = poly.circleCenter(),
|
|
|
|
|
merged = false,
|
|
|
|
|
closest = Infinity,
|
|
|
|
|
dist;
|
|
|
|
|
// TODO reject if inside camShellPolys (means there is material above)
|
|
|
|
|
// if (center.isInPolygon(camShellPolys)) return;
|
|
|
|
|
drills.forEach(function(drill) {
|
|
|
|
|
if (merged) return;
|
|
|
|
|
if ((dist = drill.last().distTo2D(center)) <= centerDiff) {
|
|
|
|
|
merged = true;
|
|
|
|
|
drill.push(center);
|
|
|
|
|
}
|
|
|
|
|
closest = Math.min(closest,dist);
|
|
|
|
|
});
|
|
|
|
|
if (!merged) {
|
|
|
|
|
drills.push(newPolygon().append(center));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// drill points to use center (average of all points) of the polygon
|
|
|
|
|
drills.forEach(function(drill) {
|
|
|
|
|
let center = drill.center(true),
|
|
|
|
|
slice = newSlice(0,null);
|
|
|
|
|
drill.points.forEach(function(point) {
|
|
|
|
|
point.x = center.x;
|
|
|
|
|
point.y = center.y;
|
|
|
|
|
});
|
|
|
|
|
slice.camMode = PRO.DRILL;
|
2020-11-08 16:44:32 -05:00
|
|
|
slice.camLines = [ drill ];
|
2020-11-07 14:55:15 -05:00
|
|
|
slice.output()
|
|
|
|
|
.setLayer("drill", {face: 0, line: 0})
|
|
|
|
|
.addPolys(drill);
|
2020-11-05 15:41:52 -05:00
|
|
|
output.append(slice);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// drill registration holes
|
|
|
|
|
function sliceDrillReg(settings, output, zThru) {
|
|
|
|
|
let proc = settings.process,
|
|
|
|
|
stock = settings.stock,
|
|
|
|
|
bounds = settings.bounds,
|
|
|
|
|
mx = (bounds.max.x + bounds.min.x) / 2,
|
|
|
|
|
my = (bounds.max.y + bounds.min.y) / 2,
|
|
|
|
|
mz = zThru || 0,
|
|
|
|
|
dx = (stock.x - (bounds.max.x - bounds.min.x)) / 4,
|
|
|
|
|
dy = (stock.y - (bounds.max.y - bounds.min.y)) / 4,
|
|
|
|
|
dz = stock.z,
|
|
|
|
|
points = [];
|
|
|
|
|
|
|
|
|
|
switch(proc.camDrillReg) {
|
|
|
|
|
case "x axis":
|
|
|
|
|
points.push(newPoint(bounds.min.x - dx, my, 0));
|
|
|
|
|
points.push(newPoint(bounds.max.x + dx, my, 0));
|
|
|
|
|
break;
|
|
|
|
|
case "y axis":
|
|
|
|
|
points.push(newPoint(mx, bounds.min.y - dy, 0));
|
|
|
|
|
points.push(newPoint(mx, bounds.max.y + dy, 0));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (points.length) {
|
|
|
|
|
let slice = newSlice(0,null), polys = [];
|
|
|
|
|
points.forEach(point => {
|
|
|
|
|
polys.push(newPolygon()
|
|
|
|
|
.append(point.clone().setZ(bounds.max.z))
|
|
|
|
|
.append(point.clone().setZ(bounds.max.z - stock.z - mz)));
|
|
|
|
|
});
|
|
|
|
|
slice.camMode = PRO.DRILL;
|
2020-11-08 16:44:32 -05:00
|
|
|
slice.camLines = polys;
|
2020-11-07 14:55:15 -05:00
|
|
|
slice.output()
|
|
|
|
|
.setLayer("register", {face: 0, line: 0})
|
|
|
|
|
.addPolys(polys);
|
2020-11-05 15:41:52 -05:00
|
|
|
output.append(slice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-25 22:50:44 -05:00
|
|
|
function cutTabs(tabs, offset, z) {
|
|
|
|
|
let noff = [];
|
|
|
|
|
tabs = tabs.filter(tab => z < tab.pos.z + tab.dim.z/2).map(tab => tab.off).flat();
|
2020-11-26 10:48:13 -05:00
|
|
|
offset.forEach(op => noff.appendAll( op.cut(POLY.union(tabs)) ));
|
2020-11-25 22:50:44 -05:00
|
|
|
return noff;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:41:52 -05:00
|
|
|
})();
|