@pascal-app/editor 0.8.0 → 0.9.2

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 (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -2,71 +2,107 @@ import {
2
2
  type AnyNodeId,
3
3
  type BuildingNode,
4
4
  type CeilingNode,
5
+ type FenceNode,
6
+ nodeRegistry,
5
7
  type SlabNode,
6
8
  useScene,
9
+ type WallNode,
7
10
  } from '@pascal-app/core'
8
11
  import { useViewer } from '@pascal-app/viewer'
12
+ import { type ComponentType, lazy, Suspense, useMemo } from 'react'
9
13
  import useEditor, { type Phase, type Tool } from '../../store/use-editor'
10
- import { CeilingBoundaryEditor } from './ceiling/ceiling-boundary-editor'
11
- import { CeilingHoleEditor } from './ceiling/ceiling-hole-editor'
12
- import { CeilingTool } from './ceiling/ceiling-tool'
13
- import { ColumnTool } from './column/column-tool'
14
- import { DoorTool } from './door/door-tool'
15
- import { CurveFenceTool } from './fence/curve-fence-tool'
16
- import { FenceTool } from './fence/fence-tool'
17
- import { MoveFenceEndpointTool } from './fence/move-fence-endpoint-tool'
18
- import { ItemTool } from './item/item-tool'
14
+ import {
15
+ useControlPointReshape,
16
+ useEditingHole,
17
+ useEndpointReshape,
18
+ useIsCurveReshape,
19
+ useMovingNode,
20
+ useReshapingNode,
21
+ useTangentReshape,
22
+ } from '../../store/use-interaction-scope'
23
+ import { Alignment3DGuideLayer } from '../editor/alignment-3d-guide-layer'
24
+ import { OpeningGuides3DLayer } from '../editor/opening-guides-3d-layer'
25
+ import { WallSnapBeaconLayer } from '../editor/wall-snap-beacon-layer'
26
+ import { ElevatorTool } from './elevator/elevator-tool'
19
27
  import { MoveTool } from './item/move-tool'
20
28
  import { RoofTool } from './roof/roof-tool'
29
+ import { getRegistryAffordanceTool } from './shared/affordance-dispatch'
30
+ import { FacingPoseIndicator } from './shared/facing-pose-indicator'
21
31
  import { SiteBoundaryEditor } from './site/site-boundary-editor'
22
- import { SlabBoundaryEditor } from './slab/slab-boundary-editor'
23
- import { SlabHoleEditor } from './slab/slab-hole-editor'
24
- import { SlabTool } from './slab/slab-tool'
25
- import { SpawnTool } from './spawn/spawn-tool'
26
32
  import { StairTool } from './stair/stair-tool'
27
- import { CurveWallTool } from './wall/curve-wall-tool'
28
- import { MoveWallEndpointTool } from './wall/move-wall-endpoint-tool'
29
- import { WallTool } from './wall/wall-tool'
30
- import { WindowTool } from './window/window-tool'
31
33
  import { ZoneBoundaryEditor } from './zone/zone-boundary-editor'
32
34
  import { ZoneTool } from './zone/zone-tool'
33
35
 
36
+ // Cache lazy tool components keyed by their loader so React.lazy isn't
37
+ // re-invoked across renders.
38
+ const lazyToolCache = new WeakMap<() => Promise<unknown>, ComponentType>()
39
+
40
+ function getRegistryTool(tool: Tool | null): ComponentType | null {
41
+ if (!tool) return null
42
+ const def = nodeRegistry.get(tool)
43
+ if (!def?.tool) return null
44
+ const cached = lazyToolCache.get(def.tool)
45
+ if (cached) return cached
46
+ const Comp = lazy(def.tool as () => Promise<{ default: ComponentType }>)
47
+ lazyToolCache.set(def.tool, Comp)
48
+ return Comp
49
+ }
50
+
51
+ // Legacy tool fallbacks — kinds whose placement tools haven't migrated
52
+ // to `def.tool` yet. Wall / fence / slab / ceiling / door / window /
53
+ // item / shelf / spawn now go through the registry path above.
34
54
  const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
35
55
  site: {
36
56
  'property-line': SiteBoundaryEditor,
37
57
  },
38
58
  structure: {
39
- wall: WallTool,
40
- fence: FenceTool,
41
- slab: SlabTool,
42
- ceiling: CeilingTool,
43
59
  roof: RoofTool,
44
60
  stair: StairTool,
45
- door: DoorTool,
46
- item: ItemTool,
47
61
  zone: ZoneTool,
48
- spawn: SpawnTool,
49
- window: WindowTool,
50
- },
51
- furnish: {
52
- item: ItemTool,
53
62
  },
63
+ furnish: {},
54
64
  }
55
65
 
56
66
  export const ToolManager: React.FC = () => {
57
67
  const phase = useEditor((state) => state.phase)
58
68
  const mode = useEditor((state) => state.mode)
59
69
  const tool = useEditor((state) => state.tool)
60
- const movingNode = useEditor((state) => state.movingNode)
61
- const movingWallEndpoint = useEditor((state) => state.movingWallEndpoint)
62
- const movingFenceEndpoint = useEditor((state) => state.movingFenceEndpoint)
63
- const curvingWall = useEditor((state) => state.curvingWall)
64
- const curvingFence = useEditor((state) => state.curvingFence)
65
- const editingHole = useEditor((state) => state.editingHole)
70
+ const movingNode = useMovingNode()
71
+ const movingNodeOrigin = useEditor((state) => state.movingNodeOrigin)
72
+ const endpointReshape = useEndpointReshape()
73
+ const controlPointReshape = useControlPointReshape()
74
+ const tangentReshape = useTangentReshape()
75
+ const isCurveReshape = useIsCurveReshape()
76
+ const reshapingNode = useReshapingNode()
77
+ // The endpoint affordance tool's `target` is kind-specific
78
+ // (`{ wall | fence, endpoint }`); rebuild it from the (frozen) reshaped node +
79
+ // the scope's endpoint. Memoised so it stays referentially stable across the
80
+ // scene-write re-renders during the drag — otherwise a fresh object each frame
81
+ // re-fires the tool's setup effect (endpoint drag would loop / freeze).
82
+ const endpointTarget = useMemo(() => {
83
+ if (!(endpointReshape && reshapingNode)) return null
84
+ return reshapingNode.type === 'fence'
85
+ ? { fence: reshapingNode as FenceNode, endpoint: endpointReshape.endpoint }
86
+ : { wall: reshapingNode as WallNode, endpoint: endpointReshape.endpoint }
87
+ }, [endpointReshape, reshapingNode])
88
+ const controlPointTarget = useMemo(() => {
89
+ if (!(controlPointReshape && reshapingNode?.type === 'fence')) return null
90
+ return { fence: reshapingNode as FenceNode, index: controlPointReshape.index }
91
+ }, [controlPointReshape, reshapingNode])
92
+ const tangentTarget = useMemo(() => {
93
+ if (!(tangentReshape && reshapingNode?.type === 'fence')) return null
94
+ return {
95
+ fence: reshapingNode as FenceNode,
96
+ index: tangentReshape.index,
97
+ side: tangentReshape.side,
98
+ }
99
+ }, [reshapingNode, tangentReshape])
100
+ const editingHole = useEditingHole()
66
101
  const selectedZoneId = useViewer((state) => state.selection.zoneId)
67
102
  const selectedIds = useViewer((state) => state.selection.selectedIds)
68
103
  const buildingId = useViewer((state) => state.selection.buildingId)
69
104
  const activeLevelId = useViewer((state) => state.selection.levelId)
105
+ const setSelection = useViewer((state) => state.setSelection)
70
106
  const nodes = useScene((state) => state.nodes)
71
107
 
72
108
  // Building transform for the local group — all building-relative tools live inside this group
@@ -81,30 +117,46 @@ export const ToolManager: React.FC = () => {
81
117
  const selectedSlabId = selectedIds.find((id) => nodes[id as AnyNodeId]?.type === 'slab') as
82
118
  | SlabNode['id']
83
119
  | undefined
120
+ const selectedSlab = selectedSlabId ? (nodes[selectedSlabId as AnyNodeId] as SlabNode) : null
121
+ const editingSlabHoleIsManual =
122
+ selectedSlabId !== undefined &&
123
+ editingHole?.nodeId === selectedSlabId &&
124
+ selectedSlab?.holeMetadata?.[editingHole.holeIndex]?.source === 'manual'
84
125
 
85
126
  // Check if a ceiling is selected
86
127
  const selectedCeilingId = selectedIds.find((id) => nodes[id as AnyNodeId]?.type === 'ceiling') as
87
128
  | CeilingNode['id']
88
129
  | undefined
89
130
 
90
- // Show site boundary editor when in site phase (toggle controls entry/exit)
91
- const showSiteBoundaryEditor = phase === 'site'
131
+ // Keep the site vertex flags available in select mode; the editor component
132
+ // switches to full polygon editing only after a flag activates site mode.
133
+ const showSiteBoundaryEditor = phase === 'site' || mode === 'select'
134
+
135
+ // A multi-selection is manipulated as one rigid group (drag / R / T), so
136
+ // per-node reshape chrome — the slab / ceiling boundary editors' vertex and
137
+ // edge handles — mounts only for a sole selection.
138
+ const isSoleSelection = selectedIds.length === 1
92
139
 
93
140
  // Show slab boundary editor when in structure/select mode with a slab selected (but not editing a hole)
94
141
  const showSlabBoundaryEditor =
95
142
  phase === 'structure' &&
96
143
  mode === 'select' &&
144
+ isSoleSelection &&
97
145
  selectedSlabId !== undefined &&
98
- (!editingHole || editingHole.nodeId !== selectedSlabId)
146
+ !editingSlabHoleIsManual
99
147
 
100
148
  // Show slab hole editor when editing a hole on the selected slab
101
149
  const showSlabHoleEditor =
102
- selectedSlabId !== undefined && editingHole !== null && editingHole.nodeId === selectedSlabId
150
+ selectedSlabId !== undefined &&
151
+ editingHole !== null &&
152
+ editingHole.nodeId === selectedSlabId &&
153
+ editingSlabHoleIsManual
103
154
 
104
155
  // Show ceiling boundary editor when in structure/select mode with a ceiling selected (but not editing a hole)
105
156
  const showCeilingBoundaryEditor =
106
157
  phase === 'structure' &&
107
158
  mode === 'select' &&
159
+ isSoleSelection &&
108
160
  selectedCeilingId !== undefined &&
109
161
  (!editingHole || editingHole.nodeId !== selectedCeilingId)
110
162
 
@@ -126,16 +178,48 @@ export const ToolManager: React.FC = () => {
126
178
  // Show build tools when in build mode
127
179
  const showBuildTool = mode === 'build' && tool !== null
128
180
 
129
- const BuildToolComponent = showBuildTool ? tools[phase]?.[tool] : null
181
+ // A move initiated from the 2D floor-plan (orange move-dot) is owned end-to-
182
+ // end by `FloorplanRegistryMoveOverlay`, which marks the origin `'2d'` at
183
+ // dot-down. Mounting the 3D affordance mover alongside it would adopt the
184
+ // same node and, on its unmount, restore the adopt-time position — snapping
185
+ // the committed 2D move back to its start. Gate the 3D mover off for 2D moves
186
+ // (the scene writes the overlay makes still mirror into the 3D view). A
187
+ // 3D-initiated move leaves the origin null until its own commit, so this only
188
+ // suppresses the 3D tool for genuinely 2D-owned moves.
189
+ const showMover = movingNode != null && movingNodeOrigin !== '2d'
190
+
191
+ // Registry-first: if the active tool's kind has a NodeDefinition with a
192
+ // tool contribution, the registry-driven tool takes over.
193
+ const RegistryToolComponent = showBuildTool ? getRegistryTool(tool) : null
194
+ const useRegistryTool = RegistryToolComponent != null
195
+
196
+ const BuildToolComponent = showBuildTool && !useRegistryTool ? tools[phase]?.[tool] : null
130
197
  const handlePlacedNodeSelected = (nodeId: AnyNodeId) => {
131
- useViewer.getState().setSelection({ selectedIds: [nodeId] })
198
+ setSelection({ selectedIds: [nodeId] })
199
+ }
200
+ const handlePlacedElevatorSelected = (
201
+ nodeId: AnyNodeId,
202
+ elevatorBuildingId: BuildingNode['id'],
203
+ ) => {
204
+ // Preserve the active level. `setSelection`'s hierarchy guard nulls
205
+ // `levelId` whenever `buildingId` is passed without an explicit
206
+ // `levelId` — which deselected the current floor plan the moment an
207
+ // elevator was placed. Pass the current level through so the floor
208
+ // plan stays selected.
209
+ setSelection({
210
+ buildingId: elevatorBuildingId,
211
+ levelId: activeLevelId ?? null,
212
+ selectedIds: [nodeId],
213
+ })
132
214
  }
133
215
 
134
216
  return (
135
217
  <>
136
218
  {/* World-space tools: site boundary and building movement operate in world coordinates */}
137
219
  {showSiteBoundaryEditor && <SiteBoundaryEditor />}
138
- {movingNode?.type === 'building' && <MoveTool onSpawnMoved={handlePlacedNodeSelected} />}
220
+ {showMover && movingNode?.type === 'building' && (
221
+ <MoveTool onNodeMoved={handlePlacedNodeSelected} onSpawnMoved={handlePlacedNodeSelected} />
222
+ )}
139
223
 
140
224
  {/* Building-local group: all other tools are relative to the selected building.
141
225
  Cursor visuals set positions in building-local space; this group applies the
@@ -145,30 +229,123 @@ export const ToolManager: React.FC = () => {
145
229
  rotation={buildingRotation as [number, number, number]}
146
230
  >
147
231
  {showZoneBoundaryEditor && selectedZoneId && <ZoneBoundaryEditor zoneId={selectedZoneId} />}
148
- {showSlabBoundaryEditor && selectedSlabId && <SlabBoundaryEditor slabId={selectedSlabId} />}
149
- {showSlabHoleEditor && selectedSlabId && editingHole && (
150
- <SlabHoleEditor holeIndex={editingHole.holeIndex} slabId={selectedSlabId} />
151
- )}
152
- {showCeilingBoundaryEditor && selectedCeilingId && (
153
- <CeilingBoundaryEditor ceilingId={selectedCeilingId} />
232
+ {showSlabBoundaryEditor &&
233
+ selectedSlabId &&
234
+ (() => {
235
+ const Registry = getRegistryAffordanceTool('slab', 'boundary-edit')
236
+ return Registry ? (
237
+ <Suspense fallback={null}>
238
+ <Registry slabId={selectedSlabId} />
239
+ </Suspense>
240
+ ) : null
241
+ })()}
242
+ {showSlabHoleEditor &&
243
+ selectedSlabId &&
244
+ editingHole &&
245
+ (() => {
246
+ const Registry = getRegistryAffordanceTool('slab', 'hole-edit')
247
+ return Registry ? (
248
+ <Suspense fallback={null}>
249
+ <Registry holeIndex={editingHole.holeIndex} slabId={selectedSlabId} />
250
+ </Suspense>
251
+ ) : null
252
+ })()}
253
+ {showCeilingBoundaryEditor &&
254
+ selectedCeilingId &&
255
+ (() => {
256
+ const Registry = getRegistryAffordanceTool('ceiling', 'boundary-edit')
257
+ return Registry ? (
258
+ <Suspense fallback={null}>
259
+ <Registry ceilingId={selectedCeilingId} />
260
+ </Suspense>
261
+ ) : null
262
+ })()}
263
+ {showCeilingHoleEditor &&
264
+ selectedCeilingId &&
265
+ editingHole &&
266
+ (() => {
267
+ const Registry = getRegistryAffordanceTool('ceiling', 'hole-edit')
268
+ return Registry ? (
269
+ <Suspense fallback={null}>
270
+ <Registry ceilingId={selectedCeilingId} holeIndex={editingHole.holeIndex} />
271
+ </Suspense>
272
+ ) : null
273
+ })()}
274
+ {endpointTarget &&
275
+ reshapingNode &&
276
+ (() => {
277
+ const RegistryAffordance = getRegistryAffordanceTool(
278
+ reshapingNode.type,
279
+ 'move-endpoint',
280
+ )
281
+ return RegistryAffordance ? (
282
+ <Suspense fallback={null}>
283
+ <RegistryAffordance target={endpointTarget} />
284
+ </Suspense>
285
+ ) : null
286
+ })()}
287
+ {isCurveReshape &&
288
+ reshapingNode &&
289
+ (() => {
290
+ const RegistryAffordance = getRegistryAffordanceTool(reshapingNode.type, 'curve')
291
+ return RegistryAffordance ? (
292
+ <Suspense fallback={null}>
293
+ <RegistryAffordance node={reshapingNode} />
294
+ </Suspense>
295
+ ) : null
296
+ })()}
297
+ {controlPointTarget &&
298
+ (() => {
299
+ const RegistryAffordance = getRegistryAffordanceTool('fence', 'move-control-point')
300
+ return RegistryAffordance ? (
301
+ <Suspense fallback={null}>
302
+ <RegistryAffordance target={controlPointTarget} />
303
+ </Suspense>
304
+ ) : null
305
+ })()}
306
+ {tangentTarget &&
307
+ (() => {
308
+ const RegistryAffordance = getRegistryAffordanceTool('fence', 'move-tangent')
309
+ return RegistryAffordance ? (
310
+ <Suspense fallback={null}>
311
+ <RegistryAffordance target={tangentTarget} />
312
+ </Suspense>
313
+ ) : null
314
+ })()}
315
+ {showMover && movingNode.type !== 'building' && (
316
+ <MoveTool
317
+ onNodeMoved={handlePlacedNodeSelected}
318
+ onSpawnMoved={handlePlacedNodeSelected}
319
+ />
154
320
  )}
155
- {showCeilingHoleEditor && selectedCeilingId && editingHole && (
156
- <CeilingHoleEditor ceilingId={selectedCeilingId} holeIndex={editingHole.holeIndex} />
321
+ {/* Registry-first: when the active tool's kind has a registered
322
+ NodeDefinition with a tool contribution, mount it here. */}
323
+ {!movingNode && useRegistryTool && RegistryToolComponent && (
324
+ <Suspense fallback={null}>
325
+ <RegistryToolComponent />
326
+ </Suspense>
157
327
  )}
158
- {movingWallEndpoint && <MoveWallEndpointTool target={movingWallEndpoint} />}
159
- {movingFenceEndpoint && <MoveFenceEndpointTool target={movingFenceEndpoint} />}
160
- {curvingWall && <CurveWallTool node={curvingWall} />}
161
- {curvingFence && <CurveFenceTool node={curvingFence} />}
162
- {movingNode && movingNode.type !== 'building' && (
163
- <MoveTool onSpawnMoved={handlePlacedNodeSelected} />
328
+ {!movingNode && !useRegistryTool && showBuildTool && tool === 'elevator' && (
329
+ <ElevatorTool
330
+ buildingId={buildingId as BuildingNode['id'] | null}
331
+ levelId={activeLevelId ?? null}
332
+ onPlaced={handlePlacedElevatorSelected}
333
+ />
164
334
  )}
165
- {!movingNode && BuildToolComponent && tool === 'spawn' ? (
166
- <SpawnTool currentLevelId={activeLevelId ?? null} onPlaced={handlePlacedNodeSelected} />
167
- ) : !movingNode && showBuildTool && tool === 'column' ? (
168
- <ColumnTool currentLevelId={activeLevelId ?? null} onPlaced={handlePlacedNodeSelected} />
169
- ) : !movingNode && BuildToolComponent && tool !== 'column' ? (
170
- <BuildToolComponent />
171
- ) : null}
335
+ {!movingNode && BuildToolComponent && tool !== 'elevator' ? <BuildToolComponent /> : null}
336
+ {/* Figma-style alignment guides published by the move / placement
337
+ tools above. Lives inside the building-local group so the
338
+ building-local guide coords render at the right world position. */}
339
+ <Alignment3DGuideLayer />
340
+ {/* The one forward-facing triangle renderer. Placement/move tools
341
+ publish their ghost pose to `useFacingPose`; this draws it. Mounted
342
+ here so it shares the building-local frame the tools publish in. */}
343
+ <FacingPoseIndicator />
344
+ {/* Wall-plane proximity / sill / equal-spacing guides for openings,
345
+ published by the door/window move tools in the same world frame. */}
346
+ <OpeningGuides3DLayer />
347
+ {/* "Magnetic" beacon at the active wall-draft snap point. */}
348
+ <WallSnapBeaconLayer />
172
349
  </group>
173
350
  </>
174
351
  )