@pascal-app/viewer 1.0.0 → 1.0.1

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 (39) hide show
  1. package/README.md +12 -0
  2. package/dist/capture/capture-runtime.js +2 -32
  3. package/dist/capture/layers/device-motion-layer.d.ts.map +1 -1
  4. package/dist/capture/layers/device-motion-layer.js +5 -7
  5. package/dist/capture/stream-rendering.d.ts.map +1 -1
  6. package/dist/capture/stream-rendering.js +2 -1
  7. package/dist/capture/use-json-artifact.d.ts +2 -0
  8. package/dist/capture/use-json-artifact.d.ts.map +1 -0
  9. package/dist/capture/use-json-artifact.js +42 -0
  10. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  11. package/dist/components/viewer/glb-scene.js +3 -1
  12. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  13. package/dist/components/viewer/post-processing.js +6 -0
  14. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  15. package/dist/components/viewer/selection-manager.js +8 -16
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/lib/isolation.d.ts +14 -30
  20. package/dist/lib/isolation.d.ts.map +1 -1
  21. package/dist/lib/isolation.js +62 -66
  22. package/dist/lib/materials.d.ts.map +1 -1
  23. package/dist/lib/materials.js +2 -1
  24. package/dist/lib/scene-visibility.js +3 -1
  25. package/dist/lib/snapshot-pipeline.d.ts +3 -1
  26. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  27. package/dist/lib/snapshot-pipeline.js +32 -16
  28. package/dist/store/use-viewer.d.ts +3 -1
  29. package/dist/store/use-viewer.d.ts.map +1 -1
  30. package/dist/store/use-viewer.js +2 -0
  31. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  32. package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
  33. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  34. package/dist/systems/geometry/geometry-system.js +4 -0
  35. package/dist/systems/window/window-system.d.ts +1 -1
  36. package/dist/systems/window/window-system.d.ts.map +1 -1
  37. package/dist/systems/zone/zone-system.d.ts.map +1 -1
  38. package/dist/systems/zone/zone-system.js +28 -2
  39. package/package.json +3 -3
package/README.md CHANGED
@@ -175,6 +175,18 @@ colors; dollhouse enables front-face rendering for surface previews and room mod
175
175
  inward-facing room surfaces from outside. It changes per-instance materials, not geometry or
176
176
  loader-cached materials. Omitting these options preserves the existing presentation.
177
177
 
178
+ ## Scene Snapshots
179
+
180
+ `createSnapshotPipeline` renders offscreen snapshots with the scene theme's backdrop by default.
181
+ Inside a `Viewer`, pass the source returned by `useSceneAtmosphere()` as `atmosphere` to capture
182
+ the active presentation's sky instead. Recreate the pipeline when that source changes; updates
183
+ to its existing uniforms do not require rebuilding it.
184
+
185
+ Use `applyEnvironment({ ..., transparent: true })` to retain scene alpha without compositing a
186
+ backdrop. The snapshot scene pass owns its transparent clear so intermediate post-processing
187
+ passes cannot turn empty sky into opaque pixels. Dispose the pipeline when it is replaced or
188
+ no longer needed.
189
+
178
190
  ## Viewer State
179
191
 
180
192
  ```typescript
@@ -7,7 +7,6 @@ import { Suspense, useCallback, useEffect, useMemo, useRef, useState, } from 're
7
7
  import { ErrorBoundary } from '../components/error-boundary';
8
8
  import { useNodeEvents } from '../hooks/use-node-events';
9
9
  import useViewer from '../store/use-viewer';
10
- import { rewriteLoopbackAssetUrl } from './asset-url';
11
10
  import { resolveCaptureFrameMatrix } from './frame';
12
11
  import { isCaptureSessionVisible, isCaptureStreamVisible } from './layer-visibility';
13
12
  import { CaptureDeviceMotionLayer } from './layers/device-motion-layer';
@@ -17,6 +16,7 @@ import { CaptureSurfaceMeshLayer } from './layers/surface-mesh-layer';
17
16
  import { useCaptureSource } from './source-state';
18
17
  import { captureModelFormat, isCaptureModelArtifact, isCapturePointCloudArtifact, isCaptureStreamRenderable, streamHydratesJsonPayload, } from './stream-rendering';
19
18
  import { parseDeviceTrajectoryPackets, parseDeviceTrajectoryPayload } from './trajectory';
19
+ import { useJsonArtifactPayload } from './use-json-artifact';
20
20
  const EMPTY_RENDERERS = {};
21
21
  const EMPTY_LAYER_VISIBILITY = {};
22
22
  export function CaptureRuntime({ defaultLayerVisibility = EMPTY_LAYER_VISIBILITY, maxPacketsPerStream = 32, onError, renderers = EMPTY_RENDERERS, resolveSource, retryKey = 0, }) {
@@ -135,7 +135,7 @@ export function CaptureStreamLayer({ descriptor, meshPresentation, packets, rend
135
135
  if (Renderer) {
136
136
  content = (_jsx(Renderer, { artifactUrl: artifactUrl, descriptor: descriptor, meshPresentation: meshPresentation, packets: packets, scan: scan, source: source, stream: stream, streamEpoch: streamEpoch }));
137
137
  }
138
- else if (layerKey === 'model' &&
138
+ else if ((layerKey === 'model' || layerKey === 'spaceformScene') &&
139
139
  isCaptureModelArtifact(stream.artifact) &&
140
140
  stream.artifact &&
141
141
  artifactUrl) {
@@ -154,36 +154,6 @@ export function CaptureStreamLayer({ descriptor, meshPresentation, packets, rend
154
154
  return content;
155
155
  return (_jsx("group", { matrix: frameMatrix, matrixAutoUpdate: false, children: content }));
156
156
  }
157
- function useJsonArtifactPayload(url) {
158
- const [error, setError] = useState(null);
159
- const [payload, setPayload] = useState(null);
160
- useEffect(() => {
161
- setError(null);
162
- setPayload(null);
163
- if (!url)
164
- return;
165
- const abort = new AbortController();
166
- void fetch(rewriteLoopbackAssetUrl(url), { signal: abort.signal })
167
- .then(async (response) => {
168
- if (!response.ok)
169
- throw new Error(`Could not load ${url}: ${response.status}`);
170
- return (await response.json());
171
- })
172
- .then((data) => {
173
- if (!abort.signal.aborted)
174
- setPayload(data);
175
- })
176
- .catch((cause) => {
177
- if (abort.signal.aborted)
178
- return;
179
- setError(cause instanceof Error ? cause : new Error(`Could not load ${url}.`));
180
- });
181
- return () => abort.abort();
182
- }, [url]);
183
- if (error)
184
- throw error;
185
- return payload;
186
- }
187
157
  function useResolvedArtifact(source, artifact) {
188
158
  const [error, setError] = useState(null);
189
159
  const [url, setUrl] = useState(null);
@@ -1 +1 @@
1
- {"version":3,"file":"device-motion-layer.d.ts","sourceRoot":"","sources":["../../../src/capture/layers/device-motion-layer.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,eAAe,CAAA;AAE7E,eAAO,MAAM,4BAA4B,IAAI,CAAA;AAE7C,wBAAgB,wBAAwB,CAAC,EACvC,SAAe,EACf,UAAU,GACX,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,gBAAgB,CAAA;CAC7B,+BAiDA"}
1
+ {"version":3,"file":"device-motion-layer.d.ts","sourceRoot":"","sources":["../../../src/capture/layers/device-motion-layer.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,eAAe,CAAA;AAE7E,eAAO,MAAM,4BAA4B,IAAI,CAAA;AAE7C,wBAAgB,wBAAwB,CAAC,EACvC,SAAe,EACf,UAAU,GACX,EAAE;IACD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,gBAAgB,CAAA;CAC7B,+BA2CA"}
@@ -36,7 +36,7 @@ export function CaptureDeviceMotionLayer({ lineWidth = 2.5, trajectory, }) {
36
36
  deviceRef.current.position.copy(position);
37
37
  deviceRef.current.quaternion.slerpQuaternions(fromQuaternion, toQuaternion, frame.alpha);
38
38
  });
39
- return (_jsxs("group", { children: [trajectorySegments.map(({ points, segment }) => (_jsx(CaptureLine, { color: "#222326", lineWidth: lineWidth, opacity: 0.78, points: points }, segment))), _jsx("group", { ref: deviceRef, children: _jsx(CameraFrustum, { lineWidth: lineWidth }) })] }));
39
+ return (_jsxs("group", { children: [trajectorySegments.map(({ points, segment }) => (_jsx(CaptureLine, { color: "#39ff14", lineWidth: lineWidth, points: points }, segment))), _jsx("group", { ref: deviceRef, children: _jsx(CameraFrustum, { lineWidth: lineWidth }) })] }));
40
40
  }
41
41
  function CameraFrustum({ lineWidth }) {
42
42
  const apex = [0, 0, 0];
@@ -52,24 +52,22 @@ function CameraFrustum({ lineWidth }) {
52
52
  [bottomLeft, bottomRight],
53
53
  [bottomRight, topRight],
54
54
  ].flat();
55
- return (_jsxs("group", { children: [_jsx(CaptureLine, { color: "#f5b900", lineWidth: lineWidth, points: points, segments: true }), _jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.025, 12, 12] }), _jsx("meshBasicMaterial", { color: "#ffd84d" })] })] }));
55
+ return (_jsxs("group", { scale: 1.35, children: [_jsx(CaptureLine, { color: "#ffee00", lineWidth: lineWidth, points: points, segments: true }), _jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.035, 12, 12] }), _jsx("meshBasicMaterial", { color: "#ffee00", toneMapped: false })] })] }));
56
56
  }
57
- function CaptureLine({ color, lineWidth, opacity = 1, points, segments = false, }) {
57
+ function CaptureLine({ color, lineWidth, points, segments = false, }) {
58
58
  const line = useMemo(() => {
59
59
  const geometry = new BufferGeometry().setFromPoints(points.map(([x, y, z]) => new Vector3(x, y, z)));
60
60
  const material = new LineBasicMaterial({
61
61
  color,
62
- depthWrite: opacity >= 1,
63
62
  linewidth: lineWidth,
64
- opacity,
65
- transparent: opacity < 1,
63
+ toneMapped: false,
66
64
  });
67
65
  const object = segments
68
66
  ? new LineSegments(geometry, material)
69
67
  : new ThreeLine(geometry, material);
70
68
  object.frustumCulled = false;
71
69
  return object;
72
- }, [color, lineWidth, opacity, points, segments]);
70
+ }, [color, lineWidth, points, segments]);
73
71
  useEffect(() => () => {
74
72
  line.geometry.dispose();
75
73
  line.material.dispose();
@@ -1 +1 @@
1
- {"version":3,"file":"stream-rendering.d.ts","sourceRoot":"","sources":["../../src/capture/stream-rendering.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAK7B,MAAM,0BAA0B,CAAA;AAOjC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAA;AAEhD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,uBAAuB,EAC/B,kBAAkB,GAAE,WAAW,CAAC,MAAM,CAAa,GAClD,OAAO,CA0BT;AAID;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAKlF;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAE9F;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAC7C,kBAAkB,GAAG,IAAI,CAS3B;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAC7C,OAAO,CAGT"}
1
+ {"version":3,"file":"stream-rendering.d.ts","sourceRoot":"","sources":["../../src/capture/stream-rendering.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAK7B,MAAM,0BAA0B,CAAA;AAOjC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAA;AAEhD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,uBAAuB,EAC/B,kBAAkB,GAAE,WAAW,CAAC,MAAM,CAAa,GAClD,OAAO,CA4BT;AAID;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAKlF;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAAG,OAAO,CAE9F;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAC7C,kBAAkB,GAAG,IAAI,CAS3B;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAC7C,OAAO,CAGT"}
@@ -9,8 +9,9 @@ export function isCaptureStreamRenderable(stream, customRendererKeys = new Set()
9
9
  const layerKey = captureLayerKey(stream);
10
10
  if (customRendererKeys.has(layerKey) || customRendererKeys.has(stream.kind))
11
11
  return true;
12
- if (layerKey === 'model')
12
+ if (layerKey === 'model' || layerKey === 'spaceformScene') {
13
13
  return isCaptureModelArtifact(stream.artifact);
14
+ }
14
15
  if (layerKey === 'deviceMotion') {
15
16
  return (stream.availability === 'live' ||
16
17
  streamHydratesJsonPayload(stream) ||
@@ -0,0 +1,2 @@
1
+ export declare function useJsonArtifactPayload(url: string | null): unknown;
2
+ //# sourceMappingURL=use-json-artifact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-json-artifact.d.ts","sourceRoot":"","sources":["../../src/capture/use-json-artifact.ts"],"names":[],"mappings":"AAgBA,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAgClE"}
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { FileLoader } from 'three';
3
+ import { rewriteLoopbackAssetUrl } from './asset-url';
4
+ const requests = new Map();
5
+ function loadJsonArtifact(url) {
6
+ const cached = requests.get(url);
7
+ if (cached)
8
+ return cached;
9
+ const request = new FileLoader().setResponseType('json').loadAsync(url);
10
+ requests.set(url, request);
11
+ // Keep successful downloads across view switches, but let a remount retry failures.
12
+ void request.catch(() => requests.delete(url));
13
+ return request;
14
+ }
15
+ export function useJsonArtifactPayload(url) {
16
+ const resolvedUrl = url ? rewriteLoopbackAssetUrl(url) : null;
17
+ const [result, setResult] = useState(null);
18
+ useEffect(() => {
19
+ if (!resolvedUrl)
20
+ return;
21
+ let active = true;
22
+ void loadJsonArtifact(resolvedUrl).then((payload) => {
23
+ if (active)
24
+ setResult({ url: resolvedUrl, payload });
25
+ }, (cause) => {
26
+ if (active) {
27
+ setResult({
28
+ url: resolvedUrl,
29
+ error: cause instanceof Error ? cause : new Error('Could not load capture data.'),
30
+ });
31
+ }
32
+ });
33
+ return () => {
34
+ active = false;
35
+ };
36
+ }, [resolvedUrl]);
37
+ if (!result || result.url !== resolvedUrl)
38
+ return null;
39
+ if (result.error)
40
+ throw result.error;
41
+ return result.payload ?? null;
42
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAkC,MAAM,kBAAkB,CAAA;AAc/E,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAqB3E,2EAA2E;AAC3E,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/D,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEzE,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAE7D;8EAC8E;AAC9E,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CAChD,GAAG,IAAI,CAAA;AA8NR;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,mBAAmB,GACpB,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;IACX;oFACgF;IAChF,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,CAAA;IACvC;mFAC+E;IAC/E,cAAc,CAAC,EAAE,OAAO,EAAE,CAAA;IAC1B;kFAC8E;IAC9E,YAAY,CAAC,EAAE,OAAO,EAAE,CAAA;IACxB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IAC7C,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAA;IAClD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IACzC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CACtD,+BAgzBA"}
1
+ {"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAkC,MAAM,kBAAkB,CAAA;AAc/E,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAqB3E,2EAA2E;AAC3E,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/D,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEzE,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAE7D;8EAC8E;AAC9E,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CAChD,GAAG,IAAI,CAAA;AA8NR;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,mBAAmB,GACpB,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;IACX;oFACgF;IAChF,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,CAAA;IACvC;mFAC+E;IAC/E,cAAc,CAAC,EAAE,OAAO,EAAE,CAAA;IAC1B;kFAC8E;IAC9E,YAAY,CAAC,EAAE,OAAO,EAAE,CAAA;IACxB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IAC7C,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAA;IAClD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IACzC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CACtD,+BAkzBA"}
@@ -483,7 +483,9 @@ export function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes,
483
483
  const targetY = baseY + (exploded ? index * EXPLODED_GAP : 0);
484
484
  // Snap (not lerp) in walkthrough so the first-person collider, built from
485
485
  // these world positions, matches the stacked building immediately.
486
- node.position.y = walkthroughMode ? targetY : lerp(node.position.y, targetY, delta * 12);
486
+ node.position.y = walkthroughMode
487
+ ? targetY
488
+ : lerp(node.position.y, targetY, Math.min(1, delta * 12));
487
489
  // Solo: hidden levels above the soloed one keep casting shadows
488
490
  // (shadow-caster-only); below-levels can't block the sun, so plain-hide.
489
491
  const hidden = !walkthroughMode && levelMode === 'solo' && Boolean(selectedLevel) && id !== selectedLevel;
@@ -1 +1 @@
1
- {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA6CA,eAAO,MAAM,YAAY;;;CAGxB,CAAA;AAGD,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAgED,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AA+CD,QAAA,MAAM,oBAAoB,GAAI,iCAG3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,SA+mBA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
1
+ {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA8CA,eAAO,MAAM,YAAY;;;CAGxB,CAAA;AAGD,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAgED,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AA+CD,QAAA,MAAM,oBAAoB,GAAI,iCAG3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,SAmnBA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
@@ -9,6 +9,7 @@ import { backdropGradient, deepSkyColor, horizonHazeColor } from '../../lib/back
9
9
  import { edgeColorFor, edgeOpacityScaleFor } from '../../lib/edge-style';
10
10
  import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
11
11
  import { inkedEdges } from '../../lib/ink-edges';
12
+ import { refreshIsolation } from '../../lib/isolation';
12
13
  import { LayerPassIndex, LayerPassNode } from '../../lib/layer-pass';
13
14
  import { GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
14
15
  import { mergedOutline } from '../../lib/merged-outline-node';
@@ -612,6 +613,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
612
613
  inkColorUniform.current.value.set(edgeColorFor(bgHex));
613
614
  inkOpacityScaleUniform.current.value = edgeOpacityScaleFor(bgHex);
614
615
  const outliner = useViewer.getState().outliner;
616
+ const restoreAtmosphere = refreshIsolation(scene);
615
617
  sanitizeOutlineObjects(outliner.selectedObjects);
616
618
  sanitizeOutlineObjects(outliner.hoveredObjects);
617
619
  if (disablePostFx ||
@@ -644,6 +646,7 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
644
646
  }
645
647
  finally {
646
648
  scene.backgroundNode = previousBackgroundNode;
649
+ restoreAtmosphere();
647
650
  }
648
651
  return;
649
652
  }
@@ -681,6 +684,9 @@ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, disablePostF
681
684
  console.error('[viewer/post-processing] Retries exhausted. Rendering without post FX for this session.');
682
685
  }
683
686
  }
687
+ finally {
688
+ restoreAtmosphere();
689
+ }
684
690
  }, 1);
685
691
  return null;
686
692
  };
@@ -1 +1 @@
1
- {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAkTA,eAAO,MAAM,gBAAgB,mCA0G5B,CAAA"}
1
+ {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAqSA,eAAO,MAAM,gBAAgB,mCA0G5B,CAAA"}
@@ -40,25 +40,15 @@ const pointInPolygonWithTolerance = (x, z, polygon) => {
40
40
  const expanded = expandPolygon(polygon, EDGE_TOLERANCE);
41
41
  return pointInPolygon(x, z, expanded);
42
42
  };
43
- // Check if a node belongs to the selected level (directly or via wall parent)
44
43
  const isNodeOnLevel = (node, levelId) => {
45
44
  const nodes = useScene.getState().nodes;
46
- // Direct child of level
47
- if (node.parentId === levelId)
48
- return true;
49
- // Wall-attached nodes (window/door/item): check if parent wall is on the level
50
- if ((node.type === 'item' || node.type === 'window' || node.type === 'door') && node.parentId) {
51
- const parentNode = nodes[node.parentId];
52
- if (parentNode?.type === 'wall' && parentNode.parentId === levelId) {
53
- return true;
54
- }
55
- // Ceiling/slab/roof-attached items: check if parent structure is on the level
56
- if ((parentNode?.type === 'ceiling' ||
57
- parentNode?.type === 'slab' ||
58
- parentNode?.type === 'roof') &&
59
- parentNode.parentId === levelId) {
45
+ const seen = new Set();
46
+ let parentId = node.parentId;
47
+ while (parentId && !seen.has(parentId)) {
48
+ if (parentId === levelId)
60
49
  return true;
61
- }
50
+ seen.add(parentId);
51
+ parentId = nodes[parentId]?.parentId ?? null;
62
52
  }
63
53
  return false;
64
54
  };
@@ -187,6 +177,7 @@ const getStrategy = () => {
187
177
  'roof-segment',
188
178
  'window',
189
179
  'door',
180
+ ...getSelectableKinds(),
190
181
  ],
191
182
  handleClick: (node, nativeEvent) => {
192
183
  let nodeToSelect = node;
@@ -224,6 +215,7 @@ const getStrategy = () => {
224
215
  'roof-segment',
225
216
  'window',
226
217
  'door',
218
+ ...getSelectableKinds(),
227
219
  ];
228
220
  if (!validTypes.includes(node.type))
229
221
  return false;
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export { disposeObject3DResources } from './lib/dispose-object3d';
25
25
  export type { EdgeMode } from './lib/edge-style';
26
26
  export { PERF_OVERLAY_ENABLED } from './lib/gpu-perf';
27
27
  export { computeHeroFraming, DEFAULT_FRAMING_EXCLUDED_TYPES, type HeroFraming, heroCameraPose, temporarilyHideNodeTypes, unionRegisteredNodeBounds, } from './lib/hero-pose';
28
- export { applyIsolation, clearIsolation, collectIsolationSubtree, isIsolationActive, } from './lib/isolation';
28
+ export { applyIsolation, clearIsolation, collectIsolationSubtree, isIsolationActive, refreshIsolation, } from './lib/isolation';
29
29
  export { configureKtx2Support, ensureKtx2Support } from './lib/ktx2-loader';
30
30
  export { LayerPassIndex } from './lib/layer-pass';
31
31
  export { BATCHED_LAYER, GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, SHADOW_ONLY_LAYER, setSurfaceRaycastLayers, ZONE_LAYER, } from './lib/layers';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ export { ADDITION, Brush, computeGeometryBoundsTree, csgEvaluator, csgGeometry,
35
35
  export { disposeObject3DResources } from './lib/dispose-object3d';
36
36
  export { PERF_OVERLAY_ENABLED } from './lib/gpu-perf';
37
37
  export { computeHeroFraming, DEFAULT_FRAMING_EXCLUDED_TYPES, heroCameraPose, temporarilyHideNodeTypes, unionRegisteredNodeBounds, } from './lib/hero-pose';
38
- export { applyIsolation, clearIsolation, collectIsolationSubtree, isIsolationActive, } from './lib/isolation';
38
+ export { applyIsolation, clearIsolation, collectIsolationSubtree, isIsolationActive, refreshIsolation, } from './lib/isolation';
39
39
  export { configureKtx2Support, ensureKtx2Support } from './lib/ktx2-loader';
40
40
  export { LayerPassIndex } from './lib/layer-pass';
41
41
  export { BATCHED_LAYER, GRID_LAYER, OVERLAY_LAYER, SCENE_LAYER, SHADOW_ONLY_LAYER, setSurfaceRaycastLayers, ZONE_LAYER, } from './lib/layers';
@@ -1,39 +1,23 @@
1
1
  import type { AnyNodeId } from '@pascal-app/core';
2
2
  import type { Object3D } from 'three';
3
- /** True while an isolation filter is applied (see {@link applyIsolation}). */
3
+ /** True while a subtree is isolated, including before its lazy meshes mount. */
4
4
  export declare function isIsolationActive(): boolean;
5
- /**
6
- * Compute the union of every isolated subtree's `Object3D` descendants.
7
- *
8
- * Pure traversal — exported so future "focus mode" / debug tooling can
9
- * reuse the same definition of "what's in the isolated set". Each root
10
- * is walked via `Object3D.traverse` (the live Three.js graph, not the
11
- * data-model `children` array — those can disagree when systems mount
12
- * synthesized sub-meshes that the data model doesn't track).
13
- */
5
+ /** Include synthesized meshes below a selected native root, including hosted openings. */
14
6
  export declare function collectIsolationSubtree(ids: ReadonlyArray<string>): Set<Object3D>;
15
7
  /**
16
- * Imperative visibility filter on the live `sceneRegistry`. Hides every
17
- * registered group (and its synthesized child meshes) outside the
18
- * isolated subtree by taking it off the scene layer.
19
- *
20
- * Why layers instead of `obj.visible = false`? Three.js's visibility
21
- * flag *cascades* — hiding a parent hides every descendant — so we
22
- * can't hide a host wall while keeping a door rendered inside it.
23
- * Layer masks are per-object and don't cascade: `WebGLRenderer
24
- * .projectObject` skips objects whose layer mask doesn't intersect the
25
- * camera's, but always recurses into their children. So we can hide
26
- * the wall and the door (hosted under it in the scene graph) still
27
- * renders, with its local position relative to the wall preserved
28
- * automatically by the matrix walk.
29
- *
30
- * The mask itself belongs to `lib/scene-visibility.ts`, which reconciles
31
- * isolation with solo's shadow-caster pass, so {@link clearIsolation}
32
- * gives an object back only what isolation took.
33
- *
34
- * Pass `null` to clear isolation (equivalent to calling
35
- * {@link clearIsolation}).
8
+ * Select native subtrees without mutating the saved scene or cascading visibility
9
+ * through their hosts. The next draw also filters unregistered presentation geometry.
36
10
  */
37
11
  export declare function applyIsolation(ids: ReadonlyArray<AnyNodeId> | null): void;
12
+ /**
13
+ * Reconcile immediately before viewport or snapshot rendering. Environment, sky,
14
+ * ground and instance batches can live outside sceneRegistry or mount asynchronously.
15
+ * Lights/cameras remain usable; masks do not cascade, so an isolated door can still
16
+ * render under its hidden wall. Collective renderers fall back to native geometry.
17
+ * The returned cleanup restores fog after the synchronous draw, before atmosphere
18
+ * owners can change it again. Do not hold this cleanup across async GPU readback.
19
+ */
20
+ export declare function refreshIsolation(scene?: Object3D): () => void;
21
+ /** Restore even detached/unregistered meshes, without undoing solo or batching holds. */
38
22
  export declare function clearIsolation(): void;
39
23
  //# sourceMappingURL=isolation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"isolation.d.ts","sourceRoot":"","sources":["../../src/lib/isolation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AASrC,8EAA8E;AAC9E,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,CAUjF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,IAAI,CAiBzE;AAUD,wBAAgB,cAAc,IAAI,IAAI,CAUrC"}
1
+ {"version":3,"file":"isolation.d.ts","sourceRoot":"","sources":["../../src/lib/isolation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,OAAO,CAAA;AAQ5C,gFAAgF;AAChF,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,0FAA0F;AAC1F,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,CASjF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,IAAI,CAOzE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,IAAI,CA6B7D;AAED,yFAAyF;AACzF,wBAAgB,cAAc,IAAI,IAAI,CAKrC"}
@@ -1,91 +1,87 @@
1
1
  'use client';
2
2
  import { sceneRegistry } from '@pascal-app/core';
3
3
  import { hideFromScene, showInScene } from './scene-visibility';
4
- // Whether a subtree is currently isolated (some objects are held off the
5
- // scene layer). Read by consumers that must not act on the partial view — e.g.
6
- // the project-thumbnail autosave skips capturing while isolated so it never
7
- // snapshots a single focused item as the whole project's thumbnail.
8
- let isolationActive = false;
9
- /** True while an isolation filter is applied (see {@link applyIsolation}). */
4
+ let isolatedIds = null;
5
+ let heldObjects = new Set();
6
+ let liveScene;
7
+ const noop = () => { };
8
+ /** True while a subtree is isolated, including before its lazy meshes mount. */
10
9
  export function isIsolationActive() {
11
- return isolationActive;
10
+ return isolatedIds !== null;
12
11
  }
13
- /**
14
- * Compute the union of every isolated subtree's `Object3D` descendants.
15
- *
16
- * Pure traversal — exported so future "focus mode" / debug tooling can
17
- * reuse the same definition of "what's in the isolated set". Each root
18
- * is walked via `Object3D.traverse` (the live Three.js graph, not the
19
- * data-model `children` array — those can disagree when systems mount
20
- * synthesized sub-meshes that the data model doesn't track).
21
- */
12
+ /** Include synthesized meshes below a selected native root, including hosted openings. */
22
13
  export function collectIsolationSubtree(ids) {
23
14
  const keep = new Set();
24
15
  for (const id of ids) {
25
16
  const root = sceneRegistry.nodes.get(id);
26
- if (!root)
27
- continue;
28
- root.traverse((child) => {
17
+ root?.traverse((child) => {
29
18
  keep.add(child);
30
19
  });
31
20
  }
32
21
  return keep;
33
22
  }
34
23
  /**
35
- * Imperative visibility filter on the live `sceneRegistry`. Hides every
36
- * registered group (and its synthesized child meshes) outside the
37
- * isolated subtree by taking it off the scene layer.
38
- *
39
- * Why layers instead of `obj.visible = false`? Three.js's visibility
40
- * flag *cascades* — hiding a parent hides every descendant — so we
41
- * can't hide a host wall while keeping a door rendered inside it.
42
- * Layer masks are per-object and don't cascade: `WebGLRenderer
43
- * .projectObject` skips objects whose layer mask doesn't intersect the
44
- * camera's, but always recurses into their children. So we can hide
45
- * the wall and the door (hosted under it in the scene graph) still
46
- * renders, with its local position relative to the wall preserved
47
- * automatically by the matrix walk.
48
- *
49
- * The mask itself belongs to `lib/scene-visibility.ts`, which reconciles
50
- * isolation with solo's shadow-caster pass, so {@link clearIsolation}
51
- * gives an object back only what isolation took.
52
- *
53
- * Pass `null` to clear isolation (equivalent to calling
54
- * {@link clearIsolation}).
24
+ * Select native subtrees without mutating the saved scene or cascading visibility
25
+ * through their hosts. The next draw also filters unregistered presentation geometry.
55
26
  */
56
27
  export function applyIsolation(ids) {
57
28
  if (ids == null || ids.length === 0) {
58
29
  clearIsolation();
59
30
  return;
60
31
  }
61
- const keep = collectIsolationSubtree(ids);
62
- // Iterate registered roots. For each one outside the keep set,
63
- // hide it and every descendant — *except* descendants that are
64
- // themselves in `keep` (a kept node nested under a non-kept host:
65
- // the isolated door under the hidden wall).
66
- for (const [, obj] of sceneRegistry.nodes) {
67
- if (keep.has(obj))
68
- continue;
69
- hideRecursive(obj, keep);
70
- }
71
- isolationActive = true;
32
+ isolatedIds = [...ids];
33
+ refreshIsolation();
72
34
  }
73
- function hideRecursive(obj, keep) {
74
- if (keep.has(obj))
75
- return;
76
- hideFromScene(obj, 'isolated');
77
- for (const child of obj.children) {
78
- hideRecursive(child, keep);
79
- }
35
+ /**
36
+ * Reconcile immediately before viewport or snapshot rendering. Environment, sky,
37
+ * ground and instance batches can live outside sceneRegistry or mount asynchronously.
38
+ * Lights/cameras remain usable; masks do not cascade, so an isolated door can still
39
+ * render under its hidden wall. Collective renderers fall back to native geometry.
40
+ * The returned cleanup restores fog after the synchronous draw, before atmosphere
41
+ * owners can change it again. Do not hold this cleanup across async GPU readback.
42
+ */
43
+ export function refreshIsolation(scene) {
44
+ if (!isolatedIds)
45
+ return noop;
46
+ if (scene)
47
+ liveScene = scene;
48
+ const keep = collectIsolationSubtree(isolatedIds);
49
+ const visited = new Set();
50
+ const hidden = new Set();
51
+ const visit = (object) => {
52
+ if (visited.has(object))
53
+ return;
54
+ visited.add(object);
55
+ const flags = object;
56
+ if (keep.has(object) || flags.isLight || flags.isCamera)
57
+ return;
58
+ hideFromScene(object, 'isolated');
59
+ hidden.add(object);
60
+ };
61
+ liveScene?.traverse(visit);
62
+ // Also covers not-yet-attached registered roots and imperative callers without a scene.
63
+ for (const object of sceneRegistry.nodes.values())
64
+ object.traverse(visit);
65
+ for (const object of heldObjects)
66
+ if (!hidden.has(object))
67
+ showInScene(object, 'isolated');
68
+ heldObjects = hidden;
69
+ const atmosphere = scene;
70
+ if (!atmosphere?.isScene)
71
+ return noop;
72
+ const { fog, fogNode } = atmosphere;
73
+ atmosphere.fog = null;
74
+ atmosphere.fogNode = null;
75
+ return () => {
76
+ atmosphere.fog = fog;
77
+ atmosphere.fogNode = fogNode;
78
+ };
80
79
  }
80
+ /** Restore even detached/unregistered meshes, without undoing solo or batching holds. */
81
81
  export function clearIsolation() {
82
- // We don't know which objects were touched without re-walking, so
83
- // walk every registered root + its descendants and drop the isolation
84
- // reason wherever it was set. `traverse` is cheap and idempotent here.
85
- for (const [, obj] of sceneRegistry.nodes) {
86
- obj.traverse((child) => {
87
- showInScene(child, 'isolated');
88
- });
89
- }
90
- isolationActive = false;
82
+ for (const object of heldObjects)
83
+ showInScene(object, 'isolated');
84
+ heldObjects.clear();
85
+ isolatedIds = null;
86
+ liveScene = undefined;
91
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,qBAAqB,EAE1B,KAAK,cAAc,EAGnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,uBAAuB,EAA4B,MAAM,cAAc,CAAA;AAOhF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAChD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAA;AAEjE,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAQpD,CAAA;AAID,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAQrD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAQpD,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAQzD,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAK5E,CAAA;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,WAAW,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAKR;AAOD,eAAO,MAAM,aAAa,yBAKxB,CAAA;AAsBF,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AA2ED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWtF;AAED,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAK/D;AA6SD,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,CACtC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,aAA0B,GAClC,KAAK,CAAC,QAAQ,CAchB;AAED,wBAAgB,2BAA2B,CACzC,cAAc,CAAC,EAAE,MAAM,EACvB,OAAO,GAAE,aAA0B,GAClC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAIvB;AAED,wBAAgB,cAAc,CAC5B,QAAQ,CAAC,EAAE,cAAc,EACzB,OAAO,GAAE,aAA0B,GAClC,KAAK,CAAC,QAAQ,CAqChB;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,cAAc,EAAE,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,EAClE,OAAO,GAAE,aAA0B,GAClC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAOvB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,aAA0B,EACnC,SAAS,SAAM,GACd,KAAK,CAAC,QAAQ,CAQhB;AAED,wBAAgB,qBAAqB,CACnC,KAAK,SAAY,EACjB,SAAS,SAAM,EACf,OAAO,GAAE,aAA0B,EACnC,IAAI,GAAE,KAAK,CAAC,IAAsB,GACjC,KAAK,CAAC,QAAQ,CAehB;AAmBD,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,KAAK,CAAC,IAAsB,EAClC,YAAY,CAAC,EAAE,MAAM,GACpB,KAAK,CAAC,QAAQ,CA8BhB;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAEhF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAEzF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CA2B3F;AAED,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAE5F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAEzF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAE1F;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,aAA0B,GAAG,KAAK,CAAC,QAAQ,CAE1F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAE9D;AAED,KAAK,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,CAAA;AAG7D,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAKtF;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAwBzC"}
1
+ {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"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"}
@@ -475,7 +475,8 @@ export function resolveSlotDefaultMaterial(slotDefault, shading = 'rendered', ro
475
475
  return (createMaterialFromPresetRef(slotDefault, shading) ??
476
476
  cachedDefaultMaterial(`slot-#ffffff-${roughness}`, '#ffffff', roughness, shading));
477
477
  }
478
- return cachedDefaultMaterial(`slot-${slotDefault}-${roughness}`, slotDefault, roughness, shading);
478
+ const color = slotDefault.startsWith('#') ? slotDefault.toLowerCase() : slotDefault;
479
+ return cachedDefaultMaterial(`slot-${color}-${roughness}`, color, roughness, shading);
479
480
  }
480
481
  export function createDefaultMaterial(color = '#ffffff', roughness = 0.9, shading = 'rendered', side = THREE.FrontSide) {
481
482
  const resolvedSide = resolveNodeMaterialSide(side);
@@ -60,10 +60,12 @@ function applyHold(obj, hold) {
60
60
  // A batched wall is both drawn and shadowed by the merged mesh, so it stays
61
61
  // out of the shadow pass too — enabling the shadow-only bit would submit its
62
62
  // triangles a second time, on top of the copy the batch already casts.
63
+ if (hold.reasons.has('isolated'))
64
+ obj.layers.disable(SHADOW_ONLY_LAYER);
63
65
  if (hold.reasons.has('batched') || hold.reasons.has('wall-batched')) {
64
66
  obj.layers.enable(BATCHED_LAYER);
65
67
  return;
66
68
  }
67
- if (hold.reasons.has('shadow-only'))
69
+ if (!hold.reasons.has('isolated') && hold.reasons.has('shadow-only'))
68
70
  obj.layers.enable(SHADOW_ONLY_LAYER);
69
71
  }
@@ -1,5 +1,6 @@
1
1
  import { type Camera, type Scene } from 'three';
2
2
  import { type WebGPURenderer } from 'three/webgpu';
3
+ import type { SceneAtmosphereSource } from '../components/viewer/scene-atmosphere';
3
4
  import { type EdgeMode } from './edge-style';
4
5
  export declare const THUMBNAIL_WIDTH = 1920;
5
6
  export declare const THUMBNAIL_HEIGHT = 1080;
@@ -43,9 +44,10 @@ export type SnapshotPipeline = {
43
44
  }) => Promise<SnapshotCaptureResult>;
44
45
  dispose: () => void;
45
46
  };
46
- export declare function createSnapshotPipeline({ renderer, scene, camera, }: {
47
+ export declare function createSnapshotPipeline({ renderer, scene, camera, atmosphere, }: {
47
48
  renderer: WebGPURenderer;
48
49
  scene: Scene;
49
50
  camera: Camera;
51
+ atmosphere?: SceneAtmosphereSource | null;
50
52
  }): Promise<SnapshotPipeline | null>;
51
53
  //# sourceMappingURL=snapshot-pipeline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/snapshot-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,KAAK,KAAK,EAAoB,MAAM,OAAO,CAAA;AAqBjF,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAA;AAGhF,OAAO,EAAE,KAAK,QAAQ,EAAqC,MAAM,cAAc,CAAA;AAK/E,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,eAAe,CAAA;AACzC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAUrC,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,MAAM,GACP,EAAE;QACD,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,QAAQ,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,KAAK,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,EACR,WAAW,EACX,UAAU,EACV,YAAY,GACb,EAAE;QACD,WAAW,CAAC,EAAE,mBAAmB,CAAA;QACjC,UAAU,CAAC,EAAE,kBAAkB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;KAC5B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED,wBAAsB,sBAAsB,CAAC,EAC3C,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,cAAc,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAwSnC"}
1
+ {"version":3,"file":"snapshot-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/snapshot-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAkB,KAAK,KAAK,EAAoB,MAAM,OAAO,CAAA;AAoBjF,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAElF,OAAO,EAAE,KAAK,QAAQ,EAAqC,MAAM,cAAc,CAAA;AAK/E,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,eAAe,CAAA;AACzC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAUrC,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,MAAM,GACP,EAAE;QACD,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,QAAQ,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,KAAK,IAAI,CAAA;IACV,OAAO,EAAE,CAAC,EACR,WAAW,EACX,UAAU,EACV,YAAY,GACb,EAAE;QACD,WAAW,CAAC,EAAE,mBAAmB,CAAA;QACjC,UAAU,CAAC,EAAE,kBAAkB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;KAC5B,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAgBD,wBAAsB,sBAAsB,CAAC,EAC3C,QAAQ,EACR,KAAK,EACL,MAAM,EACN,UAAiB,GAClB,EAAE;IACD,QAAQ,EAAE,cAAc,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAA;CAC1C,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA0SnC"}
@@ -2,8 +2,8 @@ import { Color, Matrix4, UnsignedByteType } from 'three';
2
2
  import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
3
3
  import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
4
4
  import { fxaa } from 'three/examples/jsm/tsl/display/FXAANode.js';
5
- import { convertToTexture, diffuseColor, float, mix, mrt, normalView, output, pass, sample, saturation, screenUV, smoothstep, uniform, vec3, vec4, } from 'three/tsl';
6
- import { RenderPipeline, RenderTarget } from 'three/webgpu';
5
+ import { convertToTexture, diffuseColor, float, mix, mrt, normalView, output, sample, saturation, screenUV, smoothstep, uniform, vec3, vec4, } from 'three/tsl';
6
+ import { PassNode, RenderPipeline, RenderTarget, } from 'three/webgpu';
7
7
  import { GRADE_PARAMS, SSGI_PARAMS } from '../components/viewer/post-processing';
8
8
  import { backdropGradient, deepSkyColor, horizonHazeColor } from './backdrop';
9
9
  import { edgeColorFor, edgeOpacityScaleFor } from './edge-style';
@@ -29,7 +29,22 @@ function clampSnapshotSize(width, height) {
29
29
  const scale = SNAPSHOT_MAX_EDGE / maxEdge;
30
30
  return { w: Math.round(width * scale), h: Math.round(height * scale) };
31
31
  }
32
- export async function createSnapshotPipeline({ renderer, scene, camera, }) {
32
+ class SnapshotScenePass extends PassNode {
33
+ updateBefore(frame) {
34
+ const renderer = frame.renderer;
35
+ const clearAlpha = renderer.getClearAlpha();
36
+ // RTTNode resets clear alpha in r186. The geometry mask must be cleared
37
+ // inside this pass, not inherited from the outer snapshot render.
38
+ renderer.setClearAlpha(0);
39
+ try {
40
+ return super.updateBefore(frame);
41
+ }
42
+ finally {
43
+ renderer.setClearAlpha(clearAlpha);
44
+ }
45
+ }
46
+ }
47
+ export async function createSnapshotPipeline({ renderer, scene, camera, atmosphere = null, }) {
33
48
  try {
34
49
  if (renderer.init)
35
50
  await renderer.init();
@@ -49,10 +64,9 @@ export async function createSnapshotPipeline({ renderer, scene, camera, }) {
49
64
  const inkColorUniform = uniform(new Color('#1a1d24'));
50
65
  const inkOpacityUniform = uniform(0.5);
51
66
  const inkOpacityScaleUniform = uniform(1);
52
- // pass() handles MRT internally for all material types, including custom
53
- // shaders — unlike renderer.setMRT() which crashes on non-NodeMaterials.
54
- // pass() also respects camera.layers, so caller-disabled objects are filtered.
55
- const scenePass = pass(scene, camera);
67
+ // The scene pass owns MRT and its transparent clear, including when nested
68
+ // inside FXAA's intermediate render target.
69
+ const scenePass = new SnapshotScenePass('color', scene, camera);
56
70
  scenePass.setMRT(mrt({
57
71
  output,
58
72
  diffuseColor,
@@ -111,13 +125,15 @@ export async function createSnapshotPipeline({ renderer, scene, camera, }) {
111
125
  const ndc = vec4(screenUV.x.mul(2).sub(1), float(1).sub(screenUV.y).mul(2).sub(1), 1, 1);
112
126
  const viewRay = bgProjInvUniform.mul(ndc);
113
127
  const worldDir = bgCamWorldUniform.mul(vec4(viewRay.xyz, 0)).xyz.normalize();
114
- const ungradedBgGradient = backdropGradient({
115
- dirY: worldDir.y,
116
- background: bgColorUniform,
117
- haze: bgHazeUniform,
118
- sky: bgSkyUniform,
119
- skyDeep: bgSkyDeepUniform,
120
- });
128
+ const ungradedBgGradient = atmosphere
129
+ ? atmosphere.skyRadiance(worldDir)
130
+ : backdropGradient({
131
+ dirY: worldDir.y,
132
+ background: bgColorUniform,
133
+ haze: bgHazeUniform,
134
+ sky: bgSkyUniform,
135
+ skyDeep: bgSkyDeepUniform,
136
+ });
121
137
  const bgGradient = mix(ungradedBgGradient, gradeRgb(ungradedBgGradient), gradeMixUniform);
122
138
  const alpha = scenePassColor.a;
123
139
  const finalOutput = vec4(mix(sceneRgb, mix(bgGradient, sceneRgb, alpha), bgMixUniform), mix(alpha, float(1), bgMixUniform));
@@ -159,8 +175,6 @@ export async function createSnapshotPipeline({ renderer, scene, camera, }) {
159
175
  renderTarget.setSize(captureWidth, captureHeight);
160
176
  }
161
177
  try {
162
- ;
163
- renderer.setClearAlpha(0);
164
178
  renderer.setRenderTarget(renderTarget);
165
179
  pipeline.render();
166
180
  }
@@ -268,6 +282,8 @@ export async function createSnapshotPipeline({ renderer, scene, camera, }) {
268
282
  dispose: () => {
269
283
  pipeline.dispose();
270
284
  renderTarget.dispose();
285
+ scenePass.dispose();
286
+ giPass.dispose();
271
287
  },
272
288
  };
273
289
  }
@@ -1,4 +1,4 @@
1
- import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from '@pascal-app/core';
1
+ import type { AnyNode, BaseNode, BuildingNode, LevelNode, UnitNode, ZoneNode } from '@pascal-app/core';
2
2
  import type { Object3D } from 'three';
3
3
  import type { EdgeMode } from '../lib/edge-style';
4
4
  import type { ColorPreset, RenderShading } from '../lib/materials';
@@ -17,6 +17,8 @@ type Outliner = {
17
17
  };
18
18
  type ViewerState = {
19
19
  selection: SelectionPath;
20
+ focusedUnitId: UnitNode['id'] | null;
21
+ setFocusedUnit: (id: UnitNode['id'] | null) => void;
20
22
  previewSelectedIds: BaseNode['id'][];
21
23
  setPreviewSelectedIds: (ids: BaseNode['id'][]) => void;
22
24
  /** Host-owned selection highlights rendered through the viewer's native
@@ -1 +1 @@
1
- {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
@@ -122,6 +122,8 @@ function normalizePersistedViewerState(value) {
122
122
  }
123
123
  const useViewer = create()(persist((set) => ({
124
124
  selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
125
+ focusedUnitId: null,
126
+ setFocusedUnit: (focusedUnitId) => set({ focusedUnitId }),
125
127
  previewSelectedIds: [],
126
128
  setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
127
129
  externalSelectedIds: [],
@@ -1 +1 @@
1
- {"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,YA6EhC,CAAA"}
1
+ {"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,YAiFhC,CAAA"}
@@ -64,6 +64,9 @@ export const FloorElevationSystem = () => {
64
64
  const position = effectiveNode.position;
65
65
  if (!position)
66
66
  return;
67
+ if (!(def.geometry || def.system) && dirtyNodes.has(id)) {
68
+ clearDirty(id);
69
+ }
67
70
  // `applies === false` means the kind opts OUT of floor stacking for this
68
71
  // node: its Y belongs to a host frame (a wall/ceiling-mounted item, a
69
72
  // cabinet module inside a run, a wall duct terminal). `getFloorPlacedElevation`
@@ -73,6 +76,10 @@ export const FloorElevationSystem = () => {
73
76
  // the host frame's own elevation.
74
77
  if (floorPlaced.applies && !floorPlaced.applies(effectiveNode))
75
78
  return;
79
+ // Hosted meshes inherit elevation from their parent (and possibly a surface group).
80
+ // Their live transform can be world-space, so it cannot replace the mesh's local Y.
81
+ if (!effectiveNode.parentId || nodes[effectiveNode.parentId]?.type !== 'level')
82
+ return;
76
83
  // This system is the single drag-time authority for floor-stack mesh Y:
77
84
  // tools publish base positions to live stores, renderers may
78
85
  // reconcile that base Y onto the group, then this presentation system
@@ -90,9 +97,6 @@ export const FloorElevationSystem = () => {
90
97
  maxElevation: liveTransform?.supportElevationCap,
91
98
  });
92
99
  mesh.position.y = visualPosition[1];
93
- if (!(def.geometry || def.system) && dirtyNodes.has(id)) {
94
- clearDirty(id);
95
- }
96
100
  };
97
101
  dirtyNodes.forEach((id) => {
98
102
  applyLift(id);
@@ -1 +1 @@
1
- {"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAUtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YAuM1B,CAAA;AA8ID,eAAe,cAAc,CAAA;AAE7B,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAU7D;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAC3C,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAKT"}
1
+ {"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAUtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YAuM1B,CAAA;AAiJD,eAAe,cAAc,CAAA;AAE7B,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAU7D;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAC3C,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAKT"}
@@ -212,7 +212,11 @@ function liveChildOverrideKey(node) {
212
212
  return '';
213
213
  const overrides = useLiveNodeOverrides.getState().overrides;
214
214
  const entries = [];
215
+ const childTypes = nodeRegistry.get(node.type)?.geometryChildTypes;
216
+ const nodes = useScene.getState().nodes;
215
217
  for (const childId of childIds) {
218
+ if (childTypes && !childTypes.includes(nodes[childId]?.type ?? ''))
219
+ continue;
216
220
  const override = overrides.get(childId);
217
221
  if (override)
218
222
  entries.push([childId, override]);
@@ -10,7 +10,7 @@ export declare const DOUBLE_HUNG_BOTTOM_SASH_NAME = "double-hung-bottom-sash";
10
10
  export declare const LOUVERED_WINDOW_SLATS_NAME = "louvered-window-slats";
11
11
  export declare const AWNING_WINDOW_SASH_NAME = "awning-window-sash";
12
12
  export declare const HOPPER_WINDOW_SASH_NAME = "hopper-window-sash";
13
- export declare const pendingWindowAnimationRebuilds: Set<`building_${string}` | `level_${string}` | `elevator_${string}` | `item_${string}` | `block_${string}` | `ceiling_${string}` | `column_${string}` | `wall_${string}` | `roof_${string}` | `slab_${string}` | `fence_${string}` | `construction-dimension_${string}` | `structural-grid_${string}` | `zone_${string}` | `stair_${string}` | `scan_${string}` | `guide_${string}` | `measurement_${string}` | `spawn_${string}` | `shelf_${string}` | `duct-segment_${string}` | `duct-fitting_${string}` | `duct-terminal_${string}` | `hvac-equipment_${string}` | `lineset_${string}` | `liquid-line_${string}` | `pipe-segment_${string}` | `pipe-fitting_${string}` | `pipe-trap_${string}` | `site_${string}` | `leanto_${string}` | `door_${string}` | `window_${string}` | `cabinet-module_${string}` | `cabinet_${string}` | `rseg_${string}` | `sseg_${string}` | `bvent_${string}` | `rvent_${string}` | `tvent_${string}` | `cupola_${string}` | `eyebrow-vent_${string}` | `gutter_${string}` | `chimney_${string}` | `solarpanel_${string}` | `skylight_${string}` | `dormer_${string}` | `downspout_${string}`>;
13
+ export declare const pendingWindowAnimationRebuilds: Set<`building_${string}` | `level_${string}` | `elevator_${string}` | `unit_${string}` | `block_${string}` | `ceiling_${string}` | `item_${string}` | `procedural-item_${string}` | `column_${string}` | `construction-dimension_${string}` | `wall_${string}` | `roof_${string}` | `slab_${string}` | `fence_${string}` | `structural-grid_${string}` | `imesh_${string}` | `zone_${string}` | `stair_${string}` | `scan_${string}` | `guide_${string}` | `measurement_${string}` | `spawn_${string}` | `shelf_${string}` | `duct-segment_${string}` | `duct-fitting_${string}` | `duct-terminal_${string}` | `hvac-equipment_${string}` | `lineset_${string}` | `liquid-line_${string}` | `pipe-segment_${string}` | `pipe-fitting_${string}` | `pipe-trap_${string}` | `site_${string}` | `leanto_${string}` | `door_${string}` | `window_${string}` | `cabinet_${string}` | `cabinet-module_${string}` | `rseg_${string}` | `sseg_${string}` | `bvent_${string}` | `rvent_${string}` | `tvent_${string}` | `cupola_${string}` | `eyebrow-vent_${string}` | `gutter_${string}` | `chimney_${string}` | `solarpanel_${string}` | `skylight_${string}` | `dormer_${string}` | `downspout_${string}`>;
14
14
  export declare const WindowSystem: () => null;
15
15
  /**
16
16
  * Build a fresh window mesh for preview/ghost rendering.
@@ -1 +1 @@
1
- {"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAyB9B,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,8BAA8B,8BAA8B,CAAA;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAA;AAC3E,eAAO,MAAM,gCAAgC,gCAAgC,CAAA;AAC7E,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAS3D,eAAO,MAAM,8BAA8B,kkCAAuB,CAAA;AAElE,eAAO,MAAM,YAAY,YAoHxB,CAAA;AA28GD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC,IAAI,CAInE"}
1
+ {"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAyB9B,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,8BAA8B,8BAA8B,CAAA;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAA;AAC3E,eAAO,MAAM,gCAAgC,gCAAgC,CAAA;AAC7E,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAS3D,eAAO,MAAM,8BAA8B,uoCAAuB,CAAA;AAElE,eAAO,MAAM,YAAY,YAoHxB,CAAA;AA28GD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC,IAAI,CAInE"}
@@ -1 +1 @@
1
- {"version":3,"file":"zone-system.d.ts","sourceRoot":"","sources":["../../../src/systems/zone/zone-system.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,YAuFtB,CAAA"}
1
+ {"version":3,"file":"zone-system.d.ts","sourceRoot":"","sources":["../../../src/systems/zone/zone-system.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,YAqHtB,CAAA"}
@@ -1,11 +1,23 @@
1
1
  import { sceneRegistry, useScene } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
- import { useRef } from 'react';
3
+ import { useMemo, useRef } from 'react';
4
4
  import { MathUtils } from 'three';
5
5
  import useViewer from '../../store/use-viewer';
6
6
  const TRANSITION_DURATION = 400; // ms
7
7
  const EXIT_DEBOUNCE_MS = 50; // ignore rapid exit→re-enter within this window
8
8
  export const ZoneSystem = () => {
9
+ const focusedUnitId = useViewer((state) => state.focusedUnitId);
10
+ const focusedUnitMembers = useScene((state) => {
11
+ const unit = focusedUnitId ? state.nodes[focusedUnitId] : undefined;
12
+ return unit?.type === 'unit' ? unit.members : undefined;
13
+ });
14
+ const focusedZoneIds = useMemo(() => new Set(focusedUnitMembers), [focusedUnitMembers]);
15
+ const selection = useViewer((state) => state.selection);
16
+ const showZones = useViewer((state) => state.showZones);
17
+ const lastFocusRef = useRef(null);
18
+ const lastFocusedZoneIdsRef = useRef(null);
19
+ const lastSelectionRef = useRef(null);
20
+ const lastShowZonesRef = useRef(showZones);
9
21
  const lastHighlightedZoneRef = useRef(null);
10
22
  const lastChangeTimeRef = useRef(0);
11
23
  const isTransitioningRef = useRef(false);
@@ -49,6 +61,17 @@ export const ZoneSystem = () => {
49
61
  lastChangeTimeRef.current = clock.elapsedTime * 1000;
50
62
  isTransitioningRef.current = true;
51
63
  }
64
+ if (focusedUnitId !== lastFocusRef.current ||
65
+ focusedZoneIds !== lastFocusedZoneIdsRef.current ||
66
+ selection !== lastSelectionRef.current ||
67
+ showZones !== lastShowZonesRef.current) {
68
+ lastFocusRef.current = focusedUnitId;
69
+ lastFocusedZoneIdsRef.current = focusedZoneIds;
70
+ lastSelectionRef.current = selection;
71
+ lastShowZonesRef.current = showZones;
72
+ lastChangeTimeRef.current = clock.elapsedTime * 1000;
73
+ isTransitioningRef.current = true;
74
+ }
52
75
  // Skip frame if not transitioning
53
76
  if (!isTransitioningRef.current)
54
77
  return;
@@ -63,7 +86,10 @@ export const ZoneSystem = () => {
63
86
  const zone = sceneRegistry.nodes.get(zoneId);
64
87
  if (!zone)
65
88
  return;
66
- const isHighlighted = zoneId === highlightedZone;
89
+ const isHighlighted = zoneId === highlightedZone ||
90
+ zoneId === selection.zoneId ||
91
+ selection.selectedIds.includes(zoneId) ||
92
+ focusedZoneIds.has(zoneId);
67
93
  const targetOpacity = isHighlighted ? 1 : 0;
68
94
  const walls = zone.getObjectByName('walls');
69
95
  if (walls) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/viewer",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "3D viewer component for Pascal building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "prepublishOnly": "npm run build"
34
34
  },
35
35
  "peerDependencies": {
36
- "@pascal-app/core": "^1.0.0",
36
+ "@pascal-app/core": "^1.0.1",
37
37
  "@react-three/drei": "^10",
38
38
  "@react-three/fiber": "^9",
39
39
  "react": "^18 || ^19",
@@ -46,7 +46,7 @@
46
46
  "zustand": "^5"
47
47
  },
48
48
  "devDependencies": {
49
- "@pascal-app/core": "^1.0.0",
49
+ "@pascal-app/core": "^1.0.1",
50
50
  "@pascal/typescript-config": "*",
51
51
  "@react-three/test-renderer": "^9.1.0",
52
52
  "@types/node": "^22",