@pascal-app/viewer 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  2. package/dist/components/renderers/node-renderer.js +8 -3
  3. package/dist/components/viewer/frame-limiter.d.ts +1 -0
  4. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  5. package/dist/components/viewer/frame-limiter.js +22 -3
  6. package/dist/components/viewer/glb-interactive.d.ts +25 -1
  7. package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
  8. package/dist/components/viewer/glb-interactive.js +252 -85
  9. package/dist/components/viewer/glb-mechanisms.d.ts +10 -0
  10. package/dist/components/viewer/glb-mechanisms.d.ts.map +1 -0
  11. package/dist/components/viewer/glb-mechanisms.js +19 -0
  12. package/dist/components/viewer/glb-scene.d.ts +45 -27
  13. package/dist/components/viewer/glb-scene.d.ts.map +1 -1
  14. package/dist/components/viewer/glb-scene.js +420 -506
  15. package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
  16. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
  17. package/dist/components/viewer/glb-walkthrough-controller.js +12 -1
  18. package/dist/components/viewer/index.d.ts +26 -0
  19. package/dist/components/viewer/index.d.ts.map +1 -1
  20. package/dist/components/viewer/index.js +108 -29
  21. package/dist/components/viewer/lights.d.ts.map +1 -1
  22. package/dist/components/viewer/lights.js +6 -1
  23. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  24. package/dist/components/viewer/perf-monitor.js +6 -0
  25. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  26. package/dist/components/viewer/post-processing.js +8 -3
  27. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  28. package/dist/components/viewer/registered-systems.js +16 -2
  29. package/dist/components/viewer/render-error.d.ts +18 -0
  30. package/dist/components/viewer/render-error.d.ts.map +1 -0
  31. package/dist/components/viewer/render-error.js +29 -0
  32. package/dist/components/viewer/scene-ground-replacement.d.ts +6 -0
  33. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -1
  34. package/dist/components/viewer/scene-ground-replacement.js +15 -0
  35. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  36. package/dist/components/viewer/selection-manager.js +10 -0
  37. package/dist/components/viewer/viewer-camera.d.ts +19 -1
  38. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  39. package/dist/components/viewer/viewer-camera.js +53 -5
  40. package/dist/index.d.ts +28 -15
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +26 -18
  43. package/dist/lib/asset-url.d.ts.map +1 -1
  44. package/dist/lib/asset-url.js +7 -1
  45. package/dist/lib/csg-utils.d.ts +1 -1
  46. package/dist/lib/csg-utils.d.ts.map +1 -1
  47. package/dist/lib/csg-utils.js +1 -1
  48. package/dist/lib/display-state.d.ts +25 -0
  49. package/dist/lib/display-state.d.ts.map +1 -0
  50. package/dist/lib/display-state.js +1 -0
  51. package/dist/lib/hero-pose.d.ts +12 -2
  52. package/dist/lib/hero-pose.d.ts.map +1 -1
  53. package/dist/lib/hero-pose.js +72 -5
  54. package/dist/lib/ink-edges.d.ts +4 -2
  55. package/dist/lib/ink-edges.d.ts.map +1 -1
  56. package/dist/lib/ink-edges.js +17 -7
  57. package/dist/lib/key-light-override.d.ts +11 -0
  58. package/dist/lib/key-light-override.d.ts.map +1 -0
  59. package/dist/lib/key-light-override.js +14 -0
  60. package/dist/lib/live-frame-hold.d.ts +14 -0
  61. package/dist/lib/live-frame-hold.d.ts.map +1 -0
  62. package/dist/lib/live-frame-hold.js +28 -0
  63. package/dist/lib/materials.d.ts +14 -7
  64. package/dist/lib/materials.d.ts.map +1 -1
  65. package/dist/lib/materials.js +31 -5
  66. package/dist/lib/node-top-surface-height.d.ts +6 -0
  67. package/dist/lib/node-top-surface-height.d.ts.map +1 -0
  68. package/dist/lib/node-top-surface-height.js +74 -0
  69. package/dist/lib/perf-probe-state.d.ts +39 -0
  70. package/dist/lib/perf-probe-state.d.ts.map +1 -0
  71. package/dist/lib/perf-probe-state.js +17 -0
  72. package/dist/lib/pointer-events.d.ts +8 -0
  73. package/dist/lib/pointer-events.d.ts.map +1 -1
  74. package/dist/lib/pointer-events.js +27 -4
  75. package/dist/lib/procedural-emission.d.ts +7 -0
  76. package/dist/lib/procedural-emission.d.ts.map +1 -0
  77. package/dist/lib/procedural-emission.js +54 -0
  78. package/dist/lib/renderer-capability.d.ts +2 -1
  79. package/dist/lib/renderer-capability.d.ts.map +1 -1
  80. package/dist/lib/renderer-capability.js +2 -2
  81. package/dist/lib/scene-support-height.d.ts +4 -0
  82. package/dist/lib/scene-support-height.d.ts.map +1 -0
  83. package/dist/lib/scene-support-height.js +34 -0
  84. package/dist/lib/scene-visibility.d.ts +6 -0
  85. package/dist/lib/scene-visibility.d.ts.map +1 -1
  86. package/dist/lib/scene-visibility.js +15 -3
  87. package/dist/lib/scripted-opening.d.ts +11 -0
  88. package/dist/lib/scripted-opening.d.ts.map +1 -0
  89. package/dist/lib/scripted-opening.js +20 -0
  90. package/dist/lib/snapshot-pipeline.d.ts +32 -2
  91. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  92. package/dist/lib/snapshot-pipeline.js +215 -105
  93. package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
  94. package/dist/lib/terrain-perimeter-fill.js +22 -14
  95. package/dist/lib/zone-geometry.d.ts +11 -0
  96. package/dist/lib/zone-geometry.d.ts.map +1 -0
  97. package/dist/lib/zone-geometry.js +46 -0
  98. package/dist/store/use-item-light-pool.d.ts +14 -10
  99. package/dist/store/use-item-light-pool.d.ts.map +1 -1
  100. package/dist/store/use-item-light-pool.js +52 -26
  101. package/dist/store/use-viewer.d.ts +11 -0
  102. package/dist/store/use-viewer.d.ts.map +1 -1
  103. package/dist/store/use-viewer.js +13 -6
  104. package/dist/systems/ceiling/ceiling-system.d.ts +15 -1
  105. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  106. package/dist/systems/ceiling/ceiling-system.js +86 -6
  107. package/dist/systems/door/door-system.d.ts.map +1 -1
  108. package/dist/systems/door/door-system.js +73 -22
  109. package/dist/systems/floor-elevation/floor-elevation-system.d.ts +3 -1
  110. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  111. package/dist/systems/floor-elevation/floor-elevation-system.js +148 -5
  112. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  113. package/dist/systems/geometry/geometry-system.js +71 -11
  114. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  115. package/dist/systems/interactive/interactive-system.js +40 -15
  116. package/dist/systems/interactive/procedural-controls.d.ts +13 -0
  117. package/dist/systems/interactive/procedural-controls.d.ts.map +1 -0
  118. package/dist/systems/interactive/procedural-controls.js +17 -0
  119. package/dist/systems/interactive/scripted-clips.d.ts +19 -0
  120. package/dist/systems/interactive/scripted-clips.d.ts.map +1 -0
  121. package/dist/systems/interactive/scripted-clips.js +134 -0
  122. package/dist/systems/item/item-system.d.ts.map +1 -1
  123. package/dist/systems/item/item-system.js +9 -3
  124. package/dist/systems/item/object-cut-invalidation.d.ts +3 -0
  125. package/dist/systems/item/object-cut-invalidation.d.ts.map +1 -0
  126. package/dist/systems/item/object-cut-invalidation.js +68 -0
  127. package/dist/systems/item-light/item-light-system.d.ts +1 -1
  128. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  129. package/dist/systems/item-light/item-light-system.js +47 -52
  130. package/dist/systems/level/level-system.d.ts.map +1 -1
  131. package/dist/systems/level/level-system.js +17 -19
  132. package/dist/systems/level/level-utils.d.ts +28 -1
  133. package/dist/systems/level/level-utils.d.ts.map +1 -1
  134. package/dist/systems/level/level-utils.js +36 -4
  135. package/dist/systems/level/shadow-only-levels.d.ts +9 -0
  136. package/dist/systems/level/shadow-only-levels.d.ts.map +1 -0
  137. package/dist/systems/level/shadow-only-levels.js +7 -0
  138. package/dist/systems/roof/roof-materials.d.ts +14 -2
  139. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  140. package/dist/systems/roof/roof-materials.js +54 -15
  141. package/dist/systems/roof/roof-system.d.ts +13 -3
  142. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  143. package/dist/systems/roof/roof-system.js +188 -13
  144. package/dist/systems/slab/slab-system.d.ts +1 -1
  145. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  146. package/dist/systems/slab/slab-system.js +49 -19
  147. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  148. package/dist/systems/stair/stair-system.js +133 -354
  149. package/dist/systems/wall/wall-cutout-cache.d.ts +11 -1
  150. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
  151. package/dist/systems/wall/wall-cutout-cache.js +58 -20
  152. package/dist/systems/wall/wall-cutout.d.ts +9 -1
  153. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  154. package/dist/systems/wall/wall-cutout.js +46 -32
  155. package/dist/systems/wall/wall-finish-data.d.ts +22 -0
  156. package/dist/systems/wall/wall-finish-data.d.ts.map +1 -0
  157. package/dist/systems/wall/wall-finish-data.js +17 -0
  158. package/dist/systems/wall/wall-materials.d.ts +17 -3
  159. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  160. package/dist/systems/wall/wall-materials.js +109 -156
  161. package/dist/systems/wall/wall-system.d.ts +43 -4
  162. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  163. package/dist/systems/wall/wall-system.js +601 -163
  164. package/dist/systems/window/window-system.d.ts +1 -1
  165. package/dist/systems/window/window-system.d.ts.map +1 -1
  166. package/dist/systems/window/window-system.js +44 -24
  167. package/dist/xr/presentation-context.d.ts +7 -0
  168. package/dist/xr/presentation-context.d.ts.map +1 -0
  169. package/dist/xr/presentation-context.js +10 -0
  170. package/package.json +6 -6
  171. package/dist/systems/fence/fence-system.d.ts +0 -22
  172. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  173. package/dist/systems/fence/fence-system.js +0 -357
@@ -32,7 +32,12 @@ type ViewerState = {
32
32
  cameraMode: 'perspective' | 'orthographic';
33
33
  setCameraMode: (mode: 'perspective' | 'orthographic') => void;
34
34
  sceneTheme: string;
35
+ /** The user's own pick, and the only theme that persists. */
36
+ savedSceneTheme: string;
35
37
  setSceneTheme: (id: string) => void;
38
+ /** Show a theme without saving it (e.g. a project's own mood); `null`
39
+ * returns to the saved one. */
40
+ showSceneTheme: (id: string | null) => void;
36
41
  renderContext: RenderContext;
37
42
  setRenderContext: (context: RenderContext) => void;
38
43
  /** True during a GLB bake/export pass. Renderers that normally draw via a
@@ -73,6 +78,12 @@ type ViewerState = {
73
78
  unitExplicit: boolean;
74
79
  levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
75
80
  setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
81
+ /**
82
+ * The editor's level display: stacked, with every level above the selected
83
+ * one hidden (the selected level and those below stay). Set by the host
84
+ * while editing; never persisted, so the viewer app keeps its own mode.
85
+ */
86
+ hideLevelsAboveSelection: boolean;
76
87
  wallMode: WallMode;
77
88
  setWallMode: (mode: WallMode) => void;
78
89
  showScans: boolean;
@@ -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":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,QAAQ,EACT,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAIrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGlE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAC/C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,aAAa,CAAA;AACrD,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,aAAa,CAAA;AAEhE,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAA;IAC3B,cAAc,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpC,cAAc,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IACnD,kBAAkB,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;IACpC,qBAAqB,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAA;IACtD;+EAC2E;IAC3E,mBAAmB,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;IACrC,sBAAsB,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAA;IACvD,kBAAkB,EAAE,MAAM,CAAA;IAC1B,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,UAAU,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;oCACgC;IAChC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAE3C,aAAa,EAAE,aAAa,CAAA;IAC5B,gBAAgB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAElD;;;;qBAIiB;IACjB,WAAW,EAAE,OAAO,CAAA;IACpB,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAEtC;;;4EAGwE;IACxE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAE9C,uFAAuF;IACvF,YAAY,EAAE,OAAO,CAAA;IACrB,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAEzC,OAAO,EAAE,aAAa,CAAA;IACtB,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CAAA;IAC/D,UAAU,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAE5C,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAExC,WAAW,EAAE,WAAW,CAAA;IACxB,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;IAE7C,KAAK,EAAE,QAAQ,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IAEnC,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAEtC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC3B,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,KAAK,IAAI,CAAA;IAC9C,cAAc,EAAE,cAAc,CAAA;IAC9B,iBAAiB,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;IACrD;;sDAEkD;IAClD,YAAY,EAAE,OAAO,CAAA;IAErB,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IACxE;;;;OAIG;IACH,wBAAwB,EAAE,OAAO,CAAA;IAEjC,QAAQ,EAAE,QAAQ,CAAA;IAClB,WAAW,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IAErC,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAEtC,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAEpC,gBAAgB,EAAE,OAAO,CAAA;IACzB,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAO5C,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,qBAAqB,EAAE,OAAO,CAAA;IAC9B,wBAAwB,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAA;IAGxD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAE/C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACzC,kBAAkB,EAAE,MAAM,CACxB,MAAM,EACN;QACE,SAAS,CAAC,EAAE,OAAO,CAAA;QACnB,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CACF,CAAA;IAGD,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAClB;6DACyD;IACzD,gBAAgB,EAAE,MAAM,CAAA;IACxB,oBAAoB,EAAE,MAAM,IAAI,CAAA;IAGhC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IAChF,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjG,WAAW,EAAE,OAAO,CAAA;IACpB,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAE1C,eAAe,EAAE,OAAO,CAAA;IACxB,kBAAkB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAE3C;4EACwE;IACxE,oBAAoB,EAAE,OAAO,CAAA;IAC7B,uBAAuB,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAA;IAErD,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAE9C;;;;;;;;;OASG;IACH,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CAC9C,CAAA;AAmJD,QAAA,MAAM,SAAS;;;;;;;;;4BAlMG,OAAO;6BACN,OAAO;2BACT,OAAO;mCACC,OAAO;;;;;;;;;;;;;;;;;;;;;4BAHd,OAAO;6BACN,OAAO;2BACT,OAAO;mCACC,OAAO;;;;;;;;;;;;EAsb/B,CAAA;AAED;;;;cAIc;AACd,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,QAMzE;AAED,eAAe,SAAS,CAAA"}
@@ -141,7 +141,9 @@ const useViewer = create()(persist((set) => ({
141
141
  cameraMode: 'perspective',
142
142
  setCameraMode: (mode) => set({ cameraMode: mode }),
143
143
  sceneTheme: 'studio',
144
- setSceneTheme: (id) => set({ sceneTheme: id }),
144
+ savedSceneTheme: 'studio',
145
+ setSceneTheme: (id) => set({ sceneTheme: id, savedSceneTheme: id }),
146
+ showSceneTheme: (id) => set((state) => ({ sceneTheme: id ?? state.savedSceneTheme })),
145
147
  renderContext: 'editor',
146
148
  setRenderContext: (context) => set({ renderContext: context }),
147
149
  isExporting: false,
@@ -180,6 +182,7 @@ const useViewer = create()(persist((set) => ({
180
182
  setMetricNotation: (metricNotation) => set({ unit: 'metric', metricNotation, unitExplicit: true }),
181
183
  levelMode: 'stacked',
182
184
  setLevelMode: (mode) => set({ levelMode: mode }),
185
+ hideLevelsAboveSelection: false,
183
186
  wallMode: 'up',
184
187
  setWallMode: (mode) => set({ wallMode: mode }),
185
188
  showScans: true,
@@ -295,13 +298,17 @@ const useViewer = create()(persist((set) => ({
295
298
  setInputDragging: (dragging) => set({ inputDragging: dragging }),
296
299
  }), {
297
300
  name: 'viewer-preferences',
298
- merge: (persistedState, currentState) => ({
299
- ...currentState,
300
- ...normalizePersistedViewerState(persistedState),
301
- }),
301
+ merge: (persistedState, currentState) => {
302
+ const persisted = normalizePersistedViewerState(persistedState);
303
+ return {
304
+ ...currentState,
305
+ ...persisted,
306
+ savedSceneTheme: persisted.sceneTheme ?? currentState.savedSceneTheme,
307
+ };
308
+ },
302
309
  partialize: (state) => ({
303
310
  cameraMode: state.cameraMode,
304
- sceneTheme: state.sceneTheme,
311
+ sceneTheme: state.savedSceneTheme,
305
312
  shadingByContext: state.shadingByContext,
306
313
  textures: state.textures,
307
314
  colorPreset: state.colorPreset,
@@ -1,6 +1,19 @@
1
- import { type CeilingNode } from '@pascal-app/core';
1
+ import { type CeilingNode, type CeilingSurfaceCell, useScene } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
+ /** Name of the meshes a ceiling draws its paint regions with, one per region. */
4
+ export declare const CEILING_REGION_MESH = "ceiling-region";
5
+ /**
6
+ * The material a ceiling draws a region finish with. The ceiling's renderer
7
+ * owns materials (theme, textures, scene materials) and leaves this on the
8
+ * registered mesh; the system builds geometry and asks it.
9
+ */
10
+ export type CeilingRegionMaterial = (finish: CeilingSurfaceCell['finish']) => THREE.Material;
11
+ type SceneNodes = ReturnType<typeof useScene.getState>['nodes'];
3
12
  export declare const CeilingSystem: () => null;
13
+ /**
14
+ * Updates the geometry for a single ceiling
15
+ */
16
+ export declare function updateCeilingGeometry(node: CeilingNode, mesh: THREE.Mesh, extraHoles?: Array<Array<[number, number]>>, nodes?: SceneNodes): void;
4
17
  /**
5
18
  * Generates flat ceiling geometry from polygon (no extrusion).
6
19
  *
@@ -9,4 +22,5 @@ export declare const CeilingSystem: () => null;
9
22
  * stored on the node — they are recomputed on every rebuild.
10
23
  */
11
24
  export declare function generateCeilingGeometry(ceilingNode: CeilingNode, extraHoles?: Array<Array<[number, number]>>): THREE.BufferGeometry;
25
+ export {};
12
26
  //# sourceMappingURL=ceiling-system.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAOjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,eAAO,MAAM,aAAa,YA4BzB,CAAA;AAgED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM,GAC9C,KAAK,CAAC,cAAc,CA6DtB"}
1
+ {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,kBAAkB,EAYvB,QAAQ,EACT,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,iFAAiF;AACjF,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AAEnD;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,QAAQ,CAAA;AAE5F,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAA;AAa/D,eAAO,MAAM,aAAa,YA4BzB,CAAA;AAsCD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,KAAK,CAAC,IAAI,EAChB,UAAU,GAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM,EAC/C,KAAK,GAAE,UAAsC,QA8C9C;AAqDD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM,GAC9C,KAAK,CAAC,cAAc,CA6DtB"}
@@ -1,7 +1,10 @@
1
- import { getEffectiveNode, nodeRegistry, resolveCeilingHeight, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
1
+ import { CEILING_DRAW_OFFSET, CEILING_SURFACE_ROLE, ceilingPaintRegions, computeCeilingSurfaceCells, getEffectiveCutterNode, getEffectiveNode, hostedCutterHoles, isScriptedNode, nodeRegistry, resolveCeilingHeight, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import * as THREE from 'three';
4
+ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
4
5
  import { mergeSurfaceHolePolygons } from '../surface-hole-geometry';
6
+ /** Name of the meshes a ceiling draws its paint regions with, one per region. */
7
+ export const CEILING_REGION_MESH = 'ceiling-region';
5
8
  function ensureUv2Attribute(geometry) {
6
9
  const uv = geometry.getAttribute('uv');
7
10
  if (!uv)
@@ -49,29 +52,53 @@ export const CeilingSystem = () => {
49
52
  * participate just by declaring `capabilities.ceilingCut` on its definition.
50
53
  */
51
54
  function collectCeilingHoles(ceiling, nodes) {
52
- const holes = [];
55
+ // Only the ceiling's own children can bind to it.
56
+ const hosted = { [ceiling.id]: ceiling };
57
+ for (const childId of ceiling.children ?? []) {
58
+ const child = nodes[childId];
59
+ if (child)
60
+ hosted[child.id] = getEffectiveCutterNode(child);
61
+ }
62
+ const holes = hostedCutterHoles(ceiling, hosted);
53
63
  for (const childId of ceiling.children ?? []) {
54
64
  const child = nodes[childId];
55
65
  if (!child)
56
66
  continue;
67
+ if (isScriptedNode(child) && child.source.manifest.cutters?.length)
68
+ continue;
57
69
  const def = nodeRegistry.get(child.type);
58
70
  const hole = def?.capabilities?.ceilingCut?.buildCeilingHole(child);
59
71
  if (hole)
60
72
  holes.push(hole);
61
73
  }
74
+ for (const [, def] of nodeRegistry.entries())
75
+ holes.push(...(def.capabilities?.ceilingCut?.holesFor?.(ceiling) ?? []));
62
76
  return holes;
63
77
  }
64
78
  /**
65
79
  * Updates the geometry for a single ceiling
66
80
  */
67
- function updateCeilingGeometry(node, mesh, extraHoles = [], nodes = useScene.getState().nodes) {
81
+ export function updateCeilingGeometry(node, mesh, extraHoles = [], nodes = useScene.getState().nodes) {
68
82
  const newGeo = generateCeilingGeometry(node, extraHoles);
83
+ // Painted regions partition the underside: the ceiling's own mesh keeps what
84
+ // no region covers, and each region draws as its own single-material mesh,
85
+ // so batching buckets them by material like any other surface.
86
+ const regions = node.polygon.length >= 3 ? ceilingPaintRegions(node, nodes) : [];
87
+ const cells = regions.length
88
+ ? computeCeilingSurfaceCells(node.polygon, [...mergeSurfaceHolePolygons(node.holes || []), ...extraHoles], regions)
89
+ : null;
90
+ const surface = cells?.find((cell) => cell.role === CEILING_SURFACE_ROLE);
69
91
  mesh.geometry.dispose();
70
- mesh.geometry = newGeo;
92
+ mesh.geometry = cells ? flatCeilingGeometry(surface?.polygons ?? []) : newGeo;
93
+ mesh.userData.paintRole = CEILING_SURFACE_ROLE;
94
+ syncCeilingRegionMeshes(mesh, (cells ?? []).filter((cell) => cell.role !== CEILING_SURFACE_ROLE));
71
95
  const gridMesh = mesh.getObjectByName('ceiling-grid');
72
96
  if (gridMesh) {
73
97
  gridMesh.geometry.dispose();
74
- gridMesh.geometry = newGeo.clone();
98
+ gridMesh.geometry = cells ? newGeo : newGeo.clone();
99
+ }
100
+ else if (cells) {
101
+ newGeo.dispose();
75
102
  }
76
103
  // Position at the ceiling height and reset X/Z so live-drag mesh
77
104
  // offsets (set by move tools during the drag) don't leak into the
@@ -85,7 +112,60 @@ function updateCeilingGeometry(node, mesh, extraHoles = [], nodes = useScene.get
85
112
  // follows-mode ceiling re-parks under the current plane on every rebuild
86
113
  // (level-height edits / covering-slab changes dirty-mark ceilings).
87
114
  // Slight offset to avoid z-fighting with upper-level slabs.
88
- mesh.position.y = resolveCeilingHeight(node, nodes) - 0.01 + (liveTransform?.position[1] ?? 0);
115
+ mesh.position.y =
116
+ resolveCeilingHeight(node, nodes) - CEILING_DRAW_OFFSET + (liveTransform?.position[1] ?? 0);
117
+ }
118
+ /**
119
+ * One child mesh per region cell, reused by position so a repaint keeps its
120
+ * mesh; surplus meshes go. The material comes from the renderer's resolver.
121
+ */
122
+ function syncCeilingRegionMeshes(mesh, cells) {
123
+ const existing = mesh.children.filter((child) => child.isMesh && child.name === CEILING_REGION_MESH);
124
+ const resolve = mesh.userData.ceilingRegionMaterial;
125
+ cells.forEach((cell, index) => {
126
+ let region = existing[index];
127
+ if (!region) {
128
+ region = new THREE.Mesh();
129
+ region.name = CEILING_REGION_MESH;
130
+ mesh.add(region);
131
+ }
132
+ else
133
+ region.geometry.dispose();
134
+ region.geometry = flatCeilingGeometry(cell.polygons);
135
+ region.userData.paintRole = cell.role;
136
+ region.userData.finish = cell.finish;
137
+ region.userData.__fromGeometry = true;
138
+ region.material = resolve?.(cell.finish) ?? mesh.material;
139
+ });
140
+ for (const surplus of existing.slice(cells.length)) {
141
+ surplus.geometry.dispose();
142
+ surplus.removeFromParent();
143
+ }
144
+ }
145
+ /** Flat geometry for level-XZ pieces, laid out the way `generateCeilingGeometry` lays a ceiling. */
146
+ function flatCeilingGeometry(pieces) {
147
+ const parts = pieces
148
+ .filter((piece) => piece.outer.length >= 3)
149
+ .map((piece) => {
150
+ const shape = new THREE.Shape(piece.outer.map(([x, z]) => new THREE.Vector2(x, -z)));
151
+ for (const hole of piece.holes)
152
+ if (hole.length >= 3)
153
+ shape.holes.push(new THREE.Path(hole.map(([x, z]) => new THREE.Vector2(x, -z))));
154
+ const geometry = new THREE.ShapeGeometry(shape);
155
+ geometry.rotateX(-Math.PI / 2);
156
+ geometry.computeVertexNormals();
157
+ ensureUv2Attribute(geometry);
158
+ return geometry;
159
+ });
160
+ if (parts.length === 0)
161
+ return generateCeilingGeometry({ polygon: [] });
162
+ if (parts.length === 1)
163
+ return parts[0];
164
+ const merged = mergeGeometries(parts) ?? parts[0];
165
+ for (const part of parts)
166
+ if (part !== merged)
167
+ part.dispose();
168
+ return merged;
89
169
  }
90
170
  /**
91
171
  * Generates flat ceiling geometry from polygon (no extrusion).
@@ -1 +1 @@
1
- {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAYd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2D9B,eAAO,MAAM,UAAU,YAsHtB,CAAA;AAupCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACZ,OAAO,CA6GT;AAyuCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,IAAI,CAI/D"}
1
+ {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAcd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA6D9B,eAAO,MAAM,UAAU,YAyHtB,CAAA;AAiqCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACZ,OAAO,CA6GT;AA6wCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,IAAI,CAI/D"}
@@ -1,10 +1,11 @@
1
- import { clampDoorOperationState, DEFAULT_WALL_THICKNESS, DoorNode as DoorNodeSchema, getDoorRenderOpenAmount, getEffectiveNode, getWallThickness, sceneRegistry, useInteractive, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
1
+ import { clampDoorOperationState, DEFAULT_WALL_THICKNESS, DoorNode as DoorNodeSchema, getDoorRenderOpenAmount, getEffectiveNode, getOpeningWallPlacement, getWallThickness, sceneRegistry, scriptedSize, useInteractive, useLiveNodeOverrides, useScene, } from '@pascal-app/core';
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { useEffect, useRef } from 'react';
4
4
  import * as THREE from 'three';
5
5
  import { applyWorldScaleBoxUVs } from '../../lib/box-uv';
6
- import { createDefaultMaterial, createSurfaceRoleMaterial, glassMaterial as defaultGlassMaterial, baseMaterial as getBaseMaterial, resolveMaterialRef, } from '../../lib/materials';
6
+ import { createDefaultMaterial, createMaterial, createSurfaceRoleMaterial, glassMaterial as defaultGlassMaterial, baseMaterial as getBaseMaterial, resolveMaterialRef, } from '../../lib/materials';
7
7
  import { timeSpan } from '../../lib/perf-tracks';
8
+ import { settleScriptedOpening } from '../../lib/scripted-opening';
8
9
  import useViewer from '../../store/use-viewer';
9
10
  import { getOpeningCutoutProxyDepth } from '../wall/opening-cutout-geometry';
10
11
  // Invisible material for root mesh — used as selection hitbox only
@@ -146,16 +147,20 @@ export const DoorSystem = () => {
146
147
  // rebuild reflects the in-flight drag without zustand churn. When
147
148
  // no override is set this returns the scene node unchanged.
148
149
  const effectiveNode = getEffectiveNode(node);
149
- timeSpan('door', () => updateDoorMesh(effectiveNode, mesh), {
150
+ const built = timeSpan('door', () => updateDoorMesh(effectiveNode, mesh), {
150
151
  properties: [['node', id]],
151
152
  });
153
+ // A scripted opening stays dirty until its artifact has loaded.
154
+ if (!built)
155
+ continue;
152
156
  clearDirty(id);
153
157
  rebuiltDoorsThisFrame += 1;
154
158
  // Rebuild the parent wall so its cutout reflects the updated door geometry
155
159
  // Avoid triggering expensive wall CSG rebuilds while the door is being interactively moved/duplicated.
156
160
  // The editor tools will request a final wall rebuild on commit.
157
- const isTransient = !!node.metadata?.isTransient;
158
- if (!isTransient && effectiveNode.parentId) {
161
+ const metadata = effectiveNode.metadata;
162
+ const deferParentRebuild = !!metadata?.isTransient || !!metadata?.deferParentRebuild;
163
+ if (!deferParentRebuild && effectiveNode.parentId) {
159
164
  useScene.getState().dirtyNodes.add(effectiveNode.parentId);
160
165
  }
161
166
  }
@@ -207,16 +212,21 @@ function doorSlotDefault(slotId) {
207
212
  getBaseMaterial(currentShading));
208
213
  }
209
214
  // Resolve a door's slot to a material: the `node.slots` override (colored mode
210
- // only) → the body/glass/hardware default. Textures-off ignores overrides — the
211
- // monochrome escape hatch.
215
+ // only) → the door-wide `material` for the body (panel and frame; glass and
216
+ // hardware keep their own finish) → the body/glass/hardware default.
217
+ // Textures-off ignores overrides — the monochrome escape hatch.
212
218
  function resolveDoorSlotMaterial(node, slotId) {
213
219
  const fallback = doorSlotDefault(slotId);
214
220
  if (!currentTextures)
215
221
  return fallback;
216
222
  const ref = node.slots?.[slotId];
217
- if (!ref)
218
- return fallback;
219
- return resolveMaterialRef(ref, currentSceneMaterials, currentShading) ?? fallback;
223
+ const painted = ref ? resolveMaterialRef(ref, currentSceneMaterials, currentShading) : null;
224
+ if (painted)
225
+ return painted;
226
+ if (node.material && (slotId === 'panel' || slotId === 'frame')) {
227
+ return createMaterial(node.material, currentShading);
228
+ }
229
+ return fallback;
220
230
  }
221
231
  function addBox(parent, material, w, h, d, x, y, z) {
222
232
  const geometry = new THREE.BoxGeometry(w, h, d);
@@ -675,7 +685,9 @@ function addLeafSegmentContent({ addLeafBox, addLeafShape, leafWidth, leafHeight
675
685
  addLeafShape(baseMaterial, innerPanelShape, effectiveDepth);
676
686
  }
677
687
  else {
688
+ // A leaf is paneled on both faces, so the person sees the style from either side.
678
689
  addLeafBox(baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ);
690
+ addLeafBox(baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, -panelZ);
679
691
  }
680
692
  }
681
693
  }
@@ -777,9 +789,21 @@ function addDoorLeaf(mesh, { leafWidth, leafHeight, leafCenterX, leafCenterY, le
777
789
  const hingeH = 0.1;
778
790
  const hingeW = 0.024;
779
791
  const hingeD = leafDepth + 0.016;
780
- addBox(mesh, hardwareMaterial, hingeW, hingeH, hingeD, hingeMarkerX, leafBottom + 0.25, 0);
781
- addBox(mesh, hardwareMaterial, hingeW, hingeH, hingeD, hingeMarkerX, (leafBottom + leafTop) / 2, 0);
782
- addBox(mesh, hardwareMaterial, hingeW, hingeH, hingeD, hingeMarkerX, leafTop - 0.25, 0);
792
+ const hingeRadii = roundedBoundary?.radii ?? openingTopRadii;
793
+ const cornerDrop = openingShape === 'rounded'
794
+ ? hingeSide === 'left'
795
+ ? hingeRadii.topLeft
796
+ : hingeRadii.topRight
797
+ : openingShape === 'arch'
798
+ ? getClampedArchHeight(leafWidth, leafHeight, archHeight)
799
+ : 0;
800
+ // Hinges need their full height supported by the straight jamb below the curve.
801
+ const straightEdgeTop = leafTop - cornerDrop;
802
+ for (const hingeY of [leafBottom + 0.25, (leafBottom + leafTop) / 2, leafTop - 0.25]) {
803
+ if (hingeY + hingeH / 2 > straightEdgeTop + 1e-6)
804
+ continue;
805
+ addBox(mesh, hardwareMaterial, hingeW, hingeH, hingeD, hingeMarkerX, hingeY, 0);
806
+ }
783
807
  }
784
808
  // When the leaf is swung open it projects into the room and would otherwise
785
809
  // win a top-down selection click over the floor beneath it. Drop only the
@@ -1226,8 +1250,19 @@ function updateDoorMesh(rawNode, mesh) {
1226
1250
  mesh.geometry = new THREE.BoxGeometry(node.width, node.height, node.frameDepth);
1227
1251
  mesh.material = hitboxMaterial;
1228
1252
  // Sync transform from node (React may lag behind the system by a frame during drag)
1229
- mesh.position.set(node.position[0], node.position[1], node.position[2]);
1230
- mesh.rotation.set(node.rotation[0], node.rotation[1], node.rotation[2]);
1253
+ const parent = node.parentId ? useScene.getState().nodes[node.parentId] : undefined;
1254
+ const placement = parent?.type === 'wall' && !node.roofSegmentId
1255
+ ? getOpeningWallPlacement(getEffectiveNode(parent), node, useScene.getState().nodes)
1256
+ : node;
1257
+ mesh.position.set(...placement.position);
1258
+ mesh.rotation.set(...placement.rotation);
1259
+ // Built from a script: the renderer shows its artifact, not the parametric
1260
+ // frame, and the hit box is what the script built.
1261
+ if (node.source) {
1262
+ mesh.geometry.dispose();
1263
+ mesh.geometry = new THREE.BoxGeometry(...scriptedSize(node.source.manifest));
1264
+ return settleScriptedOpening(mesh);
1265
+ }
1231
1266
  // Dispose and remove all old visual children; preserve 'cutout'
1232
1267
  for (const child of [...mesh.children]) {
1233
1268
  if (child.name === 'cutout')
@@ -1250,7 +1285,7 @@ function updateDoorMesh(rawNode, mesh) {
1250
1285
  const clampedSwingAngle = Math.max(0, Math.min(Math.PI / 2, swingAngle));
1251
1286
  if (openingKind === 'opening') {
1252
1287
  syncDoorCutout(node, mesh);
1253
- return;
1288
+ return true;
1254
1289
  }
1255
1290
  const insideWidth = width - 2 * frameThickness;
1256
1291
  const leafH = height - frameThickness; // only top frame
@@ -1492,11 +1527,12 @@ function updateDoorMesh(rawNode, mesh) {
1492
1527
  // … was not set" on a Draw(0, …)). Hide any empty mesh so it is never
1493
1528
  // drawn (it would render nothing anyway).
1494
1529
  hideEmptyGeometryMeshes(mesh);
1530
+ return true;
1495
1531
  }
1496
1532
  function hideEmptyGeometryMeshes(root) {
1497
1533
  root.traverse((obj) => {
1498
1534
  const child = obj;
1499
- if (!child.isMesh || !child.geometry)
1535
+ if (!(child.isMesh && child.geometry))
1500
1536
  return;
1501
1537
  const position = child.geometry.getAttribute('position');
1502
1538
  if (!position || position.count === 0)
@@ -1518,7 +1554,15 @@ function syncDoorCutout(node, mesh) {
1518
1554
  mesh.add(cutout);
1519
1555
  }
1520
1556
  cutout.geometry.dispose();
1521
- const depth = resolveOpeningCutoutProxyDepth(node);
1557
+ const { depth, center } = resolveOpeningCutoutProxy(node);
1558
+ cutout.position.set(0, 0, 0);
1559
+ if (center) {
1560
+ // Curved and justified walls place openings off the reference line. Remove
1561
+ // only the visual plane offset, preserving the frame's resolved floor datum.
1562
+ cutout.position
1563
+ .set(center[0] - mesh.position.x, 0, center[2] - mesh.position.z)
1564
+ .applyQuaternion(mesh.quaternion.clone().invert());
1565
+ }
1522
1566
  const openingShape = getEffectiveOpeningShape(node);
1523
1567
  if (openingShape === 'arch') {
1524
1568
  cutout.geometry = new THREE.ExtrudeGeometry(createArchShape(-node.width / 2, node.width / 2, -node.height / 2, node.height / 2, getClampedArchHeight(node.width, node.height, node.archHeight)), {
@@ -1545,11 +1589,18 @@ function syncDoorCutout(node, mesh) {
1545
1589
  // the proxy stays proud of both wall faces (front/back selection) without the
1546
1590
  // old 1m depth that blanketed the floor. Falls back to the default thickness
1547
1591
  // when the parent wall isn't a resolvable wall node.
1548
- function resolveOpeningCutoutProxyDepth(node) {
1592
+ function resolveOpeningCutoutProxy(node) {
1549
1593
  const parentId = node.parentId;
1550
- const parent = parentId ? useScene.getState().nodes[parentId] : undefined;
1551
- const wallThickness = parent?.type === 'wall' ? getWallThickness(parent) : DEFAULT_WALL_THICKNESS;
1552
- return getOpeningCutoutProxyDepth(wallThickness);
1594
+ const nodes = useScene.getState().nodes;
1595
+ const parent = parentId ? nodes[parentId] : undefined;
1596
+ const wall = parent?.type === 'wall' ? getEffectiveNode(parent) : undefined;
1597
+ const wallThickness = wall ? getWallThickness(wall) : DEFAULT_WALL_THICKNESS;
1598
+ return {
1599
+ depth: getOpeningCutoutProxyDepth(wallThickness),
1600
+ center: wall && !node.roofSegmentId && node.position[2] !== 0
1601
+ ? getOpeningWallPlacement(wall, { ...node, position: [node.position[0], node.position[1], 0] }, nodes).position
1602
+ : undefined,
1603
+ };
1553
1604
  }
1554
1605
  /**
1555
1606
  * Build a fresh door mesh for preview/ghost rendering.
@@ -14,7 +14,9 @@
14
14
  * Runs at priority 1 — before the priority-2 systems (`GeometrySystem`,
15
15
  * `ItemSystem`) so the dirty mark survives long enough for those to do
16
16
  * their own work. Kinds with no geometry/system have no downstream dirty
17
- * consumer, so this system clears their dirty mark after applying the lift.
17
+ * consumer, so this system clears their dirty mark after applying the lift,
18
+ * at priority 2 like every other consumer: the node batch snapshots marks at
19
+ * priority 1, and a mark cleared there could run before that snapshot.
18
20
  */
19
21
  export declare const FloorElevationSystem: () => null;
20
22
  //# sourceMappingURL=floor-elevation-system.d.ts.map
@@ -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,YAiFhC,CAAA"}
1
+ {"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oBAAoB,YAoNhC,CAAA"}