diff --git a/src/kiri/app/api.js b/src/kiri/app/api.js index 360775cd..49b5f11e 100644 --- a/src/kiri/app/api.js +++ b/src/kiri/app/api.js @@ -22,7 +22,7 @@ import { local } from './local.js'; import { local as dataLocal } from '../../data/local.js'; import { modal } from './modal.js'; import { mode as modeModule, process as processModule } from './mode.js'; -import { newWidget } from '../core/widget.js'; +import { newWidget } from './widget.js'; import { noop, ajax, o2js, js2o, utils } from '../core/utils.js'; import { openFiles } from './files.js'; import { platform } from './platform.js'; diff --git a/src/kiri/app/frame.js b/src/kiri/app/frame.js index ee8ddc37..ca814ae3 100644 --- a/src/kiri/app/frame.js +++ b/src/kiri/app/frame.js @@ -25,7 +25,7 @@ import { api } from './api.js'; import { load } from '../../load/file.js'; -import { newWidget } from '../core/widget.js'; +import { newWidget } from './widget.js'; import { VIEWS } from '../core/consts.js'; import { widgets } from '../core/widgets.js'; diff --git a/src/kiri/app/groups.js b/src/kiri/app/groups.js index 92434c69..fd189742 100644 --- a/src/kiri/app/groups.js +++ b/src/kiri/app/groups.js @@ -1,7 +1,7 @@ /** Copyright Stewart Allen -- All Rights Reserved */ import { selection } from './selected.js'; -import { Widget } from '../core/widget.js'; +import { Widget } from './widget.js'; /** * Merge selected widgets into a group. diff --git a/src/kiri/app/image.js b/src/kiri/app/image.js index 2867436d..97fda375 100644 --- a/src/kiri/app/image.js +++ b/src/kiri/app/image.js @@ -2,7 +2,7 @@ import { $ } from '../../moto/webui.js'; import { api } from './api.js'; -import { newWidget } from '../core/widget.js'; +import { newWidget } from './widget.js'; import { platform } from './platform.js'; import { settings } from './conf/manager.js'; diff --git a/src/kiri/app/platform.js b/src/kiri/app/platform.js index 8523632c..3d8efc81 100644 --- a/src/kiri/app/platform.js +++ b/src/kiri/app/platform.js @@ -9,7 +9,7 @@ import { newBounds } from '../../geo/bounds.js'; import { Packer } from './pack.js'; import { space } from '../../moto/space.js'; import { THREE } from '../../ext/three.js'; -import { Widget, newWidget } from '../core/widget.js'; +import { Widget, newWidget } from './widget.js'; const V0 = new THREE.Vector3(0,0,0); diff --git a/src/kiri/app/widget.js b/src/kiri/app/widget.js new file mode 100644 index 00000000..b55dd548 --- /dev/null +++ b/src/kiri/app/widget.js @@ -0,0 +1,253 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +import { Widget as WidgetCore } from '../core/widget.js'; +import { util } from '../../geo/base.js'; +import { avgc } from '../core/utils.js'; + +const solid_opacity = 1.0; + +function catalog() { return self.kiri_catalog }; +function index() { return self.kiri_catalog.index } + +/** + * UI-enhanced Widget subclass for main thread + * Adds rendering, persistence, and visual state management + */ +class Widget extends WidgetCore { + /** + * Save widget geometry to catalog + */ + saveToCatalog(filename, overwrite) { + if (!filename) { + filename = this.meta.file; + } + if (this.grouped && !overwrite) { + return this; + } + const widget = this; + const mark = Date.now(); + const vertices = widget.getGeoVertices({ unroll: true }).slice(); + widget.meta.file = filename; + widget.meta.save = mark; + catalog().putFile(filename, vertices, () => { + console.log("saved mesh ["+(vertices.length/3)+"] time ["+(Date.now()-mark)+"]"); + }); + return this; + } + + /** + * Save widget state to index + */ + saveState(ondone) { + if (!ondone) { + clearTimeout(this._save_timer); + this._save_timer = setTimeout(() => { + this._save_timer = undefined; + this.saveState(() => {}); + }, 1500); + return; + } + const widget = this; + index().put('ws-save-'+this.id, { + geo: widget.getGeoVertices({ unroll: false }).slice(), + track: widget.track, + group: this.group.id, + meta: this.meta, + anno: this.annotations() + }, result => { + widget.meta.saved = Date.now(); + if (ondone) ondone(); + }); + } + + /** + * Set mesh material color + */ + setColor(color, settings, save = true) { + if (settings) { + console.trace('legacy call with settings'); + } + if (Array.isArray(color)) { + color = color[this.getExtruder() % color.length]; + } + if (save) { + this.color = color; + } + let material = this.getMaterial(); + material.color.set(this.meta.disabled ? avgc(0x888888, color, 3) : color); + } + + /** + * Get current visual state + */ + getVisualState() { + return { + edges: this.outline ? true : false, + wires: this.wire ? true : false, + opacity: this.getMaterial().opacity + }; + } + + /** + * Set visual state + */ + setVisualState({ edges, wires, opacity}) { + this.cache.vizstate = this.getVisualState(); + this.setEdges(edges ?? false); + this.setWireframe(wires ?? false); + this.setOpacity(opacity ?? 1); + } + + /** + * Refresh visual state + */ + refreshVisualState() { + this.setVisualState(this.getVisualState()); + } + + /** + * Restore previous visual state + */ + restoreVisualState() { + if (this.cache.vizstate) { + this.setVisualState(this.cache.vizstate); + } + } + + /** + * Set material opacity + */ + setOpacity(value) { + const mat = this.getMaterial(); + if (value <= 0.0) { + mat.transparent = solid_opacity < 1.0; + mat.opacity = solid_opacity; + mat.visible = false; + } else if (util.inRange(value, 0.0, solid_opacity)) { + mat.transparent = value < 1.0; + mat.opacity = value; + mat.visible = true; + } + } + + /** + * Set edge rendering + */ + setEdges(set) { + if (!(this.api && this.api.conf)) { + // missing api features in engine mode + return; + } + let mesh = this.mesh; + if (set && set.toggle) { + set = this.outline ? false : true; + } + if (this.outline) { + mesh.remove(this.outline); + this.outline = null; + + } + if (set) { + let dark = this.api.space.is_dark(); + let cam = this.api.mode.is_cam(); + let color = dark ? 0x444444 : 0x888888; + let angle = this.api.conf.get().controller.edgeangle || 20; + let edges = new THREE.EdgesGeometry(mesh.geometry, angle); + let material = new THREE.LineBasicMaterial({ color }); + this.outline = new THREE.LineSegments(edges, material); + this.outline.renderOrder = -20; + mesh.add(this.outline); + } + } + + /** + * Set wireframe rendering + */ + setWireframe(set, color, opacity) { + if (!(this.api && this.api.conf)) { + // missing api features in engine mode + return; + } + let mesh = this.mesh, + widget = this; + if (this.wire) { + this.setOpacity(solid_opacity); + mesh.remove(this.wire); + this.wire = null; + } + if (set) { + let dark = this.api.space.is_dark(); + let mat = new THREE.MeshBasicMaterial({ + wireframe: true, + color: dark ? 0xaaaaaa : 0, + opacity: 0.5, + transparent: true + }) + let wire = widget.wire = new THREE.Mesh(mesh.geometry.shallowClone(), mat); + mesh.add(wire); + } + if (this.api.view.is_arrange()) { + this.setColor(this.color); + } else { + this.setColor(0x888888,undefined,false); + } + if (opacity !== undefined) { + widget.setOpacity(opacity); + } + } + + /** + * Load widget from catalog + */ + static loadFromCatalog(filename, ondone) { + catalog().getFile(filename, function(data) { + let widget = new Widget().loadVertices(data); + widget.meta.file = filename; + ondone(widget); + }); + } + + /** + * Load widget from saved state + */ + static loadFromState(id, ondone, move) { + const Group = Widget.Groups; + index().get('ws-save-'+id, function(data) { + if (data) { + let vertices = data.geo || data, + track = data.track || undefined, + group = data.group || id, + anno = data.anno || undefined, + widget = new Widget(id, Group.forid(group)), + meta = data.meta || widget.meta, + ptr = widget.loadVertices(vertices); + widget.meta = meta; + widget.anno = anno || widget.anno; + // restore widget position if specified + if (move && track && track.pos) { + widget.track = track; + widget.move(track.pos.x, track.pos.y, track.pos.z, true); + } + ondone(ptr); + } else { + ondone(null); + } + }); + } + + /** + * Delete widget from saved state + */ + static deleteFromState(id,ondone) { + index().remove('ws-save-'+id, ondone); + } +} + +/** + * Factory function to create UI-enhanced widgets + */ +function newWidget(id, group) { + return new Widget(id, group); +} + +export { Widget, newWidget }; diff --git a/src/kiri/app/workers.js b/src/kiri/app/workers.js index e0a9871d..c508d10e 100644 --- a/src/kiri/app/workers.js +++ b/src/kiri/app/workers.js @@ -3,7 +3,7 @@ import { api } from './api.js'; import { noop } from '../core/utils.js'; import { codec } from '../core/codec.js'; -import { Widget } from '../core/widget.js'; +import { Widget } from './widget.js'; import { newPrint } from '../core/print.js'; // this code runs in kiri's main loop diff --git a/src/kiri/app/workspace.js b/src/kiri/app/workspace.js index fd0f8806..92dd3d4b 100644 --- a/src/kiri/app/workspace.js +++ b/src/kiri/app/workspace.js @@ -9,7 +9,7 @@ import { selection } from './selected.js'; import { settings } from './conf/manager.js'; import { space as SPACE } from '../../moto/space.js'; import { utils } from '../core/utils.js'; -import { Widget } from '../core/widget.js'; +import { Widget } from './widget.js'; import { Index } from '../../data/index.js'; const { o2js, js2o, ls2o, parseOpt } = utils; diff --git a/src/kiri/core/widget.js b/src/kiri/core/widget.js index 5206e58f..a1035977 100644 --- a/src/kiri/core/widget.js +++ b/src/kiri/core/widget.js @@ -1,7 +1,5 @@ /** Copyright Stewart Allen -- All Rights Reserved */ -import { base } from '../../geo/base.js'; -import { avgc } from './utils.js'; import { util as mesh_util } from '../../mesh/util.js'; import { tool as mesh_tool } from '../../mesh/tool.js'; import { verticesToPoints } from '../../geo/points.js'; @@ -10,21 +8,13 @@ import { newPolygon } from '../../geo/polygon.js'; import { polygons as POLY } from '../../geo/polygons.js'; import { checkOverUnderOn, intersectPoints } from '../../geo/slicer.js'; -const { inRange, time } = base.util; +const sharedArrayClass = self.SharedArrayBuffer || undefined; +const hasSharedArrays = sharedArrayClass ? true : false; const solid_opacity = 1.0; const groups = []; -const sharedArrayClass = self.SharedArrayBuffer || undefined; -const hasSharedArrays = sharedArrayClass ? true : false; - let nextId = 0; -function newWidget(id,group) { return new Widget(id,group) } - -// todo: fix ui-only path thus this unorthodox binding vs importing api -function catalog() { return self.kiri_catalog }; -function index() { return self.kiri_catalog.index } - class Widget { constructor(id, group) { this.api = self.kiri_api; @@ -98,46 +88,6 @@ class Widget { this.support = false; } - saveToCatalog(filename, overwrite) { - if (!filename) { - filename = this.meta.file; - } - if (this.grouped && !overwrite) { - return this; - } - const widget = this; - const mark = time(); - const vertices = widget.getGeoVertices({ unroll: true }).slice(); - widget.meta.file = filename; - widget.meta.save = mark; - catalog().putFile(filename, vertices, () => { - console.log("saved mesh ["+(vertices.length/3)+"] time ["+(time()-mark)+"]"); - }); - return this; - } - - saveState(ondone) { - if (!ondone) { - clearTimeout(this._save_timer); - this._save_timer = setTimeout(() => { - this._save_timer = undefined; - this.saveState(() => {}); - }, 1500); - return; - } - const widget = this; - index().put('ws-save-'+this.id, { - geo: widget.getGeoVertices({ unroll: false }).slice(), - track: widget.track, - group: this.group.id, - meta: this.meta, - anno: this.annotations() - }, result => { - widget.meta.saved = time(); - if (ondone) ondone(); - }); - } - annotations() { let o = Object.clone(this.anno); if (o.support) { @@ -311,23 +261,6 @@ class Widget { this.slices = null; } - /** - * @param {number} color - */ - setColor(color, settings, save = true) { - if (settings) { - console.trace('legacy call with settings'); - } - if (Array.isArray(color)) { - color = color[this.getExtruder() % color.length]; - } - if (save) { - this.color = color; - } - let material = this.getMaterial(); - material.color.set(this.meta.disabled ? avgc(0x888888, color, 3) : color); - } - getColor() { return this.color; } @@ -362,47 +295,6 @@ class Widget { } } - getVisualState() { - return { - edges: this.outline ? true : false, - wires: this.wire ? true : false, - opacity: this.getMaterial().opacity - }; - } - - setVisualState({ edges, wires, opacity}) { - this.cache.vizstate = this.getVisualState(); - this.setEdges(edges ?? false); - this.setWireframe(wires ?? false); - this.setOpacity(opacity ?? 1); - } - - refreshVisualState() { - this.setVisualState(this.getVisualState()); - } - - restoreVisualState() { - if (this.cache.vizstate) { - this.setVisualState(this.cache.vizstate); - } - } - - /** - * @param {number} value - */ - setOpacity(value) { - const mesh = this.mesh; - const mat = this.getMaterial(); - if (value <= 0.0) { - mat.transparent = solid_opacity < 1.0; - mat.opacity = solid_opacity; - mat.visible = false; - } else if (inRange(value, 0.0, solid_opacity)) { - mat.transparent = value < 1.0; - mat.opacity = value; - mat.visible = true; - } - } toggleVisibility(bool) { const mat = this.getMaterial(); @@ -574,10 +466,10 @@ class Widget { let mesh = this.mesh, scale = this.track.scale; this.bounds = null; - this.setWireframe(false); + if (this.setWireframe) this.setWireframe(false); this.clearSlices(); mesh.geometry.applyMatrix4(new THREE.Matrix4().makeScale(x, y, z)); - if (this.outline) { + if (this.outline && this.setEdges) { this.setEdges(true); } scale.x *= (x || 1.0); @@ -593,7 +485,7 @@ class Widget { if (center) { this.center(false); } - if (this.outline) { + if (this.outline && this.setEdges) { this.setEdges(true); } if ((x || y || z) && this.api && this.api.event) { @@ -604,7 +496,7 @@ class Widget { _rotate(x, y, z, temp) { if (!temp) { this.bounds = null; - this.setWireframe(false); + if (this.setWireframe) this.setWireframe(false); this.clearSlices(); } let m4 = new THREE.Matrix4(); @@ -633,7 +525,7 @@ class Widget { this.roto = []; this.center(); this.setModified(); - this.refreshVisualState(); + if (this.refreshVisualState) this.refreshVisualState(); } mirror() { @@ -648,7 +540,7 @@ class Widget { _mirror() { this.clearSlices(); - this.setWireframe(false); + if (this.setWireframe) this.setWireframe(false); let geo = this.mesh.geometry, ot = this.track; let pos = geo.attributes.position; let arr = pos.array; @@ -782,65 +674,6 @@ class Widget { return Date.now() - mark; } - setEdges(set) { - if (!(this.api && this.api.conf)) { - // missing api features in engine mode - return; - } - let mesh = this.mesh; - if (set && set.toggle) { - set = this.outline ? false : true; - } - if (this.outline) { - mesh.remove(this.outline); - this.outline = null; - - } - if (set) { - let dark = this.api.space.is_dark(); - let cam = this.api.mode.is_cam(); - let color = dark ? 0x444444 : 0x888888; - let angle = this.api.conf.get().controller.edgeangle || 20; - let edges = new THREE.EdgesGeometry(mesh.geometry, angle); - let material = new THREE.LineBasicMaterial({ color }); - this.outline = new THREE.LineSegments(edges, material); - this.outline.renderOrder = -20; - mesh.add(this.outline); - } - } - - setWireframe(set, color, opacity) { - if (!(this.api && this.api.conf)) { - // missing api features in engine mode - return; - } - let mesh = this.mesh, - widget = this; - if (this.wire) { - this.setOpacity(solid_opacity); - mesh.remove(this.wire); - this.wire = null; - } - if (set) { - let dark = this.api.space.is_dark(); - let mat = new THREE.MeshBasicMaterial({ - wireframe: true, - color: dark ? 0xaaaaaa : 0, - opacity: 0.5, - transparent: true - }) - let wire = widget.wire = new THREE.Mesh(mesh.geometry.shallowClone(), mat); - mesh.add(wire); - } - if (this.api.view.is_arrange()) { - this.setColor(this.color); - } else { - this.setColor(0x888888,undefined,false); - } - if (opacity !== undefined) { - widget.setOpacity(opacity); - } - } show() { this.mesh.visible = true; @@ -1076,40 +909,8 @@ const Group = Widget.Groups = { } }; -Widget.loadFromCatalog = function(filename, ondone) { - catalog().getFile(filename, function(data) { - let widget = newWidget().loadVertices(data); - widget.meta.file = filename; - ondone(widget); - }); -}; - -Widget.loadFromState = function(id, ondone, move) { - index().get('ws-save-'+id, function(data) { - if (data) { - let vertices = data.geo || data, - track = data.track || undefined, - group = data.group || id, - anno = data.anno || undefined, - widget = newWidget(id, Group.forid(group)), - meta = data.meta || widget.meta, - ptr = widget.loadVertices(vertices); - widget.meta = meta; - widget.anno = anno || widget.anno; - // restore widget position if specified - if (move && track && track.pos) { - widget.track = track; - widget.move(track.pos.x, track.pos.y, track.pos.z, true); - } - ondone(ptr); - } else { - ondone(null); - } - }); -}; - -Widget.deleteFromState = function(id,ondone) { - index().remove('ws-save-'+id, ondone); -}; +function newWidget(id,group) { + return new Widget(id,group); +} export { Widget, newWidget }; diff --git a/src/kiri/core/widgets.js b/src/kiri/core/widgets.js index 5db6f325..39d621ee 100644 --- a/src/kiri/core/widgets.js +++ b/src/kiri/core/widgets.js @@ -3,7 +3,7 @@ import { $ } from '../../moto/webui.js'; import { api } from '../app/api.js'; import { load } from '../../load/file.js'; -import { Widget, newWidget } from './widget.js'; +import { Widget, newWidget } from '../app/widget.js'; let WIDGETS = []; diff --git a/src/kiri/mode/cam/init-ui.js b/src/kiri/mode/cam/init-ui.js index 55fdd744..706cb456 100644 --- a/src/kiri/mode/cam/init-ui.js +++ b/src/kiri/mode/cam/init-ui.js @@ -16,7 +16,7 @@ import { holeSelOn, selectHolesDone, clearHolesRec } from './cl-hole.js'; import { surfaceOn, surfaceDone } from './cl-surface.js'; import { helicalOn, helicalDone } from './cl-helical.js'; import { originSelectDone } from './cl-origin.js'; -import { Widget, newWidget } from '../../core/widget.js'; +import { Widget, newWidget } from '../../app/widget.js'; import { space } from '../../../moto/space.js'; import { recreateTabs } from './cl-tab.js'; diff --git a/src/kiri/mode/fdm/init-ui.js b/src/kiri/mode/fdm/init-ui.js index d0da4218..f7e4d0ff 100644 --- a/src/kiri/mode/fdm/init-ui.js +++ b/src/kiri/mode/fdm/init-ui.js @@ -8,7 +8,7 @@ import { client } from '../../app/workers.js'; import { addbox, delbox, getlastbox } from '../../core/boxes.js'; import { Layers } from '../../app/layers.js'; import { Stack } from '../../app/stack.js'; -import { Widget, newWidget } from '../../core/widget.js'; +import { Widget, newWidget } from '../../app/widget.js'; import { space } from '../../../moto/space.js'; const { Matrix4, Vector3, BufferAttribute, BufferGeometryUtils, Raycaster, Euler } = THREE; diff --git a/src/kiri/run/engine.js b/src/kiri/run/engine.js index eb5aa5a5..deb29d57 100644 --- a/src/kiri/run/engine.js +++ b/src/kiri/run/engine.js @@ -7,7 +7,7 @@ import { api } from '../app/api.js'; import { conf } from '../app/conf/defaults.js'; import { client } from '../app/workers.js'; import { load } from '../../load/file.js'; -import { newWidget } from '../core/widget.js'; +import { newWidget } from '../app/widget.js'; class Engine { constructor({ workURL, poolURL } = {}) {