diff --git a/src/geo/polygon.js b/src/geo/polygon.js index c88a06b5..f834776a 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -70,11 +70,15 @@ export class Polygon { return bounds; } - getBounds3D() { - let bounds = new THREE.Box3(); + getBounds3D(bounds = new THREE.Box3()) { for (let point of this.points) { bounds.expandByPoint(point); } + if (this.inner) { + for (let i of this.inner) { + i.getBounds3D(bounds); + } + } return bounds; } diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index 70e627e9..117559f7 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -426,6 +426,7 @@ export const conf = { camAreaSpindle: 1000, camAreaTool: 1000, camAreaMode: "clear", + camAreaOffset: "none", camAreaOver: 0.4, camAreaDown: 1, camAreaSpeed: 1000, @@ -433,6 +434,7 @@ export const conf = { camAreaExpand: 0, camAreaSmooth: 1, camAreaRefine: 0, + camAreaOutline: false, camContourAngle: 85, camContourBottom: false, camContourBridge: 0, diff --git a/src/kiri/mode/cam/cl-ops.js b/src/kiri/mode/cam/cl-ops.js index 3cdf1dcf..52c855e6 100644 --- a/src/kiri/mode/cam/cl-ops.js +++ b/src/kiri/mode/cam/cl-ops.js @@ -654,10 +654,22 @@ export function createPopOps() { 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', { spindle: 'camAreaSpindle', tool: 'camAreaTool', mode: 'camAreaMode', + offset: 'camAreaOffset', over: 'camAreaOver', down: 'camAreaDown', rate: 'camAreaSpeed', @@ -665,6 +677,7 @@ export function createPopOps() { expand: 'camAreaExpand', smooth: 'camAreaSmooth', refine: 'camAreaRefine', + outline: 'camAreaOutline', ov_topz: 0, ov_botz: 0, ov_conv: '~camConventional', @@ -672,29 +685,34 @@ export function createPopOps() { }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), 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" }), 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 }), plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: toInt, 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 }), - 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_end: UC.endExpand(), exp: UC.newExpand("overrides"), sep: UC.newBlank({ class: "pop-sep" }), 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_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }), 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" }), }; } diff --git a/src/kiri/mode/cam/op-area.js b/src/kiri/mode/cam/op-area.js index 65917d8a..2b417755 100644 --- a/src/kiri/mode/cam/op-area.js +++ b/src/kiri/mode/cam/op-area.js @@ -20,7 +20,7 @@ class OpArea extends CamOp { async slice(progress) { const pocket = 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 { settings, widget, tabs, color } = state; let { addSlices, setToolDiam, cutTabs, healPolys, shadowAt, workarea } = state; @@ -29,7 +29,7 @@ class OpArea extends CamOp { let toolDiam = areaTool.fluteDiameter(); let toolOver = areaTool.hasTaper() ? over : toolDiam * over; 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 }); @@ -98,6 +98,8 @@ class OpArea extends CamOp { let proc = 0; let pinc = 1 / polys.length; for (let area of polys) { + let bounds = area.getBounds3D(); + if (devel) newLayer().output() .setLayer("area", { line: 0xff8800 }, false) .addPolys([ area ]); @@ -105,25 +107,39 @@ class OpArea extends CamOp { newArea(); 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 zinc = 1 / zs.length; + let lzo; + outer: for (;;) for (let z of zs) { let slice = newLayer(); let layers = slice.output(); let outs = []; let clip = []; 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.offset(clip, [ -toolDiam / 2, -toolOver ], { count: 999, outs, flat: true, z, minArea: 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 - break; + break outer; } slice.camLines = outs; zroc += zinc; + lzo = z; progress(proc + (pinc * zroc), 'clear'); if (devel) layers .setLayer("shadow", { line: 0x0088ff }, false) @@ -131,12 +147,47 @@ class OpArea extends CamOp { layers .setLayer("clear", { line: 0x88ff00 }, false) .addPolys(outs); + // of the last output still cuts, we need an escape + if (z === zs.peek()) { + break outer; + } } proc += pinc; progress(proc, 'clear'); } else 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 if (mode === 'surface') { @@ -154,6 +205,7 @@ class OpArea extends CamOp { setTool(op.tool, op.rate); setSpindle(op.spindle); + // process areas as pockets while (areas?.length) { let printPoint = getPrintPoint(); let min = {