start implementing area trace

This commit is contained in:
Stewart Allen 2025-11-29 16:08:46 -05:00
commit a02e4867c6
4 changed files with 96 additions and 20 deletions

View file

@ -70,11 +70,15 @@ export class Polygon {
return bounds; return bounds;
} }
getBounds3D() { getBounds3D(bounds = new THREE.Box3()) {
let bounds = new THREE.Box3();
for (let point of this.points) { for (let point of this.points) {
bounds.expandByPoint(point); bounds.expandByPoint(point);
} }
if (this.inner) {
for (let i of this.inner) {
i.getBounds3D(bounds);
}
}
return bounds; return bounds;
} }

View file

@ -426,6 +426,7 @@ export const conf = {
camAreaSpindle: 1000, camAreaSpindle: 1000,
camAreaTool: 1000, camAreaTool: 1000,
camAreaMode: "clear", camAreaMode: "clear",
camAreaOffset: "none",
camAreaOver: 0.4, camAreaOver: 0.4,
camAreaDown: 1, camAreaDown: 1,
camAreaSpeed: 1000, camAreaSpeed: 1000,
@ -433,6 +434,7 @@ export const conf = {
camAreaExpand: 0, camAreaExpand: 0,
camAreaSmooth: 1, camAreaSmooth: 1,
camAreaRefine: 0, camAreaRefine: 0,
camAreaOutline: false,
camContourAngle: 85, camContourAngle: 85,
camContourBottom: false, camContourBottom: false,
camContourBridge: 0, camContourBridge: 0,

View file

@ -654,10 +654,22 @@ export function createPopOps() {
createPopOp('|', {}).inputs = {}; createPopOp('|', {}).inputs = {};
function isClear() {
return env.poppedRec.mode === 'clear';
}
function isTrace() {
return env.poppedRec.mode === 'trace';
}
function isSurface() {
return env.poppedRec.mode === 'surface';
}
createPopOp('area', { createPopOp('area', {
spindle: 'camAreaSpindle', spindle: 'camAreaSpindle',
tool: 'camAreaTool', tool: 'camAreaTool',
mode: 'camAreaMode', mode: 'camAreaMode',
offset: 'camAreaOffset',
over: 'camAreaOver', over: 'camAreaOver',
down: 'camAreaDown', down: 'camAreaDown',
rate: 'camAreaSpeed', rate: 'camAreaSpeed',
@ -665,6 +677,7 @@ export function createPopOps() {
expand: 'camAreaExpand', expand: 'camAreaExpand',
smooth: 'camAreaSmooth', smooth: 'camAreaSmooth',
refine: 'camAreaRefine', refine: 'camAreaRefine',
outline: 'camAreaOutline',
ov_topz: 0, ov_topz: 0,
ov_botz: 0, ov_botz: 0,
ov_conv: '~camConventional', ov_conv: '~camConventional',
@ -672,29 +685,34 @@ export function createPopOps() {
}).inputs = { }).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"), tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
mode: UC.newSelect(LANG.mo_menu, {}, "opmode"), mode: UC.newSelect(LANG.mo_menu, {}, "opmode"),
offset: UC.newSelect(LANG.cc_offs_s, { title: LANG.cc_offs_l, show: isTrace }, "traceoff"),
sep: UC.newBlank({ class: "pop-sep" }),
menu: UC.newRow([
UC.newButton("edge", traceAdd),
UC.newButton("surface", surfaceAdd),
], { class: "ext-buttons f-row" }),
outline: UC.newBoolean(LANG.cp_outl_s, undefined, { title: LANG.cp_outl_l, show: () => isClear() || isTrace() }),
expand: UC.newInput(LANG.cp_xpnd_s, { title: LANG.cp_xpnd_l, convert: toFloat, units }),
sep: UC.newBlank({ class: "pop-sep" }),
over: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.001, 100.0), units, show: isClear }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, bound: UC.bound(0, 100.0), units, show: () => isClear() || isTrace() }),
sep: UC.newBlank({ class: "pop-sep", show: isSurface }),
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: toInt, show: isSurface }),
smooth: UC.newInput(LANG.cp_smoo_s, { title: LANG.cp_smoo_l, convert: toInt, show: isSurface }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: toFloat, bound: UC.bound(0, 10.0), units, round: 4, show: isSurface }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("feeds & speeds"),
sep: UC.newBlank({ class: "pop-sep" }), sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }), spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }), rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: toInt, units }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }), plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, units }),
sep: UC.newBlank({ class: "pop-sep" }), exp_end: UC.endExpand(),
over: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.001, 100.0), units }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, bound: UC.bound(0.001, 100.0), units }),
sep: UC.newBlank({ class: "pop-sep" }),
expand: UC.newInput(LANG.cp_xpnd_s, { title: LANG.cp_xpnd_l, convert: toFloat, units }),
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: toInt }),
smooth: UC.newInput(LANG.cp_smoo_s, { title: LANG.cp_smoo_l, convert: toInt }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: toFloat, bound: UC.bound(0, 10.0), units, round: 4 }),
exp: UC.newExpand("overrides"), exp: UC.newExpand("overrides"),
sep: UC.newBlank({ class: "pop-sep" }), sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }), ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: toFloat, units }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }), ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: toFloat, units }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }), ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(), exp_end: UC.endExpand(),
sep: UC.newBlank({ class: "pop-sep" }),
menu: UC.newRow([
UC.newButton("edge", traceAdd),
UC.newButton("surface", surfaceAdd),
], { class: "ext-buttons f-row" }),
}; };
} }

View file

@ -20,7 +20,7 @@ class OpArea extends CamOp {
async slice(progress) { async slice(progress) {
const pocket = this; const pocket = this;
let { op, state } = this; let { op, state } = this;
let { tool, mode, down, over, follow, expand, plunge, smooth, tolerance } = op; let { tool, mode, down, over, follow, expand, offset, outline, smooth, tolerance } = op;
let { ov_topz, ov_botz, ov_conv } = op; let { ov_topz, ov_botz, ov_conv } = op;
let { settings, widget, tabs, color } = state; let { settings, widget, tabs, color } = state;
let { addSlices, setToolDiam, cutTabs, healPolys, shadowAt, workarea } = state; let { addSlices, setToolDiam, cutTabs, healPolys, shadowAt, workarea } = state;
@ -29,7 +29,7 @@ class OpArea extends CamOp {
let toolDiam = areaTool.fluteDiameter(); let toolDiam = areaTool.fluteDiameter();
let toolOver = areaTool.hasTaper() ? over : toolDiam * over; let toolOver = areaTool.hasTaper() ? over : toolDiam * over;
let zTop = ov_topz ? workarea.bottom_stock + ov_topz : workarea.top_stock; let zTop = ov_topz ? workarea.bottom_stock + ov_topz : workarea.top_stock;
let zBottom = ov_botz ? workarea.bottom_stock + ov_botz : workarea.bottom_stock; let zBottom = ov_botz ? workarea.bottom_stock + ov_botz : workarea.bottom_part;
console.log({ workarea, zTop, zBottom }); console.log({ workarea, zTop, zBottom });
@ -98,6 +98,8 @@ class OpArea extends CamOp {
let proc = 0; let proc = 0;
let pinc = 1 / polys.length; let pinc = 1 / polys.length;
for (let area of polys) { for (let area of polys) {
let bounds = area.getBounds3D();
if (devel) newLayer().output() if (devel) newLayer().output()
.setLayer("area", { line: 0xff8800 }, false) .setLayer("area", { line: 0xff8800 }, false)
.addPolys([ area ]); .addPolys([ area ]);
@ -105,25 +107,39 @@ class OpArea extends CamOp {
newArea(); newArea();
if (mode === 'clear') { if (mode === 'clear') {
let zs = base_util.lerp(zTop, zBottom, down); let zs = down ? base_util.lerp(zTop, zBottom, down) : [ bounds.min.z ];
let zroc = 0; let zroc = 0;
let zinc = 1 / zs.length; let zinc = 1 / zs.length;
let lzo;
outer: for (;;)
for (let z of zs) { for (let z of zs) {
let slice = newLayer(); let slice = newLayer();
let layers = slice.output(); let layers = slice.output();
let outs = []; let outs = [];
let clip = []; let clip = [];
let shadow = shadowAt(z); let shadow = shadowAt(z);
if (outline) {
shadow = shadow.clone(true);
// remove shadow inner when processing outline only
for (let poly of shadow) poly.inner = undefined;
}
POLY.subtract([ area ], shadow, clip, undefined, undefined, 0); POLY.subtract([ area ], shadow, clip, undefined, undefined, 0);
POLY.offset(clip, [ -toolDiam / 2, -toolOver ], { POLY.offset(clip, [ -toolDiam / 2, -toolOver ], {
count: 999, outs, flat: true, z, minArea: 0 count: 999, outs, flat: true, z, minArea: 0
}); });
if (outs.length === 0) { if (outs.length === 0) {
if (bounds && lzo > bounds.min.z) {
// try a bottom layer matching bottom of selection
zs = [ bounds.min.z ];
bounds = undefined;
continue outer;
}
// terminate z descent when no further output possible // terminate z descent when no further output possible
break; break outer;
} }
slice.camLines = outs; slice.camLines = outs;
zroc += zinc; zroc += zinc;
lzo = z;
progress(proc + (pinc * zroc), 'clear'); progress(proc + (pinc * zroc), 'clear');
if (devel) layers if (devel) layers
.setLayer("shadow", { line: 0x0088ff }, false) .setLayer("shadow", { line: 0x0088ff }, false)
@ -131,12 +147,47 @@ class OpArea extends CamOp {
layers layers
.setLayer("clear", { line: 0x88ff00 }, false) .setLayer("clear", { line: 0x88ff00 }, false)
.addPolys(outs); .addPolys(outs);
// of the last output still cuts, we need an escape
if (z === zs.peek()) {
break outer;
}
} }
proc += pinc; proc += pinc;
progress(proc, 'clear'); progress(proc, 'clear');
} else } else
if (mode === 'trace') { if (mode === 'trace') {
let zs = down ? base_util.lerp(zTop, bounds.min.z, down) : [ bounds.min.z ];
let zroc = 0;
let zinc = 1 / zs.length;
let lzo;
if (outline) {
area.inner = undefined;
}
for (let z of zs) {
let slice = newLayer();
let layers = slice.output();
let outs = [];
if (offset === 'none') {
outs = [ area.clone().setZ(z) ];
} else {
POLY.offset([ area ], offset === 'inside' ? [ -toolDiam / 2 ] : [ toolDiam / 2 ], {
count: 1, outs, flat: true, z, minArea: 0
});
}
if (outs.length === 0) {
// terminate z descent when no further output possible
break;
}
slice.camLines = outs;
zroc += zinc;
lzo = z;
progress(proc + (pinc * zroc), 'trace');
layers
.setLayer("trace", { line: 0x88ff00 }, false)
.addPolys(outs);
}
proc += pinc;
progress(proc, 'trace');
} else } else
if (mode === 'surface') { if (mode === 'surface') {
@ -154,6 +205,7 @@ class OpArea extends CamOp {
setTool(op.tool, op.rate); setTool(op.tool, op.rate);
setSpindle(op.spindle); setSpindle(op.spindle);
// process areas as pockets
while (areas?.length) { while (areas?.length) {
let printPoint = getPrintPoint(); let printPoint = getPrintPoint();
let min = { let min = {