@pascal-app/editor 1.0.7 → 1.1.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 (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,225 +0,0 @@
1
- import { formatNumber, type MeasurementPoint, parseLocalizedNumber } from '@pascal-app/core'
2
-
3
- export type LinearUnit = 'metric' | 'imperial'
4
- export type MetricNotation = 'meters' | 'millimeters'
5
-
6
- export const MEASUREMENT_ACTIVE_COLOR = '#6366f1'
7
- export const MEASUREMENT_DANGLING_COLOR = '#dc2626'
8
- export const MEASUREMENT_FLOORPLAN_COLOR = '#4f46e5'
9
- export const MEASUREMENT_PERSISTENT_COLOR = '#111827'
10
-
11
- export function measurementPresentationColor(dangling: boolean, active: boolean): string {
12
- if (dangling) return MEASUREMENT_DANGLING_COLOR
13
- return active ? MEASUREMENT_ACTIVE_COLOR : MEASUREMENT_PERSISTENT_COLOR
14
- }
15
-
16
- export function measurementFloorplanPresentationColor(dangling: boolean, active: boolean): string {
17
- if (dangling) return MEASUREMENT_DANGLING_COLOR
18
- return active ? MEASUREMENT_ACTIVE_COLOR : MEASUREMENT_FLOORPLAN_COLOR
19
- }
20
-
21
- type MeasurementAngleArcOptions = {
22
- radius?: number
23
- sampleCount?: number
24
- }
25
-
26
- const ANGLE_ARC_EPSILON = 1e-9
27
-
28
- const subtractPoint = (point: MeasurementPoint, origin: MeasurementPoint): MeasurementPoint => [
29
- point[0] - origin[0],
30
- point[1] - origin[1],
31
- point[2] - origin[2],
32
- ]
33
-
34
- const pointLength = (point: MeasurementPoint): number => Math.hypot(...point)
35
-
36
- const scalePoint = (point: MeasurementPoint, scale: number): MeasurementPoint => [
37
- point[0] * scale,
38
- point[1] * scale,
39
- point[2] * scale,
40
- ]
41
-
42
- const crossPoint = (first: MeasurementPoint, second: MeasurementPoint): MeasurementPoint => [
43
- first[1] * second[2] - first[2] * second[1],
44
- first[2] * second[0] - first[0] * second[2],
45
- first[0] * second[1] - first[1] * second[0],
46
- ]
47
-
48
- const dotPoint = (first: MeasurementPoint, second: MeasurementPoint): number =>
49
- first[0] * second[0] + first[1] * second[1] + first[2] * second[2]
50
-
51
- export function buildMeasurementAngleArcPoints(
52
- start: MeasurementPoint,
53
- vertex: MeasurementPoint,
54
- end: MeasurementPoint,
55
- options: MeasurementAngleArcOptions = {},
56
- ): MeasurementPoint[] {
57
- if (![...start, ...vertex, ...end].every(Number.isFinite)) return []
58
-
59
- const startVector = subtractPoint(start, vertex)
60
- const endVector = subtractPoint(end, vertex)
61
- const startLength = pointLength(startVector)
62
- const endLength = pointLength(endVector)
63
- if (startLength <= ANGLE_ARC_EPSILON || endLength <= ANGLE_ARC_EPSILON) return []
64
-
65
- const startDirection = scalePoint(startVector, 1 / startLength)
66
- const endDirection = scalePoint(endVector, 1 / endLength)
67
- const cosine = Math.max(-1, Math.min(1, dotPoint(startDirection, endDirection)))
68
- const angle = Math.acos(cosine)
69
- if (angle <= 1e-4) return []
70
-
71
- let normal = crossPoint(startDirection, endDirection)
72
- let normalLength = pointLength(normal)
73
- if (normalLength <= ANGLE_ARC_EPSILON) {
74
- const reference: MeasurementPoint = Math.abs(startDirection[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0]
75
- normal = crossPoint(startDirection, reference)
76
- normalLength = pointLength(normal)
77
- }
78
- if (normalLength <= ANGLE_ARC_EPSILON) return []
79
- normal = scalePoint(normal, 1 / normalLength)
80
- const tangent = crossPoint(normal, startDirection)
81
-
82
- const shortestSide = Math.min(startLength, endLength)
83
- const preferredRadius = options.radius ?? Math.min(Math.max(shortestSide * 0.28, 0.08), 0.75)
84
- const radius = Math.min(Math.max(preferredRadius, 0), shortestSide * 0.45)
85
- if (radius <= ANGLE_ARC_EPSILON) return []
86
-
87
- const sampleCount = Math.min(
88
- 64,
89
- Math.max(4, Math.round(options.sampleCount ?? Math.max(8, (angle / Math.PI) * 32))),
90
- )
91
- return Array.from({ length: sampleCount + 1 }, (_, index) => {
92
- const sampleAngle = angle * (index / sampleCount)
93
- const direction: MeasurementPoint = [
94
- startDirection[0] * Math.cos(sampleAngle) + tangent[0] * Math.sin(sampleAngle),
95
- startDirection[1] * Math.cos(sampleAngle) + tangent[1] * Math.sin(sampleAngle),
96
- startDirection[2] * Math.cos(sampleAngle) + tangent[2] * Math.sin(sampleAngle),
97
- ]
98
- return [
99
- vertex[0] + direction[0] * radius,
100
- vertex[1] + direction[1] * radius,
101
- vertex[2] + direction[2] * radius,
102
- ]
103
- })
104
- }
105
-
106
- const METERS_PER_FOOT = 0.3048
107
- const FEET_PER_METER = 1 / METERS_PER_FOOT
108
-
109
- type LinearControlValueOptions = {
110
- minMeters?: number
111
- maxMeters?: number
112
- }
113
-
114
- export function metersToLinearUnit(meters: number, unit: LinearUnit): number {
115
- return unit === 'imperial' ? meters * FEET_PER_METER : meters
116
- }
117
-
118
- export function linearUnitToMeters(value: number, unit: LinearUnit): number {
119
- return unit === 'imperial' ? value * METERS_PER_FOOT : value
120
- }
121
-
122
- export function parseLinearMeasurementInput(
123
- value: string,
124
- unit: LinearUnit,
125
- locale: string,
126
- ): number | null {
127
- const parsed = parseLocalizedNumber(value, locale)
128
- return parsed === null ? null : linearUnitToMeters(parsed, unit)
129
- }
130
-
131
- export function linearControlValueToMeters(
132
- value: number,
133
- unit: LinearUnit,
134
- options: LinearControlValueOptions = {},
135
- ): number {
136
- const meters = linearUnitToMeters(value, unit)
137
- const minMeters = options.minMeters ?? Number.NEGATIVE_INFINITY
138
- const maxMeters = options.maxMeters ?? Number.POSITIVE_INFINITY
139
-
140
- return Math.min(Math.max(meters, minMeters), maxMeters)
141
- }
142
-
143
- export function getLinearUnitLabel(unit: LinearUnit): string {
144
- return unit === 'imperial' ? 'ft' : 'm'
145
- }
146
-
147
- const SQUARE_FEET_PER_SQUARE_METER = FEET_PER_METER * FEET_PER_METER
148
- const CUBIC_FEET_PER_CUBIC_METER = SQUARE_FEET_PER_SQUARE_METER * FEET_PER_METER
149
-
150
- export function squareMetersToAreaUnit(squareMeters: number, unit: LinearUnit): number {
151
- return unit === 'imperial' ? squareMeters * SQUARE_FEET_PER_SQUARE_METER : squareMeters
152
- }
153
-
154
- export function getAreaUnitLabel(unit: LinearUnit): string {
155
- return unit === 'imperial' ? 'ft²' : 'm²'
156
- }
157
-
158
- export function formatAreaLabel(
159
- squareMeters: number,
160
- unit: LinearUnit,
161
- fractionDigits = 1,
162
- ): string {
163
- if (!Number.isFinite(squareMeters)) return '--'
164
-
165
- return `${formatNumber(squareMetersToAreaUnit(squareMeters, unit), {
166
- min: fractionDigits,
167
- max: fractionDigits,
168
- })}${getAreaUnitLabel(unit)}`
169
- }
170
-
171
- export function cubicMetersToVolumeUnit(cubicMeters: number, unit: LinearUnit): number {
172
- return unit === 'imperial' ? cubicMeters * CUBIC_FEET_PER_CUBIC_METER : cubicMeters
173
- }
174
-
175
- export function getVolumeUnitLabel(unit: LinearUnit): string {
176
- return unit === 'imperial' ? 'ft³' : 'm³'
177
- }
178
-
179
- export function formatVolumeLabel(
180
- cubicMeters: number,
181
- unit: LinearUnit,
182
- fractionDigits = 1,
183
- ): string {
184
- if (!Number.isFinite(cubicMeters)) return '--'
185
-
186
- return `${formatNumber(cubicMetersToVolumeUnit(cubicMeters, unit), {
187
- min: fractionDigits,
188
- max: fractionDigits,
189
- })}${getVolumeUnitLabel(unit)}`
190
- }
191
-
192
- export function formatLinearMeasurement(
193
- meters: number,
194
- unit: LinearUnit,
195
- metricNotation: MetricNotation = 'meters',
196
- ): string {
197
- if (!Number.isFinite(meters)) return '--'
198
-
199
- const absoluteMeters = Math.abs(meters)
200
-
201
- if (unit === 'imperial') {
202
- const feet = metersToLinearUnit(absoluteMeters, unit)
203
- let wholeFeet = Math.floor(feet)
204
- let inches = Math.round((feet - wholeFeet) * 12)
205
- if (inches === 12) {
206
- wholeFeet += 1
207
- inches = 0
208
- }
209
-
210
- const sign = meters < 0 && (wholeFeet !== 0 || inches !== 0) ? '-' : ''
211
-
212
- return `${sign}${wholeFeet}'${inches}"`
213
- }
214
-
215
- if (metricNotation === 'millimeters') {
216
- const roundedMillimeters = Math.round(absoluteMeters * 1000)
217
- const sign = meters < 0 && roundedMillimeters !== 0 ? '-' : ''
218
- return `${sign}${formatNumber(roundedMillimeters, { max: 0 })}mm`
219
- }
220
-
221
- const roundedMeters = Number.parseFloat(absoluteMeters.toFixed(2))
222
- const sign = meters < 0 && roundedMeters !== 0 ? '-' : ''
223
-
224
- return `${sign}${formatNumber(roundedMeters, { max: 2 })}m`
225
- }
@@ -1,37 +0,0 @@
1
- import type { AnyNode } from '@pascal-app/core'
2
-
3
- /**
4
- * A node's human label, the same in the baked artifact (`extras.label`) and the
5
- * viewer's breadcrumb: an explicit name wins, items fall back to their catalog
6
- * asset name, other kinds to a type word.
7
- */
8
- export function nodeDisplayLabel(node: AnyNode): string {
9
- if (node.name) return node.name
10
- switch (node.type) {
11
- case 'item':
12
- return (node as { asset?: { name?: string } }).asset?.name || 'Item'
13
- case 'wall':
14
- return 'Wall'
15
- case 'door':
16
- return 'Door'
17
- case 'window':
18
- return 'Window'
19
- case 'cabinet':
20
- case 'cabinet-module':
21
- return 'Cabinet'
22
- case 'slab':
23
- return 'Slab'
24
- case 'ceiling':
25
- return 'Ceiling'
26
- case 'roof':
27
- return 'Roof'
28
- case 'fence':
29
- return 'Fence'
30
- case 'column':
31
- return 'Column'
32
- case 'stair':
33
- return 'Stairs'
34
- default:
35
- return node.type
36
- }
37
- }
@@ -1,275 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test'
2
- import { readFileSync } from 'node:fs'
3
- import {
4
- type AnyNode,
5
- BuildingNode,
6
- CeilingNode,
7
- getWallArcData,
8
- getWallFaceOffsets,
9
- LevelNode,
10
- SlabNode,
11
- sceneRegistry,
12
- WallNode,
13
- } from '@pascal-app/core'
14
- import { migrateCeilingRoomLinks, migrateRoomZones } from '@pascal-app/core/scene-migrations'
15
- import { BoxGeometry, Mesh, MeshBasicMaterial } from 'three'
16
- import {
17
- buildRoomAssembly,
18
- getRoomAssembly,
19
- type RoomAssemblyHeights,
20
- resolveRoomAssemblyHeights,
21
- roomWallFaces,
22
- } from './room-assembly-overlay'
23
- import { RoomSelectionIndex, type RoomSelectionRecord } from './room-selection'
24
-
25
- const levelId = 'level_room_assembly'
26
- const buildingId = 'building_room_assembly'
27
-
28
- function wall(id: string, start: [number, number], end: [number, number], extra = {}) {
29
- return WallNode.parse({
30
- id: `wall_${id}`,
31
- parentId: levelId,
32
- start,
33
- end,
34
- thickness: 0.2,
35
- ...extra,
36
- })
37
- }
38
-
39
- function scene(walls: WallNode[], extra: AnyNode[] = []) {
40
- const raw = Object.fromEntries(
41
- [
42
- BuildingNode.parse({ id: buildingId, children: [levelId] }),
43
- LevelNode.parse({
44
- id: levelId,
45
- parentId: buildingId,
46
- children: [...walls, ...extra].map((node) => node.id),
47
- }),
48
- ...walls,
49
- ...extra,
50
- ].map((node) => [node.id, node]),
51
- )
52
- return migrateCeilingRoomLinks(migrateRoomZones(raw).nodes).nodes as Record<string, AnyNode>
53
- }
54
-
55
- // Two 4 x 4 rooms split by `shared`, with a T-stem into the east room.
56
- function twoRooms(extraWalls: WallNode[] = []) {
57
- return scene([
58
- wall('south', [0, 0], [8, 0]),
59
- wall('east', [8, 0], [8, 4]),
60
- wall('north', [8, 4], [0, 4]),
61
- wall('west', [0, 4], [0, 0]),
62
- wall('shared', [4, 0], [4, 4]),
63
- ...extraWalls,
64
- ])
65
- }
66
-
67
- function roomAt(records: RoomSelectionRecord[], point: [number, number]) {
68
- const [x, z] = point
69
- return records.find(({ polygon }) => {
70
- const xs = polygon.map((p) => p[0])
71
- const zs = polygon.map((p) => p[1])
72
- return x > Math.min(...xs) && x < Math.max(...xs) && z > Math.min(...zs) && z < Math.max(...zs)
73
- })!
74
- }
75
-
76
- function withNodes(nodes: Record<string, AnyNode>, ...added: AnyNode[]) {
77
- const level = nodes[levelId] as LevelNode
78
- return {
79
- ...nodes,
80
- ...Object.fromEntries(added.map((node) => [node.id, node])),
81
- [levelId]: { ...level, children: [...level.children, ...added.map((node) => node.id)] },
82
- } as Record<string, AnyNode>
83
- }
84
-
85
- const heights = (tops: Record<string, number>, floorY = 0): RoomAssemblyHeights => ({
86
- floorY,
87
- wallTops: new Map(Object.entries(tops)),
88
- ceiling: null,
89
- })
90
-
91
- const length = ({ from, to }: { from: readonly number[]; to: readonly number[] }) =>
92
- Math.hypot(to[0]! - from[0]!, to[1]! - from[1]!)
93
-
94
- function yValues(array: Float32Array) {
95
- const ys = new Set<number>()
96
- for (let i = 1; i < array.length; i += 3) ys.add(Math.round(array[i]! * 1000) / 1000)
97
- return [...ys].sort((a, b) => a - b)
98
- }
99
-
100
- afterEach(() => sceneRegistry.clear())
101
-
102
- describe('room assembly faces', () => {
103
- test('only the room-facing face, clipped at the partition and the corners', () => {
104
- const room = roomAt(new RoomSelectionIndex(levelId).update(twoRooms()), [2, 2])
105
- const faces = roomWallFaces(room.geometry)
106
- const byWall = new Map<string, typeof faces>()
107
- for (const face of faces) byWall.set(face.wallId, [...(byWall.get(face.wallId) ?? []), face])
108
- expect([...byWall.keys()].sort()).toEqual(
109
- ['wall_north', 'wall_shared', 'wall_south', 'wall_west'].sort(),
110
- )
111
- const south = byWall.get('wall_south')!
112
- expect(south.every(({ face }) => face === 'a')).toBe(true)
113
- for (const { from, to } of south) {
114
- expect(from[1]).toBeCloseTo(0.1)
115
- expect(to[1]).toBeCloseTo(0.1)
116
- for (const x of [from[0], to[0]]) {
117
- expect(x).toBeGreaterThanOrEqual(0.1 - 1e-6)
118
- expect(x).toBeLessThanOrEqual(3.9 + 1e-6)
119
- }
120
- }
121
- expect(south.reduce((sum, face) => sum + length(face), 0)).toBeCloseTo(3.8)
122
- // The partition contributes its west face only.
123
- for (const { from, to } of byWall.get('wall_shared')!) {
124
- expect(from[0]).toBeCloseTo(3.9)
125
- expect(to[0]).toBeCloseTo(3.9)
126
- }
127
- })
128
-
129
- test('a T-stem splits the host face into per-room t-ranges', () => {
130
- const nodes = twoRooms([wall('stem', [4, 2], [8, 2])])
131
- const records = new RoomSelectionIndex(levelId).update(nodes)
132
- const lower = roomAt(records, [6, 1])
133
- const upper = roomAt(records, [6, 3])
134
- for (const [room, zMin, zMax] of [
135
- [lower, 0.1, 1.9],
136
- [upper, 2.1, 3.9],
137
- ] as const) {
138
- const shared = roomWallFaces(room.geometry).filter(({ wallId }) => wallId === 'wall_shared')
139
- const span = room.spans.find(({ boundaryId }) => boundaryId === 'wall_shared')!
140
- expect(shared.reduce((sum, face) => sum + length(face), 0)).toBeCloseTo(zMax - zMin)
141
- for (const { from, to, face } of shared) {
142
- expect(face).toBe(span.face)
143
- for (const [x, z] of [from, to]) {
144
- expect(x).toBeCloseTo(4.1)
145
- expect(z).toBeGreaterThanOrEqual(zMin - 1e-6)
146
- expect(z).toBeLessThanOrEqual(zMax + 1e-6)
147
- const t = z / 4
148
- expect(t).toBeGreaterThanOrEqual(span.t0 - 1e-6)
149
- expect(t).toBeLessThanOrEqual(span.t1 + 1e-6)
150
- }
151
- }
152
- }
153
- })
154
-
155
- test('curved faces follow the offset arc and only draw verticals at their ends', () => {
156
- const nodes = twoRooms()
157
- const curved = { ...(nodes.wall_shared as WallNode), curveOffset: 0.5 }
158
- const room = roomAt(
159
- new RoomSelectionIndex(levelId).update({ ...nodes, [curved.id]: curved }),
160
- [1, 2],
161
- )
162
- const faces = roomWallFaces(room.geometry).filter(({ wallId }) => wallId === curved.id)
163
- expect(faces.length).toBeGreaterThan(4)
164
- const arc = getWallArcData(curved)!
165
- const face = faces[0]!.face
166
- const radius = arc.radius - arc.direction * getWallFaceOffsets(curved)[face]
167
- for (const { from, to } of faces) {
168
- for (const [x, z] of [from, to])
169
- expect(Math.hypot(x - arc.center.x, z - arc.center.y)).toBeCloseTo(radius, 2)
170
- }
171
- const tops = Object.fromEntries(room.boundaryWallIds.map((id) => [id, 2.5]))
172
- const { outline } = buildRoomAssembly(room.geometry, heights(tops))
173
- let verticals = 0
174
- for (let i = 0; i < outline.length; i += 6) {
175
- if (
176
- outline[i + 1]! !== outline[i + 4]! &&
177
- Math.hypot(outline[i]! - outline[i + 3]!, outline[i + 2]! - outline[i + 5]!) < 1e-6
178
- )
179
- verticals++
180
- }
181
- // Four walls, two run ends each; the arc's chords share their joints.
182
- expect(verticals).toBe(8)
183
- })
184
- })
185
-
186
- describe('room assembly geometry', () => {
187
- test('walls rise from the plate top to their own tops; the ceiling sits at its height', () => {
188
- const base = twoRooms()
189
- const initial = roomAt(new RoomSelectionIndex(levelId).update(base), [2, 2])
190
- const polygon: [number, number][] = [
191
- [0.1, 0.1],
192
- [3.9, 0.1],
193
- [3.9, 3.9],
194
- [0.1, 3.9],
195
- ]
196
- const nodes = withNodes(
197
- { ...base, wall_south: { ...(base.wall_south as WallNode), height: 2.1 } },
198
- SlabNode.parse({ parentId: levelId, polygon, zoneIds: [initial.zoneId], elevation: 0.3 }),
199
- CeilingNode.parse({ parentId: levelId, polygon, zoneId: initial.zoneId, height: 2.4 }),
200
- )
201
- const room = roomAt(new RoomSelectionIndex(levelId).update(nodes), [2, 2])
202
- const resolved = resolveRoomAssemblyHeights(room, nodes)
203
- expect(resolved.floorY).toBe(0.3)
204
- expect(resolved.ceiling?.y).toBe(2.4)
205
- expect(resolved.wallTops.get('wall_south')).toBe(2.1)
206
- const planeTop = resolved.wallTops.get('wall_west')!
207
- expect(planeTop).toBeGreaterThan(2.1)
208
- const assembly = buildRoomAssembly(room.geometry, resolved)
209
- expect(yValues(assembly.floor)).toEqual([0.305])
210
- expect(yValues(assembly.surfaces)).toEqual(
211
- [0.305, 2.1, 2.38, Math.round(planeTop * 1000) / 1000].sort((a, b) => a - b),
212
- )
213
- expect(yValues(assembly.outline)).toContain(2.38)
214
- expect(assembly.floor.length).toBeGreaterThan(0)
215
- })
216
-
217
- test('cache hits on the same topology revision and misses on topology or height change', () => {
218
- const index = new RoomSelectionIndex(levelId)
219
- const nodes = twoRooms()
220
- const room = roomAt(index.update(nodes), [2, 2])
221
- const tops = Object.fromEntries(room.boundaryWallIds.map((id) => [id, 2.5]))
222
- const first = getRoomAssembly(room.geometry, heights(tops))
223
- // A fresh heights object with equal values and an unrelated scene edit both hit.
224
- expect(getRoomAssembly(room.geometry, heights({ ...tops }))).toBe(first)
225
- const zone = nodes[room.zoneId]!
226
- const renamed = roomAt(
227
- index.update({ ...nodes, [zone.id]: { ...zone, name: 'Kitchen' } as AnyNode }),
228
- [2, 2],
229
- )
230
- expect(renamed.geometry).toBe(room.geometry)
231
- expect(getRoomAssembly(renamed.geometry, heights(tops))).toBe(first)
232
- expect(getRoomAssembly(room.geometry, heights({ ...tops, wall_south: 2 }))).not.toBe(first)
233
- const moved = roomAt(
234
- index.update({
235
- ...nodes,
236
- wall_shared: { ...(nodes.wall_shared as WallNode), start: [5, 0], end: [5, 4] },
237
- }),
238
- [2, 2],
239
- )
240
- expect(moved.geometry).not.toBe(room.geometry)
241
- const rebuilt = getRoomAssembly(moved.geometry, heights(tops))
242
- expect(rebuilt).not.toBe(first)
243
- expect(rebuilt.floor.length).toBeGreaterThan(0)
244
- })
245
-
246
- test('building and resolving never touch the rendered wall meshes', () => {
247
- const nodes = twoRooms()
248
- const room = roomAt(new RoomSelectionIndex(levelId).update(nodes), [2, 2])
249
- const meshes = room.boundaryWallIds.map((id) => {
250
- const mesh = new Mesh(new BoxGeometry(), new MeshBasicMaterial())
251
- mesh.userData = { batched: true, pascalId: id }
252
- sceneRegistry.nodes.set(id, mesh)
253
- return {
254
- mesh,
255
- material: mesh.material,
256
- geometry: mesh.geometry,
257
- layers: mesh.layers.mask,
258
- userData: structuredClone(mesh.userData),
259
- visible: mesh.visible,
260
- }
261
- })
262
- getRoomAssembly(room.geometry, resolveRoomAssemblyHeights(room, nodes))
263
- for (const { mesh, material, geometry, layers, userData, visible } of meshes) {
264
- expect(mesh.material).toBe(material)
265
- expect(mesh.geometry).toBe(geometry)
266
- expect(mesh.layers.mask).toBe(layers)
267
- expect(mesh.userData).toEqual(userData)
268
- expect(mesh.visible).toBe(visible)
269
- }
270
- for (const file of ['./room-assembly-overlay.ts', '../components/editor/room-highlight.tsx']) {
271
- const source = readFileSync(new URL(file, import.meta.url), 'utf8')
272
- expect(source).not.toMatch(/\.material\s*=[^=]|userData|sceneRegistry\.nodes\.set/)
273
- }
274
- })
275
- })