@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
@@ -4,36 +4,28 @@ import {
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
6
  type CeilingNode,
7
- ColumnNode,
8
7
  createSceneApi,
9
- DoorNode,
10
- ElevatorNode,
11
8
  emitter,
12
- FenceNode,
13
- generateId,
9
+ type FenceNode,
14
10
  getActiveRoofHeight,
15
11
  getEffectiveNode,
16
12
  getWallCurveLength,
17
13
  getWallEffectiveHeightForNodes,
18
14
  getWallThickness,
19
- ItemNode,
20
15
  isCurvedWall,
16
+ isDerivedNode,
21
17
  isRegistryMovable,
22
18
  isRegistrySelectable,
23
19
  isSplineFence,
24
20
  type NodeQuickAction,
25
21
  nodeRegistry,
26
- RoofSegmentNode,
27
22
  runAsSingleSceneHistoryStep,
28
23
  type SlabNode,
29
- SpawnNode,
30
- StairSegmentNode,
31
24
  sceneRegistry,
32
25
  summarizeSystemFor,
33
26
  useLiveNodeOverrides,
34
27
  useScene,
35
- WallNode,
36
- WindowNode,
28
+ type WallNode,
37
29
  } from '@pascal-app/core'
38
30
  import { useViewer } from '@pascal-app/viewer'
39
31
  import { Html } from '@react-three/drei'
@@ -44,17 +36,13 @@ import { useShallow } from 'zustand/react/shallow'
44
36
  import { useReducedMotion } from '../../hooks/use-reduced-motion'
45
37
  import { resolveMoveActionNode } from '../../lib/direct-manipulation'
46
38
  import { getFloatingMenuScale } from '../../lib/floating-menu-scale'
47
- import {
48
- createFreshPlacementSubtree,
49
- duplicatesAsFreshSubtree,
50
- prepareFreshPlacementRootDuplicate,
51
- } from '../../lib/fresh-planar-placement'
52
39
  import { resolveFloatingActionMenuVisibility } from '../../lib/interaction/overlay-policy'
53
40
  import { curveReshapeScope, holeEditScope } from '../../lib/interaction/scope'
54
- import { duplicateWithoutMove, registryMoveDisabled } from '../../lib/node-action-movement'
41
+ import { registryMoveDisabled } from '../../lib/node-action-movement'
55
42
  import { playBlockedQuickActionFeedback } from '../../lib/quick-action-feedback'
56
43
  import { collectQuickActionNodeScope } from '../../lib/quick-action-nodes'
57
- import { duplicateRoofSubtree } from '../../lib/roof-duplication'
44
+ import { deleteSelectedSeparator } from '../../lib/room-structure-commands'
45
+ import { captureElementActionOrigin, completeElementAction } from '../../lib/room-zone-routing'
58
46
  import { emitDeleteSFX, sfxEmitter } from '../../lib/sfx-bus'
59
47
  import { cn } from '../../lib/utils'
60
48
  import useEditor from '../../store/use-editor'
@@ -64,8 +52,10 @@ import useInteractionScope, {
64
52
  useIsCurveReshape,
65
53
  } from '../../store/use-interaction-scope'
66
54
  import { IconRefGlyph } from '../ui/icon-ref'
55
+ import { duplicateNodeAndPickUp } from './duplicate-node'
67
56
  import { formatMeasurement, MeasurementPill } from './measurement-pill'
68
57
  import { NodeActionMenu } from './node-action-menu'
58
+ import { startZoneRoomTransform } from './room-controls'
69
59
 
70
60
  /**
71
61
  * A kind shows the system pill when it exposes typed ports — `def.ports`
@@ -126,6 +116,8 @@ const MENU_Y_OFFSETS: Record<string, number> = {
126
116
  // Slab: clears the height arrow that sits at elevation + 0.22 plus the
127
117
  // chevron's own visual reach, so the menu floats just above it.
128
118
  slab: 0.7,
119
+ // Room: measured from the top of its walls, with a wall's clearance.
120
+ zone: 0.5,
129
121
  // Ceiling: clears the upward height arrow that sits ~0.22 above the
130
122
  // ceiling plane, plus extra headroom so the menu doesn't crowd the
131
123
  // chevron at any zoom level.
@@ -135,8 +127,16 @@ const MENU_Y_OFFSETS: Record<string, number> = {
135
127
  shelf: 0.6,
136
128
  }
137
129
 
138
- function getMenuYOffset(node: AnyNode | null): number {
130
+ export function getMenuYOffset(node: AnyNode | null): number {
139
131
  if (!node) return MENU_Y_OFFSET_DEFAULT + EXTRA_MENU_LIFT
132
+ // A window or door built from a script has a height arrow only when its script declares height.
133
+ if (
134
+ (node.type === 'door' || node.type === 'window') &&
135
+ node.source &&
136
+ !node.source.manifest.params.some((spec) => spec.id === 'height')
137
+ ) {
138
+ return MENU_Y_OFFSET_DEFAULT + EXTRA_MENU_LIFT
139
+ }
140
140
  if (node.type === 'stair-segment') {
141
141
  return (MENU_Y_OFFSETS[`stair-${node.segmentType}`] ?? MENU_Y_OFFSET_DEFAULT) + EXTRA_MENU_LIFT
142
142
  }
@@ -229,6 +229,28 @@ function getObjectGeometryKey(object: THREE.Object3D): string {
229
229
  return parts.join('|')
230
230
  }
231
231
 
232
+ const _meshBox = new THREE.Box3()
233
+
234
+ /** The bounds of what renders: hidden objects and invisible-material hit boxes are skipped. */
235
+ function setFromVisibleMeshes(box: THREE.Box3, root: THREE.Object3D): void {
236
+ box.makeEmpty()
237
+ root.updateWorldMatrix(true, true)
238
+ const visit = (object: THREE.Object3D) => {
239
+ if (!object.visible) return
240
+ const mesh = object as THREE.Mesh
241
+ const material = mesh.material as THREE.Material | THREE.Material[] | undefined
242
+ const shown = Array.isArray(material) ? material.some((m) => m.visible) : material?.visible
243
+ if (mesh.isMesh && shown && mesh.geometry) {
244
+ mesh.geometry.computeBoundingBox()
245
+ if (mesh.geometry.boundingBox) {
246
+ box.union(_meshBox.copy(mesh.geometry.boundingBox).applyMatrix4(mesh.matrixWorld))
247
+ }
248
+ }
249
+ for (const child of object.children) visit(child)
250
+ }
251
+ visit(root)
252
+ }
253
+
232
254
  function setNodeDerivedMenuAnchor(
233
255
  node: AnyNode,
234
256
  object: THREE.Object3D,
@@ -443,7 +465,12 @@ export function FloatingActionMenu() {
443
465
  if (needsRecompute) {
444
466
  const effectiveNode = getEffectiveNode(node)
445
467
  if (!setNodeDerivedMenuAnchor(effectiveNode, obj, anchorRef.current)) {
446
- _anchorBox.setFromObject(obj)
468
+ // Built from a script: its hidden hit box, cutout and collider are not what the person sees.
469
+ if ('source' in effectiveNode && effectiveNode.source)
470
+ setFromVisibleMeshes(_anchorBox, obj)
471
+ if (_anchorBox.isEmpty() || !('source' in effectiveNode && effectiveNode.source)) {
472
+ _anchorBox.setFromObject(obj)
473
+ }
447
474
  if (!_anchorBox.isEmpty()) {
448
475
  _anchorBox.getCenter(_anchorCenter)
449
476
  // Position above the object. Per-type offsets clear each kind's
@@ -489,6 +516,7 @@ export function FloatingActionMenu() {
489
516
  (e: React.MouseEvent) => {
490
517
  e.stopPropagation()
491
518
  if (!node) return
519
+ if (startZoneRoomTransform(node, 'move')) return
492
520
  sfxEmitter.emit('sfx:item-pick')
493
521
  const sceneNodes = useScene.getState().nodes
494
522
  setMovingNode(resolveMoveActionNode(node, sceneNodes) as any)
@@ -499,193 +527,9 @@ export function FloatingActionMenu() {
499
527
  const handleDuplicate = useCallback(
500
528
  (e: React.MouseEvent) => {
501
529
  e.stopPropagation()
502
- if (!node?.parentId) return
503
- sfxEmitter.emit('sfx:item-pick')
504
-
505
- if (registryMoveDisabled(node)) {
506
- try {
507
- const id = duplicateWithoutMove(node)
508
- if (id) setSelection({ selectedIds: [id] })
509
- } catch (error) {
510
- console.error('Failed to duplicate node', error)
511
- }
512
- return
513
- }
514
-
515
- if (node.type === 'roof') {
516
- try {
517
- duplicateRoofSubtree(node.id as AnyNodeId, { mode: 'move' })
518
- } catch (error) {
519
- console.error('Failed to duplicate roof', error)
520
- }
521
- return
522
- }
523
-
524
- useScene.temporal.getState().pause()
525
-
526
- if (duplicatesAsFreshSubtree(node as AnyNode)) {
527
- let draftId: AnyNodeId | null = null
528
- try {
529
- draftId = createFreshPlacementSubtree(node.id as AnyNodeId)
530
- const draft = draftId ? useScene.getState().nodes[draftId] : null
531
- if (draft) {
532
- setMovingNode(draft as any)
533
- setSelection({ selectedIds: [] })
534
- return
535
- }
536
- } catch (error) {
537
- if (draftId && useScene.getState().nodes[draftId]) {
538
- useScene.getState().deleteNode(draftId)
539
- }
540
- console.error('Failed to duplicate node subtree', error)
541
- }
542
- useScene.temporal.getState().resume()
543
- return
544
- }
545
-
546
- const duplicateInfo = prepareFreshPlacementRootDuplicate(node as AnyNode) as any
547
-
548
- let duplicate: AnyNode | null = null
549
- try {
550
- if (node.type === 'door') {
551
- duplicate = DoorNode.parse(duplicateInfo)
552
- } else if (node.type === 'window') {
553
- duplicate = WindowNode.parse(duplicateInfo)
554
- } else if (node.type === 'item') {
555
- duplicate = ItemNode.parse(duplicateInfo)
556
- } else if (node.type === 'elevator') {
557
- duplicate = ElevatorNode.parse(duplicateInfo)
558
- } else if (node.type === 'column') {
559
- duplicate = ColumnNode.parse(duplicateInfo)
560
- } else if (node.type === 'wall') {
561
- duplicate = WallNode.parse(duplicateInfo)
562
- } else if (node.type === 'fence') {
563
- duplicate = FenceNode.parse(duplicateInfo)
564
- duplicate.start = [duplicate.start[0] + 1, duplicate.start[1] + 1]
565
- duplicate.end = [duplicate.end[0] + 1, duplicate.end[1] + 1]
566
- } else if (node.type === 'roof-segment') {
567
- duplicateInfo.id = generateId('rseg')
568
- duplicate = RoofSegmentNode.parse(duplicateInfo)
569
- } else if (node.type === 'stair-segment') {
570
- duplicate = StairSegmentNode.parse(duplicateInfo)
571
- } else if (node.type === 'spawn') {
572
- duplicate = SpawnNode.parse(duplicateInfo)
573
- }
574
-
575
- // Registry-driven fallback: any kind with a NodeDefinition can be
576
- // duplicated through its schema's parse(). Future built-in kinds
577
- // get duplicate for free.
578
- if (!duplicate) {
579
- const def = nodeRegistry.get(node.type)
580
- if (def) {
581
- duplicate = def.schema.parse(duplicateInfo) as AnyNode
582
- }
583
- }
584
- } catch (error) {
585
- console.error('Failed to parse duplicate', error)
586
- useScene.temporal.getState().resume()
587
- return
588
- }
589
-
590
- if (!duplicate) {
591
- useScene.temporal.getState().resume()
592
- return
593
- }
594
-
595
- if (duplicate) {
596
- if (
597
- duplicate.type === 'door' ||
598
- duplicate.type === 'window' ||
599
- duplicate.type === 'elevator'
600
- ) {
601
- useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId)
602
- } else if (duplicate.type === 'wall') {
603
- useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId)
604
- } else if (duplicate.type === 'fence') {
605
- useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId)
606
- } else if (duplicate.type === 'roof-segment' || duplicate.type === 'stair-segment') {
607
- // Add small offset to make it visible
608
- if ('position' in duplicate) {
609
- duplicate.position = [
610
- duplicate.position[0] + 1,
611
- duplicate.position[1],
612
- duplicate.position[2] + 1,
613
- ]
614
- }
615
- useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId)
616
- } else if (
617
- duplicate.type === 'item' ||
618
- duplicate.type === 'chimney' ||
619
- duplicate.type === 'dormer'
620
- ) {
621
- // Items, chimneys & dormers use pure drag-to-place: NO node is
622
- // inserted into the scene until the user clicks to commit. The
623
- // `setMovingNode` call below hands the clone (with
624
- // `metadata.isNew = true` + no id) to its move tool —
625
- // `MoveItemTool` / `MoveChimneyTool` / `MoveDormerTool` — which
626
- // create a draft and call `createNode` on the commit click.
627
- // Pre-creating here would drop a second copy into the scene
628
- // before any click — the furnish-tab "duplicate auto-places an
629
- // item without clicking" bug. (Item has its own
630
- // draft-committing move tool, so it must skip the generic
631
- // registry auto-create branch below.)
632
- } else if (
633
- duplicate.type === 'duct-segment' ||
634
- duplicate.type === 'duct-fitting' ||
635
- duplicate.type === 'pipe-segment' ||
636
- duplicate.type === 'lineset' ||
637
- duplicate.type === 'liquid-line'
638
- ) {
639
- // Duct runs & fittings, DWV pipe runs, and refrigerant linesets use
640
- // pure drag-to-place: NO node is inserted into the scene until the
641
- // commit click. `setMovingNode` below hands the clone (with
642
- // `metadata.isNew`) to its ghost tool (`MoveDuctSegmentTool` /
643
- // `MoveDuctFittingTool` / `MovePipeSegmentTool` / `MoveLinesetTool`),
644
- // which previews a translucent copy inside a footprint bounding box
645
- // on the cursor and calls `createNode` on the drop click.
646
- // Pre-creating here would drop a copy before any click — the
647
- // "auto-places it" bug.
648
- } else if (nodeRegistry.has(duplicate.type)) {
649
- // Registry-driven kinds: offset slightly so the duplicate doesn't
650
- // overlap exactly, then create + hand to the move tool. Mirrors the
651
- // roof-segment / stair-segment behavior.
652
- if ('position' in duplicate && Array.isArray((duplicate as any).position)) {
653
- const pos = (duplicate as { position: [number, number, number] }).position
654
- ;(duplicate as { position: [number, number, number] }).position = [
655
- pos[0] + 1,
656
- pos[1],
657
- pos[2] + 1,
658
- ]
659
- } else if ('path' in duplicate && Array.isArray((duplicate as any).path)) {
660
- // Other polyline kinds (pipe / lineset) carry a `path`, not a
661
- // `position`. Create the copy HIDDEN so nothing is auto-placed:
662
- // their shared path mover reveals it as a cursor-following
663
- // preview on the first mouse move and commits on the next click.
664
- ;(duplicate as { visible?: boolean }).visible = false
665
- }
666
- useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId)
667
- }
668
- if (
669
- duplicate.type === 'item' ||
670
- duplicate.type === 'elevator' ||
671
- duplicate.type === 'column' ||
672
- duplicate.type === 'wall' ||
673
- duplicate.type === 'fence' ||
674
- duplicate.type === 'window' ||
675
- duplicate.type === 'door' ||
676
- duplicate.type === 'roof-segment' ||
677
- duplicate.type === 'spawn' ||
678
- duplicate.type === 'stair-segment' ||
679
- // Registry-driven kinds get picked up by MoveTool's generic
680
- // fallback (MoveRegistryNodeTool) so the user can reposition.
681
- nodeRegistry.has(duplicate.type)
682
- ) {
683
- setMovingNode(duplicate as any)
684
- }
685
- setSelection({ selectedIds: [] })
686
- }
530
+ if (node) duplicateNodeAndPickUp(node as AnyNode)
687
531
  },
688
- [node, setMovingNode, setSelection],
532
+ [node],
689
533
  )
690
534
 
691
535
  const handleAddHole = useCallback(
@@ -732,9 +576,15 @@ export function FloatingActionMenu() {
732
576
  (e: React.MouseEvent) => {
733
577
  e.stopPropagation()
734
578
  if (!selectedId) return
579
+ const origin = captureElementActionOrigin([selectedId])
580
+ if (node?.type === 'separator') {
581
+ deleteSelectedSeparator(selectedId as AnyNodeId, origin)
582
+ return
583
+ }
735
584
  emitDeleteSFX(node?.type)
736
585
  setSelection({ selectedIds: [] })
737
586
  useScene.getState().deleteNode(selectedId as AnyNodeId)
587
+ completeElementAction(origin)
738
588
  },
739
589
  [node?.type, selectedId, setSelection],
740
590
  )
@@ -773,6 +623,8 @@ export function FloatingActionMenu() {
773
623
 
774
624
  if (
775
625
  !(selectedId && node && isValidType && !isFloorplanHovered && mode !== 'delete') ||
626
+ // A footprint's floor has one control, its height handle; no pill.
627
+ (node.type === 'slab' && node.plateRole === 'base') ||
776
628
  endpointReshape ||
777
629
  isCurveReshape ||
778
630
  !menuVisibility.root
@@ -804,7 +656,11 @@ export function FloatingActionMenu() {
804
656
  ? handleFind
805
657
  : undefined
806
658
  }
807
- onAddHole={node && HOLE_TYPES.includes(node.type) ? handleAddHole : undefined}
659
+ onAddHole={
660
+ node && HOLE_TYPES.includes(node.type) && !isDerivedNode(node)
661
+ ? handleAddHole
662
+ : undefined
663
+ }
808
664
  onCurve={
809
665
  (node?.type === 'fence' && !isSplineFence(node) && !isCurvedWall(node)) ||
810
666
  (node?.type === 'wall' && canCurveSelectedWall)