@pascal-app/viewer 1.0.0 → 1.0.3

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 (73) 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/index.d.ts +12 -0
  13. package/dist/components/viewer/index.d.ts.map +1 -1
  14. package/dist/components/viewer/index.js +28 -7
  15. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  16. package/dist/components/viewer/post-processing.js +6 -0
  17. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  18. package/dist/components/viewer/selection-manager.js +8 -16
  19. package/dist/components/viewer/viewer-camera.d.ts +15 -1
  20. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  21. package/dist/components/viewer/viewer-camera.js +43 -5
  22. package/dist/index.d.ts +8 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +8 -6
  25. package/dist/lib/csg-utils.d.ts +1 -1
  26. package/dist/lib/csg-utils.d.ts.map +1 -1
  27. package/dist/lib/csg-utils.js +1 -1
  28. package/dist/lib/isolation.d.ts +14 -30
  29. package/dist/lib/isolation.d.ts.map +1 -1
  30. package/dist/lib/isolation.js +62 -66
  31. package/dist/lib/materials.d.ts.map +1 -1
  32. package/dist/lib/materials.js +2 -1
  33. package/dist/lib/renderer-capability.d.ts +2 -1
  34. package/dist/lib/renderer-capability.d.ts.map +1 -1
  35. package/dist/lib/renderer-capability.js +2 -2
  36. package/dist/lib/scene-visibility.js +3 -1
  37. package/dist/lib/snapshot-pipeline.d.ts +3 -1
  38. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  39. package/dist/lib/snapshot-pipeline.js +32 -16
  40. package/dist/store/use-viewer.d.ts +3 -1
  41. package/dist/store/use-viewer.d.ts.map +1 -1
  42. package/dist/store/use-viewer.js +2 -0
  43. package/dist/systems/door/door-system.d.ts.map +1 -1
  44. package/dist/systems/door/door-system.js +18 -5
  45. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  46. package/dist/systems/floor-elevation/floor-elevation-system.js +122 -3
  47. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  48. package/dist/systems/geometry/geometry-system.js +4 -0
  49. package/dist/systems/level/level-system.js +14 -14
  50. package/dist/systems/level/level-utils.d.ts +25 -1
  51. package/dist/systems/level/level-utils.d.ts.map +1 -1
  52. package/dist/systems/level/level-utils.js +25 -4
  53. package/dist/systems/wall/wall-cutout-cache.d.ts +4 -1
  54. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
  55. package/dist/systems/wall/wall-cutout-cache.js +16 -7
  56. package/dist/systems/wall/wall-cutout.d.ts +3 -1
  57. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  58. package/dist/systems/wall/wall-cutout.js +5 -5
  59. package/dist/systems/wall/wall-materials.d.ts +11 -2
  60. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  61. package/dist/systems/wall/wall-materials.js +24 -18
  62. package/dist/systems/wall/wall-system.d.ts +15 -2
  63. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  64. package/dist/systems/wall/wall-system.js +20 -21
  65. package/dist/systems/window/window-system.d.ts +1 -1
  66. package/dist/systems/window/window-system.d.ts.map +1 -1
  67. package/dist/systems/window/window-system.js +3 -2
  68. package/dist/systems/zone/zone-system.d.ts.map +1 -1
  69. package/dist/systems/zone/zone-system.js +28 -2
  70. package/dist/xr/presentation-context.d.ts +7 -0
  71. package/dist/xr/presentation-context.d.ts.map +1 -0
  72. package/dist/xr/presentation-context.js +10 -0
  73. package/package.json +4 -4
@@ -11,8 +11,21 @@ export declare function mergeWallCutoutBrushes(brushes: readonly Brush[]): {
11
11
  export declare function shouldDeferWallRebuild(wallId: string, nodes: Record<AnyNodeId, AnyNode>, rebuiltThisFrame: number, elapsedMs: number): boolean;
12
12
  /** Rebuilds this system still owes — neighbours deferred during a drag. */
13
13
  export declare function getPendingWallRebuildCount(): number;
14
- export declare const WallSystem: () => null;
15
- export declare function runWallBuildFrame(): void;
14
+ export type WallGeometryAdapterContext = {
15
+ isLive: (id: AnyNodeId) => boolean;
16
+ };
17
+ export type WallGeometryAdapter = {
18
+ prepareChildren?: (wall: WallNode, children: readonly AnyNode[], context: WallGeometryAdapterContext) => {
19
+ envelopeChildren: AnyNode[];
20
+ renderChildren: AnyNode[];
21
+ };
22
+ buildGeometry?: (wall: WallNode, envelope: THREE.BufferGeometry, children: readonly AnyNode[]) => THREE.BufferGeometry;
23
+ syncAuxiliaryGeometry?: (wall: WallNode, mesh: THREE.Mesh, geometry: THREE.BufferGeometry) => void;
24
+ };
25
+ export declare const WallSystem: ({ geometryAdapter }?: {
26
+ geometryAdapter?: WallGeometryAdapter;
27
+ }) => null;
28
+ export declare function runWallBuildFrame(geometryAdapter?: WallGeometryAdapter): void;
16
29
  type WallTerrainBottomSampler = (x: number, z: number) => number | null;
17
30
  export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler): THREE.BufferGeometry;
18
31
  //# sourceMappingURL=wall-system.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAyBd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAsBvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AA2DhE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG;IACjE,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CA4FA;AAmbD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAID,eAAO,MAAM,UAAU,YAMtB,CAAA;AAED,wBAAgB,iBAAiB,SAahC;AA6bD,KAAK,wBAAwB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AAgEvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,EACjB,aAAa,SAAgB,EAC7B,YAAY,GAAE,SAAS,sBAAsB,EAE5C,EACD,YAAY,SAAuB,EACnC,eAAe,CAAC,EAAE,wBAAwB,GACzC,KAAK,CAAC,cAAc,CAwPtB"}
1
+ {"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAyBd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAsBvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AA2DhE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG;IACjE,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CA4FA;AAmbD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAID,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,0BAA0B,KAChC;QAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;QAAC,cAAc,EAAE,OAAO,EAAE,CAAA;KAAE,CAAA;IAC/D,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,KACzB,KAAK,CAAC,cAAc,CAAA;IACzB,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,cAAc,KAAK,IAAI,CAAA;CACnG,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,sBAAqB;IAAE,eAAe,CAAC,EAAE,mBAAmB,CAAA;CAAO,SAM7F,CAAA;AAED,wBAAgB,iBAAiB,CAAC,eAAe,CAAC,EAAE,mBAAmB,QAatE;AAmcD,KAAK,wBAAwB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AAgEvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,EACjB,aAAa,SAAgB,EAC7B,YAAY,GAAE,SAAS,sBAAsB,EAE5C,EACD,YAAY,SAAuB,EACnC,eAAe,CAAC,EAAE,wBAAwB,GACzC,KAAK,CAAC,cAAc,CAwPtB"}
@@ -496,14 +496,14 @@ export function getPendingWallRebuildCount() {
496
496
  return drainStats.pendingNeighbours;
497
497
  }
498
498
  let placeholderSweepCountdown = WALL_PLACEHOLDER_SWEEP_INTERVAL;
499
- export const WallSystem = () => {
499
+ export const WallSystem = ({ geometryAdapter } = {}) => {
500
500
  useScene((state) => state.dirtyNodes);
501
501
  useLiveNodeOverrides((s) => s.overrides);
502
502
  useEffect(() => () => clearLevelMiterCache(), []);
503
- useFrame(runWallBuildFrame, 4);
503
+ useFrame(() => runWallBuildFrame(geometryAdapter), 4);
504
504
  return null;
505
505
  };
506
- export function runWallBuildFrame() {
506
+ export function runWallBuildFrame(geometryAdapter) {
507
507
  const initialBuild = isWallInitialBuildActive();
508
508
  const token = useScene.getState().hydrationToken;
509
509
  if (token !== stalledHydrationToken) {
@@ -512,13 +512,13 @@ export function runWallBuildFrame() {
512
512
  }
513
513
  drainStats.wallsConsumedThisFrame = 0;
514
514
  try {
515
- consumeWallBuildFrame(initialBuild);
515
+ consumeWallBuildFrame(initialBuild, geometryAdapter);
516
516
  }
517
517
  finally {
518
518
  publishWallDrainStats();
519
519
  }
520
520
  }
521
- function consumeWallBuildFrame(initialBuild) {
521
+ function consumeWallBuildFrame(initialBuild, geometryAdapter) {
522
522
  const clearDirty = useScene.getState().clearDirty;
523
523
  // Self-heal: any registered wall still on its mount-time placeholder
524
524
  // geometry with NO dirty mark gets re-marked, so a lost mark (system
@@ -600,7 +600,7 @@ function consumeWallBuildFrame(initialBuild) {
600
600
  }
601
601
  const mesh = sceneRegistry.nodes.get(wallId);
602
602
  if (mesh) {
603
- timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData), {
603
+ timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData, geometryAdapter), {
604
604
  properties: [['node', wallId]],
605
605
  });
606
606
  clearDirty(wallId);
@@ -677,7 +677,7 @@ function consumeWallBuildFrame(initialBuild) {
677
677
  }
678
678
  const mesh = sceneRegistry.nodes.get(wallId);
679
679
  if (mesh) {
680
- timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData), {
680
+ timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData, geometryAdapter), {
681
681
  properties: [['node', wallId]],
682
682
  });
683
683
  notifyWallRebuilt(wallId);
@@ -766,7 +766,7 @@ function getLevelWalls(levelId) {
766
766
  * (override-merged) so a 2D drag visibly moves the 3D mesh without
767
767
  * having touched `useScene` mid-drag.
768
768
  */
769
- function updateWallGeometry(wallId, miterData) {
769
+ function updateWallGeometry(wallId, miterData, geometryAdapter) {
770
770
  const nodes = useScene.getState().nodes;
771
771
  const sceneNode = nodes[wallId];
772
772
  if (sceneNode?.type !== 'wall')
@@ -785,38 +785,37 @@ function updateWallGeometry(wallId, miterData) {
785
785
  ? (x, z) => terrainSupportLift(nodes, levelId, x, z)
786
786
  : undefined;
787
787
  const childrenIds = node.children || [];
788
- // Merge live overrides into door / window children so cutouts track an
789
- // in-flight resize drag (door width arrow, window height arrow, etc.)
790
- // without waiting on the scene store. Non-cutout children pass through
791
- // unchanged.
792
788
  const childrenNodes = childrenIds
793
789
  .map((childId) => nodes[childId])
794
790
  .filter((n) => n !== undefined)
795
791
  .map((child) => {
796
792
  if (child.type !== 'door' && child.type !== 'window')
797
793
  return child;
798
- // `getEffectiveNode` folds in resize overrides (width/height arrows).
799
- // Position moves publish to `useLiveTransforms` instead, so fold that
800
- // in too — opening cutout brushes are rebuilt directly from the
801
- // effective node position rather than from the rendered proxy mesh.
802
794
  const effective = getEffectiveNode(child);
803
795
  const live = useLiveTransforms.getState().get(child.id);
804
- if (!live?.position)
805
- return effective;
806
- return { ...effective, position: live.position };
796
+ return live?.position ? { ...effective, position: live.position } : effective;
807
797
  });
808
- const builtGeo = generateExtrudedWall(node, childrenNodes, miterData, slabElevation, slabSupport.baseElevation, slabSupport.baseSegments, planeTop, terrainBottomAt);
798
+ const prepared = geometryAdapter?.prepareChildren?.(node, childrenNodes, {
799
+ isLive: (id) => useLiveNodeOverrides.getState().get(id) !== undefined ||
800
+ useLiveTransforms.getState().get(id) !== undefined,
801
+ }) ?? {
802
+ envelopeChildren: childrenNodes,
803
+ renderChildren: childrenNodes,
804
+ };
805
+ const builtGeo = generateExtrudedWall(node, prepared.envelopeChildren, miterData, slabElevation, slabSupport.baseElevation, slabSupport.baseSegments, planeTop, terrainBottomAt);
809
806
  const wallAngle = Math.atan2(node.end[1] - node.start[1], node.end[0] - node.start[0]);
810
807
  // World transform the render mesh will apply (position + Y-rotation below).
811
808
  // Reproduce it here so the UVs can be projected in WORLD space — see
812
809
  // `applyWorldPlanarWallUVs`.
813
810
  const wallWorldMatrix = new THREE.Matrix4().compose(new THREE.Vector3(node.start[0], slabElevation, node.start[1]), new THREE.Quaternion().setFromAxisAngle(WALL_UV_Y_AXIS, -wallAngle), WALL_UV_UNIT_SCALE);
814
- const newGeo = applyWorldPlanarWallUVs(builtGeo, wallWorldMatrix);
811
+ const renderedGeo = geometryAdapter?.buildGeometry?.(node, builtGeo, prepared.renderChildren) ?? builtGeo;
812
+ const newGeo = applyWorldPlanarWallUVs(renderedGeo, wallWorldMatrix);
815
813
  mesh.geometry.dispose();
816
814
  // A degenerate rebuild (zero-length or fully cut wall) yields as few vertices
817
815
  // as the mount-time placeholder; the stamp keeps the sweep from re-marking it.
818
816
  newGeo.userData.built = true;
819
817
  mesh.geometry = newGeo;
818
+ geometryAdapter?.syncAuxiliaryGeometry?.(node, mesh, newGeo);
820
819
  // Update collision mesh
821
820
  const collisionMesh = mesh.getObjectByName('collision-mesh');
822
821
  if (collisionMesh) {
@@ -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,YAqHxB,CAAA;AA28GD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC,IAAI,CAInE"}
@@ -136,8 +136,9 @@ export const WindowSystem = () => {
136
136
  // Rebuild the parent wall so its cutout reflects the updated window geometry
137
137
  // Avoid triggering expensive wall CSG rebuilds while the window is being interactively moved/duplicated.
138
138
  // The editor tools will request a final wall rebuild on commit.
139
- const isTransient = !!node.metadata?.isTransient;
140
- if (!isTransient && effectiveNode.parentId) {
139
+ const metadata = effectiveNode.metadata;
140
+ const deferParentRebuild = !!metadata?.isTransient || !!metadata?.deferParentRebuild;
141
+ if (!deferParentRebuild && effectiveNode.parentId) {
141
142
  useScene.getState().dirtyNodes.add(effectiveNode.parentId);
142
143
  }
143
144
  }
@@ -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) {
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare function ImmersiveXRPresentationProvider({ children, enabled, }: {
3
+ children: ReactNode;
4
+ enabled: boolean;
5
+ }): import("react").JSX.Element;
6
+ export declare function useImmersiveXRPresentation(): boolean;
7
+ //# sourceMappingURL=presentation-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation-context.d.ts","sourceRoot":"","sources":["../../src/xr/presentation-context.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAIjE,wBAAgB,+BAA+B,CAAC,EAC9C,QAAQ,EACR,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,SAAS,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB,+BAMA;AAED,wBAAgB,0BAA0B,YAEzC"}
@@ -0,0 +1,10 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from 'react';
4
+ const ImmersiveXRPresentationContext = createContext(false);
5
+ export function ImmersiveXRPresentationProvider({ children, enabled, }) {
6
+ return (_jsx(ImmersiveXRPresentationContext.Provider, { value: enabled, children: children }));
7
+ }
8
+ export function useImmersiveXRPresentation() {
9
+ return useContext(ImmersiveXRPresentationContext);
10
+ }
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.3",
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.3",
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.3",
50
50
  "@pascal/typescript-config": "*",
51
51
  "@react-three/test-renderer": "^9.1.0",
52
52
  "@types/node": "^22",
@@ -67,7 +67,7 @@
67
67
  ],
68
68
  "repository": {
69
69
  "type": "git",
70
- "url": "https://github.com/pascalorg/editor.git",
70
+ "url": "git+https://github.com/pascalorg/editor.git",
71
71
  "directory": "packages/viewer"
72
72
  },
73
73
  "license": "MIT",