implement svg trace path loading

This commit is contained in:
Stewart Allen 2026-01-07 21:56:26 -05:00
commit 6ec01bbdbc
5 changed files with 150 additions and 79 deletions

View file

@ -1034,7 +1034,11 @@ function newButton(label, action, opt = {}) {
*/
function newRow(children, options) {
let row = addCollapsableElement((options && options.noadd) ? null : addTo);
if (children) children.forEach(function (c) { row.appendChild(c) });
if (children) {
children.forEach((c) => {
row.appendChild(c);
});
};
addModeControls(row, options);
if (options && options.class) {
options.class.split(' ').forEach(ce => {

View file

@ -8,7 +8,7 @@ import { opFlip } from './cl-flip.js';
import { selectHoles } from './cl-hole.js';
import { selectHelical } from './cl-helical.js';
import { surfaceAdd } from './cl-surface.js';
import { traceAdd } from './cl-trace.js';
import { traceAdd, traceClear, traceLoad } from './cl-trace.js';
import { startFaceUp } from '../../app/face-tool.js';
import { showZBottom, showZTop, updateStock } from './cl-stock.js';
@ -137,14 +137,21 @@ export function createPopOp(type, map) {
return rec;
},
hideshow: () => {
let conf = api.conf.get();
for (let inp of Object.values(op.inputs)) {
let parent = inp.parentElement;
if (parent && parent.setVisible && parent.__opt.show) {
// for normal inputs
parent.setVisible(parent.__opt.show(op, api.conf.get()));
parent.setVisible(parent.__opt.show(op, conf));
} else if (inp.__opt?.show) {
// for blanks
inp.setVisible(inp.__opt.show(op, api.conf.get()));
inp.setVisible(inp.__opt.show(op, conf));
} else if (inp.children) {
for (let c of inp.children) {
if (c.__opt?.show) {
c.setVisible(c.__opt.show(op, conf));
}
}
}
}
},
@ -322,10 +329,6 @@ export function createPopOps() {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
direction: UC.newSelect(LANG.ou_dire_s, { title: LANG.ou_dire_l }, "direction"),
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" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0) }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
leave: UC.newInput(LANG.cr_lsto_s, { title: LANG.cr_lsto_l, convert: toFloat, units }),
@ -336,7 +339,12 @@ export function createPopOps() {
inside: UC.newBoolean(LANG.cr_olin_s, undefined, { title: LANG.cr_olin_l, show: () => !env.poppedRec.all || env.poppedRec.inside }),
omitthru: UC.newBoolean(LANG.co_omit_s, undefined, { title: LANG.co_omit_l }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("overrides", ov_hover),
exp: UC.newExpand("feeds & speeds", { }),
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 }),
exp_end: UC.endExpand(),
exp: UC.newExpand("top & bottom", ov_hover),
ov_topz: UC.newInput(LANG.ou_ztop_s, ov_topz),
ov_botz: UC.newInput(LANG.ou_zbot_s, ov_botz),
exp_end: UC.endExpand(),
@ -364,9 +372,6 @@ export function createPopOps() {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
direction: UC.newSelect(LANG.ou_dire_s, { title: LANG.ou_dire_l }, "direction"),
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 }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0), show: () => env.popOp.outline.rec.wide }),
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: toInt, bound: UC.bound(1, 500), show: () => env.popOp.outline.rec.wide }),
@ -380,7 +385,12 @@ export function createPopOps() {
dogbones: UC.newBoolean(LANG.co_dogb_s, undefined, { title: LANG.co_dogb_l, show: (op) => { return !op.inputs.wide.checked } }),
revbones: UC.newBoolean(LANG.co_dogr_s, undefined, { title: LANG.co_dogr_l, show: () => env.poppedRec.dogbones }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("overrides", ov_hover),
exp: UC.newExpand("feeds & speeds", { }),
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 }),
exp_end: UC.endExpand(),
exp: UC.newExpand("top & bottom", ov_hover),
ov_topz: UC.newInput(LANG.ou_ztop_s, ov_topz),
ov_botz: UC.newInput(LANG.ou_zbot_s, ov_botz),
exp_end: UC.endExpand(),
@ -487,10 +497,6 @@ export function createPopOps() {
offset: UC.newSelect(LANG.cc_offs_s, { title: LANG.cc_offs_l, show: () => (env.poppedRec.mode === 'follow') }, "traceoff"),
direction: UC.newSelect(LANG.ou_dire_s, { title: LANG.ou_dire_l }, "direction"),
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" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0), show: (op) => env.popOp.trace.rec.mode === "clear" }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
offover: UC.newInput(LANG.cc_offd_s, { title: LANG.cc_offd_l, convert: toFloat, units, show: () => env.poppedRec.offset !== "none" || env.poppedRec.mode === "clear" }),
@ -499,13 +505,22 @@ export function createPopOps() {
merge: UC.newBoolean(LANG.co_merg_s, undefined, { title: LANG.co_merg_l, show: () => !env.popOp.trace.rec.down }),
dogbone: UC.newBoolean(LANG.co_dogb_s, undefined, { title: LANG.co_dogb_l, show: canDogBones }),
revbone: UC.newBoolean(LANG.co_dogr_s, undefined, { title: LANG.co_dogr_l, show: canDogBonesRev }),
exp: UC.newExpand("overrides", ov_hover),
exp: UC.newExpand("feeds & speeds", { }),
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 }),
exp_end: UC.endExpand(),
exp: UC.newExpand("top & bottom", ov_hover),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, ov_topz),
ov_botz: UC.newInput(LANG.ou_zbot_s, ov_botz),
exp_end: UC.endExpand(),
sep: UC.newBlank({ class: "pop-sep" }),
menu: UC.newRow([UC.newButton("select", traceAdd)], { class: "ext-buttons f-row" }),
menu: UC.newRow([
UC.newButton("select", traceAdd),
// UC.newButton("load", traceLoad, { show: () => env.poppedRec.mode === "follow" }),
// UC.newButton("", traceClear, { icon: '<i class="fa fa-trash"></i>' }),
], { class: "ext-buttons f-row", row: true }),
};
createPopOp('pocket', {
@ -530,10 +545,6 @@ export function createPopOps() {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
direction: UC.newSelect(LANG.ou_dire_s, { title: LANG.ou_dire_l }, "direction"),
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" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 1.0) }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units, show: () => !env.poppedRec.contour }),
sep: UC.newBlank({ class: "pop-sep" }),
@ -545,7 +556,12 @@ export function createPopOps() {
sep: UC.newBlank({ class: "pop-sep" }),
contour: UC.newBoolean(LANG.cp_cont_s, undefined, { title: LANG.cp_cont_s }),
outline: UC.newBoolean(LANG.cp_outl_s, undefined, { title: LANG.cp_outl_l }),
exp: UC.newExpand("overrides", ov_hover),
exp: UC.newExpand("feeds & speeds", { }),
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 }),
exp_end: UC.endExpand(),
exp: UC.newExpand("top & bottom", ov_hover),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, ov_topz),
ov_botz: UC.newInput(LANG.ou_zbot_s, ov_botz),

View file

@ -2,9 +2,11 @@
import { api } from '../../app/api.js';
import { env, clearPops, isDark } from './init-ui.js';
import { parse } from '../../../load/svg.js';
import { CAM } from './driver-fe.js';
import { Layers } from '../../app/layers.js';
import { Stack } from '../../app/stack.js';
import { newPolygon } from '../../../geo/polygon.js';
export let traceOn = false;
export let lastTrace;
@ -13,6 +15,95 @@ export function setLastTrace(trace) {
lastTrace = trace;
}
export function traceLoad(ev) {
let widget = api.widgets.all()[0];
if (!widget) return;
window.showOpenFilePicker({
types: [{
description: 'SVG Files',
accept: { 'text/svg': ['.svg'] }
}],
multiple: false
}).then(async fileHandle => {
const file = await fileHandle[0].getFile()
const text = await file.text(); // string
// const buf = await file.arrayBuffer(); // binary
// const blob = await file.slice();
const polys = parse(text, { flat: true, soup: true });
const rec = env.poppedRec;
rec.areas[widget.id] = polys.map(p => p.toArray());
rec.svg = true;
traceAdd();
}).catch(error => {
console.log('no file load', error);
});
}
export function traceClear(ev) {
return;
api.widgets.for(widget => {
unselectTraces(widget, true);
widget.traces = [];
widget.adds = [];
});
env.poppedRec.areas = [];
env.poppedRec.svg = false;
traceDone();
}
function startTraceAdd(ids) {
api.hide.alert(alert);
alert = api.show.alert("[esc] cancels trace editing");
api.widgets.for(widget => {
if (ids.indexOf(widget.id) >= 0) {
unselectTraces(widget, true);
widget.trace_stack = null;
}
if (widget.trace_stack) {
widget.adds.appendAll(widget.trace_stack.meshes);
widget.trace_stack.show();
return;
}
let areas = (env.poppedRec.areas[widget.id] || []);
let stack = new Stack(widget.mesh);
widget.trace_stack = stack;
widget.traces?.forEach(poly => {
let match = areas.filter(arr => poly.matches(arr));
let layers = new Layers();
layers.setLayer("trace", { line: 0xaaaa55, fat: 4, order: -10 }, false).addPoly(poly);
stack.addLayers(layers);
stack.new_meshes.forEach(mesh => {
mesh.trace = { widget, poly };
// ensure trace poly singleton from matches
if (match.length > 0) {
poly._trace = match[0];
} else {
poly._trace = poly.toArray();
}
});
widget.adds.appendAll(stack.new_meshes);
// console.log(widget)
});
});
// ensure appropriate traces are toggled matching current record
api.widgets.for(widget => {
widget.setVisualState({ opacity: 0.25 });
let areas = (env.poppedRec.areas[widget.id] || []);
let stack = widget.trace_stack;
stack.meshes.forEach(mesh => {
let { poly } = mesh.trace;
let match = areas.filter(arr => poly.matches(arr));
if (match.length > 0) {
if (!mesh.selected) {
traceToggle(mesh, true);
}
} else if (mesh.selected) {
traceToggle(mesh, true);
}
});
});
}
export function traceAdd(ev) {
if (traceOn) {
return traceDone();
@ -25,58 +116,16 @@ export function traceAdd(ev) {
api.feature.hoverAdds = true;
env.hover = traceHover;
env.hoverUp = traceHoverUp;
CAM.traces((ids) => {
api.hide.alert(alert);
alert = api.show.alert("[esc] cancels trace editing");
api.widgets.for(widget => {
if (ids.indexOf(widget.id) >= 0) {
unselectTraces(widget, true);
widget.trace_stack = null;
}
if (widget.trace_stack) {
widget.adds.appendAll(widget.trace_stack.meshes);
widget.trace_stack.show();
return;
}
let areas = (env.poppedRec.areas[widget.id] || []);
let stack = new Stack(widget.mesh);
widget.trace_stack = stack;
widget.traces?.forEach(poly => {
let match = areas.filter(arr => poly.matches(arr));
let layers = new Layers();
layers.setLayer("trace", { line: 0xaaaa55, fat: 4, order: -10 }, false).addPoly(poly);
stack.addLayers(layers);
stack.new_meshes.forEach(mesh => {
mesh.trace = { widget, poly };
// ensure trace poly singleton from matches
if (match.length > 0) {
poly._trace = match[0];
} else {
poly._trace = poly.toArray();
}
});
widget.adds.appendAll(stack.new_meshes);
// console.log(widget)
});
});
// ensure appropriate traces are toggled matching current record
api.widgets.for(widget => {
widget.setVisualState({ opacity: 0.25 });
let areas = (env.poppedRec.areas[widget.id] || []);
let stack = widget.trace_stack;
stack.meshes.forEach(mesh => {
let { poly } = mesh.trace;
let match = areas.filter(arr => poly.matches(arr));
if (match.length > 0) {
if (!mesh.selected) {
traceToggle(mesh, true);
}
} else if (mesh.selected) {
traceToggle(mesh, true);
}
});
});
}, env.poppedRec.select === 'lines');
if (env.poppedRec.svg === true) {
let widgets = api.widgets.all();
for (let [ id, areas ] of Object.entries(env.poppedRec.areas)) {
let widget = widgets.filter(w => w.id === id)[0];
widget.traces = areas.map(arr => newPolygon().fromArray(arr));
}
startTraceAdd(Object.keys(env.poppedRec.areas));
} else {
CAM.traces(startTraceAdd, env.poppedRec.select === 'lines');
}
}
export function traceDone() {

View file

@ -1346,10 +1346,12 @@ details[open] summary::after {
cursor: pointer;
}
.ext-buttons button {
display: inline-flex;
line-height: 1;
justify-content: center;
align-items: center;
text-align: center;
flex-grow: 1;
flex: 1 1 0; /* equal width */
margin-top: 3px;
height: 75%;
}

View file

@ -110,8 +110,8 @@
<div id="file-import">
<label lk="import">import</label>
<span><i class="fas fa-file-upload"></i></span>
<!-- required file input loader -->
<input id="load-file" type="file" name="loadme" style="display:none"></input>
<!-- required file input loaders -->
<input id="load-file" type="file" name="loadme" style="display:none" accept=".km,.kmz,.stl,.obj,.svg,.png,.jpg,.jpeg,.gcode,.nc"></input>
</div>
<hr>
<div id="app-export">