Merge branch 'dev-4.2-refactor-trace' into rel-4.2
This commit is contained in:
commit
0d2261566d
5 changed files with 195 additions and 92 deletions
|
|
@ -1002,6 +1002,23 @@ class Polygon {
|
|||
return this.z !== undefined ? this.z : this.points[i || 0]?.z || 0;
|
||||
}
|
||||
|
||||
minZ() {
|
||||
return Math.min(...this.points.map(p => p.z));
|
||||
}
|
||||
|
||||
avgZ() {
|
||||
return [...this.points.map(p => p.z)].reduce((a,v) => a+v) / this.points.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} z value target
|
||||
* @param {*} epsilon allowed Z variance
|
||||
* @returns {boolean} true if all points Z within epsilon of value
|
||||
*/
|
||||
onZ(z, epsilon = 10e-4) {
|
||||
return Math.max(...this.points.map(p => Math.abs(z - p.z))) < epsilon;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
render(layer, color, recursive, open) {
|
||||
|
|
|
|||
|
|
@ -1008,7 +1008,6 @@ CAM.init = function(kiri, api) {
|
|||
CAM.traces((ids) => {
|
||||
api.hide.alert(alert);
|
||||
alert = api.show.alert("[esc] cancels trace editing");
|
||||
kiri.api.widgets.opacity(0.8);
|
||||
kiri.api.widgets.for(widget => {
|
||||
if (ids.indexOf(widget.id) >= 0) {
|
||||
unselectTraces(widget, true);
|
||||
|
|
@ -1042,6 +1041,7 @@ CAM.init = function(kiri, api) {
|
|||
});
|
||||
// ensure appropriate traces are toggled matching current record
|
||||
kiri.api.widgets.for(widget => {
|
||||
widget.setVisualState({ opacity: 0.25 });
|
||||
let areas = (poppedRec.areas[widget.id] || []);
|
||||
let stack = widget.trace_stack;
|
||||
stack.meshes.forEach(mesh => {
|
||||
|
|
@ -1070,6 +1070,7 @@ CAM.init = function(kiri, api) {
|
|||
api.feature.hover = false;
|
||||
api.feature.hoverAdds = false;
|
||||
kiri.api.widgets.for(widget => {
|
||||
widget.restoreVisualState();
|
||||
if (widget.trace_stack) {
|
||||
widget.trace_stack.hide();
|
||||
widget.adds.removeAll(widget.trace_stack.meshes);
|
||||
|
|
@ -1096,17 +1097,15 @@ CAM.init = function(kiri, api) {
|
|||
color.r = colorSave.r;
|
||||
color.g = colorSave.g;
|
||||
color.b = colorSave.b;
|
||||
lastTrace.position.z -= 0.01;
|
||||
}
|
||||
lastTrace = null;
|
||||
if (data.type === 'platform') {
|
||||
lastTrace = null;
|
||||
return;
|
||||
}
|
||||
if (!data.int.object.trace) {
|
||||
return;
|
||||
}
|
||||
lastTrace = data.int.object;
|
||||
lastTrace.position.z += 0.01;
|
||||
if (lastTrace.selected) {
|
||||
let event = data.event;
|
||||
let target = event.target;
|
||||
|
|
@ -1115,11 +1114,8 @@ CAM.init = function(kiri, api) {
|
|||
}
|
||||
let material = lastTrace.material[0] || lastTrace.material;
|
||||
let color = material.color;
|
||||
let {r, g, b} = color;
|
||||
material.colorSave = {r, g, b};
|
||||
color.r = 0;
|
||||
color.g = 0;
|
||||
color.b = 1;
|
||||
material.colorSave = color.clone();
|
||||
color.setHex(isDark() ? 0x0066ff : 0x0000ff);
|
||||
};
|
||||
func.traceHoverUp = function(int, ev) {
|
||||
if (!int) return;
|
||||
|
|
@ -1128,8 +1124,9 @@ CAM.init = function(kiri, api) {
|
|||
if (ev.metaKey || ev.ctrlKey) {
|
||||
let { selected } = object;
|
||||
let { widget, poly } = object.trace;
|
||||
let avgZ = poly.avgZ();
|
||||
for (let add of widget.adds) {
|
||||
if (add.trace && add.selected !== selected && add.trace.poly.getZ() === poly.getZ()) {
|
||||
if (add.trace && add.selected !== selected && add.trace.poly.onZ(avgZ)) {
|
||||
func.traceToggle(add);
|
||||
}
|
||||
}
|
||||
|
|
@ -1147,23 +1144,17 @@ CAM.init = function(kiri, api) {
|
|||
let wlist = areas[widget.id] = areas[widget.id] || [];
|
||||
obj.selected = !obj.selected;
|
||||
if (!colorSave) {
|
||||
colorSave = material.colorSave = {
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b
|
||||
};
|
||||
colorSave = material.colorSave = color.clone();
|
||||
}
|
||||
if (obj.selected) {
|
||||
obj.position.z += 0.01;
|
||||
color.r = colorSave.r = 0.9;
|
||||
color.g = colorSave.g = 0;
|
||||
color.b = colorSave.b = 0.1;
|
||||
color.setHex(isDark() ? 0xdd0011 : 0xff0033);
|
||||
colorSave.r = color.r;
|
||||
colorSave.g = color.g;
|
||||
colorSave.b = color.b;
|
||||
if (!skip) wlist.push(poly._trace);
|
||||
} else {
|
||||
obj.position.z -= 0.01;
|
||||
color.r = colorSave.r = 0xaa/255;
|
||||
color.g = colorSave.g = 0xaa/255;
|
||||
color.b = colorSave.b = 0x55/255;
|
||||
color.setHex(0xaaaa55);
|
||||
colorSave.setHex(0xaaaa55);
|
||||
if (!skip) wlist.remove(poly._trace);
|
||||
}
|
||||
API.conf.save();
|
||||
|
|
@ -1192,9 +1183,6 @@ CAM.init = function(kiri, api) {
|
|||
func.hover = func.selectHolesHover;
|
||||
func.hoverUp = func.selectHolesHoverUp;
|
||||
|
||||
|
||||
|
||||
|
||||
const widgets = kiri.api.widgets.all()
|
||||
/**
|
||||
* creates a mesh for a hole and adds it to a widget
|
||||
|
|
@ -1712,13 +1700,11 @@ CAM.init = function(kiri, api) {
|
|||
dogbone: 'camTraceDogbone',
|
||||
revbone: 'camTraceDogbone',
|
||||
merge: 'camTraceMerge',
|
||||
select: 'camTraceMode',
|
||||
ov_topz: 0,
|
||||
ov_botz: 0,
|
||||
ov_conv: '~camConventional',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
select: UC.newSelect(LANG.cc_sele_s, {title:LANG.cc_sele_l}, "select"),
|
||||
mode: UC.newSelect(LANG.cu_type_s, {title:LANG.cu_type_l}, "trace"),
|
||||
offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l, show:() => (poppedRec.mode === 'follow')}, "traceoff"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
|
|
@ -2162,16 +2148,18 @@ function validateTools(tools) {
|
|||
}
|
||||
}
|
||||
|
||||
function isDark() { return API.space.is_dark() };
|
||||
|
||||
function addbox() { return FDM.addbox(...arguments)};
|
||||
|
||||
function delbox() { return FDM.delbox(...arguments)};
|
||||
|
||||
function boxColor() {
|
||||
return API.space.is_dark() ? 0x00ddff : 0x0000dd;
|
||||
return isDark() ? 0x00ddff : 0x0000dd;
|
||||
}
|
||||
|
||||
function boxOpacity() {
|
||||
return API.space.is_dark() ? 0.75 : 0.6;
|
||||
return isDark() ? 0.75 : 0.6;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
|
|
@ -2254,7 +2242,7 @@ function updateStock() {
|
|||
camStock.position.z = center.z;
|
||||
camStock.rotation.x = currentIndex || 0;
|
||||
camStock.lines.material.color =
|
||||
new THREE.Color(API.space.is_dark() ? 0x555555 : 0xaaaaaa);
|
||||
new THREE.Color(isDark() ? 0x555555 : 0xaaaaaa);
|
||||
} else if (camStock) {
|
||||
SPACE.world.remove(camStock);
|
||||
camStock = null;
|
||||
|
|
|
|||
|
|
@ -976,11 +976,12 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
|
||||
async slice(progress) {
|
||||
const debug = false;
|
||||
let { op, state } = this;
|
||||
let { tool, rate, down, plunge, offset, offover, thru } = op;
|
||||
let { ov_conv } = op;
|
||||
let { settings, widget, addSlices, zThru, tabs, workarea } = state;
|
||||
let { updateToolDiams, cutTabs, cutPolys, healPolys, color } = state;
|
||||
let { updateToolDiams, cutTabs, cutPolys, healPolys, color, shadowAt } = state;
|
||||
let { process, stock } = settings;
|
||||
let { camStockClipTo } = process;
|
||||
if (state.isIndexed) {
|
||||
|
|
@ -1042,17 +1043,39 @@ class OpTrace extends CamOp {
|
|||
.setLayer("trace follow", {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
}
|
||||
function clearZ(polys, z, down) {
|
||||
function clearZnew(polys, z, down) {
|
||||
if (down) {
|
||||
// adjust step down to a value <= down that
|
||||
// ends on the lowest z specified
|
||||
let diff = zTop - z;
|
||||
down = diff / Math.ceil(diff / down);
|
||||
}
|
||||
let zs = down ? base.util.lerp(zTop, z, down) : [ z ];
|
||||
let nested = POLY.nest(polys);
|
||||
for (let poly of nested) {
|
||||
let zpro = 0, zinc = 1 / (polys.length * zs.length);
|
||||
for (let poly of polys) {
|
||||
// newPocket();
|
||||
for (let z of zs) {
|
||||
let clip = [], shadow;
|
||||
shadow = shadowAt(z);
|
||||
POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0);
|
||||
if (op.outline) {
|
||||
POLY.clearInner(clip);
|
||||
}
|
||||
if (clip.length === 0) {
|
||||
continue;
|
||||
}
|
||||
let slice = newSliceOut(z);
|
||||
let count = 999;
|
||||
slice.camTrace = { tool, rate, plunge };
|
||||
POLY.offset([ poly ], [ -toolDiam/2, -toolOver ], {
|
||||
count:999, outs: slice.camLines = [], flat:true, z,
|
||||
minArea: 0
|
||||
});
|
||||
if (toolDiam) {
|
||||
const offs = [ -toolDiam / 2, -toolOver ];
|
||||
POLY.offset(clip, offs, {
|
||||
count, outs: slice.camLines = [], flat:true, z, minArea: 0
|
||||
});
|
||||
} else {
|
||||
// when engraving with a 0 width tip
|
||||
slice.camLines = clip;
|
||||
}
|
||||
if (tabs) {
|
||||
slice.camLines = cutTabs(tabs, POLY.flatten(slice.camLines, null, true), z);
|
||||
} else {
|
||||
|
|
@ -1060,8 +1083,14 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, cutdir, false);
|
||||
slice.output()
|
||||
.setLayer("trace clear", {line: color}, false)
|
||||
.setLayer("trace", {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
if (debug && shadow) slice.output()
|
||||
.setLayer("trace shadow", {line: 0xff8811}, false)
|
||||
.addPolys(shadow)
|
||||
progress(zpro, "trace");
|
||||
zpro += zinc;
|
||||
addSlices(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1176,7 +1205,7 @@ class OpTrace extends CamOp {
|
|||
const zbo = widget.track.top - widget.track.box.d;
|
||||
let zmap = {};
|
||||
for (let poly of polys) {
|
||||
let z = minZ(poly.getZ());
|
||||
let z = minZ(poly.minZ());
|
||||
if (offover) {
|
||||
let pnew = POLY.offset([poly], -offover, { minArea: 0, open: true });
|
||||
if (pnew) {
|
||||
|
|
@ -1190,7 +1219,7 @@ class OpTrace extends CamOp {
|
|||
(zmap[z] = zmap[z] || []).appendAll(poly);
|
||||
}
|
||||
for (let [zv, polys] of Object.entries(zmap)) {
|
||||
clearZ(polys, parseFloat(zv), down);
|
||||
clearZnew(polys, parseFloat(zv), down);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.line
|
||||
// dep: geo.point
|
||||
// dep: geo.polygons
|
||||
// dep: kiri.slice
|
||||
|
|
@ -14,7 +15,7 @@ gapp.register("kiri-mode.cam.slice", (root, exports) => {
|
|||
const { base, kiri } = root;
|
||||
const { util } = base;
|
||||
const { driver, newSlice, setSliceTracker } = kiri;
|
||||
const { polygons, newPoint, newPolygon } = base;
|
||||
const { polygons, newLine, newPoint, newPolygon } = base;
|
||||
const { CAM } = driver;
|
||||
const { OPS } = CAM;
|
||||
|
||||
|
|
@ -379,60 +380,107 @@ CAM.addDogbones = function(poly, dist, reverse) {
|
|||
};
|
||||
|
||||
CAM.traces = async function(settings, widget, single) {
|
||||
if (widget.traces && widget.trace_single === single) {
|
||||
// do no work if cached
|
||||
if (widget.traces) {
|
||||
return false;
|
||||
}
|
||||
let slicer = new kiri.cam_slicer(widget);
|
||||
let zees = Object.assign({}, slicer.zFlat, slicer.zLine);
|
||||
let indices = [...new Set(Object.keys(zees)
|
||||
.map(kv => parseFloat(kv).round(5))
|
||||
.appendAll(Object.entries(single ? slicer.zLine : {}).map(ze => {
|
||||
let [ zk, zv ] = ze;
|
||||
return zv > 1 ? parseFloat(zk).round(5) : null;
|
||||
})
|
||||
.filter(v => v !== null))
|
||||
)]
|
||||
.sort((a,b) => b - a);
|
||||
let traces = [];
|
||||
// find and trim polys (including open) to shadow
|
||||
let oneach = data => {
|
||||
if (single) {
|
||||
for (let line of data.lines) {
|
||||
if (line.p1.distTo2D(line.p2) > 1) {
|
||||
traces.push(newPolygon().append(line.p1).append(line.p2).setOpen());
|
||||
}
|
||||
}
|
||||
} else
|
||||
base.polygons.flatten(data.tops).forEach(poly => {
|
||||
poly.inner = null;
|
||||
poly.parent = null;
|
||||
let z = poly.getZ();
|
||||
for (let i=0, il=traces.length; i<il; i++) {
|
||||
let trace = traces[i];
|
||||
let dz = Math.abs(z - trace.getZ());
|
||||
// only compare polys farther apart in Z
|
||||
if (dz > 0.01) {
|
||||
continue;
|
||||
}
|
||||
// do not add duplicates
|
||||
if (traces[i].isEquivalent(poly) && dz < 0.1) {
|
||||
// console.log({ dup: poly });
|
||||
return;
|
||||
}
|
||||
}
|
||||
traces.push(poly);
|
||||
});
|
||||
};
|
||||
|
||||
// --- points → line segments ---
|
||||
let edges = new THREE.EdgesGeometry(widget.mesh.geometry, 20);
|
||||
let array = edges.attributes.position.array;
|
||||
let pcache = {};
|
||||
let points = new Array(2);
|
||||
let lines = new Array(points.length / 2);
|
||||
for (let i=0, j=0, k=0, l=array.length; i<l; ) {
|
||||
let ps = [ array[i++], array[i++], array[i++] ];
|
||||
let key = ps.map(v => (v * 10000) | 0).join(',');
|
||||
let point = pcache[key];
|
||||
if (!point) {
|
||||
point = newPoint(ps[0], ps[1], ps[2], key);
|
||||
point.lines = [];
|
||||
pcache[key] = point;
|
||||
}
|
||||
points[j++ % 2] = point;
|
||||
if (j % 2 === 0) {
|
||||
let [ p0, p1 ] = points;
|
||||
let line = lines[k++] = newLine(p0, p1);
|
||||
p0.lines.push(line);
|
||||
p1.lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
// --- segments → chains (bidirectional walk) ---
|
||||
let chains = [];
|
||||
{
|
||||
const segs = lines.slice(); // shallow copy
|
||||
segs.forEach(s => (s.visited = false));
|
||||
|
||||
function step(dirPoint, prevSeg, pushFront, chain) {
|
||||
let curr = dirPoint;
|
||||
let prev = prevSeg;
|
||||
|
||||
while (curr.lines.length === 2) { // stay inside the arc
|
||||
const next = curr.lines.find(l => !l.visited && l !== prev);
|
||||
if (!next) break; // should not happen
|
||||
|
||||
next.visited = true;
|
||||
if (pushFront) chain.unshift(next); // prepend or append?
|
||||
else chain.push(next);
|
||||
|
||||
curr = next.p1 === curr ? next.p2 : next.p1;
|
||||
prev = next;
|
||||
}
|
||||
}
|
||||
|
||||
for (const seed of segs) {
|
||||
if (seed.visited) continue;
|
||||
|
||||
seed.visited = true; // always include the seed
|
||||
const chain = [seed];
|
||||
|
||||
// grow backwards from p1 (prepend)
|
||||
step(seed.p1, seed, true, chain);
|
||||
|
||||
// grow forwards from p2 (append)
|
||||
step(seed.p2, seed, false, chain);
|
||||
|
||||
chains.push(chain);
|
||||
}
|
||||
}
|
||||
|
||||
// --- chains → polylines ---
|
||||
const polylines = [];
|
||||
|
||||
for (const chain of chains) {
|
||||
const poly = newPolygon().setOpen();
|
||||
|
||||
// choose any node whose degree !== 2; if none, it’s a closed loop
|
||||
let start = null;
|
||||
for (const s of chain) {
|
||||
if (s.p1.lines.length !== 2) { start = s.p1; break; }
|
||||
if (s.p2.lines.length !== 2) { start = s.p2; break; }
|
||||
}
|
||||
if (!start) start = chain[0].p1; // closed loop
|
||||
|
||||
let curr = start, prevSeg = null;
|
||||
poly.push(curr); // first point
|
||||
|
||||
while (true) {
|
||||
const nextSeg = curr.lines.find(
|
||||
l => l !== prevSeg && chain.includes(l)
|
||||
);
|
||||
if (!nextSeg) break; // open end reached
|
||||
|
||||
curr = nextSeg.p1 === curr ? nextSeg.p2 : nextSeg.p1;
|
||||
poly.push(curr); // << push *before* test
|
||||
if (curr === start) break; // loop closed
|
||||
|
||||
prevSeg = nextSeg;
|
||||
}
|
||||
polylines.push(poly);
|
||||
}
|
||||
|
||||
widget.traces = polylines;
|
||||
|
||||
let opts = { each: oneach, over: false, flatoff: 0, edges: true, openok: true, lines: true };
|
||||
await slicer.slice(indices, opts);
|
||||
// pick up bottom features
|
||||
opts.over = true;
|
||||
await slicer.slice(indices, opts);
|
||||
widget.traces = traces;
|
||||
widget.trace_single = single;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -355,6 +355,27 @@ class Widget {
|
|||
}
|
||||
}
|
||||
|
||||
getVisualState() {
|
||||
return {
|
||||
edges: this.outline ? true : false,
|
||||
wires: this.wire ? true : false,
|
||||
opacity: this.getMaterial().opacity
|
||||
};
|
||||
}
|
||||
|
||||
setVisualState({ edges, wires, opacity}) {
|
||||
this.cache.vizstate = this.getVisualState();
|
||||
this.setEdges(edges ?? false);
|
||||
this.setWireframe(wires ?? false);
|
||||
this.setOpacity(opacity ?? 1);
|
||||
}
|
||||
|
||||
restoreVisualState() {
|
||||
if (this.cache.vizstate) {
|
||||
this.setVisualState(this.cache.vizstate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue