@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
@@ -0,0 +1,287 @@
1
+ /**
2
+ * What the site plan reads off the scene — the site, the building on it, its
3
+ * storeys, the walls' outline, the roof's outline and the outdoor slabs
4
+ * (porches, decks, patios, and the flatwork: driveway and walks) — in SITE
5
+ * metres. Shared by the drawing (`build-site-plan-drawing.ts`) and the
6
+ * coverage / impervious-area figures (`coverage.ts`) so both read the same
7
+ * parts. Pure: no store.
8
+ */
9
+ import {
10
+ type AnyNode,
11
+ type AnyNodeId,
12
+ type BuildingNode,
13
+ getRoofSegmentDeckReach,
14
+ type LevelNode,
15
+ migrateSiteMetadata,
16
+ type RoofSegmentNode,
17
+ type SceneSnapshot,
18
+ type SiteNode,
19
+ siteFrame,
20
+ turnPlanPoint,
21
+ unionPolygons,
22
+ yawOf,
23
+ } from '@pascal-app/core'
24
+ import { type Pt, pointInPolygon, polygonArea, polygonBounds } from './geometry'
25
+
26
+ /**
27
+ * The scene's site node, with legacy `metadata.setbacks / zone / apn` lifted
28
+ * onto the real fields. This is a READ-side lift only — nothing is written
29
+ * back to the store, so a scene that was never re-saved keeps its metadata.
30
+ * A persistence-side migration would belong in
31
+ * `packages/core/src/utils/scene-migrations.ts` (not owned by this
32
+ * workstream); see the note in `migrateSiteMetadata`.
33
+ */
34
+ export function findSite(scene: SceneSnapshot): SiteNode | null {
35
+ for (const node of Object.values(scene.nodes)) {
36
+ if (node.type !== 'site') continue
37
+ const site = node as SiteNode
38
+ const patch = migrateSiteMetadata(site)
39
+ return Object.keys(patch).length > 0 ? { ...site, ...patch } : site
40
+ }
41
+ return null
42
+ }
43
+
44
+ export function findBuilding(scene: SceneSnapshot, site: SiteNode | null): BuildingNode | null {
45
+ if (site) {
46
+ for (const childId of site.children ?? []) {
47
+ const child = scene.nodes[childId as AnyNodeId]
48
+ if (child?.type === 'building') return child as BuildingNode
49
+ }
50
+ }
51
+ for (const node of Object.values(scene.nodes)) {
52
+ if (node.type === 'building') return node as BuildingNode
53
+ }
54
+ return null
55
+ }
56
+
57
+ /** Lowest `level` number among the building's level children. */
58
+ export function findLowestLevel(
59
+ scene: SceneSnapshot,
60
+ building: BuildingNode | null,
61
+ ): LevelNode | null {
62
+ if (!building) return null
63
+ let best: LevelNode | null = null
64
+ for (const childId of building.children ?? []) {
65
+ const child = scene.nodes[childId as AnyNodeId] as AnyNode | undefined
66
+ if (child?.type !== 'level') continue
67
+ const level = child as LevelNode
68
+ if (!best || level.level < best.level) best = level
69
+ }
70
+ return best
71
+ }
72
+
73
+ /**
74
+ * The roof's outline on the lot — every roof segment's plan rectangle out to
75
+ * its deck's reach at its eaves and rakes, through the segment's, the roof's
76
+ * and the building's turns, unioned into the outer ring(s). Dashed on the site plan: what the eye sees
77
+ * from above is the roof, and the setback is measured to the wall under it.
78
+ */
79
+ export function roofOutlineRings(
80
+ scene: SceneSnapshot,
81
+ level: LevelNode | null,
82
+ building: BuildingNode | null,
83
+ ): Pt[][] {
84
+ if (!level) return []
85
+ const toSite = siteFrame(building)
86
+ const roofs = new Map<string, { position?: number[]; rotation?: unknown }>()
87
+ for (const childId of level.children ?? []) {
88
+ const child = scene.nodes[childId as AnyNodeId] as AnyNode | undefined
89
+ if (child?.type === 'roof')
90
+ roofs.set(String(child.id), child as { position?: number[]; rotation?: unknown })
91
+ }
92
+ if (roofs.size === 0) return []
93
+ const rects: Pt[][] = []
94
+ for (const node of Object.values(scene.nodes)) {
95
+ if (node.type !== 'roof-segment') continue
96
+ const roof = roofs.get(String((node as { parentId?: unknown }).parentId))
97
+ if (!roof) continue
98
+ const seg = node as RoofSegmentNode
99
+ const reach = getRoofSegmentDeckReach(seg)
100
+ const w = (seg.width ?? 0) / 2 + reach.x
101
+ const d = (seg.depth ?? 0) / 2 + reach.z
102
+ if (w <= 0 || d <= 0) continue
103
+ const segYaw = yawOf(seg.rotation)
104
+ const roofYaw = yawOf(roof.rotation)
105
+ const sx = seg.position?.[0] ?? 0
106
+ const sz = seg.position?.[2] ?? 0
107
+ const rx = roof.position?.[0] ?? 0
108
+ const rz = roof.position?.[2] ?? 0
109
+ const local: Pt[] = [
110
+ [-w, -d],
111
+ [w, -d],
112
+ [w, d],
113
+ [-w, d],
114
+ ]
115
+ rects.push(
116
+ local.map(([lx, lz]) => {
117
+ const [ax, az] = turnPlanPoint(lx, lz, segYaw)
118
+ const [bx, bz] = turnPlanPoint(ax + sx, az + sz, roofYaw)
119
+ return toSite(bx + rx, bz + rz)
120
+ }),
121
+ )
122
+ }
123
+ if (rects.length === 0) return []
124
+ const rings = unionPolygons(rects.map((r) => r.map((p) => [p[0], p[1]]))) as Pt[][]
125
+ return rings.length > 0 ? rings : rects
126
+ }
127
+
128
+ /** Every storey of the building at or above grade (level ≥ 0), lowest first. */
129
+ export function aboveGradeLevels(scene: SceneSnapshot, building: BuildingNode | null): LevelNode[] {
130
+ if (!building) return []
131
+ const out: LevelNode[] = []
132
+ for (const childId of building.children ?? []) {
133
+ const child = scene.nodes[childId as AnyNodeId] as AnyNode | undefined
134
+ if (child?.type === 'level' && (child as LevelNode).level >= 0) out.push(child as LevelNode)
135
+ }
136
+ return out.sort((a, b) => a.level - b.level)
137
+ }
138
+
139
+ /* ------------------------------------------------------ outdoor slabs */
140
+
141
+ /** What an outdoor slab is on the site plan and in the impervious table. */
142
+ export type OutdoorKind = 'porch' | 'deck' | 'patio' | 'landing' | 'driveway' | 'walk'
143
+
144
+ export interface OutdoorPart {
145
+ id: string
146
+ kind: OutdoorKind
147
+ /** Upper-case label for the plan ("PORCH", "REAR DECK", "DRIVEWAY"). */
148
+ label: string
149
+ ring: Pt[]
150
+ /** Plan area, m². */
151
+ area: number
152
+ /** Under a roof (≥ half its area inside a roof outline): building coverage, not open paving. */
153
+ covered: boolean
154
+ /** Wood decking (a deck): open boards over grade. */
155
+ wood: boolean
156
+ }
157
+
158
+ /**
159
+ * Flatwork: a slab that is site paving, not part of the building — the
160
+ * generator's driveway and walks (`metadata.flatwork`), and a hand-drawn slab
161
+ * named like one (the floor plans leave the same slabs out).
162
+ */
163
+ const HARDSCAPE =
164
+ /driveway|drive\s*way|\bdrive\b|approach|walkway|sidewalk|\bwalk\b|\bpath\b|apron|parking|\bpavers?\b/i
165
+ export function flatworkKindOf(node: {
166
+ type?: string
167
+ name?: unknown
168
+ metadata?: unknown
169
+ }): 'driveway' | 'walk' | null {
170
+ if (node.type !== 'slab') return null
171
+ const meta = (node.metadata ?? {}) as { flatwork?: unknown; floor?: unknown }
172
+ if (meta.flatwork === 'driveway' || meta.flatwork === 'walk') return meta.flatwork
173
+ const name = typeof node.name === 'string' ? node.name : ''
174
+ const floor = typeof meta.floor === 'string' ? meta.floor : ''
175
+ const text = `${name} ${floor}`
176
+ if (!HARDSCAPE.test(text)) return null
177
+ return /drive|approach|apron|parking/i.test(text) ? 'driveway' : 'walk'
178
+ }
179
+
180
+ /** The house's own floors: the storey slab, a raised platform, the garage pad, an upper floor. */
181
+ const HOUSE_FLOORS = new Set(['slab-on-grade', 'platform', 'garage-slab-at-grade', 'floor'])
182
+
183
+ /** Share of `ring`'s area inside any of `covers` (a 6 × 6 sample grid). */
184
+ function coveredShare(ring: readonly Pt[], covers: readonly Pt[][]): number {
185
+ if (covers.length === 0) return 0
186
+ const b = polygonBounds(ring)
187
+ let inside = 0
188
+ let under = 0
189
+ const N = 6
190
+ for (let i = 0; i < N; i++) {
191
+ for (let j = 0; j < N; j++) {
192
+ const x = b.minX + ((i + 0.5) / N) * (b.maxX - b.minX)
193
+ const y = b.minY + ((j + 0.5) / N) * (b.maxY - b.minY)
194
+ if (!pointInPolygon(ring, x, y)) continue
195
+ inside++
196
+ if (covers.some((c) => pointInPolygon(c, x, y))) under++
197
+ }
198
+ }
199
+ return inside > 0 ? under / inside : 0
200
+ }
201
+
202
+ /**
203
+ * The level's outdoor slabs in site metres: porches, decks, patios and
204
+ * landings (a generator `metadata.porch`, or a deck / porch-slab floor, or a
205
+ * slab lying outside the house's walls) and the flatwork. The house's own
206
+ * floors (the storey slab, a raised platform, the garage pad) and a porch
207
+ * cover's beam are not outdoor parts. `roofs` decides which are covered.
208
+ */
209
+ export function outdoorParts(
210
+ scene: SceneSnapshot,
211
+ level: LevelNode | null,
212
+ building: BuildingNode | null,
213
+ walls: readonly Pt[][],
214
+ roofs: readonly Pt[][],
215
+ ): OutdoorPart[] {
216
+ if (!level) return []
217
+ const toSite = siteFrame(building)
218
+ const out: OutdoorPart[] = []
219
+ for (const childId of level.children ?? []) {
220
+ const child = scene.nodes[childId as AnyNodeId] as AnyNode | undefined
221
+ if (child?.type !== 'slab' || child.visible === false) continue
222
+ const slab = child as unknown as {
223
+ id: string
224
+ polygon?: number[][]
225
+ metadata?: Record<string, unknown>
226
+ name?: string
227
+ }
228
+ const poly = slab.polygon ?? []
229
+ if (poly.length < 3) continue
230
+ const ring = poly.map((p) => toSite(p[0] ?? 0, p[1] ?? 0))
231
+ const area = polygonArea(ring)
232
+ const meta = slab.metadata ?? {}
233
+ const floor = typeof meta.floor === 'string' ? meta.floor : ''
234
+ const flat = flatworkKindOf(child as never)
235
+ if (flat) {
236
+ out.push({
237
+ id: slab.id,
238
+ kind: flat,
239
+ label: flat === 'driveway' ? 'DRIVEWAY' : 'WALK',
240
+ ring,
241
+ area,
242
+ covered: false,
243
+ wood: false,
244
+ })
245
+ continue
246
+ }
247
+ // a porch cover's beam box is not a floor
248
+ if (/beam/i.test(floor) || /beam/i.test(slab.name ?? '')) continue
249
+ const porch = meta.porch as { policy?: string; entrance?: string } | undefined
250
+ const outdoorFloor = floor === 'deck' || floor === 'porch-slab'
251
+ if (!porch && !outdoorFloor) {
252
+ // a hand-drawn slab: the house's floor when it lies under the walls
253
+ if (HOUSE_FLOORS.has(floor)) continue
254
+ const c = ring.reduce<[number, number]>(
255
+ (s, p) => [s[0] + p[0] / ring.length, s[1] + p[1] / ring.length],
256
+ [0, 0],
257
+ )
258
+ if (walls.length === 0 || walls.some((w) => pointInPolygon(w, c[0], c[1]))) continue
259
+ }
260
+ const wood = floor === 'deck'
261
+ const policy = porch?.policy ?? ''
262
+ const kind: OutdoorKind =
263
+ policy === 'deck' || (wood && policy !== 'full' && policy !== 'entry')
264
+ ? 'deck'
265
+ : policy === 'landing'
266
+ ? 'landing'
267
+ : policy === 'patio'
268
+ ? 'patio'
269
+ : 'porch'
270
+ const label =
271
+ porch?.policy === 'entry'
272
+ ? 'PORCH'
273
+ : porch?.policy === 'landing'
274
+ ? 'LANDING'
275
+ : (slab.name ?? kind).toUpperCase()
276
+ out.push({
277
+ id: slab.id,
278
+ kind,
279
+ label,
280
+ ring,
281
+ area,
282
+ covered: coveredShare(ring, roofs) >= 0.5,
283
+ wood,
284
+ })
285
+ }
286
+ return out
287
+ }
@@ -0,0 +1,138 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ type BuildingNode,
6
+ pauseSceneHistory,
7
+ resumeSceneHistory,
8
+ useScene,
9
+ } from '@pascal-app/core'
10
+ import { type PointerEvent as ReactPointerEvent, useCallback, useMemo, useRef } from 'react'
11
+ import { FloorplanGeometryRenderer } from '../../../components/editor-2d/renderers/floorplan-geometry-renderer'
12
+ import { clientToPlan } from '../plan-coords'
13
+ import { buildSitePlanDrawing } from './build-site-plan-drawing'
14
+
15
+ /**
16
+ * Site-plan view of the 2D editor.
17
+ *
18
+ * Mounted INSTEAD of `<FloorplanRegistryLayer>` when
19
+ * `useDrawingView().drawingType === 'site-plan'`, inside the same scene `<g>`,
20
+ * so the coordinates it draws in (site metres, origin = the geocoded point,
21
+ * x east, y south) are the ones `clientToPlan` reports back.
22
+ *
23
+ * Everything is recomputed from the scene snapshot on each store change, so
24
+ * the lot line, envelope, footprint and yard dimensions are live.
25
+ */
26
+ export function FloorplanSitePlanLayer() {
27
+ // Subscribing to the whole node map is what the site plan actually depends
28
+ // on (walls, building placement, site fields); the drawing is cheap enough
29
+ // (tens of primitives) that memoising on the map identity is sufficient.
30
+ const nodes = useScene((s) => s.nodes)
31
+
32
+ const drawing = useMemo(() => {
33
+ const state = useScene.getState()
34
+ return buildSitePlanDrawing({
35
+ nodes,
36
+ rootNodeIds: state.rootNodeIds,
37
+ collections: state.collections,
38
+ materials: state.materials,
39
+ installedPlugins: state.installedPlugins,
40
+ })
41
+ }, [nodes])
42
+
43
+ const dragRef = useRef<{
44
+ id: AnyNodeId
45
+ pointerId: number
46
+ startPlan: [number, number]
47
+ startPosition: [number, number, number]
48
+ } | null>(null)
49
+
50
+ const buildingId = drawing.meta.buildingId
51
+
52
+ const handlePointerDown = useCallback(
53
+ (event: ReactPointerEvent<SVGGElement>) => {
54
+ if (!buildingId || event.button !== 0) return
55
+ const building = useScene.getState().nodes[buildingId] as BuildingNode | undefined
56
+ if (!building) return
57
+ const startPlan = clientToPlan(event.clientX, event.clientY)
58
+ if (!startPlan) return
59
+ event.preventDefault()
60
+ event.stopPropagation()
61
+ event.currentTarget.setPointerCapture(event.pointerId)
62
+ dragRef.current = {
63
+ id: buildingId,
64
+ pointerId: event.pointerId,
65
+ startPlan: [startPlan[0], startPlan[1]],
66
+ startPosition: [...building.position] as [number, number, number],
67
+ }
68
+ // One history entry for the whole drag, committed on pointer-up.
69
+ pauseSceneHistory(useScene)
70
+ },
71
+ [buildingId],
72
+ )
73
+
74
+ const handlePointerMove = useCallback((event: ReactPointerEvent<SVGGElement>) => {
75
+ const drag = dragRef.current
76
+ if (!drag || drag.pointerId !== event.pointerId) return
77
+ const plan = clientToPlan(event.clientX, event.clientY)
78
+ if (!plan) return
79
+ const dx = plan[0] - drag.startPlan[0]
80
+ const dz = plan[1] - drag.startPlan[1]
81
+ useScene.getState().updateNode(drag.id, {
82
+ position: [drag.startPosition[0] + dx, drag.startPosition[1], drag.startPosition[2] + dz],
83
+ })
84
+ }, [])
85
+
86
+ const endDrag = useCallback((event: ReactPointerEvent<SVGGElement>) => {
87
+ const drag = dragRef.current
88
+ if (!drag || drag.pointerId !== event.pointerId) return
89
+ dragRef.current = null
90
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
91
+ event.currentTarget.releasePointerCapture(event.pointerId)
92
+ }
93
+ resumeSceneHistory(useScene)
94
+ }, [])
95
+
96
+ const footprintIndexes = new Set<number>()
97
+ drawing.primitives.forEach((p, i) => {
98
+ const meta = (p as { metadata?: Record<string, unknown> }).metadata
99
+ if (meta?.sitePlan === 'building-footprint') footprintIndexes.add(i)
100
+ })
101
+
102
+ return (
103
+ <g data-site-plan="">
104
+ {drawing.primitives.map((geometry, index) =>
105
+ footprintIndexes.has(index) ? null : (
106
+ <FloorplanGeometryRenderer
107
+ geometry={geometry}
108
+ // eslint-disable-next-line react/no-array-index-key -- primitives are positional, rebuilt wholesale each render
109
+ key={`site-plan-${index}`}
110
+ />
111
+ ),
112
+ )}
113
+ {/* Footprint bands live in their own group so the whole building drags
114
+ as one target, writing `building.position` — the same field the 3D
115
+ move tool writes. */}
116
+ <g
117
+ data-site-plan-building=""
118
+ onPointerCancel={endDrag}
119
+ onPointerDown={handlePointerDown}
120
+ onPointerMove={handlePointerMove}
121
+ onPointerUp={endDrag}
122
+ style={{ cursor: buildingId ? 'move' : undefined }}
123
+ >
124
+ {drawing.primitives.map((geometry, index) =>
125
+ footprintIndexes.has(index) ? (
126
+ <FloorplanGeometryRenderer
127
+ geometry={geometry}
128
+ // eslint-disable-next-line react/no-array-index-key -- see above
129
+ key={`site-plan-fp-${index}`}
130
+ />
131
+ ) : null,
132
+ )}
133
+ </g>
134
+ </g>
135
+ )
136
+ }
137
+
138
+ export default FloorplanSitePlanLayer
@@ -0,0 +1,69 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { parseStreetName, sameStreet } from './street-name'
3
+
4
+ describe('parseStreetName (USPS Pub. 28)', () => {
5
+ test('the number dropped, the directional and the type standardised', () => {
6
+ expect(parseStreetName('4121 NW 34th St')).toEqual({
7
+ pre: 'nw',
8
+ name: '34th',
9
+ type: 'st',
10
+ post: '',
11
+ })
12
+ expect(parseStreetName('Northwest 34th Terrace')).toEqual({
13
+ pre: 'nw',
14
+ name: '34th',
15
+ type: 'ter',
16
+ post: '',
17
+ })
18
+ expect(parseStreetName('N.W. 34th Terr.')).toEqual({
19
+ pre: 'nw',
20
+ name: '34th',
21
+ type: 'ter',
22
+ post: '',
23
+ })
24
+ expect(parseStreetName('2715 LAKE HUNTER DR')).toEqual({
25
+ pre: '',
26
+ name: 'lake hunter',
27
+ type: 'dr',
28
+ post: '',
29
+ })
30
+ expect(parseStreetName('100 1st Ave N')).toEqual({
31
+ pre: '',
32
+ name: '1st',
33
+ type: 'ave',
34
+ post: 'n',
35
+ })
36
+ expect(parseStreetName('Del Prado Boulevard South').type).toBe('blvd')
37
+ expect(parseStreetName('Pine Island Pkwy').type).toBe('pkwy')
38
+ expect(parseStreetName('Old Dixie Highway').type).toBe('hwy')
39
+ })
40
+
41
+ test('a word is only a type or a directional when a name is left beside it', () => {
42
+ expect(parseStreetName('North St')).toEqual({ pre: '', name: 'north', type: 'st', post: '' })
43
+ expect(parseStreetName('Broadway')).toEqual({ pre: '', name: 'broadway', type: '', post: '' })
44
+ expect(parseStreetName('')).toEqual({ pre: '', name: '', type: '', post: '' })
45
+ })
46
+ })
47
+
48
+ describe('sameStreet', () => {
49
+ test('abbreviated and spelled-out lines of one street match', () => {
50
+ expect(sameStreet('4121 NW 34th St', 'Northwest 34th Street')).toBe(true)
51
+ expect(sameStreet('1247 NE 109th St', 'Northeast 109th Street')).toBe(true)
52
+ expect(sameStreet('2600 Castro Way', 'Castro Way')).toBe(true)
53
+ expect(sameStreet('44 Oak Cir', 'Oak Circle')).toBe(true)
54
+ expect(sameStreet('9 Elm Ct', 'Elm Court')).toBe(true)
55
+ })
56
+
57
+ test('a different street type or quadrant is a different street', () => {
58
+ expect(sameStreet('4121 NW 34th St', 'Northwest 34th Terrace')).toBe(false)
59
+ expect(sameStreet('4121 NW 34th St', 'Southwest 34th Street')).toBe(false)
60
+ expect(sameStreet('12 Oak Ln', 'Oak Place')).toBe(false)
61
+ expect(sameStreet('12 Oak Rd', 'Oak Road')).toBe(true)
62
+ })
63
+
64
+ test('a line that leaves the type or the directional out does not rule a street out', () => {
65
+ expect(sameStreet('S Castro', 'Castro Way')).toBe(true)
66
+ expect(sameStreet('4121 NW 34th St', '34th Street')).toBe(true)
67
+ expect(sameStreet('', 'Castro Way')).toBe(false)
68
+ })
69
+ })