splitting cam client into modules

This commit is contained in:
Stewart Allen 2025-07-06 13:13:11 -04:00
commit 37e081ed8e
8 changed files with 1493 additions and 1432 deletions

View file

@ -0,0 +1,82 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from '../../kiri/api.js';
import { env } from './client.js';
import { CAM } from './driver-fe.js';
export function opFlip() {
api.view.set_arrange();
let widgets = api.widgets.all();
let { process } = env.current;
let { ops, op2 } = process;
// add flip singleton to b-side
let add2 = op2.length === 0;
let axis = env.poppedRec.axis;
env.flipping = true;
process.camZAnchor = {
top: "bottom",
bottom: "top",
middle: "middle"
}[process.camZAnchor];
// flip tabs
for (let widget of widgets) {
let anno = api.widgets.annotate(widget.id).tab || [];
let wbm = widget.bounds.max.z;
for (let tab of anno) {
let box = widget.tabs[tab.id].box;s
let bpo = box.position;
let xr = 0, yr = 0;
let flz = wbm - bpo.z;
if (axis === 'X') {
tab.pos.y = -tab.pos.y;
bpo.y = -bpo.y;
xr = Math.PI / 2;
}
if (axis === 'Y') {
tab.pos.x = -tab.pos.x;
bpo.x = -bpo.x;
yr = Math.PI / 2;
}
tab.pos.z = bpo.z = flz;
let [rx, ry, rz, rw] = tab.rot;
let qat = new THREE.Quaternion(rx, ry, rz, rw);
let eul = new THREE.Euler().setFromQuaternion(qat);
eul._z = -eul._z;
tab.rot = new THREE.Quaternion().setFromEuler(eul);
}
clearTabs(widget, true);
restoreTabs([widget]);
}
// flip widget
if (axis === 'X') {
api.selection.rotate(Math.PI, 0, 0);
}
if (axis === 'Y') {
api.selection.rotate(0, Math.PI, 0);
}
// clear traces cache
CAM.traces_clear();
api.client.clear();
env.flipping = false;
process.ops = op2;
process.op2 = ops;
// flip camZBottom
if (env.poppedRec.invert && process.camZBottom && env.camZBottom) {
const maxZ = env.camZBottom._max.z
process.camZBottom = maxZ - process.camZBottom;
api.util.rec2ui(process);
updateStock();
}
// keep flip operations in sync
for (let op of op2) {
if (op.type === 'flip') {
op.axis = env.poppedRec.axis;
op.invert = env.poppedRec.invert;
}
}
if (add2) {
opAdd(env.poppedRec);
} else {
opRender();
}
}

View file

@ -0,0 +1,216 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from '../../kiri/api.js';
import { env } from './client.js';
import { lastTrace, setLastTrace } from './cl-trace.js';
export let holeSelOn = false, lastSelHoles;
/**
* Client side function to select holes in widgets for CAM operations.
* If holes have already been analyzed, they are displayed immediately.
* Otherwise, the CAM server is queried and the results are cached.
* @param {boolean} individual - select all holes, false to only select
* holes of tool diameter.
*/
export async function selectHoles(individual) {
// console.log("client individual selected",individual)
if (holeSelOn) {
return selectHolesDone();
}
env.clearPops();
let alert = api.show.alert("analyzing parts...");
holeSelOn = env.hoveredOp;
holeSelOn.classList.add("editing");
api.feature.hover = true;
api.feature.hoverAdds = true;
env.hover = selectHolesHover;
env.hoverUp = selectHolesHoverUp;
const widgets = api.widgets.all()
/**
* creates a mesh for a hole and adds it to a widget
* @param {Object3D} widget - widget to add the hole mesh to
* @param {Object} drill - {depth,selected} object of the hole
* @returns {Mesh} the created mesh
*/
function createHoleMesh(widget, drill) {
let { depth, selected, diam } = drill
let color = selected ? 0xFF0000 : 0x39e366
let geo = new THREE.CylinderGeometry(diam / 2, diam / 2, depth, 20);
const material = new THREE.MeshPhongMaterial({ color });
let mesh = new THREE.Mesh(geo, material);
mesh.position.copy(drill)
mesh.position.z -= depth / 2
mesh.rotation.x = Math.PI / 2
drill.widgetID = widget.id
drill.meshId = mesh.id; // add pointers to both objects
mesh.hole = drill;
mesh.parent = widget.mesh;
widget.mesh.add(mesh);
widget.adds.push(mesh); // for click detection
return mesh
}
let meshesCached = widgets.every(widget => env.poppedRec.drills[widget.id] != undefined)
if (individual && meshesCached) {
// if any widget already has cached holes
// console.log("already has cached holes",env.poppedRec.drills)
api.hide.alert(alert);
api.widgets.for(widget => {
if (widget.adds) {
let drills = env.poppedRec.drills[widget.id];
let ids = drills.map(hole => hole.meshId);
if (false && widget.adds.includes(mesh)) {
// mesh in the old code was a dead artifact
// so this code branch never actually ran
widget.adds.forEach(add => {
if (ids.includes(add.id)) {
add.visible = true;
}
})
} else {
drills.forEach(drill => {
createHoleMesh(widget, drill);
});
}
}
})
} else {
// if no widget has cached holes
let alert2 = api.show.alert("");
let found = await CAM.holes(
individual,
env.poppedRec,
(progress, msg) => {
alert2[0] = msg;
api.show.progress(progress, msg);
api.alerts.update();
},
async centers => {
api.show.progress(0);
if (!Array.isArray(centers)) {
console.log("worker returned a malformed drills response");
return;
}
if (centers.length === 0) {
console.log("no drill holes found");
return;
}
let shadow = centers.some(c => c.shadowed);
api.hide.alert(alert2);
if (shadow) {
alert2 = api.show.alert("Some holes are shadowed by part and are not shown.");
}
centers = centers ?? [];
// list of all hole centers and if they are selected
api.widgets.for(widget => {
const { holes } = centers.find(center => center.id == widget.id);
// console.log(holes)
if (!holes.length) unselectHoles(holes);
holes.forEach(hole => {
createHoleMesh(widget, hole);
});
//add hole data to record
env.poppedRec.drills = env.poppedRec.drills ?? {};
env.poppedRec.drills[widget.id] = holes;
//give widget access to an array of drill records that refrence it
//so that it can be cleared when widget is rotated or mirrored etc.
if (!widget.drills) { widget.drills = [] };
widget.drills.push(holes);
})
}
);
if (!found || found.length === 0) {
api.hide.alert(alert);
api.hide.alert(alert2);
api.show.alert("no drill holes found");
return;
}
}
//hide the alert once hole meshes are calculated on the worker, and then added to the scene
// api.hide.alert(alert);
let escAlert = api.show.alert("[esc] cancels drill editing", 1000);
setTimeout(() => {
api.hide.alert(escAlert);
}, 5000);
api.widgets.opacity(0.8);
}
export function selectHolesHover(data) {
//not used right now. may be useful in the future
if (lastTrace) {
let { color, colorSave } = lastTrace.material[0] || lastTrace.material;
color.r = colorSave.r;
color.g = colorSave.g;
color.b = colorSave.b;
lastTrace.position.z -= 0.01;
}
if (data.type === 'platform') {
setLastTrace();
return;
}
}
export function selectHolesHoverUp(int, ev) {
if (!int) return; //if not a hole mesh return
let { object } = int;
selectHoleToggle(object);
}
/**
* Toggle the selection of a hole mesh and update its color
* @param {Object3D} mesh - the hole mesh to toggle
*/
export function selectHoleToggle(mesh) {
let { hole } = mesh
if (!hole) return
hole.selected = !hole.selected;
mesh.material.color.setHex(hole.selected ? 0xFF0000 : 0x39e366);
}
/**
* Clears the recorded holes in the widget (widget.drills array)
* and also clears the widget.adds array.
* @param {Object} widget - the widget with the drills array to clear
*/
export function clearHolesRec(widget) {
if (widget.drills) {
widget.drills.forEach(rec => {
})
}
if (widget.adds) {
widget.adds.length = 0 //clear adds array
}
}
/**
* Cleanup function for selectHoles.
* Removes all adds from the scene, hides the alert, and resets the opacity of the widgets.
* Also resets the hover features and the editing class on the holeSelOn html element.
*/
export function selectHolesDone() {
if (!holeSelOn) {
return;
}
env.func.unpop();
holeSelOn.classList.remove("editing");
holeSelOn = false;
api.widgets.opacity(1);
api.hide.alert(alert);
api.feature.hover = false;
api.feature.hoverAdds = false;
api.widgets.for(widget => {
for (let add of widget.adds) {
add.visible = false
widget.mesh.remove(add);
}
});
}
export function unselectHoles(widget) {
if (!widget.holes) return
widget.holes.forEach(hole => {
hole.selected = false
})
}

View file

@ -0,0 +1,598 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $, h } from '../../moto/webui.js';
import { api } from '../../kiri/api.js';
import { env } from './client.js';
import { Tool } from './tool.js';
import { opFlip } from './cl-flip.js';
import { selectHoles } from './cl-hole.js';
import { surfaceAdd } from './cl-surface.js';
import { traceAdd } from './cl-trace.js';
const { MODES } = api.const;
const { uc: UC } = api;
const { alerts, conf } = api;
const { current: LANG } = api.language;
let seed = Date.now();
function hasIndexing() {
return env.isIndexed;
}
function hasSpindle() {
return env.current.device.spindleMax > 0;
}
function zTop() {
return api.conf.get().process.camZTop > 0;
}
function zBottom() {
return api.conf.get().process.camZBottom > 0;
}
// create custom gcode editor function
function gcodeEditor(label, field) {
return function () {
opGCode(label, field);
}
}
function opGCode(label, field = 'gcode') {
api.dialog.show('any');
const { c_gcode } = h.bind(
$('mod-any'), h.div({ id: "camop_dialog" }, [
h.label(label || 'custom gcode operation'),
h.textarea({ id: "c_gcode", rows: 15, cols: 50 }),
h.button({
_: 'done', onclick: () => {
api.dialog.hide();
api.conf.save();
}
})
])
);
let av = env.poppedRec[field] || [];
c_gcode.value = typeof (av) === 'string' ? av : av.join('\n');
c_gcode.onkeyup = (el) => {
env.poppedRec[field] = c_gcode.value.trim().split('\n');
};
c_gcode.focus();
}
/**
* Create a new popup operation (popOp) for a given type.
* @param {string} type - the name of the popOp to create
* @param {object} map - an object mapping popOp keys to either a string
* (representing a key in current.process) or a value (to be used as the default).
* @returns {object} the newly created popOp.
*/
export function createPopOp(type, map) {
let op = env.popOp[type] = {
div: UC.newElement('div', { id: `${type}-op`, class: "cam-pop-op" }),
use: (rec) => {
op.rec = rec;
for (let [key, val] of Object.entries(op.inputs)) {
let type = val.type;
let from = map[key];
let rval = rec[key];
// fill undef entries older defines
if (type && (rval === null || rval === undefined)) {
if (typeof (from) === 'string') {
rec[key] = env.current.process[from];
} else if (from !== undefined) {
rec[key] = from;
} else {
console.log('error', { key, val, type, from });
}
}
}
api.util.rec2ui(rec, op.inputs);
op.hideshow();
},
using: (rec) => {
return op.rec === rec;
},
bind: (ev) => {
api.util.ui2rec(op.rec, op.inputs);
const settings = conf.get();
const { tool } = new Tool(settings, op.rec.tool); //get tool by id
const opType = op.rec.type
const drillOrRegister = opType == "drill" || opType == "register"
if (!drillOrRegister && tool.type == "drill") {
alerts.show(`Warning: Drills should not be used for ${opType} operations.`)
}
else if (drillOrRegister && tool.type != "drill") {
alerts.show(`Warning: Only drills should be used for drilling operations.`)
}
for (let [key, val] of Object.entries(op.rec)) {
let saveTo = map[key];
if (saveTo && typeof (key) === 'string' && !key.startsWith("~")) {
env.current.process[saveTo] = val;
}
}
api.conf.save();
op.hideshow();
},
new: () => {
let rec = { type };
for (let [key, src] of Object.entries(map)) {
rec[key] = typeof (src) === 'string'
? env.current.process[src.replace('~', '')]
: src;
}
return rec;
},
hideshow: () => {
for (let inp of Object.values(op.inputs)) {
let parent = inp.parentElement;
if (parent && parent.setVisible && parent.__opt.show) {
parent.setVisible(parent.__opt.show(op, api.conf.get()));
}
}
},
addNote: () => {
if (!op.note && type !== 'flip') {
const divid = `div-${++seed}`;
const noteid = `note-${++seed}`;
const div = document.createElement('div');
div.setAttribute('id', divid);
div.classList.add('pop-tics')
op.div.appendChild(div);
div.innerHTML = h.build(
h.div([h.label({ id: noteid })])
);
op.note = { divid, noteid };
}
if (op.note) {
const { divid, noteid } = op.note;
const div = $(divid);
if (div) div.onclick = () => {
api.uc.prompt('Edit Note for Operation', env.poppedRec.note || '').then(note => {
if (note !== undefined && note !== null) {
env.poppedRec.note = op.x = note;
api.conf.save();
}
opRender();
});
};
const note = $(noteid);
if (note) note.innerText = env.poppedRec.note || '';
}
},
group: [],
};
/**
* @function createRecordGetter
* @description Creates a getter function for the current record.
* The getter will bind the current record to the op before returning it.
* This is useful for passing a record to a pre-slice function
* @returns {function} A getter function for the current record.
*/
op.createRecordGetter = () => {
return () => {
op.bind();
return op.rec;
}
}
UC.restore({
addTo: op.div,
bindTo: op.bind,
lastDiv: op.div,
lastGroup: op.group
});
return op;
}
export function createPopOps() {
createPopOp('level', {
tool: 'camLevelTool',
spindle: 'camLevelSpindle',
step: 'camLevelOver',
rate: 'camLevelSpeed',
down: 'camLevelDown',
over: 'camLevelOver',
stock: 'camLevelStock'
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
down: UC.newInput(LANG.cc_loff_s, { title: LANG.cc_loff_l, convert: UC.toFloat, units: true }),
over: UC.newInput(LANG.cc_lxyo_s, { title: LANG.cc_lxyo_l, convert: UC.toFloat, units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
stock: UC.newBoolean(LANG.cc_lsto_s, undefined, { title: LANG.cc_lsto_l }),
};
createPopOp('rough', {
tool: 'camRoughTool',
spindle: 'camRoughSpindle',
down: 'camRoughDown',
step: 'camRoughOver',
rate: 'camRoughSpeed',
plunge: 'camRoughPlunge',
leave: 'camRoughStock',
leavez: 'camRoughStockZ',
all: 'camRoughAll',
voids: 'camRoughVoid',
flats: 'camRoughFlat',
inside: 'camRoughIn',
ov_topz: 0,
ov_botz: 0,
ov_conv: '~camConventional',
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
leave: UC.newInput(LANG.cr_lsto_s, { title: LANG.cr_lsto_l, convert: UC.toFloat, units: true }),
leavez: UC.newInput(LANG.cr_lstz_s, { title: LANG.cr_lstz_l, convert: UC.toFloat, bound: UC.bound(0, 10), units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
all: UC.newBoolean(LANG.cr_clst_s, undefined, { title: LANG.cr_clst_l, show: hasIndexing }),
voids: UC.newBoolean(LANG.cr_clrp_s, undefined, { title: LANG.cr_clrp_l }),
flats: UC.newBoolean(LANG.cr_clrf_s, undefined, { title: LANG.cr_clrf_l }),
inside: UC.newBoolean(LANG.cr_olin_s, undefined, { title: LANG.cr_olin_l }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("overrides"),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
};
createPopOp('outline', {
tool: 'camOutlineTool',
spindle: 'camOutlineSpindle',
step: 'camOutlineOver',
steps: 'camOutlineOverCount',
down: 'camOutlineDown',
rate: 'camOutlineSpeed',
plunge: 'camOutlinePlunge',
dogbones: 'camOutlineDogbone',
omitvoid: 'camOutlineOmitVoid',
omitthru: 'camOutlineOmitThru',
outside: 'camOutlineOut',
inside: 'camOutlineIn',
wide: 'camOutlineWide',
top: 'camOutlineTop',
ov_topz: 0,
ov_botz: 0,
ov_conv: '~camConventional',
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.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: UC.toInt, bound: UC.bound(1, 5), show: () => env.popOp.outline.rec.wide }),
sep: UC.newBlank({ class: "pop-sep" }),
top: UC.newBoolean(LANG.co_clrt_s, undefined, { title: LANG.co_clrt_l }),
inside: UC.newBoolean(LANG.co_olin_s, undefined, { title: LANG.co_olin_l, show: (op) => { return !op.inputs.outside.checked } }),
outside: UC.newBoolean(LANG.co_olot_s, undefined, { title: LANG.co_olot_l, show: (op) => { return !op.inputs.inside.checked } }),
sep: UC.newBlank({ class: "pop-sep" }),
omitthru: UC.newBoolean(LANG.co_omit_s, undefined, { title: LANG.co_omit_l, xshow: (op) => { return op.inputs.outside.checked } }),
omitvoid: UC.newBoolean(LANG.co_omvd_s, undefined, { title: LANG.co_omvd_l, xshow: (op) => { return op.inputs.outside.checked } }),
wide: UC.newBoolean(LANG.co_wide_s, undefined, { title: LANG.co_wide_l, show: (op) => { return !op.inputs.inside.checked } }),
dogbones: UC.newBoolean(LANG.co_dogb_s, undefined, { title: LANG.co_dogb_l, show: (op) => { return !op.inputs.wide.checked } }),
sep: UC.newBlank({ class: "pop-sep" }),
exp: UC.newExpand("overrides"),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, { title: LANG.ou_conv_l }),
exp_end: UC.endExpand(),
};
const contourFilter = gcodeEditor('Layer Filter', 'filter');
createPopOp('contour', {
tool: 'camContourTool',
spindle: 'camContourSpindle',
step: 'camContourOver',
rate: 'camContourSpeed',
angle: 'camContourAngle',
leave: 'camContourLeave',
tolerance: 'camTolerance',
flatness: 'camFlatness',
reduction: 'camContourReduce',
bridging: 'camContourBridge',
bottom: 'camContourBottom',
curves: 'camContourCurves',
inside: 'camContourIn',
filter: 'camContourFilter',
axis: 'X'
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 10.0) }),
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: UC.toFloat, bound: UC.bound(0, 100) }),
sep: UC.newBlank({ class: "pop-sep" }),
angle: UC.newInput(LANG.cf_angl_s, { title: LANG.cf_angl_l, convert: UC.toFloat, bound: UC.bound(45, 90), show: (op) => op.inputs.curves.checked }),
flatness: UC.newInput(LANG.ou_flat_s, { title: LANG.ou_flat_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), units: false, round: 4 }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4 }),
reduction: UC.newInput(LANG.ou_redu_s, { title: LANG.ou_redu_l, convert: UC.toInt, bound: UC.bound(0, 10), units: false }),
// bridging: UC.newInput(LANG.ou_brdg_s, {title:LANG.ou_brdg_l, convert:UC.toFloat, bound:UC.bound(0,1000.0), units:true, round:4, show:(op) => op.inputs.curves.checked}),
sep: UC.newBlank({ class: "pop-sep" }),
curves: UC.newBoolean(LANG.cf_curv_s, undefined, { title: LANG.cf_curv_l }),
inside: UC.newBoolean(LANG.cf_olin_s, undefined, { title: LANG.cf_olin_l }),
bottom: UC.newBoolean(LANG.cf_botm_s, undefined, { title: LANG.cf_botm_l, show: (op, conf) => conf ? conf.process.camZBottom : 0 }),
filter: UC.newRow([UC.newButton(LANG.filter, contourFilter)], { class: "ext-buttons f-row" })
};
createPopOp('lathe', {
tool: 'camLatheTool',
spindle: 'camLatheSpindle',
step: 'camLatheOver',
angle: 'camLatheAngle',
rate: 'camLatheSpeed',
tolerance: 'camTolerance',
filter: 'camContourFilter',
leave: 'camContourLeave',
linear: 'camLatheLinear'
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
// axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 100.0) }),
angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: UC.toFloat, bound: UC.bound(0.01, 180.0) }),
sep: UC.newBlank({ class: "pop-sep" }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4 }),
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: UC.toFloat, bound: UC.bound(0, 100) }),
sep: UC.newBlank({ class: "pop-sep" }),
linear: UC.newBoolean(LANG.ci_line_s, undefined, { title: LANG.ci_line_l }),
// filter: UC.newRow([ UC.newButton(LANG.filter, contourFilter) ], {class:"ext-buttons f-row"})
};
function canDogBones() {
if (!env.poppedRec) return false;
return env.poppedRec.mode === 'follow';// && env.poppedRec.offset && env.poppedRec.offset !== 'none';
}
function canDogBonesRev() {
return canDogBones() && env.poppedRec.dogbone;
}
function zDogSep() {
return canDogBones() || zBottom();
}
createPopOp('trace', {
mode: 'camTraceType',
offset: 'camTraceOffset',
spindle: 'camTraceSpindle',
tool: 'camTraceTool',
step: 'camTraceOver',
down: 'camTraceDown',
thru: 'camTraceThru',
rate: 'camTraceSpeed',
plunge: 'camTracePlunge',
offover: 'camTraceOffOver',
dogbone: 'camTraceDogbone',
revbone: 'camTraceDogbone',
merge: 'camTraceMerge',
ov_topz: 0,
ov_botz: 0,
ov_conv: '~camConventional',
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
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: () => (env.poppedRec.mode === 'follow') }, "traceoff"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.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: UC.toFloat, units: true }),
thru: UC.newInput(LANG.cc_thru_s, { title: LANG.cc_thru_l, convert: UC.toFloat, units: true }),
offover: UC.newInput(LANG.cc_offd_s, { title: LANG.cc_offd_l, convert: UC.toFloat, units: true, show: () => env.poppedRec.offset !== "none" || env.poppedRec.mode === "clear" }),
sep: UC.newBlank({ class: "pop-sep", modes: MODES.CAM, xshow: zDogSep }),
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"),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
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("select", traceAdd)], { class: "ext-buttons f-row" }),
};
createPopOp('pocket', {
spindle: 'camPocketSpindle',
tool: 'camPocketTool',
step: 'camPocketOver',
down: 'camPocketDown',
rate: 'camPocketSpeed',
plunge: 'camPocketPlunge',
expand: 'camPocketExpand',
smooth: 'camPocketSmooth',
refine: 'camPocketRefine',
follow: 'camPocketFollow',
contour: 'camPocketContour',
engrave: 'camPocketEngrave',
outline: 'camPocketOutline',
ov_topz: 0,
ov_botz: 0,
ov_conv: '~camConventional',
tolerance: 'camTolerance',
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
sep: UC.newBlank({ class: "pop-sep" }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.contour }),
sep: UC.newBlank({ class: "pop-sep" }),
expand: UC.newInput(LANG.cp_xpnd_s, { title: LANG.cp_xpnd_l, convert: UC.toFloat, units: true, xshow: () => !env.poppedRec.contour }),
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: UC.toInt, show: () => env.poppedRec.contour }),
smooth: UC.newInput(LANG.cp_smoo_s, { title: LANG.cp_smoo_l, convert: UC.toInt }),
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4, show: () => env.poppedRec.contour }),
follow: UC.newInput(LANG.cp_foll_s, { title: LANG.cp_foll_l, convert: UC.toFloat }),
sep: UC.newBlank({ class: "pop-sep" }),
contour: UC.newBoolean(LANG.cp_cont_s, undefined, { title: LANG.cp_cont_s }),
engrave: UC.newBoolean(LANG.cp_engr_s, undefined, { title: LANG.cp_engr_l, show: () => env.poppedRec.contour }),
outline: UC.newBoolean(LANG.cp_outl_s, undefined, { title: LANG.cp_outl_l }),
exp: UC.newExpand("overrides"),
sep: UC.newBlank({ class: "pop-sep" }),
ov_topz: UC.newInput(LANG.ou_ztop_s, { title: LANG.ou_ztop_l, convert: UC.toFloat, units: true }),
ov_botz: UC.newInput(LANG.ou_zbot_s, { title: LANG.ou_zbot_l, convert: UC.toFloat, units: true }),
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("select", surfaceAdd)], { class: "ext-buttons f-row" }),
};
createPopOp('drill', {
tool: 'camDrillTool',
spindle: 'camDrillSpindle',
down: 'camDrillDown',
rate: 'camDrillDownSpeed',
dwell: 'camDrillDwell',
lift: 'camDrillLift',
mark: 'camDrillMark',
precision: 'camDrillPrecision',
thru: 'camDrillThru',
fromTop: 'camDrillFromStockTop',
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
dwell: UC.newInput(LANG.cd_dwll_s, { title: LANG.cd_dwll_l, convert: UC.toFloat }),
lift: UC.newInput(LANG.cd_lift_s, { title: LANG.cd_lift_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.mark }),
mark: UC.newBoolean(LANG.cd_mark_s, undefined, { title: LANG.cd_mark_l, show: () => !env.poppedRec.fromTop }),
fromTop: UC.newBoolean(LANG.cd_ftop_s, undefined, { title: LANG.cd_ftop_l, show: () => !env.poppedRec.mark }),
sep: UC.newBlank({ class: "pop-sep" }),
thru: UC.newInput(LANG.cd_dtru_s, { title: LANG.cd_dtru_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.mark }),
precision: UC.newInput(LANG.cd_prcn_s, { title: LANG.cd_prcn_l, convert: UC.toFloat, units: true, show: () => !env.poppedRec.mark }),
sep: UC.newBlank({ class: "pop-sep", }),
actions: UC.newRow([
UC.newButton(LANG.select, () => selectHoles(true), { title: LANG.cd_seli_l }),
UC.newButton(LANG.cd_sela_s, () => selectHoles(false), { title: LANG.cd_sela_l })
], { class: "ext-buttons f-col" })
};
createPopOp('register', {
tool: 'camDrillTool',
spindle: 'camDrillSpindle',
down: 'camDrillDown',
rate: 'camDrillDownSpeed',
dwell: 'camDrillDwell',
lift: 'camDrillLift',
feed: 'camRegisterSpeed',
thru: 'camRegisterThru'
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
axis: UC.newSelect(LANG.cd_axis, {}, "regaxis"),
points: UC.newSelect(LANG.cd_points, { show: () => env.poppedRec.axis !== '-' }, "regpoints"),
sep: UC.newBlank({ class: "pop-sep" }),
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
rate: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
feed: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true, show: () => env.poppedRec.axis === '-' }),
sep: UC.newBlank({ class: "pop-sep" }),
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
dwell: UC.newInput(LANG.cd_dwll_s, { title: LANG.cd_dwll_l, convert: UC.toFloat, show: () => env.poppedRec.axis !== '-' }),
lift: UC.newInput(LANG.cd_lift_s, { title: LANG.cd_lift_l, convert: UC.toFloat, units: true, show: () => env.poppedRec.axis !== '-' }),
sep: UC.newBlank({ class: "pop-sep" }),
thru: UC.newInput(LANG.cd_thru_s, { title: LANG.cd_thru_l, convert: UC.toFloat, units: true }),
};
createPopOp('flip', {
axis: 'camFlipAxis',
invert: 'camFlipInvert'
}).inputs = {
axis: UC.newSelect(LANG.cd_axis, {}, "regaxis"),
sep: UC.newBlank({ class: "pop-sep", modes: MODES.CAM, show: zBottom }),
invert: UC.newBoolean(LANG.cf_nvrt_s, undefined, { title: LANG.cf_nvrt_l, show: zBottom }),
sep: UC.newBlank({ class: "pop-sep" }),
action: UC.newRow([UC.newButton(LANG.cf_menu, opFlip)], { class: "ext-buttons f-row" })
};
createPopOp('gcode', {
gcode: 'camCustomGcode',
}).inputs = {
action: UC.newRow([UC.newButton(LANG.edit, gcodeEditor())], { class: "ext-buttons f-row" })
};
function angleTowardZUp() {
api.event.emit('tool.mesh.face-up');
}
createPopOp('index', {
degrees: 'camIndexAxis',
absolute: 'camIndexAbs'
}).inputs = {
degrees: UC.newInput(LANG.ci_degr_s, { title: LANG.ci_degr_l, convert: UC.toFloat, bound: UC.bound(-360, 360.0) }),
absolute: UC.newBoolean(LANG.ci_abso_s, undefined, { title: LANG.ci_abso_l }),
select: UC.newRow([UC.newButton(LANG.ci_face_s, angleTowardZUp)], { class: "ext-buttons f-row" })
};
const editEnable = gcodeEditor('Laser Enable Script', 'enable');
const editOn = gcodeEditor('Laser On Script', 'on');
const editOff = gcodeEditor('Laser Off Script', 'off');
createPopOp('laser on', {
enable: 'camLaserEnable',
on: 'camLaserOn',
off: 'camLaserOff',
power: 'camLaserPower',
adapt: 'camLaserAdaptive',
adaptrp: 'camLaserAdaptMod',
flat: 'camLaserFlatten',
flatz: 'camLaserFlatZ',
minp: 'camLaserPowerMin',
maxp: 'camLaserPowerMax',
minz: 'camLaserZMin',
maxz: 'camLaserZMax',
}).inputs = {
enable: UC.newRow([UC.newButton(LANG.enable, editEnable)], { class: "ext-buttons f-row" }),
on: UC.newRow([UC.newButton(LANG.on, editOn)], { class: "ext-buttons f-row" }),
off: UC.newRow([UC.newButton(LANG.off, editOff)], { class: "ext-buttons f-row" }),
sep: UC.newBlank({ class: "pop-sep" }),
power: UC.newInput(LANG.cl_powr_s, { title: LANG.cl_powr_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), show: () => !env.poppedRec.adapt }),
maxp: UC.newInput(LANG.cl_maxp_s, { title: LANG.cl_maxp_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), show: () => env.poppedRec.adapt }),
minp: UC.newInput(LANG.cl_minp_s, { title: LANG.cl_minp_l, convert: UC.toFloat, bound: UC.bound(0, 1.0), show: () => env.poppedRec.adapt }),
maxz: UC.newInput(LANG.cl_maxz_s, { title: LANG.cl_maxz_l, convert: UC.toFloat, show: () => env.poppedRec.adapt }),
minz: UC.newInput(LANG.cl_minz_s, { title: LANG.cl_minz_l, convert: UC.toFloat, show: () => env.poppedRec.adapt }),
flatz: UC.newInput(LANG.cl_flaz_s, { title: LANG.cl_flaz_l, convert: UC.toFloat, show: () => env.poppedRec.flat }),
sep: UC.newBlank({ class: "pop-sep" }),
adapt: UC.newBoolean(LANG.cl_adap_s, undefined, { title: LANG.cl_adap_l }),
adaptrp: UC.newBoolean(LANG.cl_adrp_s, undefined, { title: LANG.cl_adrp_l, show: () => env.poppedRec.adapt }),
flat: UC.newBoolean(LANG.cl_flat_s, undefined, { title: LANG.cl_flat_l }),
};
const editDisable = gcodeEditor('Laser Disable Script', 'disable');
createPopOp('laser off', {
disable: 'camLaserDisable',
}).inputs = {
disable: UC.newRow([UC.newButton(LANG.disable, editDisable)], { class: "ext-buttons f-row" }),
};
createPopOp('|', {}).inputs = {};
}

View file

@ -0,0 +1,141 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from '../../kiri/api.js';
import { env } from './client.js';
import { space as SPACE } from '../../moto/space.js';
const { ui: UI } = api;
export function updateStock() {
if (env.isAnimate) {
if (env.isIndexed) {
SPACE.world.remove(env.camStock);
env.camStock = undefined;
}
return;
}
if (!env.isCamMode) {
SPACE.world.remove(env.camZTop);
SPACE.world.remove(env.camZBottom);
SPACE.world.remove(env.camStock);
env.camStock = null;
env.camZTop = null;
env.camZBottom = null;
return;
}
api.platform.update_bounds();
const settings = api.conf.get();
const widgets = api.widgets.all();
const { stock, process } = settings;
const { x, y, z, center } = stock;
UI.func.animate.classList.add('disabled');
if (env.camStock) {
SPACE.world.remove(env.camStock);
env.camStock = null;
}
if (x && y && z) {
UI.func.animate.classList.remove('disabled');
{
let geo = new THREE.BoxGeometry(1, 1, 1);
let mat = new THREE.MeshBasicMaterial({
color: 0x777777,
opacity: 0.05,
transparent: true,
side: THREE.DoubleSide
});
env.camStock = new THREE.Mesh(geo, mat);
env.camStock.renderOrder = 2;
let lo = 0.5;
let lidat = [
lo, lo, lo, lo, lo, -lo,
lo, lo, lo, lo, -lo, lo,
lo, lo, lo, -lo, lo, lo,
-lo, -lo, -lo, -lo, -lo, lo,
-lo, -lo, -lo, -lo, lo, -lo,
-lo, -lo, -lo, lo, -lo, -lo,
lo, lo, -lo, -lo, lo, -lo,
lo, lo, -lo, lo, -lo, -lo,
lo, -lo, -lo, lo, -lo, lo,
lo, -lo, lo, -lo, -lo, lo,
-lo, -lo, lo, -lo, lo, lo,
-lo, lo, lo, -lo, lo, -lo
];
let ligeo = new THREE.BufferGeometry();
ligeo.setAttribute('position', new THREE.BufferAttribute(lidat.toFloat32(), 3));
let limat = new THREE.LineBasicMaterial({ color: 0xaaaaaa });
let lines = new THREE.LineSegments(ligeo, limat);
env.camStock.lines = lines;
env.camStock.add(lines);
SPACE.world.add(env.camStock);
}
// fight z fighting in threejs
const { scale, position, lines } = env.camStock;
scale.x = x + 0.005;
scale.y = y + 0.005;
scale.z = z + 0.005;
position.x = center.x;
position.y = center.y;
position.z = center.z;
lines.material.color = new THREE.Color(env.isDark() ? 0x555555 : 0xaaaaaa);
}
SPACE.world.remove(env.camZTop);
if (process.camZTop && widgets.length) {
let max = { x, y, z };
for (let w of widgets) {
max.x = Math.max(max.x, w.track.box.w);
max.y = Math.max(max.y, w.track.box.h);
max.z = Math.max(max.z, w.track.box.d);
}
let geo = new THREE.PlaneGeometry(max.x, max.y);
let mat = new THREE.MeshBasicMaterial({
color: 0x777777,
opacity: 0.55,
transparent: true,
side: THREE.DoubleSide
});
const camZTop = env.camZTop = new THREE.Mesh(geo, mat);
camZTop._max = max;
camZTop.renderOrder = 1;
camZTop.position.x = center.x;
camZTop.position.y = center.y;
camZTop.position.z = process.camZTop;
SPACE.world.add(camZTop);
} else {
env.camZTop = undefined;
}
SPACE.world.remove(env.camZBottom);
if (process.camZBottom && widgets.length) {
let max = { x, y, z };
for (let w of widgets) {
max.x = Math.max(max.x, w.track.box.w);
max.y = Math.max(max.y, w.track.box.h);
max.z = Math.max(max.z, w.track.box.d);
}
let geo = new THREE.PlaneGeometry(max.x, max.y);
let mat = new THREE.MeshBasicMaterial({
color: 0x777777,
opacity: 0.55,
transparent: true,
side: THREE.DoubleSide
});
const camZBottom = env.camZBottom = new THREE.Mesh(geo, mat);
camZBottom._max = max;
camZBottom.renderOrder = 1;
camZBottom.position.x = center.x;
camZBottom.position.y = center.y;
camZBottom.position.z = process.camZBottom;
SPACE.world.add(camZBottom);
} else {
env.camZBottom = undefined;
}
SPACE.update();
}

View file

@ -0,0 +1,65 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from '../../kiri/api.js';
import { env } from './client.js';
import { CAM } from './driver-fe.js';
const DEG2RAD = Math.PI / 180;
const RAD2DEG = 180 / Math.PI;
export let surfaceOn = false;
let alert, lastWidget;
export function surfaceAdd(ev) {
if (surfaceOn) {
return surfaceDone();
}
env.clearPops();
alert = api.show.alert("analyzing surfaces...", 1000);
let surfaces = env.poppedRec.surfaces;
let radians = env.poppedRec.follow * DEG2RAD;
CAM.surface_prep(env.currentIndex * RAD2DEG, () => {
api.hide.alert(alert);
alert = api.show.alert("[esc] cancels surface selection");
for (let [wid, arr] of Object.entries(surfaces)) {
let widget = api.widgets.forid(wid);
if (widget && arr.length)
for (let faceid of arr) {
CAM.surface_toggle(widget, faceid, radians, faceids => {
// surfaces[widget.id] = faceids;
});
}
}
});
surfaceOn = env.hoveredOp;
surfaceOn.classList.add("editing");
api.feature.on_mouse_up = (obj, ev) => {
let { face } = obj;
let min = Math.min(face.a, face.b, face.c);
let faceid = min / 3;
let widget = lastWidget = obj.object.widget;
CAM.surface_toggle(widget, faceid, radians, faceids => {
surfaces[widget.id] = faceids;
});
};
}
export function surfaceDone() {
if (!(surfaceOn && env.poppedRec && env.poppedRec.surfaces)) {
return;
}
let surfaces = env.poppedRec.surfaces;
for (let wid of Object.keys(surfaces)) {
let widget = api.widgets.forid(wid);
if (widget) {
CAM.surface_clear(widget);
} else {
delete surfaces[wid];
}
}
api.hide.alert(alert);
api.feature.on_mouse_up = undefined;
surfaceOn.classList.remove("editing");
surfaceOn = false;
}

View file

View file

@ -0,0 +1,181 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from '../../kiri/api.js';
import { env } from './client.js';
import { CAM } from './driver-fe.js';
import { Layers } from '../../kiri/layers.js';
import { Stack } from '../../kiri/stack.js';
export let traceOn = false;
export let lastTrace;
export function setLastTrace(trace) {
lastTrace = trace;
}
export function traceAdd(ev) {
if (traceOn) {
return traceDone();
}
env.clearPops();
alert = api.show.alert("analyzing parts...", 1000);
traceOn = env.hoveredOp;
traceOn.classList.add("editing");
api.feature.hover = true;
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');
}
export function traceDone() {
if (!traceOn) {
return;
}
env.func.unpop();
traceOn.classList.remove("editing");
traceOn = false;
api.widgets.opacity(1);
api.hide.alert(alert);
api.feature.hover = false;
api.feature.hoverAdds = false;
api.widgets.for(widget => {
widget.restoreVisualState();
if (widget.trace_stack) {
widget.trace_stack.hide();
widget.adds.removeAll(widget.trace_stack.meshes);
}
});
}
export function traceHover(data) {
if (lastTrace) {
let { color, colorSave } = lastTrace.material[0] || lastTrace.material;
color.r = colorSave.r;
color.g = colorSave.g;
color.b = colorSave.b;
}
lastTrace = null;
if (data.type === 'platform') {
return;
}
if (!data.int.object.trace) {
return;
}
lastTrace = data.int.object;
if (lastTrace.selected) {
let event = data.event;
let target = event.target;
let { clientX, clientY } = event;
let { offsetWidth, offsetHeight } = target;
}
let material = lastTrace.material[0] || lastTrace.material;
let color = material.color;
material.colorSave = color.clone();
color.setHex(env.isDark() ? 0x0066ff : 0x0000ff);
}
export function traceHoverUp(int, ev) {
if (!int) return;
let { object } = int;
traceToggle(object);
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.onZ(avgZ)) {
traceToggle(add);
}
}
}
}
export function traceToggle(obj, skip) {
let material = obj.material[0] || obj.material;
if (!material) return;
let { color, colorSave } = material;
let { widget, poly } = obj.trace;
let areas = env.poppedRec.areas;
if (!areas) {
return;
}
let wlist = areas[widget.id] = areas[widget.id] || [];
obj.selected = !obj.selected;
if (!colorSave) {
colorSave = material.colorSave = color.clone();
}
if (obj.selected) {
color.setHex(env.isDark() ? 0xdd0011 : 0xff0033);
colorSave.r = color.r;
colorSave.g = color.g;
colorSave.b = color.b;
if (!skip) wlist.push(poly._trace);
} else {
color.setHex(0xaaaa55);
colorSave.setHex(0xaaaa55);
if (!skip) wlist.remove(poly._trace);
}
api.conf.save();
}
export function unselectTraces(widget, skip) {
if (widget.trace_stack) {
widget.trace_stack.meshes.forEach(mesh => {
if (mesh.selected) {
traceToggle(mesh, skip);
}
});
}
}

File diff suppressed because it is too large Load diff