@pascal-app/editor 1.0.7 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -7,6 +7,7 @@ import {
7
7
  collectionIdsOf,
8
8
  createSceneApi,
9
9
  getWallMidpointHandlePoint,
10
+ isNodeDuplicable,
10
11
  type NodeQuickAction,
11
12
  nodeRegistry,
12
13
  runAsSingleSceneHistoryStep,
@@ -255,7 +256,7 @@ export function FloorplanRegistryActionMenu() {
255
256
  const canMove =
256
257
  !registryMoveDisabled(node) &&
257
258
  (!!def.capabilities.movable || !!def.floorplanMoveTarget || !!def.affordanceTools?.move)
258
- const canDuplicate = def.capabilities.duplicable !== false
259
+ const canDuplicate = isNodeDuplicable(node)
259
260
  const canDelete = def.capabilities.deletable !== false
260
261
  const canAddHole = node.type === 'slab' || node.type === 'ceiling'
261
262
 
@@ -26,7 +26,7 @@ import {
26
26
  useScene,
27
27
  } from '@pascal-app/core'
28
28
  import { nodeLevelFrame } from '@pascal-app/core/procedural-items'
29
- import { useViewer } from '@pascal-app/viewer'
29
+ import { isTypingTarget, useViewer } from '@pascal-app/viewer'
30
30
  import { useEffect, useMemo } from 'react'
31
31
  import { commitFreshPlacementSubtree } from '../../lib/fresh-planar-placement'
32
32
  import { isHistoryShortcut } from '../../lib/history'
@@ -39,7 +39,6 @@ import {
39
39
  import { movementSfxStepKey } from '../../lib/sfx/movement-tick'
40
40
  import { sfxEmitter } from '../../lib/sfx-bus'
41
41
  import { surfaceAttachmentId } from '../../lib/surface-attachment'
42
- import { isTypingTarget } from '../../lib/typing-target'
43
42
  import { resolveAlignmentForFloorplanView } from '../../lib/world-grid-snap'
44
43
  import useAlignmentGuides from '../../store/use-alignment-guides'
45
44
  import useEditor, {
@@ -850,14 +849,25 @@ export function FloorplanRegistryMoveOverlay() {
850
849
  }
851
850
  boxEl.setAttribute('transform', transform)
852
851
  const oldY = originalPosition[1]
852
+ const position = nodeRegistry.get(movingNode.type)?.capabilities?.movable?.resolvePosition?.({
853
+ node: useScene.getState().nodes[movingNode.id] ?? movingNode,
854
+ position: [finalX, oldY, finalZ],
855
+ nodes: useScene.getState().nodes,
856
+ }) ?? [finalX, oldY, finalZ]
857
+ if (def?.capabilities?.movable?.resolvePosition) {
858
+ useLiveTransforms.getState().set(movingNode.id, {
859
+ position: position as [number, number, number],
860
+ rotation: currentRotation,
861
+ })
862
+ }
853
863
  lastPositionValid = movableValidityConfig?.isValidPosition
854
864
  ? movableValidityConfig.isValidPosition({
855
865
  node: {
856
866
  ...movingNode,
857
- position: [finalX, oldY, finalZ],
867
+ position,
858
868
  rotation: currentRotation,
859
869
  } as AnyNode,
860
- position: [finalX, oldY, finalZ],
870
+ position: position as [number, number, number],
861
871
  rotation: currentRotation,
862
872
  levelId:
863
873
  (useViewer.getState().selection.levelId as AnyNodeId | null) ??
@@ -885,6 +895,11 @@ export function FloorplanRegistryMoveOverlay() {
885
895
  (storedRotation as [number?, number?, number?])[2] ?? 0,
886
896
  ]
887
897
  : snapped.rotation
898
+ const position = nodeRegistry.get(movingNode.type)?.capabilities?.movable?.resolvePosition?.({
899
+ node: useScene.getState().nodes[movingNode.id] ?? movingNode,
900
+ position: [sx, oldY, sz],
901
+ nodes: useScene.getState().nodes,
902
+ }) ?? [sx, oldY, sz]
888
903
  const rotationPatch = 'rotation' in movingNode ? { rotation } : {}
889
904
  const atomicPreview =
890
905
  isFreshPlacement &&
@@ -937,7 +952,7 @@ export function FloorplanRegistryMoveOverlay() {
937
952
  const committedId = commitFreshPlacementSubtree(
938
953
  movingNode.id as AnyNodeId,
939
954
  {
940
- position: [sx, oldY, sz],
955
+ position,
941
956
  ...rotationPatch,
942
957
  metadata: stripPlacementMetadataFlags((movingNode as { metadata?: unknown }).metadata),
943
958
  visible: true,
@@ -953,7 +968,7 @@ export function FloorplanRegistryMoveOverlay() {
953
968
  useScene.getState().updateNode(
954
969
  movingNode.id as AnyNodeId,
955
970
  {
956
- position: [sx, oldY, sz],
971
+ position,
957
972
  ...rotationPatch,
958
973
  } as Partial<AnyNode>,
959
974
  )
@@ -1005,6 +1020,8 @@ export function FloorplanRegistryMoveOverlay() {
1005
1020
  for (const relatedEntry of relatedEntries) {
1006
1021
  relatedEntry.style.visibility = ''
1007
1022
  }
1023
+ if (def?.capabilities?.movable?.resolvePosition)
1024
+ useLiveTransforms.getState().clear(movingNode.id)
1008
1025
  boxEl.remove()
1009
1026
  useAlignmentGuides.getState().clear()
1010
1027
  }
@@ -1,47 +1,10 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { floorplanGeometryMetadata } from '../../../lib/floorplan/floorplan-extension'
3
2
  import {
4
- floorplanAnnotationObstacleMode,
5
3
  polylineObstacleRectangles,
6
4
  resolveAnnotationLabelRectangles,
7
5
  resolveSvgAnnotationCollisions,
8
6
  } from './floorplan-annotation-layout'
9
7
 
10
- describe('floorplanAnnotationObstacleMode', () => {
11
- test('treats fixed annotation categories as layout obstacles', () => {
12
- expect(
13
- floorplanAnnotationObstacleMode({
14
- kind: 'text',
15
- x: 0,
16
- y: 0,
17
- text: 'BEDROOM',
18
- fontSize: 0.18,
19
- metadata: floorplanGeometryMetadata({ annotationRole: 'room-label' }),
20
- }),
21
- ).toBe('bounds')
22
- expect(
23
- floorplanAnnotationObstacleMode({
24
- kind: 'line',
25
- x1: 0,
26
- y1: 0,
27
- x2: 1,
28
- y2: 0,
29
- metadata: floorplanGeometryMetadata({ annotationRole: 'column-center' }),
30
- }),
31
- ).toBe('bounds')
32
- expect(
33
- floorplanAnnotationObstacleMode({
34
- kind: 'polyline',
35
- points: [
36
- [0, 0],
37
- [1, 0],
38
- ],
39
- metadata: floorplanGeometryMetadata({ annotationRole: 'stair-annotation' }),
40
- }),
41
- ).toBe('outline')
42
- })
43
- })
44
-
45
8
  describe('resolveAnnotationLabelRectangles', () => {
46
9
  test('keeps the higher-priority label and moves the conflicting label', () => {
47
10
  const shifts = resolveAnnotationLabelRectangles([
@@ -1,6 +1,3 @@
1
- import type { FloorplanGeometry } from '@pascal-app/core'
2
- import { readFloorplanGeometryMetadata } from '../../../lib/floorplan/floorplan-extension'
3
-
4
1
  export type AnnotationLabelRectangle = {
5
2
  id: string
6
3
  x: number
@@ -467,33 +464,6 @@ function candidateCost(
467
464
  return preference + distance + perpendicularMovement * 4
468
465
  }
469
466
 
470
- export function isFloorplanAnnotationObstacleGeometry(geometry: FloorplanGeometry): boolean {
471
- if (floorplanAnnotationObstacleMode(geometry)) return true
472
- if (geometry.kind !== 'group') return false
473
- const hasPlate = geometry.children.some(
474
- (child) => child.kind === 'rect' || child.kind === 'circle',
475
- )
476
- const hasUprightText = geometry.children.some((child) => child.kind === 'text' && child.upright)
477
- return hasPlate && hasUprightText
478
- }
479
-
480
- export function floorplanAnnotationObstacleMode(
481
- geometry: FloorplanGeometry,
482
- ): 'bounds' | 'outline' | '' | undefined {
483
- const metadata = readFloorplanGeometryMetadata(geometry)
484
- if (metadata.annotationObstacle) return metadata.annotationObstacle
485
- switch (metadata.annotationRole) {
486
- case 'room-label':
487
- return geometry.kind === 'text' ? 'bounds' : undefined
488
- case 'column-center':
489
- return geometry.kind === 'line' || geometry.kind === 'text' ? 'bounds' : undefined
490
- case 'stair-annotation':
491
- return geometry.kind === 'polyline' || geometry.kind === 'line' ? 'outline' : 'bounds'
492
- default:
493
- return undefined
494
- }
495
- }
496
-
497
467
  function rectanglesOverlap(
498
468
  left: Pick<AnnotationLabelRectangle, 'x' | 'y' | 'width' | 'height'>,
499
469
  right: Pick<AnnotationLabelRectangle, 'x' | 'y' | 'width' | 'height'>,
@@ -3,7 +3,8 @@
3
3
  import type { AnyNode, AnyNodeId } from '@pascal-app/core'
4
4
  import { useLocale, useScene } from '@pascal-app/core'
5
5
  import { useViewer } from '@pascal-app/viewer'
6
- import { useCallback, useEffect, useRef, useState } from 'react'
6
+ import { FloorplanDimensionHitsProvider } from '@pascal-app/viewer/ui'
7
+ import { type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
7
8
  import { createPortal } from 'react-dom'
8
9
  import {
9
10
  parseDimensionInput,
@@ -26,8 +27,8 @@ import useEditor from '../../../store/use-editor'
26
27
  * anchor the value is written as `textOverride` on a construction-dimension
27
28
  * node instead, and the label renders an "override" badge.
28
29
  *
29
- * The label is found by DOM delegation on the attributes
30
- * `floorplan-dimension-renderer.tsx` emits, so this works identically for
30
+ * The label is found by DOM delegation on the attributes the viewer's
31
+ * dimension renderer emits under `FloorplanDimensionHitsGate`, so this works identically for
31
32
  * `dimension` and `dimension-string` geometry, for automatic wall dimensions,
32
33
  * contextual dimensions and manual construction-dimension nodes — and on
33
34
  * sheets, which mount the same renderer.
@@ -62,6 +63,15 @@ export function useDimensionEditAllowed(): boolean {
62
63
  return useEditor((state) => isDimensionEditAllowed(state, { readOnly }))
63
64
  }
64
65
 
66
+ /** Plan dimension labels under the editor carry their typeable hit rects while typing is allowed. */
67
+ export function FloorplanDimensionHitsGate({ children }: { children: ReactNode }) {
68
+ return (
69
+ <FloorplanDimensionHitsProvider value={useDimensionEditAllowed()}>
70
+ {children}
71
+ </FloorplanDimensionHitsProvider>
72
+ )
73
+ }
74
+
65
75
  /**
66
76
  * The typeable dimension value under a press, with the node typing it drives:
67
77
  * the pressed element's or, when the plan's dashed group box lies over it, the
@@ -19,6 +19,7 @@ export type DraftMeasurement = {
19
19
  }
20
20
 
21
21
  export function FloorplanDraftMeasurement({
22
+ lengthHidden = false,
22
23
  measurement,
23
24
  measurementStroke,
24
25
  labelBackground,
@@ -26,6 +27,8 @@ export function FloorplanDraftMeasurement({
26
27
  sceneRotationDeg,
27
28
  unitsPerPixel,
28
29
  }: {
30
+ /** Leave the length plate to something drawn in its place (the typed-entry HUD). */
31
+ lengthHidden?: boolean
29
32
  measurement: DraftMeasurement
30
33
  measurementStroke: string
31
34
  labelBackground: string
@@ -68,7 +71,10 @@ export function FloorplanDraftMeasurement({
68
71
 
69
72
  return (
70
73
  <g pointerEvents="none">
71
- <g transform={`translate(${cx} ${cy}) rotate(${labelAngleDeg})`}>
74
+ <g
75
+ display={lengthHidden ? 'none' : undefined}
76
+ transform={`translate(${cx} ${cy}) rotate(${labelAngleDeg})`}
77
+ >
72
78
  <rect
73
79
  fill={labelBg}
74
80
  height={lengthPlateH}
@@ -4,17 +4,18 @@ import {
4
4
  type AnyNode,
5
5
  type AnyNodeId,
6
6
  type FloorplanGeometry,
7
+ formatLinearMeasurement,
7
8
  type GeometryContext,
8
9
  nodeRegistry,
9
10
  useScene,
10
11
  } from '@pascal-app/core'
11
12
  import { useViewer } from '@pascal-app/viewer'
13
+ import { FloorplanDimensionRenderer, FloorplanGeometryRenderer } from '@pascal-app/viewer/ui'
12
14
  import { memo, useMemo } from 'react'
13
- import { formatLinearMeasurement } from '../../../lib/measurements'
14
15
  import usePlacementPreview from '../../../store/use-placement-preview'
16
+ import { usePlacementTyping } from '../../../store/use-placement-typing'
15
17
  import { useFloorplanRender, useFloorplanSceneRotation } from '../floorplan-render-context'
16
- import { FloorplanDimensionRenderer } from './floorplan-dimension-renderer'
17
- import { FloorplanGeometryRenderer } from './floorplan-geometry-renderer'
18
+ import { FloorplanPlacementTypingHud } from './floorplan-placement-typing-hud'
18
19
 
19
20
  export interface FloorplanNodePreviewProps {
20
21
  node: AnyNode
@@ -139,14 +140,24 @@ export const FloorplanPlacementPreviewLayer = memo(function FloorplanPlacementPr
139
140
  const dimensions = usePlacementPreview((s) => s.dimensions)
140
141
  const activeDimensionId = usePlacementPreview((s) => s.activeDimensionId)
141
142
  const dimensionInput = usePlacementPreview((s) => s.dimensionInput)
143
+ const typingProjectedPosition = usePlacementTyping((s) => s.projectedPosition)
142
144
  const unit = useViewer((s) => s.unit)
143
145
  const metricNotation = useViewer((s) => s.metricNotation)
144
146
  const sceneRotationDeg = useFloorplanSceneRotation()
147
+ const unitsPerPixel = useFloorplanRender()?.unitsPerPixel ?? 1
145
148
  if (!node) return null
146
149
 
147
150
  return (
148
151
  <g data-floorplan-placement-preview>
149
152
  <FloorplanNodePreview contextNodes={contextNodes} node={node} parentNode={parentNode} />
153
+ {typingProjectedPosition ? (
154
+ <FloorplanPlacementTypingHud
155
+ sceneRotationDeg={sceneRotationDeg}
156
+ unitsPerPixel={unitsPerPixel}
157
+ x={typingProjectedPosition[0]}
158
+ y={typingProjectedPosition[2]}
159
+ />
160
+ ) : null}
150
161
  <g data-floorplan-placement-dimensions>
151
162
  {dimensions
152
163
  .filter((dimension) => dimension.renderInFloorplan !== false)
@@ -0,0 +1,35 @@
1
+ import { afterEach, expect, test } from 'bun:test'
2
+ import {
3
+ PLACEMENT_TYPING_LENGTH_ANGLE,
4
+ usePlacementTyping,
5
+ } from '../../../store/use-placement-typing'
6
+ import { renderCurrentMarkup } from '../../../test-utils/render-current-markup'
7
+ import { FloorplanPlacementTypingHud } from './floorplan-placement-typing-hud'
8
+
9
+ afterEach(() => {
10
+ usePlacementTyping.getState().clear()
11
+ })
12
+
13
+ test('sizes and counter-rotates the HUD from the props it is given', () => {
14
+ usePlacementTyping.getState().begin({ labels: PLACEMENT_TYPING_LENGTH_ANGLE, activeField: 0 })
15
+
16
+ const markup = renderCurrentMarkup(
17
+ <svg>
18
+ <FloorplanPlacementTypingHud sceneRotationDeg={30} unitsPerPixel={0.02} x={1} y={2} />
19
+ </svg>,
20
+ )
21
+
22
+ expect(markup).toContain('transform="translate(1 2) rotate(-30)"')
23
+ expect(markup).toMatch(/<foreignObject[^>]*width="4.72"/)
24
+ expect(markup).toContain('scale(0.02)')
25
+ })
26
+
27
+ test('renders nothing without a typing session', () => {
28
+ const markup = renderCurrentMarkup(
29
+ <svg>
30
+ <FloorplanPlacementTypingHud sceneRotationDeg={0} unitsPerPixel={0.02} x={0} y={0} />
31
+ </svg>,
32
+ )
33
+
34
+ expect(markup).not.toContain('foreignObject')
35
+ })
@@ -0,0 +1,65 @@
1
+ 'use client'
2
+
3
+ import { usePlacementTyping } from '../../../store/use-placement-typing'
4
+ import { PlacementCoordinateInput } from '../../tools/shared/placement-coordinate-input'
5
+
6
+ const HUD_WIDTH_PX = 236
7
+ const HUD_HEIGHT_PX = 64
8
+ const HUD_LIFT_PX = 14
9
+
10
+ /**
11
+ * The typed-entry HUD placed in the plan at `x`/`y` (level-local metres).
12
+ * HTML inside a `foreignObject` is laid out in CSS pixels equal to SVG user
13
+ * units, and the plan's `<g>` is zoomed and rotated, so the box is scaled by
14
+ * `unitsPerPixel` and counter-rotated to stay screen-sized and upright. Both
15
+ * come from the caller: the wall draft renders outside `FloorplanRenderProvider`.
16
+ * It sits just above its anchor, and only the HUD itself takes the pointer, so
17
+ * clicks on the ghost or the draft under it still land on the plan.
18
+ */
19
+ export function FloorplanPlacementTypingHud({
20
+ placeholders,
21
+ sceneRotationDeg,
22
+ unitsPerPixel,
23
+ x,
24
+ y,
25
+ }: {
26
+ placeholders?: readonly string[]
27
+ sceneRotationDeg: number
28
+ unitsPerPixel: number
29
+ x: number
30
+ y: number
31
+ }) {
32
+ const isActive = usePlacementTyping((state) => state.isActive)
33
+ const scale = Math.max(unitsPerPixel, 1e-6)
34
+ if (!isActive) return null
35
+
36
+ return (
37
+ <g transform={`translate(${x} ${y}) rotate(${-sceneRotationDeg})`}>
38
+ <foreignObject
39
+ height={HUD_HEIGHT_PX * scale}
40
+ overflow="visible"
41
+ pointerEvents="none"
42
+ style={{ overflow: 'visible' }}
43
+ width={HUD_WIDTH_PX * scale}
44
+ x={(-HUD_WIDTH_PX / 2) * scale}
45
+ y={-(HUD_HEIGHT_PX + HUD_LIFT_PX) * scale}
46
+ >
47
+ <div
48
+ style={{
49
+ alignItems: 'flex-end',
50
+ display: 'flex',
51
+ height: HUD_HEIGHT_PX,
52
+ justifyContent: 'center',
53
+ overflow: 'visible',
54
+ pointerEvents: 'none',
55
+ transform: `scale(${scale})`,
56
+ transformOrigin: 'top left',
57
+ width: HUD_WIDTH_PX,
58
+ }}
59
+ >
60
+ <PlacementCoordinateInput placeholders={placeholders} />
61
+ </div>
62
+ </foreignObject>
63
+ </g>
64
+ )
65
+ }
@@ -7,15 +7,19 @@ import type {
7
7
  FloorplanPalette,
8
8
  LiveNodeOverrides,
9
9
  } from '@pascal-app/core'
10
- import { type AnyNodeDefinition, emitter, nodeRegistry, registerNode } from '@pascal-app/core'
10
+ import {
11
+ type AnyNodeDefinition,
12
+ emitter,
13
+ floorplanGeometryMetadata,
14
+ nodeRegistry,
15
+ registerNode,
16
+ } from '@pascal-app/core'
11
17
  import { createElement } from 'react'
12
18
  import { renderToStaticMarkup } from 'react-dom/server'
13
19
  import { z } from 'zod'
14
- import {
15
- FLOORPLAN_NODE_EXTENSION_KEY,
16
- floorplanGeometryMetadata,
17
- } from '../../../lib/floorplan/floorplan-extension'
20
+ import { AXIS_MOVE_DRAG_LABEL, PATH_POINT_DRAG_LABEL } from '../../../lib/contextual-help'
18
21
  import type { SessionWrites } from '../../../lib/session-writes'
22
+ import { snapContextOf } from '../../../lib/snapping-mode'
19
23
  import {
20
24
  buildFloorplanEntryGeometry,
21
25
  cancelFloorplanAffordanceDrag,
@@ -276,6 +280,26 @@ describe('floorplan affordance ownership', () => {
276
280
  driver: 'floorplan',
277
281
  })
278
282
  })
283
+
284
+ // With no scope, Select mode reads every snap mode as off, so these drags
285
+ // never snapped to the grid on the plan.
286
+ test('gives the MEP run point and segment drags a handle-drag scope in the run snap context', () => {
287
+ const point = floorplanAffordanceReshapeScope('move-path-point', 'duct_1', { pointIndex: 0 })
288
+ const segment = floorplanAffordanceReshapeScope('move-segment', 'duct_1', { segmentIndex: 0 })
289
+ expect(point).toEqual({ kind: 'handle-drag', nodeId: 'duct_1', handle: PATH_POINT_DRAG_LABEL })
290
+ expect(segment).toEqual({ kind: 'handle-drag', nodeId: 'duct_1', handle: AXIS_MOVE_DRAG_LABEL })
291
+ for (const scope of [point, segment]) {
292
+ expect(
293
+ snapContextOf({
294
+ scope: scope!,
295
+ mode: 'select',
296
+ tool: null,
297
+ profileOf: () => undefined,
298
+ profileOfNode: () => 'structural',
299
+ }),
300
+ ).toBe('polygon')
301
+ }
302
+ })
279
303
  })
280
304
 
281
305
  function cabinetRun(id: string, children: string[] = [], parentId: string | null = 'level_test') {
@@ -754,11 +778,7 @@ describe('collectFloorplanLinkedLevelNodes', () => {
754
778
  category: 'structure',
755
779
  defaults: () => ({}) as never,
756
780
  floorplan: () => null,
757
- extensions: {
758
- [FLOORPLAN_NODE_EXTENSION_KEY]: {
759
- linkedLevelIds: () => ['level_upper' as AnyNodeId],
760
- },
761
- },
781
+ floorplanLinkedLevelIds: () => ['level_upper' as AnyNodeId],
762
782
  } as unknown as AnyNodeDefinition)
763
783
  const child = {
764
784
  id: 'linked_child',
@@ -4,15 +4,22 @@ import {
4
4
  type AnyNode,
5
5
  type AnyNodeDefinition,
6
6
  type AnyNodeId,
7
+ buildFloorplanContext,
8
+ createFloorplanContextExtensions,
7
9
  createSceneApi,
8
10
  emitter,
9
11
  type FloorplanAffordanceSession,
12
+ type FloorplanAnnotationRole,
10
13
  type FloorplanGeometry,
11
14
  type FloorplanPalette,
12
15
  type FloorplanPoint,
13
16
  type FloorplanScope,
17
+ type FloorplanWallDimensionFaces,
18
+ floorplanAnnotationObstacleMode,
19
+ floorplanLayerRank,
14
20
  type GeometryContext,
15
21
  hidesDescendants,
22
+ isFloorplanAnnotationObstacleGeometry,
16
23
  isNodeKindEnabled,
17
24
  isRegistryMovable,
18
25
  kindsWithFloorplanScope,
@@ -20,6 +27,7 @@ import {
20
27
  type LiveTransform,
21
28
  nodeRegistry,
22
29
  pauseSceneHistory,
30
+ readFloorplanGeometryMetadata,
23
31
  resolveBuildingForLevel,
24
32
  resolveSelectionProxyId,
25
33
  resumeSceneHistory,
@@ -28,6 +36,10 @@ import {
28
36
  useLiveNodeOverrides,
29
37
  useLiveTransforms,
30
38
  useScene,
39
+ WALL_PULL_AFFORDANCE,
40
+ WALL_PUSH_AFFORDANCE,
41
+ type WallPullGripPayload,
42
+ withFloorplanGeometryMetadata,
31
43
  } from '@pascal-app/core'
32
44
  import {
33
45
  beginPerfAction,
@@ -37,6 +49,14 @@ import {
37
49
  useRevealWaiting,
38
50
  useViewer,
39
51
  } from '@pascal-app/viewer'
52
+ import {
53
+ FloorplanDimensionRenderer,
54
+ FloorplanGeometryRenderer,
55
+ resolveFloorplanAnnotationUpdate,
56
+ resolveFloorplanLabelAngle,
57
+ selectionModifiersFromEvent,
58
+ updateSvgFloorplanLabelOrientations,
59
+ } from '@pascal-app/viewer/ui'
40
60
  import {
41
61
  type ComponentProps,
42
62
  memo,
@@ -55,11 +75,14 @@ import {
55
75
  hoverRoomFromHit,
56
76
  resolvePlanRoomHit,
57
77
  roomPickingEnabled,
58
- selectionModifiersFromEvent,
59
78
  useRoomRecords,
60
79
  } from '../../../hooks/use-selected-room'
61
80
  import { useCeilingEditSession } from '../../../lib/ceiling-edit-session'
62
- import { ROTATE_HANDLE_DRAG_LABEL } from '../../../lib/contextual-help'
81
+ import {
82
+ AXIS_MOVE_DRAG_LABEL,
83
+ PATH_POINT_DRAG_LABEL,
84
+ ROTATE_HANDLE_DRAG_LABEL,
85
+ } from '../../../lib/contextual-help'
63
86
  import {
64
87
  canDirectRotateNode,
65
88
  resolveDirectManipulationNode,
@@ -75,22 +98,11 @@ import {
75
98
  filterFloorplanAnnotationGeometry,
76
99
  } from '../../../lib/floorplan/annotation-visibility'
77
100
  import { resolveNodeForDrawingType } from '../../../lib/floorplan/drawing-coordination'
78
- import {
79
- createFloorplanContextExtensions,
80
- type FloorplanAnnotationRole,
81
- type FloorplanWallDimensionFaces,
82
- getFloorplanNodeExtension,
83
- readFloorplanGeometryMetadata,
84
- withFloorplanGeometryMetadata,
85
- } from '../../../lib/floorplan/floorplan-extension'
101
+ import { getFloorplanNodeExtension } from '../../../lib/floorplan/floorplan-extension'
86
102
  import {
87
103
  type FloorplanMode,
88
104
  resolveFloorplanAnnotationVisibility,
89
105
  } from '../../../lib/floorplan/floorplan-mode'
90
- import {
91
- buildFloorplanContext,
92
- floorplanLayerRank,
93
- } from '../../../lib/floorplan/floorplan-readonly'
94
106
  import { clientToPlan } from '../../../lib/floorplan/plan-coords'
95
107
  import {
96
108
  type ActiveInteractionScope,
@@ -102,7 +114,7 @@ import {
102
114
  isIdle,
103
115
  tangentReshapeScope,
104
116
  } from '../../../lib/interaction/scope'
105
- import { runFloorplanWallPush, WALL_PUSH_AFFORDANCE } from '../../../lib/room-handle-drag'
117
+ import { runFloorplanWallPush } from '../../../lib/room-handle-drag'
106
118
  import { selectRoom, selectRoomFromHit } from '../../../lib/room-selection-commands'
107
119
  import { clickZoneArea, hoverZoneArea } from '../../../lib/room-zone-routing'
108
120
  import {
@@ -114,11 +126,7 @@ import { createSessionWrites, type SessionWrites } from '../../../lib/session-wr
114
126
  import { sfxEmitter } from '../../../lib/sfx-bus'
115
127
  import { clearSurfacePlanSnapFeedback } from '../../../lib/surface-plan-snap'
116
128
  import { paintZoneMembership } from '../../../lib/units'
117
- import {
118
- startWallPull,
119
- WALL_PULL_AFFORDANCE,
120
- type WallPullGripPayload,
121
- } from '../../../lib/wall-pull'
129
+ import { startWallPull } from '../../../lib/wall-pull'
122
130
  import useDirectManipulationFeedback from '../../../store/use-direct-manipulation-feedback'
123
131
  import useDrawingView from '../../../store/use-drawing-view'
124
132
  import useEditor, { isAngleSnapActive, type Mode } from '../../../store/use-editor'
@@ -153,23 +161,11 @@ import {
153
161
  useFloorplanStaticRender,
154
162
  useFloorplanStaticUnitsPerPixel,
155
163
  } from '../floorplan-render-context'
156
- import {
157
- floorplanAnnotationObstacleMode,
158
- isFloorplanAnnotationObstacleGeometry,
159
- resolveSvgAnnotationCollisions,
160
- svgAnnotationLabelId,
161
- } from './floorplan-annotation-layout'
164
+ import { resolveSvgAnnotationCollisions, svgAnnotationLabelId } from './floorplan-annotation-layout'
162
165
  import {
163
166
  FloorplanDimensionEditOverlay,
164
167
  typeableDimensionAt,
165
168
  } from './floorplan-dimension-edit-overlay'
166
- import { FloorplanDimensionRenderer } from './floorplan-dimension-renderer'
167
- import { FloorplanGeometryRenderer } from './floorplan-geometry-renderer'
168
- import {
169
- resolveFloorplanAnnotationUpdate,
170
- resolveFloorplanLabelAngle,
171
- updateSvgFloorplanLabelOrientations,
172
- } from './floorplan-label-angle'
173
169
  import { useFloorplanReveal } from './floorplan-reveal'
174
170
 
175
171
  /**
@@ -321,7 +317,7 @@ export function subscribeFloorplanAffordanceToolCancel(
321
317
  // edits that set a direction/shape map to a scope; resize / rotate / body-move
322
318
  // affordances return `null` (no polygon/wall snapping chip). Keyed off the
323
319
  // affordance name the kinds register (`move-vertex` / `move-edge` / `add-vertex`
324
- // / `curve` / `move-endpoint`).
320
+ // / `curve` / `move-endpoint` / `move-path-point` / `move-segment`).
325
321
  export function floorplanAffordanceReshapeScope(
326
322
  affordance: string,
327
323
  nodeId: string,
@@ -362,6 +358,14 @@ export function floorplanAffordanceReshapeScope(
362
358
  if (affordance.includes('rotate')) {
363
359
  return { kind: 'handle-drag', nodeId, handle: ROTATE_HANDLE_DRAG_LABEL }
364
360
  }
361
+ // MEP run point and run segment drags: the same handle-drag scopes as their
362
+ // 3D arrows, so they snap in the kind's own context (polygon for runs).
363
+ if (affordance === 'move-path-point') {
364
+ return { kind: 'handle-drag', nodeId, handle: PATH_POINT_DRAG_LABEL }
365
+ }
366
+ if (affordance === 'move-segment') {
367
+ return { kind: 'handle-drag', nodeId, handle: AXIS_MOVE_DRAG_LABEL }
368
+ }
365
369
  return null
366
370
  }
367
371
 
@@ -3633,7 +3637,7 @@ export function collectFloorplanLinkedLevelNodes(
3633
3637
  for (const [rawId, node] of Object.entries(nodes)) {
3634
3638
  if (!node) continue
3635
3639
  const definition = nodeRegistry.get(node.type)
3636
- const linkedLevelIds = getFloorplanNodeExtension(definition)?.linkedLevelIds
3640
+ const linkedLevelIds = definition?.floorplanLinkedLevelIds
3637
3641
  if (!definition?.floorplan || !linkedLevelIds) continue
3638
3642
  const id = rawId as AnyNodeId
3639
3643
  if (excludedIds.has(id)) continue