@pascal-app/editor 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 (498) hide show
  1. package/package.json +16 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +221 -128
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +37 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +536 -487
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/grid.tsx +31 -7
  29. package/src/components/editor/group-actions.ts +59 -11
  30. package/src/components/editor/group-floating-action-menu.tsx +19 -9
  31. package/src/components/editor/group-move-3d.ts +4 -6
  32. package/src/components/editor/group-rotate-handle.tsx +81 -35
  33. package/src/components/editor/group-selection-box-3d.tsx +25 -17
  34. package/src/components/editor/group-transform-shared.test.ts +126 -1
  35. package/src/components/editor/group-transform-shared.ts +79 -73
  36. package/src/components/editor/handles/handle-arrow.tsx +72 -2
  37. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  38. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  39. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  40. package/src/components/editor/handles/use-handle-drag.ts +272 -131
  41. package/src/components/editor/index.tsx +316 -176
  42. package/src/components/editor/join-walls-pill.tsx +44 -0
  43. package/src/components/editor/node-action-menu.tsx +70 -70
  44. package/src/components/editor/node-arrow-handles.tsx +167 -14
  45. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  46. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  47. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  48. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  49. package/src/components/editor/registry-action-contributions.tsx +132 -0
  50. package/src/components/editor/room-controls.tsx +847 -0
  51. package/src/components/editor/room-floor-highlight.tsx +128 -0
  52. package/src/components/editor/room-handles-2d.tsx +354 -0
  53. package/src/components/editor/room-handles.tsx +675 -0
  54. package/src/components/editor/room-highlight.tsx +225 -0
  55. package/src/components/editor/room-labels.test.ts +357 -0
  56. package/src/components/editor/room-labels.tsx +990 -0
  57. package/src/components/editor/room-transform-ghost.tsx +256 -0
  58. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  59. package/src/components/editor/selection-manager.tsx +451 -317
  60. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  61. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  62. package/src/components/editor/snapshot-capture.test.ts +52 -4
  63. package/src/components/editor/snapshot-capture.ts +30 -5
  64. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  65. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  66. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  67. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  68. package/src/components/editor/views/host-view.tsx +61 -0
  69. package/src/components/editor/views/plugins-view.tsx +670 -0
  70. package/src/components/editor/views/scene-region.tsx +59 -0
  71. package/src/components/editor/views/use-editor-views.ts +57 -0
  72. package/src/components/editor/views/view-bar.tsx +220 -0
  73. package/src/components/editor/wall-justification.test.ts +140 -0
  74. package/src/components/editor/wall-move-side-handles.tsx +114 -163
  75. package/src/components/editor/wall-opening-highlights.tsx +2 -3
  76. package/src/components/editor/wall-push-arrow.tsx +92 -0
  77. package/src/components/editor/wall-region-handles.tsx +509 -0
  78. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  79. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  80. package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
  81. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  82. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  83. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
  84. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  85. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
  86. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  88. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  89. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +166 -0
  90. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  91. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
  92. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
  93. package/src/components/editor-2d/svg-paths.ts +12 -15
  94. package/src/components/procedural-items/inspector.tsx +40 -15
  95. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  96. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  97. package/src/components/systems/zone/zone-system.test.ts +47 -0
  98. package/src/components/systems/zone/zone-system.tsx +31 -66
  99. package/src/components/tools/__fixtures__/hosting.ts +10 -0
  100. package/src/components/tools/item/item-surface-placement.test.ts +3 -9
  101. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
  102. package/src/components/tools/item/placement-history.test.tsx +474 -0
  103. package/src/components/tools/item/placement-strategies.ts +63 -47
  104. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
  105. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  106. package/src/components/tools/item/use-draft-node.ts +207 -97
  107. package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
  108. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  109. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  110. package/src/components/tools/registry/item-surface-move.test.ts +10 -2
  111. package/src/components/tools/registry/item-surface-move.ts +46 -24
  112. package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
  114. package/src/components/tools/select/box-select-tool.tsx +2 -12
  115. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  116. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  117. package/src/components/tools/select/select-candidates.test.ts +37 -4
  118. package/src/components/tools/select/select-candidates.ts +15 -11
  119. package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
  120. package/src/components/tools/shared/affordance-dispatch.ts +13 -0
  121. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  122. package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
  123. package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
  124. package/src/components/tools/shared/pointer-support-cap.ts +17 -5
  125. package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
  126. package/src/components/tools/shared/surface-rejection.tsx +14 -0
  127. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  128. package/src/components/tools/stair/stair-defaults.ts +0 -3
  129. package/src/components/tools/stair/stair-tool.tsx +90 -42
  130. package/src/components/tools/tool-manager.tsx +82 -23
  131. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  132. package/src/components/tools/zone/zone-tool.tsx +4 -12
  133. package/src/components/ui/action-menu/action-button.tsx +3 -0
  134. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  135. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  136. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  137. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  138. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  139. package/src/components/ui/command-palette/index.tsx +5 -24
  140. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  141. package/src/components/ui/controls/material-paint-panel.tsx +101 -91
  142. package/src/components/ui/controls/material-picker.tsx +8 -67
  143. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  144. package/src/components/ui/controls/select-control.tsx +65 -0
  145. package/src/components/ui/controls/slider-control.tsx +76 -31
  146. package/src/components/ui/floating-level-selector.tsx +9 -14
  147. package/src/components/ui/helpers/building-helper.tsx +4 -1
  148. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  149. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  150. package/src/components/ui/helpers/helper-manager.tsx +348 -19
  151. package/src/components/ui/helpers/item-helper.tsx +8 -0
  152. package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
  153. package/src/components/ui/icon-ref.tsx +45 -1
  154. package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
  155. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  156. package/src/components/ui/node-icon-image.tsx +16 -0
  157. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  158. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  159. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  160. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  161. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  162. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  163. package/src/components/ui/panels/multi-field-value.ts +6 -3
  164. package/src/components/ui/panels/multi-height-mode.tsx +27 -21
  165. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  166. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  167. package/src/components/ui/panels/node-display.ts +8 -3
  168. package/src/components/ui/panels/panel-manager.tsx +103 -68
  169. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  170. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  171. package/src/components/ui/panels/parametric-inspector.tsx +7 -19
  172. package/src/components/ui/panels/reference-panel.tsx +2 -3
  173. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  174. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  175. package/src/components/ui/panels/room-name-options.ts +47 -0
  176. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  177. package/src/components/ui/panels/room-panel.tsx +725 -0
  178. package/src/components/ui/panels/shape-choice.tsx +58 -0
  179. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  180. package/src/components/ui/primitives/color-dot.tsx +36 -24
  181. package/src/components/ui/primitives/shortcut-token.tsx +4 -0
  182. package/src/components/ui/primitives/sidebar.tsx +2 -0
  183. package/src/components/ui/right-stack.test.ts +29 -0
  184. package/src/components/ui/right-stack.tsx +145 -0
  185. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  186. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  187. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
  188. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
  189. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  190. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  191. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  192. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  193. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  194. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  195. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  196. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  197. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  198. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  199. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  200. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  211. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  215. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  216. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  217. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  218. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  219. package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
  220. package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
  221. package/src/components/viewer/floorplan-preview.tsx +6 -18
  222. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  223. package/src/components/viewer/viewer-rooms.tsx +175 -0
  224. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  225. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  226. package/src/components/viewer/viewer-stage.tsx +2 -2
  227. package/src/components/viewer-zone-system.tsx +9 -9
  228. package/src/hooks/use-auto-save.integration.test.tsx +311 -0
  229. package/src/hooks/use-auto-save.ts +55 -14
  230. package/src/hooks/use-ceiling-events.test.ts +1 -0
  231. package/src/hooks/use-ceiling-events.ts +2 -0
  232. package/src/hooks/use-grid-events.test.ts +2 -0
  233. package/src/hooks/use-grid-events.ts +16 -11
  234. package/src/hooks/use-keyboard.test.ts +182 -0
  235. package/src/hooks/use-keyboard.ts +122 -91
  236. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  237. package/src/hooks/use-open-wall-ends.ts +238 -0
  238. package/src/hooks/use-placement-notice.test.ts +48 -0
  239. package/src/hooks/use-placement-notice.ts +42 -0
  240. package/src/hooks/use-selected-room.ts +143 -0
  241. package/src/index.tsx +306 -39
  242. package/src/lib/catalog-panel-model.ts +39 -0
  243. package/src/lib/catalog-selection.test.ts +27 -0
  244. package/src/lib/catalog-selection.ts +8 -0
  245. package/src/lib/ceiling-edit-session.test.ts +321 -0
  246. package/src/lib/ceiling-edit-session.ts +206 -0
  247. package/src/lib/ceiling-region-session.test.ts +179 -0
  248. package/src/lib/contextual-help.test.ts +27 -1
  249. package/src/lib/contextual-help.ts +16 -9
  250. package/src/lib/continuation.test.ts +19 -1
  251. package/src/lib/continuation.ts +25 -4
  252. package/src/lib/door-interaction.ts +14 -0
  253. package/src/lib/editor-level-display.test.ts +96 -0
  254. package/src/lib/editor-level-display.ts +48 -0
  255. package/src/lib/editor-views.ts +144 -0
  256. package/src/lib/export-geometry-wait.test.ts +90 -0
  257. package/src/lib/floor-edit-session.ts +153 -0
  258. package/src/lib/floor-footprints.test.ts +563 -0
  259. package/src/lib/floor-footprints.ts +395 -0
  260. package/src/lib/floor-foundation-support.test.ts +283 -0
  261. package/src/lib/floor-opening-draft.ts +218 -0
  262. package/src/lib/floor-openings.test.ts +216 -0
  263. package/src/lib/floor-region-geometry.ts +191 -0
  264. package/src/lib/floor-region-session.test.ts +485 -0
  265. package/src/lib/floor-region-session.ts +589 -0
  266. package/src/lib/floor-region-snap.ts +101 -0
  267. package/src/lib/floor-step-hover.test.ts +32 -0
  268. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  269. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  270. package/src/lib/floorplan/dimension-drive.ts +479 -0
  271. package/src/lib/floorplan/floorplan-export.test.ts +15 -9
  272. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  273. package/src/lib/floorplan/floorplan-extension.ts +48 -0
  274. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  275. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  276. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  277. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  278. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  279. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  280. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  281. package/src/lib/floorplan/geometry.ts +13 -6
  282. package/src/lib/floorplan/index.ts +0 -9
  283. package/src/lib/floorplan/items.ts +5 -2
  284. package/src/lib/floorplan/marks.test.ts +213 -0
  285. package/src/lib/floorplan/marks.ts +238 -0
  286. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  287. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  288. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  289. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  290. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  291. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  292. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  293. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  294. package/src/lib/floorplan/schedules.test.ts +189 -0
  295. package/src/lib/floorplan/schedules.ts +339 -0
  296. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  297. package/src/lib/floorplan/sheet-export.ts +318 -0
  298. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  299. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  300. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  301. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  302. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  303. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  304. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  305. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  306. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  307. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  308. package/src/lib/floorplan/site-plan/index.ts +71 -0
  309. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  310. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  311. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  312. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  313. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  314. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  315. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  316. package/src/lib/floorplan/types.ts +1 -27
  317. package/src/lib/fresh-planar-placement.test.ts +36 -0
  318. package/src/lib/fresh-planar-placement.ts +191 -36
  319. package/src/lib/geometry-script/author.ts +152 -0
  320. package/src/lib/geometry-script/client.ts +152 -0
  321. package/src/lib/geometry-script/protocol.ts +12 -0
  322. package/src/lib/gesture-lifecycle.test.ts +595 -0
  323. package/src/lib/gesture-lifecycle.ts +290 -0
  324. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  325. package/src/lib/glb-export.test.ts +544 -16
  326. package/src/lib/glb-export.ts +256 -125
  327. package/src/lib/grid-frame.test.ts +19 -0
  328. package/src/lib/grid-frame.ts +18 -0
  329. package/src/lib/history.test.ts +6 -0
  330. package/src/lib/history.ts +7 -0
  331. package/src/lib/hud-title.ts +109 -0
  332. package/src/lib/hydration-gated-storage.ts +49 -0
  333. package/src/lib/ifc-export.test.ts +174 -0
  334. package/src/lib/ifc-export.ts +201 -0
  335. package/src/lib/ifc-parts.ts +15 -0
  336. package/src/lib/inspector-expanded.test.ts +44 -0
  337. package/src/lib/inspector-expanded.ts +36 -0
  338. package/src/lib/interaction/scope.ts +27 -1
  339. package/src/lib/keyboard-pan.test.ts +184 -0
  340. package/src/lib/keyboard-pan.ts +238 -0
  341. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  342. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  343. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  344. package/src/lib/lot/clean-ring.test.ts +186 -0
  345. package/src/lib/lot/clean-ring.ts +217 -0
  346. package/src/lib/lot/dossier.test.ts +202 -0
  347. package/src/lib/lot/dossier.ts +498 -0
  348. package/src/lib/lot/drop-in.test.ts +326 -0
  349. package/src/lib/lot/drop-in.ts +407 -0
  350. package/src/lib/lot/index.ts +43 -0
  351. package/src/lib/lot/lot-patch.test.ts +340 -0
  352. package/src/lib/lot/lot-patch.ts +394 -0
  353. package/src/lib/lot/parcel-provider.ts +43 -0
  354. package/src/lib/lot/terrain.test.ts +161 -0
  355. package/src/lib/lot/terrain.ts +266 -0
  356. package/src/lib/material-catalog-model.ts +74 -0
  357. package/src/lib/material-paint-panel-model.ts +80 -0
  358. package/src/lib/material-paint.ts +4 -0
  359. package/src/lib/mezzanine-actions.test.ts +342 -0
  360. package/src/lib/mezzanine-draft.test.ts +501 -0
  361. package/src/lib/mezzanine-draft.ts +278 -0
  362. package/src/lib/mezzanine-messages.ts +26 -0
  363. package/src/lib/mezzanine-selection.test.ts +207 -0
  364. package/src/lib/mezzanine-stairs.ts +46 -0
  365. package/src/lib/model-export.ts +3 -1
  366. package/src/lib/node-action-movement.ts +17 -6
  367. package/src/lib/node-display-label.ts +37 -0
  368. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  369. package/src/lib/orbit-floor-clearance.ts +106 -0
  370. package/src/lib/overlay-transform.test.ts +28 -0
  371. package/src/lib/overlay-transform.ts +12 -0
  372. package/src/lib/paint-commit-route.test.ts +37 -0
  373. package/src/lib/paint-commit-route.ts +17 -0
  374. package/src/lib/paint-eyedropper.test.tsx +165 -0
  375. package/src/lib/paint-eyedropper.ts +99 -0
  376. package/src/lib/paint-pass-through.test.ts +111 -0
  377. package/src/lib/paint-pass-through.ts +76 -0
  378. package/src/lib/paint-region-hover.ts +25 -0
  379. package/src/lib/paint-region-mode.ts +154 -0
  380. package/src/lib/paint-regions.test.ts +166 -0
  381. package/src/lib/paint-regions.ts +192 -0
  382. package/src/lib/paint-scope.test.ts +317 -35
  383. package/src/lib/paint-scope.ts +266 -25
  384. package/src/lib/panel-rows.ts +96 -0
  385. package/src/lib/panel-tool-options.test.ts +34 -0
  386. package/src/lib/panel-tool-options.ts +46 -0
  387. package/src/lib/parametric-node-update.ts +37 -0
  388. package/src/lib/plate-paint-affected.test.ts +300 -0
  389. package/src/lib/plate-paint-affected.ts +177 -0
  390. package/src/lib/plugin-chat.ts +50 -0
  391. package/src/lib/plugin-panels.test.ts +25 -0
  392. package/src/lib/plugin-panels.ts +94 -0
  393. package/src/lib/print-shell-compiler.ts +1 -0
  394. package/src/lib/registered-draft-snap.ts +12 -0
  395. package/src/lib/roof-duplication.ts +10 -2
  396. package/src/lib/room-assembly-overlay.test.ts +275 -0
  397. package/src/lib/room-assembly-overlay.ts +266 -0
  398. package/src/lib/room-built-on.test.ts +379 -0
  399. package/src/lib/room-built-on.ts +138 -0
  400. package/src/lib/room-construction-commands.ts +712 -0
  401. package/src/lib/room-construction.test.ts +746 -0
  402. package/src/lib/room-construction.ts +385 -0
  403. package/src/lib/room-controls-phase6.test.ts +630 -0
  404. package/src/lib/room-divide-session.ts +523 -0
  405. package/src/lib/room-handle-drag.ts +1068 -0
  406. package/src/lib/room-name-catalog.test.ts +90 -0
  407. package/src/lib/room-name-catalog.ts +168 -0
  408. package/src/lib/room-push-dimensions.test.ts +311 -0
  409. package/src/lib/room-push-dimensions.ts +251 -0
  410. package/src/lib/room-selection-commands.ts +62 -0
  411. package/src/lib/room-selection.test.ts +973 -0
  412. package/src/lib/room-selection.ts +417 -0
  413. package/src/lib/room-structure-commands.ts +94 -0
  414. package/src/lib/room-structure.test.ts +921 -0
  415. package/src/lib/room-transform-session.ts +570 -0
  416. package/src/lib/room-zone-routing.test.ts +294 -0
  417. package/src/lib/room-zone-routing.ts +197 -0
  418. package/src/lib/scene-clipboard.test.ts +364 -0
  419. package/src/lib/scene-clipboard.ts +229 -48
  420. package/src/lib/scene-material-refs.test.ts +79 -0
  421. package/src/lib/scene-material-refs.ts +79 -0
  422. package/src/lib/scene.test.ts +87 -0
  423. package/src/lib/scene.ts +39 -45
  424. package/src/lib/selection-routing.test.ts +129 -45
  425. package/src/lib/selection-routing.ts +53 -74
  426. package/src/lib/session-writes.test.ts +120 -0
  427. package/src/lib/session-writes.ts +205 -0
  428. package/src/lib/sidebar-panel.ts +62 -0
  429. package/src/lib/site-boundary.test.ts +4 -6
  430. package/src/lib/site-boundary.ts +1 -1
  431. package/src/lib/snapping-mode.test.ts +17 -2
  432. package/src/lib/snapping-mode.ts +19 -7
  433. package/src/lib/spatial-drag-plane.test.ts +53 -0
  434. package/src/lib/spatial-drag-plane.ts +19 -0
  435. package/src/lib/spatial-pointer-input.test.ts +72 -0
  436. package/src/lib/spatial-pointer-input.ts +62 -0
  437. package/src/lib/spatial-snap.test.ts +47 -0
  438. package/src/lib/terrace-draft.test.ts +145 -0
  439. package/src/lib/terrace-draft.ts +213 -0
  440. package/src/lib/terrain-panel-model.ts +117 -0
  441. package/src/lib/tool-cursor-icon.test.ts +16 -0
  442. package/src/lib/tool-cursor-icon.ts +11 -0
  443. package/src/lib/typing-target.ts +9 -0
  444. package/src/lib/units.ts +77 -39
  445. package/src/lib/vector-edges.test.ts +58 -0
  446. package/src/lib/vector-edges.ts +400 -0
  447. package/src/lib/viewer-selection.test.ts +577 -0
  448. package/src/lib/viewer-selection.ts +569 -0
  449. package/src/lib/wall-draw-variant.test.ts +71 -0
  450. package/src/lib/wall-draw-variant.ts +91 -0
  451. package/src/lib/wall-paint-region-session.ts +246 -0
  452. package/src/lib/wall-paint-region.test.ts +373 -0
  453. package/src/lib/wall-polygon-draft.test.ts +262 -0
  454. package/src/lib/wall-polygon-draft.ts +286 -0
  455. package/src/lib/wall-push.test.ts +453 -0
  456. package/src/lib/wall-region-face.ts +352 -0
  457. package/src/lib/wall-region-handles.test.ts +304 -0
  458. package/src/lib/wall-region-handles.ts +368 -0
  459. package/src/lib/wall-region-list.test.ts +119 -0
  460. package/src/lib/wall-region-list.ts +136 -0
  461. package/src/lib/wall-region-snap.ts +110 -0
  462. package/src/lib/window-interaction.ts +14 -0
  463. package/src/lib/world-grid-snap.test.ts +48 -0
  464. package/src/lib/world-grid-snap.ts +9 -5
  465. package/src/lib/zone-content.ts +4 -1
  466. package/src/lib/zone-name.test.ts +17 -0
  467. package/src/lib/zone-name.ts +17 -0
  468. package/src/store/opens-in-select-mode.test.ts +223 -0
  469. package/src/store/paint-mode.test.ts +188 -0
  470. package/src/store/rotation-snapping.test.ts +53 -0
  471. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  472. package/src/store/tool-mode.test.ts +137 -6
  473. package/src/store/use-delete-confirmation.ts +28 -1
  474. package/src/store/use-drawing-view.test.ts +24 -3
  475. package/src/store/use-drawing-view.ts +29 -1
  476. package/src/store/use-editor.tsx +442 -323
  477. package/src/store/use-floorplan-draft-preview.ts +23 -1
  478. package/src/store/use-floorplan-marquee.ts +12 -2
  479. package/src/store/use-interaction-scope.ts +155 -6
  480. package/src/store/use-quick-measurement-hud.ts +3 -3
  481. package/src/store/use-stair-build-preview.ts +13 -5
  482. package/src/store/use-wall-move-ghosts.ts +4 -2
  483. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  484. package/src/store/use-wall-region-handles-selection.ts +139 -0
  485. package/src/store/view-layout.test.ts +175 -0
  486. package/src/store/view-layout.ts +196 -0
  487. package/src/test-utils/immediate-animation-frames.ts +16 -0
  488. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  489. package/src/test-utils/selection-harness.ts +82 -0
  490. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  491. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  492. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  493. package/src/components/tools/fence/fence-drafting.ts +0 -266
  494. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  495. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  496. package/src/lib/floorplan/stairs.ts +0 -489
  497. package/src/lib/level-duplication.test.ts +0 -144
  498. package/src/lib/level-duplication.ts +0 -195
@@ -9,12 +9,16 @@ import {
9
9
  type SlabNode,
10
10
  useScene,
11
11
  type WallNode,
12
+ type ZoneNode,
12
13
  } from '@pascal-app/core'
13
14
  import { useViewer } from '@pascal-app/viewer'
14
15
  import { type ComponentType, lazy, Suspense, useMemo } from 'react'
15
16
  import { useRegisteredToolEnabled } from '../../hooks/use-registered-tool-enabled'
17
+ import { useCeilingEditCeilingId } from '../../lib/ceiling-edit-session'
18
+ import type { ReshapeKind } from '../../lib/interaction/scope'
19
+ import { paintRegionTargets, usePaintRegionMode } from '../../lib/paint-region-mode'
16
20
  import { siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
17
- import useEditor, { type Phase, type Tool } from '../../store/use-editor'
21
+ import useEditor, { type Tool } from '../../store/use-editor'
18
22
  import useInteractionScope, {
19
23
  useControlPointReshape,
20
24
  useEditingHole,
@@ -28,12 +32,19 @@ import useInteractionScope, {
28
32
  } from '../../store/use-interaction-scope'
29
33
  import { Alignment3DGuideLayer } from '../editor/alignment-3d-guide-layer'
30
34
  import { Elevation3DGuideLayer } from '../editor/elevation-3d-guide-layer'
35
+ import { FloorOpeningsOverlay3D } from '../editor/floor-openings-overlay'
36
+ import { FloorRegionControls3D } from '../editor/floor-region-controls'
37
+ import { OpenWallEnds3DLayer } from '../editor/open-wall-ends-3d-layer'
31
38
  import { OpeningGuides3DLayer } from '../editor/opening-guides-3d-layer'
32
39
  import { WallSnapBeaconLayer } from '../editor/wall-snap-beacon-layer'
33
40
  import { ElevatorTool } from './elevator/elevator-tool'
34
41
  import { MoveTool } from './item/move-tool'
42
+ import { WallPaintRegionTool } from './paint-region/wall-paint-region-tool'
35
43
  import { RegistryToolProvider } from './registry-tool-context'
36
- import { getRegistryAffordanceTool } from './shared/affordance-dispatch'
44
+ import {
45
+ getRegistryAffordanceTool,
46
+ preloadRegistryAffordanceTools,
47
+ } from './shared/affordance-dispatch'
37
48
  import { FacingPoseIndicator } from './shared/facing-pose-indicator'
38
49
  import { SiteBoundaryEditor } from './site/site-boundary-editor'
39
50
  import { TerrainSculptTool } from './site/terrain-sculpt-tool'
@@ -44,6 +55,15 @@ import { ZoneTool } from './zone/zone-tool'
44
55
  // Cache lazy tool components keyed by their loader so React.lazy isn't
45
56
  // re-invoked across renders.
46
57
  const lazyToolCache = new WeakMap<() => Promise<unknown>, ComponentType>()
58
+ // Reshapes with their own arm below; anything else resolves by name.
59
+ const DEDICATED_RESHAPES = new Set<ReshapeKind>([
60
+ 'curve',
61
+ 'hole',
62
+ 'endpoint',
63
+ 'boundary',
64
+ 'control-point',
65
+ 'tangent',
66
+ ])
47
67
  const registryToolPreloadCache = new WeakMap<AnyNodeDefinition, Promise<void>>()
48
68
 
49
69
  export function preloadRegistryToolModules(tool: string | null): Promise<void> {
@@ -60,8 +80,7 @@ export function preloadRegistryToolModules(tool: string | null): Promise<void> {
60
80
  if (def.system) loaders.push(def.system.module)
61
81
  if (def.parametrics?.customPanel) loaders.push(def.parametrics.customPanel)
62
82
  if (def.parametrics?.trailingSection) loaders.push(def.parametrics.trailingSection)
63
- const moveTool = def.affordanceTools?.move
64
- if (moveTool) loaders.push(moveTool)
83
+ loaders.push(() => preloadRegistryAffordanceTools(tool))
65
84
 
66
85
  const preload = Promise.allSettled(loaders.map((loader) => loader())).then(() => undefined)
67
86
  registryToolPreloadCache.set(def, preload)
@@ -88,21 +107,17 @@ function getRegistryTool(tool: Tool | null): ComponentType | null {
88
107
  // Legacy tool fallbacks — kinds whose placement tools haven't migrated
89
108
  // to `def.tool` yet. Wall / fence / slab / ceiling / door / window /
90
109
  // item / shelf / spawn now go through the registry path above.
91
- const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
92
- site: {
93
- 'property-line': SiteBoundaryEditor,
94
- },
95
- structure: {
96
- stair: StairTool,
97
- zone: ZoneTool,
98
- },
99
- furnish: {},
110
+ const tools: Partial<Record<Tool, React.FC>> = {
111
+ 'property-line': SiteBoundaryEditor,
112
+ stair: StairTool,
113
+ zone: ZoneTool,
100
114
  }
101
115
 
102
116
  export const ToolManager: React.FC = () => {
103
117
  const phase = useEditor((state) => state.phase)
104
118
  const mode = useEditor((state) => state.mode)
105
119
  const tool = useEditor((state) => state.tool)
120
+ const paintsWallRegions = usePaintRegionMode((state) => paintRegionTargets(state.mode).wall)
106
121
  const registeredToolEnabled = useRegisteredToolEnabled(tool)
107
122
  const movingNode = useMovingNode()
108
123
  const registryToolOwnsPlacement = useInteractionScope(
@@ -114,6 +129,11 @@ export const ToolManager: React.FC = () => {
114
129
  const tangentReshape = useTangentReshape()
115
130
  const isCurveReshape = useIsCurveReshape()
116
131
  const isToolDrivenReshape = useIsToolDrivenReshape()
132
+ const registryReshape = useInteractionScope((state) =>
133
+ state.scope.kind === 'reshaping' && !DEDICATED_RESHAPES.has(state.scope.reshape)
134
+ ? state.scope.reshape
135
+ : null,
136
+ )
117
137
  const isFloorplanDrivenReshape = useIsFloorplanDrivenReshape()
118
138
  const reshapingNode = useReshapingNode()
119
139
  // The endpoint affordance tool's `target` is kind-specific
@@ -140,7 +160,18 @@ export const ToolManager: React.FC = () => {
140
160
  }
141
161
  }, [reshapingNode, tangentReshape])
142
162
  const editingHole = useEditingHole()
143
- const selectedZoneId = useViewer((state) => state.selection.zoneId)
163
+ const editCeilingId = useCeilingEditCeilingId()
164
+ // A zone picked as a zone, from the Scene list (zoneId) or its area or pill (sole selection).
165
+ const zoneSelectionId = useViewer((state) => state.selection.zoneId)
166
+ const soleSelectedId = useViewer((state) =>
167
+ state.selection.selectedIds.length === 1 ? state.selection.selectedIds[0] : undefined,
168
+ )
169
+ const soleSelectedZone = useScene((state) =>
170
+ soleSelectedId && state.nodes[soleSelectedId as AnyNodeId]?.type === 'zone'
171
+ ? (soleSelectedId as ZoneNode['id'])
172
+ : null,
173
+ )
174
+ const selectedZoneId = zoneSelectionId ?? soleSelectedZone
144
175
  const selectedIds = useViewer((state) => state.selection.selectedIds)
145
176
  const buildingId = useViewer((state) => state.selection.buildingId)
146
177
  const activeLevelId = useViewer((state) => state.selection.levelId)
@@ -196,12 +227,14 @@ export const ToolManager: React.FC = () => {
196
227
  // edge handles — mounts only for a sole selection.
197
228
  const isSoleSelection = selectedIds.length === 1
198
229
 
199
- // Show slab boundary editor when in structure/select mode with a slab selected (but not editing a hole)
230
+ // Show slab boundary editor in select mode with a slab selected (but not editing a hole)
200
231
  const showSlabBoundaryEditor =
201
- phase === 'structure' &&
202
232
  mode === 'select' &&
203
233
  isSoleSelection &&
204
234
  selectedSlabId !== undefined &&
235
+ // Derived plates (a footprint's floor, a room's raised plate) follow their
236
+ // rooms; only user-drawn slabs get the outline editor.
237
+ !selectedSlab?.plateRole &&
205
238
  !isFloorplanDrivenReshape &&
206
239
  !editingSlabHoleIsManual
207
240
 
@@ -213,26 +246,28 @@ export const ToolManager: React.FC = () => {
213
246
  !isFloorplanDrivenReshape &&
214
247
  editingSlabHoleIsManual
215
248
 
216
- // Show ceiling boundary editor when in structure/select mode with a ceiling selected (but not editing a hole)
249
+ // Show ceiling boundary editor only inside the ceiling's Edit ceiling session (not while editing a hole)
217
250
  const showCeilingBoundaryEditor =
218
- phase === 'structure' &&
219
251
  mode === 'select' &&
220
252
  isSoleSelection &&
221
253
  selectedCeilingId !== undefined &&
254
+ selectedCeilingId === editCeilingId &&
222
255
  !isFloorplanDrivenReshape &&
223
256
  (!editingHole || editingHole.nodeId !== selectedCeilingId)
224
257
 
225
- // Show ceiling hole editor when editing a hole on the selected ceiling
258
+ // Show ceiling hole editor when editing a manual hole inside the ceiling's Edit ceiling session
226
259
  const showCeilingHoleEditor =
227
260
  selectedCeilingId !== undefined &&
261
+ selectedCeilingId === editCeilingId &&
228
262
  editingHole !== null &&
229
263
  editingHole.nodeId === selectedCeilingId &&
264
+ ((nodes[selectedCeilingId as AnyNodeId] as CeilingNode).holeMetadata?.[editingHole.holeIndex]
265
+ ?.source ?? 'manual') === 'manual' &&
230
266
  !isFloorplanDrivenReshape
231
267
 
232
- // Show zone boundary editor when in structure/select mode with a zone selected
268
+ // Show zone boundary editor in select mode with a zone selected
233
269
  // Hide when editing a slab or ceiling to avoid overlapping handles
234
270
  const showZoneBoundaryEditor =
235
- phase === 'structure' &&
236
271
  mode === 'select' &&
237
272
  selectedZoneId !== null &&
238
273
  !isFloorplanDrivenReshape &&
@@ -257,7 +292,7 @@ export const ToolManager: React.FC = () => {
257
292
  const RegistryToolComponent = showBuildTool ? getRegistryTool(tool) : null
258
293
  const useRegistryTool = RegistryToolComponent != null
259
294
 
260
- const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[phase]?.[tool] : null
295
+ const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[tool] : null
261
296
  const handlePlacedNodeSelected = (nodeId: AnyNodeId) => {
262
297
  setSelection({ selectedIds: [nodeId] })
263
298
  }
@@ -281,10 +316,16 @@ export const ToolManager: React.FC = () => {
281
316
  <RegistryToolProvider value={registryToolContext}>
282
317
  {/* World-space tools: site boundary and building movement operate in world coordinates */}
283
318
  {showSiteBoundaryEditor && <SiteBoundaryEditor />}
284
- {/* Terrain sculpting is a mode rather than a `tools[phase][tool]` entry —
319
+ {/* Terrain sculpting is a mode rather than a `tools[tool]` entry —
285
320
  it places no node — so it gets its own gate here. World-space, because
286
321
  the ground is not building-local. */}
287
322
  {sculpting && <TerrainSculptTool />}
323
+ {/* "Paint part of the floor": level-space, follows the level matrix itself. */}
324
+ <FloorRegionControls3D />
325
+ {/* Floor and ceiling openings: level-space, follows the level matrix itself. */}
326
+ <FloorOpeningsOverlay3D />
327
+ {/* "Paint part of the wall": follows the wall mesh's own matrix. */}
328
+ {mode === 'material-paint' && paintsWallRegions && <WallPaintRegionTool />}
288
329
  {showMover && movingNode?.type === 'building' && (
289
330
  <MoveTool onNodeMoved={handlePlacedNodeSelected} onSpawnMoved={handlePlacedNodeSelected} />
290
331
  )}
@@ -384,6 +425,22 @@ export const ToolManager: React.FC = () => {
384
425
  </Suspense>
385
426
  ) : null
386
427
  })()}
428
+ {/* Reshape kinds without an arm above mount the kind's own affordance tool
429
+ under the reshape's name (`def.affordanceTools[reshape]`, e.g. wall 'split'). */}
430
+ {isToolDrivenReshape &&
431
+ reshapingNode &&
432
+ registryReshape &&
433
+ (() => {
434
+ const RegistryAffordance = getRegistryAffordanceTool(
435
+ reshapingNode.type,
436
+ registryReshape,
437
+ )
438
+ return RegistryAffordance ? (
439
+ <Suspense fallback={null}>
440
+ <RegistryAffordance node={reshapingNode} />
441
+ </Suspense>
442
+ ) : null
443
+ })()}
387
444
  {showMover && movingNode.type !== 'building' && (
388
445
  <MoveTool
389
446
  onNodeMoved={handlePlacedNodeSelected}
@@ -421,6 +478,8 @@ export const ToolManager: React.FC = () => {
421
478
  <Elevation3DGuideLayer />
422
479
  {/* "Magnetic" beacon at the active wall-draft snap point. */}
423
480
  <WallSnapBeaconLayer />
481
+ {/* Wall ends that aren't joined, with a one-click join — twin of the floor plan's. */}
482
+ <OpenWallEnds3DLayer />
424
483
  </group>
425
484
  </RegistryToolProvider>
426
485
  )
@@ -22,6 +22,11 @@ import {
22
22
  type WallNode,
23
23
  WallNode as WallSchema,
24
24
  } from '@pascal-app/core'
25
+ import {
26
+ migrateCeilingRoomLinks,
27
+ migrateFloorPlates,
28
+ migrateRoomZones,
29
+ } from '@pascal-app/core/scene-migrations'
25
30
  import { useViewer } from '@pascal-app/viewer'
26
31
  import useEditor from '../../../store/use-editor'
27
32
  import useInteractionScope from '../../../store/use-interaction-scope'
@@ -721,7 +726,9 @@ describe('createWallOnCurrentLevel', () => {
721
726
  expect(useScene.temporal.getState().pastStates.length - before).toBe(1)
722
727
  })
723
728
 
724
- test('a room divider splits customized auto slabs and ceilings', () => {
729
+ test.each([
730
+ 0.05, 0.18,
731
+ ])('a room divider preserves the footprint floor at %s and splits ceilings', (elevation) => {
725
732
  const walls = [
726
733
  makeWall([0, 0], [8, 0], 'wall_bottom'),
727
734
  makeWall([8, 0], [8, 6], 'wall_right'),
@@ -737,7 +744,8 @@ describe('createWallOnCurrentLevel', () => {
737
744
  [8, 6],
738
745
  [0, 6],
739
746
  ],
740
- elevation: 0.18,
747
+ elevation,
748
+ floorHeight: elevation,
741
749
  thickness: 0.32,
742
750
  autoFromWalls: true,
743
751
  })
@@ -748,6 +756,11 @@ describe('createWallOnCurrentLevel', () => {
748
756
  autoFromWalls: true,
749
757
  })
750
758
  seedLevel(walls, [slab, ceiling])
759
+ useScene.setState({
760
+ nodes: migrateFloorPlates(
761
+ migrateCeilingRoomLinks(migrateRoomZones(useScene.getState().nodes).nodes).nodes,
762
+ ).nodes as Record<AnyNodeId, AnyNode>,
763
+ })
751
764
  const stopDetection = initSpaceDetectionSync(useScene, useEditor)
752
765
 
753
766
  try {
@@ -756,11 +769,13 @@ describe('createWallOnCurrentLevel', () => {
756
769
  const nodes = Object.values(useScene.getState().nodes)
757
770
  const slabs = nodes.filter((node) => node.type === 'slab')
758
771
  const ceilings = nodes.filter((node) => node.type === 'ceiling')
759
- expect(slabs).toHaveLength(2)
772
+ expect(slabs).toHaveLength(1)
773
+ expect(slabs[0]).toMatchObject({ plateRole: 'base', floorHeight: elevation })
774
+ expect(slabs[0]!.zoneIds).toHaveLength(2)
760
775
  expect(ceilings).toHaveLength(2)
761
776
  expect(
762
777
  slabs.every(
763
- (node) => node.autoFromWalls && node.elevation === 0.18 && node.thickness === 0.32,
778
+ (node) => node.autoFromWalls && node.elevation === elevation && node.thickness === 0.32,
764
779
  ),
765
780
  ).toBe(true)
766
781
  expect(ceilings.every((node) => node.autoFromWalls)).toBe(true)
@@ -3,10 +3,10 @@ import {
3
3
  emitter,
4
4
  type GridEvent,
5
5
  type LevelNode,
6
+ newZone,
6
7
  runAsSingleSceneHistoryStep,
7
8
  snapPointAlongAngleRay,
8
9
  useScene,
9
- ZoneNode,
10
10
  } from '@pascal-app/core'
11
11
  import { useViewer } from '@pascal-app/viewer'
12
12
  import { useEffect, useMemo, useRef, useState } from 'react'
@@ -19,6 +19,7 @@ import {
19
19
  } from './../../../lib/surface-plan-snap'
20
20
  import { focusedUnitNode } from './../../../lib/units'
21
21
  import { snapWorldXZForActiveBuilding } from './../../../lib/world-grid-snap'
22
+ import { nextZoneName } from '../../../lib/zone-name'
22
23
  import useEditor, { isAngleSnapActive, isGridSnapActive } from './../../../store/use-editor'
23
24
  import { useFloorplanDraftPreview } from './../../../store/use-floorplan-draft-preview'
24
25
  import { CursorSphere } from '../shared/cursor-sphere'
@@ -31,18 +32,9 @@ const Y_OFFSET = 0.02
31
32
  const commitZoneDrawing = (levelId: LevelNode['id'], points: Array<[number, number]>) => {
32
33
  const { createNode, updateNode, nodes } = useScene.getState()
33
34
 
34
- // Count existing zones for naming and color cycling
35
- const zoneCount = Object.values(nodes).filter((n) => n.type === 'zone').length
36
- const name = `Zone ${zoneCount + 1}`
35
+ const name = nextZoneName(nodes)
37
36
 
38
- // Default to blue, cycle through palette for subsequent zones
39
- const color = '#3b82f6'
40
-
41
- const zone = ZoneNode.parse({
42
- name,
43
- polygon: points,
44
- color,
45
- })
37
+ const zone = newZone({ name, polygon: points })
46
38
 
47
39
  const focusedUnit = focusedUnitNode()
48
40
 
@@ -36,6 +36,9 @@ export const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProp
36
36
  <Tooltip>
37
37
  <TooltipTrigger asChild>
38
38
  <Button
39
+ // These are toggles, not commands: the armed/unarmed state has to be
40
+ // readable without decoding the active-state styling.
41
+ aria-pressed={isActive}
39
42
  className={cn('relative h-11 w-11 transition-all', className)}
40
43
  onClick={(event) => {
41
44
  triggerSFX('sfx:menu-click')
@@ -3,12 +3,13 @@
3
3
  import { emitter } from '@pascal-app/core'
4
4
  import Image from 'next/image'
5
5
  import useEditor from '../../../store/use-editor'
6
+ import { visibleScene } from '../../../store/view-layout'
6
7
  import { ActionButton } from './action-button'
7
8
 
8
9
  export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
9
10
  // Orbit stays useful in 2D-only (it spins the synced floorplan view), but
10
11
  // top view only tilts the hidden 3D camera — pointless without the canvas.
11
- const is2dOnly = useEditor((s) => s.viewMode === '2d')
12
+ const is2dOnly = useEditor((s) => visibleScene(s) === '2d')
12
13
 
13
14
  const goToTopView = () => {
14
15
  emitter.emit('camera-controls:top-view')
@@ -9,7 +9,7 @@ import useEditor from './../../../store/use-editor'
9
9
  import { ActionButton } from './action-button'
10
10
  import { MeasurementControl } from './measurement-control'
11
11
 
12
- type ControlId = 'select' | 'box-select' | 'zone' | 'delete'
12
+ type ControlId = 'select' | 'box-select' | 'delete'
13
13
 
14
14
  type ControlConfig = {
15
15
  id: ControlId
@@ -32,14 +32,6 @@ const controls: ControlConfig[] = [
32
32
  color: 'hover:bg-blue-500/20 hover:text-blue-400',
33
33
  activeColor: 'bg-blue-500/20 text-blue-400',
34
34
  },
35
- {
36
- id: 'zone',
37
- imageSrc: '/icons/zone.webp',
38
- label: 'Zone',
39
- shortcut: 'Z',
40
- color: 'hover:bg-green-500/20 hover:text-green-400',
41
- activeColor: 'bg-green-500/20 text-green-400',
42
- },
43
35
  {
44
36
  id: 'delete',
45
37
  icon: Trash2,
@@ -52,49 +44,23 @@ const controls: ControlConfig[] = [
52
44
 
53
45
  export function ControlModes() {
54
46
  const mode = useEditor((state) => state.mode)
55
- const phase = useEditor((state) => state.phase)
56
47
  const selectionTool = useEditor((state) => state.floorplanSelectionTool)
57
48
  const armToolMode = useEditor((state) => state.armToolMode)
58
- const setPhase = useEditor((state) => state.setPhase)
59
- const setStructureLayer = useEditor((state) => state.setStructureLayer)
60
49
  const setSelectionTool = useEditor((state) => state.setFloorplanSelectionTool)
61
50
 
62
- const isSiteEditing = phase === 'site'
63
-
64
- const structureLayer = useEditor((state) => state.structureLayer)
65
-
66
51
  const getIsActive = (id: ControlId): boolean => {
67
52
  if (id === 'select') return mode === 'select' && selectionTool === 'click'
68
53
  if (id === 'box-select') return mode === 'select' && selectionTool === 'marquee'
69
- if (id === 'zone')
70
- return mode === 'build' && phase === 'structure' && structureLayer === 'zones'
71
54
  return mode === id
72
55
  }
73
56
 
74
57
  const handleClick = (id: ControlId) => {
75
- // Exit site editing first if needed. Sculpting is a site-phase mode, so
76
- // leaving the phase is exactly the right way to leave the brush — but the
77
- // order matters: `setPhase` resets the mode, and setting the mode first
78
- // would have it overwritten below.
79
- if (isSiteEditing) {
80
- setPhase('structure')
81
- setStructureLayer('elements')
82
- }
83
-
84
58
  if (id === 'select') {
85
59
  armToolMode({ mode: 'select' })
86
60
  setSelectionTool('click')
87
61
  } else if (id === 'box-select') {
88
62
  armToolMode({ mode: 'select' })
89
63
  setSelectionTool('marquee')
90
- } else if (id === 'zone') {
91
- if (getIsActive('zone')) {
92
- armToolMode({ mode: 'select' })
93
- } else {
94
- setPhase('structure')
95
- setStructureLayer('zones')
96
- armToolMode({ mode: 'build', tool: 'zone' })
97
- }
98
64
  } else {
99
65
  armToolMode({ mode: id })
100
66
  }
@@ -121,6 +87,7 @@ export function ControlModes() {
121
87
  // A static hook for a host app that wants to point a first-run
122
88
  // tour at this button. Nothing here reads it.
123
89
  data-guide-target={c.id === 'select' ? 'mode-select' : undefined}
90
+ isActive={isActive}
124
91
  label={c.label}
125
92
  onClick={() => handleClick(c.id)}
126
93
  shortcut={c.shortcut}
@@ -22,6 +22,7 @@ import { useState } from 'react'
22
22
  import type { CreatableMeasurementKind } from '../../../lib/measurement-kind'
23
23
  import { cn } from '../../../lib/utils'
24
24
  import useEditor from '../../../store/use-editor'
25
+ import { VIEW_2D } from '../../../store/view-layout'
25
26
  import useFloorplanMode from '../../../store/use-floorplan-mode'
26
27
  import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
27
28
  import { ActionButton } from './action-button'
@@ -74,12 +75,10 @@ export function MeasurementControl() {
74
75
  (state) => state.toolDefaults['construction-dimension']?.mode,
75
76
  )
76
77
  const setMode = useEditor((state) => state.setMode)
77
- const setPhase = useEditor((state) => state.setPhase)
78
78
  const setLastMeasurementKind = useEditor((state) => state.setLastMeasurementKind)
79
- const setStructureLayer = useEditor((state) => state.setStructureLayer)
80
79
  const setTool = useEditor((state) => state.setTool)
81
80
  const setToolDefaults = useEditor((state) => state.setToolDefaults)
82
- const setViewMode = useEditor((state) => state.setViewMode)
81
+ const showView = useEditor((state) => state.showView)
83
82
 
84
83
  const selectedOption =
85
84
  measurementOptions.find((option) => option.kind === selectedKind) ?? measurementOptions[0]
@@ -104,8 +103,6 @@ export function MeasurementControl() {
104
103
  : selectedOption.label
105
104
 
106
105
  const activateMeasurement = (kind: CreatableMeasurementKind) => {
107
- setPhase('structure')
108
- setStructureLayer('elements')
109
106
  setLastMeasurementKind(kind)
110
107
  setToolDefaults('measurement', { kind })
111
108
  setMode('build')
@@ -121,8 +118,6 @@ export function MeasurementControl() {
121
118
  }
122
119
 
123
120
  const activateSmartMeasurement = () => {
124
- setPhase('structure')
125
- setStructureLayer('elements')
126
121
  setToolDefaults('measurement', { kind: 'smart' })
127
122
  setMode('build')
128
123
  setTool('measurement')
@@ -136,9 +131,7 @@ export function MeasurementControl() {
136
131
  useFloorplanMode.getState().showExpertModeNotice('Construction Dimension')
137
132
  return
138
133
  }
139
- setPhase('structure')
140
- setStructureLayer('elements')
141
- setViewMode('2d')
134
+ showView(VIEW_2D)
142
135
  setToolDefaults('construction-dimension', { chainMode, mode: dimensionMode })
143
136
  setMode('build')
144
137
  setTool('construction-dimension')