@pascal-app/editor 1.0.3 → 1.0.5

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 (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -5,10 +5,12 @@ import {
5
5
  type AnyNode,
6
6
  type AnyNodeId,
7
7
  type BuildingNode,
8
+ type CameraControlFrameEvent,
8
9
  type CeilingNode,
9
10
  CeilingNode as CeilingNodeSchema,
10
11
  type ColumnNode,
11
12
  calculateLevelMiters,
13
+ createSizedStairFlight,
12
14
  DEFAULT_ANGLE_STEP,
13
15
  type DoorNode,
14
16
  DoorNode as DoorNodeSchema,
@@ -17,7 +19,6 @@ import {
17
19
  type FenceNode,
18
20
  type FloorplanGeometry,
19
21
  type GeometryContext,
20
- type GridEvent,
21
22
  type GuideNode,
22
23
  getRenderableSlabPolygon,
23
24
  getWallChordFrame,
@@ -27,6 +28,7 @@ import {
27
28
  isCurvedWall,
28
29
  type LevelNode,
29
30
  loadAssetUrl,
31
+ newZone,
30
32
  nodeRegistry,
31
33
  normalizeWallCurveOffset,
32
34
  type Point2D,
@@ -40,7 +42,6 @@ import {
40
42
  SlabNode as SlabNodeSchema,
41
43
  type StairNode,
42
44
  StairNode as StairNodeSchema,
43
- StairSegmentNode as StairSegmentNodeSchema,
44
45
  sampleWallCenterline,
45
46
  sceneRegistry,
46
47
  snapPointAlongAngleRay,
@@ -56,14 +57,13 @@ import {
56
57
  WindowNode as WindowNodeSchema,
57
58
  wallClosesRoom,
58
59
  wallRectangleCorners,
59
- ZoneNode as ZoneNodeSchema,
60
- type ZoneNode as ZoneNodeType,
61
60
  } from '@pascal-app/core'
62
61
  import { useSegmentDraftChain, useWallSnapIndicator } from '@pascal-app/editor'
63
62
  import { getSceneTheme, useViewer } from '@pascal-app/viewer'
64
63
  import { Command, Ruler } from 'lucide-react'
65
64
  import {
66
65
  type ComponentProps,
66
+ type CSSProperties,
67
67
  memo,
68
68
  type MouseEvent as ReactMouseEvent,
69
69
  type ReactNode,
@@ -78,11 +78,15 @@ import {
78
78
  import { createPortal } from 'react-dom'
79
79
  import { Vector3 } from 'three'
80
80
  import { useShallow } from 'zustand/react/shallow'
81
+ import {
82
+ hoverRoomFromHit,
83
+ resolvePlanRoomHit,
84
+ roomPickingEnabled,
85
+ } from '../../hooks/use-selected-room'
81
86
  import { resolveCeilingPlanPointSnap } from '../../lib/ceiling-plan-snap'
82
87
  import {
83
88
  alignFloorplanDraftPoint,
84
89
  buildFloorplanItemEntry,
85
- buildFloorplanStairEntry as buildSharedFloorplanStairEntry,
86
90
  collectLevelDescendants,
87
91
  FLOORPLAN_VIEW_ROTATION_DEG,
88
92
  floorplanLocalToWorldPoint,
@@ -91,29 +95,59 @@ import {
91
95
  type FloorplanNodeTransform as SharedFloorplanNodeTransform,
92
96
  worldToFloorplanLocalPoint,
93
97
  } from '../../lib/floorplan'
98
+ import { buildFloorplanContext } from '../../lib/floorplan/floorplan-readonly'
99
+ // Site-plan view. Owned under `lib/floorplan/site-plan/**`; this panel
100
+ // only chooses between it and the registry layer, and mounts the switch.
101
+ import {
102
+ FloorplanDrawingTypeSwitch,
103
+ useSitePlanAvailable,
104
+ } from '../../lib/floorplan/site-plan/drawing-type-switch'
105
+ import { FloorplanSitePlanLayer } from '../../lib/floorplan/site-plan/site-plan-layer'
94
106
  import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid-event-point'
95
107
  import type { EditorGridEvent } from '../../lib/grid-event-presentation'
96
108
  import { groundHeightAt } from '../../lib/ground-surface'
97
109
  import { guideEmitter } from '../../lib/guide-events'
98
110
  import {
99
111
  acceptsKeyboardPan,
112
+ advanceKeyboardPanMotion,
100
113
  clearKeyboardPanKeys,
114
+ createKeyboardPanMotion,
101
115
  isKeyboardPanKey,
102
116
  type KeyboardPanState,
103
- keyboardPanDirection,
104
117
  keyboardPanSpeed,
118
+ resetKeyboardPanMotion,
105
119
  setKeyboardPanKey,
120
+ syncKeyboardPanMotion,
106
121
  } from '../../lib/keyboard-pan'
107
122
  import { measurementHint, parseMeasurement } from '../../lib/measurement-parser'
108
123
  import { formatLinearMeasurement, linearUnitToMeters } from '../../lib/measurements'
124
+ import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap'
125
+ import { selectRoomFromHit } from '../../lib/room-selection-commands'
126
+ import { enterBuildingFromPlanHit } from '../../lib/selection-routing'
109
127
  import { sfxEmitter } from '../../lib/sfx-bus'
110
128
  import { SITE_BOUNDARY_DRAG_LABEL, siteBoundaryHandlesEnabled } from '../../lib/site-boundary'
111
129
  import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap'
112
- import { cancelPendingZonePaint, focusedUnitNode, paintZoneMembership } from '../../lib/units'
130
+ import {
131
+ cancelPendingZonePaint,
132
+ focusedUnitNode,
133
+ paintZoneMembership,
134
+ zoneAtLevelPoint,
135
+ } from '../../lib/units'
113
136
  import { cn } from '../../lib/utils'
137
+ import { framingAnimates } from '../../lib/viewer-selection'
138
+ import { getWallDrawVariant } from '../../lib/wall-draw-variant'
139
+ import {
140
+ addWallPolygonDraftCorner,
141
+ commitWallPolygonDraft,
142
+ discardWallPolygonDraft,
143
+ startWallPolygonDraft,
144
+ wallPolygonDraftWalls,
145
+ } from '../../lib/wall-polygon-draft'
114
146
  import { snapBuildingLocalToWorldGrid } from '../../lib/world-grid-snap'
147
+ import { nextZoneName } from '../../lib/zone-name'
115
148
  import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
116
149
  import useAlignmentGuides from '../../store/use-alignment-guides'
150
+ import useDrawingView from '../../store/use-drawing-view'
117
151
  import type { GuideUiState, NavigationSyncPose } from '../../store/use-editor'
118
152
  import useEditor, {
119
153
  isAngleSnapActive,
@@ -132,11 +166,19 @@ import useInteractionScope, {
132
166
  import usePlacementPreview from '../../store/use-placement-preview'
133
167
  import { expandSessionSelectionForNode } from '../../store/use-session-groups'
134
168
  import { useStairBuildPreview } from '../../store/use-stair-build-preview'
169
+ import {
170
+ activeViewLayout,
171
+ isViewVisible,
172
+ VIEW_2D,
173
+ VIEW_3D,
174
+ visibleScene,
175
+ } from '../../store/view-layout'
135
176
  import { FloorplanAlignmentGuideLayer } from '../editor-2d/floorplan-alignment-guide-layer'
136
177
  import { FloorplanCursorIndicatorOverlay as Editor2dFloorplanCursorIndicatorOverlay } from '../editor-2d/floorplan-cursor-indicator-overlay'
137
178
  import { FloorplanGroupActionMenu } from '../editor-2d/floorplan-group-action-menu'
138
179
  import { FloorplanSiteKeyHandler } from '../editor-2d/floorplan-hotkey-handlers'
139
180
  import { FloorplanMeasurementToolLayer } from '../editor-2d/floorplan-measurement-tool-layer'
181
+ import { FloorplanOpenWallEndsLayer } from '../editor-2d/floorplan-open-wall-ends-layer'
140
182
  import { FloorplanRegisteredToolLayer } from '../editor-2d/floorplan-registered-tool-layer'
141
183
  import { FloorplanRegistryActionMenu } from '../editor-2d/floorplan-registry-action-menu'
142
184
  import { FloorplanRegistryMoveOverlay } from '../editor-2d/floorplan-registry-move-overlay'
@@ -147,7 +189,7 @@ import {
147
189
  import { FloorplanSnapBeaconLayer } from '../editor-2d/floorplan-snap-beacon-layer'
148
190
  import { FloorplanWallMoveGhostLayer } from '../editor-2d/floorplan-wall-move-ghost-layer'
149
191
  import { FloorplanDraftLayer } from '../editor-2d/renderers/floorplan-draft-layer'
150
- import { FloorplanDraftWallMeasurement } from '../editor-2d/renderers/floorplan-draft-wall-measurement'
192
+ import { FloorplanDraftMeasurement } from '../editor-2d/renderers/floorplan-draft-measurement'
151
193
  import { FloorplanGeometryRenderer } from '../editor-2d/renderers/floorplan-geometry-renderer'
152
194
  import { FloorplanMarqueeLayer } from '../editor-2d/renderers/floorplan-marquee-layer'
153
195
  import { FloorplanPlacementPreviewLayer } from '../editor-2d/renderers/floorplan-placement-preview-layer'
@@ -155,10 +197,8 @@ import {
155
197
  FloorplanRegistryLayer,
156
198
  RotationAngleOverlay,
157
199
  } from '../editor-2d/renderers/floorplan-registry-layer'
158
- import { FloorplanStairLayer } from '../editor-2d/renderers/floorplan-stair-layer'
159
200
  import { FloorplanVoronoiLayer } from '../editor-2d/renderers/floorplan-voronoi-layer'
160
- import { buildSvgPolylinePath, formatPolygonPath, getArcPlanPoint } from '../editor-2d/svg-paths'
161
- import { snapFenceDraftPoint } from '../tools/fence/fence-drafting'
201
+ import { buildSvgPolylinePath, formatPolygonPath } from '../editor-2d/svg-paths'
162
202
  import { snapToHalf } from '../tools/item/placement-math'
163
203
  import {
164
204
  isBoxSelectPointerSuppressed,
@@ -188,15 +228,6 @@ import {
188
228
  getAngleToSegmentReference,
189
229
  getSegmentAngleReferenceAtPoint,
190
230
  } from '../tools/shared/segment-angle'
191
- import {
192
- DEFAULT_STAIR_ATTACHMENT_SIDE,
193
- DEFAULT_STAIR_FILL_TO_FLOOR,
194
- DEFAULT_STAIR_HEIGHT,
195
- DEFAULT_STAIR_LENGTH,
196
- DEFAULT_STAIR_STEP_COUNT,
197
- DEFAULT_STAIR_THICKNESS,
198
- DEFAULT_STAIR_WIDTH,
199
- } from '../tools/stair/stair-defaults'
200
231
  import {
201
232
  chainEndJoinsExistingWall,
202
233
  createWallOnCurrentLevel,
@@ -208,8 +239,6 @@ import {
208
239
  WALL_JOIN_SNAP_RADIUS,
209
240
  type WallPlanPoint,
210
241
  } from '../tools/wall/wall-drafting'
211
-
212
- import { PALETTE_COLORS } from '../ui/primitives/color-dot'
213
242
  import { FloorplanCompassButton } from '../viewer/floorplan-compass-button'
214
243
  import { resolveFloorplanBackgroundSelection } from './floorplan-background-selection'
215
244
  import {
@@ -232,6 +261,7 @@ import {
232
261
  import { useFloorplanBackgroundPlacement } from './use-floorplan-background-placement'
233
262
  import { useFloorplanHitTesting } from './use-floorplan-hit-testing'
234
263
  import { useFloorplanSceneData } from './use-floorplan-scene-data'
264
+ import { sceneSpan } from './views/scene-region'
235
265
 
236
266
  const FALLBACK_VIEW_SIZE = 12
237
267
  const FLOORPLAN_PADDING = 2
@@ -282,7 +312,6 @@ const FLOORPLAN_HOVER_TRANSITION = 'opacity 180ms cubic-bezier(0.2, 0, 0, 1)'
282
312
  const FLOORPLAN_WALL_HIT_STROKE_WIDTH = 18
283
313
  const FLOORPLAN_WALL_STROKE_WIDTH = '1'
284
314
  const FLOORPLAN_OPENING_HIT_STROKE_WIDTH = 16
285
- const noopFloorplanStairHandler = () => {}
286
315
  const FLOORPLAN_OPENING_STROKE_WIDTH = 0.05
287
316
  const FLOORPLAN_ENDPOINT_HIT_STROKE_WIDTH = 18
288
317
  const FLOORPLAN_ENDPOINT_HOVER_GLOW_STROKE_WIDTH = 16
@@ -2124,68 +2153,6 @@ function movePlanPointTowards(start: Point2D, end: Point2D, distance: number): P
2124
2153
  return interpolatePlanPoint(start, end, Math.min(1, distance / totalDistance))
2125
2154
  }
2126
2155
 
2127
- function getNormalizedFloorplanStairSweepAngle(stair: StairNode) {
2128
- const stairType = stair.stairType ?? 'straight'
2129
- const baseSweepAngle = stair.sweepAngle ?? (stairType === 'spiral' ? Math.PI * 2 : Math.PI / 2)
2130
-
2131
- if (Math.abs(baseSweepAngle) >= Math.PI * 2) {
2132
- return Math.sign(baseSweepAngle || 1) * (Math.PI * 2 - 0.001)
2133
- }
2134
-
2135
- return baseSweepAngle
2136
- }
2137
-
2138
- function getFloorplanSpiralLandingSweep(stair: StairNode, sweepAngle: number) {
2139
- if (
2140
- (stair.stairType ?? 'straight') !== 'spiral' ||
2141
- (stair.topLandingMode ?? 'none') !== 'integrated'
2142
- ) {
2143
- return 0
2144
- }
2145
-
2146
- const innerRadius = Math.max(0.05, stair.innerRadius ?? 0.9)
2147
- const width = Math.max(stair.width ?? 1, 0.4)
2148
- const landingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max(width * 0.9, 0.8))
2149
-
2150
- return (
2151
- Math.min(Math.PI * 0.75, landingDepth / Math.max(innerRadius + width / 2, 0.1)) *
2152
- Math.sign(sweepAngle || 1)
2153
- )
2154
- }
2155
-
2156
- function getFloorplanCurvedStairHitPolygon(stair: StairNode): Point2D[] {
2157
- const stairType = stair.stairType ?? 'straight'
2158
- const sweepAngle = getNormalizedFloorplanStairSweepAngle(stair)
2159
- const startAngle = -stair.rotation - sweepAngle / 2
2160
- const endAngle = startAngle + sweepAngle + getFloorplanSpiralLandingSweep(stair, sweepAngle)
2161
- const center = {
2162
- x: stair.position[0],
2163
- y: stair.position[2],
2164
- }
2165
- const innerRadius = Math.max(
2166
- stairType === 'spiral' ? 0.05 : 0.2,
2167
- stair.innerRadius ?? (stairType === 'spiral' ? 0.2 : 0.9),
2168
- )
2169
- const outerRadius = innerRadius + stair.width
2170
- const outerArcLength = Math.abs(sweepAngle) * outerRadius
2171
- const segmentCount = Math.max(
2172
- 24,
2173
- Math.ceil(Math.abs(sweepAngle) / (Math.PI / 24)),
2174
- Math.ceil(outerArcLength / 0.14),
2175
- )
2176
- const outerPoints: Point2D[] = []
2177
- const innerPoints: Point2D[] = []
2178
-
2179
- for (let index = 0; index <= segmentCount; index += 1) {
2180
- const t = index / segmentCount
2181
- const angle = startAngle + (endAngle - startAngle) * t
2182
- outerPoints.push(getArcPlanPoint(center, outerRadius, angle))
2183
- innerPoints.push(getArcPlanPoint(center, innerRadius, angle))
2184
- }
2185
-
2186
- return [...outerPoints, ...innerPoints.reverse()]
2187
- }
2188
-
2189
2156
  function isPointInsidePolygonWithHoles(
2190
2157
  point: Point2D,
2191
2158
  polygon: Point2D[],
@@ -4226,107 +4193,57 @@ const FloorplanPolygonHandleLayer = memo(function FloorplanPolygonHandleLayer({
4226
4193
  )
4227
4194
  })
4228
4195
 
4229
- // Static segment for the in-flight stair build preview. No per-render
4230
- // dependency (the geometry only moves / rotates), so it lives at module scope
4231
- // instead of a `useMemo`.
4232
- const FLOORPLAN_PREVIEW_STAIR_SEGMENT = StairSegmentNodeSchema.parse({
4233
- id: 'sseg_floorplan_preview',
4234
- segmentType: 'stair',
4235
- width: DEFAULT_STAIR_WIDTH,
4236
- length: DEFAULT_STAIR_LENGTH,
4237
- height: DEFAULT_STAIR_HEIGHT,
4238
- stepCount: DEFAULT_STAIR_STEP_COUNT,
4239
- attachmentSide: DEFAULT_STAIR_ATTACHMENT_SIDE,
4240
- fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
4241
- thickness: DEFAULT_STAIR_THICKNESS,
4242
- position: [0, 0, 0],
4243
- metadata: { isTransient: true, isFloorplanPreview: true },
4244
- })
4245
-
4246
- const EMPTY_FLOORPLAN_ID_SET: ReadonlySet<string> = new Set()
4247
-
4248
- type FloorplanStairLayerPalette = ComponentProps<typeof FloorplanStairLayer>['palette']
4249
-
4250
- // Leaf layer for the stair tool's in-flight 2D build preview. Subscribes to the
4251
- // `useStairBuildPreview` store directly so a per-`grid:move` point update (or an
4252
- // R/T rotation) re-renders ONLY this tiny layer — never the ~120-220ms
4253
- // `FloorplanPanel`. This mirrors how column / elevator placement stays smooth by
4254
- // routing preview state through a store + leaf. Committed stairs render through
4255
- // `FloorplanRegistryLayer`; this layer is non-interactive (noop handlers, empty
4256
- // hit sets), so it never participates in hover / select.
4257
4196
  function FloorplanStairBuildPreviewLayer({
4258
4197
  palette,
4259
- isDeleteMode,
4260
4198
  }: {
4261
- palette: FloorplanStairLayerPalette
4262
- isDeleteMode: boolean
4199
+ palette: FloorplanRenderContextValue['palette']
4263
4200
  }) {
4264
- const phase = useEditor((s) => s.phase)
4265
4201
  const mode = useEditor((s) => s.mode)
4266
4202
  const tool = useEditor((s) => s.tool)
4267
4203
  const point = useStairBuildPreview((s) => s.point)
4268
4204
  const rotation = useStairBuildPreview((s) => s.rotation)
4269
- const isActive = phase === 'structure' && mode === 'build' && tool === 'stair'
4205
+ const rise = useStairBuildPreview((s) => s.rise)
4206
+ const length = useStairBuildPreview((s) => s.length)
4207
+ const stepCount = useStairBuildPreview((s) => s.stepCount)
4208
+ const isActive = mode === 'build' && tool === 'stair'
4270
4209
 
4271
- const previewEntry = useMemo(() => {
4272
- if (!(isActive && point)) {
4210
+ const previewGeometry = useMemo(() => {
4211
+ if (!(isActive && point && rise && rise > 0)) {
4273
4212
  return null
4274
4213
  }
4214
+ const previewSegment = {
4215
+ ...createSizedStairFlight(rise),
4216
+ ...(length !== null ? { length } : {}),
4217
+ ...(stepCount !== null ? { stepCount } : {}),
4218
+ id: 'sseg_floorplan_preview' as const,
4219
+ parentId: 'stair_floorplan_preview' as const,
4220
+ }
4275
4221
  const previewStair = StairNodeSchema.parse({
4276
4222
  id: 'stair_floorplan_preview',
4277
4223
  name: 'Staircase preview',
4278
4224
  position: [point[0], 0, point[1]],
4279
4225
  rotation,
4280
- children: [FLOORPLAN_PREVIEW_STAIR_SEGMENT.id],
4226
+ children: [previewSegment.id],
4281
4227
  metadata: { isTransient: true, isFloorplanPreview: true },
4282
4228
  })
4283
- const entry = buildSharedFloorplanStairEntry(previewStair, [FLOORPLAN_PREVIEW_STAIR_SEGMENT])
4284
- if (!entry) {
4285
- return null
4286
- }
4287
- const hitPolygons =
4288
- (previewStair.stairType ?? 'straight') === 'straight'
4289
- ? entry.segments.map((segmentEntry) => segmentEntry.polygon)
4290
- : [getFloorplanCurvedStairHitPolygon(previewStair)]
4291
-
4292
- return {
4293
- ...entry,
4294
- hitPolygons,
4295
- segments: entry.segments.map((segmentEntry) => ({
4296
- ...segmentEntry,
4297
- innerPoints: formatPolygonPoints(segmentEntry.innerPolygon),
4298
- points: formatPolygonPoints(segmentEntry.polygon),
4299
- treadBars: segmentEntry.treadBars.map((polygon) => ({
4300
- points: formatPolygonPoints(polygon),
4301
- polygon,
4302
- })),
4303
- })),
4304
- }
4305
- }, [isActive, point, rotation])
4306
-
4307
- if (!previewEntry) {
4308
- return null
4309
- }
4229
+ const nodes = { [previewStair.id]: previewStair, [previewSegment.id]: previewSegment }
4230
+ return (
4231
+ nodeRegistry.get(previewStair.type)?.floorplan?.(
4232
+ previewStair,
4233
+ buildFloorplanContext(previewStair, nodes, {
4234
+ selected: false,
4235
+ highlighted: false,
4236
+ hovered: false,
4237
+ moving: true,
4238
+ unit: 'metric',
4239
+ palette,
4240
+ }),
4241
+ ) ?? null
4242
+ )
4243
+ }, [isActive, point, rotation, rise, length, stepCount, palette])
4310
4244
 
4311
- return (
4312
- <FloorplanStairLayer
4313
- canFocusStairs={false}
4314
- canSelectStairs={false}
4315
- cursor={EDITOR_CURSOR}
4316
- highlightedIdSet={EMPTY_FLOORPLAN_ID_SET}
4317
- hitStrokeWidth={FLOORPLAN_OPENING_HIT_STROKE_WIDTH}
4318
- hoveredStairId={null}
4319
- isDeleteMode={isDeleteMode}
4320
- onStairDoubleClick={noopFloorplanStairHandler}
4321
- onStairHoverChange={noopFloorplanStairHandler}
4322
- onStairHoverEnter={noopFloorplanStairHandler}
4323
- onStairPointerDown={noopFloorplanStairHandler}
4324
- onStairSelect={noopFloorplanStairHandler}
4325
- palette={palette}
4326
- selectedIdSet={EMPTY_FLOORPLAN_ID_SET}
4327
- stairEntries={[previewEntry]}
4328
- />
4329
- )
4245
+ if (!previewGeometry) return null
4246
+ return <FloorplanGeometryRenderer geometry={previewGeometry} pointerEventsOverride="none" />
4330
4247
  }
4331
4248
 
4332
4249
  // Leaf overlay for the cursor-following draft preview: the cursor crosshair plus
@@ -4530,6 +4447,7 @@ function FloorplanLinearDraftLayer({
4530
4447
  const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd)
4531
4448
  const roofDraftQuarterTurn = useFloorplanDraftPreview((s) => s.roofDraftQuarterTurn)
4532
4449
  const wallRectangleDraftStart = useFloorplanDraftPreview((s) => s.wallRectangleDraftStart)
4450
+ const wallPolygonDraftPoints = useFloorplanDraftPreview((s) => s.wallPolygonDraftPoints)
4533
4451
  // The live cursor is the rectangle's opposite corner; select it only while a
4534
4452
  // rectangle draft is open so idle moves don't re-render this layer.
4535
4453
  const wallRectangleDraftEnd = useFloorplanDraftPreview((s) =>
@@ -4599,6 +4517,20 @@ function FloorplanLinearDraftLayer({
4599
4517
  wallRectangleDraftStart,
4600
4518
  ])
4601
4519
 
4520
+ // An open Polygon room's placed sides — draft state, drawn like the
4521
+ // rectangle draft (mitered together) until the polygon is written.
4522
+ const polygonRoomDraft = useMemo(() => {
4523
+ if (!(levelId && isWallBuildActive && wallPolygonDraftPoints.length >= 2)) return null
4524
+ const draftWalls = wallPolygonDraftPoints.slice(1).map((end, index) =>
4525
+ getSharedFloorplanWall({
4526
+ ...buildDraftWall(levelId, wallPolygonDraftPoints[index]!, end),
4527
+ id: `wall_polygon_draft_${index}` as WallNode['id'],
4528
+ }),
4529
+ )
4530
+ const miterData = calculateLevelMiters(draftWalls)
4531
+ return draftWalls.map((wall) => formatPolygonPoints(getWallPlanFootprint(wall, miterData)))
4532
+ }, [isWallBuildActive, levelId, wallPolygonDraftPoints])
4533
+
4602
4534
  const draftPolygonPoints = useMemo(() => {
4603
4535
  if (isRoofBuildActive && roofDraftStart && roofDraftEnd) {
4604
4536
  const minX = Math.min(roofDraftStart[0], roofDraftEnd[0])
@@ -4831,7 +4763,7 @@ function FloorplanLinearDraftLayer({
4831
4763
  )}
4832
4764
 
4833
4765
  {draftWallMeasurement && (
4834
- <FloorplanDraftWallMeasurement
4766
+ <FloorplanDraftMeasurement
4835
4767
  labelBackground={isDark ? '#0f172a' : '#ffffff'}
4836
4768
  labelText={isDark ? '#e2e8f0' : '#171717'}
4837
4769
  measurement={draftWallMeasurement}
@@ -4841,6 +4773,22 @@ function FloorplanLinearDraftLayer({
4841
4773
  />
4842
4774
  )}
4843
4775
 
4776
+ {polygonRoomDraft?.map((points, index) => (
4777
+ <FloorplanDraftLayer
4778
+ anchorFill={draftStroke}
4779
+ draftAnchorPoints={EMPTY_DRAFT_ANCHOR_POINTS}
4780
+ draftFill={draftFill}
4781
+ draftPolygonPoints={points}
4782
+ draftStroke={draftStroke}
4783
+ key={`polygon-room-${index}`}
4784
+ linearDraftSegment={null}
4785
+ polygonDraftClosingSegment={null}
4786
+ polygonDraftPolygonPoints={null}
4787
+ polygonDraftPolylinePoints={null}
4788
+ unitsPerPixel={unitsPerPixel}
4789
+ />
4790
+ ))}
4791
+
4844
4792
  {rectangleDraft?.polygons.map((points, index) => (
4845
4793
  <FloorplanDraftLayer
4846
4794
  anchorFill={draftStroke}
@@ -4858,7 +4806,7 @@ function FloorplanLinearDraftLayer({
4858
4806
  ))}
4859
4807
 
4860
4808
  {rectangleDraft?.measurements.map((measurement, index) => (
4861
- <FloorplanDraftWallMeasurement
4809
+ <FloorplanDraftMeasurement
4862
4810
  key={index}
4863
4811
  labelBackground={isDark ? '#0f172a' : '#ffffff'}
4864
4812
  labelText={isDark ? '#e2e8f0' : '#171717'}
@@ -4962,6 +4910,12 @@ export function FloorplanPanel({
4962
4910
  const unit = useViewer((state) => state.unit)
4963
4911
  const metricNotation = useViewer((state) => state.metricNotation)
4964
4912
  const showGrid = useViewer((state) => state.showGrid)
4913
+ // Floor plan vs. site plan. Site plan hides the level geometry and draws
4914
+ // the lot instead — see `lib/floorplan/site-plan/site-plan-layer.tsx`. A
4915
+ // persisted site-plan choice on a scene with no lot reads as the floor plan.
4916
+ const sitePlanAvailable = useSitePlanAvailable()
4917
+ const storedDrawingType = useDrawingView((state) => state.drawingType)
4918
+ const activeDrawingType = sitePlanAvailable ? storedDrawingType : 'floor-plan'
4965
4919
  const showGuides = useViewer((state) => state.showGuides)
4966
4920
  const setShowGuides = useViewer((state) => state.setShowGuides)
4967
4921
  const selectedItem = useEditor((state) => state.selectedItem)
@@ -4971,8 +4925,21 @@ export function FloorplanPanel({
4971
4925
  // editor/index.tsx — subscribing here lets us re-fit the viewport when
4972
4926
  // the user closes and re-opens the 2D editor instead of restoring the
4973
4927
  // stale viewport from before they closed it.
4974
- const isFloorplanOpen = useEditor((state) => state.isFloorplanOpen)
4975
- const viewMode = useEditor((state) => state.viewMode)
4928
+ const isFloorplanOpen = useEditor((state) => isViewVisible(state, VIEW_2D))
4929
+ // The compass sits at the scene's bottom-left corner, the drawing switch
4930
+ // beside it. With the plan in the right pane both move to its bottom-right
4931
+ // corner (compass outermost), clear of the dock centred over the scene.
4932
+ const planOnRight = useEditor((state) => {
4933
+ const layout = activeViewLayout(state)
4934
+ return layout.split && layout.panes[1] === VIEW_2D
4935
+ })
4936
+ // `null` while no scene view is on screen: the compass has nothing to orient.
4937
+ const sceneLeft = useEditor((state) => sceneSpan(activeViewLayout(state))?.left ?? null)
4938
+ // Mirrored, the pair stays left of the helper toggle in the stage's corner.
4939
+ const compassStyle: CSSProperties = planOnRight
4940
+ ? { right: 56 }
4941
+ : { left: `calc(${(sceneLeft ?? 0) * 100}% + 12px)` }
4942
+ const shownScene = useEditor(visibleScene)
4976
4943
  // Mirror for callbacks that fire outside React's render (the per-frame
4977
4944
  // navigation-pose subscriber): when the 2D panel is hidden (`display:none` in
4978
4945
  // 3D mode) it must NOT re-render on every camera-zoom frame.
@@ -4990,8 +4957,6 @@ export function FloorplanPanel({
4990
4957
  const activeHandleDrag = useActiveHandleDrag()
4991
4958
  const setPhase = useEditor((state) => state.setPhase)
4992
4959
  const setMovingNode = useEditor((state) => state.setMovingNode)
4993
- const structureLayer = useEditor((state) => state.structureLayer)
4994
- const setStructureLayer = useEditor((state) => state.setStructureLayer)
4995
4960
  const setTool = useEditor((state) => state.setTool)
4996
4961
  const tool = useEditor((state) => state.tool)
4997
4962
  const deleteNode = useScene((state) => state.deleteNode)
@@ -5273,12 +5238,6 @@ export function FloorplanPanel({
5273
5238
  height: number
5274
5239
  } | null>(null)
5275
5240
 
5276
- useEffect(() => {
5277
- if (structureLayer === 'zones' && floorplanSelectionTool === 'marquee') {
5278
- setFloorplanSelectionTool('click')
5279
- }
5280
- }, [floorplanSelectionTool, setFloorplanSelectionTool, structureLayer])
5281
-
5282
5241
  useEffect(() => {
5283
5242
  setIsMacPlatform(navigator.platform.toUpperCase().includes('MAC'))
5284
5243
  }, [])
@@ -5792,21 +5751,21 @@ export function FloorplanPanel({
5792
5751
  )
5793
5752
 
5794
5753
  const isSiteEditActive = phase === 'site'
5795
- const isWallBuildActive = phase === 'structure' && mode === 'build' && tool === 'wall'
5796
- const isSlabBuildActive = phase === 'structure' && mode === 'build' && tool === 'slab'
5797
- const isCeilingBuildActive = phase === 'structure' && mode === 'build' && tool === 'ceiling'
5798
- const isZoneBuildActive = phase === 'structure' && mode === 'build' && tool === 'zone'
5799
- const isDoorBuildActive = phase === 'structure' && mode === 'build' && tool === 'door'
5800
- const isWindowBuildActive = phase === 'structure' && mode === 'build' && tool === 'window'
5754
+ const isWallBuildActive = mode === 'build' && tool === 'wall'
5755
+ const isSlabBuildActive = mode === 'build' && tool === 'slab'
5756
+ const isCeilingBuildActive = mode === 'build' && tool === 'ceiling'
5757
+ const isZoneBuildActive = mode === 'build' && tool === 'zone'
5758
+ const isDoorBuildActive = mode === 'build' && tool === 'door'
5759
+ const isWindowBuildActive = mode === 'build' && tool === 'window'
5801
5760
  const isPolygonBuildActive = isSlabBuildActive || isZoneBuildActive
5802
5761
  const isPolygonDraftBuildActive = isPolygonBuildActive || isCeilingBuildActive
5803
5762
  const isOpeningBuildActive = isDoorBuildActive || isWindowBuildActive
5804
5763
  const isOpeningMoveActive = movingOpeningType !== null
5805
5764
  const isOpeningPlacementActive = isOpeningBuildActive || isOpeningMoveActive
5806
- const isFenceBuildActive = phase === 'structure' && mode === 'build' && tool === 'fence'
5765
+ const isFenceBuildActive = mode === 'build' && tool === 'fence'
5807
5766
  const fenceContinuation = useEditor((state) => state.continuationByContext.fence)
5808
- const isRoofBuildActive = phase === 'structure' && mode === 'build' && tool === 'roof'
5809
- const isStairBuildActive = phase === 'structure' && mode === 'build' && tool === 'stair'
5767
+ const isRoofBuildActive = mode === 'build' && tool === 'roof'
5768
+ const isStairBuildActive = mode === 'build' && tool === 'stair'
5810
5769
  const isStairMoveActive = movingNode?.type === 'stair'
5811
5770
  const isRoofMoveActive = movingNode?.type === 'roof' || movingNode?.type === 'roof-segment'
5812
5771
  const isSlabMoveActive = movingNode?.type === 'slab'
@@ -5932,12 +5891,11 @@ export function FloorplanPanel({
5932
5891
  mode === 'select' &&
5933
5892
  floorplanSelectionTool === 'marquee' &&
5934
5893
  !movingNode &&
5935
- !isFenceEndpointMoveActive &&
5936
- structureLayer !== 'zones'
5894
+ !isFenceEndpointMoveActive
5937
5895
  const isScreenSelectionToolActive =
5938
5896
  mode === 'select' &&
5939
5897
  floorplanSelectionTool === 'click' &&
5940
- (phase === 'structure' || phase === 'furnish') &&
5898
+ phase === 'building' &&
5941
5899
  !movingNode &&
5942
5900
  !isFenceEndpointMoveActive &&
5943
5901
  !referenceScaleDraft &&
@@ -5947,8 +5905,7 @@ export function FloorplanPanel({
5947
5905
  mode === 'select' &&
5948
5906
  floorplanSelectionTool === 'click' &&
5949
5907
  !movingNode &&
5950
- !isFenceEndpointMoveActive &&
5951
- structureLayer !== 'zones'
5908
+ !isFenceEndpointMoveActive
5952
5909
  const canInteractElementFloorplanGeometry = isDeleteMode || canSelectElementFloorplanGeometry
5953
5910
  const canInteractFloorplanSlabs = isDeleteMode || canSelectElementFloorplanGeometry
5954
5911
  const canInteractWithGuides =
@@ -5960,42 +5917,7 @@ export function FloorplanPanel({
5960
5917
  mode === 'select' &&
5961
5918
  floorplanSelectionTool === 'click' &&
5962
5919
  !movingNode &&
5963
- !isFenceEndpointMoveActive &&
5964
- structureLayer === 'zones'
5965
- const canInteractFloorplanZones = isDeleteMode || canSelectFloorplanZones
5966
- const isFloorplanStructureContextActive = phase === 'structure' && structureLayer !== 'zones'
5967
- const isFloorplanFurnishContextActive = phase === 'furnish'
5968
- const isFloorplanItemContextActive =
5969
- isFloorplanFurnishContextActive || isFloorplanStructureContextActive
5970
- const canSelectFloorplanStairs =
5971
- (mode === 'select' &&
5972
- floorplanSelectionTool === 'click' &&
5973
- !movingNode &&
5974
- !isFenceEndpointMoveActive &&
5975
- isFloorplanStructureContextActive) ||
5976
- isDeleteMode
5977
- const canSelectFloorplanElevators = canSelectFloorplanStairs
5978
- const canSelectFloorplanSpawns = canSelectFloorplanStairs
5979
- const canSelectFloorplanItems =
5980
- (mode === 'select' &&
5981
- floorplanSelectionTool === 'click' &&
5982
- !movingNode &&
5983
- !isFenceEndpointMoveActive &&
5984
- isFloorplanItemContextActive) ||
5985
- isDeleteMode
5986
- const canFocusFloorplanStairs =
5987
- mode === 'select' &&
5988
- floorplanSelectionTool === 'click' &&
5989
- !movingNode &&
5990
- !isFenceEndpointMoveActive &&
5991
- isFloorplanStructureContextActive
5992
- const canFocusFloorplanSpawns = canFocusFloorplanStairs
5993
- const canFocusFloorplanItems =
5994
- mode === 'select' &&
5995
- floorplanSelectionTool === 'click' &&
5996
- !movingNode &&
5997
- !isFenceEndpointMoveActive &&
5998
- isFloorplanItemContextActive
5920
+ !isFenceEndpointMoveActive
5999
5921
  const visibleSitePolygon = displaySitePolygon
6000
5922
  const canUseSiteBoundaryVertexHandles =
6001
5923
  visibleSitePolygon !== null && siteBoundaryHandlesEnabled({ mode, phase })
@@ -6772,6 +6694,58 @@ export function FloorplanPanel({
6772
6694
  stopFloorplanViewAnimation,
6773
6695
  ])
6774
6696
 
6697
+ // A frame request ("show me this node") moves the 3D camera and the plan follows its pose; with no
6698
+ // 3D view on screen no pose reaches the plan, so it frames the node itself. The request waits for
6699
+ // the selection it came with to render: a node on another floor or in another building is placed
6700
+ // with that building's transform, after the floor change has reset the view.
6701
+ const [pendingFrame, setPendingFrame] = useState<CameraControlFrameEvent | null>(null)
6702
+ useEffect(() => {
6703
+ const frame = (event: CameraControlFrameEvent) => {
6704
+ if (isFloorplanOpenRef.current && !isViewVisible(useEditor.getState(), VIEW_3D))
6705
+ setPendingFrame(event)
6706
+ }
6707
+ emitter.on('camera-controls:frame', frame)
6708
+ return () => emitter.off('camera-controls:frame', frame)
6709
+ }, [])
6710
+
6711
+ useEffect(() => {
6712
+ if (!pendingFrame) return
6713
+ setPendingFrame(null)
6714
+ const userRotationDeg = latestFloorplanUserRotationDegRef.current
6715
+ const toLocal = (x: number, z: number) =>
6716
+ worldToFloorplanLocalPoint(x, z, buildingPosition, buildingRotationY)
6717
+ const { bounds, pose } = pendingFrame
6718
+ let centre: SvgPoint
6719
+ let width: number | undefined
6720
+ if (bounds) {
6721
+ // The box's corners as the plan shows them now, so it fits the pane at any turn and aspect.
6722
+ const sceneRotationDeg = FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
6723
+ const shown = [bounds.min[0], bounds.max[0]].flatMap((x) =>
6724
+ [bounds.min[2], bounds.max[2]].map((z) => rotateSvgPoint(toLocal(x, z), sceneRotationDeg)),
6725
+ )
6726
+ const xs = shown.map((point) => point.x)
6727
+ const ys = shown.map((point) => point.y)
6728
+ centre = toLocal((bounds.min[0] + bounds.max[0]) / 2, (bounds.min[2] + bounds.max[2]) / 2)
6729
+ width = Math.max(
6730
+ Math.max(...xs) - Math.min(...xs) + FLOORPLAN_PADDING * 2,
6731
+ (Math.max(...ys) - Math.min(...ys) + FLOORPLAN_PADDING * 2) * svgAspectRatio,
6732
+ )
6733
+ } else if (pose) {
6734
+ // A saved view with nothing to measure: its target, at the zoom the person has.
6735
+ centre = toLocal(pose.target[0], pose.target[2])
6736
+ } else return
6737
+ // A floor change has just reset the view to the whole floor: glide from there, not from nothing.
6738
+ latestViewportRef.current ??= latestFittedViewportRef.current
6739
+ applyFloorplanNavigationView(centre, userRotationDeg, width, { smooth: framingAnimates() })
6740
+ }, [
6741
+ applyFloorplanNavigationView,
6742
+ buildingPosition,
6743
+ buildingRotationDeg,
6744
+ buildingRotationY,
6745
+ pendingFrame,
6746
+ svgAspectRatio,
6747
+ ])
6748
+
6775
6749
  const viewBox = useMemo(() => {
6776
6750
  const currentViewport = viewport ?? fittedViewport
6777
6751
  const width = currentViewport.width
@@ -7648,7 +7622,7 @@ export function FloorplanPanel({
7648
7622
  scheduleFloorplanZoomCommit()
7649
7623
  const userRotationDeg = latestFloorplanUserRotationDegRef.current
7650
7624
  floorplanZoomPoseRef.current = { localCenter, userRotationDeg, viewWidth: nextWidth }
7651
- if (useEditor.getState().viewMode === 'split') {
7625
+ if (visibleScene(useEditor.getState()) === 'split') {
7652
7626
  publishFloorplanNavigationPose(localCenter, userRotationDeg, nextWidth)
7653
7627
  }
7654
7628
  },
@@ -7731,28 +7705,24 @@ export function FloorplanPanel({
7731
7705
  right: false,
7732
7706
  })
7733
7707
  useEffect(() => {
7734
- if (viewMode !== '2d') return
7708
+ if (shownScene !== '2d') return
7735
7709
  const keys = keyboardPanKeysRef.current
7710
+ const motion = createKeyboardPanMotion()
7736
7711
  let frame: number | null = null
7737
- let lastTime: number | null = null
7738
7712
  const sceneRotationDeg = (userRotationDeg: number) =>
7739
7713
  FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
7740
- const step = (time: number) => {
7741
- const { horizontal, vertical } = keyboardPanDirection(keys)
7714
+ const step = () => {
7742
7715
  const viewport = latestViewportRef.current ?? latestFittedViewportRef.current
7743
- if ((horizontal === 0 && vertical === 0) || !viewport) {
7716
+ if (!(viewport && advanceKeyboardPanMotion(motion, keys, performance.now()))) {
7744
7717
  frame = null
7745
- lastTime = null
7718
+ resetKeyboardPanMotion(motion)
7746
7719
  commitFloorplanPan()
7747
7720
  return
7748
7721
  }
7749
- const elapsed = lastTime === null ? 0 : Math.min((time - lastTime) / 1000, 0.05)
7750
- lastTime = time
7751
- const distance =
7752
- (keyboardPanSpeed(viewport.width) * elapsed) / Math.hypot(horizontal, vertical)
7722
+ const speed = keyboardPanSpeed(viewport.width)
7753
7723
  const next = {
7754
- centerX: viewport.centerX + horizontal * distance,
7755
- centerY: viewport.centerY - vertical * distance,
7724
+ centerX: viewport.centerX + motion.stepX * speed,
7725
+ centerY: viewport.centerY - motion.stepY * speed,
7756
7726
  width: viewport.width,
7757
7727
  }
7758
7728
  const userRotationDeg = latestFloorplanUserRotationDegRef.current
@@ -7773,12 +7743,15 @@ export function FloorplanPanel({
7773
7743
  }
7774
7744
  const onKeyDown = (event: KeyboardEvent) => {
7775
7745
  if (!(isKeyboardPanKey(event.code) && acceptsKeyboardPan(event))) return
7746
+ syncKeyboardPanMotion(motion, keys, performance.now())
7776
7747
  if (setKeyboardPanKey(keys, event.code, true)) start()
7777
7748
  event.preventDefault()
7778
7749
  event.stopPropagation()
7779
7750
  }
7780
7751
  const onKeyUp = (event: KeyboardEvent) => {
7781
- if (isKeyboardPanKey(event.code) && setKeyboardPanKey(keys, event.code, false)) {
7752
+ if (!isKeyboardPanKey(event.code)) return
7753
+ syncKeyboardPanMotion(motion, keys, performance.now())
7754
+ if (setKeyboardPanKey(keys, event.code, false)) {
7782
7755
  event.preventDefault()
7783
7756
  event.stopPropagation()
7784
7757
  }
@@ -7828,7 +7801,7 @@ export function FloorplanPanel({
7828
7801
  commitFloorplanPan,
7829
7802
  publishFloorplanNavigationPose,
7830
7803
  smoothFloorplanNavigationView,
7831
- viewMode,
7804
+ shownScene,
7832
7805
  ])
7833
7806
 
7834
7807
  useLayoutEffect(() => {
@@ -7880,7 +7853,10 @@ export function FloorplanPanel({
7880
7853
  }
7881
7854
  }, [isFloorplanOpen, stopFloorplanViewAnimation])
7882
7855
 
7856
+ // Clearing an open wall draft drops a Polygon room in progress (nothing of
7857
+ // it was written); paths that keep it commit first.
7883
7858
  const clearWallPlacementDraft = useCallback(() => {
7859
+ discardWallPolygonDraft()
7884
7860
  setDraftStart(null)
7885
7861
  setWallChainFirstVertex(null)
7886
7862
  wallConstructionOptionsRef.current = undefined
@@ -7937,10 +7913,9 @@ export function FloorplanPanel({
7937
7913
  setHoveredSiteHandleId(null)
7938
7914
  }, [clearSiteBoundaryLivePreview])
7939
7915
  const exitSiteEditingToSelect = useCallback(() => {
7940
- setPhase('structure')
7941
- setStructureLayer('elements')
7916
+ setPhase('building')
7942
7917
  setMode('select')
7943
- }, [setMode, setPhase, setStructureLayer])
7918
+ }, [setMode, setPhase])
7944
7919
 
7945
7920
  const clearDraft = useCallback(() => {
7946
7921
  clearWallPlacementDraft()
@@ -8002,10 +7977,8 @@ export function FloorplanPanel({
8002
7977
  }
8003
7978
 
8004
7979
  const { createNode, updateNode, nodes } = useScene.getState()
8005
- const zoneCount = Object.values(nodes).filter((node) => node.type === 'zone').length
8006
- const zone = ZoneNodeSchema.parse({
8007
- color: PALETTE_COLORS[zoneCount % PALETTE_COLORS.length],
8008
- name: `Zone ${zoneCount + 1}`,
7980
+ const zone = newZone({
7981
+ name: nextZoneName(nodes),
8009
7982
  polygon: points.map(([x, z]) => [x, z] as [number, number]),
8010
7983
  })
8011
7984
 
@@ -8080,22 +8053,6 @@ export function FloorplanPanel({
8080
8053
  useStairBuildPreview.getState().reset()
8081
8054
  return
8082
8055
  }
8083
-
8084
- const handleGridMove = (event: GridEvent) => {
8085
- // Publish to the dedicated store (deduped on the snapped point), NOT panel
8086
- // state: the stair preview lives in `FloorplanStairBuildPreviewLayer`, so a
8087
- // per-move update re-renders only that tiny leaf instead of this entire
8088
- // (~200ms) panel — the same pattern that keeps column/elevator smooth.
8089
- useStairBuildPreview
8090
- .getState()
8091
- .setPoint(getSnappedFloorplanPoint([event.localPosition[0], event.localPosition[2]]))
8092
- }
8093
-
8094
- emitter.on('grid:move', handleGridMove)
8095
-
8096
- return () => {
8097
- emitter.off('grid:move', handleGridMove)
8098
- }
8099
8056
  }, [isStairBuildActive])
8100
8057
 
8101
8058
  useEffect(() => {
@@ -8287,13 +8244,13 @@ export function FloorplanPanel({
8287
8244
 
8288
8245
  if (
8289
8246
  isStairBuildActive &&
8290
- useEditor.getState().viewMode === '2d' &&
8247
+ visibleScene(useEditor.getState()) === '2d' &&
8291
8248
  (event.key === 'r' || event.key === 'R')
8292
8249
  ) {
8293
8250
  useStairBuildPreview.getState().rotateBy(Math.PI / 4)
8294
8251
  } else if (
8295
8252
  isStairBuildActive &&
8296
- useEditor.getState().viewMode === '2d' &&
8253
+ visibleScene(useEditor.getState()) === '2d' &&
8297
8254
  (event.key === 't' || event.key === 'T')
8298
8255
  ) {
8299
8256
  useStairBuildPreview.getState().rotateBy(-Math.PI / 4)
@@ -9004,7 +8961,7 @@ export function FloorplanPanel({
9004
8961
  // tools that subscribe to these grid events.
9005
8962
  const emitFloorplanGridEvent = useCallback(
9006
8963
  (
9007
- eventType: 'move' | 'click' | 'double-click',
8964
+ eventType: 'move' | 'click' | 'double-click' | 'pointerdown' | 'pointerup',
9008
8965
  planPoint: WallPlanPoint,
9009
8966
  nativeEvent: ReactMouseEvent<SVGSVGElement> | ReactPointerEvent<SVGSVGElement>,
9010
8967
  ) => {
@@ -9205,7 +9162,7 @@ export function FloorplanPanel({
9205
9162
  const nextUserRotationDeg = rotationState.initialUserRotationDeg + angleDeltaDeg
9206
9163
 
9207
9164
  applyFloorplanRotationImperatively(rotationState, nextUserRotationDeg)
9208
- if (useEditor.getState().viewMode === 'split') {
9165
+ if (visibleScene(useEditor.getState()) === 'split') {
9209
9166
  publishFloorplanNavigationPose(
9210
9167
  rotationState.viewportCenterLocal,
9211
9168
  nextUserRotationDeg,
@@ -9247,7 +9204,7 @@ export function FloorplanPanel({
9247
9204
  userRotationDeg: currentUserRotationDeg,
9248
9205
  viewWidth: currentViewport.width,
9249
9206
  }
9250
- if (useEditor.getState().viewMode === 'split') {
9207
+ if (visibleScene(useEditor.getState()) === 'split') {
9251
9208
  publishFloorplanNavigationPose(localCenter, currentUserRotationDeg, currentViewport.width)
9252
9209
  }
9253
9210
 
@@ -9339,6 +9296,14 @@ export function FloorplanPanel({
9339
9296
  }
9340
9297
 
9341
9298
  if (isFenceBuildActive) {
9299
+ if (useEditor.getState().getContinuation('fence') === 'freehand') {
9300
+ useAlignmentGuides.getState().clear()
9301
+ emitFloorplanGridEvent('move', planPoint, event)
9302
+ setCursorPoint((previousPoint) =>
9303
+ previousPoint && pointsEqual(previousPoint, planPoint) ? previousPoint : planPoint,
9304
+ )
9305
+ return
9306
+ }
9342
9307
  // Fence draft: grid snap (+ existing-wall/fence endpoint snap), then
9343
9308
  // Figma alignment — same endpoint-wins precedence as the wall branch.
9344
9309
  // While a draft is open the segment locks to 15° rays from its start.
@@ -9346,17 +9311,23 @@ export function FloorplanPanel({
9346
9311
  // there is no Shift hold-to-bypass. Alignment follows the magnetic snap
9347
9312
  // mode, not Alt (continuation is cycled through the HUD / C).
9348
9313
  const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive()
9349
- const fenceSnapped = snapFenceDraftPoint({
9350
- point: planPoint,
9351
- walls,
9352
- fences,
9353
- start: fenceDraftStart ?? undefined,
9354
- angleSnap: fenceAngleSnap,
9355
- magnetic: isMagneticSnapActive(),
9356
- })
9314
+ const fenceSnapped = snapRegisteredDraftPoint(
9315
+ 'fence',
9316
+ {
9317
+ point: planPoint,
9318
+ walls,
9319
+ fences,
9320
+ start: fenceDraftStart ?? undefined,
9321
+ angleSnap: fenceAngleSnap,
9322
+ magnetic: isMagneticSnapActive(),
9323
+ },
9324
+ planPoint,
9325
+ )
9357
9326
  const fenceGridBase = snapWallPointToGrid(planPoint)
9358
9327
  const fenceLocked =
9359
- fenceSnapped[0] !== fenceGridBase[0] || fenceSnapped[1] !== fenceGridBase[1]
9328
+ event.altKey ||
9329
+ fenceSnapped[0] !== fenceGridBase[0] ||
9330
+ fenceSnapped[1] !== fenceGridBase[1]
9360
9331
  let snappedPoint = fenceSnapped
9361
9332
  if (fenceLocked) useAlignmentGuides.getState().clear()
9362
9333
  // Alignment lines show in every mode; the pull applies only when
@@ -9542,7 +9513,7 @@ export function FloorplanPanel({
9542
9513
  const wallAngleSnap = draftStart !== null && isAngleSnapActive()
9543
9514
  const wallSnap = snapWallDraftPointDetailed({
9544
9515
  point: planPoint,
9545
- walls,
9516
+ walls: [...walls, ...wallPolygonDraftWalls()],
9546
9517
  start: draftStart ?? undefined,
9547
9518
  angleSnap: wallAngleSnap,
9548
9519
  magnetic: isMagneticSnapActive(),
@@ -9649,7 +9620,7 @@ export function FloorplanPanel({
9649
9620
  const firstPoint = slabDraftPoints[0]
9650
9621
  if (firstPoint && slabDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
9651
9622
  // 2D-only view: the 3D tool can't commit, so close the polygon here.
9652
- if (useEditor.getState().viewMode === '2d') {
9623
+ if (visibleScene(useEditor.getState()) === '2d') {
9653
9624
  createSlabOnCurrentLevel(slabDraftPoints, slabPlacementBaseRef.current)
9654
9625
  }
9655
9626
  clearDraft()
@@ -9699,7 +9670,7 @@ export function FloorplanPanel({
9699
9670
  }
9700
9671
 
9701
9672
  // 2D-only view: the 3D tool can't commit, so create the slab here.
9702
- if (useEditor.getState().viewMode === '2d') {
9673
+ if (visibleScene(useEditor.getState()) === '2d') {
9703
9674
  createSlabOnCurrentLevel(nextPoints, slabPlacementBaseRef.current)
9704
9675
  }
9705
9676
  clearDraft()
@@ -9715,7 +9686,7 @@ export function FloorplanPanel({
9715
9686
 
9716
9687
  const firstPoint = ceilingDraftPoints[0]
9717
9688
  if (firstPoint && ceilingDraftPoints.length >= 3 && isPointNearPlanPoint(point, firstPoint)) {
9718
- if (useEditor.getState().viewMode === '2d') {
9689
+ if (visibleScene(useEditor.getState()) === '2d') {
9719
9690
  createCeilingOnCurrentLevel(ceilingDraftPoints)
9720
9691
  }
9721
9692
  clearCeilingPlacementDraft()
@@ -9748,7 +9719,7 @@ export function FloorplanPanel({
9748
9719
  return
9749
9720
  }
9750
9721
 
9751
- if (useEditor.getState().viewMode === '2d') {
9722
+ if (visibleScene(useEditor.getState()) === '2d') {
9752
9723
  createCeilingOnCurrentLevel(nextPoints)
9753
9724
  }
9754
9725
  clearCeilingPlacementDraft()
@@ -9815,6 +9786,14 @@ export function FloorplanPanel({
9815
9786
  useEditor.getState().toolDefaults.wall,
9816
9787
  )
9817
9788
  : undefined
9789
+ // 2D-only owns a Polygon room's draft; split view leaves it to the 3D tool.
9790
+ if (
9791
+ levelId &&
9792
+ visibleScene(useEditor.getState()) === '2d' &&
9793
+ getWallDrawVariant() === 'polygon'
9794
+ ) {
9795
+ startWallPolygonDraft(levelId as AnyNodeId, point, wallConstructionOptionsRef.current)
9796
+ }
9818
9797
  setDraftStart(point)
9819
9798
  setWallChainFirstVertex(point)
9820
9799
  setDraftEnd(point)
@@ -9840,8 +9819,22 @@ export function FloorplanPanel({
9840
9819
  // `display:none`, so the tool never commits. Mirror the slab /
9841
9820
  // ceiling 2D-only committers: create locally here, gated on the
9842
9821
  // view, so split / 3D keep their single-owner tool commit.
9843
- const viewIs2DOnly = useEditor.getState().viewMode === '2d'
9822
+ const viewIs2DOnly = visibleScene(useEditor.getState()) === '2d'
9844
9823
  let createdWall: WallNode | null = null
9824
+ // 2D-only Polygon room: the corner joins the draft; nothing is written
9825
+ // until it closes, seals or tees (then every wall at once).
9826
+ if (viewIs2DOnly && getWallDrawVariant() === 'polygon') {
9827
+ if (addWallPolygonDraftCorner(point) !== 'open') {
9828
+ commitWallPolygonDraft()
9829
+ clearWallPlacementDraft()
9830
+ setCursorPoint(null)
9831
+ return
9832
+ }
9833
+ setDraftStart(point)
9834
+ setDraftEnd(point)
9835
+ setCursorPoint(point)
9836
+ return
9837
+ }
9845
9838
  if (viewIs2DOnly) {
9846
9839
  createdWall = createWallOnCurrentLevel(
9847
9840
  draftStart,
@@ -10045,18 +10038,25 @@ export function FloorplanPanel({
10045
10038
  return
10046
10039
  }
10047
10040
 
10041
+ if (useInteractionScope.getState().scope.kind !== 'idle') return
10048
10042
  const modifierKeys = getSelectionModifierKeys(event)
10049
10043
 
10044
+ if (enterBuildingFromPlanHit(getFloorplanHitIdAtPoint(planPoint))) return
10045
+ if (roomPickingEnabled()) {
10046
+ const hitId = getFloorplanHitIdAtPoint(planPoint)
10047
+ if (selectRoomFromHit(resolvePlanRoomHit(hitId, planPoint), modifierKeys, hitId)) return
10048
+ }
10050
10049
  const backgroundSelection = resolveFloorplanBackgroundSelection({
10051
10050
  canSelectElementFloorplanGeometry,
10052
- canSelectFloorplanZones,
10051
+ canSelectFloorplanZones: canSelectFloorplanZones && !!useViewer.getState().focusedUnitId,
10053
10052
  currentSelectedIds: useViewer.getState().selection.selectedIds,
10054
10053
  expandIdsForNode: expandSessionSelectionForNode,
10055
- getFloorplanHitIdAtPoint,
10054
+ getFloorplanHitIdAtPoint: useViewer.getState().focusedUnitId
10055
+ ? (point) => zoneAtLevelPoint(point[0], point[1])?.id ?? null
10056
+ : getFloorplanHitIdAtPoint,
10056
10057
  isWallBuildActive,
10057
10058
  modifierKeys,
10058
10059
  planPoint,
10059
- structureLayer,
10060
10060
  })
10061
10061
 
10062
10062
  if (backgroundSelection.handled) {
@@ -10104,15 +10104,14 @@ export function FloorplanPanel({
10104
10104
  }
10105
10105
 
10106
10106
  if (backgroundSelection.kind === 'clear-zones') {
10107
- setSelection({ zoneId: null })
10108
- // Return to structure select (same as 3D grid click)
10109
- useEditor.getState().setStructureLayer('elements')
10110
- useEditor.getState().setMode('select')
10107
+ useEditor.getState().clearRoom()
10108
+ setSelection({ selectedIds: [], zoneId: null })
10111
10109
  return
10112
10110
  }
10113
10111
 
10114
10112
  if (!backgroundSelection.preserveSelection) {
10115
- setSelection({ selectedIds: [] })
10113
+ useEditor.getState().clearRoom()
10114
+ setSelection({ selectedIds: [], zoneId: null })
10116
10115
  }
10117
10116
  return
10118
10117
  }
@@ -10130,7 +10129,6 @@ export function FloorplanPanel({
10130
10129
  referenceScaleDraft,
10131
10130
  setSelectedReferenceId,
10132
10131
  setSelection,
10133
- structureLayer,
10134
10132
  getFloorplanHitIdAtPoint,
10135
10133
  unit,
10136
10134
  emitFloorplanGridEvent,
@@ -10150,6 +10148,49 @@ export function FloorplanPanel({
10150
10148
  },
10151
10149
  [handleBackgroundClick],
10152
10150
  )
10151
+ const handleSvgPointerDown = useCallback(
10152
+ (event: ReactPointerEvent<SVGSVGElement>) => {
10153
+ if (
10154
+ isFenceBuildActive &&
10155
+ useEditor.getState().getContinuation('fence') === 'freehand' &&
10156
+ event.button === 0
10157
+ ) {
10158
+ const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
10159
+ if (point) {
10160
+ clearFencePlacementDraft()
10161
+ event.currentTarget.setPointerCapture(event.pointerId)
10162
+ emitFloorplanGridEvent('pointerdown', point, event)
10163
+ }
10164
+ }
10165
+ handlePointerDown(event)
10166
+ },
10167
+ [
10168
+ clearFencePlacementDraft,
10169
+ emitFloorplanGridEvent,
10170
+ getPlanPointFromClientPoint,
10171
+ handlePointerDown,
10172
+ isFenceBuildActive,
10173
+ ],
10174
+ )
10175
+ const handleSvgPointerUp = useCallback(
10176
+ (event: ReactPointerEvent<SVGSVGElement>) => {
10177
+ if (
10178
+ isFenceBuildActive &&
10179
+ useEditor.getState().getContinuation('fence') === 'freehand' &&
10180
+ event.button === 0
10181
+ ) {
10182
+ const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
10183
+ if (point) emitFloorplanGridEvent('pointerup', point, event)
10184
+ }
10185
+ endFloorplanNavigation(event)
10186
+ },
10187
+ [
10188
+ emitFloorplanGridEvent,
10189
+ endFloorplanNavigation,
10190
+ getPlanPointFromClientPoint,
10191
+ isFenceBuildActive,
10192
+ ],
10193
+ )
10153
10194
  const handleBackgroundDoubleClick = useCallback(
10154
10195
  (event: ReactMouseEvent<SVGSVGElement>) => {
10155
10196
  if (!(isPolygonDraftBuildActive && !isRoofBuildActive)) {
@@ -10300,28 +10341,18 @@ export function FloorplanPanel({
10300
10341
  if (floorplanScreenSelectionOwnsInputDraggingRef.current) {
10301
10342
  useViewer.getState().setInputDragging(false)
10302
10343
  floorplanScreenSelectionOwnsInputDraggingRef.current = false
10344
+ useInteractionScope.getState().endIf((scope) => scope.kind === 'box-select')
10303
10345
  }
10304
10346
  }, [syncPreviewSelectedIds])
10305
10347
 
10306
10348
  const commitFloorplanScreenSelection = useCallback(
10307
10349
  (nextSelectedIds: string[], event: PointerEvent) => {
10308
10350
  const modifierKeys = getSelectionModifierKeys(event)
10309
- const shouldAppend = modifierKeys.meta || modifierKeys.ctrl || modifierKeys.shift
10310
-
10311
10351
  setSelectedReferenceId(null)
10312
10352
 
10313
- if (phase === 'structure' && structureLayer === 'zones') {
10314
- if (nextSelectedIds.length > 0) {
10315
- setSelection({ zoneId: nextSelectedIds[0] as ZoneNodeType['id'] })
10316
- } else if (!shouldAppend) {
10317
- setSelection({ zoneId: null })
10318
- }
10319
- return
10320
- }
10321
-
10322
10353
  addFloorplanSelection(nextSelectedIds, modifierKeys)
10323
10354
  },
10324
- [addFloorplanSelection, phase, setSelectedReferenceId, setSelection, structureLayer],
10355
+ [addFloorplanSelection, setSelectedReferenceId],
10325
10356
  )
10326
10357
 
10327
10358
  useEffect(() => {
@@ -10371,6 +10402,7 @@ export function FloorplanPanel({
10371
10402
  if (!state.isDragging && dragDistance >= SCREEN_RECTANGLE_SELECTION_DRAG_THRESHOLD_PX) {
10372
10403
  state.isDragging = true
10373
10404
  floorplanScreenSelectionOwnsInputDraggingRef.current = true
10405
+ useInteractionScope.getState().begin({ kind: 'box-select' })
10374
10406
  useViewer.getState().setInputDragging(true)
10375
10407
  markBoxSelectHandled()
10376
10408
 
@@ -10775,11 +10807,16 @@ export function FloorplanPanel({
10775
10807
  const hasFloorplanCursorIndicator =
10776
10808
  Boolean(movingOpeningType) ||
10777
10809
  (mode === 'build' && tool !== null) ||
10778
- (mode === 'select' && floorplanSelectionTool === 'marquee' && structureLayer !== 'zones') ||
10810
+ (mode === 'select' && floorplanSelectionTool === 'marquee') ||
10779
10811
  mode === 'delete'
10780
10812
 
10781
10813
  const handleSvgPointerMove = useCallback(
10782
10814
  (event: ReactPointerEvent<SVGSVGElement>) => {
10815
+ if (roomPickingEnabled() && !(event.target as Element).closest('.floorplan-registry-entry')) {
10816
+ const point = getPlanPointFromClientPoint(event.clientX, event.clientY)
10817
+ if (point)
10818
+ hoverRoomFromHit(resolvePlanRoomHit(null, point), getSelectionModifierKeys(event), null)
10819
+ }
10783
10820
  if (
10784
10821
  hasFloorplanCursorIndicator &&
10785
10822
  !isSpacePanPressed &&
@@ -10812,6 +10849,7 @@ export function FloorplanPanel({
10812
10849
  [
10813
10850
  handlePointerMove,
10814
10851
  hasFloorplanCursorIndicator,
10852
+ getPlanPointFromClientPoint,
10815
10853
  isSpacePanPressed,
10816
10854
  siteVertexDragState,
10817
10855
  setFloorplanCursorPosition,
@@ -10819,6 +10857,7 @@ export function FloorplanPanel({
10819
10857
  )
10820
10858
 
10821
10859
  const handleSvgPointerLeave = useCallback(() => {
10860
+ useEditor.getState().setHoveredRoom(null)
10822
10861
  setFloorplanCursorPosition(null)
10823
10862
  setHoveredGuideCorner(null)
10824
10863
  handlePointerLeave()
@@ -11129,8 +11168,7 @@ export function FloorplanPanel({
11129
11168
  )
11130
11169
  })()
11131
11170
 
11132
- setPhase('structure')
11133
- setStructureLayer('elements')
11171
+ setPhase('building')
11134
11172
  setMode('select')
11135
11173
 
11136
11174
  const nextSelection: Parameters<typeof setSelection>[0] = {
@@ -11147,16 +11185,7 @@ export function FloorplanPanel({
11147
11185
  }
11148
11186
 
11149
11187
  setSelection(nextSelection)
11150
- }, [
11151
- currentBuildingId,
11152
- floorplanLevels,
11153
- levelNode,
11154
- setMode,
11155
- setPhase,
11156
- setSelection,
11157
- setStructureLayer,
11158
- site,
11159
- ])
11188
+ }, [currentBuildingId, floorplanLevels, levelNode, setMode, setPhase, setSelection, site])
11160
11189
  const activeDraftAnchorPoint =
11161
11190
  referenceScaleDraft?.start ??
11162
11191
  draftStart ??
@@ -11236,8 +11265,10 @@ export function FloorplanPanel({
11236
11265
  pill, plus the group pill for multi-selections. */}
11237
11266
  <FloorplanRegistryActionMenu />
11238
11267
  <FloorplanGroupActionMenu />
11268
+ <FloorplanDrawingTypeSwitch className={planOnRight ? 'right-24 left-auto' : undefined} />
11239
11269
 
11240
11270
  {!isStudioWorkspace &&
11271
+ sceneLeft !== null &&
11241
11272
  (levelNode?.type === 'level' || hasAmbientBuildingLevel) &&
11242
11273
  (compassHost ? (
11243
11274
  createPortal(
@@ -11245,6 +11276,7 @@ export function FloorplanPanel({
11245
11276
  needleRef={compassNeedleRef}
11246
11277
  northRotationDeg={floorplanUserRotationDeg}
11247
11278
  onAlignNorth={alignFloorplanViewToNorth}
11279
+ style={compassStyle}
11248
11280
  />,
11249
11281
  compassHost,
11250
11282
  )
@@ -11282,7 +11314,7 @@ export function FloorplanPanel({
11282
11314
  }}
11283
11315
  >
11284
11316
  <div className="mb-3 flex items-start gap-2.5">
11285
- <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-border bg-white/5">
11317
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl border border-border bg-black/4 dark:bg-white/5">
11286
11318
  <Ruler className="h-4 w-4 text-foreground/80" />
11287
11319
  </div>
11288
11320
  <div className="min-w-0">
@@ -11294,7 +11326,7 @@ export function FloorplanPanel({
11294
11326
  </div>
11295
11327
  </div>
11296
11328
 
11297
- <div className="mb-3 rounded-xl border border-border/70 bg-white/5 px-3 py-2">
11329
+ <div className="mb-3 rounded-xl border border-border/70 bg-black/4 dark:bg-white/5 px-3 py-2">
11298
11330
  <div className="text-[11px] text-muted-foreground uppercase tracking-wide">
11299
11331
  Drawn line
11300
11332
  </div>
@@ -11357,7 +11389,7 @@ export function FloorplanPanel({
11357
11389
 
11358
11390
  <div className="mt-4 flex justify-end gap-2">
11359
11391
  <button
11360
- className="h-8 rounded-lg border border-border px-3 font-medium text-muted-foreground text-xs transition hover:bg-white/8 hover:text-foreground"
11392
+ className="h-8 rounded-lg border border-border px-3 font-medium text-muted-foreground text-xs transition hover:bg-black/5 dark:hover:bg-white/8 hover:text-foreground"
11361
11393
  onClick={() => setPendingReferenceScale(null)}
11362
11394
  type="button"
11363
11395
  >
@@ -11385,7 +11417,7 @@ export function FloorplanPanel({
11385
11417
  // renderer, handle layers) must NOT render/reconcile while hidden —
11386
11418
  // otherwise every scene/selection change in pure 3D re-rendered the
11387
11419
  // whole floorplan tree (profiler: 150–200ms on a wall-endpoint drag).
11388
- // `isFloorplanOpen` is `viewMode !== '3d'`, so this still renders fully
11420
+ // `isFloorplanOpen` is "the plan is in a pane", so this still renders fully
11389
11421
  // in both 2D and split. Viewport state lives on the still-mounted
11390
11422
  // panel, so pan/zoom is preserved across the toggle.
11391
11423
  <svg
@@ -11395,11 +11427,11 @@ export function FloorplanPanel({
11395
11427
  onContextMenu={(event) => event.preventDefault()}
11396
11428
  onDoubleClick={isMarqueeSelectionToolActive ? undefined : handleBackgroundDoubleClick}
11397
11429
  onPointerCancel={endFloorplanNavigation}
11398
- onPointerDown={handlePointerDown}
11430
+ onPointerDown={handleSvgPointerDown}
11399
11431
  onPointerDownCapture={handleNavigationPointerDown}
11400
11432
  onPointerLeave={handleSvgPointerLeave}
11401
11433
  onPointerMove={handleSvgPointerMove}
11402
- onPointerUp={endFloorplanNavigation}
11434
+ onPointerUp={handleSvgPointerUp}
11403
11435
  ref={svgRef}
11404
11436
  style={{
11405
11437
  cursor:
@@ -11496,7 +11528,7 @@ export function FloorplanPanel({
11496
11528
  subscribes to the `useStairBuildPreview` store directly, so a
11497
11529
  per-`grid:move` cursor update re-renders only that tiny layer
11498
11530
  rather than this whole panel. */}
11499
- <FloorplanStairBuildPreviewLayer isDeleteMode={isDeleteMode} palette={palette} />
11531
+ <FloorplanStairBuildPreviewLayer palette={floorplanRegistryPalette} />
11500
11532
 
11501
11533
  <FloorplanReferenceScaleLayer
11502
11534
  draft={referenceScaleDraft}
@@ -11547,7 +11579,13 @@ export function FloorplanPanel({
11547
11579
  whose extent is derived from the current viewBox and
11548
11580
  would create a measure→fit→measure loop. */}
11549
11581
  <g ref={floorplanContentRef}>
11550
- <FloorplanRegistryLayer />
11582
+ {/* Site-plan mode swaps the level drawing for the lot /
11583
+ setback / footprint drawing, in SITE coordinates. */}
11584
+ {activeDrawingType === 'site-plan' ? (
11585
+ <FloorplanSitePlanLayer />
11586
+ ) : (
11587
+ <FloorplanRegistryLayer />
11588
+ )}
11551
11589
  {/* Faint footprint ghost of the node being placed by a
11552
11590
  registry placement tool (e.g. column), following the
11553
11591
  cursor. The 3D mesh preview is hidden in 2D, so this is
@@ -11561,6 +11599,7 @@ export function FloorplanPanel({
11561
11599
  `floorplan-wall-move-ghost-layer.tsx`. */}
11562
11600
  <FloorplanWallMoveGhostLayer />
11563
11601
  </g>
11602
+ {activeDrawingType !== 'site-plan' && <FloorplanOpenWallEndsLayer />}
11564
11603
  <FloorplanMeasurementToolLayer />
11565
11604
  <FloorplanRegisteredToolLayer />
11566
11605
  {floorplanSceneSlot}
@@ -11577,42 +11616,54 @@ export function FloorplanPanel({
11577
11616
  paint on top of node geometry. */}
11578
11617
  <FloorplanAlignmentGuideLayer />
11579
11618
 
11580
- <FloorplanSiteLayer
11581
- dimmed={selectedIds.length > 1 || previewSelectedIds.length > 1}
11582
- isHighlighted={isSiteBoundaryHighlighted}
11583
- palette={palette}
11584
- sitePolygon={visibleSitePolygon}
11585
- />
11619
+ {/* The property line, its vertex handles and its edge labels are
11620
+ drawn in BUILDING-LOCAL metres (the frame the level's walls
11621
+ live in). The site-plan drawing type draws the lot itself, in
11622
+ SITE metres, so mounting these there paints a second lot
11623
+ offset by the building's own position — the "two dashed
11624
+ boxes" — and it shifts whenever the house moves. */}
11625
+ {activeDrawingType !== 'site-plan' && (
11626
+ <FloorplanSiteLayer
11627
+ dimmed={selectedIds.length > 1 || previewSelectedIds.length > 1}
11628
+ isHighlighted={isSiteBoundaryHighlighted}
11629
+ palette={palette}
11630
+ sitePolygon={visibleSitePolygon}
11631
+ />
11632
+ )}
11586
11633
 
11587
- <FloorplanPolygonHandleLayer
11588
- edgeHandles={siteEdgeHandles}
11589
- hoveredHandleId={hoveredSiteHandleId}
11590
- midpointHandles={siteMidpointHandles}
11591
- onHandleHoverChange={setHoveredSiteHandleId}
11592
- onMidpointPointerDown={(nodeId, edgeIndex, event) =>
11593
- handleSiteMidpointPointerDown(nodeId as SiteNode['id'], edgeIndex, event)
11594
- }
11595
- onVertexDoubleClick={(nodeId, vertexIndex, event) =>
11596
- handleSiteVertexDoubleClick(nodeId as SiteNode['id'], vertexIndex, event)
11597
- }
11598
- onVertexPointerDown={(nodeId, vertexIndex, event) =>
11599
- handleSiteVertexPointerDown(nodeId as SiteNode['id'], vertexIndex, event)
11600
- }
11601
- palette={palette}
11602
- unitsPerPixel={floorplanUnitsPerPixel}
11603
- vertexHandles={siteVertexHandles}
11604
- />
11634
+ {activeDrawingType !== 'site-plan' && (
11635
+ <FloorplanPolygonHandleLayer
11636
+ edgeHandles={siteEdgeHandles}
11637
+ hoveredHandleId={hoveredSiteHandleId}
11638
+ midpointHandles={siteMidpointHandles}
11639
+ onHandleHoverChange={setHoveredSiteHandleId}
11640
+ onMidpointPointerDown={(nodeId, edgeIndex, event) =>
11641
+ handleSiteMidpointPointerDown(nodeId as SiteNode['id'], edgeIndex, event)
11642
+ }
11643
+ onVertexDoubleClick={(nodeId, vertexIndex, event) =>
11644
+ handleSiteVertexDoubleClick(nodeId as SiteNode['id'], vertexIndex, event)
11645
+ }
11646
+ onVertexPointerDown={(nodeId, vertexIndex, event) =>
11647
+ handleSiteVertexPointerDown(nodeId as SiteNode['id'], vertexIndex, event)
11648
+ }
11649
+ palette={palette}
11650
+ unitsPerPixel={floorplanUnitsPerPixel}
11651
+ vertexHandles={siteVertexHandles}
11652
+ />
11653
+ )}
11605
11654
 
11606
- <FloorplanSiteEdgeLabelLayer
11607
- labelBackground={isDark ? '#0f172a' : '#ffffff'}
11608
- labelText={isDark ? '#e2e8f0' : '#171717'}
11609
- palette={palette}
11610
- sceneRotationDeg={floorplanSceneRotationDeg}
11611
- shouldShow={shouldShowSiteEdgeLabels}
11612
- sitePolygon={visibleSitePolygon}
11613
- unit={unit}
11614
- unitsPerPixel={floorplanUnitsPerPixel}
11615
- />
11655
+ {activeDrawingType !== 'site-plan' && (
11656
+ <FloorplanSiteEdgeLabelLayer
11657
+ labelBackground={isDark ? '#0f172a' : '#ffffff'}
11658
+ labelText={isDark ? '#e2e8f0' : '#171717'}
11659
+ palette={palette}
11660
+ sceneRotationDeg={floorplanSceneRotationDeg}
11661
+ shouldShow={shouldShowSiteEdgeLabels}
11662
+ sitePolygon={visibleSitePolygon}
11663
+ unit={unit}
11664
+ unitsPerPixel={floorplanUnitsPerPixel}
11665
+ />
11666
+ )}
11616
11667
 
11617
11668
  {/* "Magnetic" wall-snap beacon — per-kind glyph at the active
11618
11669
  draft / endpoint-move snap point. Same store + coord space as