@pascal-app/viewer 0.5.1 → 0.6.0

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.
Files changed (53) hide show
  1. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  2. package/dist/components/renderers/ceiling/ceiling-renderer.js +4 -3
  3. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
  4. package/dist/components/renderers/door/door-renderer.js +5 -2
  5. package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
  6. package/dist/components/renderers/fence/fence-renderer.js +16 -2
  7. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  8. package/dist/components/renderers/roof/roof-renderer.js +31 -9
  9. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +42 -8
  11. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/site/site-renderer.js +7 -1
  13. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
  14. package/dist/components/renderers/slab/slab-renderer.js +37 -8
  15. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
  16. package/dist/components/renderers/stair/stair-renderer.js +172 -39
  17. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
  18. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +37 -8
  19. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  20. package/dist/components/renderers/wall/wall-renderer.js +4 -9
  21. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  22. package/dist/components/renderers/window/window-renderer.js +5 -2
  23. package/dist/components/viewer/frame-limiter.d.ts +6 -0
  24. package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
  25. package/dist/components/viewer/frame-limiter.js +31 -0
  26. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  27. package/dist/components/viewer/ground-occluder.js +5 -1
  28. package/dist/components/viewer/index.d.ts.map +1 -1
  29. package/dist/components/viewer/index.js +11 -11
  30. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  31. package/dist/components/viewer/post-processing.js +33 -36
  32. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  33. package/dist/components/viewer/selection-manager.js +12 -1
  34. package/dist/hooks/use-node-events.d.ts.map +1 -1
  35. package/dist/hooks/use-node-events.js +2 -0
  36. package/dist/lib/materials.d.ts +4 -1
  37. package/dist/lib/materials.d.ts.map +1 -1
  38. package/dist/lib/materials.js +178 -2
  39. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  40. package/dist/lib/merged-outline-node.js +11 -4
  41. package/dist/store/use-viewer.d.ts +2 -2
  42. package/dist/systems/roof/roof-materials.d.ts +5 -0
  43. package/dist/systems/roof/roof-materials.d.ts.map +1 -0
  44. package/dist/systems/roof/roof-materials.js +29 -0
  45. package/dist/systems/stair/stair-materials.d.ts +7 -0
  46. package/dist/systems/stair/stair-materials.d.ts.map +1 -0
  47. package/dist/systems/stair/stair-materials.js +33 -0
  48. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  49. package/dist/systems/wall/wall-cutout.js +4 -123
  50. package/dist/systems/wall/wall-materials.d.ts +16 -0
  51. package/dist/systems/wall/wall-materials.d.ts.map +1 -0
  52. package/dist/systems/wall/wall-materials.js +148 -0
  53. package/package.json +4 -4
@@ -27,13 +27,23 @@ const MAX_PIPELINE_RETRIES = 3;
27
27
  const RETRY_DELAY_MS = 500;
28
28
  const DARK_BG = '#1f2433';
29
29
  const LIGHT_BG = '#ffffff';
30
+ function sanitizeOutlineObjects(objects) {
31
+ let nextIndex = 0;
32
+ for (const object of objects) {
33
+ if (!(object && typeof object.id === 'number' && object.parent)) {
34
+ continue;
35
+ }
36
+ objects[nextIndex] = object;
37
+ nextIndex++;
38
+ }
39
+ objects.length = nextIndex;
40
+ }
30
41
  const PostProcessingPasses = () => {
31
42
  const { gl: renderer, scene, camera } = useThree();
32
43
  const renderPipelineRef = useRef(null);
33
44
  const hasPipelineErrorRef = useRef(false);
34
45
  const retryCountRef = useRef(0);
35
46
  const rebuildTimeoutRef = useRef(null);
36
- const [isInitialized, setIsInitialized] = useState(false);
37
47
  // Background color uniform — updated every frame via lerp, read by the TSL pipeline.
38
48
  // Initialised from the current theme so there's no flash on first render.
39
49
  const initBg = useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG;
@@ -58,30 +68,6 @@ const PostProcessingPasses = () => {
58
68
  }
59
69
  setPipelineVersion((v) => v + 1);
60
70
  }, []);
61
- // Renderer initialization
62
- useEffect(() => {
63
- let mounted = true;
64
- const initRenderer = async () => {
65
- try {
66
- if (renderer && renderer.init) {
67
- await renderer.init();
68
- }
69
- if (mounted) {
70
- setIsInitialized(true);
71
- }
72
- }
73
- catch (error) {
74
- console.error('[viewer] Failed to initialize renderer for post-processing.', error);
75
- if (mounted) {
76
- setIsInitialized(false);
77
- }
78
- }
79
- };
80
- initRenderer();
81
- return () => {
82
- mounted = false;
83
- };
84
- }, [renderer]);
85
71
  // Reset retry state when project changes
86
72
  useEffect(() => {
87
73
  // Intentionally touch projectId so the effect reruns on project switches.
@@ -106,13 +92,30 @@ const PostProcessingPasses = () => {
106
92
  // as explicit rebuild triggers instead of accidental extra dependencies.
107
93
  void projectId;
108
94
  void pipelineVersion;
109
- if (!(renderer && scene && camera && isInitialized)) {
95
+ if (!(renderer && scene && camera)) {
110
96
  return;
111
97
  }
112
98
  hasPipelineErrorRef.current = false;
99
+ // WebGPU availability check: SSGI, denoise, and RenderPipeline are all
100
+ // WebGPU-only APIs. When the browser falls back to WebGL2 (no
101
+ // `navigator.gpu`, or the device couldn't be created), building the
102
+ // pipeline either throws silently or produces a broken output where
103
+ // the scene renders for a few frames and then goes black as the retry
104
+ // loop fights the direct-render fallback path. Short-circuit here so
105
+ // `useFrame` uses the direct `renderer.render(scene, camera)` path
106
+ // exclusively and never attempts the TSL pipeline.
107
+ const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
108
+ if (!hasWebGPU) {
109
+ console.warn('[viewer] WebGPU unavailable — rendering without post-processing (SSGI, outlines, denoise).');
110
+ hasPipelineErrorRef.current = true;
111
+ renderPipelineRef.current = null;
112
+ return;
113
+ }
113
114
  // Clear outliner arrays synchronously to prevent stale Object3D refs
114
115
  // from the previous project leaking into the new pipeline's outline passes.
115
116
  const outliner = useViewer.getState().outliner;
117
+ sanitizeOutlineObjects(outliner.selectedObjects);
118
+ sanitizeOutlineObjects(outliner.hoveredObjects);
116
119
  outliner.selectedObjects.length = 0;
117
120
  outliner.hoveredObjects.length = 0;
118
121
  try {
@@ -218,21 +221,15 @@ const PostProcessingPasses = () => {
218
221
  }
219
222
  renderPipelineRef.current = null;
220
223
  };
221
- }, [
222
- renderer,
223
- scene,
224
- camera,
225
- hoverHighlightMode,
226
- isInitialized,
227
- zoneLayers,
228
- projectId,
229
- pipelineVersion,
230
- ]);
224
+ }, [renderer, scene, camera, hoverHighlightMode, zoneLayers, projectId, pipelineVersion]);
231
225
  useFrame((_, delta) => {
232
226
  // Animate background colour toward the current theme target (same lerp as AnimatedBackground)
233
227
  bgTarget.current.set(useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG);
234
228
  bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
235
229
  bgUniform.current.value.copy(bgCurrent.current);
230
+ const outliner = useViewer.getState().outliner;
231
+ sanitizeOutlineObjects(outliner.selectedObjects);
232
+ sanitizeOutlineObjects(outliner.hoveredObjects);
236
233
  if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
237
234
  try {
238
235
  if (renderer.setClearAlpha) {
@@ -1 +1 @@
1
- {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAiRA,eAAO,MAAM,gBAAgB,+CAuE5B,CAAA"}
1
+ {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAiRA,eAAO,MAAM,gBAAgB,+CA2E5B,CAAA"}
@@ -222,6 +222,10 @@ export const SelectionManager = () => {
222
222
  return;
223
223
  if (strategy.isValid(event.node)) {
224
224
  event.stopPropagation();
225
+ if (event.node.type === 'slab') {
226
+ useViewer.setState({ hoveredId: null });
227
+ return;
228
+ }
225
229
  useViewer.setState({ hoveredId: event.node.id });
226
230
  }
227
231
  };
@@ -311,10 +315,14 @@ const OutlinerSync = () => {
311
315
  const selection = useViewer((s) => s.selection);
312
316
  const hoveredId = useViewer((s) => s.hoveredId);
313
317
  const outliner = useViewer((s) => s.outliner);
318
+ const nodes = useScene((s) => s.nodes);
314
319
  useEffect(() => {
315
320
  // Sync selected objects
316
321
  outliner.selectedObjects.length = 0;
317
322
  for (const id of selection.selectedIds) {
323
+ const node = nodes[id];
324
+ if (node?.type === 'slab')
325
+ continue;
318
326
  const obj = sceneRegistry.nodes.get(id);
319
327
  if (obj)
320
328
  outliner.selectedObjects.push(obj);
@@ -322,10 +330,13 @@ const OutlinerSync = () => {
322
330
  // Sync hovered objects
323
331
  outliner.hoveredObjects.length = 0;
324
332
  if (hoveredId) {
333
+ const hoveredNode = nodes[hoveredId];
334
+ if (hoveredNode?.type === 'slab')
335
+ return;
325
336
  const obj = sceneRegistry.nodes.get(hoveredId);
326
337
  if (obj)
327
338
  outliner.hoveredObjects.push(obj);
328
339
  }
329
- }, [selection, hoveredId, outliner]);
340
+ }, [selection, hoveredId, outliner, nodes]);
330
341
  return null;
331
342
  };
@@ -1 +1 @@
1
- {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,8 @@ export function useNodeEvents(node, type) {
9
9
  position: [e.point.x, e.point.y, e.point.z],
10
10
  localPosition: [localPoint.x, localPoint.y, localPoint.z],
11
11
  normal: e.face ? [e.face.normal.x, e.face.normal.y, e.face.normal.z] : undefined,
12
+ faceIndex: e.faceIndex ?? undefined,
13
+ object: e.object,
12
14
  stopPropagation: () => e.stopPropagation(),
13
15
  nativeEvent: e,
14
16
  };
@@ -1,5 +1,8 @@
1
- import { type MaterialSchema } from '@pascal-app/core';
1
+ import { type MaterialPresetPayload, type MaterialSchema } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
+ export declare function applyMaterialPresetToMaterials(materialInput: THREE.Material | THREE.Material[], preset: MaterialPresetPayload | null | undefined): void;
4
+ export declare function createMaterialFromPreset(preset: MaterialPresetPayload): THREE.MeshStandardMaterial;
5
+ export declare function createMaterialFromPresetRef(materialPreset?: string): THREE.MeshStandardMaterial | null;
3
6
  export declare function createMaterial(material?: MaterialSchema): THREE.MeshStandardMaterial;
4
7
  export declare function createDefaultMaterial(color?: string, roughness?: number): THREE.MeshStandardMaterial;
5
8
  export declare const DEFAULT_WALL_MATERIAL: THREE.MeshStandardMaterial;
@@ -1 +1 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,cAAc,EAAmB,MAAM,kBAAkB,CAAA;AAChG,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAc9B,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAmBpF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,SAAY,EACjB,SAAS,SAAM,GACd,KAAK,CAAC,oBAAoB,CAO5B;AAED,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,uBAAuB,4BAOlC,CAAA;AACF,eAAO,MAAM,wBAAwB,4BAAyC,CAAA;AAC9E,eAAO,MAAM,qBAAqB,4BAAyC,CAAA;AAC3E,eAAO,MAAM,sBAAsB,4BAAwC,CAAA;AAE3E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAE9D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC"}
1
+ {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,cAAc,EAGpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2M9B,wBAAgB,8BAA8B,CAC5C,aAAa,EAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,EAChD,MAAM,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,QAcjD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,KAAK,CAAC,oBAAoB,CAWlG;AAED,wBAAgB,2BAA2B,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAItG;AAED,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAsBpF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,SAAY,EACjB,SAAS,SAAM,GACd,KAAK,CAAC,oBAAoB,CAO5B;AAED,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,uBAAuB,4BAOlC,CAAA;AACF,eAAO,MAAM,wBAAwB,4BAAyC,CAAA;AAC9E,eAAO,MAAM,qBAAqB,4BAAyC,CAAA;AAC3E,eAAO,MAAM,sBAAsB,4BAAwC,CAAA;AAE3E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAE9D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAWzC"}
@@ -1,4 +1,4 @@
1
- import { resolveMaterial } from '@pascal-app/core';
1
+ import { getMaterialPresetByRef, resolveMaterial, } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
3
  const sideMap = {
4
4
  front: THREE.FrontSide,
@@ -6,15 +6,185 @@ const sideMap = {
6
6
  double: THREE.DoubleSide,
7
7
  };
8
8
  const materialCache = new Map();
9
+ const textureCache = new Map();
10
+ const textureLoadPromises = new Map();
11
+ const textureLoader = new THREE.TextureLoader();
12
+ const wrapMap = {
13
+ Repeat: THREE.RepeatWrapping,
14
+ ClampToEdge: THREE.ClampToEdgeWrapping,
15
+ MirroredRepeat: THREE.MirroredRepeatWrapping,
16
+ };
17
+ const SRGB_TEXTURE_SLOTS = ['map', 'emissiveMap'];
9
18
  function getCacheKey(props) {
10
19
  return `${props.color}-${props.roughness}-${props.metalness}-${props.opacity}-${props.transparent}-${props.side}`;
11
20
  }
21
+ function getTextureKey(material) {
22
+ const texture = material?.texture;
23
+ if (!texture)
24
+ return 'none';
25
+ const repeat = texture.repeat?.join('x') ?? 'default';
26
+ const scale = texture.scale ?? 'default';
27
+ return `${texture.url}-${repeat}-${scale}`;
28
+ }
29
+ function getTexture(material) {
30
+ const textureConfig = material?.texture;
31
+ if (!textureConfig?.url)
32
+ return undefined;
33
+ const cacheKey = getTextureKey(material);
34
+ const cached = textureCache.get(cacheKey);
35
+ if (cached)
36
+ return cached;
37
+ const texture = textureLoader.load(textureConfig.url);
38
+ texture.wrapS = THREE.RepeatWrapping;
39
+ texture.wrapT = THREE.RepeatWrapping;
40
+ const repeatX = textureConfig.repeat?.[0] ?? textureConfig.scale ?? 1;
41
+ const repeatY = textureConfig.repeat?.[1] ?? textureConfig.scale ?? 1;
42
+ texture.repeat.set(repeatX, repeatY);
43
+ texture.colorSpace = THREE.SRGBColorSpace;
44
+ textureCache.set(cacheKey, texture);
45
+ return texture;
46
+ }
47
+ function isStandardMaterial(material) {
48
+ return (material instanceof THREE.MeshStandardMaterial ||
49
+ material instanceof THREE.MeshPhysicalMaterial);
50
+ }
51
+ function applyTextureProperties(texture, props, slot) {
52
+ texture.wrapS = wrapMap[props.wrapS];
53
+ texture.wrapT = wrapMap[props.wrapT];
54
+ texture.repeat.set(props.repeatX, props.repeatY);
55
+ texture.rotation = props.rotation;
56
+ texture.flipY = props.flipY;
57
+ texture.colorSpace = SRGB_TEXTURE_SLOTS.includes(slot ?? 'map')
58
+ ? THREE.SRGBColorSpace
59
+ : THREE.NoColorSpace;
60
+ texture.needsUpdate = true;
61
+ return texture;
62
+ }
63
+ function getPresetTextureCacheKey(path, props, slot) {
64
+ return `${path}-${props.repeatX}-${props.repeatY}-${props.rotation}-${props.wrapS}-${props.wrapT}-${props.flipY}-${slot ?? 'map'}`;
65
+ }
66
+ function getPresetTexture(path, props, slot) {
67
+ const cacheKey = getPresetTextureCacheKey(path, props, slot);
68
+ const cached = textureCache.get(cacheKey);
69
+ if (cached)
70
+ return cached;
71
+ const texture = textureLoader.load(path);
72
+ applyTextureProperties(texture, props, slot);
73
+ textureCache.set(cacheKey, texture);
74
+ return texture;
75
+ }
76
+ async function loadPresetTexture(path, props, slot) {
77
+ const cacheKey = getPresetTextureCacheKey(path, props, slot);
78
+ const cached = textureCache.get(cacheKey);
79
+ if (cached)
80
+ return cached;
81
+ const existingPromise = textureLoadPromises.get(cacheKey);
82
+ if (existingPromise)
83
+ return existingPromise;
84
+ const promise = textureLoader
85
+ .loadAsync(path)
86
+ .then((texture) => {
87
+ applyTextureProperties(texture, props, slot);
88
+ textureCache.set(cacheKey, texture);
89
+ textureLoadPromises.delete(cacheKey);
90
+ return texture;
91
+ })
92
+ .catch((error) => {
93
+ console.warn('[viewer] Failed to load material texture', path, error);
94
+ textureLoadPromises.delete(cacheKey);
95
+ return null;
96
+ });
97
+ textureLoadPromises.set(cacheKey, promise);
98
+ return promise;
99
+ }
100
+ function queueTextureAssignment(material, slot, path, props) {
101
+ if (!path) {
102
+ material[slot] = null;
103
+ return;
104
+ }
105
+ const cacheKey = getPresetTextureCacheKey(path, props, slot);
106
+ const cached = textureCache.get(cacheKey);
107
+ if (cached) {
108
+ material[slot] = cached;
109
+ return;
110
+ }
111
+ material[slot] = null;
112
+ void loadPresetTexture(path, props, slot).then((texture) => {
113
+ if (!texture)
114
+ return;
115
+ material[slot] = texture;
116
+ material.needsUpdate = true;
117
+ });
118
+ }
119
+ function applyMaterialMapProperties(material, mapProperties) {
120
+ material.color.set(mapProperties.color);
121
+ material.roughness = mapProperties.roughness;
122
+ material.metalness = mapProperties.metalness;
123
+ material.emissiveIntensity = mapProperties.emissiveIntensity;
124
+ material.emissive.set(mapProperties.emissiveColor);
125
+ material.displacementScale = mapProperties.displacementScale;
126
+ material.bumpScale = mapProperties.bumpScale;
127
+ material.aoMapIntensity = mapProperties.aoMapIntensity;
128
+ material.lightMapIntensity = mapProperties.lightMapIntensity;
129
+ material.transparent = mapProperties.transparent;
130
+ material.opacity = mapProperties.opacity;
131
+ material.side =
132
+ mapProperties.side === 0
133
+ ? THREE.FrontSide
134
+ : mapProperties.side === 1
135
+ ? THREE.BackSide
136
+ : THREE.DoubleSide;
137
+ material.normalScale.set(mapProperties.normalScaleX, mapProperties.normalScaleY);
138
+ material.needsUpdate = true;
139
+ }
140
+ function applyMaterialPresetTextures(material, preset) {
141
+ const { maps, mapProperties } = preset;
142
+ queueTextureAssignment(material, 'map', maps.albedoMap, mapProperties);
143
+ queueTextureAssignment(material, 'normalMap', maps.normalMap, mapProperties);
144
+ queueTextureAssignment(material, 'roughnessMap', maps.roughnessMap, mapProperties);
145
+ queueTextureAssignment(material, 'metalnessMap', maps.metalnessMap, mapProperties);
146
+ queueTextureAssignment(material, 'displacementMap', maps.displacementMap, mapProperties);
147
+ queueTextureAssignment(material, 'aoMap', maps.aoMap, mapProperties);
148
+ queueTextureAssignment(material, 'bumpMap', maps.bumpMap, mapProperties);
149
+ queueTextureAssignment(material, 'alphaMap', maps.alphaMap, mapProperties);
150
+ queueTextureAssignment(material, 'lightMap', maps.lightMap, mapProperties);
151
+ queueTextureAssignment(material, 'emissiveMap', maps.emissiveMap, mapProperties);
152
+ material.needsUpdate = true;
153
+ }
154
+ export function applyMaterialPresetToMaterials(materialInput, preset) {
155
+ if (!preset)
156
+ return;
157
+ const materials = (Array.isArray(materialInput) ? materialInput : [materialInput]).filter(isStandardMaterial);
158
+ if (materials.length === 0)
159
+ return;
160
+ for (const material of materials) {
161
+ applyMaterialMapProperties(material, preset.mapProperties);
162
+ applyMaterialPresetTextures(material, preset);
163
+ }
164
+ }
165
+ export function createMaterialFromPreset(preset) {
166
+ const cacheKey = JSON.stringify(preset);
167
+ if (materialCache.has(cacheKey)) {
168
+ return materialCache.get(cacheKey);
169
+ }
170
+ const material = new THREE.MeshStandardMaterial();
171
+ applyMaterialPresetToMaterials(material, preset);
172
+ materialCache.set(cacheKey, material);
173
+ return material;
174
+ }
175
+ export function createMaterialFromPresetRef(materialPreset) {
176
+ const preset = getMaterialPresetByRef(materialPreset);
177
+ if (!preset)
178
+ return null;
179
+ return createMaterialFromPreset(preset);
180
+ }
12
181
  export function createMaterial(material) {
13
182
  const props = resolveMaterial(material);
14
- const cacheKey = getCacheKey(props);
183
+ const cacheKey = `${getCacheKey(props)}-${getTextureKey(material)}`;
15
184
  if (materialCache.has(cacheKey)) {
16
185
  return materialCache.get(cacheKey);
17
186
  }
187
+ const map = getTexture(material);
18
188
  const threeMaterial = new THREE.MeshStandardMaterial({
19
189
  color: props.color,
20
190
  roughness: props.roughness,
@@ -22,6 +192,7 @@ export function createMaterial(material) {
22
192
  opacity: props.opacity,
23
193
  transparent: props.transparent,
24
194
  side: sideMap[props.side],
195
+ map,
25
196
  });
26
197
  materialCache.set(cacheKey, threeMaterial);
27
198
  return threeMaterial;
@@ -56,4 +227,9 @@ export function clearMaterialCache() {
56
227
  material.dispose();
57
228
  }
58
229
  materialCache.clear();
230
+ for (const texture of textureCache.values()) {
231
+ texture.dispose();
232
+ }
233
+ textureCache.clear();
234
+ textureLoadPromises.clear();
59
235
  }
@@ -1 +1 @@
1
- {"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
@@ -440,10 +440,17 @@ export class MergedOutlineNode extends TempNode {
440
440
  }
441
441
  _buildCache(objects, cache) {
442
442
  for (const obj of objects) {
443
- obj.traverse((child) => {
444
- if (child.isMesh || child.isSprite)
445
- cache.add(child);
446
- });
443
+ if (!obj || !obj.traverse)
444
+ continue;
445
+ try {
446
+ obj.traverse((child) => {
447
+ if (child.isMesh || child.isSprite)
448
+ cache.add(child);
449
+ });
450
+ }
451
+ catch {
452
+ // Skip objects that were disposed or removed from the scene graph
453
+ }
447
454
  }
448
455
  }
449
456
  }
@@ -61,7 +61,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
61
61
  cameraMode: "perspective" | "orthographic";
62
62
  theme: "light" | "dark";
63
63
  unit: "metric" | "imperial";
64
- levelMode: "stacked" | "exploded" | "solo" | "manual";
64
+ levelMode: "manual" | "stacked" | "exploded" | "solo";
65
65
  wallMode: "up" | "cutaway" | "down";
66
66
  projectPreferences: Record<string, {
67
67
  showScans?: boolean;
@@ -78,7 +78,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
78
78
  cameraMode: "perspective" | "orthographic";
79
79
  theme: "light" | "dark";
80
80
  unit: "metric" | "imperial";
81
- levelMode: "stacked" | "exploded" | "solo" | "manual";
81
+ levelMode: "manual" | "stacked" | "exploded" | "solo";
82
82
  wallMode: "up" | "cutaway" | "down";
83
83
  projectPreferences: Record<string, {
84
84
  showScans?: boolean;
@@ -0,0 +1,5 @@
1
+ import { type RoofNode } from '@pascal-app/core';
2
+ import * as THREE from 'three';
3
+ export type RoofMaterialArray = [THREE.Material, THREE.Material, THREE.Material, THREE.Material];
4
+ export declare function getRoofMaterialArray(node: RoofNode): RoofMaterialArray | null;
5
+ //# sourceMappingURL=roof-materials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAiBhG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI,CAmB7E"}
@@ -0,0 +1,29 @@
1
+ import { getEffectiveRoofSurfaceMaterial, } from '@pascal-app/core';
2
+ import * as THREE from 'three';
3
+ import { createMaterial, createMaterialFromPresetRef } from '../../lib/materials';
4
+ function createResolvedMaterial(material, materialPreset) {
5
+ if (materialPreset) {
6
+ return createMaterialFromPresetRef(materialPreset);
7
+ }
8
+ if (material) {
9
+ return createMaterial(material);
10
+ }
11
+ return null;
12
+ }
13
+ export function getRoofMaterialArray(node) {
14
+ const top = getEffectiveRoofSurfaceMaterial(node, 'top');
15
+ const edge = getEffectiveRoofSurfaceMaterial(node, 'edge');
16
+ const wall = getEffectiveRoofSurfaceMaterial(node, 'wall');
17
+ const topMaterial = createResolvedMaterial(top.material, top.materialPreset);
18
+ const edgeMaterial = createResolvedMaterial(edge.material, edge.materialPreset);
19
+ const wallMaterial = createResolvedMaterial(wall.material, wall.materialPreset);
20
+ if (!(topMaterial || edgeMaterial || wallMaterial)) {
21
+ return null;
22
+ }
23
+ return [
24
+ edgeMaterial ?? wallMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
25
+ wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
26
+ wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
27
+ topMaterial ?? wallMaterial ?? edgeMaterial ?? new THREE.MeshStandardMaterial(),
28
+ ];
29
+ }
@@ -0,0 +1,7 @@
1
+ import { type StairNode, type StairSegmentNode } from '@pascal-app/core';
2
+ import type * as THREE from 'three';
3
+ export type StairBodyMaterials = [THREE.Material, THREE.Material];
4
+ export declare function getStairBodyMaterials(stair: StairNode): StairBodyMaterials;
5
+ export declare function getStairRailingMaterial(stair: StairNode): THREE.Material;
6
+ export declare function getStraightStairSegmentBodyMaterials(segment: StairSegmentNode, parentNode?: StairNode): StairBodyMaterials;
7
+ //# sourceMappingURL=stair-materials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stair-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/stair/stair-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAOnC,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAiBjE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,kBAAkB,CAQ1E;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAAC,QAAQ,CAGxE;AAED,wBAAgB,oCAAoC,CAClD,OAAO,EAAE,gBAAgB,EACzB,UAAU,CAAC,EAAE,SAAS,GACrB,kBAAkB,CAWpB"}
@@ -0,0 +1,33 @@
1
+ import { getEffectiveStairSurfaceMaterial, } from '@pascal-app/core';
2
+ import { createMaterial, createMaterialFromPresetRef, DEFAULT_STAIR_MATERIAL, } from '../../lib/materials';
3
+ function createResolvedMaterial(material, materialPreset) {
4
+ if (materialPreset) {
5
+ return createMaterialFromPresetRef(materialPreset) ?? DEFAULT_STAIR_MATERIAL;
6
+ }
7
+ if (material) {
8
+ return createMaterial(material);
9
+ }
10
+ return DEFAULT_STAIR_MATERIAL;
11
+ }
12
+ export function getStairBodyMaterials(stair) {
13
+ const tread = getEffectiveStairSurfaceMaterial(stair, 'tread');
14
+ const side = getEffectiveStairSurfaceMaterial(stair, 'side');
15
+ return [
16
+ createResolvedMaterial(tread.material, tread.materialPreset),
17
+ createResolvedMaterial(side.material, side.materialPreset),
18
+ ];
19
+ }
20
+ export function getStairRailingMaterial(stair) {
21
+ const railing = getEffectiveStairSurfaceMaterial(stair, 'railing');
22
+ return createResolvedMaterial(railing.material, railing.materialPreset);
23
+ }
24
+ export function getStraightStairSegmentBodyMaterials(segment, parentNode) {
25
+ if (segment.material !== undefined || typeof segment.materialPreset === 'string') {
26
+ const override = createResolvedMaterial(segment.material, segment.materialPreset);
27
+ return [override, override];
28
+ }
29
+ if (parentNode) {
30
+ return getStairBodyMaterials(parentNode);
31
+ }
32
+ return [DEFAULT_STAIR_MATERIAL, DEFAULT_STAIR_MATERIAL];
33
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAsMA,eAAO,MAAM,UAAU,YAoHtB,CAAA"}
1
+ {"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAsCA,eAAO,MAAM,UAAU,YAkHtB,CAAA"}