add codec factory option, improve geo vertex caching, fix surface find in indexed mode

This commit is contained in:
Stewart Allen 2025-12-28 19:55:44 -05:00
commit d73f72586a
9 changed files with 71 additions and 23 deletions

View file

@ -518,6 +518,7 @@ export const conf = {
camLatheAngle: 1, camLatheAngle: 1,
camLatheLinear: true, camLatheLinear: true,
camLatheOver: 0.1, camLatheOver: 0.1,
camLatheDown: 0,
camLatheSpeed: 500, camLatheSpeed: 500,
camLatheSpindle: 1000, camLatheSpindle: 1000,
camLatheTool: 1000, camLatheTool: 1000,

View file

@ -7,6 +7,7 @@ import { codec } from '../../core/codec.js';
import { consts } from '../../core/consts.js'; import { consts } from '../../core/consts.js';
import { space } from '../../../moto/space.js'; import { space } from '../../../moto/space.js';
import { local } from '../../../data/local.js'; import { local } from '../../../data/local.js';
import { newWidget } from '../widget.js';
import { utils } from '../../core/utils.js'; import { utils } from '../../core/utils.js';
import { version } from '../../../moto/license.js'; import { version } from '../../../moto/license.js';
import { JSZip } from '../../../ext/jszip-esm.js'; import { JSZip } from '../../../ext/jszip-esm.js';
@ -608,6 +609,7 @@ function settingsImport(data, ask) {
work.type = 100; work.type = 100;
} }
} }
codec.setFactory(codec.TYPE.WIDGET, newWidget);
codec.decode(data.work).forEach(widget => { codec.decode(data.work).forEach(widget => {
api.platform.add(widget, 0, true, true); api.platform.add(widget, 0, true, true);
}); });

View file

@ -7,6 +7,7 @@ import { Slice, newSlice, Top, newTop } from './slice.js';
import { Widget, newWidget } from './widget.js'; import { Widget, newWidget } from './widget.js';
const decoders = {}; const decoders = {};
const factories = {};
const freeMem = true; const freeMem = true;
const TYPE = { const TYPE = {
@ -213,6 +214,11 @@ function decodePointArray2D(array, z, fn) {
return points; return points;
} }
function setFactory(type, factory) {
factories[type] = factory;
return codec;
}
// ----- Widget Codec ----- // ----- Widget Codec -----
Widget.prototype.encode = function(state) { Widget.prototype.encode = function(state) {
@ -236,7 +242,8 @@ registerDecoder(TYPE.WIDGET, function(v, state) {
const id = v.id, const id = v.id,
group = v.group || id, group = v.group || id,
track = v.track || undefined, track = v.track || undefined,
widget = newWidget(id, Widget.Groups.forid(group)), factory = factories[TYPE.WIDGET] ?? newWidget,
widget = factory(id, Widget.Groups.forid(group)),
meta= v.meta || widget.meta, meta= v.meta || widget.meta,
anno = v.anno || widget.anno; anno = v.anno || widget.anno;
@ -480,6 +487,7 @@ registerDecoder(TYPE.LAYERS, function(v, state) {
}); });
export const codec = { export const codec = {
TYPE,
encode, encode,
decode, decode,
registerDecoder, registerDecoder,
@ -488,10 +496,12 @@ export const codec = {
decodePointArray, decodePointArray,
encodePointArray2D, encodePointArray2D,
decodePointArray2D, decodePointArray2D,
setFactory,
toCodable toCodable
}; };
export { export {
TYPE,
encode, encode,
decode, decode,
registerDecoder, registerDecoder,
@ -500,6 +510,7 @@ export {
decodePointArray, decodePointArray,
encodePointArray2D, encodePointArray2D,
decodePointArray2D, decodePointArray2D,
setFactory,
toCodable toCodable
}; };

View file

@ -181,9 +181,13 @@ class Widget {
return this.loadVertices(...arguments); return this.loadVertices(...arguments);
} }
setModified() { setModified(reason) {
this.modified = true; this.modified = true;
this.boundingBoxNeedsUpdate = true; this.boundingBoxNeedsUpdate = true;
this.clearShadows();
if (reason !== 'axis') {
this.cache.geo = undefined;
}
if (this.mesh && this.mesh.geometry) { if (this.mesh && this.mesh.geometry) {
// this fixes ray intersections after the mesh is modified // this fixes ray intersections after the mesh is modified
this.mesh.geometry.boundingSphere = null; this.mesh.geometry.boundingSphere = null;
@ -355,7 +359,7 @@ class Widget {
// invalidate cached points // invalidate cached points
if (x || y || z) { if (x || y || z) {
this.points = null; this.points = null;
this.setModified(); this.setModified('moveMesh');
} }
} }
@ -372,11 +376,11 @@ class Widget {
} }
setAxisIndex(deg) { setAxisIndex(deg) {
// console.trace(deg);
let rad = deg * (Math.PI / 180); let rad = deg * (Math.PI / 180);
if (rad !== this.track.indexRad) { if (rad !== this.track.indexRad) {
this.track.indexRad = rad; this.track.indexRad = rad;
this.clearShadows(); this.setModified('axis');
this.setModified();
this._updateMeshPosition(); this._updateMeshPosition();
} }
} }
@ -429,7 +433,7 @@ class Widget {
pos.z += (z || 0); pos.z += (z || 0);
} }
if (x || y || z) { if (x || y || z) {
this.setModified(); this.setModified('_move');
this._updateMeshPosition(); this._updateMeshPosition();
} }
} }
@ -475,7 +479,7 @@ class Widget {
scale.x *= (x || 1.0); scale.x *= (x || 1.0);
scale.y *= (y || 1.0); scale.y *= (y || 1.0);
scale.z *= (z || 1.0); scale.z *= (z || 1.0);
this.setModified(); this.setModified('_scale');
} }
rotate(x, y, z, temp, center = true) { rotate(x, y, z, temp, center = true) {
@ -514,7 +518,7 @@ class Widget {
rot.y += (y || 0); rot.y += (y || 0);
rot.z += (z || 0); rot.z += (z || 0);
} }
this.setModified(); this.setModified('_rotate');
} }
// undo all accumulated rotations // undo all accumulated rotations
@ -524,7 +528,7 @@ class Widget {
}); });
this.roto = []; this.roto = [];
this.center(); this.center();
this.setModified(); this.setModified('unrotate');
if (this.refreshVisualState) this.refreshVisualState(); if (this.refreshVisualState) this.refreshVisualState();
} }
@ -563,7 +567,7 @@ class Widget {
} }
pos.needsUpdate = true; pos.needsUpdate = true;
ot.mirror = !ot.mirror; ot.mirror = !ot.mirror;
this.setModified(); this.setModified('_mirror');
this.points = null; this.points = null;
} }
@ -586,6 +590,17 @@ class Widget {
getGeoVertices(opt = {}) { getGeoVertices(opt = {}) {
const { unroll, translate } = opt; const { unroll, translate } = opt;
let cacheKey = [
unroll ? 1 : 0,
translate ? 1 : 0,
((this.track.indexRad ?? 0) * 100000) | 0
].join(',');
let marked = Date.now();
let geoCache = this.cache.geo = (this.cache.geo || {});
let cached = geoCache[cacheKey];
if (cached) {
return cached.pos;
}
let geo = this.mesh.geometry; let geo = this.mesh.geometry;
let pos = geo.getAttribute('position'); let pos = geo.getAttribute('position');
// if indexed, return points rotated about X and then offset // if indexed, return points rotated about X and then offset
@ -595,6 +610,7 @@ class Widget {
.applyMatrix4( new THREE.Matrix4().makeRotationX(-track.indexRad) ); .applyMatrix4( new THREE.Matrix4().makeRotationX(-track.indexRad) );
} }
pos = pos.array; pos = pos.array;
// unroll indexed geometry
if (geo.index && unroll !== false) { if (geo.index && unroll !== false) {
let idx = geo.index.array; let idx = geo.index.array;
let len = idx.length; let len = idx.length;
@ -608,12 +624,8 @@ class Widget {
pp2[inc++] = pos[ip]; pp2[inc++] = pos[ip];
} }
pos = pp2; pos = pp2;
} else if (translate) {
pos = pos.slice();
} }
// used by CAM.shadowAt() geoCache[cacheKey] = { pos, marked };
this.cache.geo = pos;
delete this.cache.shadow;
return pos; return pos;
} }
@ -631,7 +643,7 @@ class Widget {
if (!this.bounds || refresh || this.boundingBoxNeedsUpdate) { if (!this.bounds || refresh || this.boundingBoxNeedsUpdate) {
this.bounds = new THREE.Box3().setFromArray(this.getGeoVertices({ this.bounds = new THREE.Box3().setFromArray(this.getGeoVertices({
translate: true, translate: true,
unroll: false unroll: true
})); }));
this.boundingBoxNeedsUpdate = false; this.boundingBoxNeedsUpdate = false;
} }
@ -701,6 +713,7 @@ class Widget {
// find closest shadow above and use to speed up delta shadow gen // find closest shadow above and use to speed up delta shadow gen
let zover = Object.keys(shadows).map(v => parseFloat(v)).filter(v => v > z); let zover = Object.keys(shadows).map(v => parseFloat(v)).filter(v => v > z);
let minZabove = Math.min(Infinity, ...zover); let minZabove = Math.min(Infinity, ...zover);
// shift shadow probeline down a fraction to capture z flats with FP noise
let shadow = this.#computeShadowAt(z - 0.005, minZabove); let shadow = this.#computeShadowAt(z - 0.005, minZabove);
if (minZabove < Infinity) { if (minZabove < Infinity) {
shadow = POLY.union([...shadow, ...shadows[minZabove]], 0, true, { wasm: false }); shadow = POLY.union([...shadow, ...shadows[minZabove]], 0, true, { wasm: false });
@ -725,7 +738,7 @@ class Widget {
if (this.cache.shadow) { if (this.cache.shadow) {
return; return;
} }
const geo = this.cache.geo; const geo = this.getGeoVertices({ unroll: true, translate: true });
const length = geo.length; const length = geo.length;
const bounds = this.getBoundingBox(); const bounds = this.getBoundingBox();
const stack = {}; const stack = {};

View file

@ -350,8 +350,9 @@ export function createPopOps() {
createPopOp('lathe', { createPopOp('lathe', {
tool: 'camLatheTool', tool: 'camLatheTool',
spindle: 'camLatheSpindle', spindle: 'camLatheSpindle',
step: 'camLatheOver',
angle: 'camLatheAngle', angle: 'camLatheAngle',
step: 'camLatheOver',
down: 'camLatheDown',
rate: 'camLatheSpeed', rate: 'camLatheSpeed',
tolerance: 'camTolerance', tolerance: 'camTolerance',
filter: 'camContourFilter', filter: 'camContourFilter',
@ -365,8 +366,9 @@ export function createPopOps() {
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 }),
sep: UC.newBlank({ class: "pop-sep" }), 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, 100.0) }),
angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: toFloat, bound: UC.bound(0.01, 180.0) }), angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: toFloat, bound: UC.bound(0.01, 180.0) }),
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.01, 100.0) }),
// down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, units }),
sep: UC.newBlank({ class: "pop-sep" }), sep: UC.newBlank({ class: "pop-sep" }),
offStart: UC.newInput(LANG.ci_laso_s, { title: LANG.ci_laso_l, convert: toFloat}), offStart: UC.newInput(LANG.ci_laso_s, { title: LANG.ci_laso_l, convert: toFloat}),
offEnd: UC.newInput(LANG.ci_laeo_s, { title: LANG.ci_laeo_l, convert: toFloat}), offEnd: UC.newInput(LANG.ci_laeo_s, { title: LANG.ci_laeo_l, convert: toFloat}),

View file

@ -18,9 +18,9 @@ function surface_prep(widget, index) {
} }
}; };
function surface_find(widget, faces, radians) { function surface_find(widget, faces, radians, filterZ) {
surface_prep(widget); surface_prep(widget);
return widget.tool.findConnectedSurface(faces, radians || 0, 0.0); return widget.tool.findConnectedSurface(faces, radians, filterZ);
}; };
function init(worker) { function init(worker) {

View file

@ -114,6 +114,10 @@ export function menu() {
camStockOffset: newBoolean(LANG.cs_offs_s, onBooleanClick, {title:LANG.cs_offs_l}), camStockOffset: newBoolean(LANG.cs_offs_s, onBooleanClick, {title:LANG.cs_offs_l}),
camStockIndexed: newBoolean(LANG.cs_indx_s, onBooleanClick, {title:LANG.cs_indx_l}), camStockIndexed: newBoolean(LANG.cs_indx_s, onBooleanClick, {title:LANG.cs_indx_l}),
camStockIndexGrid: newBoolean(LANG.cs_ishg_s, onBooleanClick, {title:LANG.cs_ishg_l, show:() => ui.camStockIndexed.checked}), camStockIndexGrid: newBoolean(LANG.cs_ishg_s, onBooleanClick, {title:LANG.cs_ishg_l, show:() => ui.camStockIndexed.checked}),
// separator: newBlank({ class:"set-sep", driven }),
// camStockManual: newRow([
// (ui.stockPlace = newButton('position', onButtonClick, { })),
// ], {class:"ext-buttons f-row"}),
_____: newGroup(LANG.cc_menu, $('cam-limits'), { modes:CAM, driven, separator }), _____: newGroup(LANG.cc_menu, $('cam-limits'), { modes:CAM, driven, separator }),
camZAnchor: newSelect(LANG.ou_zanc_s, {title: LANG.ou_zanc_l, action:zAnchorSave, show:() => !ui.camStockIndexed.checked}, "zanchor"), camZAnchor: newSelect(LANG.ou_zanc_s, {title: LANG.ou_zanc_l, action:zAnchorSave, show:() => !ui.camStockIndexed.checked}, "zanchor"),
camZOffset: newInput(LANG.ou_ztof_s, {title:LANG.ou_ztof_l, convert:toFloat, units}), camZOffset: newInput(LANG.ou_ztof_s, {title:LANG.ou_ztof_l, convert:toFloat, units}),

View file

@ -792,8 +792,24 @@ export function init() {
// TAB/TRACE BUTTON HANDLERS // TAB/TRACE BUTTON HANDLERS
api.event.on("button.click", target => { api.event.on("button.click", target => {
let process = api.conf.get().process; let { process, stock } = api.conf.get();
switch (target) { switch (target) {
case api.ui.stockPlace:
api.uc.prompt("stock x,y,z position", "").then(csv => {
if (csv) {
let [x,y,z] = csv.split(',').map(v => parseInt(v) || 0);
let center = {
x: x ?? 0,
y: y ?? 0,
z: z ?? 0
}
// move widgets based on postion
// more complex than just moving stock
// because stock is based on contents, not the other way
console.log({ x,y,z });
}
});
return;
case api.ui.tabAdd: case api.ui.tabAdd:
return tabAdd(); return tabAdd();
case api.ui.tabDun: case api.ui.tabDun:

View file

@ -241,7 +241,6 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
} }
async function computeShadows() { async function computeShadows() {
// console.log('(re)compute shadows');
await new OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => { await new OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => {
// console.log('reshadowing', progress.round(3)); // console.log('reshadowing', progress.round(3));
}); });