checkpoint topo and tool refactoring

This commit is contained in:
Stewart Allen 2020-11-05 14:47:18 -05:00
commit c3120263f0
9 changed files with 582 additions and 474 deletions

2
app.js
View file

@ -305,6 +305,8 @@ const script = {
"mode/fdm/driver",
"mode/sla/driver",
"mode/cam/driver",
"mode/cam/tool",
"mode/cam/topo",
"mode/laser/driver",
"kiri/widget",
"kiri/print",

View file

@ -2,7 +2,7 @@
## `C` cosmetic, `F` functional, `P` performance, `B` bug fix
* `F` auto unit conversion in/out of fields so display = pref. but always store mm?
* `F` ui control of import + decimation + precision
* `F` implement an in-app bug reporting system
* `F` extend mesh object to store raw + annotations (rot,scale,pos)
* share raw data w/ dups, encode/decode

View file

@ -254,14 +254,22 @@
PRO.swap = function(x,y) {
let poly = this,
points = poly.points,
length = points.length;
poly.bounds = new Bounds();
length = points.length,
bounds = new Bounds();
if (x) {
for (let i=0; i<length; i++) {
let p = points[i];
if (x) p.swapXZ();
else if (y) p.swapYZ();
poly.bounds.update(p);
p.swapXZ();
bounds.update(p);
}
} else if (y) {
for (let i=0; i<length; i++) {
let p = points[i];
p.swapYZ();
bounds.update(p);
}
}
poly.bounds = bounds;
if (poly.inner) poly.inner.forEach(function(i) {
i.swap(x,y);
});
@ -408,7 +416,11 @@
p.move(offset);
bounds.update(p);
});
if (scope.inner) scope.inner.forEach(function(p) { p.move(offset) });
if (scope.inner) {
scope.inner.forEach(function(p) {
p.move(offset);
});
}
return scope;
};

View file

@ -95,7 +95,6 @@
flats: encode(this.flats, state),
solids: encode(this.solids, state),
supports: encode(this.supports, state)
//groups: encode(this.groups, state)
};
};
@ -109,7 +108,6 @@
slice.flats = decode(v.flats, state);
slice.solids = decode(v.solids, state);
slice.supports = decode(v.supports, state);
//slice.groups = decode(v.groups, state);
return slice;
});

View file

@ -116,7 +116,10 @@
}
function onBooleanClick() {
// prevent hiding elements in device editor on clicks
if (!API.modal.visible()) {
UC.refresh();
}
API.conf.update();
DOC.activeElement.blur();
}

View file

@ -16,26 +16,35 @@
class Slicer {
constructor(points, options) {
this.options = {};
if (points) {
this.setPoints(points, options);
}
}
setOptions(options) {
Object.assign(this.options, options || {});
return this.options;
}
setPoints(points, options) {
this.points = points;
this.bounds = null;
this.points = this.swap(points, options);
this.zFlat = {}; // accumulated flat area at z height
this.zLine = {}; // count of z coplanar lines
this.zList = {}; // count of z values for auto slicing
this.zSum = 0; // used in bucketing calculations
return this
.computeBounds()
.computeFeatures(options)
.computeFeatures()
.computeBuckets();
}
computeBounds() {
if (!this.bounds) {
this.bounds = new THREE.Box3();
this.bounds.setFromPoints(this.points);
}
return this;
}
@ -43,7 +52,7 @@
// these are used for auto-slicing in laser
// and to flats detection in CAM mode
computeFeatures(options) {
const opt = options || {};
const opt = this.setOptions(options);
const points = this.points;
const bounds = this.bounds;
const zFlat = this.zFlat;
@ -169,7 +178,7 @@
// slice through points at given Z and return polygons
slice(z, options, index, total, mark) {
let opt = options || {};
const opt = this.setOptions(options);
if (Array.isArray(z)) {
const mark = UTIL.time();
@ -238,8 +247,14 @@
retn.lines = removeDuplicateLines(lines);
retn.tops = POLY.nest(connectLines(retn.lines));
if (opt.swapX || opt.swapY) {
this.unswap(opt.swapX, opt.swapY, retn.lines, retn.tops);
}
if (opt.genso) {
retn.slice = newSlice(z).addTops(retn.tops);
retn.slice.lines = retn.lines;
retn.slice.groups = retn.tops;
}
}
@ -250,6 +265,91 @@
return retn;
}
swap(points, options) {
const opt = this.setOptions(options);
if (!(opt && (opt.swapX || opt.swapY))) {
return points;
}
let btmp = new THREE.Box3(),
pref = {},
cached;
points = points.slice();
btmp.setFromPoints(points);
if (opt.swapX) this.ox = -btmp.max.x;
if (opt.swapY) this.oy = -btmp.max.y;
// array re-uses points so we need
// to be careful not to alter a point
// more than once
for (let p, index=0; index<points.length; index++) {
p = points[index];
cached = pref[p.key];
// skip points already altered
if (cached) {
points[index] = cached;
continue;
}
cached = p.clone();
if (opt.swapX) cached.swapXZ();
if (opt.swapY) cached.swapYZ();
cached.rekey();
pref[p.key] = cached;
points[index] = cached;
}
// update temp bounds from new points
btmp.setFromPoints(points);
for (let p, index=0; index<points.length; index++) {
p = points[index];
if (p.mod === 1) continue;
p.mod = 1;
p.z -= btmp.min.z;
}
// update temp bounds from points with altered Z
btmp.setFromPoints(points);
this.bounds = btmp;
return points;
}
unswap(swapX, swapY, lines, polys) {
let move = {x: this.ox || 0, y: this.oy || 0, z: 0};
// unswap lines
let llen = lines.length,
idx, line;
// shared points causing problems
for (idx=0; idx<llen; idx++) {
line = lines[idx];
line.p1 = line.p1.clone();
line.p2 = line.p2.clone();
}
for (idx=0; idx<llen; idx++) {
line = lines[idx];
if (swapX) {
line.p1.swapXZ();
line.p2.swapXZ();
}
if (swapY) {
line.p1.swapYZ();
line.p2.swapYZ();
}
line.p1.move(move);
line.p2.move(move);
}
polys.forEach(poly => {
poly.swap(swapX, swapY);
poly.move(move);
});
}
interval(step, options) {
let opt = options || {},
bounds = this.bounds,

View file

@ -17,9 +17,7 @@
sliceRender,
printSetup,
printExport,
printRender,
getToolById,
getToolDiameter,
printRender
},
CPRO = CAM.process = {
LEVEL: 1,
@ -66,99 +64,10 @@
});
}
function getToolById(settings, id) {
for (let i=0, t=settings.tools; i<t.length; i++) {
if (t[i].id === id) return t[i];
}
return null;
};
function getToolDiameter(settings, id) {
let tool = getToolById(settings, id);
if (!tool) return 0;
return (tool.metric ? 1 : 25.4) * tool.flute_diam;
};
function getToolTipDiameter(settings, id) {
let tool = getToolById(settings, id);
if (!tool) return 0;
return (tool.metric ? 1 : 25.4) * tool.taper_tip;
};
function getToolShaftDiameter(settings, id) {
let tool = getToolById(settings, id);
if (!tool) return 0;
return (tool.metric ? 1 : 25.4) * tool.shaft_diam;
};
function getToolShaftOffset(settings, id) {
let tool = getToolById(settings, id);
if (!tool) return 0;
return (tool.metric ? 1 : 25.4) * tool.flute_len;
};
function createToolProfile(settings, id, topo) {
// generate tool profile
let tool = getToolById(settings, id),
ball = tool.type === "ballmill",
taper = tool.type === "tapermill",
shaft_diameter = getToolShaftDiameter(settings, id),
shaft_radius = shaft_diameter / 2,
shaft_pix_float = shaft_diameter / topo.resolution,
shaft_pix_int = Math.round(shaft_pix_float),
shaft_radius_pix_float = shaft_pix_float / 2,
shaft_offset = getToolShaftOffset(settings, id),
flute_diameter = getToolDiameter(settings, id),
flute_radius = flute_diameter / 2,
flute_pix_float = flute_diameter / topo.resolution,
// flute_pix_int = Math.round(flute_pix_float),
flute_radius_pix_float = flute_pix_float / 2,
tip_diameter = getToolTipDiameter(settings, id),
tip_pix_float = tip_diameter / topo.resolution,
tip_radius_pix_float = tip_pix_float / 2,
tip_max_radius_offset = flute_radius_pix_float - tip_radius_pix_float,
profile_pix_iter = shaft_pix_int + (1 - shaft_pix_int % 2),
toolCenter = (shaft_pix_int - (shaft_pix_int % 2)) / 2,
toolOffset = [],
larger_shaft = shaft_diameter - flute_diameter > 0.001;
// console.log({
// tool: tool.name,
// rez: topo.resolution,
// diam: flute_diameter,
// pix: flute_pix_float.toFixed(2),
// rad: flute_radius_pix_float.toFixed(2),
// tocks: profile_pix_iter,
// shaft_offset,
// larger_shaft
// });
// for each point in tool profile, check inside radius
for (let x = 0; x < profile_pix_iter; x++) {
for (let y = 0; y < profile_pix_iter; y++) {
let dx = x - toolCenter,
dy = y - toolCenter,
dist_from_center = Math.sqrt(dx * dx + dy * dy);
if (dist_from_center <= flute_radius_pix_float) {
// console.log({x,y,dx,dy,dist:dist_from_center,ln:dbl.length})
// flute offset points
let z_offset = 0;
if (ball) {
z_offset = (1 - Math.cos((dist_from_center / flute_radius_pix_float) * HPI)) * -flute_radius;
} else if (taper && dist_from_center >= tip_radius_pix_float) {
z_offset = ((dist_from_center - tip_radius_pix_float) / tip_max_radius_offset) * -shaft_offset;
}
toolOffset.push(dx, dy, z_offset);
} else if (shaft_offset && larger_shaft && dist_from_center <= shaft_radius_pix_float) {
// shaft offset points
toolOffset.push(dx, dy, -shaft_offset);
}
}
}
return toolOffset;
};
function getMaxZBetween(terrain, x1, y1, x2, y2, z, zadd, off, over) {
/**
* return tool Z clearance height for a line segment movement path
*/
function getZClearPath(terrain, x1, y1, x2, y2, z, zadd, off, over) {
let maxz = z;
let check = [];
for (let i=0; i<terrain.length; i++) {
@ -199,69 +108,6 @@
return maxz;
}
/**
* find highest z on a line segment
* x,y are in platform coodinates
*/
function getTopoZPathMax(widget, profile, x1, y1, x2, y2) {
let topo = widget.topo,
rez = topo.resolution,
bounds = widget.getBoundingBox(),
dx = x2-x1,
dy = y2-y1,
md = Math.max(Math.abs(dx),Math.abs(dy)),
mi = md / rez,
ix = dx / mi,
iy = dy / mi,
zmax = 0;
// implement fast grid fingerprinting. if no z variance within
// the scan area (or min scan delta set from last point), then
// use the last computed zmax and carry on
while (mi-- > 0) {
let tx1 = Math.round((x1 - bounds.min.x) / rez),
ty1 = Math.round((y1 - bounds.min.y) / rez);
zmax = Math.max(zmax, getMaxTopoToolZ(topo, profile, tx1, ty1, true));
x1 += ix;
y1 += iy;
}
return zmax;
};
/**
* x,y are in topo grid int coordinates
*/
function getMaxTopoToolZ(topo, profile, x, y, floormax) {
let tv, tx, ty, tz, gv, i = 0, mz = -1;
const sx = topo.stepsx, sy = topo.stepsy, xl = sx - 1, yl = sy - 1;
while (i < profile.length) {
// tool profile point x, y, and z offsets
let tx = profile[i++] + x;
let ty = profile[i++] + y;
let tz = profile[i++];
if (tx < 0 || tx > xl || ty < 0 || ty > yl) {
// if outside max topo steps, use 0
gv = 0;
} else {
// lookup grid value @ tx, ty
gv = topo.data[tx * sy + ty] || 0;
}
// inside the topo but off the part
if (floormax && gv === 0) {
// return topo.bounds.max.z;
gv = topo.bounds.max.z;
}
// update the rest
mz = Math.max(tz + gv, mz);
}
return Math.max(mz,0);
};
/**
* call out to slicer
*/
@ -289,257 +135,6 @@
return selected;
}
/**
* @param {Widget} widget
* @param {Object} settings
* @param {Function} ondone
* @param {Function} onupdate
*/
function generateTopoMap(widget, settings, ondone, onupdate) {
let mesh = widget.mesh,
proc = settings.process,
outp = settings.process,
resolution = outp.camTolerance,
diameter = getToolDiameter(settings, proc.camContourTool),
tool = getToolById(settings, proc.camContourTool),
toolStep = diameter * proc.camContourOver,
traceJoin = diameter / 2,
pocketOnly = proc.camOutlinePocket,
bounds = widget.getBoundingBox().clone(),
minX = bounds.min.x,// - diameter,
maxX = bounds.max.x,// + diameter,
minY = bounds.min.y,// - diameter,
maxY = bounds.max.y,// + diameter,
zBottom = outp.camZBottom,
boundsX = maxX - minX,
boundsY = maxY - minY,
maxangle = proc.camContourAngle,
curvesOnly = proc.camContourCurves,
R2A = 180 / Math.PI,
stepsx = Math.ceil(boundsX / resolution),
stepsy = Math.ceil(boundsY / resolution),
data = new Float32Array(stepsx * stepsy),
topo = widget.topo = {
data: data,
stepsx: stepsx,
stepsy: stepsy,
bounds: bounds,
diameter: diameter,
resolution: resolution
},
toolOffset = createToolProfile(settings, proc.camContourTool, topo),
newslices = [],
newlines,
newtop,
newtrace,
sliceout,
latent,
lastP,
slice, lx, ly,
startTime = time();
// return highest z within tools radius
function maxzat(x,y) {
return getMaxTopoToolZ(topo, toolOffset, x, y);
}
function push_point(x,y,z) {
let newP = newPoint(x,y,z);
if (lastP && lastP.z === z) {
if (curvesOnly) {
end_poly();
} else {
latent = newP;
}
} else {
if (latent) {
newtrace.push(latent);
latent = null;
}
newtrace.push(newP);
}
lastP = newP;
}
function end_poly() {
if (latent) {
newtrace.push(latent);
}
if (newtrace.length > 0) {
// add additional constraint on min perimeter()
if (newtrace.length > 1) {
sliceout.push(newtrace);
}
newtrace = newPolygon().setOpen();
}
latent = undefined;
lastP = undefined;
}
function topoSlicesDone(slices) {
let gridx = 0,
gridy,
gridi, // index
gridv, // value
zMin = Math.max(bounds.min.z, zBottom) + 0.0001,
x, y, tv, ltv;
// for each Y slice, find z grid value (x/z swapped)
for (let j=0, jl=slices.length; j<jl; j++) {
let slice = slices[j],
lines = slice.lines;
gridy = 0;
// slices have x/z swapped
for (y = minY; y < maxY && gridy < stepsy; y += resolution) {
gridi = gridx * stepsy + gridy;
gridv = data[gridi] || 0;
// strategy using raw lines (faster slice, but more lines)
for (let i=0, il=lines.length; i<il; i++) {
let line = lines[i], p1 = line.p1, p2 = line.p2;
if (
(p1.z > zMin || p2.z > zMin) && // one endpoint above 0
(p1.z > gridv || p2.z > gridv) && // one endpoint above gridv
((p1.y <= y && p2.y >= y) || // one endpoint left
(p2.y <= y && p1.y >= y)) // one endpoint right
) {
let dy = p1.y - p2.y,
dz = p1.z - p2.z,
pct = (p1.y - y) / dy,
nz = p1.z - (dz * pct);
if (nz > gridv) {
gridv = data[gridi] = Math.max(nz, zMin);
}
}
}
gridy++;
}
gridx++;
onupdate(0.20 + (gridx/stepsx) * 0.50, "trace surface");
}
// x contouring
if (proc.camContourXOn) {
startTime = time();
// emit slice per X
for (x = minX; x <= maxX; x += toolStep) {
gridx = Math.round(((x - minX) / boundsX) * stepsx);
ly = gridy = 0;
slice = newSlice(gridx, mesh.newGroup ? mesh.newGroup() : null);
slice.camMode = CPRO.CONTOUR_X;
slice.lines = newlines = [];
newtop = slice.addTop(newPolygon().setOpen()).poly;
newtrace = newPolygon().setOpen();
sliceout = slice.tops[0].traces = [ ];
for (y = minY; y < maxY; y += resolution) {
if (pocketOnly && (data[gridx * stepsy + gridy] || 0) === 0) {
end_poly();
gridy++;
ly = 0;
continue;
}
tv = maxzat(gridx, gridy);
if (tv === 0) {
end_poly();
gridy++;
ly = 0;
continue;
}
if (ly) {
if (mesh) newlines.push(newLine(
newPoint(x,ly,ltv),
newPoint(x,y,tv)
));
let ang = Math.abs((Math.atan2(ltv - tv, resolution) * R2A) % 90);
// over max angle, turn into square edge (up or down)
if (ang > maxangle) {
if (ltv > tv) {
// down = forward,down
push_point(x,y,ltv);
} else {
// up = up,forward
push_point(x,ly,tv);
}
}
}
push_point(x,y,tv);
ly = y;
ltv = tv;
gridy++;
}
end_poly();
if (sliceout.length > 0) {
newslices.push(slice);
}
onupdate(0.70 + (gridx/stepsx) * 0.15, "contour x");
}
}
// y contouring
if (proc.camContourYOn) {
startTime = time();
// emit slice per Y
for (y = minY; y <= maxY; y += toolStep) {
gridy = Math.round(((y - minY) / boundsY) * stepsy);
lx = gridx = 0;
slice = newSlice(gridy, mesh.newGroup ? mesh.newGroup() : null);
slice.camMode = CPRO.CONTOUR_Y;
slice.lines = newlines = [];
newtop = slice.addTop(newPolygon().setOpen()).poly;
newtrace = newPolygon().setOpen();
sliceout = slice.tops[0].traces = [ ];
for (x = minX; x <= maxX; x += resolution) {
if (pocketOnly && (data[gridx * stepsy + gridy] || 0) === 0) {
end_poly();
gridx++;
ly = 0;
continue;
}
tv = maxzat(gridx, gridy);
if (tv === 0) {
end_poly();
gridx++;
lx = 0;
continue;
}
if (lx) {
if (mesh) newlines.push(newLine(
newPoint(lx,y,ltv),
newPoint(x,y,tv)
));
let ang = Math.abs((Math.atan2(ltv - tv, resolution) * R2A) % 90);
// over max angle, turn into square edge (up or down)
if (ang > maxangle) {
if (ltv > tv) {
// down = forward,down
push_point(x,y,ltv);
} else {
// up = up,forward
push_point(lx,y,tv);
}
}
}
push_point(x,y,tv);
lx = x;
ltv = tv;
gridx++;
}
end_poly();
if (sliceout.length > 0) {
newslices.push(slice);
}
onupdate(0.85 + (gridy/stepsy) * 0.15, "contour y");
}
}
ondone(newslices);
}
// slices progress left-to-right along the X axis
doSlicing(widget, {height:resolution, swapX:true, topo:true}, topoSlicesDone, function(update) {
onupdate(0.0 + update * 0.20, "topo slice");
});
}
/**
* Find top paths to trace when using ball and taper mills
* in waterline outlining and tracing modes.
@ -696,18 +291,18 @@
proc = conf.process,
sliceAll = widget.slices = [],
unitsName = settings.controller.units,
roughToolDiam = getToolDiameter(conf, proc.camRoughTool),
outlineToolDiam = getToolDiameter(conf, proc.camOutlineTool),
contourToolDiam = getToolDiameter(conf, proc.camContourTool),
drillToolDiam = getToolDiameter(conf, proc.camDrillTool),
roughTool = new CAM.Tool(conf, proc.camRoughTool),
roughToolDiam = roughTool.fluteDiameter(),
drillTool = new CAM.Tool(conf, proc.camDrillTool),
drillToolDiam = drillTool.fluteDiameter(),
procFacing = proc.camRoughOn && proc.camZTopOffset,
procRough = proc.camRoughOn && proc.camRoughDown && roughToolDiam,
procRough = proc.camRoughOn && proc.camRoughDown,
procOutlineIn = proc.camOutlineIn,
procOutlineOn = proc.camOutlineOn,
procOutlineWide = proc.camOutlineWide,
procOutline = procOutlineOn && proc.camOutlineDown && outlineToolDiam,
procContourX = proc.camContourXOn && proc.camOutlinePlunge && contourToolDiam,
procContourY = proc.camContourYOn && proc.camOutlinePlunge && contourToolDiam,
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,
@ -784,12 +379,12 @@
if (procDrillReg) {
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
sliceDrillReg(settings, widget, sliceAll, zThru);
sliceDrillReg(settings, sliceAll, zThru);
}
if (procDrill) {
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
sliceDrill(settings, widget, tslices, sliceAll);
sliceDrill(drillTool, tslices, sliceAll);
}
// identify through holes
@ -884,7 +479,10 @@
// create outline slices
if (procOutline) {
let outlineTool = new CAM.Tool(conf, proc.camOutlineTool);
let outlineToolDiam = outlineTool.fluteDiameter();
maxToolDiam = Math.max(maxToolDiam, outlineToolDiam);
let shadow = [];
let slices = [];
slicer.slice(slicer.interval(outlineDown, { down: true, min: zBottom }), { each: (data, index, total) => {
@ -953,12 +551,16 @@
// 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)
generateTopoMap(widget, settings, function(slices) {
sliceAll.appendAll(slices);
}, function(update, msg) {
if (procContour) {
new CAM.Topo(widget, settings, {
onupdate: (update, msg) => {
onupdate(0.40 + update * 0.50, msg || "create topo");
},
ondone: (slices) => {
sliceAll.appendAll(slices);
}
});
}
// prepare for tracing paths
let traceTool;
@ -978,9 +580,9 @@
};
// drilling op
function sliceDrill(settings, widget, slices, output) {
function sliceDrill(tool, slices, output) {
let drills = [],
drillToolDiam = getToolDiameter(settings, settings.process.camDrillTool),
drillToolDiam = tool.flueDiameter(),
centerDiff = drillToolDiam * 0.1,
area = (drillToolDiam/2) * (drillToolDiam/2) * Math.PI,
areaDelta = area * 0.05;
@ -1027,7 +629,7 @@
}
// drill registration holes
function sliceDrillReg(settings, widget, output, zThru) {
function sliceDrillReg(settings, output, zThru) {
let proc = settings.process,
stock = settings.stock,
bounds = settings.bounds,
@ -1142,8 +744,7 @@
if (widgetIndex >= widgetCount || !widget) return;
let getTool = getToolById,
settings = print.settings,
let settings = print.settings,
device = settings.device,
process = settings.process,
stock = settings.stock,
@ -1181,7 +782,6 @@
tool,
toolDiam,
toolDiamMove,
toolProfile,
feedRate,
plungeRate,
lastTool,
@ -1198,7 +798,7 @@
terrain = widget.terrain.map(data => {
return {
z: data.z,
tops: data.tops,//POLY.offset(data.tops, maxToolDiam, {z: data.z})
tops: data.tops,
};
});
@ -1213,12 +813,9 @@
function setTool(toolID, feed, plunge) {
if (toolID !== lastTool) {
tool = getToolById(settings, toolID);
toolDiam = getToolDiameter(settings, toolID);
tool = new CAM.Tool(settings, toolID);
toolDiam = tool.fluteDiameter();
toolDiamMove = toolDiam; // TODO validate w/ multiple models
if (widget.topo) {
toolProfile = createToolProfile(settings, toolID, widget.topo);
}
lastTool = toolID;
}
feedRate = feed;
@ -1303,7 +900,7 @@
null,
0,
time,
tool.number
tool.getNumber()
);
}
@ -1322,7 +919,7 @@
if (!lastPoint) {
let above = point.clone().setZ(zmax + zadd);
// before first point, move cutting head to point above it
layerPush(above, 0, 0, tool.number);
layerPush(above, 0, 0, tool.getNumber());
// then set that as the lastPoint
lastPoint = above;
}
@ -1343,14 +940,14 @@
isMove = false;
} else if (deltaZ <= -tolerance) {
// move over before descending
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool.number);
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool.getNumber());
// new pos for plunge calc
deltaXY = 0;
}
} //else (TODO verify no else here b/c above could change isMove)
// move over things
if ((deltaXY > toolDiam || (deltaZ > toolDiam && deltaXY > tolerance)) && (isMove || absDeltaZ >= tolerance)) {
let maxz = getMaxZBetween(
let maxz = getZClearPath(
terrain,
lastPoint.x,// - wmx,
lastPoint.y,// - wmy,
@ -1361,26 +958,16 @@
maxToolDiam/2,
zclear
) + ztOff,
// let maxz = (toolProfile ? Math.max(
// getTopoZPathMax(
// widget,
// toolProfile,
// lastPoint.x - wmx,
// lastPoint.y - wmy,
// point.x - wmx,
// point.y - wmy),
// point.z,
// lastPoint.z) : zmax) + ztOff + zadd,
mustGoUp = Math.max(maxz - point.z, maxz - lastPoint.z) >= tolerance,
clearz = maxz;
// up if any point between higher than start/outline
if (mustGoUp) {
clearz = maxz + zclear;
layerPush(lastPoint.clone().setZ(clearz), 0, 0, tool.number);
layerPush(lastPoint.clone().setZ(clearz), 0, 0, tool.getNumber());
}
// over to point above where we descend to
if (mustGoUp || point.z < maxz) {
layerPush(point.clone().setZ(clearz), 0, 0, tool.number);
layerPush(point.clone().setZ(clearz), 0, 0, tool.getNumber());
// new pos for plunge calc
deltaXY = 0;
}
@ -1403,7 +990,7 @@
point,
cut ? 1 : 0,
rate,
tool.number
tool.getNumber()
);
lastPoint = point;
layerOut.spindle = spindle;
@ -1653,7 +1240,7 @@
// last layer/move is to zmax
// injected into the last layer generated
if (lastPoint)
addOutput(newOutput[newOutput.length-1], printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool.number);
addOutput(newOutput[newOutput.length-1], printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool.getNumber());
// replace output single flattened layer with all points
print.output = newOutput;

103
src/mode/cam/tool.js Normal file
View file

@ -0,0 +1,103 @@
/** 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;
class Tool {
constructor(settings, id) {
this.tool = settings.tools.filter(tool => tool.id === id)[0];
}
getType() {
return this.tool.type;
}
getNumber() {
return this.tool.number;
}
isMetric() {
return this.tool.metric;
}
unitScale() {
return this.isMetric() ? 1 : 25.4;
}
fluteLength() {
return this.unitScale() * this.tool.flute_len;
}
fluteDiameter() {
return this.unitScale() * this.tool.flute_diam;
}
tipDiameter() {
return this.unitScale() * this.tool.taper_tip;
}
shaftDiameter() {
return this.unitScale() * this.tool.shaft_diam;
}
generateProfile(resolution) {
// generate tool profile
let type = this.getType(),
ball = type === "ballmill",
taper = type === "tapermill",
tip_diameter = this.tipDiameter(),
shaft_offset = this.fluteLength(),
flute_diameter = this.fluteDiameter(),
shaft_diameter = this.shaftDiameter(),
shaft_radius = shaft_diameter / 2,
shaft_pix_float = shaft_diameter / resolution,
shaft_pix_int = Math.round(shaft_pix_float),
shaft_radius_pix_float = shaft_pix_float / 2,
flute_radius = flute_diameter / 2,
flute_pix_float = flute_diameter / resolution,
flute_radius_pix_float = flute_pix_float / 2,
tip_pix_float = tip_diameter / resolution,
tip_radius_pix_float = tip_pix_float / 2,
tip_max_radius_offset = flute_radius_pix_float - tip_radius_pix_float,
profile_pix_iter = shaft_pix_int + (1 - shaft_pix_int % 2),
toolCenter = (shaft_pix_int - (shaft_pix_int % 2)) / 2,
toolOffset = [],
larger_shaft = shaft_diameter - flute_diameter > 0.001;
// for each point in tool profile, check inside radius
for (let x = 0; x < profile_pix_iter; x++) {
for (let y = 0; y < profile_pix_iter; y++) {
let dx = x - toolCenter,
dy = y - toolCenter,
dist_from_center = Math.sqrt(dx * dx + dy * dy);
if (dist_from_center <= flute_radius_pix_float) {
// console.log({x,y,dx,dy,dist:dist_from_center,ln:dbl.length})
// flute offset points
let z_offset = 0;
if (ball) {
z_offset = (1 - Math.cos((dist_from_center / flute_radius_pix_float) * HPI)) * -flute_radius;
} else if (taper && dist_from_center >= tip_radius_pix_float) {
z_offset = ((dist_from_center - tip_radius_pix_float) / tip_max_radius_offset) * -shaft_offset;
}
toolOffset.push(dx, dy, z_offset);
} else if (shaft_offset && larger_shaft && dist_from_center <= shaft_radius_pix_float) {
// shaft offset points
toolOffset.push(dx, dy, -shaft_offset);
}
}
}
this.profile = toolOffset;
return this;
}
}
CAM.Tool = Tool;
})();

303
src/mode/cam/topo.js Normal file
View file

@ -0,0 +1,303 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
(function() {
let KIRI = self.kiri,
BASE = self.base,
CAM = KIRI.driver.CAM,
CPRO = CAM.process,
newSlice = KIRI.newSlice,
newLine = BASE.newLine,
newPoint = BASE.newPoint,
newPolygon = BASE.newPolygon,
noop = function() {};
class Topo {
constructor(widget, settings, options) {
let opt = options || {},
ondone = opt.ondone || noop,
onupdate = opt.onupdate || noop,
mesh = widget.mesh,
proc = settings.process,
resolution = proc.camTolerance,
tool = new CAM.Tool(settings, proc.camContourTool),
toolOffset = tool.generateProfile(resolution).profile,
toolDiameter = tool.fluteDiameter(),
toolStep = toolDiameter * proc.camContourOver,
traceJoin = toolDiameter / 2,
pocketOnly = proc.camOutlinePocket,
bounds = widget.getBoundingBox().clone(),
minX = bounds.min.x,
maxX = bounds.max.x,
minY = bounds.min.y,
maxY = bounds.max.y,
zBottom = proc.camZBottom,
boundsX = maxX - minX,
boundsY = maxY - minY,
maxangle = proc.camContourAngle,
curvesOnly = proc.camContourCurves,
R2A = 180 / Math.PI,
stepsx = Math.ceil(boundsX / resolution),
stepsy = Math.ceil(boundsY / resolution),
data = new Float32Array(stepsx * stepsy),
topo = this.topo = widget.topo = {
data: data,
stepsx: stepsx,
stepsy: stepsy,
bounds: bounds,
diameter: toolDiameter,
resolution: resolution,
profile: toolOffset,
widget: widget
},
newslices = [],
newlines,
newtop,
newtrace,
sliceout,
latent,
lastP,
slice, lx, ly,
startTime = time();
// return the touching z given topo x,y and a tool profile
function toolTipZ(x,y) {
let profile = toolOffset,
sx = stepsx,
sy = stepsy,
xl = sx - 1,
yl = sy - 1;
let tv, tx, ty, tz, gv, i = 0, mz = -1;
while (i < profile.length) {
// tool profile point x, y, and z offsets
let tx = profile[i++] + x;
let ty = profile[i++] + y;
let tz = profile[i++];
if (tx < 0 || tx > xl || ty < 0 || ty > yl) {
// if outside max topo steps, use 0
gv = 0;
} else {
// lookup grid value @ tx, ty
gv = topo.data[tx * sy + ty] || 0;
}
// inside the topo but off the part
// if (floormax && gv === 0) {
// // return topo.bounds.max.z;
// gv = topo.bounds.max.z;
// }
// update the rest
mz = Math.max(tz + gv, mz);
}
return Math.max(mz,0);
}
function push_point(x,y,z) {
let newP = newPoint(x,y,z);
if (lastP && lastP.z === z) {
if (curvesOnly) {
end_poly();
} else {
latent = newP;
}
} else {
if (latent) {
newtrace.push(latent);
latent = null;
}
newtrace.push(newP);
}
lastP = newP;
}
function end_poly() {
if (latent) {
newtrace.push(latent);
}
if (newtrace.length > 0) {
// add additional constraint on min perimeter()
if (newtrace.length > 1) {
sliceout.push(newtrace);
}
newtrace = newPolygon().setOpen();
}
latent = undefined;
lastP = undefined;
}
function processSlices(slices) {
let gridx = 0,
gridy,
gridi, // index
gridv, // value
zMin = Math.max(bounds.min.z, zBottom) + 0.0001,
x, y, tv, ltv;
// for each Y slice, find z grid value (x/z swapped)
for (let j=0, jl=slices.length; j<jl; j++) {
let slice = slices[j],
lines = slice.lines;
gridy = 0;
// slices have x/z swapped
for (y = minY; y < maxY && gridy < stepsy; y += resolution) {
gridi = gridx * stepsy + gridy;
gridv = data[gridi] || 0;
// strategy using raw lines (faster slice, but more lines)
for (let i=0, il=lines.length; i<il; i++) {
let line = lines[i], p1 = line.p1, p2 = line.p2;
if (
(p1.z > zMin || p2.z > zMin) && // one endpoint above 0
(p1.z > gridv || p2.z > gridv) && // one endpoint above gridv
((p1.y <= y && p2.y >= y) || // one endpoint left
(p2.y <= y && p1.y >= y)) // one endpoint right
) {
let dy = p1.y - p2.y,
dz = p1.z - p2.z,
pct = (p1.y - y) / dy,
nz = p1.z - (dz * pct);
if (nz > gridv) {
gridv = data[gridi] = Math.max(nz, zMin);
}
}
}
gridy++;
}
gridx++;
onupdate(0.20 + (gridx/stepsx) * 0.50, "trace surface");
}
// x contouring
if (proc.camContourXOn) {
startTime = time();
// emit slice per X
for (x = minX; x <= maxX; x += toolStep) {
gridx = Math.round(((x - minX) / boundsX) * stepsx);
ly = gridy = 0;
slice = newSlice(gridx, mesh.newGroup ? mesh.newGroup() : null);
slice.camMode = CPRO.CONTOUR_X;
slice.lines = newlines = [];
newtop = slice.addTop(newPolygon().setOpen()).poly;
newtrace = newPolygon().setOpen();
sliceout = slice.tops[0].traces = [ ];
for (y = minY; y < maxY; y += resolution) {
if (pocketOnly && (data[gridx * stepsy + gridy] || 0) === 0) {
end_poly();
gridy++;
ly = 0;
continue;
}
tv = toolTipZ(gridx, gridy);
if (tv === 0) {
end_poly();
gridy++;
ly = 0;
continue;
}
if (ly) {
if (mesh) newlines.push(newLine(
newPoint(x,ly,ltv),
newPoint(x,y,tv)
));
let ang = Math.abs((Math.atan2(ltv - tv, resolution) * R2A) % 90);
// over max angle, turn into square edge (up or down)
if (ang > maxangle) {
if (ltv > tv) {
// down = forward,down
push_point(x,y,ltv);
} else {
// up = up,forward
push_point(x,ly,tv);
}
}
}
push_point(x,y,tv);
ly = y;
ltv = tv;
gridy++;
}
end_poly();
if (sliceout.length > 0) {
newslices.push(slice);
}
onupdate(0.70 + (gridx/stepsx) * 0.15, "contour x");
}
}
// y contouring
if (proc.camContourYOn) {
startTime = time();
// emit slice per Y
for (y = minY; y <= maxY; y += toolStep) {
gridy = Math.round(((y - minY) / boundsY) * stepsy);
lx = gridx = 0;
slice = newSlice(gridy, mesh.newGroup ? mesh.newGroup() : null);
slice.camMode = CPRO.CONTOUR_Y;
slice.lines = newlines = [];
newtop = slice.addTop(newPolygon().setOpen()).poly;
newtrace = newPolygon().setOpen();
sliceout = slice.tops[0].traces = [ ];
for (x = minX; x <= maxX; x += resolution) {
if (pocketOnly && (data[gridx * stepsy + gridy] || 0) === 0) {
end_poly();
gridx++;
ly = 0;
continue;
}
tv = toolTipZ(gridx, gridy);
if (tv === 0) {
end_poly();
gridx++;
lx = 0;
continue;
}
if (lx) {
if (mesh) newlines.push(newLine(
newPoint(lx,y,ltv),
newPoint(x,y,tv)
));
let ang = Math.abs((Math.atan2(ltv - tv, resolution) * R2A) % 90);
// over max angle, turn into square edge (up or down)
if (ang > maxangle) {
if (ltv > tv) {
// down = forward,down
push_point(x,y,ltv);
} else {
// up = up,forward
push_point(lx,y,tv);
}
}
}
push_point(x,y,tv);
lx = x;
ltv = tv;
gridx++;
}
end_poly();
if (sliceout.length > 0) {
newslices.push(slice);
}
onupdate(0.85 + (gridy/stepsy) * 0.15, "contour y");
}
}
ondone(newslices);
}
let slicer = new KIRI.slicer2(widget.getPoints(), { swapX: true });
let sindex = slicer.interval(resolution);
let slices = slicer.slice(sindex, { each: (data, index, total) => {
onupdate(0.0 + (index/total) * 0.20, "topo slice");
}, genso: true });
// ondone(slices.map(data => data.slice));
processSlices(slices.map(data => data.slice));
}
}
CAM.Topo = Topo;
})();