checkpoint topo and tool refactoring
This commit is contained in:
parent
7db47ca185
commit
c3120263f0
9 changed files with 582 additions and 474 deletions
2
app.js
2
app.js
|
|
@ -305,6 +305,8 @@ const script = {
|
||||||
"mode/fdm/driver",
|
"mode/fdm/driver",
|
||||||
"mode/sla/driver",
|
"mode/sla/driver",
|
||||||
"mode/cam/driver",
|
"mode/cam/driver",
|
||||||
|
"mode/cam/tool",
|
||||||
|
"mode/cam/topo",
|
||||||
"mode/laser/driver",
|
"mode/laser/driver",
|
||||||
"kiri/widget",
|
"kiri/widget",
|
||||||
"kiri/print",
|
"kiri/print",
|
||||||
|
|
|
||||||
2
notes.md
2
notes.md
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## `C` cosmetic, `F` functional, `P` performance, `B` bug fix
|
## `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` implement an in-app bug reporting system
|
||||||
* `F` extend mesh object to store raw + annotations (rot,scale,pos)
|
* `F` extend mesh object to store raw + annotations (rot,scale,pos)
|
||||||
* share raw data w/ dups, encode/decode
|
* share raw data w/ dups, encode/decode
|
||||||
|
|
|
||||||
|
|
@ -254,14 +254,22 @@
|
||||||
PRO.swap = function(x,y) {
|
PRO.swap = function(x,y) {
|
||||||
let poly = this,
|
let poly = this,
|
||||||
points = poly.points,
|
points = poly.points,
|
||||||
length = points.length;
|
length = points.length,
|
||||||
poly.bounds = new Bounds();
|
bounds = new Bounds();
|
||||||
for (let i=0; i<length; i++) {
|
if (x) {
|
||||||
let p = points[i];
|
for (let i=0; i<length; i++) {
|
||||||
if (x) p.swapXZ();
|
let p = points[i];
|
||||||
else if (y) p.swapYZ();
|
p.swapXZ();
|
||||||
poly.bounds.update(p);
|
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) {
|
if (poly.inner) poly.inner.forEach(function(i) {
|
||||||
i.swap(x,y);
|
i.swap(x,y);
|
||||||
});
|
});
|
||||||
|
|
@ -408,7 +416,11 @@
|
||||||
p.move(offset);
|
p.move(offset);
|
||||||
bounds.update(p);
|
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;
|
return scope;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@
|
||||||
flats: encode(this.flats, state),
|
flats: encode(this.flats, state),
|
||||||
solids: encode(this.solids, state),
|
solids: encode(this.solids, state),
|
||||||
supports: encode(this.supports, state)
|
supports: encode(this.supports, state)
|
||||||
//groups: encode(this.groups, state)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -109,7 +108,6 @@
|
||||||
slice.flats = decode(v.flats, state);
|
slice.flats = decode(v.flats, state);
|
||||||
slice.solids = decode(v.solids, state);
|
slice.solids = decode(v.solids, state);
|
||||||
slice.supports = decode(v.supports, state);
|
slice.supports = decode(v.supports, state);
|
||||||
//slice.groups = decode(v.groups, state);
|
|
||||||
|
|
||||||
return slice;
|
return slice;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBooleanClick() {
|
function onBooleanClick() {
|
||||||
UC.refresh();
|
// prevent hiding elements in device editor on clicks
|
||||||
|
if (!API.modal.visible()) {
|
||||||
|
UC.refresh();
|
||||||
|
}
|
||||||
API.conf.update();
|
API.conf.update();
|
||||||
DOC.activeElement.blur();
|
DOC.activeElement.blur();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,26 +16,35 @@
|
||||||
|
|
||||||
class Slicer {
|
class Slicer {
|
||||||
constructor(points, options) {
|
constructor(points, options) {
|
||||||
|
this.options = {};
|
||||||
if (points) {
|
if (points) {
|
||||||
this.setPoints(points, options);
|
this.setPoints(points, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOptions(options) {
|
||||||
|
Object.assign(this.options, options || {});
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
|
||||||
setPoints(points, 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.zFlat = {}; // accumulated flat area at z height
|
||||||
this.zLine = {}; // count of z coplanar lines
|
this.zLine = {}; // count of z coplanar lines
|
||||||
this.zList = {}; // count of z values for auto slicing
|
this.zList = {}; // count of z values for auto slicing
|
||||||
this.zSum = 0; // used in bucketing calculations
|
this.zSum = 0; // used in bucketing calculations
|
||||||
return this
|
return this
|
||||||
.computeBounds()
|
.computeBounds()
|
||||||
.computeFeatures(options)
|
.computeFeatures()
|
||||||
.computeBuckets();
|
.computeBuckets();
|
||||||
}
|
}
|
||||||
|
|
||||||
computeBounds() {
|
computeBounds() {
|
||||||
this.bounds = new THREE.Box3();
|
if (!this.bounds) {
|
||||||
this.bounds.setFromPoints(this.points);
|
this.bounds = new THREE.Box3();
|
||||||
|
this.bounds.setFromPoints(this.points);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +52,7 @@
|
||||||
// these are used for auto-slicing in laser
|
// these are used for auto-slicing in laser
|
||||||
// and to flats detection in CAM mode
|
// and to flats detection in CAM mode
|
||||||
computeFeatures(options) {
|
computeFeatures(options) {
|
||||||
const opt = options || {};
|
const opt = this.setOptions(options);
|
||||||
const points = this.points;
|
const points = this.points;
|
||||||
const bounds = this.bounds;
|
const bounds = this.bounds;
|
||||||
const zFlat = this.zFlat;
|
const zFlat = this.zFlat;
|
||||||
|
|
@ -169,7 +178,7 @@
|
||||||
|
|
||||||
// slice through points at given Z and return polygons
|
// slice through points at given Z and return polygons
|
||||||
slice(z, options, index, total, mark) {
|
slice(z, options, index, total, mark) {
|
||||||
let opt = options || {};
|
const opt = this.setOptions(options);
|
||||||
|
|
||||||
if (Array.isArray(z)) {
|
if (Array.isArray(z)) {
|
||||||
const mark = UTIL.time();
|
const mark = UTIL.time();
|
||||||
|
|
@ -238,8 +247,14 @@
|
||||||
retn.lines = removeDuplicateLines(lines);
|
retn.lines = removeDuplicateLines(lines);
|
||||||
retn.tops = POLY.nest(connectLines(retn.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) {
|
if (opt.genso) {
|
||||||
retn.slice = newSlice(z).addTops(retn.tops);
|
retn.slice = newSlice(z).addTops(retn.tops);
|
||||||
|
retn.slice.lines = retn.lines;
|
||||||
|
retn.slice.groups = retn.tops;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,6 +265,91 @@
|
||||||
return retn;
|
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) {
|
interval(step, options) {
|
||||||
let opt = options || {},
|
let opt = options || {},
|
||||||
bounds = this.bounds,
|
bounds = this.bounds,
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@
|
||||||
sliceRender,
|
sliceRender,
|
||||||
printSetup,
|
printSetup,
|
||||||
printExport,
|
printExport,
|
||||||
printRender,
|
printRender
|
||||||
getToolById,
|
|
||||||
getToolDiameter,
|
|
||||||
},
|
},
|
||||||
CPRO = CAM.process = {
|
CPRO = CAM.process = {
|
||||||
LEVEL: 1,
|
LEVEL: 1,
|
||||||
|
|
@ -66,99 +64,10 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getToolById(settings, id) {
|
/**
|
||||||
for (let i=0, t=settings.tools; i<t.length; i++) {
|
* return tool Z clearance height for a line segment movement path
|
||||||
if (t[i].id === id) return t[i];
|
*/
|
||||||
}
|
function getZClearPath(terrain, x1, y1, x2, y2, z, zadd, off, over) {
|
||||||
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) {
|
|
||||||
let maxz = z;
|
let maxz = z;
|
||||||
let check = [];
|
let check = [];
|
||||||
for (let i=0; i<terrain.length; i++) {
|
for (let i=0; i<terrain.length; i++) {
|
||||||
|
|
@ -199,69 +108,6 @@
|
||||||
return maxz;
|
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
|
* call out to slicer
|
||||||
*/
|
*/
|
||||||
|
|
@ -289,257 +135,6 @@
|
||||||
return selected;
|
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
|
* Find top paths to trace when using ball and taper mills
|
||||||
* in waterline outlining and tracing modes.
|
* in waterline outlining and tracing modes.
|
||||||
|
|
@ -696,18 +291,18 @@
|
||||||
proc = conf.process,
|
proc = conf.process,
|
||||||
sliceAll = widget.slices = [],
|
sliceAll = widget.slices = [],
|
||||||
unitsName = settings.controller.units,
|
unitsName = settings.controller.units,
|
||||||
roughToolDiam = getToolDiameter(conf, proc.camRoughTool),
|
roughTool = new CAM.Tool(conf, proc.camRoughTool),
|
||||||
outlineToolDiam = getToolDiameter(conf, proc.camOutlineTool),
|
roughToolDiam = roughTool.fluteDiameter(),
|
||||||
contourToolDiam = getToolDiameter(conf, proc.camContourTool),
|
drillTool = new CAM.Tool(conf, proc.camDrillTool),
|
||||||
drillToolDiam = getToolDiameter(conf, proc.camDrillTool),
|
drillToolDiam = drillTool.fluteDiameter(),
|
||||||
procFacing = proc.camRoughOn && proc.camZTopOffset,
|
procFacing = proc.camRoughOn && proc.camZTopOffset,
|
||||||
procRough = proc.camRoughOn && proc.camRoughDown && roughToolDiam,
|
procRough = proc.camRoughOn && proc.camRoughDown,
|
||||||
procOutlineIn = proc.camOutlineIn,
|
procOutlineIn = proc.camOutlineIn,
|
||||||
procOutlineOn = proc.camOutlineOn,
|
procOutlineOn = proc.camOutlineOn,
|
||||||
procOutlineWide = proc.camOutlineWide,
|
procOutlineWide = proc.camOutlineWide,
|
||||||
procOutline = procOutlineOn && proc.camOutlineDown && outlineToolDiam,
|
procOutline = procOutlineOn && proc.camOutlineDown,
|
||||||
procContourX = proc.camContourXOn && proc.camOutlinePlunge && contourToolDiam,
|
procContourX = proc.camContourXOn && proc.camOutlinePlunge,
|
||||||
procContourY = proc.camContourYOn && proc.camOutlinePlunge && contourToolDiam,
|
procContourY = proc.camContourYOn && proc.camOutlinePlunge,
|
||||||
procContour = procContourX || procContourY,
|
procContour = procContourX || procContourY,
|
||||||
procDrill = proc.camDrillingOn && proc.camDrillDown && proc.camDrillDownSpeed,
|
procDrill = proc.camDrillingOn && proc.camDrillDown && proc.camDrillDownSpeed,
|
||||||
procDrillReg = proc.camDrillReg,
|
procDrillReg = proc.camDrillReg,
|
||||||
|
|
@ -784,12 +379,12 @@
|
||||||
|
|
||||||
if (procDrillReg) {
|
if (procDrillReg) {
|
||||||
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
||||||
sliceDrillReg(settings, widget, sliceAll, zThru);
|
sliceDrillReg(settings, sliceAll, zThru);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (procDrill) {
|
if (procDrill) {
|
||||||
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
||||||
sliceDrill(settings, widget, tslices, sliceAll);
|
sliceDrill(drillTool, tslices, sliceAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
// identify through holes
|
// identify through holes
|
||||||
|
|
@ -884,7 +479,10 @@
|
||||||
|
|
||||||
// create outline slices
|
// create outline slices
|
||||||
if (procOutline) {
|
if (procOutline) {
|
||||||
|
let outlineTool = new CAM.Tool(conf, proc.camOutlineTool);
|
||||||
|
let outlineToolDiam = outlineTool.fluteDiameter();
|
||||||
maxToolDiam = Math.max(maxToolDiam, outlineToolDiam);
|
maxToolDiam = Math.max(maxToolDiam, outlineToolDiam);
|
||||||
|
|
||||||
let shadow = [];
|
let shadow = [];
|
||||||
let slices = [];
|
let slices = [];
|
||||||
slicer.slice(slicer.interval(outlineDown, { down: true, min: zBottom }), { each: (data, index, total) => {
|
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
|
// we need topo for safe travel moves when roughing and outlining
|
||||||
// not generated when drilling-only. then all z moves use bounds max.
|
// not generated when drilling-only. then all z moves use bounds max.
|
||||||
// also generates x and y contouring when selected
|
// also generates x and y contouring when selected
|
||||||
if (procContour)
|
if (procContour) {
|
||||||
generateTopoMap(widget, settings, function(slices) {
|
new CAM.Topo(widget, settings, {
|
||||||
sliceAll.appendAll(slices);
|
onupdate: (update, msg) => {
|
||||||
}, function(update, msg) {
|
onupdate(0.40 + update * 0.50, msg || "create topo");
|
||||||
onupdate(0.40 + update * 0.50, msg || "create topo");
|
},
|
||||||
});
|
ondone: (slices) => {
|
||||||
|
sliceAll.appendAll(slices);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// prepare for tracing paths
|
// prepare for tracing paths
|
||||||
let traceTool;
|
let traceTool;
|
||||||
|
|
@ -978,9 +580,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// drilling op
|
// drilling op
|
||||||
function sliceDrill(settings, widget, slices, output) {
|
function sliceDrill(tool, slices, output) {
|
||||||
let drills = [],
|
let drills = [],
|
||||||
drillToolDiam = getToolDiameter(settings, settings.process.camDrillTool),
|
drillToolDiam = tool.flueDiameter(),
|
||||||
centerDiff = drillToolDiam * 0.1,
|
centerDiff = drillToolDiam * 0.1,
|
||||||
area = (drillToolDiam/2) * (drillToolDiam/2) * Math.PI,
|
area = (drillToolDiam/2) * (drillToolDiam/2) * Math.PI,
|
||||||
areaDelta = area * 0.05;
|
areaDelta = area * 0.05;
|
||||||
|
|
@ -1027,7 +629,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// drill registration holes
|
// drill registration holes
|
||||||
function sliceDrillReg(settings, widget, output, zThru) {
|
function sliceDrillReg(settings, output, zThru) {
|
||||||
let proc = settings.process,
|
let proc = settings.process,
|
||||||
stock = settings.stock,
|
stock = settings.stock,
|
||||||
bounds = settings.bounds,
|
bounds = settings.bounds,
|
||||||
|
|
@ -1142,8 +744,7 @@
|
||||||
|
|
||||||
if (widgetIndex >= widgetCount || !widget) return;
|
if (widgetIndex >= widgetCount || !widget) return;
|
||||||
|
|
||||||
let getTool = getToolById,
|
let settings = print.settings,
|
||||||
settings = print.settings,
|
|
||||||
device = settings.device,
|
device = settings.device,
|
||||||
process = settings.process,
|
process = settings.process,
|
||||||
stock = settings.stock,
|
stock = settings.stock,
|
||||||
|
|
@ -1181,7 +782,6 @@
|
||||||
tool,
|
tool,
|
||||||
toolDiam,
|
toolDiam,
|
||||||
toolDiamMove,
|
toolDiamMove,
|
||||||
toolProfile,
|
|
||||||
feedRate,
|
feedRate,
|
||||||
plungeRate,
|
plungeRate,
|
||||||
lastTool,
|
lastTool,
|
||||||
|
|
@ -1198,7 +798,7 @@
|
||||||
terrain = widget.terrain.map(data => {
|
terrain = widget.terrain.map(data => {
|
||||||
return {
|
return {
|
||||||
z: data.z,
|
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) {
|
function setTool(toolID, feed, plunge) {
|
||||||
if (toolID !== lastTool) {
|
if (toolID !== lastTool) {
|
||||||
tool = getToolById(settings, toolID);
|
tool = new CAM.Tool(settings, toolID);
|
||||||
toolDiam = getToolDiameter(settings, toolID);
|
toolDiam = tool.fluteDiameter();
|
||||||
toolDiamMove = toolDiam; // TODO validate w/ multiple models
|
toolDiamMove = toolDiam; // TODO validate w/ multiple models
|
||||||
if (widget.topo) {
|
|
||||||
toolProfile = createToolProfile(settings, toolID, widget.topo);
|
|
||||||
}
|
|
||||||
lastTool = toolID;
|
lastTool = toolID;
|
||||||
}
|
}
|
||||||
feedRate = feed;
|
feedRate = feed;
|
||||||
|
|
@ -1303,7 +900,7 @@
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
time,
|
time,
|
||||||
tool.number
|
tool.getNumber()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1322,7 +919,7 @@
|
||||||
if (!lastPoint) {
|
if (!lastPoint) {
|
||||||
let above = point.clone().setZ(zmax + zadd);
|
let above = point.clone().setZ(zmax + zadd);
|
||||||
// before first point, move cutting head to point above it
|
// 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
|
// then set that as the lastPoint
|
||||||
lastPoint = above;
|
lastPoint = above;
|
||||||
}
|
}
|
||||||
|
|
@ -1343,14 +940,14 @@
|
||||||
isMove = false;
|
isMove = false;
|
||||||
} else if (deltaZ <= -tolerance) {
|
} else if (deltaZ <= -tolerance) {
|
||||||
// move over before descending
|
// 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
|
// new pos for plunge calc
|
||||||
deltaXY = 0;
|
deltaXY = 0;
|
||||||
}
|
}
|
||||||
} //else (TODO verify no else here b/c above could change isMove)
|
} //else (TODO verify no else here b/c above could change isMove)
|
||||||
// move over things
|
// move over things
|
||||||
if ((deltaXY > toolDiam || (deltaZ > toolDiam && deltaXY > tolerance)) && (isMove || absDeltaZ >= tolerance)) {
|
if ((deltaXY > toolDiam || (deltaZ > toolDiam && deltaXY > tolerance)) && (isMove || absDeltaZ >= tolerance)) {
|
||||||
let maxz = getMaxZBetween(
|
let maxz = getZClearPath(
|
||||||
terrain,
|
terrain,
|
||||||
lastPoint.x,// - wmx,
|
lastPoint.x,// - wmx,
|
||||||
lastPoint.y,// - wmy,
|
lastPoint.y,// - wmy,
|
||||||
|
|
@ -1361,26 +958,16 @@
|
||||||
maxToolDiam/2,
|
maxToolDiam/2,
|
||||||
zclear
|
zclear
|
||||||
) + ztOff,
|
) + 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,
|
mustGoUp = Math.max(maxz - point.z, maxz - lastPoint.z) >= tolerance,
|
||||||
clearz = maxz;
|
clearz = maxz;
|
||||||
// up if any point between higher than start/outline
|
// up if any point between higher than start/outline
|
||||||
if (mustGoUp) {
|
if (mustGoUp) {
|
||||||
clearz = maxz + zclear;
|
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
|
// over to point above where we descend to
|
||||||
if (mustGoUp || point.z < maxz) {
|
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
|
// new pos for plunge calc
|
||||||
deltaXY = 0;
|
deltaXY = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1403,7 +990,7 @@
|
||||||
point,
|
point,
|
||||||
cut ? 1 : 0,
|
cut ? 1 : 0,
|
||||||
rate,
|
rate,
|
||||||
tool.number
|
tool.getNumber()
|
||||||
);
|
);
|
||||||
lastPoint = point;
|
lastPoint = point;
|
||||||
layerOut.spindle = spindle;
|
layerOut.spindle = spindle;
|
||||||
|
|
@ -1653,7 +1240,7 @@
|
||||||
// last layer/move is to zmax
|
// last layer/move is to zmax
|
||||||
// injected into the last layer generated
|
// injected into the last layer generated
|
||||||
if (lastPoint)
|
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
|
// replace output single flattened layer with all points
|
||||||
print.output = newOutput;
|
print.output = newOutput;
|
||||||
|
|
|
||||||
103
src/mode/cam/tool.js
Normal file
103
src/mode/cam/tool.js
Normal 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
303
src/mode/cam/topo.js
Normal 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;
|
||||||
|
|
||||||
|
})();
|
||||||
Loading…
Reference in a new issue