@selvajs/visualization 1.6.0-beta.3 → 1.6.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gpu-dispose-Cestx__L.cjs +2 -0
- package/dist/gpu-dispose-Cestx__L.cjs.map +1 -0
- package/dist/gpu-dispose-gTGxGTL4.js +2 -0
- package/dist/gpu-dispose-gTGxGTL4.js.map +1 -0
- package/dist/gpu-ownership-DcHWbATk.js +2 -0
- package/dist/gpu-ownership-DcHWbATk.js.map +1 -0
- package/dist/gpu-ownership-ilj3JEdn.cjs +2 -0
- package/dist/gpu-ownership-ilj3JEdn.cjs.map +1 -0
- package/dist/merged-draw-CO5Ev9ia.js +2 -0
- package/dist/merged-draw-CO5Ev9ia.js.map +1 -0
- package/dist/merged-draw-SXYrxFt4.cjs +2 -0
- package/dist/merged-draw-SXYrxFt4.cjs.map +1 -0
- package/dist/parse.cjs +8 -8
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.d.cts +5 -0
- package/dist/parse.d.ts +5 -0
- package/dist/parse.js +8 -8
- package/dist/parse.js.map +1 -1
- package/dist/render.cjs +4 -4
- package/dist/render.cjs.map +1 -1
- package/dist/render.js +4 -4
- package/dist/render.js.map +1 -1
- package/dist/scene.cjs +1 -1
- package/dist/scene.cjs.map +1 -1
- package/dist/scene.js +1 -1
- package/package.json +2 -1
- package/dist/gpu-dispose-D3uBOjqr.cjs +0 -2
- package/dist/gpu-dispose-D3uBOjqr.cjs.map +0 -1
- package/dist/gpu-dispose-Dr69ZlDk.js +0 -2
- package/dist/gpu-dispose-Dr69ZlDk.js.map +0 -1
- package/dist/merged-draw-B4lGH7FT.cjs +0 -2
- package/dist/merged-draw-B4lGH7FT.cjs.map +0 -1
- package/dist/merged-draw-jrCP6Cz4.js +0 -2
- package/dist/merged-draw-jrCP6Cz4.js.map +0 -1
- package/dist/rolldown-runtime-BocRIvOZ.cjs +0 -1
package/dist/scene.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./gpu-ownership-ilj3JEdn.cjs"),t=require("./merged-draw-SXYrxFt4.cjs");let n=require("three");n=e.s(n,1);const r=/* @__PURE__ */ new Set([`grid`,`floor`,`label-layer`,`measure`]);function i(e){return!(e instanceof n.Camera)&&!(e instanceof n.Light)&&!r.has(e.userData?.id)}function a(e){return e.children.filter(i)}function o(e){return e.replace(/^Line(Segments)?2$/,`Curve`).replace(`Mesh`,``).replace(`Object3D`,`Obj`)||e}function s(e){return e.userData?.name||e.userData?.fileName||e.name||o(e.type)}function c(e){return o(e.type)}function l(e){let t=e.userData;if(!t)return null;if(typeof t.trackingKey==`string`&&t.trackingKey)return t.trackingKey;if(typeof t.id==`string`&&t.id)return t.id;let n=typeof t.name==`string`?t.name:e.name,r=typeof t.layer==`string`?t.layer:``;return n?`name${r}${n}`:null}function u(e){return l(e)??e.uuid}function d(e){let t=e.userData?.members;return!Array.isArray(t)||t.length===0?[u(e)]:t.map((t,n)=>typeof t.trackingKey==`string`&&t.trackingKey?t.trackingKey:t.name?`name${t.layer}${t.name}`:`${e.uuid}${n}`)}function f(e){let t=e.userData?.members;return Array.isArray(t)&&t.length>0?t:null}const p=(e,t)=>e.userData?.layer||e.userData?.category||t;function m(e,t){let n=[],r=/* @__PURE__ */ new Map,i=e=>{let t=r.get(e)??0;return r.set(e,t+1),t===0?e:`${e}#${t}`};for(let r of a(e)){let e=f(r);if(!e){let e=u(r);n.push({object:r,memberIndex:null,key:e,rowKey:i(e),label:s(r),layer:p(r,t)});continue}let a=d(r);e.forEach((e,s)=>{let c=a[s]??`${r.uuid}:${s}`;n.push({object:r,memberIndex:s,key:c,rowKey:i(c),label:e.name||o(r.type),layer:e.layer||p(r,t)})})}return n}const h=`Default`;function g(e){let t=/* @__PURE__ */ new Map;for(let n of e){let e=n.userData?.layer||n.userData?.category||`Default`,r=t.get(e);r||(r=[],t.set(e,r)),r.push(n)}return t}function _(e,t){if(!t.trim())return e;let n=t.toLowerCase(),r=/* @__PURE__ */ new Map;for(let[t,i]of e){let e=t.toLowerCase().includes(n)?i:i.filter(e=>s(e).toLowerCase().includes(n));e.length>0&&r.set(t,e)}return r}function v(e){let t=/* @__PURE__ */ new Map;for(let n of e){let e=t.get(n.layer);e||(e=[],t.set(n.layer,e)),e.push(n)}return t}function y(e,t){if(!t.trim())return e;let n=t.toLowerCase(),r=/* @__PURE__ */ new Map;for(let[t,i]of e){let e=t.toLowerCase().includes(n)?i:i.filter(e=>e.label.toLowerCase().includes(n));e.length>0&&r.set(t,e)}return r}function b(e,n){let r=e;if(!r.isMesh||!Array.isArray(r.userData?.members))return;let i=/* @__PURE__ */ new Set;d(r).forEach((e,t)=>{n.has(e)&&i.add(t)}),t.a(r,i)}function x(e=/* @__PURE__ */ new Set){let t=t=>d(t).every(t=>e.has(t)),n={hidden:e,isHidden:t,setVisible(t,n){t.visible=n,t.traverse(e=>{e.visible=n});for(let r of d(t))n?e.delete(r):e.add(r);b(t,e)},isEntryHidden:n=>n.memberIndex===null?t(n.object):e.has(n.key),setEntryVisible(t,r){t.memberIndex===null?n.setVisible(t.object,r):(r?e.delete(t.key):e.add(t.key),b(t.object,e),r&&!t.object.visible&&t.object.traverse(e=>{e.visible=!0}))},isLayerHidden:e=>e.length>0&&e.every(e=>n.isHidden(e)),isLayerPartial(e){let t=e.filter(e=>n.isHidden(e)).length;return t>0&&t<e.length},toggleLayer(e){let t=n.isLayerHidden(e);for(let r of e)n.setVisible(r,t)},applyTo(n){for(let r of n)b(r,e),t(r)&&(r.visible=!1,r.traverse(e=>{e.visible=!1}))},reset:()=>e.clear()};return n}function S(e=/* @__PURE__ */ new Set){let t=null,n=/* @__PURE__ */ new Set,r=e=>{t=e;for(let t of n)t(e)};return{selected:e,get anchor(){return t},isSelected:t=>e.has(t),select(n,i,a){if(i.shiftKey&&t){let r=a(),o=r.indexOf(t),s=r.indexOf(n);if(o!==-1&&s!==-1){let[t,n]=o<s?[o,s]:[s,o];i.toggleKey||e.clear();for(let i=t;i<=n;i++)e.add(r[i])}}else i.toggleKey?e.has(n)?e.delete(n):e.add(n):(e.clear(),e.add(n)),r(n)},clear(){e.clear(),r(null)},onAnchorChange(e){return n.add(e),()=>n.delete(e)}}}function C(e,t={}){let n=t.sets?.collapsed??/* @__PURE__ */ new Set,r=x(t.sets?.hidden),i=S(t.sets?.selected),o={visibility:r,selection:i,collapsed:n,objects:()=>a(e),entries:()=>m(e,h),entryGroups:(t=``)=>y(v(m(e,h)),t),layerGroups:(t=``)=>_(g(a(e)),t),isCollapsed:e=>n.has(e),toggleCollapsed(e){n.has(e)?n.delete(e):n.add(e)},toggleObject(e){if(i.isSelected(e.uuid)&&i.selected.size>1){let e=o.objects().filter(e=>i.isSelected(e.uuid)),t=e.every(e=>r.isHidden(e));for(let n of e)r.setVisible(n,t)}else r.setVisible(e,r.isHidden(e))},toggleEntry(e){if(i.isSelected(e.key)&&i.selected.size>1){let e=o.entries().filter(e=>i.isSelected(e.key)),t=e.every(e=>r.isEntryHidden(e));for(let n of e)r.setEntryVisible(n,t)}else r.setEntryVisible(e,r.isEntryHidden(e))},select(e,t,n){i.select(e,t,()=>o.flatVisibleUuids(n))},onAnchorChange:e=>i.onAnchorChange(e),flatVisibleUuids(e){let t=[];for(let[r,i]of o.entryGroups(e))if(!n.has(r))for(let e of i)t.push(e.key);return t},applyTo(){r.applyTo(o.objects()),i.clear()},reset(){r.reset(),i.clear()}};return o}exports.createSceneOutliner=C,exports.getMemberKeys=d,exports.getObjectLabel=s,exports.getTrackingKey=u,exports.getTypeLabel=c;
|
|
2
2
|
//# sourceMappingURL=scene.cjs.map
|
package/dist/scene.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene.cjs","names":["THREE","require_rolldown_runtime","require_merged_draw","three","HELPER_IDS","isSceneContent","object","getSceneObjects","scene","prettyType","type","getObjectLabel","getTypeLabel","getStableKey","data","name","layer","getTrackingKey","getMemberKeys","members","member","i","membersOf","layerOf","fallback","getSceneEntries","entries","seen","rowKeyFor","n","key","defaultLayer","keys","DEFAULT_LAYER","groupByLayer","groups","obj","objects","bucket","filterLayerGroups","query","q","filtered","layerName","matching","groupEntriesByLayer","entry","filterEntryGroups","applyEntryVisibility","mesh","indices","hidden","createVisibilityState","allHidden","state","visible","count","show","createSelectionState","anchor","anchorListeners","setAnchor","next","listener","selected","uuid","modifiers","flat","flatOrder","a","b","lo","hi","createSceneOutliner","collapsed","options","visibility","selection","outliner","searchQuery","chosen","o","e","result"],"sources":["../src/scene/objects.ts","../src/scene/identity.ts","../src/scene/entries.ts","../src/scene/layers.ts","../src/scene/member-visibility.ts","../src/scene/visibility.ts","../src/scene/selection.ts","../src/scene/outliner.ts"],"sourcesContent":["// ============================================================================\n// Scene content: which objects are user content, and how they are labelled\n// ============================================================================\n//\n// A live THREE.Scene holds more than the solve's output — the renderer also adds a camera, lights,\n// and viewer aids (grid, floor, measurement overlay, CSS2D label layer). Anything presenting the\n// scene to a user has to filter those out the same way.\n\nimport * as THREE from 'three';\n\n// Viewer aids tagged by `userData.id` in `render/`; live in the scene graph but are not solve output.\nexport const HELPER_IDS: ReadonlySet<string> = new Set(['grid', 'floor', 'label-layer', 'measure']);\n\nexport function isSceneContent(object: THREE.Object3D): boolean {\n\treturn (\n\t\t!(object instanceof THREE.Camera) &&\n\t\t!(object instanceof THREE.Light) &&\n\t\t!HELPER_IDS.has(object.userData?.id)\n\t);\n}\n\n// Only top-level children: a mesh's own sub-objects (edge overlays, labels) are governed by it,\n// not listed alongside it.\nexport function getSceneObjects(scene: THREE.Scene): THREE.Object3D[] {\n\treturn scene.children.filter(isSceneContent);\n}\n\n// `Line2`/`LineSegments2` are how curves are rendered — an implementation detail no user should\n// have to decode.\nexport function prettyType(type: string): string {\n\treturn (\n\t\ttype\n\t\t\t.replace(/^Line(Segments)?2$/, 'Curve')\n\t\t\t.replace('Mesh', '')\n\t\t\t.replace('Object3D', 'Obj') || type\n\t);\n}\n\nexport function getObjectLabel(object: THREE.Object3D): string {\n\treturn (\n\t\tobject.userData?.name || object.userData?.fileName || object.name || prettyType(object.type)\n\t);\n}\n\nexport function getTypeLabel(object: THREE.Object3D): string {\n\treturn prettyType(object.type);\n}\n","// ============================================================================\n// Stable object identity across solves\n// ============================================================================\n//\n// A solve discards all content and rebuilds it, so `THREE.Object3D.uuid` (assigned per instance)\n// cannot answer \"is this the same wall I hid a minute ago\". Anything that must outlive a solve —\n// hidden state, selection, per-object overrides — keys on the writer-minted object id instead,\n// stamped by the parse layer as `userData.trackingKey` (Selva mints\n// `{componentGuid}/{branchPath}/{slotIndex}`; the viewer never parses it).\n//\n// A merged mesh is several source objects in one THREE object, so its identity is per member:\n// `userData.members` carries each member's key, and the hidden-set stores member keys.\n\nimport type * as THREE from 'three';\n\n// Unit separator, not a printable character: layer/object names may contain anything a user can\n// type, and 'a' + ':' + 'b:c' must not collide with 'a:b' + ':' + 'c'.\nconst SEP = String.fromCharCode(31);\n\n/** One source object inside a merged mesh — see `finalizeMergedMesh`. */\nexport interface MergedMember {\n\ttrackingKey?: string;\n\tname: string;\n\tlayer: string;\n\tmetadata: Record<string, string>;\n\t/** Start of this member's window into the merged index buffer; absent on older merges. */\n\tindexStart?: number;\n\t/** Length of that window, in indices. */\n\tindexCount?: number;\n}\n\n/**\n * The minted `userData.trackingKey` when the writer supplied one; `userData.id` for display\n * items (their pre-baked pick key); `name` + `layer` as a weaker fallback for foreign writers\n * that mint no ids — two unnamed meshes on one layer collide under it.\n */\nexport function getStableKey(object: THREE.Object3D): string | null {\n\tconst data = object.userData;\n\tif (!data) return null;\n\n\tif (typeof data.trackingKey === 'string' && data.trackingKey) return data.trackingKey;\n\n\t// Display items carry their minted id under `id` (which doubles as the pick key).\n\tif (typeof data.id === 'string' && data.id) return data.id;\n\n\tconst name = typeof data.name === 'string' ? data.name : object.name;\n\tconst layer = typeof data.layer === 'string' ? data.layer : '';\n\tif (name) return `name${SEP}${layer}${SEP}${name}`;\n\n\treturn null;\n}\n\n/** Falls back to the instance uuid when the object has no stable identity, unlike `getStableKey`. */\nexport function getTrackingKey(object: THREE.Object3D): string {\n\treturn getStableKey(object) ?? object.uuid;\n}\n\n/**\n * The identity keys hidden state hangs off: each member's key for a merged mesh, the object's own\n * for everything else. A member without a minted key falls back to its name + layer, then to the\n * merged object's uuid (per-instance, like any other identity-less object).\n */\nexport function getMemberKeys(object: THREE.Object3D): string[] {\n\tconst members = object.userData?.members as MergedMember[] | undefined;\n\tif (!Array.isArray(members) || members.length === 0) {\n\t\treturn [getTrackingKey(object)];\n\t}\n\n\treturn members.map((member, i) => {\n\t\tif (typeof member.trackingKey === 'string' && member.trackingKey) return member.trackingKey;\n\t\tif (member.name) return `name${SEP}${member.layer}${SEP}${member.name}`;\n\t\treturn `${object.uuid}${SEP}${i}`;\n\t});\n}\n","// ============================================================================\n// Scene entries: one listable thing per source object\n// ============================================================================\n//\n// The outliner used to list `THREE.Object3D`s directly, which held while one source object was one\n// THREE object. Merging by material breaks that: an imported building model renders as a few dozen\n// merged meshes, so listing objects would show a few dozen rows named after whichever member\n// happened to be first, and the building elements the user actually works with would be\n// unreachable.\n//\n// An entry is one *listable* thing — a plain object, or one member inside a merged mesh. Rendering\n// stays merged (that is what keeps the model fast); only the listing is expanded. Entries carry the\n// identity key visibility already uses, so hiding a row works the same either way.\n\nimport type * as THREE from 'three';\n\nimport { getMemberKeys, getTrackingKey, type MergedMember } from './identity.js';\nimport { getObjectLabel, getSceneObjects, prettyType } from './objects.js';\n\nexport interface SceneEntry {\n\t/** The THREE object that renders this entry — the merged mesh when `member` is set. */\n\tobject: THREE.Object3D;\n\t/** The member's index within `object.userData.members`, or null for a whole object. */\n\tmemberIndex: number | null;\n\t/**\n\t * Stable identity. Matches the key visibility stores, so a row's eye toggles exactly the thing\n\t * the row names. Deliberately not unique: two identity-less objects sharing a name and layer\n\t * collide here, and that collision means \"these hide together\". Key a list on\n\t * {@link rowKey} instead.\n\t */\n\tkey: string;\n\t/**\n\t * {@link key}, made unique across the returned entries by suffixing repeats. Keying a list on\n\t * `key` throws on the collisions identity deliberately tolerates.\n\t */\n\trowKey: string;\n\tlabel: string;\n\tlayer: string;\n}\n\n/** Members of a merged mesh, or null for an ordinary object. */\nfunction membersOf(object: THREE.Object3D): MergedMember[] | null {\n\tconst members = object.userData?.members as MergedMember[] | undefined;\n\treturn Array.isArray(members) && members.length > 0 ? members : null;\n}\n\nconst layerOf = (object: THREE.Object3D, fallback: string): string =>\n\tobject.userData?.layer || object.userData?.category || fallback;\n\n/**\n * One entry per source object: merged meshes expand into their members, everything else stays\n * itself. Member keys come from {@link getMemberKeys}, which is the same order as `members`, so a\n * row and the hidden-set agree on identity.\n */\nexport function getSceneEntries(scene: THREE.Scene, defaultLayer: string): SceneEntry[] {\n\tconst entries: SceneEntry[] = [];\n\t// Occurrences of each identity key so far, so the nth repeat of one key becomes `key#n`.\n\tconst seen = new Map<string, number>();\n\tconst rowKeyFor = (key: string): string => {\n\t\tconst n = seen.get(key) ?? 0;\n\t\tseen.set(key, n + 1);\n\t\treturn n === 0 ? key : `${key}#${n}`;\n\t};\n\n\tfor (const object of getSceneObjects(scene)) {\n\t\tconst members = membersOf(object);\n\t\tif (!members) {\n\t\t\tconst key = getTrackingKey(object);\n\t\t\tentries.push({\n\t\t\t\tobject,\n\t\t\t\tmemberIndex: null,\n\t\t\t\tkey,\n\t\t\t\trowKey: rowKeyFor(key),\n\t\t\t\tlabel: getObjectLabel(object),\n\t\t\t\tlayer: layerOf(object, defaultLayer)\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst keys = getMemberKeys(object);\n\t\tmembers.forEach((member, i) => {\n\t\t\tconst key = keys[i] ?? `${object.uuid}:${i}`;\n\t\t\tentries.push({\n\t\t\t\tobject,\n\t\t\t\tmemberIndex: i,\n\t\t\t\tkey,\n\t\t\t\trowKey: rowKeyFor(key),\n\t\t\t\tlabel: member.name || prettyType(object.type),\n\t\t\t\t// A merge never spans layers (`splitGroupByLayer`), so the member's layer and its\n\t\t\t\t// mesh's agree; the member's is used anyway, since it is the one being listed.\n\t\t\t\tlayer: member.layer || layerOf(object, defaultLayer)\n\t\t\t});\n\t\t});\n\t}\n\n\treturn entries;\n}\n","// ============================================================================\n// Layer grouping and search filtering\n// ============================================================================\n\nimport type * as THREE from 'three';\nimport { getObjectLabel } from './objects.js';\nimport type { SceneEntry } from './entries.js';\n\nexport const DEFAULT_LAYER = 'Default';\n\n/**\n * Group content objects by their Grasshopper layer.\n *\n * `userData.layer` wins over `userData.category`; objects with neither land in {@link DEFAULT_LAYER}.\n * Insertion order is preserved, so the grouping follows scene-graph order rather than sorting\n * alphabetically — the order geometry was baked in is meaningful to the author.\n */\nexport function groupByLayer(objects: THREE.Object3D[]): Map<string, THREE.Object3D[]> {\n\tconst groups = new Map<string, THREE.Object3D[]>();\n\tfor (const obj of objects) {\n\t\tconst layer: string = obj.userData?.layer || obj.userData?.category || DEFAULT_LAYER;\n\t\tlet bucket = groups.get(layer);\n\t\tif (!bucket) {\n\t\t\tbucket = [];\n\t\t\tgroups.set(layer, bucket);\n\t\t}\n\t\tbucket.push(obj);\n\t}\n\treturn groups;\n}\n\n/**\n * Filter grouped layers by a free-text query.\n *\n * A layer whose *name* matches keeps all its objects — searching for a layer means wanting to see\n * what's on it. Otherwise the layer keeps only the objects whose labels match, and drops out\n * entirely when none do. An empty query returns the input untouched.\n */\nexport function filterLayerGroups(\n\tgroups: Map<string, THREE.Object3D[]>,\n\tquery: string\n): Map<string, THREE.Object3D[]> {\n\tif (!query.trim()) return groups;\n\tconst q = query.toLowerCase();\n\tconst filtered = new Map<string, THREE.Object3D[]>();\n\tfor (const [layerName, objects] of groups) {\n\t\tconst matching = layerName.toLowerCase().includes(q)\n\t\t\t? objects\n\t\t\t: objects.filter((obj) => getObjectLabel(obj).toLowerCase().includes(q));\n\t\tif (matching.length > 0) filtered.set(layerName, matching);\n\t}\n\treturn filtered;\n}\n\n/** {@link groupByLayer}, over entries — each entry already carries the layer it belongs to. */\nexport function groupEntriesByLayer(entries: SceneEntry[]): Map<string, SceneEntry[]> {\n\tconst groups = new Map<string, SceneEntry[]>();\n\tfor (const entry of entries) {\n\t\tlet bucket = groups.get(entry.layer);\n\t\tif (!bucket) {\n\t\t\tbucket = [];\n\t\t\tgroups.set(entry.layer, bucket);\n\t\t}\n\t\tbucket.push(entry);\n\t}\n\treturn groups;\n}\n\n/** {@link filterLayerGroups}, over entries. */\nexport function filterEntryGroups(\n\tgroups: Map<string, SceneEntry[]>,\n\tquery: string\n): Map<string, SceneEntry[]> {\n\tif (!query.trim()) return groups;\n\tconst q = query.toLowerCase();\n\tconst filtered = new Map<string, SceneEntry[]>();\n\tfor (const [layerName, entries] of groups) {\n\t\tconst matching = layerName.toLowerCase().includes(q)\n\t\t\t? entries\n\t\t\t: entries.filter((entry) => entry.label.toLowerCase().includes(q));\n\t\tif (matching.length > 0) filtered.set(layerName, matching);\n\t}\n\treturn filtered;\n}\n","// ============================================================================\n// Hiding one member of a merged mesh\n// ============================================================================\n//\n// `.visible` is per THREE object, so it cannot hide one wall inside a merged mesh that draws a\n// whole material group. The hidden-set is translated to member indices and handed to\n// `shared/merged-draw.ts`, which rebuilds the drawn index ranges to skip them.\n\nimport type * as THREE from 'three';\n\nimport { setHiddenMembers } from '../shared/index.js';\nimport { getMemberKeys } from './identity.js';\n\n/** Rebuilds `object`'s drawn ranges from the hidden-set. No-op unless it is a merged mesh. */\nexport function applyEntryVisibility(object: THREE.Object3D, hidden: Set<string>): void {\n\tconst mesh = object as THREE.Mesh;\n\tif (!mesh.isMesh || !Array.isArray(mesh.userData?.members)) return;\n\n\tconst indices = new Set<number>();\n\tgetMemberKeys(mesh).forEach((key, i) => {\n\t\tif (hidden.has(key)) indices.add(i);\n\t});\n\tsetHiddenMembers(mesh, indices);\n}\n","// ============================================================================\n// Visibility: hidden-set bookkeeping over live THREE objects\n// ============================================================================\n//\n// Hiding an object flips `.visible` on the object *and its whole subtree* (edge overlays and CSS2D\n// labels are children, and three does not propagate `.visible` down for the label renderer), and\n// records the fact in a hidden-set so the UI can render an eye-off state and compute per-layer\n// tri-state.\n//\n// The set is keyed by stable identity (see `identity.ts`), not `uuid`, so hiding survives a solve —\n// a solve regenerates uuids but not the minted ids the keys read. A merged mesh stores one key per\n// source member, so re-merging under a different material/layer grouping can't lose the state.\n// Objects with no identifying userData fall back to their uuid, so their hidden state lasts only\n// until the next solve.\n\nimport type * as THREE from 'three';\nimport { getMemberKeys } from './identity.js';\nimport { applyEntryVisibility } from './member-visibility.js';\n\n/** The parts of a `SceneEntry` visibility needs; kept structural so entries.ts stays optional. */\nexport interface EntryRef {\n\tobject: THREE.Object3D;\n\tmemberIndex: number | null;\n\tkey: string;\n}\n\n// Backed by a caller-supplied `Set` so a reactive host can pass a framework-observable set\n// (Svelte's `SvelteSet`) and get re-renders for free.\nexport interface VisibilityState {\n\treadonly hidden: Set<string>;\n\tisHidden(object: THREE.Object3D): boolean;\n\tsetVisible(object: THREE.Object3D, visible: boolean): void;\n\tisLayerHidden(objects: THREE.Object3D[]): boolean;\n\t/** Drives the tri-state eye icon. */\n\tisLayerPartial(objects: THREE.Object3D[]): boolean;\n\ttoggleLayer(objects: THREE.Object3D[]): void;\n\t/**\n\t * Hide or show one entry — a whole object, or a single member inside a merged mesh.\n\t *\n\t * A merged mesh renders as one THREE object, so a member cannot be hidden by flipping\n\t * `.visible`. Its key goes in the hidden-set and the mesh's drawn index ranges are rebuilt to\n\t * skip it, which is what {@link applyEntryVisibility} does.\n\t */\n\tsetEntryVisible(entry: EntryRef, visible: boolean): void;\n\tisEntryHidden(entry: EntryRef): boolean;\n\t/** Restores `.visible` on everything the user had hidden before the solve. Call after each solve. */\n\tapplyTo(objects: THREE.Object3D[]): void;\n\treset(): void;\n}\n\nexport function createVisibilityState(hidden: Set<string> = new Set()): VisibilityState {\n\t// An object is hidden when EVERY member key is in the set — for a plain mesh that is its one\n\t// key; for a merged mesh it means all source members are hidden.\n\tconst allHidden = (object: THREE.Object3D) =>\n\t\tgetMemberKeys(object).every((key) => hidden.has(key));\n\n\tconst state: VisibilityState = {\n\t\thidden,\n\n\t\tisHidden: allHidden,\n\n\t\tsetVisible(object, visible) {\n\t\t\tobject.visible = visible;\n\t\t\tobject.traverse((child) => {\n\t\t\t\tchild.visible = visible;\n\t\t\t});\n\t\t\tfor (const key of getMemberKeys(object)) {\n\t\t\t\tif (visible) hidden.delete(key);\n\t\t\t\telse hidden.add(key);\n\t\t\t}\n\t\t\t// Showing the whole object must also drop ranges left by hiding single members.\n\t\t\tapplyEntryVisibility(object, hidden);\n\t\t},\n\n\t\tisEntryHidden: (entry) =>\n\t\t\tentry.memberIndex === null ? allHidden(entry.object) : hidden.has(entry.key),\n\n\t\tsetEntryVisible(entry, visible) {\n\t\t\tif (entry.memberIndex === null) {\n\t\t\t\tstate.setVisible(entry.object, visible);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (visible) hidden.delete(entry.key);\n\t\t\telse hidden.add(entry.key);\n\t\t\tapplyEntryVisibility(entry.object, hidden);\n\t\t\t// The whole mesh may be off from a layer toggle; showing one member has to turn it back on.\n\t\t\tif (visible && !entry.object.visible) {\n\t\t\t\tentry.object.traverse((child) => {\n\t\t\t\t\tchild.visible = true;\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\tisLayerHidden: (objects) => objects.length > 0 && objects.every((obj) => state.isHidden(obj)),\n\n\t\tisLayerPartial(objects) {\n\t\t\tconst count = objects.filter((obj) => state.isHidden(obj)).length;\n\t\t\treturn count > 0 && count < objects.length;\n\t\t},\n\n\t\ttoggleLayer(objects) {\n\t\t\t// A partially hidden layer hides the rest — only a fully hidden layer comes back.\n\t\t\tconst show = state.isLayerHidden(objects);\n\t\t\tfor (const obj of objects) state.setVisible(obj, show);\n\t\t},\n\n\t\tapplyTo(objects) {\n\t\t\tfor (const object of objects) {\n\t\t\t\t// A merged mesh can be partly hidden, which `.visible` cannot express — rebuild its\n\t\t\t\t// drawn ranges before the whole-object rule below considers it.\n\t\t\t\tapplyEntryVisibility(object, hidden);\n\t\t\t\t// Only push objects down. Anything not in the set keeps whatever visibility the render\n\t\t\t\t// layer gave it, so this never fights another feature that hid something for its own\n\t\t\t\t// reasons (isolate mode, section-box culling).\n\t\t\t\tif (!allHidden(object)) continue;\n\t\t\t\tobject.visible = false;\n\t\t\t\tobject.traverse((child) => {\n\t\t\t\t\tchild.visible = false;\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\treset: () => hidden.clear()\n\t};\n\treturn state;\n}\n","// ============================================================================\n// Selection: click / ctrl-click / shift-range over a flat list\n// ============================================================================\n//\n// Standard file-explorer selection semantics. Range selection needs the *flattened, currently\n// visible* order — what the user actually sees after search filtering and layer collapse — which\n// only the caller knows, so it is passed in per click rather than derived here.\n\nexport interface SelectionModifiers {\n\tshiftKey: boolean;\n\t/** Ctrl (Windows/Linux) or Meta (macOS) — the caller normalizes the platform difference. */\n\ttoggleKey: boolean;\n}\n\nexport interface SelectionState {\n\treadonly selected: Set<string>;\n\t/** The click that anchors the next shift-range. */\n\treadonly anchor: string | null;\n\tisSelected(uuid: string): boolean;\n\t/**\n\t * Apply a click.\n\t *\n\t * @param flatOrder The uuids currently visible, in display order — the span a shift-range walks.\n\t */\n\tselect(uuid: string, modifiers: SelectionModifiers, flatOrder: () => string[]): void;\n\tclear(): void;\n\t/**\n\t * Observe anchor moves. The anchor is a scalar, so unlike `selected` it cannot be watched by\n\t * handing in an observable set — a host that renders it mirrors it through this instead.\n\t *\n\t * @returns An unsubscribe function.\n\t */\n\tonAnchorChange(listener: (anchor: string | null) => void): () => void;\n}\n\nexport function createSelectionState(selected: Set<string> = new Set()): SelectionState {\n\tlet anchor: string | null = null;\n\tconst anchorListeners = new Set<(anchor: string | null) => void>();\n\n\tconst setAnchor = (next: string | null) => {\n\t\tanchor = next;\n\t\tfor (const listener of anchorListeners) listener(next);\n\t};\n\n\treturn {\n\t\tselected,\n\t\tget anchor() {\n\t\t\treturn anchor;\n\t\t},\n\n\t\tisSelected: (uuid) => selected.has(uuid),\n\n\t\tselect(uuid, modifiers, flatOrder) {\n\t\t\tif (modifiers.shiftKey && anchor) {\n\t\t\t\tconst flat = flatOrder();\n\t\t\t\tconst a = flat.indexOf(anchor);\n\t\t\t\tconst b = flat.indexOf(uuid);\n\t\t\t\tif (a !== -1 && b !== -1) {\n\t\t\t\t\tconst [lo, hi] = a < b ? [a, b] : [b, a];\n\t\t\t\t\t// Ctrl+Shift extends the selection; plain Shift replaces it.\n\t\t\t\t\tif (!modifiers.toggleKey) selected.clear();\n\t\t\t\t\tfor (let i = lo; i <= hi; i++) selected.add(flat[i]);\n\t\t\t\t}\n\t\t\t\t// The anchor stays put, so dragging the shift-click around pivots on the same origin.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (modifiers.toggleKey) {\n\t\t\t\tif (selected.has(uuid)) selected.delete(uuid);\n\t\t\t\telse selected.add(uuid);\n\t\t\t} else {\n\t\t\t\tselected.clear();\n\t\t\t\tselected.add(uuid);\n\t\t\t}\n\t\t\tsetAnchor(uuid);\n\t\t},\n\n\t\tclear() {\n\t\t\tselected.clear();\n\t\t\tsetAnchor(null);\n\t\t},\n\n\t\tonAnchorChange(listener) {\n\t\t\tanchorListeners.add(listener);\n\t\t\treturn () => anchorListeners.delete(listener);\n\t\t}\n\t};\n}\n","// ============================================================================\n// SceneOutliner: the object-list state machine over a live THREE.Scene\n// ============================================================================\n//\n// Everything an outliner panel needs — content filtering, layer grouping, search, collapse,\n// visibility and selection — with no DOM and no framework. A host renders `layerGroups()` and\n// forwards clicks; see `SceneManager.svelte` in `@selvajs/ui` for the Svelte binding.\n\nimport type * as THREE from 'three';\nimport { getSceneObjects } from './objects.js';\nimport { getSceneEntries, type SceneEntry } from './entries.js';\nimport {\n\tDEFAULT_LAYER,\n\tfilterEntryGroups,\n\tfilterLayerGroups,\n\tgroupByLayer,\n\tgroupEntriesByLayer\n} from './layers.js';\nimport { createVisibilityState, type VisibilityState } from './visibility.js';\nimport { createSelectionState, type SelectionModifiers, type SelectionState } from './selection.js';\n\nexport interface SceneOutlinerOptions {\n\t/**\n\t * Backing sets for the mutable state. Supply framework-observable sets (e.g. Svelte's\n\t * `SvelteSet`) to make a host re-render on mutation; omit for plain `Set`s.\n\t */\n\tsets?: {\n\t\thidden?: Set<string>;\n\t\tselected?: Set<string>;\n\t\tcollapsed?: Set<string>;\n\t};\n}\n\nexport interface SceneOutliner {\n\treadonly visibility: VisibilityState;\n\treadonly selection: SelectionState;\n\treadonly collapsed: Set<string>;\n\n\t/** Not memoized — recomputes from the scene on every call. */\n\tobjects(): THREE.Object3D[];\n\t/**\n\t * One entry per source object, which is what a panel lists. Merged meshes expand into their\n\t * members, so an imported model shows its building elements rather than the handful of meshes\n\t * they were merged into; everything else is one entry for itself.\n\t */\n\tentries(): SceneEntry[];\n\t/** {@link layerGroups}, over entries. */\n\tentryGroups(searchQuery?: string): Map<string, SceneEntry[]>;\n\t/**\n\t * Content grouped by layer, filtered by `searchQuery` (free text over layer and object\n\t * names). The query is a parameter rather than outliner state so a host can derive this\n\t * without writing back — mutating the outliner from a `$derived` is a Svelte error.\n\t */\n\tlayerGroups(searchQuery?: string): Map<string, THREE.Object3D[]>;\n\n\tisCollapsed(layerName: string): boolean;\n\ttoggleCollapsed(layerName: string): void;\n\n\t/**\n\t * Toggle one object's visibility. When the object is part of a multi-selection, the whole\n\t * selection follows it — hiding one of five selected meshes hides all five.\n\t */\n\ttoggleObject(object: THREE.Object3D): void;\n\n\t/** {@link toggleObject}, for one entry — a whole object or a single merged member. */\n\ttoggleEntry(entry: SceneEntry): void;\n\n\t/**\n\t * Select by entry key (see {@link SceneEntry}) — a merged member is selectable on its own, so\n\t * uuid would not distinguish siblings inside one mesh.\n\t *\n\t * Shift-ranges resolve against `flatVisibleUuids()`, not scene-graph order — so pass the\n\t * same `searchQuery` the panel is displaying, or a range will span filtered-out objects.\n\t */\n\tselect(key: string, modifiers: SelectionModifiers, searchQuery?: string): void;\n\n\t/**\n\t * Observe shift-range anchor moves, for hosts that mirror it into their own state.\n\t * Convenience passthrough to `selection.onAnchorChange`.\n\t *\n\t * @returns An unsubscribe function.\n\t */\n\tonAnchorChange(listener: (anchor: string | null) => void): () => void;\n\n\t/**\n\t * The entry keys currently visible in the panel, in display order: every entry of every\n\t * non-collapsed layer that survived the search filter. This is the span a shift-range walks.\n\t */\n\tflatVisibleUuids(searchQuery?: string): string[];\n\n\t/**\n\t * Re-apply hidden state to freshly built scene content. **Call after every solve.**\n\t *\n\t * A solve discards all content and rebuilds it, so whatever the user had hidden comes back\n\t * visible unless it is hidden again. Hidden state is keyed by stable identity rather than\n\t * instance uuid, so this restores it (see `identity.ts`). Selection is dropped: it refers to\n\t * object instances that no longer exist, and a persistent selection across solves is not a\n\t * behaviour anyone asked for.\n\t */\n\tapplyTo(): void;\n\n\t/** Drop all hidden and selected state, showing everything. Objects are left as they are. */\n\treset(): void;\n}\n\nexport function createSceneOutliner(\n\tscene: THREE.Scene,\n\toptions: SceneOutlinerOptions = {}\n): SceneOutliner {\n\tconst collapsed = options.sets?.collapsed ?? new Set<string>();\n\tconst visibility = createVisibilityState(options.sets?.hidden);\n\tconst selection = createSelectionState(options.sets?.selected);\n\n\tconst outliner: SceneOutliner = {\n\t\tvisibility,\n\t\tselection,\n\t\tcollapsed,\n\n\t\tobjects: () => getSceneObjects(scene),\n\n\t\tentries: () => getSceneEntries(scene, DEFAULT_LAYER),\n\n\t\tentryGroups: (searchQuery = '') =>\n\t\t\tfilterEntryGroups(groupEntriesByLayer(getSceneEntries(scene, DEFAULT_LAYER)), searchQuery),\n\n\t\tlayerGroups: (searchQuery = '') =>\n\t\t\tfilterLayerGroups(groupByLayer(getSceneObjects(scene)), searchQuery),\n\n\t\tisCollapsed: (layerName) => collapsed.has(layerName),\n\n\t\ttoggleCollapsed(layerName) {\n\t\t\tif (collapsed.has(layerName)) collapsed.delete(layerName);\n\t\t\telse collapsed.add(layerName);\n\t\t},\n\n\t\ttoggleObject(object) {\n\t\t\tif (selection.isSelected(object.uuid) && selection.selected.size > 1) {\n\t\t\t\tconst chosen = outliner.objects().filter((o) => selection.isSelected(o.uuid));\n\t\t\t\t// Show the group only once every member is hidden, matching the per-layer rule.\n\t\t\t\tconst allHidden = chosen.every((o) => visibility.isHidden(o));\n\t\t\t\tfor (const o of chosen) visibility.setVisible(o, allHidden);\n\t\t\t} else {\n\t\t\t\tvisibility.setVisible(object, visibility.isHidden(object));\n\t\t\t}\n\t\t},\n\n\t\ttoggleEntry(entry) {\n\t\t\tif (selection.isSelected(entry.key) && selection.selected.size > 1) {\n\t\t\t\tconst chosen = outliner.entries().filter((e) => selection.isSelected(e.key));\n\t\t\t\tconst allHidden = chosen.every((e) => visibility.isEntryHidden(e));\n\t\t\t\tfor (const e of chosen) visibility.setEntryVisible(e, allHidden);\n\t\t\t} else {\n\t\t\t\tvisibility.setEntryVisible(entry, visibility.isEntryHidden(entry));\n\t\t\t}\n\t\t},\n\n\t\tselect(key, modifiers, searchQuery) {\n\t\t\tselection.select(key, modifiers, () => outliner.flatVisibleUuids(searchQuery));\n\t\t},\n\n\t\tonAnchorChange: (listener) => selection.onAnchorChange(listener),\n\n\t\tflatVisibleUuids(searchQuery) {\n\t\t\tconst result: string[] = [];\n\t\t\tfor (const [layerName, entries] of outliner.entryGroups(searchQuery)) {\n\t\t\t\tif (collapsed.has(layerName)) continue;\n\t\t\t\tfor (const entry of entries) result.push(entry.key);\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\n\t\tapplyTo() {\n\t\t\tvisibility.applyTo(outliner.objects());\n\t\t\tselection.clear();\n\t\t},\n\n\t\treset() {\n\t\t\tvisibility.reset();\n\t\t\tselection.clear();\n\t\t}\n\t};\n\n\treturn outliner;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scene.cjs","names":["THREE","require_gpu_ownership","require_merged_draw","three","HELPER_IDS","isSceneContent","object","getSceneObjects","scene","prettyType","type","getObjectLabel","getTypeLabel","getStableKey","data","name","layer","getTrackingKey","getMemberKeys","members","member","i","membersOf","layerOf","fallback","getSceneEntries","entries","seen","rowKeyFor","n","key","defaultLayer","keys","DEFAULT_LAYER","groupByLayer","groups","obj","objects","bucket","filterLayerGroups","query","q","filtered","layerName","matching","groupEntriesByLayer","entry","filterEntryGroups","applyEntryVisibility","mesh","indices","hidden","createVisibilityState","allHidden","state","visible","count","show","createSelectionState","anchor","anchorListeners","setAnchor","next","listener","selected","uuid","modifiers","flat","flatOrder","a","b","lo","hi","createSceneOutliner","collapsed","options","visibility","selection","outliner","searchQuery","chosen","o","e","result"],"sources":["../src/scene/objects.ts","../src/scene/identity.ts","../src/scene/entries.ts","../src/scene/layers.ts","../src/scene/member-visibility.ts","../src/scene/visibility.ts","../src/scene/selection.ts","../src/scene/outliner.ts"],"sourcesContent":["// ============================================================================\n// Scene content: which objects are user content, and how they are labelled\n// ============================================================================\n//\n// A live THREE.Scene holds more than the solve's output — the renderer also adds a camera, lights,\n// and viewer aids (grid, floor, measurement overlay, CSS2D label layer). Anything presenting the\n// scene to a user has to filter those out the same way.\n\nimport * as THREE from 'three';\n\n// Viewer aids tagged by `userData.id` in `render/`; live in the scene graph but are not solve output.\nexport const HELPER_IDS: ReadonlySet<string> = new Set(['grid', 'floor', 'label-layer', 'measure']);\n\nexport function isSceneContent(object: THREE.Object3D): boolean {\n\treturn (\n\t\t!(object instanceof THREE.Camera) &&\n\t\t!(object instanceof THREE.Light) &&\n\t\t!HELPER_IDS.has(object.userData?.id)\n\t);\n}\n\n// Only top-level children: a mesh's own sub-objects (edge overlays, labels) are governed by it,\n// not listed alongside it.\nexport function getSceneObjects(scene: THREE.Scene): THREE.Object3D[] {\n\treturn scene.children.filter(isSceneContent);\n}\n\n// `Line2`/`LineSegments2` are how curves are rendered — an implementation detail no user should\n// have to decode.\nexport function prettyType(type: string): string {\n\treturn (\n\t\ttype\n\t\t\t.replace(/^Line(Segments)?2$/, 'Curve')\n\t\t\t.replace('Mesh', '')\n\t\t\t.replace('Object3D', 'Obj') || type\n\t);\n}\n\nexport function getObjectLabel(object: THREE.Object3D): string {\n\treturn (\n\t\tobject.userData?.name || object.userData?.fileName || object.name || prettyType(object.type)\n\t);\n}\n\nexport function getTypeLabel(object: THREE.Object3D): string {\n\treturn prettyType(object.type);\n}\n","// ============================================================================\n// Stable object identity across solves\n// ============================================================================\n//\n// A solve discards all content and rebuilds it, so `THREE.Object3D.uuid` (assigned per instance)\n// cannot answer \"is this the same wall I hid a minute ago\". Anything that must outlive a solve —\n// hidden state, selection, per-object overrides — keys on the writer-minted object id instead,\n// stamped by the parse layer as `userData.trackingKey` (Selva mints\n// `{componentGuid}/{branchPath}/{slotIndex}`; the viewer never parses it).\n//\n// A merged mesh is several source objects in one THREE object, so its identity is per member:\n// `userData.members` carries each member's key, and the hidden-set stores member keys.\n\nimport type * as THREE from 'three';\n\n// Unit separator, not a printable character: layer/object names may contain anything a user can\n// type, and 'a' + ':' + 'b:c' must not collide with 'a:b' + ':' + 'c'.\nconst SEP = String.fromCharCode(31);\n\n/** One source object inside a merged mesh — see `finalizeMergedMesh`. */\nexport interface MergedMember {\n\ttrackingKey?: string;\n\tname: string;\n\tlayer: string;\n\tmetadata: Record<string, string>;\n\t/** Start of this member's window into the merged index buffer; absent on older merges. */\n\tindexStart?: number;\n\t/** Length of that window, in indices. */\n\tindexCount?: number;\n}\n\n/**\n * The minted `userData.trackingKey` when the writer supplied one; `userData.id` for display\n * items (their pre-baked pick key); `name` + `layer` as a weaker fallback for foreign writers\n * that mint no ids — two unnamed meshes on one layer collide under it.\n */\nexport function getStableKey(object: THREE.Object3D): string | null {\n\tconst data = object.userData;\n\tif (!data) return null;\n\n\tif (typeof data.trackingKey === 'string' && data.trackingKey) return data.trackingKey;\n\n\t// Display items carry their minted id under `id` (which doubles as the pick key).\n\tif (typeof data.id === 'string' && data.id) return data.id;\n\n\tconst name = typeof data.name === 'string' ? data.name : object.name;\n\tconst layer = typeof data.layer === 'string' ? data.layer : '';\n\tif (name) return `name${SEP}${layer}${SEP}${name}`;\n\n\treturn null;\n}\n\n/** Falls back to the instance uuid when the object has no stable identity, unlike `getStableKey`. */\nexport function getTrackingKey(object: THREE.Object3D): string {\n\treturn getStableKey(object) ?? object.uuid;\n}\n\n/**\n * The identity keys hidden state hangs off: each member's key for a merged mesh, the object's own\n * for everything else. A member without a minted key falls back to its name + layer, then to the\n * merged object's uuid (per-instance, like any other identity-less object).\n */\nexport function getMemberKeys(object: THREE.Object3D): string[] {\n\tconst members = object.userData?.members as MergedMember[] | undefined;\n\tif (!Array.isArray(members) || members.length === 0) {\n\t\treturn [getTrackingKey(object)];\n\t}\n\n\treturn members.map((member, i) => {\n\t\tif (typeof member.trackingKey === 'string' && member.trackingKey) return member.trackingKey;\n\t\tif (member.name) return `name${SEP}${member.layer}${SEP}${member.name}`;\n\t\treturn `${object.uuid}${SEP}${i}`;\n\t});\n}\n","// ============================================================================\n// Scene entries: one listable thing per source object\n// ============================================================================\n//\n// The outliner used to list `THREE.Object3D`s directly, which held while one source object was one\n// THREE object. Merging by material breaks that: an imported building model renders as a few dozen\n// merged meshes, so listing objects would show a few dozen rows named after whichever member\n// happened to be first, and the building elements the user actually works with would be\n// unreachable.\n//\n// An entry is one *listable* thing — a plain object, or one member inside a merged mesh. Rendering\n// stays merged (that is what keeps the model fast); only the listing is expanded. Entries carry the\n// identity key visibility already uses, so hiding a row works the same either way.\n\nimport type * as THREE from 'three';\n\nimport { getMemberKeys, getTrackingKey, type MergedMember } from './identity.js';\nimport { getObjectLabel, getSceneObjects, prettyType } from './objects.js';\n\nexport interface SceneEntry {\n\t/** The THREE object that renders this entry — the merged mesh when `member` is set. */\n\tobject: THREE.Object3D;\n\t/** The member's index within `object.userData.members`, or null for a whole object. */\n\tmemberIndex: number | null;\n\t/**\n\t * Stable identity. Matches the key visibility stores, so a row's eye toggles exactly the thing\n\t * the row names. Deliberately not unique: two identity-less objects sharing a name and layer\n\t * collide here, and that collision means \"these hide together\". Key a list on\n\t * {@link rowKey} instead.\n\t */\n\tkey: string;\n\t/**\n\t * {@link key}, made unique across the returned entries by suffixing repeats. Keying a list on\n\t * `key` throws on the collisions identity deliberately tolerates.\n\t */\n\trowKey: string;\n\tlabel: string;\n\tlayer: string;\n}\n\n/** Members of a merged mesh, or null for an ordinary object. */\nfunction membersOf(object: THREE.Object3D): MergedMember[] | null {\n\tconst members = object.userData?.members as MergedMember[] | undefined;\n\treturn Array.isArray(members) && members.length > 0 ? members : null;\n}\n\nconst layerOf = (object: THREE.Object3D, fallback: string): string =>\n\tobject.userData?.layer || object.userData?.category || fallback;\n\n/**\n * One entry per source object: merged meshes expand into their members, everything else stays\n * itself. Member keys come from {@link getMemberKeys}, which is the same order as `members`, so a\n * row and the hidden-set agree on identity.\n */\nexport function getSceneEntries(scene: THREE.Scene, defaultLayer: string): SceneEntry[] {\n\tconst entries: SceneEntry[] = [];\n\t// Occurrences of each identity key so far, so the nth repeat of one key becomes `key#n`.\n\tconst seen = new Map<string, number>();\n\tconst rowKeyFor = (key: string): string => {\n\t\tconst n = seen.get(key) ?? 0;\n\t\tseen.set(key, n + 1);\n\t\treturn n === 0 ? key : `${key}#${n}`;\n\t};\n\n\tfor (const object of getSceneObjects(scene)) {\n\t\tconst members = membersOf(object);\n\t\tif (!members) {\n\t\t\tconst key = getTrackingKey(object);\n\t\t\tentries.push({\n\t\t\t\tobject,\n\t\t\t\tmemberIndex: null,\n\t\t\t\tkey,\n\t\t\t\trowKey: rowKeyFor(key),\n\t\t\t\tlabel: getObjectLabel(object),\n\t\t\t\tlayer: layerOf(object, defaultLayer)\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst keys = getMemberKeys(object);\n\t\tmembers.forEach((member, i) => {\n\t\t\tconst key = keys[i] ?? `${object.uuid}:${i}`;\n\t\t\tentries.push({\n\t\t\t\tobject,\n\t\t\t\tmemberIndex: i,\n\t\t\t\tkey,\n\t\t\t\trowKey: rowKeyFor(key),\n\t\t\t\tlabel: member.name || prettyType(object.type),\n\t\t\t\t// A merge never spans layers (`splitGroupByLayer`), so the member's layer and its\n\t\t\t\t// mesh's agree; the member's is used anyway, since it is the one being listed.\n\t\t\t\tlayer: member.layer || layerOf(object, defaultLayer)\n\t\t\t});\n\t\t});\n\t}\n\n\treturn entries;\n}\n","// ============================================================================\n// Layer grouping and search filtering\n// ============================================================================\n\nimport type * as THREE from 'three';\nimport { getObjectLabel } from './objects.js';\nimport type { SceneEntry } from './entries.js';\n\nexport const DEFAULT_LAYER = 'Default';\n\n/**\n * Group content objects by their Grasshopper layer.\n *\n * `userData.layer` wins over `userData.category`; objects with neither land in {@link DEFAULT_LAYER}.\n * Insertion order is preserved, so the grouping follows scene-graph order rather than sorting\n * alphabetically — the order geometry was baked in is meaningful to the author.\n */\nexport function groupByLayer(objects: THREE.Object3D[]): Map<string, THREE.Object3D[]> {\n\tconst groups = new Map<string, THREE.Object3D[]>();\n\tfor (const obj of objects) {\n\t\tconst layer: string = obj.userData?.layer || obj.userData?.category || DEFAULT_LAYER;\n\t\tlet bucket = groups.get(layer);\n\t\tif (!bucket) {\n\t\t\tbucket = [];\n\t\t\tgroups.set(layer, bucket);\n\t\t}\n\t\tbucket.push(obj);\n\t}\n\treturn groups;\n}\n\n/**\n * Filter grouped layers by a free-text query.\n *\n * A layer whose *name* matches keeps all its objects — searching for a layer means wanting to see\n * what's on it. Otherwise the layer keeps only the objects whose labels match, and drops out\n * entirely when none do. An empty query returns the input untouched.\n */\nexport function filterLayerGroups(\n\tgroups: Map<string, THREE.Object3D[]>,\n\tquery: string\n): Map<string, THREE.Object3D[]> {\n\tif (!query.trim()) return groups;\n\tconst q = query.toLowerCase();\n\tconst filtered = new Map<string, THREE.Object3D[]>();\n\tfor (const [layerName, objects] of groups) {\n\t\tconst matching = layerName.toLowerCase().includes(q)\n\t\t\t? objects\n\t\t\t: objects.filter((obj) => getObjectLabel(obj).toLowerCase().includes(q));\n\t\tif (matching.length > 0) filtered.set(layerName, matching);\n\t}\n\treturn filtered;\n}\n\n/** {@link groupByLayer}, over entries — each entry already carries the layer it belongs to. */\nexport function groupEntriesByLayer(entries: SceneEntry[]): Map<string, SceneEntry[]> {\n\tconst groups = new Map<string, SceneEntry[]>();\n\tfor (const entry of entries) {\n\t\tlet bucket = groups.get(entry.layer);\n\t\tif (!bucket) {\n\t\t\tbucket = [];\n\t\t\tgroups.set(entry.layer, bucket);\n\t\t}\n\t\tbucket.push(entry);\n\t}\n\treturn groups;\n}\n\n/** {@link filterLayerGroups}, over entries. */\nexport function filterEntryGroups(\n\tgroups: Map<string, SceneEntry[]>,\n\tquery: string\n): Map<string, SceneEntry[]> {\n\tif (!query.trim()) return groups;\n\tconst q = query.toLowerCase();\n\tconst filtered = new Map<string, SceneEntry[]>();\n\tfor (const [layerName, entries] of groups) {\n\t\tconst matching = layerName.toLowerCase().includes(q)\n\t\t\t? entries\n\t\t\t: entries.filter((entry) => entry.label.toLowerCase().includes(q));\n\t\tif (matching.length > 0) filtered.set(layerName, matching);\n\t}\n\treturn filtered;\n}\n","// ============================================================================\n// Hiding one member of a merged mesh\n// ============================================================================\n//\n// `.visible` is per THREE object, so it cannot hide one wall inside a merged mesh that draws a\n// whole material group. The hidden-set is translated to member indices and handed to\n// `shared/merged-draw.ts`, which rebuilds the drawn index ranges to skip them.\n\nimport type * as THREE from 'three';\n\nimport { setHiddenMembers } from '../shared/index.js';\nimport { getMemberKeys } from './identity.js';\n\n/** Rebuilds `object`'s drawn ranges from the hidden-set. No-op unless it is a merged mesh. */\nexport function applyEntryVisibility(object: THREE.Object3D, hidden: Set<string>): void {\n\tconst mesh = object as THREE.Mesh;\n\tif (!mesh.isMesh || !Array.isArray(mesh.userData?.members)) return;\n\n\tconst indices = new Set<number>();\n\tgetMemberKeys(mesh).forEach((key, i) => {\n\t\tif (hidden.has(key)) indices.add(i);\n\t});\n\tsetHiddenMembers(mesh, indices);\n}\n","// ============================================================================\n// Visibility: hidden-set bookkeeping over live THREE objects\n// ============================================================================\n//\n// Hiding an object flips `.visible` on the object *and its whole subtree* (edge overlays and CSS2D\n// labels are children, and three does not propagate `.visible` down for the label renderer), and\n// records the fact in a hidden-set so the UI can render an eye-off state and compute per-layer\n// tri-state.\n//\n// The set is keyed by stable identity (see `identity.ts`), not `uuid`, so hiding survives a solve —\n// a solve regenerates uuids but not the minted ids the keys read. A merged mesh stores one key per\n// source member, so re-merging under a different material/layer grouping can't lose the state.\n// Objects with no identifying userData fall back to their uuid, so their hidden state lasts only\n// until the next solve.\n\nimport type * as THREE from 'three';\nimport { getMemberKeys } from './identity.js';\nimport { applyEntryVisibility } from './member-visibility.js';\n\n/** The parts of a `SceneEntry` visibility needs; kept structural so entries.ts stays optional. */\nexport interface EntryRef {\n\tobject: THREE.Object3D;\n\tmemberIndex: number | null;\n\tkey: string;\n}\n\n// Backed by a caller-supplied `Set` so a reactive host can pass a framework-observable set\n// (Svelte's `SvelteSet`) and get re-renders for free.\nexport interface VisibilityState {\n\treadonly hidden: Set<string>;\n\tisHidden(object: THREE.Object3D): boolean;\n\tsetVisible(object: THREE.Object3D, visible: boolean): void;\n\tisLayerHidden(objects: THREE.Object3D[]): boolean;\n\t/** Drives the tri-state eye icon. */\n\tisLayerPartial(objects: THREE.Object3D[]): boolean;\n\ttoggleLayer(objects: THREE.Object3D[]): void;\n\t/**\n\t * Hide or show one entry — a whole object, or a single member inside a merged mesh.\n\t *\n\t * A merged mesh renders as one THREE object, so a member cannot be hidden by flipping\n\t * `.visible`. Its key goes in the hidden-set and the mesh's drawn index ranges are rebuilt to\n\t * skip it, which is what {@link applyEntryVisibility} does.\n\t */\n\tsetEntryVisible(entry: EntryRef, visible: boolean): void;\n\tisEntryHidden(entry: EntryRef): boolean;\n\t/** Restores `.visible` on everything the user had hidden before the solve. Call after each solve. */\n\tapplyTo(objects: THREE.Object3D[]): void;\n\treset(): void;\n}\n\nexport function createVisibilityState(hidden: Set<string> = new Set()): VisibilityState {\n\t// An object is hidden when EVERY member key is in the set — for a plain mesh that is its one\n\t// key; for a merged mesh it means all source members are hidden.\n\tconst allHidden = (object: THREE.Object3D) =>\n\t\tgetMemberKeys(object).every((key) => hidden.has(key));\n\n\tconst state: VisibilityState = {\n\t\thidden,\n\n\t\tisHidden: allHidden,\n\n\t\tsetVisible(object, visible) {\n\t\t\tobject.visible = visible;\n\t\t\tobject.traverse((child) => {\n\t\t\t\tchild.visible = visible;\n\t\t\t});\n\t\t\tfor (const key of getMemberKeys(object)) {\n\t\t\t\tif (visible) hidden.delete(key);\n\t\t\t\telse hidden.add(key);\n\t\t\t}\n\t\t\t// Showing the whole object must also drop ranges left by hiding single members.\n\t\t\tapplyEntryVisibility(object, hidden);\n\t\t},\n\n\t\tisEntryHidden: (entry) =>\n\t\t\tentry.memberIndex === null ? allHidden(entry.object) : hidden.has(entry.key),\n\n\t\tsetEntryVisible(entry, visible) {\n\t\t\tif (entry.memberIndex === null) {\n\t\t\t\tstate.setVisible(entry.object, visible);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (visible) hidden.delete(entry.key);\n\t\t\telse hidden.add(entry.key);\n\t\t\tapplyEntryVisibility(entry.object, hidden);\n\t\t\t// The whole mesh may be off from a layer toggle; showing one member has to turn it back on.\n\t\t\tif (visible && !entry.object.visible) {\n\t\t\t\tentry.object.traverse((child) => {\n\t\t\t\t\tchild.visible = true;\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\tisLayerHidden: (objects) => objects.length > 0 && objects.every((obj) => state.isHidden(obj)),\n\n\t\tisLayerPartial(objects) {\n\t\t\tconst count = objects.filter((obj) => state.isHidden(obj)).length;\n\t\t\treturn count > 0 && count < objects.length;\n\t\t},\n\n\t\ttoggleLayer(objects) {\n\t\t\t// A partially hidden layer hides the rest — only a fully hidden layer comes back.\n\t\t\tconst show = state.isLayerHidden(objects);\n\t\t\tfor (const obj of objects) state.setVisible(obj, show);\n\t\t},\n\n\t\tapplyTo(objects) {\n\t\t\tfor (const object of objects) {\n\t\t\t\t// A merged mesh can be partly hidden, which `.visible` cannot express — rebuild its\n\t\t\t\t// drawn ranges before the whole-object rule below considers it.\n\t\t\t\tapplyEntryVisibility(object, hidden);\n\t\t\t\t// Only push objects down. Anything not in the set keeps whatever visibility the render\n\t\t\t\t// layer gave it, so this never fights another feature that hid something for its own\n\t\t\t\t// reasons (isolate mode, section-box culling).\n\t\t\t\tif (!allHidden(object)) continue;\n\t\t\t\tobject.visible = false;\n\t\t\t\tobject.traverse((child) => {\n\t\t\t\t\tchild.visible = false;\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\n\t\treset: () => hidden.clear()\n\t};\n\treturn state;\n}\n","// ============================================================================\n// Selection: click / ctrl-click / shift-range over a flat list\n// ============================================================================\n//\n// Standard file-explorer selection semantics. Range selection needs the *flattened, currently\n// visible* order — what the user actually sees after search filtering and layer collapse — which\n// only the caller knows, so it is passed in per click rather than derived here.\n\nexport interface SelectionModifiers {\n\tshiftKey: boolean;\n\t/** Ctrl (Windows/Linux) or Meta (macOS) — the caller normalizes the platform difference. */\n\ttoggleKey: boolean;\n}\n\nexport interface SelectionState {\n\treadonly selected: Set<string>;\n\t/** The click that anchors the next shift-range. */\n\treadonly anchor: string | null;\n\tisSelected(uuid: string): boolean;\n\t/**\n\t * Apply a click.\n\t *\n\t * @param flatOrder The uuids currently visible, in display order — the span a shift-range walks.\n\t */\n\tselect(uuid: string, modifiers: SelectionModifiers, flatOrder: () => string[]): void;\n\tclear(): void;\n\t/**\n\t * Observe anchor moves. The anchor is a scalar, so unlike `selected` it cannot be watched by\n\t * handing in an observable set — a host that renders it mirrors it through this instead.\n\t *\n\t * @returns An unsubscribe function.\n\t */\n\tonAnchorChange(listener: (anchor: string | null) => void): () => void;\n}\n\nexport function createSelectionState(selected: Set<string> = new Set()): SelectionState {\n\tlet anchor: string | null = null;\n\tconst anchorListeners = new Set<(anchor: string | null) => void>();\n\n\tconst setAnchor = (next: string | null) => {\n\t\tanchor = next;\n\t\tfor (const listener of anchorListeners) listener(next);\n\t};\n\n\treturn {\n\t\tselected,\n\t\tget anchor() {\n\t\t\treturn anchor;\n\t\t},\n\n\t\tisSelected: (uuid) => selected.has(uuid),\n\n\t\tselect(uuid, modifiers, flatOrder) {\n\t\t\tif (modifiers.shiftKey && anchor) {\n\t\t\t\tconst flat = flatOrder();\n\t\t\t\tconst a = flat.indexOf(anchor);\n\t\t\t\tconst b = flat.indexOf(uuid);\n\t\t\t\tif (a !== -1 && b !== -1) {\n\t\t\t\t\tconst [lo, hi] = a < b ? [a, b] : [b, a];\n\t\t\t\t\t// Ctrl+Shift extends the selection; plain Shift replaces it.\n\t\t\t\t\tif (!modifiers.toggleKey) selected.clear();\n\t\t\t\t\tfor (let i = lo; i <= hi; i++) selected.add(flat[i]);\n\t\t\t\t}\n\t\t\t\t// The anchor stays put, so dragging the shift-click around pivots on the same origin.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (modifiers.toggleKey) {\n\t\t\t\tif (selected.has(uuid)) selected.delete(uuid);\n\t\t\t\telse selected.add(uuid);\n\t\t\t} else {\n\t\t\t\tselected.clear();\n\t\t\t\tselected.add(uuid);\n\t\t\t}\n\t\t\tsetAnchor(uuid);\n\t\t},\n\n\t\tclear() {\n\t\t\tselected.clear();\n\t\t\tsetAnchor(null);\n\t\t},\n\n\t\tonAnchorChange(listener) {\n\t\t\tanchorListeners.add(listener);\n\t\t\treturn () => anchorListeners.delete(listener);\n\t\t}\n\t};\n}\n","// ============================================================================\n// SceneOutliner: the object-list state machine over a live THREE.Scene\n// ============================================================================\n//\n// Everything an outliner panel needs — content filtering, layer grouping, search, collapse,\n// visibility and selection — with no DOM and no framework. A host renders `layerGroups()` and\n// forwards clicks; see `SceneManager.svelte` in `@selvajs/ui` for the Svelte binding.\n\nimport type * as THREE from 'three';\nimport { getSceneObjects } from './objects.js';\nimport { getSceneEntries, type SceneEntry } from './entries.js';\nimport {\n\tDEFAULT_LAYER,\n\tfilterEntryGroups,\n\tfilterLayerGroups,\n\tgroupByLayer,\n\tgroupEntriesByLayer\n} from './layers.js';\nimport { createVisibilityState, type VisibilityState } from './visibility.js';\nimport { createSelectionState, type SelectionModifiers, type SelectionState } from './selection.js';\n\nexport interface SceneOutlinerOptions {\n\t/**\n\t * Backing sets for the mutable state. Supply framework-observable sets (e.g. Svelte's\n\t * `SvelteSet`) to make a host re-render on mutation; omit for plain `Set`s.\n\t */\n\tsets?: {\n\t\thidden?: Set<string>;\n\t\tselected?: Set<string>;\n\t\tcollapsed?: Set<string>;\n\t};\n}\n\nexport interface SceneOutliner {\n\treadonly visibility: VisibilityState;\n\treadonly selection: SelectionState;\n\treadonly collapsed: Set<string>;\n\n\t/** Not memoized — recomputes from the scene on every call. */\n\tobjects(): THREE.Object3D[];\n\t/**\n\t * One entry per source object, which is what a panel lists. Merged meshes expand into their\n\t * members, so an imported model shows its building elements rather than the handful of meshes\n\t * they were merged into; everything else is one entry for itself.\n\t */\n\tentries(): SceneEntry[];\n\t/** {@link layerGroups}, over entries. */\n\tentryGroups(searchQuery?: string): Map<string, SceneEntry[]>;\n\t/**\n\t * Content grouped by layer, filtered by `searchQuery` (free text over layer and object\n\t * names). The query is a parameter rather than outliner state so a host can derive this\n\t * without writing back — mutating the outliner from a `$derived` is a Svelte error.\n\t */\n\tlayerGroups(searchQuery?: string): Map<string, THREE.Object3D[]>;\n\n\tisCollapsed(layerName: string): boolean;\n\ttoggleCollapsed(layerName: string): void;\n\n\t/**\n\t * Toggle one object's visibility. When the object is part of a multi-selection, the whole\n\t * selection follows it — hiding one of five selected meshes hides all five.\n\t */\n\ttoggleObject(object: THREE.Object3D): void;\n\n\t/** {@link toggleObject}, for one entry — a whole object or a single merged member. */\n\ttoggleEntry(entry: SceneEntry): void;\n\n\t/**\n\t * Select by entry key (see {@link SceneEntry}) — a merged member is selectable on its own, so\n\t * uuid would not distinguish siblings inside one mesh.\n\t *\n\t * Shift-ranges resolve against `flatVisibleUuids()`, not scene-graph order — so pass the\n\t * same `searchQuery` the panel is displaying, or a range will span filtered-out objects.\n\t */\n\tselect(key: string, modifiers: SelectionModifiers, searchQuery?: string): void;\n\n\t/**\n\t * Observe shift-range anchor moves, for hosts that mirror it into their own state.\n\t * Convenience passthrough to `selection.onAnchorChange`.\n\t *\n\t * @returns An unsubscribe function.\n\t */\n\tonAnchorChange(listener: (anchor: string | null) => void): () => void;\n\n\t/**\n\t * The entry keys currently visible in the panel, in display order: every entry of every\n\t * non-collapsed layer that survived the search filter. This is the span a shift-range walks.\n\t */\n\tflatVisibleUuids(searchQuery?: string): string[];\n\n\t/**\n\t * Re-apply hidden state to freshly built scene content. **Call after every solve.**\n\t *\n\t * A solve discards all content and rebuilds it, so whatever the user had hidden comes back\n\t * visible unless it is hidden again. Hidden state is keyed by stable identity rather than\n\t * instance uuid, so this restores it (see `identity.ts`). Selection is dropped: it refers to\n\t * object instances that no longer exist, and a persistent selection across solves is not a\n\t * behaviour anyone asked for.\n\t */\n\tapplyTo(): void;\n\n\t/** Drop all hidden and selected state, showing everything. Objects are left as they are. */\n\treset(): void;\n}\n\nexport function createSceneOutliner(\n\tscene: THREE.Scene,\n\toptions: SceneOutlinerOptions = {}\n): SceneOutliner {\n\tconst collapsed = options.sets?.collapsed ?? new Set<string>();\n\tconst visibility = createVisibilityState(options.sets?.hidden);\n\tconst selection = createSelectionState(options.sets?.selected);\n\n\tconst outliner: SceneOutliner = {\n\t\tvisibility,\n\t\tselection,\n\t\tcollapsed,\n\n\t\tobjects: () => getSceneObjects(scene),\n\n\t\tentries: () => getSceneEntries(scene, DEFAULT_LAYER),\n\n\t\tentryGroups: (searchQuery = '') =>\n\t\t\tfilterEntryGroups(groupEntriesByLayer(getSceneEntries(scene, DEFAULT_LAYER)), searchQuery),\n\n\t\tlayerGroups: (searchQuery = '') =>\n\t\t\tfilterLayerGroups(groupByLayer(getSceneObjects(scene)), searchQuery),\n\n\t\tisCollapsed: (layerName) => collapsed.has(layerName),\n\n\t\ttoggleCollapsed(layerName) {\n\t\t\tif (collapsed.has(layerName)) collapsed.delete(layerName);\n\t\t\telse collapsed.add(layerName);\n\t\t},\n\n\t\ttoggleObject(object) {\n\t\t\tif (selection.isSelected(object.uuid) && selection.selected.size > 1) {\n\t\t\t\tconst chosen = outliner.objects().filter((o) => selection.isSelected(o.uuid));\n\t\t\t\t// Show the group only once every member is hidden, matching the per-layer rule.\n\t\t\t\tconst allHidden = chosen.every((o) => visibility.isHidden(o));\n\t\t\t\tfor (const o of chosen) visibility.setVisible(o, allHidden);\n\t\t\t} else {\n\t\t\t\tvisibility.setVisible(object, visibility.isHidden(object));\n\t\t\t}\n\t\t},\n\n\t\ttoggleEntry(entry) {\n\t\t\tif (selection.isSelected(entry.key) && selection.selected.size > 1) {\n\t\t\t\tconst chosen = outliner.entries().filter((e) => selection.isSelected(e.key));\n\t\t\t\tconst allHidden = chosen.every((e) => visibility.isEntryHidden(e));\n\t\t\t\tfor (const e of chosen) visibility.setEntryVisible(e, allHidden);\n\t\t\t} else {\n\t\t\t\tvisibility.setEntryVisible(entry, visibility.isEntryHidden(entry));\n\t\t\t}\n\t\t},\n\n\t\tselect(key, modifiers, searchQuery) {\n\t\t\tselection.select(key, modifiers, () => outliner.flatVisibleUuids(searchQuery));\n\t\t},\n\n\t\tonAnchorChange: (listener) => selection.onAnchorChange(listener),\n\n\t\tflatVisibleUuids(searchQuery) {\n\t\t\tconst result: string[] = [];\n\t\t\tfor (const [layerName, entries] of outliner.entryGroups(searchQuery)) {\n\t\t\t\tif (collapsed.has(layerName)) continue;\n\t\t\t\tfor (const entry of entries) result.push(entry.key);\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\n\t\tapplyTo() {\n\t\t\tvisibility.applyTo(outliner.objects());\n\t\t\tselection.clear();\n\t\t},\n\n\t\treset() {\n\t\t\tvisibility.reset();\n\t\t\tselection.clear();\n\t\t}\n\t};\n\n\treturn outliner;\n}\n"],"mappings":"A,6LAWA,MAAaI,kBAAkC,IAAI,IAAI,CAAC,OAAQ,QAAS,cAAe,SAAS,CAAC,EAElG,SAAgBC,EAAe,EAAiC,CAC/D,MACC,EAAEC,aAAkBN,EAAM,SAC1B,EAAEM,aAAkBN,EAAM,QAC1B,CAACI,EAAW,IAAIE,EAAO,UAAU,EAAE,CAErC,CAIA,SAAgBC,EAAgB,EAAsC,CACrE,OAAOC,EAAM,SAAS,OAAOH,CAAc,CAC5C,CAIA,SAAgBI,EAAW,EAAsB,CAChD,OACCC,EACE,QAAQ,qBAAsB,OAAO,CAAC,CACtC,QAAQ,OAAQ,EAAE,CAAC,CACnB,QAAQ,WAAY,KAAK,GAAKA,CAElC,CAEA,SAAgBC,EAAe,EAAgC,CAC9D,OACCL,EAAO,UAAU,MAAQA,EAAO,UAAU,UAAYA,EAAO,MAAQG,EAAWH,EAAO,IAAI,CAE7F,CAEA,SAAgBM,EAAa,EAAgC,CAC5D,OAAOH,EAAWH,EAAO,IAAI,CAC9B,CCVA,SAAgBO,EAAa,EAAuC,CACnE,IAAMC,EAAOR,EAAO,SACpB,GAAI,CAACQ,EAAM,OAAO,KAElB,GAAI,OAAOA,EAAK,aAAgB,UAAYA,EAAK,YAAa,OAAOA,EAAK,YAG1E,GAAI,OAAOA,EAAK,IAAO,UAAYA,EAAK,GAAI,OAAOA,EAAK,GAExD,IAAMC,EAAO,OAAOD,EAAK,MAAS,SAAWA,EAAK,KAAOR,EAAO,KAC1DU,EAAQ,OAAOF,EAAK,OAAU,SAAWA,EAAK,MAAQ,GAG5D,OAFIC,EAAa,QAAaC,KAAcD,IAErC,IACR,CAGA,SAAgBE,EAAe,EAAgC,CAC9D,OAAOJ,EAAaP,CAAM,GAAKA,EAAO,IACvC,CAOA,SAAgBY,EAAc,EAAkC,CAC/D,IAAMC,EAAUb,EAAO,UAAU,QAKjC,MAJI,CAAC,MAAM,QAAQa,CAAO,GAAKA,EAAQ,SAAW,EAC1C,CAACF,EAAeX,CAAM,CAAC,EAGxBa,EAAQ,KAAK,EAAQ,IACvB,OAAOC,EAAO,aAAgB,UAAYA,EAAO,YAAoBA,EAAO,YAC5EA,EAAO,KAAa,QAAaA,EAAO,SAAcA,EAAO,OAC1D,GAAGd,EAAO,QAAae,GAC9B,CACF,CChCA,SAASC,EAAU,EAA+C,CACjE,IAAMH,EAAUb,EAAO,UAAU,QACjC,OAAO,MAAM,QAAQa,CAAO,GAAKA,EAAQ,OAAS,EAAIA,EAAU,IACjE,CAEA,MAAMI,GAAW,EAAwB,IACxCjB,EAAO,UAAU,OAASA,EAAO,UAAU,UAAYkB,EAOxD,SAAgBC,EAAgB,EAAoB,EAAoC,CACvF,IAAMC,EAAwB,CAAC,EAEzBC,kBAAO,IAAI,IACXC,EAAa,GAAwB,CAC1C,IAAMC,EAAIF,EAAK,IAAIG,CAAG,GAAK,EAE3B,OADAH,EAAK,IAAIG,EAAKD,EAAI,CAAC,EACZA,IAAM,EAAIC,EAAM,GAAGA,EAAI,GAAGD,GAClC,EAEA,IAAK,IAAMvB,KAAUC,EAAgBC,CAAK,EAAG,CAC5C,IAAMW,EAAUG,EAAUhB,CAAM,EAChC,GAAI,CAACa,EAAS,CACb,IAAMW,EAAMb,EAAeX,CAAM,EACjCoB,EAAQ,KAAK,CACZ,SACA,YAAa,KACb,MACA,OAAQE,EAAUE,CAAG,EACrB,MAAOnB,EAAeL,CAAM,EAC5B,MAAOiB,EAAQjB,EAAQyB,CAAY,CACpC,CAAC,EACD,QACD,CAEA,IAAMC,EAAOd,EAAcZ,CAAM,EACjCa,EAAQ,SAAS,EAAQ,IAAM,CAC9B,IAAMW,EAAME,EAAKX,IAAM,GAAGf,EAAO,KAAK,GAAGe,IACzCK,EAAQ,KAAK,CACZ,SACA,YAAaL,EACb,MACA,OAAQO,EAAUE,CAAG,EACrB,MAAOV,EAAO,MAAQX,EAAWH,EAAO,IAAI,EAG5C,MAAOc,EAAO,OAASG,EAAQjB,EAAQyB,CAAY,CACpD,CAAC,CACF,CAAC,CACF,CAEA,OAAOL,CACR,CCxFA,MAAaO,EAAgB,UAS7B,SAAgBC,EAAa,EAA0D,CACtF,IAAMC,kBAAS,IAAI,IACnB,IAAK,IAAMC,KAAOC,EAAS,CAC1B,IAAMrB,EAAgBoB,EAAI,UAAU,OAASA,EAAI,UAAU,UAAA,UACvDE,EAASH,EAAO,IAAInB,CAAK,EACxBsB,IACJ,EAAS,CAAC,EACVH,EAAO,IAAInB,EAAOsB,CAAM,GAEzBA,EAAO,KAAKF,CAAG,CAChB,CACA,OAAOD,CACR,CASA,SAAgBI,EACf,EACA,EACgC,CAChC,GAAI,CAACC,EAAM,KAAK,EAAG,OAAOL,EAC1B,IAAMM,EAAID,EAAM,YAAY,EACtBE,kBAAW,IAAI,IACrB,IAAK,GAAM,CAACC,EAAWN,KAAYF,EAAQ,CAC1C,IAAMS,EAAWD,EAAU,YAAY,CAAC,CAAC,SAASF,CAAC,EAChDJ,EACAA,EAAQ,OAAQ,GAAQ1B,EAAeyB,CAAG,CAAC,CAAC,YAAY,CAAC,CAAC,SAASK,CAAC,CAAC,EACpEG,EAAS,OAAS,GAAGF,EAAS,IAAIC,EAAWC,CAAQ,CAC1D,CACA,OAAOF,CACR,CAGA,SAAgBG,EAAoB,EAAkD,CACrF,IAAMV,kBAAS,IAAI,IACnB,IAAK,IAAMW,KAASpB,EAAS,CAC5B,IAAIY,EAASH,EAAO,IAAIW,EAAM,KAAK,EAC9BR,IACJ,EAAS,CAAC,EACVH,EAAO,IAAIW,EAAM,MAAOR,CAAM,GAE/BA,EAAO,KAAKQ,CAAK,CAClB,CACA,OAAOX,CACR,CAGA,SAAgBY,EACf,EACA,EAC4B,CAC5B,GAAI,CAACP,EAAM,KAAK,EAAG,OAAOL,EAC1B,IAAMM,EAAID,EAAM,YAAY,EACtBE,kBAAW,IAAI,IACrB,IAAK,GAAM,CAACC,EAAWjB,KAAYS,EAAQ,CAC1C,IAAMS,EAAWD,EAAU,YAAY,CAAC,CAAC,SAASF,CAAC,EAChDf,EACAA,EAAQ,OAAQ,GAAUoB,EAAM,MAAM,YAAY,CAAC,CAAC,SAASL,CAAC,CAAC,EAC9DG,EAAS,OAAS,GAAGF,EAAS,IAAIC,EAAWC,CAAQ,CAC1D,CACA,OAAOF,CACR,CCrEA,SAAgBM,EAAqB,EAAwB,EAA2B,CACvF,IAAMC,EAAO3C,EACb,GAAI,CAAC2C,EAAK,QAAU,CAAC,MAAM,QAAQA,EAAK,UAAU,OAAO,EAAG,OAE5D,IAAMC,kBAAU,IAAI,IACpBhC,EAAc+B,CAAI,CAAC,CAAC,SAAS,EAAK,IAAM,CACnCE,EAAO,IAAIrB,CAAG,GAAGoB,EAAQ,IAAI7B,CAAC,CACnC,CAAC,EACDnB,EAAA,EAAiB+C,EAAMC,CAAO,CAC/B,CC2BA,SAAgBE,EAAsB,kBAAsB,IAAI,IAAwB,CAGvF,IAAMC,EAAa,GAClBnC,EAAcZ,CAAM,CAAC,CAAC,MAAO,GAAQ6C,EAAO,IAAIrB,CAAG,CAAC,EAE/CwB,EAAyB,CAC9B,SAEA,SAAUD,EAEV,WAAW,EAAQ,EAAS,CAC3B,EAAO,QAAUE,EACjBjD,EAAO,SAAU,GAAU,CAC1B,EAAM,QAAUiD,CACjB,CAAC,EACD,IAAK,IAAMzB,KAAOZ,EAAcZ,CAAM,EACjCiD,EAASJ,EAAO,OAAOrB,CAAG,EACzBqB,EAAO,IAAIrB,CAAG,EAGpBkB,EAAqB1C,EAAQ6C,CAAM,CACpC,EAEA,cAAgB,GACfL,EAAM,cAAgB,KAAOO,EAAUP,EAAM,MAAM,EAAIK,EAAO,IAAIL,EAAM,GAAG,EAE5E,gBAAgB,EAAO,EAAS,CAC3BA,EAAM,cAAgB,KACzBQ,EAAM,WAAWR,EAAM,OAAQS,CAAO,GAGnCA,EAASJ,EAAO,OAAOL,EAAM,GAAG,EAC/BK,EAAO,IAAIL,EAAM,GAAG,EACzBE,EAAqBF,EAAM,OAAQK,CAAM,EAErCI,GAAW,CAACT,EAAM,OAAO,SAC5BA,EAAM,OAAO,SAAU,GAAU,CAChC,EAAM,QAAU,EACjB,CAAC,EAEH,EAEA,cAAgB,GAAYT,EAAQ,OAAS,GAAKA,EAAQ,MAAO,GAAQiB,EAAM,SAASlB,CAAG,CAAC,EAE5F,eAAe,EAAS,CACvB,IAAMoB,EAAQnB,EAAQ,OAAQ,GAAQiB,EAAM,SAASlB,CAAG,CAAC,CAAC,CAAC,OAC3D,OAAOoB,EAAQ,GAAKA,EAAQnB,EAAQ,MACrC,EAEA,YAAY,EAAS,CAEpB,IAAMoB,EAAOH,EAAM,cAAcjB,CAAO,EACxC,IAAK,IAAMD,KAAOC,EAASiB,EAAM,WAAWlB,EAAKqB,CAAI,CACtD,EAEA,QAAQ,EAAS,CAChB,IAAK,IAAMnD,KAAU+B,EAGpBW,EAAqB1C,EAAQ6C,CAAM,EAI9BE,EAAU/C,CAAM,IACrB,EAAO,QAAU,GACjBA,EAAO,SAAU,GAAU,CAC1B,EAAM,QAAU,EACjB,CAAC,EAEH,EAEA,UAAa6C,EAAO,MAAM,CAC3B,EACA,OAAOG,CACR,CC1FA,SAAgBI,EAAqB,kBAAwB,IAAI,IAAuB,CACvF,IAAIC,EAAwB,KACtBC,kBAAkB,IAAI,IAEtBC,EAAa,GAAwB,CAC1C,EAASC,EACT,IAAK,IAAMC,KAAYH,EAAiBG,EAASD,CAAI,CACtD,EAEA,MAAO,CACN,WACA,IAAI,QAAS,CACZ,OAAOH,CACR,EAEA,WAAa,GAASK,EAAS,IAAIC,CAAI,EAEvC,OAAO,EAAM,EAAW,EAAW,CAClC,GAAIC,EAAU,UAAYP,EAAQ,CACjC,IAAMQ,EAAOC,EAAU,EACjBC,EAAIF,EAAK,QAAQR,CAAM,EACvBW,EAAIH,EAAK,QAAQF,CAAI,EAC3B,GAAII,IAAM,IAAMC,IAAM,GAAI,CACzB,GAAM,CAACC,EAAIC,GAAMH,EAAIC,EAAI,CAACD,EAAGC,CAAC,EAAI,CAACA,EAAGD,CAAC,EAElCH,EAAU,WAAWF,EAAS,MAAM,EACzC,IAAK,IAAI,EAAIO,EAAI,GAAKC,EAAI,IAAKR,EAAS,IAAIG,EAAK,EAAE,CACpD,CAGD,MAEID,EAAU,UACTF,EAAS,IAAIC,CAAI,EAAGD,EAAS,OAAOC,CAAI,EACvCD,EAAS,IAAIC,CAAI,GAEtBD,EAAS,MAAM,EACfA,EAAS,IAAIC,CAAI,GAElBJ,EAAUI,CAAI,CACf,EAEA,OAAQ,CACPD,EAAS,MAAM,EACfH,EAAU,IAAI,CACf,EAEA,eAAe,EAAU,CAExB,OADAD,EAAgB,IAAIG,CAAQ,MACfH,EAAgB,OAAOG,CAAQ,CAC7C,CACD,CACD,CCkBA,SAAgBU,EACf,EACA,EAAgC,CAAC,EACjB,CAChB,IAAMC,EAAYC,EAAQ,MAAM,2BAAa,IAAI,IAC3CC,EAAaxB,EAAsBuB,EAAQ,MAAM,MAAM,EACvDE,EAAYnB,EAAqBiB,EAAQ,MAAM,QAAQ,EAEvDG,EAA0B,CAC/B,aACA,YACA,YAEA,YAAevE,EAAgBC,CAAK,EAEpC,YAAeiB,EAAgBjB,EAAOyB,CAAa,EAEnD,aAAc,EAAc,KAC3Bc,EAAkBF,EAAoBpB,EAAgBjB,EAAOyB,CAAa,CAAC,EAAG8C,CAAW,EAE1F,aAAc,EAAc,KAC3BxC,EAAkBL,EAAa3B,EAAgBC,CAAK,CAAC,EAAGuE,CAAW,EAEpE,YAAc,GAAcL,EAAU,IAAI/B,CAAS,EAEnD,gBAAgB,EAAW,CACtB+B,EAAU,IAAI/B,CAAS,EAAG+B,EAAU,OAAO/B,CAAS,EACnD+B,EAAU,IAAI/B,CAAS,CAC7B,EAEA,aAAa,EAAQ,CACpB,GAAIkC,EAAU,WAAWvE,EAAO,IAAI,GAAKuE,EAAU,SAAS,KAAO,EAAG,CACrE,IAAMG,EAASF,EAAS,QAAQ,CAAC,CAAC,OAAQ,GAAMD,EAAU,WAAWI,EAAE,IAAI,CAAC,EAEtE5B,EAAY2B,EAAO,MAAO,GAAMJ,EAAW,SAASK,CAAC,CAAC,EAC5D,IAAK,IAAMA,KAAKD,EAAQJ,EAAW,WAAWK,EAAG5B,CAAS,CAC3D,MACCuB,EAAW,WAAWtE,EAAQsE,EAAW,SAAStE,CAAM,CAAC,CAE3D,EAEA,YAAY,EAAO,CAClB,GAAIuE,EAAU,WAAW/B,EAAM,GAAG,GAAK+B,EAAU,SAAS,KAAO,EAAG,CACnE,IAAMG,EAASF,EAAS,QAAQ,CAAC,CAAC,OAAQ,GAAMD,EAAU,WAAW,EAAE,GAAG,CAAC,EACrExB,EAAY2B,EAAO,MAAO,GAAMJ,EAAW,cAAc,CAAC,CAAC,EACjE,IAAK,IAAMM,KAAKF,EAAQJ,EAAW,gBAAgBM,EAAG7B,CAAS,CAChE,MACCuB,EAAW,gBAAgB9B,EAAO8B,EAAW,cAAc9B,CAAK,CAAC,CAEnE,EAEA,OAAO,EAAK,EAAW,EAAa,CACnC+B,EAAU,OAAO/C,EAAKoC,MAAiBY,EAAS,iBAAiBC,CAAW,CAAC,CAC9E,EAEA,eAAiB,GAAaF,EAAU,eAAed,CAAQ,EAE/D,iBAAiB,EAAa,CAC7B,IAAMoB,EAAmB,CAAC,EAC1B,IAAK,GAAM,CAACxC,EAAWjB,KAAYoD,EAAS,YAAYC,CAAW,EAClE,GAAI,GAAU,IAAIpC,CAAS,EAC3B,IAAK,IAAMG,KAASpB,EAASyD,EAAO,KAAKrC,EAAM,GAAG,EAEnD,OAAOqC,CACR,EAEA,SAAU,CACTP,EAAW,QAAQE,EAAS,QAAQ,CAAC,EACrCD,EAAU,MAAM,CACjB,EAEA,OAAQ,CACPD,EAAW,MAAM,EACjBC,EAAU,MAAM,CACjB,CACD,EAEA,OAAOC,CACR,C"}
|
package/dist/scene.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./merged-draw-
|
|
1
|
+
import{a as e}from"./merged-draw-CO5Ev9ia.js";import*as t from"three";const n=/* @__PURE__ */ new Set([`grid`,`floor`,`label-layer`,`measure`]);function r(e){return!(e instanceof t.Camera)&&!(e instanceof t.Light)&&!n.has(e.userData?.id)}function i(e){return e.children.filter(r)}function a(e){return e.replace(/^Line(Segments)?2$/,`Curve`).replace(`Mesh`,``).replace(`Object3D`,`Obj`)||e}function o(e){return e.userData?.name||e.userData?.fileName||e.name||a(e.type)}function s(e){return a(e.type)}function c(e){let t=e.userData;if(!t)return null;if(typeof t.trackingKey==`string`&&t.trackingKey)return t.trackingKey;if(typeof t.id==`string`&&t.id)return t.id;let n=typeof t.name==`string`?t.name:e.name,r=typeof t.layer==`string`?t.layer:``;return n?`name${r}${n}`:null}function l(e){return c(e)??e.uuid}function u(e){let t=e.userData?.members;return!Array.isArray(t)||t.length===0?[l(e)]:t.map((t,n)=>typeof t.trackingKey==`string`&&t.trackingKey?t.trackingKey:t.name?`name${t.layer}${t.name}`:`${e.uuid}${n}`)}function d(e){let t=e.userData?.members;return Array.isArray(t)&&t.length>0?t:null}const f=(e,t)=>e.userData?.layer||e.userData?.category||t;function p(e,t){let n=[],r=/* @__PURE__ */ new Map,s=e=>{let t=r.get(e)??0;return r.set(e,t+1),t===0?e:`${e}#${t}`};for(let r of i(e)){let e=d(r);if(!e){let e=l(r);n.push({object:r,memberIndex:null,key:e,rowKey:s(e),label:o(r),layer:f(r,t)});continue}let i=u(r);e.forEach((e,o)=>{let c=i[o]??`${r.uuid}:${o}`;n.push({object:r,memberIndex:o,key:c,rowKey:s(c),label:e.name||a(r.type),layer:e.layer||f(r,t)})})}return n}const m=`Default`;function h(e){let t=/* @__PURE__ */ new Map;for(let n of e){let e=n.userData?.layer||n.userData?.category||`Default`,r=t.get(e);r||(r=[],t.set(e,r)),r.push(n)}return t}function g(e,t){if(!t.trim())return e;let n=t.toLowerCase(),r=/* @__PURE__ */ new Map;for(let[t,i]of e){let e=t.toLowerCase().includes(n)?i:i.filter(e=>o(e).toLowerCase().includes(n));e.length>0&&r.set(t,e)}return r}function _(e){let t=/* @__PURE__ */ new Map;for(let n of e){let e=t.get(n.layer);e||(e=[],t.set(n.layer,e)),e.push(n)}return t}function v(e,t){if(!t.trim())return e;let n=t.toLowerCase(),r=/* @__PURE__ */ new Map;for(let[t,i]of e){let e=t.toLowerCase().includes(n)?i:i.filter(e=>e.label.toLowerCase().includes(n));e.length>0&&r.set(t,e)}return r}function y(t,n){let r=t;if(!r.isMesh||!Array.isArray(r.userData?.members))return;let i=/* @__PURE__ */ new Set;u(r).forEach((e,t)=>{n.has(e)&&i.add(t)}),e(r,i)}function b(e=/* @__PURE__ */ new Set){let t=t=>u(t).every(t=>e.has(t)),n={hidden:e,isHidden:t,setVisible(t,n){t.visible=n,t.traverse(e=>{e.visible=n});for(let r of u(t))n?e.delete(r):e.add(r);y(t,e)},isEntryHidden:n=>n.memberIndex===null?t(n.object):e.has(n.key),setEntryVisible(t,r){t.memberIndex===null?n.setVisible(t.object,r):(r?e.delete(t.key):e.add(t.key),y(t.object,e),r&&!t.object.visible&&t.object.traverse(e=>{e.visible=!0}))},isLayerHidden:e=>e.length>0&&e.every(e=>n.isHidden(e)),isLayerPartial(e){let t=e.filter(e=>n.isHidden(e)).length;return t>0&&t<e.length},toggleLayer(e){let t=n.isLayerHidden(e);for(let r of e)n.setVisible(r,t)},applyTo(n){for(let r of n)y(r,e),t(r)&&(r.visible=!1,r.traverse(e=>{e.visible=!1}))},reset:()=>e.clear()};return n}function x(e=/* @__PURE__ */ new Set){let t=null,n=/* @__PURE__ */ new Set,r=e=>{t=e;for(let t of n)t(e)};return{selected:e,get anchor(){return t},isSelected:t=>e.has(t),select(n,i,a){if(i.shiftKey&&t){let r=a(),o=r.indexOf(t),s=r.indexOf(n);if(o!==-1&&s!==-1){let[t,n]=o<s?[o,s]:[s,o];i.toggleKey||e.clear();for(let i=t;i<=n;i++)e.add(r[i])}}else i.toggleKey?e.has(n)?e.delete(n):e.add(n):(e.clear(),e.add(n)),r(n)},clear(){e.clear(),r(null)},onAnchorChange(e){return n.add(e),()=>n.delete(e)}}}function S(e,t={}){let n=t.sets?.collapsed??/* @__PURE__ */ new Set,r=b(t.sets?.hidden),a=x(t.sets?.selected),o={visibility:r,selection:a,collapsed:n,objects:()=>i(e),entries:()=>p(e,m),entryGroups:(t=``)=>v(_(p(e,m)),t),layerGroups:(t=``)=>g(h(i(e)),t),isCollapsed:e=>n.has(e),toggleCollapsed(e){n.has(e)?n.delete(e):n.add(e)},toggleObject(e){if(a.isSelected(e.uuid)&&a.selected.size>1){let e=o.objects().filter(e=>a.isSelected(e.uuid)),t=e.every(e=>r.isHidden(e));for(let n of e)r.setVisible(n,t)}else r.setVisible(e,r.isHidden(e))},toggleEntry(e){if(a.isSelected(e.key)&&a.selected.size>1){let e=o.entries().filter(e=>a.isSelected(e.key)),t=e.every(e=>r.isEntryHidden(e));for(let n of e)r.setEntryVisible(n,t)}else r.setEntryVisible(e,r.isEntryHidden(e))},select(e,t,n){a.select(e,t,()=>o.flatVisibleUuids(n))},onAnchorChange:e=>a.onAnchorChange(e),flatVisibleUuids(e){let t=[];for(let[r,i]of o.entryGroups(e))if(!n.has(r))for(let e of i)t.push(e.key);return t},applyTo(){r.applyTo(o.objects()),a.clear()},reset(){r.reset(),a.clear()}};return o}export{S as createSceneOutliner,u as getMemberKeys,o as getObjectLabel,l as getTrackingKey,s as getTypeLabel};
|
|
2
2
|
//# sourceMappingURL=scene.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/visualization",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.4",
|
|
4
4
|
"description": "Headless, extensible viewer core for Selva — parse, render and scene layers over Three.js",
|
|
5
5
|
"author": "VektorNode",
|
|
6
6
|
"license": "MIT",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"@types/three": "^0.186.0",
|
|
89
89
|
"eslint": "^10.13.0",
|
|
90
90
|
"eslint-config-prettier": "^10.1.8",
|
|
91
|
+
"fast-check": "^4.10.2",
|
|
91
92
|
"globals": "^17.13.0",
|
|
92
93
|
"prettier": "^3.9.10",
|
|
93
94
|
"three": "^0.186.1",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./rolldown-runtime-BocRIvOZ.cjs");let t=require("three");t=e.t(t,1);const n={VALIDATION_ERROR:`VALIDATION_ERROR`,INVALID_STATE:`INVALID_STATE`,ENVIRONMENT_ERROR:`ENVIRONMENT_ERROR`,INVALID_CONFIG:`INVALID_CONFIG`,ENCODING_ERROR:`ENCODING_ERROR`,UNKNOWN_ERROR:`UNKNOWN_ERROR`};var r=class extends Error{code;context;originalError;constructor(e,t=n.UNKNOWN_ERROR,r){super(e),this.name=`VisualizationError`,this.code=t,this.context=r?.context,this.originalError=r?.originalError,r?.originalError&&(this.cause=r.originalError)}},i=class{debug(){}info(){}warn(){}error(){}},a=class{debug(e,...t){console.debug(e,...t)}info(e,...t){console.info(e,...t)}warn(e,...t){console.warn(e,...t)}error(e,...t){console.error(e,...t)}};let o=new i;function s(){return o}function c(e){if(e===null){o=new i;return}let t=[`debug`,`info`,`warn`,`error`].filter(t=>typeof e[t]!=`function`);if(t.length>0)throw new r(`Logger is missing required method(s): ${t.join(`, `)}. A logger must implement debug, info, warn and error.`,n.INVALID_CONFIG,{context:{missingMethods:t}});o=e}function l(){c(new a)}const u={studio:{toneMapping:t.ACESFilmicToneMapping,toneMappingExposure:1,envMapIntensity:1,environmentIntensity:.85,hemisphereIntensity:.5,ambientIntensity:.25,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:1.6},technical:{toneMapping:t.NeutralToneMapping,toneMappingExposure:1,envMapIntensity:.55,environmentIntensity:.6,hemisphereIntensity:.25,ambientIntensity:.15,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:2.2},showcase:{toneMapping:t.ACESFilmicToneMapping,toneMappingExposure:1.15,envMapIntensity:1.4,environmentIntensity:1,hemisphereIntensity:.2,ambientIntensity:.1,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:2.6},arctic:{toneMapping:t.NeutralToneMapping,toneMappingExposure:1.05,envMapIntensity:.85,environmentIntensity:1.1,hemisphereIntensity:.3,ambientIntensity:.15,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:2,materialOverride:{color:15659509,metalness:0,roughness:.75}},xray:{toneMapping:t.NeutralToneMapping,toneMappingExposure:1.3,envMapIntensity:.4,environmentIntensity:.8,hemisphereIntensity:.6,ambientIntensity:.6,cullBackfaces:!1,ambientOcclusion:!1,sunlightIntensity:.6,materialOverride:{color:10470632,metalness:0,roughness:.9,opacity:.28,depthWrite:!1,side:t.DoubleSide}},lineart:{toneMapping:t.NeutralToneMapping,toneMappingExposure:1,envMapIntensity:1,environmentIntensity:1,hemisphereIntensity:.6,ambientIntensity:3.2,cullBackfaces:!1,ambientOcclusion:!1,sunlightIntensity:0,requiresEdges:!0,materialOverride:{color:16251130,metalness:0,roughness:1}},wireframe:{toneMapping:t.NeutralToneMapping,toneMappingExposure:1,envMapIntensity:1,environmentIntensity:1,hemisphereIntensity:.4,ambientIntensity:2,cullBackfaces:!1,ambientOcclusion:!1,sunlightIntensity:0,materialOverride:{color:2830648,metalness:0,roughness:1,wireframe:!0,side:t.DoubleSide}}};function d(e,t){return e+(1-e)*Math.min(1,Math.max(0,t))}function f(e){let t=u[e];return{envMapIntensity:t.envMapIntensity,cullBackfaces:t.cullBackfaces}}function p(e){if(!e||typeof e!=`string`)return s().warn(`Invalid color input: ${e}, using white`),new t.Color(16777215);let n=e.trim();if(/^#?[0-9A-Fa-f]{6}$/.test(n))try{let e=n.startsWith(`#`)?n:`#${n}`;return new t.Color(e)}catch{return s().warn(`Invalid hex color: ${e}, using white`),new t.Color(16777215)}if(n.includes(`,`)){let e=n.split(`,`).map(e=>parseInt(e.trim(),10));if(e.length===3&&e.every(e=>!isNaN(e)&&e>=0&&e<=255))return new t.Color(e[0]/255,e[1]/255,e[2]/255)}let r=n.toLowerCase();return r in t.Color.NAMES?new t.Color(t.Color.NAMES[r]):(s().warn(`Invalid color string: ${e}, using white`),new t.Color(16777215))}function m(e,t,n=`z`){e.forEach(e=>{e.position[n]-=t})}function h(e){let n=new t.Box3;return e.length===0||e.forEach(e=>{e.updateMatrixWorld(!0);let r=new t.Box3().setFromObject(e);n.union(r)}),n}const g=/* @__PURE__ */ new Set;function _(e){return e?!g.has(e):!1}const v=/* @__PURE__ */ new Set;let y=1;function b(e){let t=Math.max(1,e);if(t!==y){y=t;for(let e of v)e(t)}}function x(e){return v.add(e),e(y),()=>v.delete(e)}function S(e){if(_(e)){for(let[n,r]of Object.entries(e))n!==`envMap`&&r instanceof t.Texture&&r.dispose();e.dispose()}}function C(e){e&&(Array.isArray(e)?e.forEach(S):S(e))}function w(e,t={}){let{materials:n=!0}=t;e.traverse(e=>{let t=e;(t.geometry||t.material)&&(t.geometry&&t.geometry?.dispose(),n&&C(t.material))})}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"g",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"h",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return`selvaEnvMapIntensity`}}),Object.defineProperty(exports,"m",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return`technical`}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return d}});
|
|
2
|
-
//# sourceMappingURL=gpu-dispose-D3uBOjqr.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gpu-dispose-D3uBOjqr.cjs","names":["THREE","THREE","THREE","require_rolldown_runtime","three","ErrorCodes","VisualizationError","message","code","options","NoOpLogger","ConsoleLogger","args","internalLogger","getLogger","setLogger","logger","missing","method","enableDebugLogging","LOOK_PRESETS","lookEnvMapIntensity","lookValue","metalness","materialAppearanceForLook","preset","look","parseColor","colorString","trimmed","hex","rgb","c","n","named","applyOffset","meshes","axis","offset","computeCombinedBoundingBox","combinedBoundingBox","mesh","bbox","protectedMaterials","canDisposeMaterial","material","observers","maxAnisotropy","publishMaxAnisotropy","next","value","observe","observeMaxAnisotropy","disposeMaterial","key","disposeMaterials","disposeObjectTree","root","renderable","object","materials"],"sources":["../src/shared/errors.ts","../src/shared/logger.ts","../src/shared/looks.ts","../src/shared/geometry.ts","../src/shared/gpu-ownership.ts","../src/shared/gpu-capabilities.ts","../src/shared/gpu-dispose.ts"],"sourcesContent":["/**\n * Errors for the visualization package. Replaces `@selvajs/compute`'s `RhinoComputeError`, which\n * mis-named failures on paths (e.g. the plugin WebSocket) that never touch Rhino.Compute. `code`\n * values match compute's so existing catch-sites keep working.\n */\n\nexport const ErrorCodes = {\n\t/** Structural check failed: bad magic bytes, out-of-window index, malformed metadata. */\n\tVALIDATION_ERROR: 'VALIDATION_ERROR',\n\tINVALID_STATE: 'INVALID_STATE',\n\t/** No `DecompressionStream`, no WebGL context, etc. */\n\tENVIRONMENT_ERROR: 'ENVIRONMENT_ERROR',\n\tINVALID_CONFIG: 'INVALID_CONFIG',\n\t/** Base64 input could not be decoded. */\n\tENCODING_ERROR: 'ENCODING_ERROR',\n\tUNKNOWN_ERROR: 'UNKNOWN_ERROR'\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\nexport class VisualizationError extends Error {\n\tpublic readonly code: ErrorCode;\n\tpublic readonly context?: Record<string, unknown>;\n\tpublic readonly originalError?: Error;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: ErrorCode = ErrorCodes.UNKNOWN_ERROR,\n\t\toptions?: { context?: Record<string, unknown>; originalError?: Error }\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'VisualizationError';\n\t\tthis.code = code;\n\t\tthis.context = options?.context;\n\t\tthis.originalError = options?.originalError;\n\t\tif (options?.originalError) {\n\t\t\t(this as { cause?: unknown }).cause = options.originalError;\n\t\t}\n\t}\n}\n","/**\n * Logging facility for the visualization package. Deliberately local rather than imported from\n * `@selvajs/compute` (logging isn't a compute concern). Mirrors compute's logger shape so a host\n * wanting one sink for both can call `setLogger(computeLogger.getLogger())`.\n */\n\nimport { VisualizationError, ErrorCodes } from './errors.js';\n\nexport interface Logger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nclass NoOpLogger implements Logger {\n\tdebug(): void {}\n\tinfo(): void {}\n\twarn(): void {}\n\terror(): void {}\n}\n\nclass ConsoleLogger implements Logger {\n\tdebug(message: string, ...args: unknown[]): void {\n\t\t// eslint-disable-next-line no-console -- this class exists to be the console sink\n\t\tconsole.debug(message, ...args);\n\t}\n\n\tinfo(message: string, ...args: unknown[]): void {\n\t\tconsole.info(message, ...args);\n\t}\n\n\twarn(message: string, ...args: unknown[]): void {\n\t\tconsole.warn(message, ...args);\n\t}\n\n\terror(message: string, ...args: unknown[]): void {\n\t\tconsole.error(message, ...args);\n\t}\n}\n\n// Defaults to no-op: a library that writes to the console uninvited is a nuisance in a host with\n// its own logging. Opt in with setLogger or enableDebugLogging.\nlet internalLogger: Logger = new NoOpLogger();\n\n// Call per-use rather than caching — the sink is settable at any time.\nexport function getLogger(): Logger {\n\treturn internalLogger;\n}\n\n// Validates logger shape up front: failing here beats a confusing \"getLogger().debug is not a\n// function\" at some later, unrelated call site.\nexport function setLogger(logger: Logger | Console | null): void {\n\tif (logger === null) {\n\t\tinternalLogger = new NoOpLogger();\n\t\treturn;\n\t}\n\n\tconst missing = (['debug', 'info', 'warn', 'error'] as const).filter(\n\t\t(method) => typeof (logger as unknown as Record<string, unknown>)[method] !== 'function'\n\t);\n\tif (missing.length > 0) {\n\t\tthrow new VisualizationError(\n\t\t\t`Logger is missing required method(s): ${missing.join(', ')}. A logger must implement debug, info, warn and error.`,\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { missingMethods: missing } }\n\t\t);\n\t}\n\n\tinternalLogger = logger as Logger;\n}\n\nexport function enableDebugLogging(): void {\n\tsetLogger(new ConsoleLogger());\n}\n","import * as THREE from 'three';\n\nimport type { Look, LookPreset, MaterialAppearanceOptions } from './types.js';\n\n/** The look applied when the caller passes no `look` option. */\nexport const DEFAULT_LOOK: Look = 'technical';\n\n/**\n * Single source of truth for both `applyDefaults` (construction) and `setLook` (runtime), so the two\n * can't drift.\n *\n * Every shaded look pairs a key light with contact shading (GTAO), because image-based lighting on\n * its own lights all faces of a box nearly equally — the result is a white silhouette with no\n * readable form. GTAO is a full-screen pass and does cost frames; `xray` skips it since there are no\n * opaque contacts left to shade.\n */\nexport const LOOK_PRESETS: Record<Look, LookPreset> = {\n\t// Fills shadows in (vs. showcase, which lets them fall off), but still key-lit: ACES plus a\n\t// moderate sun, with fill high enough that shadowed faces keep detail.\n\tstudio: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 0.85,\n\t\themisphereIntensity: 0.5,\n\t\tambientIntensity: 0.25,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 1.6\n\t},\n\t// The sun carries this look and the ambient/IBL only lift shadows off black. Fill was the thing\n\t// making it read as flat white paper: every source except the sun is orientation-independent, so\n\t// the more of them you stack the less the three faces of a corner differ.\n\ttechnical: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 0.55,\n\t\tenvironmentIntensity: 0.6,\n\t\themisphereIntensity: 0.25,\n\t\tambientIntensity: 0.15,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 2.2\n\t},\n\t// Fill pulled furthest DOWN and the sun pushed hardest, so light-to-shadow falloff is steepest —\n\t// a dramatic hero shot, unlike `studio` which fills shadows back in.\n\tshowcase: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1.15,\n\t\tenvMapIntensity: 1.4,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.2,\n\t\tambientIntensity: 0.1,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 2.6\n\t},\n\t// Every mesh forced to one near-white clay, so form and shadow carry the image instead of the\n\t// model's own colours. AO on: with all albedo differences gone, contact shading is the only thing\n\t// left separating touching parts — without it the model reads as one white blob.\n\tarctic: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1.05,\n\t\tenvMapIntensity: 0.85,\n\t\tenvironmentIntensity: 1.1,\n\t\themisphereIntensity: 0.3,\n\t\tambientIntensity: 0.15,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 2.0,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0xeef1f5,\n\t\t\tmetalness: 0,\n\t\t\troughness: 0.75\n\t\t}\n\t},\n\t// See-through blue-white with depth writes off, so interior geometry shows through the shell.\n\t// Exposure pushed up because stacked transparent layers darken fast; the sun is dialled back\n\t// because a hard highlight on a transparent shell hides what is behind it.\n\txray: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1.3,\n\t\tenvMapIntensity: 0.4,\n\t\tenvironmentIntensity: 0.8,\n\t\themisphereIntensity: 0.6,\n\t\tambientIntensity: 0.6,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false,\n\t\tsunlightIntensity: 0.6,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0x9fc4e8,\n\t\t\tmetalness: 0,\n\t\t\troughness: 0.9,\n\t\t\topacity: 0.28,\n\t\t\tdepthWrite: false,\n\t\t\tside: THREE.DoubleSide\n\t\t}\n\t},\n\t// The architectural line drawing: feature edges over flat white faces. The faces are the whole\n\t// point even though you never really see them — they still write depth, so a near member hides\n\t// the ones behind it. True wireframe can't do that, and on a dense frame (thousands of members,\n\t// every edge of every one drawn at once) it collapses into a black mass.\n\t//\n\t// Unlit on purpose: any directional shading would compete with the lines for the eye. Ambient\n\t// alone lands every face on the same flat white, so the only contrast in the image is the edges.\n\t// AO is off for the same reason — a grey contact smear under a line drawing just muddies it.\n\tlineart: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\t// The faces are MeshPhysicalMaterial, so they take their brightness from lighting, not from\n\t\t// `color`: zero out the lights and they render black however white the override is. Hemisphere\n\t\t// and ambient carry it rather than IBL, because `scene.environment` only exists if the host\n\t\t// loaded an HDR — leaning on IBL would render this look black in any viewer without one. Both\n\t\t// are orientation-independent, so every face lands on the same flat white and the edges stay\n\t\t// the only contrast in the image. Ambient is weighted over hemisphere because hemisphere's\n\t\t// ground colour is a warm brown by default: leaning on it tints every downward face grey and\n\t\t// the drawing stops reading as one flat white.\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.6,\n\t\tambientIntensity: 3.2,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false,\n\t\tsunlightIntensity: 0,\n\t\trequiresEdges: true,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0xf7f8fa,\n\t\t\tmetalness: 0,\n\t\t\troughness: 1\n\t\t}\n\t},\n\t// Triangle edges only — the tessellation itself, diagonals and all. That makes it a mesh-\n\t// inspection tool, not a drawing: for a readable line drawing use `lineart`, which draws\n\t// feature edges and lets near faces occlude far ones. Nothing here is a lit surface:\n\t// the sun is off and fill carries the image, because a directional light on a line renders it\n\t// black wherever the line runs away from the key. AO is off for the same reason — a full-screen\n\t// contact pass has no contacts to find between wires, and it only costs frames.\n\twireframe: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\t// Lit for the same reason as lineart (see there): PBR lines are black unlit, and hemisphere\n\t\t// plus ambient work with no HDR loaded. Dimmer than lineart because these lines are dark\n\t\t// on white, not white on dark.\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.4,\n\t\tambientIntensity: 2.0,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false,\n\t\tsunlightIntensity: 0,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0x2b3138,\n\t\t\tmetalness: 0,\n\t\t\troughness: 1,\n\t\t\twireframe: true,\n\t\t\tside: THREE.DoubleSide\n\t\t}\n\t}\n};\n\n/**\n * `userData` key holding a material's own `envMapIntensity`, set by the parser when the wire\n * material carries one. `setLook` keeps it instead of the look's value, except in looks with a\n * `materialOverride`: those restyle every mesh alike, so the look's value wins there.\n */\nexport const OWN_ENV_MAP_INTENSITY = 'selvaEnvMapIntensity';\n\n/**\n * A look dims `envMapIntensity` to turn down fill, but three scales a material's diffuse and\n * specular IBL together. A metal has no diffuse, so the same dimming would only take away its\n * reflection, which is all a metal shows. Blending toward 1 by metalness keeps a dielectric's fill\n * on the look and a metal's reflection the same in every look.\n */\nexport function lookEnvMapIntensity(lookValue: number, metalness: number): number {\n\treturn lookValue + (1 - lookValue) * Math.min(1, Math.max(0, metalness));\n}\n\n/** Baked at parse time (not toggleable at runtime). */\nexport function materialAppearanceForLook(look: Look): MaterialAppearanceOptions {\n\tconst preset = LOOK_PRESETS[look];\n\treturn {\n\t\tenvMapIntensity: preset.envMapIntensity,\n\t\tcullBackfaces: preset.cullBackfaces\n\t};\n}\n","import * as THREE from 'three';\n\nimport { getLogger } from './logger.js';\n\n// parse/ needs these and must never import upward from render/, so they live in shared/ instead\n// of render's three-helpers.\n\nexport function parseColor(colorString: string): THREE.Color {\n\tif (!colorString || typeof colorString !== 'string') {\n\t\tgetLogger().warn(`Invalid color input: ${colorString}, using white`);\n\t\treturn new THREE.Color(0xffffff);\n\t}\n\n\tconst trimmed = colorString.trim();\n\n\tif (/^#?[0-9A-Fa-f]{6}$/.test(trimmed)) {\n\t\ttry {\n\t\t\tconst hex = trimmed.startsWith('#') ? trimmed : `#${trimmed}`;\n\t\t\treturn new THREE.Color(hex);\n\t\t} catch {\n\t\t\tgetLogger().warn(`Invalid hex color: ${colorString}, using white`);\n\t\t\treturn new THREE.Color(0xffffff);\n\t\t}\n\t}\n\n\tif (trimmed.includes(',')) {\n\t\tconst rgb = trimmed.split(',').map((c) => parseInt(c.trim(), 10));\n\t\tif (rgb.length === 3 && rgb.every((n) => !isNaN(n) && n >= 0 && n <= 255)) {\n\t\t\treturn new THREE.Color(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255);\n\t\t}\n\t}\n\n\t// `new THREE.Color(name)` never throws on unknown names (it logs its own warning and leaves the\n\t// color white), so validate against three's CSS name table instead.\n\tconst named = trimmed.toLowerCase();\n\tif (named in THREE.Color.NAMES) {\n\t\treturn new THREE.Color(THREE.Color.NAMES[named as keyof typeof THREE.Color.NAMES]);\n\t}\n\n\tgetLogger().warn(`Invalid color string: ${colorString}, using white`);\n\treturn new THREE.Color(0xffffff);\n}\n\n/** Defaults to `z` (Rhino's up axis); pass an explicit axis when the scene uses a different `sceneUp`. */\nexport function applyOffset(\n\tmeshes: THREE.Object3D[],\n\toffset: number,\n\taxis: 'x' | 'y' | 'z' = 'z'\n): void {\n\tmeshes.forEach((mesh) => {\n\t\tmesh.position[axis] -= offset;\n\t});\n}\n\nexport function computeCombinedBoundingBox(meshes: THREE.Object3D[]): THREE.Box3 {\n\tconst combinedBoundingBox = new THREE.Box3();\n\tif (meshes.length === 0) return combinedBoundingBox;\n\tmeshes.forEach((mesh) => {\n\t\tmesh.updateMatrixWorld(true);\n\t\tconst bbox = new THREE.Box3().setFromObject(mesh);\n\t\tcombinedBoundingBox.union(bbox);\n\t});\n\treturn combinedBoundingBox;\n}\n","import type * as THREE from 'three';\n\n// ============================================================================\n// GPU resource ownership — the single rule every disposal path obeys\n// ============================================================================\n\n/**\n * A GPU resource has exactly one owner: a **module singleton** (nobody ever frees it) or the\n * **scene** (freed on teardown). Every disposal path asks {@link canDisposeMaterial} and disposes\n * only if true. Geometries and textures are always scene-owned, so they have no claim to check.\n */\n\n// Module-scope singletons from `render/three-materials.ts`, shared across meshes and solves —\n// disposing one would free textures still in use and force a recompile. Held here (not imported\n// there) to avoid an import cycle.\nconst protectedMaterials = new Set<THREE.Material>();\n\n/** Register materials as never-disposable. Called once by `render/three-materials.ts`. */\nexport function protectMaterials(materials: Iterable<THREE.Material>): void {\n\tfor (const material of materials) protectedMaterials.add(material);\n}\n\n/** True when `material` is scene-owned (not one of the protected singletons). */\nexport function canDisposeMaterial(material: THREE.Material | undefined): boolean {\n\tif (!material) return false;\n\treturn !protectedMaterials.has(material);\n}\n","// ============================================================================\n// GPU capabilities — published by the renderer, read by whoever needs them\n// ============================================================================\n\n// Lets a renderer publish GPU capabilities (max anisotropy) without `render/` and `parse/` importing\n// each other: the renderer publishes on init, interested parties subscribe.\n\ntype AnisotropyObserver = (value: number) => void;\n\nconst observers = new Set<AnisotropyObserver>();\n\n// Module load order between layers isn't guaranteed, so a subscriber arriving after init still\n// needs the current value rather than waiting for a second renderer to publish.\nlet maxAnisotropy = 1;\n\nexport function publishMaxAnisotropy(value: number): void {\n\tconst next = Math.max(1, value);\n\tif (next === maxAnisotropy) return;\n\tmaxAnisotropy = next;\n\tfor (const observe of observers) observe(next);\n}\n\n/** Fires immediately with the current value (1 until a renderer has reported), then on every change. */\nexport function observeMaxAnisotropy(observe: AnisotropyObserver): () => void {\n\tobservers.add(observe);\n\tobserve(maxAnisotropy);\n\treturn () => observers.delete(observe);\n}\n","import * as THREE from 'three';\n\nimport { canDisposeMaterial } from './gpu-ownership.js';\n\n// ============================================================================\n// The disposal walkers — every teardown path in the package goes through these\n// ============================================================================\n\n/** Frees a material and its textures, respecting ownership (`./gpu-ownership.ts`). */\nexport function disposeMaterial(material: THREE.Material): void {\n\tif (!canDisposeMaterial(material)) return;\n\n\tfor (const [key, value] of Object.entries(material)) {\n\t\t// An envMap is the scene's, shared by every metal: freeing it here would blank them all.\n\t\tif (key === 'envMap') continue;\n\t\tif (value instanceof THREE.Texture) {\n\t\t\tvalue.dispose();\n\t\t}\n\t}\n\tmaterial.dispose();\n}\n\nfunction disposeMaterials(material: THREE.Material | THREE.Material[] | undefined): void {\n\tif (!material) return;\n\tif (Array.isArray(material)) material.forEach(disposeMaterial);\n\telse disposeMaterial(material);\n}\n\nexport interface DisposeOptions {\n\t/** Free materials and their textures too. Default true; the solve memo passes false. */\n\tmaterials?: boolean;\n\t/** Called for each geometry before disposal, so a subsystem can free derived resources keyed on it (e.g. edge caches). */\n\tonGeometry?: (geometry: THREE.BufferGeometry) => void;\n}\n\n/**\n * Frees the GPU resources of an object subtree, respecting ownership. **The only traversal that\n * should dispose scene content** — a second disposing `traverse` risks missing an ownership guard.\n */\nexport function disposeObjectTree(root: THREE.Object3D, options: DisposeOptions = {}): void {\n\tconst { materials = true } = options;\n\n\troot.traverse((object) => {\n\t\tconst renderable = object as Partial<THREE.Mesh> & THREE.Object3D;\n\t\tif (!renderable.geometry && !renderable.material) return;\n\n\t\tif (renderable.geometry) {\n\t\t\trenderable.geometry?.dispose();\n\t\t}\n\n\t\tif (materials) disposeMaterials(renderable.material);\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import*as e from"three";const t={VALIDATION_ERROR:`VALIDATION_ERROR`,INVALID_STATE:`INVALID_STATE`,ENVIRONMENT_ERROR:`ENVIRONMENT_ERROR`,INVALID_CONFIG:`INVALID_CONFIG`,ENCODING_ERROR:`ENCODING_ERROR`,UNKNOWN_ERROR:`UNKNOWN_ERROR`};var n=class extends Error{code;context;originalError;constructor(e,n=t.UNKNOWN_ERROR,r){super(e),this.name=`VisualizationError`,this.code=n,this.context=r?.context,this.originalError=r?.originalError,r?.originalError&&(this.cause=r.originalError)}},r=class{debug(){}info(){}warn(){}error(){}},i=class{debug(e,...t){console.debug(e,...t)}info(e,...t){console.info(e,...t)}warn(e,...t){console.warn(e,...t)}error(e,...t){console.error(e,...t)}};let a=new r;function o(){return a}function s(e){if(e===null){a=new r;return}let i=[`debug`,`info`,`warn`,`error`].filter(t=>typeof e[t]!=`function`);if(i.length>0)throw new n(`Logger is missing required method(s): ${i.join(`, `)}. A logger must implement debug, info, warn and error.`,t.INVALID_CONFIG,{context:{missingMethods:i}});a=e}function c(){s(new i)}const l=`technical`,u={studio:{toneMapping:e.ACESFilmicToneMapping,toneMappingExposure:1,envMapIntensity:1,environmentIntensity:.85,hemisphereIntensity:.5,ambientIntensity:.25,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:1.6},technical:{toneMapping:e.NeutralToneMapping,toneMappingExposure:1,envMapIntensity:.55,environmentIntensity:.6,hemisphereIntensity:.25,ambientIntensity:.15,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:2.2},showcase:{toneMapping:e.ACESFilmicToneMapping,toneMappingExposure:1.15,envMapIntensity:1.4,environmentIntensity:1,hemisphereIntensity:.2,ambientIntensity:.1,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:2.6},arctic:{toneMapping:e.NeutralToneMapping,toneMappingExposure:1.05,envMapIntensity:.85,environmentIntensity:1.1,hemisphereIntensity:.3,ambientIntensity:.15,cullBackfaces:!1,ambientOcclusion:!0,sunlightIntensity:2,materialOverride:{color:15659509,metalness:0,roughness:.75}},xray:{toneMapping:e.NeutralToneMapping,toneMappingExposure:1.3,envMapIntensity:.4,environmentIntensity:.8,hemisphereIntensity:.6,ambientIntensity:.6,cullBackfaces:!1,ambientOcclusion:!1,sunlightIntensity:.6,materialOverride:{color:10470632,metalness:0,roughness:.9,opacity:.28,depthWrite:!1,side:e.DoubleSide}},lineart:{toneMapping:e.NeutralToneMapping,toneMappingExposure:1,envMapIntensity:1,environmentIntensity:1,hemisphereIntensity:.6,ambientIntensity:3.2,cullBackfaces:!1,ambientOcclusion:!1,sunlightIntensity:0,requiresEdges:!0,materialOverride:{color:16251130,metalness:0,roughness:1}},wireframe:{toneMapping:e.NeutralToneMapping,toneMappingExposure:1,envMapIntensity:1,environmentIntensity:1,hemisphereIntensity:.4,ambientIntensity:2,cullBackfaces:!1,ambientOcclusion:!1,sunlightIntensity:0,materialOverride:{color:2830648,metalness:0,roughness:1,wireframe:!0,side:e.DoubleSide}}},d=`selvaEnvMapIntensity`;function f(e,t){return e+(1-e)*Math.min(1,Math.max(0,t))}function p(e){let t=u[e];return{envMapIntensity:t.envMapIntensity,cullBackfaces:t.cullBackfaces}}function m(t){if(!t||typeof t!=`string`)return o().warn(`Invalid color input: ${t}, using white`),new e.Color(16777215);let n=t.trim();if(/^#?[0-9A-Fa-f]{6}$/.test(n))try{let t=n.startsWith(`#`)?n:`#${n}`;return new e.Color(t)}catch{return o().warn(`Invalid hex color: ${t}, using white`),new e.Color(16777215)}if(n.includes(`,`)){let t=n.split(`,`).map(e=>parseInt(e.trim(),10));if(t.length===3&&t.every(e=>!isNaN(e)&&e>=0&&e<=255))return new e.Color(t[0]/255,t[1]/255,t[2]/255)}let r=n.toLowerCase();return r in e.Color.NAMES?new e.Color(e.Color.NAMES[r]):(o().warn(`Invalid color string: ${t}, using white`),new e.Color(16777215))}function h(e,t,n=`z`){e.forEach(e=>{e.position[n]-=t})}function g(t){let n=new e.Box3;return t.length===0||t.forEach(t=>{t.updateMatrixWorld(!0);let r=new e.Box3().setFromObject(t);n.union(r)}),n}const _=/* @__PURE__ */ new Set;function v(e){return e?!_.has(e):!1}const y=/* @__PURE__ */ new Set;let b=1;function x(e){let t=Math.max(1,e);if(t!==b){b=t;for(let e of y)e(t)}}function S(e){return y.add(e),e(b),()=>y.delete(e)}function C(t){if(v(t)){for(let[n,r]of Object.entries(t))n!==`envMap`&&r instanceof e.Texture&&r.dispose();t.dispose()}}function w(e){e&&(Array.isArray(e)?e.forEach(C):C(e))}function T(e,t={}){let{materials:n=!0}=t;e.traverse(e=>{let t=e;(t.geometry||t.material)&&(t.geometry&&t.geometry?.dispose(),n&&w(t.material))})}export{g as a,u as c,p as d,c as f,n as g,t as h,h as i,d as l,s as m,S as n,m as o,o as p,x as r,l as s,T as t,f as u};
|
|
2
|
-
//# sourceMappingURL=gpu-dispose-Dr69ZlDk.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gpu-dispose-Dr69ZlDk.js","names":["THREE","ErrorCodes","VisualizationError","message","code","options","NoOpLogger","ConsoleLogger","args","internalLogger","getLogger","setLogger","logger","missing","method","enableDebugLogging","DEFAULT_LOOK","LOOK_PRESETS","OWN_ENV_MAP_INTENSITY","lookEnvMapIntensity","lookValue","metalness","materialAppearanceForLook","preset","look","parseColor","colorString","trimmed","hex","rgb","c","n","named","applyOffset","meshes","axis","offset","computeCombinedBoundingBox","combinedBoundingBox","mesh","bbox","protectedMaterials","canDisposeMaterial","material","observers","maxAnisotropy","publishMaxAnisotropy","next","value","observe","observeMaxAnisotropy","disposeMaterial","key","disposeMaterials","disposeObjectTree","root","renderable","object","materials"],"sources":["../src/shared/errors.ts","../src/shared/logger.ts","../src/shared/looks.ts","../src/shared/geometry.ts","../src/shared/gpu-ownership.ts","../src/shared/gpu-capabilities.ts","../src/shared/gpu-dispose.ts"],"sourcesContent":["/**\n * Errors for the visualization package. Replaces `@selvajs/compute`'s `RhinoComputeError`, which\n * mis-named failures on paths (e.g. the plugin WebSocket) that never touch Rhino.Compute. `code`\n * values match compute's so existing catch-sites keep working.\n */\n\nexport const ErrorCodes = {\n\t/** Structural check failed: bad magic bytes, out-of-window index, malformed metadata. */\n\tVALIDATION_ERROR: 'VALIDATION_ERROR',\n\tINVALID_STATE: 'INVALID_STATE',\n\t/** No `DecompressionStream`, no WebGL context, etc. */\n\tENVIRONMENT_ERROR: 'ENVIRONMENT_ERROR',\n\tINVALID_CONFIG: 'INVALID_CONFIG',\n\t/** Base64 input could not be decoded. */\n\tENCODING_ERROR: 'ENCODING_ERROR',\n\tUNKNOWN_ERROR: 'UNKNOWN_ERROR'\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\nexport class VisualizationError extends Error {\n\tpublic readonly code: ErrorCode;\n\tpublic readonly context?: Record<string, unknown>;\n\tpublic readonly originalError?: Error;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: ErrorCode = ErrorCodes.UNKNOWN_ERROR,\n\t\toptions?: { context?: Record<string, unknown>; originalError?: Error }\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'VisualizationError';\n\t\tthis.code = code;\n\t\tthis.context = options?.context;\n\t\tthis.originalError = options?.originalError;\n\t\tif (options?.originalError) {\n\t\t\t(this as { cause?: unknown }).cause = options.originalError;\n\t\t}\n\t}\n}\n","/**\n * Logging facility for the visualization package. Deliberately local rather than imported from\n * `@selvajs/compute` (logging isn't a compute concern). Mirrors compute's logger shape so a host\n * wanting one sink for both can call `setLogger(computeLogger.getLogger())`.\n */\n\nimport { VisualizationError, ErrorCodes } from './errors.js';\n\nexport interface Logger {\n\tdebug(message: string, ...args: unknown[]): void;\n\tinfo(message: string, ...args: unknown[]): void;\n\twarn(message: string, ...args: unknown[]): void;\n\terror(message: string, ...args: unknown[]): void;\n}\n\nclass NoOpLogger implements Logger {\n\tdebug(): void {}\n\tinfo(): void {}\n\twarn(): void {}\n\terror(): void {}\n}\n\nclass ConsoleLogger implements Logger {\n\tdebug(message: string, ...args: unknown[]): void {\n\t\t// eslint-disable-next-line no-console -- this class exists to be the console sink\n\t\tconsole.debug(message, ...args);\n\t}\n\n\tinfo(message: string, ...args: unknown[]): void {\n\t\tconsole.info(message, ...args);\n\t}\n\n\twarn(message: string, ...args: unknown[]): void {\n\t\tconsole.warn(message, ...args);\n\t}\n\n\terror(message: string, ...args: unknown[]): void {\n\t\tconsole.error(message, ...args);\n\t}\n}\n\n// Defaults to no-op: a library that writes to the console uninvited is a nuisance in a host with\n// its own logging. Opt in with setLogger or enableDebugLogging.\nlet internalLogger: Logger = new NoOpLogger();\n\n// Call per-use rather than caching — the sink is settable at any time.\nexport function getLogger(): Logger {\n\treturn internalLogger;\n}\n\n// Validates logger shape up front: failing here beats a confusing \"getLogger().debug is not a\n// function\" at some later, unrelated call site.\nexport function setLogger(logger: Logger | Console | null): void {\n\tif (logger === null) {\n\t\tinternalLogger = new NoOpLogger();\n\t\treturn;\n\t}\n\n\tconst missing = (['debug', 'info', 'warn', 'error'] as const).filter(\n\t\t(method) => typeof (logger as unknown as Record<string, unknown>)[method] !== 'function'\n\t);\n\tif (missing.length > 0) {\n\t\tthrow new VisualizationError(\n\t\t\t`Logger is missing required method(s): ${missing.join(', ')}. A logger must implement debug, info, warn and error.`,\n\t\t\tErrorCodes.INVALID_CONFIG,\n\t\t\t{ context: { missingMethods: missing } }\n\t\t);\n\t}\n\n\tinternalLogger = logger as Logger;\n}\n\nexport function enableDebugLogging(): void {\n\tsetLogger(new ConsoleLogger());\n}\n","import * as THREE from 'three';\n\nimport type { Look, LookPreset, MaterialAppearanceOptions } from './types.js';\n\n/** The look applied when the caller passes no `look` option. */\nexport const DEFAULT_LOOK: Look = 'technical';\n\n/**\n * Single source of truth for both `applyDefaults` (construction) and `setLook` (runtime), so the two\n * can't drift.\n *\n * Every shaded look pairs a key light with contact shading (GTAO), because image-based lighting on\n * its own lights all faces of a box nearly equally — the result is a white silhouette with no\n * readable form. GTAO is a full-screen pass and does cost frames; `xray` skips it since there are no\n * opaque contacts left to shade.\n */\nexport const LOOK_PRESETS: Record<Look, LookPreset> = {\n\t// Fills shadows in (vs. showcase, which lets them fall off), but still key-lit: ACES plus a\n\t// moderate sun, with fill high enough that shadowed faces keep detail.\n\tstudio: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 0.85,\n\t\themisphereIntensity: 0.5,\n\t\tambientIntensity: 0.25,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 1.6\n\t},\n\t// The sun carries this look and the ambient/IBL only lift shadows off black. Fill was the thing\n\t// making it read as flat white paper: every source except the sun is orientation-independent, so\n\t// the more of them you stack the less the three faces of a corner differ.\n\ttechnical: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\tenvMapIntensity: 0.55,\n\t\tenvironmentIntensity: 0.6,\n\t\themisphereIntensity: 0.25,\n\t\tambientIntensity: 0.15,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 2.2\n\t},\n\t// Fill pulled furthest DOWN and the sun pushed hardest, so light-to-shadow falloff is steepest —\n\t// a dramatic hero shot, unlike `studio` which fills shadows back in.\n\tshowcase: {\n\t\ttoneMapping: THREE.ACESFilmicToneMapping,\n\t\ttoneMappingExposure: 1.15,\n\t\tenvMapIntensity: 1.4,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.2,\n\t\tambientIntensity: 0.1,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 2.6\n\t},\n\t// Every mesh forced to one near-white clay, so form and shadow carry the image instead of the\n\t// model's own colours. AO on: with all albedo differences gone, contact shading is the only thing\n\t// left separating touching parts — without it the model reads as one white blob.\n\tarctic: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1.05,\n\t\tenvMapIntensity: 0.85,\n\t\tenvironmentIntensity: 1.1,\n\t\themisphereIntensity: 0.3,\n\t\tambientIntensity: 0.15,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: true,\n\t\tsunlightIntensity: 2.0,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0xeef1f5,\n\t\t\tmetalness: 0,\n\t\t\troughness: 0.75\n\t\t}\n\t},\n\t// See-through blue-white with depth writes off, so interior geometry shows through the shell.\n\t// Exposure pushed up because stacked transparent layers darken fast; the sun is dialled back\n\t// because a hard highlight on a transparent shell hides what is behind it.\n\txray: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1.3,\n\t\tenvMapIntensity: 0.4,\n\t\tenvironmentIntensity: 0.8,\n\t\themisphereIntensity: 0.6,\n\t\tambientIntensity: 0.6,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false,\n\t\tsunlightIntensity: 0.6,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0x9fc4e8,\n\t\t\tmetalness: 0,\n\t\t\troughness: 0.9,\n\t\t\topacity: 0.28,\n\t\t\tdepthWrite: false,\n\t\t\tside: THREE.DoubleSide\n\t\t}\n\t},\n\t// The architectural line drawing: feature edges over flat white faces. The faces are the whole\n\t// point even though you never really see them — they still write depth, so a near member hides\n\t// the ones behind it. True wireframe can't do that, and on a dense frame (thousands of members,\n\t// every edge of every one drawn at once) it collapses into a black mass.\n\t//\n\t// Unlit on purpose: any directional shading would compete with the lines for the eye. Ambient\n\t// alone lands every face on the same flat white, so the only contrast in the image is the edges.\n\t// AO is off for the same reason — a grey contact smear under a line drawing just muddies it.\n\tlineart: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\t// The faces are MeshPhysicalMaterial, so they take their brightness from lighting, not from\n\t\t// `color`: zero out the lights and they render black however white the override is. Hemisphere\n\t\t// and ambient carry it rather than IBL, because `scene.environment` only exists if the host\n\t\t// loaded an HDR — leaning on IBL would render this look black in any viewer without one. Both\n\t\t// are orientation-independent, so every face lands on the same flat white and the edges stay\n\t\t// the only contrast in the image. Ambient is weighted over hemisphere because hemisphere's\n\t\t// ground colour is a warm brown by default: leaning on it tints every downward face grey and\n\t\t// the drawing stops reading as one flat white.\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.6,\n\t\tambientIntensity: 3.2,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false,\n\t\tsunlightIntensity: 0,\n\t\trequiresEdges: true,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0xf7f8fa,\n\t\t\tmetalness: 0,\n\t\t\troughness: 1\n\t\t}\n\t},\n\t// Triangle edges only — the tessellation itself, diagonals and all. That makes it a mesh-\n\t// inspection tool, not a drawing: for a readable line drawing use `lineart`, which draws\n\t// feature edges and lets near faces occlude far ones. Nothing here is a lit surface:\n\t// the sun is off and fill carries the image, because a directional light on a line renders it\n\t// black wherever the line runs away from the key. AO is off for the same reason — a full-screen\n\t// contact pass has no contacts to find between wires, and it only costs frames.\n\twireframe: {\n\t\ttoneMapping: THREE.NeutralToneMapping,\n\t\ttoneMappingExposure: 1,\n\t\t// Lit for the same reason as lineart (see there): PBR lines are black unlit, and hemisphere\n\t\t// plus ambient work with no HDR loaded. Dimmer than lineart because these lines are dark\n\t\t// on white, not white on dark.\n\t\tenvMapIntensity: 1.0,\n\t\tenvironmentIntensity: 1.0,\n\t\themisphereIntensity: 0.4,\n\t\tambientIntensity: 2.0,\n\t\tcullBackfaces: false,\n\t\tambientOcclusion: false,\n\t\tsunlightIntensity: 0,\n\t\tmaterialOverride: {\n\t\t\tcolor: 0x2b3138,\n\t\t\tmetalness: 0,\n\t\t\troughness: 1,\n\t\t\twireframe: true,\n\t\t\tside: THREE.DoubleSide\n\t\t}\n\t}\n};\n\n/**\n * `userData` key holding a material's own `envMapIntensity`, set by the parser when the wire\n * material carries one. `setLook` keeps it instead of the look's value, except in looks with a\n * `materialOverride`: those restyle every mesh alike, so the look's value wins there.\n */\nexport const OWN_ENV_MAP_INTENSITY = 'selvaEnvMapIntensity';\n\n/**\n * A look dims `envMapIntensity` to turn down fill, but three scales a material's diffuse and\n * specular IBL together. A metal has no diffuse, so the same dimming would only take away its\n * reflection, which is all a metal shows. Blending toward 1 by metalness keeps a dielectric's fill\n * on the look and a metal's reflection the same in every look.\n */\nexport function lookEnvMapIntensity(lookValue: number, metalness: number): number {\n\treturn lookValue + (1 - lookValue) * Math.min(1, Math.max(0, metalness));\n}\n\n/** Baked at parse time (not toggleable at runtime). */\nexport function materialAppearanceForLook(look: Look): MaterialAppearanceOptions {\n\tconst preset = LOOK_PRESETS[look];\n\treturn {\n\t\tenvMapIntensity: preset.envMapIntensity,\n\t\tcullBackfaces: preset.cullBackfaces\n\t};\n}\n","import * as THREE from 'three';\n\nimport { getLogger } from './logger.js';\n\n// parse/ needs these and must never import upward from render/, so they live in shared/ instead\n// of render's three-helpers.\n\nexport function parseColor(colorString: string): THREE.Color {\n\tif (!colorString || typeof colorString !== 'string') {\n\t\tgetLogger().warn(`Invalid color input: ${colorString}, using white`);\n\t\treturn new THREE.Color(0xffffff);\n\t}\n\n\tconst trimmed = colorString.trim();\n\n\tif (/^#?[0-9A-Fa-f]{6}$/.test(trimmed)) {\n\t\ttry {\n\t\t\tconst hex = trimmed.startsWith('#') ? trimmed : `#${trimmed}`;\n\t\t\treturn new THREE.Color(hex);\n\t\t} catch {\n\t\t\tgetLogger().warn(`Invalid hex color: ${colorString}, using white`);\n\t\t\treturn new THREE.Color(0xffffff);\n\t\t}\n\t}\n\n\tif (trimmed.includes(',')) {\n\t\tconst rgb = trimmed.split(',').map((c) => parseInt(c.trim(), 10));\n\t\tif (rgb.length === 3 && rgb.every((n) => !isNaN(n) && n >= 0 && n <= 255)) {\n\t\t\treturn new THREE.Color(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255);\n\t\t}\n\t}\n\n\t// `new THREE.Color(name)` never throws on unknown names (it logs its own warning and leaves the\n\t// color white), so validate against three's CSS name table instead.\n\tconst named = trimmed.toLowerCase();\n\tif (named in THREE.Color.NAMES) {\n\t\treturn new THREE.Color(THREE.Color.NAMES[named as keyof typeof THREE.Color.NAMES]);\n\t}\n\n\tgetLogger().warn(`Invalid color string: ${colorString}, using white`);\n\treturn new THREE.Color(0xffffff);\n}\n\n/** Defaults to `z` (Rhino's up axis); pass an explicit axis when the scene uses a different `sceneUp`. */\nexport function applyOffset(\n\tmeshes: THREE.Object3D[],\n\toffset: number,\n\taxis: 'x' | 'y' | 'z' = 'z'\n): void {\n\tmeshes.forEach((mesh) => {\n\t\tmesh.position[axis] -= offset;\n\t});\n}\n\nexport function computeCombinedBoundingBox(meshes: THREE.Object3D[]): THREE.Box3 {\n\tconst combinedBoundingBox = new THREE.Box3();\n\tif (meshes.length === 0) return combinedBoundingBox;\n\tmeshes.forEach((mesh) => {\n\t\tmesh.updateMatrixWorld(true);\n\t\tconst bbox = new THREE.Box3().setFromObject(mesh);\n\t\tcombinedBoundingBox.union(bbox);\n\t});\n\treturn combinedBoundingBox;\n}\n","import type * as THREE from 'three';\n\n// ============================================================================\n// GPU resource ownership — the single rule every disposal path obeys\n// ============================================================================\n\n/**\n * A GPU resource has exactly one owner: a **module singleton** (nobody ever frees it) or the\n * **scene** (freed on teardown). Every disposal path asks {@link canDisposeMaterial} and disposes\n * only if true. Geometries and textures are always scene-owned, so they have no claim to check.\n */\n\n// Module-scope singletons from `render/three-materials.ts`, shared across meshes and solves —\n// disposing one would free textures still in use and force a recompile. Held here (not imported\n// there) to avoid an import cycle.\nconst protectedMaterials = new Set<THREE.Material>();\n\n/** Register materials as never-disposable. Called once by `render/three-materials.ts`. */\nexport function protectMaterials(materials: Iterable<THREE.Material>): void {\n\tfor (const material of materials) protectedMaterials.add(material);\n}\n\n/** True when `material` is scene-owned (not one of the protected singletons). */\nexport function canDisposeMaterial(material: THREE.Material | undefined): boolean {\n\tif (!material) return false;\n\treturn !protectedMaterials.has(material);\n}\n","// ============================================================================\n// GPU capabilities — published by the renderer, read by whoever needs them\n// ============================================================================\n\n// Lets a renderer publish GPU capabilities (max anisotropy) without `render/` and `parse/` importing\n// each other: the renderer publishes on init, interested parties subscribe.\n\ntype AnisotropyObserver = (value: number) => void;\n\nconst observers = new Set<AnisotropyObserver>();\n\n// Module load order between layers isn't guaranteed, so a subscriber arriving after init still\n// needs the current value rather than waiting for a second renderer to publish.\nlet maxAnisotropy = 1;\n\nexport function publishMaxAnisotropy(value: number): void {\n\tconst next = Math.max(1, value);\n\tif (next === maxAnisotropy) return;\n\tmaxAnisotropy = next;\n\tfor (const observe of observers) observe(next);\n}\n\n/** Fires immediately with the current value (1 until a renderer has reported), then on every change. */\nexport function observeMaxAnisotropy(observe: AnisotropyObserver): () => void {\n\tobservers.add(observe);\n\tobserve(maxAnisotropy);\n\treturn () => observers.delete(observe);\n}\n","import * as THREE from 'three';\n\nimport { canDisposeMaterial } from './gpu-ownership.js';\n\n// ============================================================================\n// The disposal walkers — every teardown path in the package goes through these\n// ============================================================================\n\n/** Frees a material and its textures, respecting ownership (`./gpu-ownership.ts`). */\nexport function disposeMaterial(material: THREE.Material): void {\n\tif (!canDisposeMaterial(material)) return;\n\n\tfor (const [key, value] of Object.entries(material)) {\n\t\t// An envMap is the scene's, shared by every metal: freeing it here would blank them all.\n\t\tif (key === 'envMap') continue;\n\t\tif (value instanceof THREE.Texture) {\n\t\t\tvalue.dispose();\n\t\t}\n\t}\n\tmaterial.dispose();\n}\n\nfunction disposeMaterials(material: THREE.Material | THREE.Material[] | undefined): void {\n\tif (!material) return;\n\tif (Array.isArray(material)) material.forEach(disposeMaterial);\n\telse disposeMaterial(material);\n}\n\nexport interface DisposeOptions {\n\t/** Free materials and their textures too. Default true; the solve memo passes false. */\n\tmaterials?: boolean;\n\t/** Called for each geometry before disposal, so a subsystem can free derived resources keyed on it (e.g. edge caches). */\n\tonGeometry?: (geometry: THREE.BufferGeometry) => void;\n}\n\n/**\n * Frees the GPU resources of an object subtree, respecting ownership. **The only traversal that\n * should dispose scene content** — a second disposing `traverse` risks missing an ownership guard.\n */\nexport function disposeObjectTree(root: THREE.Object3D, options: DisposeOptions = {}): void {\n\tconst { materials = true } = options;\n\n\troot.traverse((object) => {\n\t\tconst renderable = object as Partial<THREE.Mesh> & THREE.Object3D;\n\t\tif (!renderable.geometry && !renderable.material) return;\n\n\t\tif (renderable.geometry) {\n\t\t\trenderable.geometry?.dispose();\n\t\t}\n\n\t\tif (materials) disposeMaterials(renderable.material);\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=/* @__PURE__ */ new WeakMap,t=/* @__PURE__ */ new WeakMap,n=/* @__PURE__ */ new Set;function r(e){let t=e.userData?.members;return!Array.isArray(t)||t.length===0||t.some(e=>e.indexStart==null||e.indexCount==null)?null:t}function i(t){let r=e.get(t);return r||(r={base:Array.isArray(t.material)?t.material[0]:t.material,hidden:n,highlight:null},e.set(t,r)),r}function a(t,n,r){let{geometry:i}=t;if(i.clearGroups(),r.hidden.size===0&&!r.highlight){t.material=r.base,e.delete(t);return}let a=-1,o=0,s=0,c=()=>{a>=0&&i.addGroup(a,o-a,s),a=-1};for(let e=0;e<n.length;e++){if(r.hidden.has(e)){c();continue}let{indexStart:t,indexCount:i}=n[e],l=+(r.highlight?.index===e);(a<0||l!==s||t!==o)&&(c(),a=t,s=l),o=t+i}c(),t.material=r.highlight?[r.base,r.highlight.material]:[r.base]}function o(e,n){let o=r(e);if(!o)return;let s=i(e);n(s),a(e,o,s),t.get(e)?.forEach(e=>e())}function s(t){return e.get(t)?.hidden??n}function c(e,t){let n=s(e);n.size===t.size&&[...t].every(e=>n.has(e))||o(e,e=>{e.hidden=new Set(t)})}function l(t){return e.get(t)?.base??(Array.isArray(t.material)?t.material[0]:t.material)}function u(e,t,n){o(e,e=>{e.highlight={index:t,material:n}})}function d(t){e.get(t)?.highlight&&o(t,e=>{e.highlight=null})}function f(e,n){let r=t.get(e);return r||t.set(e,r=/* @__PURE__ */ new Set),r.add(n),()=>r.delete(n)}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return d}});
|
|
2
|
-
//# sourceMappingURL=merged-draw-B4lGH7FT.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"merged-draw-B4lGH7FT.cjs","names":["states","listeners","NONE","windowsOf","members","mesh","m","stateOf","state","rebuild","geometry","runStart","runEnd","runMaterial","flush","i","windows","material","indexStart","indexCount","update","change","listener","getHiddenMembers","setHiddenMembers","current","hidden","getBaseMaterial","setMemberHighlight","clearMemberHighlight","onMemberDrawChange","set"],"sources":["../src/shared/merged-draw.ts"],"sourcesContent":["// ============================================================================\n// Which parts of a merged mesh draw, and with what material\n// ============================================================================\n//\n// A merged mesh draws many source members in one call. Two features draw only part of it: hiding\n// a member (`scene/`) and tinting the selected member (`render/`). Both need geometry groups, and\n// three honours groups only when `material` is an array: with a single material it draws the whole\n// index range and ignores them. So one owner builds the groups and the material array from both\n// states together. Two writers each replacing the groups would undo each other.\n//\n// Members are addressed by their index in `userData.members`, which carries each member's window\n// into the index buffer (`finalizeMergedMesh`).\n\nimport type * as THREE from 'three';\n\ninterface MemberWindow {\n\tindexStart?: number;\n\tindexCount?: number;\n}\n\ninterface DrawState {\n\t/** The mesh's own material, restored once nothing is hidden or highlighted. */\n\tbase: THREE.Material;\n\thidden: ReadonlySet<number>;\n\thighlight: { index: number; material: THREE.Material } | null;\n}\n\nconst states = new WeakMap<THREE.Mesh, DrawState>();\nconst listeners = new WeakMap<THREE.Mesh, Set<() => void>>();\n\nconst NONE: ReadonlySet<number> = new Set();\n\nfunction windowsOf(mesh: THREE.Mesh): Required<MemberWindow>[] | null {\n\tconst members = mesh.userData?.members as MemberWindow[] | undefined;\n\tif (!Array.isArray(members) || members.length === 0) return null;\n\t// One member without a window means the ranges can't be rebuilt without dropping geometry.\n\tif (members.some((m) => m.indexStart == null || m.indexCount == null)) return null;\n\treturn members as Required<MemberWindow>[];\n}\n\nfunction stateOf(mesh: THREE.Mesh): DrawState {\n\tlet state = states.get(mesh);\n\tif (!state) {\n\t\tconst base = Array.isArray(mesh.material) ? mesh.material[0]! : mesh.material;\n\t\tstate = { base, hidden: NONE, highlight: null };\n\t\tstates.set(mesh, state);\n\t}\n\treturn state;\n}\n\nfunction rebuild(mesh: THREE.Mesh, windows: Required<MemberWindow>[], state: DrawState): void {\n\tconst { geometry } = mesh;\n\tgeometry.clearGroups();\n\n\tif (state.hidden.size === 0 && !state.highlight) {\n\t\tmesh.material = state.base;\n\t\tstates.delete(mesh);\n\t\treturn;\n\t}\n\n\t// Coalesce neighbours with the same material, so hiding one member costs two draws, not N.\n\tlet runStart = -1;\n\tlet runEnd = 0;\n\tlet runMaterial = 0;\n\tconst flush = () => {\n\t\tif (runStart >= 0) geometry.addGroup(runStart, runEnd - runStart, runMaterial);\n\t\trunStart = -1;\n\t};\n\tfor (let i = 0; i < windows.length; i++) {\n\t\tif (state.hidden.has(i)) {\n\t\t\tflush();\n\t\t\tcontinue;\n\t\t}\n\t\tconst { indexStart, indexCount } = windows[i]!;\n\t\tconst material = state.highlight?.index === i ? 1 : 0;\n\t\tif (runStart < 0 || material !== runMaterial || indexStart !== runEnd) {\n\t\t\tflush();\n\t\t\trunStart = indexStart;\n\t\t\trunMaterial = material;\n\t\t}\n\t\trunEnd = indexStart + indexCount;\n\t}\n\tflush();\n\n\tmesh.material = state.highlight ? [state.base, state.highlight.material] : [state.base];\n}\n\nfunction update(mesh: THREE.Mesh, change: (state: DrawState) => void): void {\n\tconst windows = windowsOf(mesh);\n\tif (!windows) return;\n\tconst state = stateOf(mesh);\n\tchange(state);\n\trebuild(mesh, windows, state);\n\tlisteners.get(mesh)?.forEach((listener) => listener());\n}\n\n/** Members of `mesh` not drawn, by index into `userData.members`. */\nexport function getHiddenMembers(mesh: THREE.Mesh): ReadonlySet<number> {\n\treturn states.get(mesh)?.hidden ?? NONE;\n}\n\nexport function setHiddenMembers(mesh: THREE.Mesh, hidden: ReadonlySet<number>): void {\n\tconst current = getHiddenMembers(mesh);\n\tif (current.size === hidden.size && [...hidden].every((i) => current.has(i))) return;\n\tupdate(mesh, (state) => {\n\t\tstate.hidden = new Set(hidden);\n\t});\n}\n\n/** The material `mesh` draws with when nothing is highlighted. */\nexport function getBaseMaterial(mesh: THREE.Mesh): THREE.Material {\n\treturn (\n\t\tstates.get(mesh)?.base ?? (Array.isArray(mesh.material) ? mesh.material[0]! : mesh.material)\n\t);\n}\n\n/** Draws member `index` with `material`. The caller owns `material` and disposes it. */\nexport function setMemberHighlight(\n\tmesh: THREE.Mesh,\n\tindex: number,\n\tmaterial: THREE.Material\n): void {\n\tupdate(mesh, (state) => {\n\t\tstate.highlight = { index, material };\n\t});\n}\n\nexport function clearMemberHighlight(mesh: THREE.Mesh): void {\n\tif (!states.get(mesh)?.highlight) return;\n\tupdate(mesh, (state) => {\n\t\tstate.highlight = null;\n\t});\n}\n\n/** Calls `listener` after the hidden set or highlight of `mesh` changes. Returns an unsubscribe. */\nexport function onMemberDrawChange(mesh: THREE.Mesh, listener: () => void): () => void {\n\tlet set = listeners.get(mesh);\n\tif (!set) listeners.set(mesh, (set = new Set()));\n\tset.add(listener);\n\treturn () => set.delete(listener);\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=/* @__PURE__ */ new WeakMap,t=/* @__PURE__ */ new WeakMap,n=/* @__PURE__ */ new Set;function r(e){let t=e.userData?.members;return!Array.isArray(t)||t.length===0||t.some(e=>e.indexStart==null||e.indexCount==null)?null:t}function i(t){let r=e.get(t);return r||(r={base:Array.isArray(t.material)?t.material[0]:t.material,hidden:n,highlight:null},e.set(t,r)),r}function a(t,n,r){let{geometry:i}=t;if(i.clearGroups(),r.hidden.size===0&&!r.highlight){t.material=r.base,e.delete(t);return}let a=-1,o=0,s=0,c=()=>{a>=0&&i.addGroup(a,o-a,s),a=-1};for(let e=0;e<n.length;e++){if(r.hidden.has(e)){c();continue}let{indexStart:t,indexCount:i}=n[e],l=+(r.highlight?.index===e);(a<0||l!==s||t!==o)&&(c(),a=t,s=l),o=t+i}c(),t.material=r.highlight?[r.base,r.highlight.material]:[r.base]}function o(e,n){let o=r(e);if(!o)return;let s=i(e);n(s),a(e,o,s),t.get(e)?.forEach(e=>e())}function s(t){return e.get(t)?.hidden??n}function c(e,t){let n=s(e);n.size===t.size&&[...t].every(e=>n.has(e))||o(e,e=>{e.hidden=new Set(t)})}function l(t){return e.get(t)?.base??(Array.isArray(t.material)?t.material[0]:t.material)}function u(e,t,n){o(e,e=>{e.highlight={index:t,material:n}})}function d(t){e.get(t)?.highlight&&o(t,e=>{e.highlight=null})}function f(e,n){let r=t.get(e);return r||t.set(e,r=/* @__PURE__ */ new Set),r.add(n),()=>r.delete(n)}export{c as a,f as i,l as n,u as o,s as r,d as t};
|
|
2
|
-
//# sourceMappingURL=merged-draw-jrCP6Cz4.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"merged-draw-jrCP6Cz4.js","names":["states","listeners","NONE","windowsOf","members","mesh","m","stateOf","state","rebuild","geometry","runStart","runEnd","runMaterial","flush","i","windows","material","indexStart","indexCount","update","change","listener","getHiddenMembers","setHiddenMembers","current","hidden","getBaseMaterial","setMemberHighlight","clearMemberHighlight","onMemberDrawChange","set"],"sources":["../src/shared/merged-draw.ts"],"sourcesContent":["// ============================================================================\n// Which parts of a merged mesh draw, and with what material\n// ============================================================================\n//\n// A merged mesh draws many source members in one call. Two features draw only part of it: hiding\n// a member (`scene/`) and tinting the selected member (`render/`). Both need geometry groups, and\n// three honours groups only when `material` is an array: with a single material it draws the whole\n// index range and ignores them. So one owner builds the groups and the material array from both\n// states together. Two writers each replacing the groups would undo each other.\n//\n// Members are addressed by their index in `userData.members`, which carries each member's window\n// into the index buffer (`finalizeMergedMesh`).\n\nimport type * as THREE from 'three';\n\ninterface MemberWindow {\n\tindexStart?: number;\n\tindexCount?: number;\n}\n\ninterface DrawState {\n\t/** The mesh's own material, restored once nothing is hidden or highlighted. */\n\tbase: THREE.Material;\n\thidden: ReadonlySet<number>;\n\thighlight: { index: number; material: THREE.Material } | null;\n}\n\nconst states = new WeakMap<THREE.Mesh, DrawState>();\nconst listeners = new WeakMap<THREE.Mesh, Set<() => void>>();\n\nconst NONE: ReadonlySet<number> = new Set();\n\nfunction windowsOf(mesh: THREE.Mesh): Required<MemberWindow>[] | null {\n\tconst members = mesh.userData?.members as MemberWindow[] | undefined;\n\tif (!Array.isArray(members) || members.length === 0) return null;\n\t// One member without a window means the ranges can't be rebuilt without dropping geometry.\n\tif (members.some((m) => m.indexStart == null || m.indexCount == null)) return null;\n\treturn members as Required<MemberWindow>[];\n}\n\nfunction stateOf(mesh: THREE.Mesh): DrawState {\n\tlet state = states.get(mesh);\n\tif (!state) {\n\t\tconst base = Array.isArray(mesh.material) ? mesh.material[0]! : mesh.material;\n\t\tstate = { base, hidden: NONE, highlight: null };\n\t\tstates.set(mesh, state);\n\t}\n\treturn state;\n}\n\nfunction rebuild(mesh: THREE.Mesh, windows: Required<MemberWindow>[], state: DrawState): void {\n\tconst { geometry } = mesh;\n\tgeometry.clearGroups();\n\n\tif (state.hidden.size === 0 && !state.highlight) {\n\t\tmesh.material = state.base;\n\t\tstates.delete(mesh);\n\t\treturn;\n\t}\n\n\t// Coalesce neighbours with the same material, so hiding one member costs two draws, not N.\n\tlet runStart = -1;\n\tlet runEnd = 0;\n\tlet runMaterial = 0;\n\tconst flush = () => {\n\t\tif (runStart >= 0) geometry.addGroup(runStart, runEnd - runStart, runMaterial);\n\t\trunStart = -1;\n\t};\n\tfor (let i = 0; i < windows.length; i++) {\n\t\tif (state.hidden.has(i)) {\n\t\t\tflush();\n\t\t\tcontinue;\n\t\t}\n\t\tconst { indexStart, indexCount } = windows[i]!;\n\t\tconst material = state.highlight?.index === i ? 1 : 0;\n\t\tif (runStart < 0 || material !== runMaterial || indexStart !== runEnd) {\n\t\t\tflush();\n\t\t\trunStart = indexStart;\n\t\t\trunMaterial = material;\n\t\t}\n\t\trunEnd = indexStart + indexCount;\n\t}\n\tflush();\n\n\tmesh.material = state.highlight ? [state.base, state.highlight.material] : [state.base];\n}\n\nfunction update(mesh: THREE.Mesh, change: (state: DrawState) => void): void {\n\tconst windows = windowsOf(mesh);\n\tif (!windows) return;\n\tconst state = stateOf(mesh);\n\tchange(state);\n\trebuild(mesh, windows, state);\n\tlisteners.get(mesh)?.forEach((listener) => listener());\n}\n\n/** Members of `mesh` not drawn, by index into `userData.members`. */\nexport function getHiddenMembers(mesh: THREE.Mesh): ReadonlySet<number> {\n\treturn states.get(mesh)?.hidden ?? NONE;\n}\n\nexport function setHiddenMembers(mesh: THREE.Mesh, hidden: ReadonlySet<number>): void {\n\tconst current = getHiddenMembers(mesh);\n\tif (current.size === hidden.size && [...hidden].every((i) => current.has(i))) return;\n\tupdate(mesh, (state) => {\n\t\tstate.hidden = new Set(hidden);\n\t});\n}\n\n/** The material `mesh` draws with when nothing is highlighted. */\nexport function getBaseMaterial(mesh: THREE.Mesh): THREE.Material {\n\treturn (\n\t\tstates.get(mesh)?.base ?? (Array.isArray(mesh.material) ? mesh.material[0]! : mesh.material)\n\t);\n}\n\n/** Draws member `index` with `material`. The caller owns `material` and disposes it. */\nexport function setMemberHighlight(\n\tmesh: THREE.Mesh,\n\tindex: number,\n\tmaterial: THREE.Material\n): void {\n\tupdate(mesh, (state) => {\n\t\tstate.highlight = { index, material };\n\t});\n}\n\nexport function clearMemberHighlight(mesh: THREE.Mesh): void {\n\tif (!states.get(mesh)?.highlight) return;\n\tupdate(mesh, (state) => {\n\t\tstate.highlight = null;\n\t});\n}\n\n/** Calls `listener` after the hidden set or highlight of `mesh` changes. Returns an unsubscribe. */\nexport function onMemberDrawChange(mesh: THREE.Mesh, listener: () => void): () => void {\n\tlet set = listeners.get(mesh);\n\tif (!set) listeners.set(mesh, (set = new Set()));\n\tset.add(listener);\n\treturn () => set.delete(listener);\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
|