@pascal-app/editor 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (498) hide show
  1. package/package.json +16 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +221 -128
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +37 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +536 -487
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/grid.tsx +31 -7
  29. package/src/components/editor/group-actions.ts +59 -11
  30. package/src/components/editor/group-floating-action-menu.tsx +19 -9
  31. package/src/components/editor/group-move-3d.ts +4 -6
  32. package/src/components/editor/group-rotate-handle.tsx +81 -35
  33. package/src/components/editor/group-selection-box-3d.tsx +25 -17
  34. package/src/components/editor/group-transform-shared.test.ts +126 -1
  35. package/src/components/editor/group-transform-shared.ts +79 -73
  36. package/src/components/editor/handles/handle-arrow.tsx +72 -2
  37. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  38. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  39. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  40. package/src/components/editor/handles/use-handle-drag.ts +272 -131
  41. package/src/components/editor/index.tsx +316 -176
  42. package/src/components/editor/join-walls-pill.tsx +44 -0
  43. package/src/components/editor/node-action-menu.tsx +70 -70
  44. package/src/components/editor/node-arrow-handles.tsx +167 -14
  45. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  46. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  47. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  48. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  49. package/src/components/editor/registry-action-contributions.tsx +132 -0
  50. package/src/components/editor/room-controls.tsx +847 -0
  51. package/src/components/editor/room-floor-highlight.tsx +128 -0
  52. package/src/components/editor/room-handles-2d.tsx +354 -0
  53. package/src/components/editor/room-handles.tsx +675 -0
  54. package/src/components/editor/room-highlight.tsx +225 -0
  55. package/src/components/editor/room-labels.test.ts +357 -0
  56. package/src/components/editor/room-labels.tsx +990 -0
  57. package/src/components/editor/room-transform-ghost.tsx +256 -0
  58. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  59. package/src/components/editor/selection-manager.tsx +451 -317
  60. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  61. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  62. package/src/components/editor/snapshot-capture.test.ts +52 -4
  63. package/src/components/editor/snapshot-capture.ts +30 -5
  64. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  65. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  66. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  67. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  68. package/src/components/editor/views/host-view.tsx +61 -0
  69. package/src/components/editor/views/plugins-view.tsx +670 -0
  70. package/src/components/editor/views/scene-region.tsx +59 -0
  71. package/src/components/editor/views/use-editor-views.ts +57 -0
  72. package/src/components/editor/views/view-bar.tsx +220 -0
  73. package/src/components/editor/wall-justification.test.ts +140 -0
  74. package/src/components/editor/wall-move-side-handles.tsx +114 -163
  75. package/src/components/editor/wall-opening-highlights.tsx +2 -3
  76. package/src/components/editor/wall-push-arrow.tsx +92 -0
  77. package/src/components/editor/wall-region-handles.tsx +509 -0
  78. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  79. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  80. package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
  81. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  82. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  83. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
  84. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  85. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
  86. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  88. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  89. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +166 -0
  90. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  91. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
  92. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
  93. package/src/components/editor-2d/svg-paths.ts +12 -15
  94. package/src/components/procedural-items/inspector.tsx +40 -15
  95. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  96. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  97. package/src/components/systems/zone/zone-system.test.ts +47 -0
  98. package/src/components/systems/zone/zone-system.tsx +31 -66
  99. package/src/components/tools/__fixtures__/hosting.ts +10 -0
  100. package/src/components/tools/item/item-surface-placement.test.ts +3 -9
  101. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
  102. package/src/components/tools/item/placement-history.test.tsx +474 -0
  103. package/src/components/tools/item/placement-strategies.ts +63 -47
  104. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
  105. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  106. package/src/components/tools/item/use-draft-node.ts +207 -97
  107. package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
  108. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  109. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  110. package/src/components/tools/registry/item-surface-move.test.ts +10 -2
  111. package/src/components/tools/registry/item-surface-move.ts +46 -24
  112. package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
  114. package/src/components/tools/select/box-select-tool.tsx +2 -12
  115. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  116. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  117. package/src/components/tools/select/select-candidates.test.ts +37 -4
  118. package/src/components/tools/select/select-candidates.ts +15 -11
  119. package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
  120. package/src/components/tools/shared/affordance-dispatch.ts +13 -0
  121. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  122. package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
  123. package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
  124. package/src/components/tools/shared/pointer-support-cap.ts +17 -5
  125. package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
  126. package/src/components/tools/shared/surface-rejection.tsx +14 -0
  127. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  128. package/src/components/tools/stair/stair-defaults.ts +0 -3
  129. package/src/components/tools/stair/stair-tool.tsx +90 -42
  130. package/src/components/tools/tool-manager.tsx +82 -23
  131. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  132. package/src/components/tools/zone/zone-tool.tsx +4 -12
  133. package/src/components/ui/action-menu/action-button.tsx +3 -0
  134. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  135. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  136. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  137. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  138. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  139. package/src/components/ui/command-palette/index.tsx +5 -24
  140. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  141. package/src/components/ui/controls/material-paint-panel.tsx +101 -91
  142. package/src/components/ui/controls/material-picker.tsx +8 -67
  143. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  144. package/src/components/ui/controls/select-control.tsx +65 -0
  145. package/src/components/ui/controls/slider-control.tsx +76 -31
  146. package/src/components/ui/floating-level-selector.tsx +9 -14
  147. package/src/components/ui/helpers/building-helper.tsx +4 -1
  148. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  149. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  150. package/src/components/ui/helpers/helper-manager.tsx +348 -19
  151. package/src/components/ui/helpers/item-helper.tsx +8 -0
  152. package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
  153. package/src/components/ui/icon-ref.tsx +45 -1
  154. package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
  155. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  156. package/src/components/ui/node-icon-image.tsx +16 -0
  157. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  158. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  159. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  160. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  161. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  162. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  163. package/src/components/ui/panels/multi-field-value.ts +6 -3
  164. package/src/components/ui/panels/multi-height-mode.tsx +27 -21
  165. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  166. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  167. package/src/components/ui/panels/node-display.ts +8 -3
  168. package/src/components/ui/panels/panel-manager.tsx +103 -68
  169. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  170. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  171. package/src/components/ui/panels/parametric-inspector.tsx +7 -19
  172. package/src/components/ui/panels/reference-panel.tsx +2 -3
  173. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  174. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  175. package/src/components/ui/panels/room-name-options.ts +47 -0
  176. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  177. package/src/components/ui/panels/room-panel.tsx +725 -0
  178. package/src/components/ui/panels/shape-choice.tsx +58 -0
  179. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  180. package/src/components/ui/primitives/color-dot.tsx +36 -24
  181. package/src/components/ui/primitives/shortcut-token.tsx +4 -0
  182. package/src/components/ui/primitives/sidebar.tsx +2 -0
  183. package/src/components/ui/right-stack.test.ts +29 -0
  184. package/src/components/ui/right-stack.tsx +145 -0
  185. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  186. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  187. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
  188. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
  189. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  190. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  191. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  192. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  193. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  194. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  195. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  196. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  197. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  198. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  199. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  200. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  211. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  215. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  216. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  217. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  218. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  219. package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
  220. package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
  221. package/src/components/viewer/floorplan-preview.tsx +6 -18
  222. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  223. package/src/components/viewer/viewer-rooms.tsx +175 -0
  224. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  225. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  226. package/src/components/viewer/viewer-stage.tsx +2 -2
  227. package/src/components/viewer-zone-system.tsx +9 -9
  228. package/src/hooks/use-auto-save.integration.test.tsx +311 -0
  229. package/src/hooks/use-auto-save.ts +55 -14
  230. package/src/hooks/use-ceiling-events.test.ts +1 -0
  231. package/src/hooks/use-ceiling-events.ts +2 -0
  232. package/src/hooks/use-grid-events.test.ts +2 -0
  233. package/src/hooks/use-grid-events.ts +16 -11
  234. package/src/hooks/use-keyboard.test.ts +182 -0
  235. package/src/hooks/use-keyboard.ts +122 -91
  236. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  237. package/src/hooks/use-open-wall-ends.ts +238 -0
  238. package/src/hooks/use-placement-notice.test.ts +48 -0
  239. package/src/hooks/use-placement-notice.ts +42 -0
  240. package/src/hooks/use-selected-room.ts +143 -0
  241. package/src/index.tsx +306 -39
  242. package/src/lib/catalog-panel-model.ts +39 -0
  243. package/src/lib/catalog-selection.test.ts +27 -0
  244. package/src/lib/catalog-selection.ts +8 -0
  245. package/src/lib/ceiling-edit-session.test.ts +321 -0
  246. package/src/lib/ceiling-edit-session.ts +206 -0
  247. package/src/lib/ceiling-region-session.test.ts +179 -0
  248. package/src/lib/contextual-help.test.ts +27 -1
  249. package/src/lib/contextual-help.ts +16 -9
  250. package/src/lib/continuation.test.ts +19 -1
  251. package/src/lib/continuation.ts +25 -4
  252. package/src/lib/door-interaction.ts +14 -0
  253. package/src/lib/editor-level-display.test.ts +96 -0
  254. package/src/lib/editor-level-display.ts +48 -0
  255. package/src/lib/editor-views.ts +144 -0
  256. package/src/lib/export-geometry-wait.test.ts +90 -0
  257. package/src/lib/floor-edit-session.ts +153 -0
  258. package/src/lib/floor-footprints.test.ts +563 -0
  259. package/src/lib/floor-footprints.ts +395 -0
  260. package/src/lib/floor-foundation-support.test.ts +283 -0
  261. package/src/lib/floor-opening-draft.ts +218 -0
  262. package/src/lib/floor-openings.test.ts +216 -0
  263. package/src/lib/floor-region-geometry.ts +191 -0
  264. package/src/lib/floor-region-session.test.ts +485 -0
  265. package/src/lib/floor-region-session.ts +589 -0
  266. package/src/lib/floor-region-snap.ts +101 -0
  267. package/src/lib/floor-step-hover.test.ts +32 -0
  268. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  269. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  270. package/src/lib/floorplan/dimension-drive.ts +479 -0
  271. package/src/lib/floorplan/floorplan-export.test.ts +15 -9
  272. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  273. package/src/lib/floorplan/floorplan-extension.ts +48 -0
  274. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  275. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  276. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  277. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  278. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  279. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  280. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  281. package/src/lib/floorplan/geometry.ts +13 -6
  282. package/src/lib/floorplan/index.ts +0 -9
  283. package/src/lib/floorplan/items.ts +5 -2
  284. package/src/lib/floorplan/marks.test.ts +213 -0
  285. package/src/lib/floorplan/marks.ts +238 -0
  286. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  287. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  288. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  289. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  290. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  291. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  292. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  293. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  294. package/src/lib/floorplan/schedules.test.ts +189 -0
  295. package/src/lib/floorplan/schedules.ts +339 -0
  296. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  297. package/src/lib/floorplan/sheet-export.ts +318 -0
  298. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  299. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  300. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  301. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  302. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  303. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  304. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  305. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  306. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  307. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  308. package/src/lib/floorplan/site-plan/index.ts +71 -0
  309. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  310. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  311. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  312. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  313. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  314. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  315. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  316. package/src/lib/floorplan/types.ts +1 -27
  317. package/src/lib/fresh-planar-placement.test.ts +36 -0
  318. package/src/lib/fresh-planar-placement.ts +191 -36
  319. package/src/lib/geometry-script/author.ts +152 -0
  320. package/src/lib/geometry-script/client.ts +152 -0
  321. package/src/lib/geometry-script/protocol.ts +12 -0
  322. package/src/lib/gesture-lifecycle.test.ts +595 -0
  323. package/src/lib/gesture-lifecycle.ts +290 -0
  324. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  325. package/src/lib/glb-export.test.ts +544 -16
  326. package/src/lib/glb-export.ts +256 -125
  327. package/src/lib/grid-frame.test.ts +19 -0
  328. package/src/lib/grid-frame.ts +18 -0
  329. package/src/lib/history.test.ts +6 -0
  330. package/src/lib/history.ts +7 -0
  331. package/src/lib/hud-title.ts +109 -0
  332. package/src/lib/hydration-gated-storage.ts +49 -0
  333. package/src/lib/ifc-export.test.ts +174 -0
  334. package/src/lib/ifc-export.ts +201 -0
  335. package/src/lib/ifc-parts.ts +15 -0
  336. package/src/lib/inspector-expanded.test.ts +44 -0
  337. package/src/lib/inspector-expanded.ts +36 -0
  338. package/src/lib/interaction/scope.ts +27 -1
  339. package/src/lib/keyboard-pan.test.ts +184 -0
  340. package/src/lib/keyboard-pan.ts +238 -0
  341. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  342. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  343. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  344. package/src/lib/lot/clean-ring.test.ts +186 -0
  345. package/src/lib/lot/clean-ring.ts +217 -0
  346. package/src/lib/lot/dossier.test.ts +202 -0
  347. package/src/lib/lot/dossier.ts +498 -0
  348. package/src/lib/lot/drop-in.test.ts +326 -0
  349. package/src/lib/lot/drop-in.ts +407 -0
  350. package/src/lib/lot/index.ts +43 -0
  351. package/src/lib/lot/lot-patch.test.ts +340 -0
  352. package/src/lib/lot/lot-patch.ts +394 -0
  353. package/src/lib/lot/parcel-provider.ts +43 -0
  354. package/src/lib/lot/terrain.test.ts +161 -0
  355. package/src/lib/lot/terrain.ts +266 -0
  356. package/src/lib/material-catalog-model.ts +74 -0
  357. package/src/lib/material-paint-panel-model.ts +80 -0
  358. package/src/lib/material-paint.ts +4 -0
  359. package/src/lib/mezzanine-actions.test.ts +342 -0
  360. package/src/lib/mezzanine-draft.test.ts +501 -0
  361. package/src/lib/mezzanine-draft.ts +278 -0
  362. package/src/lib/mezzanine-messages.ts +26 -0
  363. package/src/lib/mezzanine-selection.test.ts +207 -0
  364. package/src/lib/mezzanine-stairs.ts +46 -0
  365. package/src/lib/model-export.ts +3 -1
  366. package/src/lib/node-action-movement.ts +17 -6
  367. package/src/lib/node-display-label.ts +37 -0
  368. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  369. package/src/lib/orbit-floor-clearance.ts +106 -0
  370. package/src/lib/overlay-transform.test.ts +28 -0
  371. package/src/lib/overlay-transform.ts +12 -0
  372. package/src/lib/paint-commit-route.test.ts +37 -0
  373. package/src/lib/paint-commit-route.ts +17 -0
  374. package/src/lib/paint-eyedropper.test.tsx +165 -0
  375. package/src/lib/paint-eyedropper.ts +99 -0
  376. package/src/lib/paint-pass-through.test.ts +111 -0
  377. package/src/lib/paint-pass-through.ts +76 -0
  378. package/src/lib/paint-region-hover.ts +25 -0
  379. package/src/lib/paint-region-mode.ts +154 -0
  380. package/src/lib/paint-regions.test.ts +166 -0
  381. package/src/lib/paint-regions.ts +192 -0
  382. package/src/lib/paint-scope.test.ts +317 -35
  383. package/src/lib/paint-scope.ts +266 -25
  384. package/src/lib/panel-rows.ts +96 -0
  385. package/src/lib/panel-tool-options.test.ts +34 -0
  386. package/src/lib/panel-tool-options.ts +46 -0
  387. package/src/lib/parametric-node-update.ts +37 -0
  388. package/src/lib/plate-paint-affected.test.ts +300 -0
  389. package/src/lib/plate-paint-affected.ts +177 -0
  390. package/src/lib/plugin-chat.ts +50 -0
  391. package/src/lib/plugin-panels.test.ts +25 -0
  392. package/src/lib/plugin-panels.ts +94 -0
  393. package/src/lib/print-shell-compiler.ts +1 -0
  394. package/src/lib/registered-draft-snap.ts +12 -0
  395. package/src/lib/roof-duplication.ts +10 -2
  396. package/src/lib/room-assembly-overlay.test.ts +275 -0
  397. package/src/lib/room-assembly-overlay.ts +266 -0
  398. package/src/lib/room-built-on.test.ts +379 -0
  399. package/src/lib/room-built-on.ts +138 -0
  400. package/src/lib/room-construction-commands.ts +712 -0
  401. package/src/lib/room-construction.test.ts +746 -0
  402. package/src/lib/room-construction.ts +385 -0
  403. package/src/lib/room-controls-phase6.test.ts +630 -0
  404. package/src/lib/room-divide-session.ts +523 -0
  405. package/src/lib/room-handle-drag.ts +1068 -0
  406. package/src/lib/room-name-catalog.test.ts +90 -0
  407. package/src/lib/room-name-catalog.ts +168 -0
  408. package/src/lib/room-push-dimensions.test.ts +311 -0
  409. package/src/lib/room-push-dimensions.ts +251 -0
  410. package/src/lib/room-selection-commands.ts +62 -0
  411. package/src/lib/room-selection.test.ts +973 -0
  412. package/src/lib/room-selection.ts +417 -0
  413. package/src/lib/room-structure-commands.ts +94 -0
  414. package/src/lib/room-structure.test.ts +921 -0
  415. package/src/lib/room-transform-session.ts +570 -0
  416. package/src/lib/room-zone-routing.test.ts +294 -0
  417. package/src/lib/room-zone-routing.ts +197 -0
  418. package/src/lib/scene-clipboard.test.ts +364 -0
  419. package/src/lib/scene-clipboard.ts +229 -48
  420. package/src/lib/scene-material-refs.test.ts +79 -0
  421. package/src/lib/scene-material-refs.ts +79 -0
  422. package/src/lib/scene.test.ts +87 -0
  423. package/src/lib/scene.ts +39 -45
  424. package/src/lib/selection-routing.test.ts +129 -45
  425. package/src/lib/selection-routing.ts +53 -74
  426. package/src/lib/session-writes.test.ts +120 -0
  427. package/src/lib/session-writes.ts +205 -0
  428. package/src/lib/sidebar-panel.ts +62 -0
  429. package/src/lib/site-boundary.test.ts +4 -6
  430. package/src/lib/site-boundary.ts +1 -1
  431. package/src/lib/snapping-mode.test.ts +17 -2
  432. package/src/lib/snapping-mode.ts +19 -7
  433. package/src/lib/spatial-drag-plane.test.ts +53 -0
  434. package/src/lib/spatial-drag-plane.ts +19 -0
  435. package/src/lib/spatial-pointer-input.test.ts +72 -0
  436. package/src/lib/spatial-pointer-input.ts +62 -0
  437. package/src/lib/spatial-snap.test.ts +47 -0
  438. package/src/lib/terrace-draft.test.ts +145 -0
  439. package/src/lib/terrace-draft.ts +213 -0
  440. package/src/lib/terrain-panel-model.ts +117 -0
  441. package/src/lib/tool-cursor-icon.test.ts +16 -0
  442. package/src/lib/tool-cursor-icon.ts +11 -0
  443. package/src/lib/typing-target.ts +9 -0
  444. package/src/lib/units.ts +77 -39
  445. package/src/lib/vector-edges.test.ts +58 -0
  446. package/src/lib/vector-edges.ts +400 -0
  447. package/src/lib/viewer-selection.test.ts +577 -0
  448. package/src/lib/viewer-selection.ts +569 -0
  449. package/src/lib/wall-draw-variant.test.ts +71 -0
  450. package/src/lib/wall-draw-variant.ts +91 -0
  451. package/src/lib/wall-paint-region-session.ts +246 -0
  452. package/src/lib/wall-paint-region.test.ts +373 -0
  453. package/src/lib/wall-polygon-draft.test.ts +262 -0
  454. package/src/lib/wall-polygon-draft.ts +286 -0
  455. package/src/lib/wall-push.test.ts +453 -0
  456. package/src/lib/wall-region-face.ts +352 -0
  457. package/src/lib/wall-region-handles.test.ts +304 -0
  458. package/src/lib/wall-region-handles.ts +368 -0
  459. package/src/lib/wall-region-list.test.ts +119 -0
  460. package/src/lib/wall-region-list.ts +136 -0
  461. package/src/lib/wall-region-snap.ts +110 -0
  462. package/src/lib/window-interaction.ts +14 -0
  463. package/src/lib/world-grid-snap.test.ts +48 -0
  464. package/src/lib/world-grid-snap.ts +9 -5
  465. package/src/lib/zone-content.ts +4 -1
  466. package/src/lib/zone-name.test.ts +17 -0
  467. package/src/lib/zone-name.ts +17 -0
  468. package/src/store/opens-in-select-mode.test.ts +223 -0
  469. package/src/store/paint-mode.test.ts +188 -0
  470. package/src/store/rotation-snapping.test.ts +53 -0
  471. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  472. package/src/store/tool-mode.test.ts +137 -6
  473. package/src/store/use-delete-confirmation.ts +28 -1
  474. package/src/store/use-drawing-view.test.ts +24 -3
  475. package/src/store/use-drawing-view.ts +29 -1
  476. package/src/store/use-editor.tsx +442 -323
  477. package/src/store/use-floorplan-draft-preview.ts +23 -1
  478. package/src/store/use-floorplan-marquee.ts +12 -2
  479. package/src/store/use-interaction-scope.ts +155 -6
  480. package/src/store/use-quick-measurement-hud.ts +3 -3
  481. package/src/store/use-stair-build-preview.ts +13 -5
  482. package/src/store/use-wall-move-ghosts.ts +4 -2
  483. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  484. package/src/store/use-wall-region-handles-selection.ts +139 -0
  485. package/src/store/view-layout.test.ts +175 -0
  486. package/src/store/view-layout.ts +196 -0
  487. package/src/test-utils/immediate-animation-frames.ts +16 -0
  488. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  489. package/src/test-utils/selection-harness.ts +82 -0
  490. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  491. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  492. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  493. package/src/components/tools/fence/fence-drafting.ts +0 -266
  494. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  495. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  496. package/src/lib/floorplan/stairs.ts +0 -489
  497. package/src/lib/level-duplication.test.ts +0 -144
  498. package/src/lib/level-duplication.ts +0 -195
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Lot drop-in — the store-aware half. One call does what "click a button,
3
+ * the lot drops in" needs:
4
+ *
5
+ * 1. `resolve` — address (or a picked suggestion's
6
+ * coordinates) → the real parcel ring, APN, county, zoning.
7
+ * 2. `roads` — the streets around the lot from OpenStreetMap,
8
+ * in the lot's own frame (fail-soft: no roads is not an error).
9
+ * 3. `sitePatchFromParcel` — the site node patch: ring, address,
10
+ * provenance, the street-facing front edge, planning-default setbacks
11
+ * when the site has none, north up.
12
+ * 4. The scene's site node is updated (created at the root when the scene
13
+ * has none) and a building that fell outside the new ring is
14
+ * re-centred on it — `building.position` only, never the walls.
15
+ * 5. `elevation` — USGS ground over the lot into the site's
16
+ * heightfield (`site.terrain`, terrain.ts; fail-soft, flat lots write
17
+ * nothing), so a foundation can read the hill.
18
+ *
19
+ * Used by the Lot rail panel, the Generate panel (drop in, then generate)
20
+ * and the Site inspector's "Find parcel", so every path behaves the same.
21
+ * Nothing here is invented: a lookup that fails says why and writes nothing.
22
+ * The calls go to the host's parcel provider (`setParcelProvider`).
23
+ */
24
+ import {
25
+ type AnyNode,
26
+ type AnyNodeId,
27
+ type BuildingNode,
28
+ generateId,
29
+ SiteNode,
30
+ useScene,
31
+ } from '@pascal-app/core'
32
+ import {
33
+ buildingRecentreOffset,
34
+ buildSitePlanDrawing,
35
+ } from '../floorplan/site-plan/build-site-plan-drawing'
36
+ import {
37
+ answered,
38
+ type CodeBasisData,
39
+ contourLinesFromDossier,
40
+ type Dossier,
41
+ describeDossier,
42
+ type ElevationData,
43
+ type FloodData,
44
+ fetchDossier,
45
+ frontageSegmentsMetres,
46
+ type ParcelData,
47
+ parcelRingMetres,
48
+ setbacksCitation,
49
+ setbacksFromZoning,
50
+ siteFactsFromDossier,
51
+ type ZoningData,
52
+ } from './dossier'
53
+ import {
54
+ type DropInInput,
55
+ describeLotSummary,
56
+ type LotRoad,
57
+ type LotSummary,
58
+ type ParcelResolveData,
59
+ sitePatchFromParcel,
60
+ } from './lot-patch'
61
+ import { getParcelProvider, NO_PARCEL_SERVICE, type ParcelProvider } from './parcel-provider'
62
+ import {
63
+ describeTerrainSample,
64
+ sampleLotTerrain,
65
+ type TerrainSampleResult,
66
+ type TerrainSampleSummary,
67
+ } from './terrain'
68
+
69
+ export interface LotDropInResult {
70
+ ok: boolean
71
+ error?: string
72
+ siteId?: string
73
+ summary?: LotSummary
74
+ /** True when a building was moved back onto the new lot. */
75
+ recentred?: boolean
76
+ /** Why no roads were used, when the road lookup failed ('' when it worked). */
77
+ roadsFailure?: string
78
+ /** The USGS terrain read, when it worked (flat lots write no heightfield). */
79
+ terrain?: TerrainSampleSummary | null
80
+ /** Why no terrain was read ('' when it worked). */
81
+ terrainFailure?: string
82
+ /** The Pascal Map dossier's status line, or why none was read ('' when it worked). */
83
+ dossierLine?: string
84
+ dossierFailure?: string
85
+ /** One status line. */
86
+ message: string
87
+ }
88
+
89
+ export interface DropInOptions {
90
+ /** The site to write; default = the scene's first root site (created when there is none). */
91
+ siteId?: string
92
+ /** Skip the road lookup (the front edge stays north-facing). */
93
+ roads?: boolean
94
+ roadsRadiusM?: number
95
+ /** Skip the USGS terrain read (the ground stays flat). */
96
+ terrain?: boolean
97
+ terrainDeadlineMs?: number
98
+ /** Skip the Pascal Map dossier (the parcel / roads / elevation routes alone). */
99
+ dossier?: boolean
100
+ /** Default = the provider the host set. */
101
+ provider?: ParcelProvider
102
+ }
103
+
104
+ type RoadsResponse = { ok: boolean; reason?: string; roads?: LotRoad[] }
105
+
106
+ /** The scene's site node — the requested one, else the first root site. */
107
+ export function findSiteNode(siteId?: string): SiteNode | null {
108
+ const s = useScene.getState()
109
+ if (siteId) {
110
+ const n = s.nodes[siteId as AnyNodeId] as AnyNode | undefined
111
+ if (n?.type === 'site') return n as SiteNode
112
+ }
113
+ for (const id of s.rootNodeIds) {
114
+ const n = s.nodes[id as AnyNodeId] as AnyNode | undefined
115
+ if (n?.type === 'site') return n as SiteNode
116
+ }
117
+ return null
118
+ }
119
+
120
+ /** Resolve the parcel, map the streets, write the site. */
121
+ export async function dropInLot(
122
+ input: DropInInput,
123
+ options: DropInOptions = {},
124
+ ): Promise<LotDropInResult> {
125
+ const provider = options.provider ?? getParcelProvider()
126
+ if (!provider) return { ok: false, error: 'no parcel service', message: NO_PARCEL_SERVICE }
127
+ const address = (input.address ?? '').trim()
128
+ const hasCoords = Number.isFinite(input.latitude) && Number.isFinite(input.longitude)
129
+ if (!address && !hasCoords)
130
+ return { ok: false, error: 'address is required', message: 'Type an address first.' }
131
+
132
+ // The Pascal Map dossier first — the parcel with its frontage, the
133
+ // zoning, flood, code basis, utilities, soils, wetlands, structures and
134
+ // boundaries in one read. Where its parcel plane covers the lot the ring
135
+ // comes from it; where it does not, the parcel route below stands and
136
+ // the sections that DID answer (flood, utilities, …) still ride the site.
137
+ let dossier: Dossier | null = null
138
+ let dossierFailure = ''
139
+ if (options.dossier !== false) {
140
+ const read = await fetchDossier(provider, {
141
+ address,
142
+ ...(hasCoords ? { latitude: input.latitude, longitude: input.longitude } : {}),
143
+ })
144
+ if (read.ok) dossier = read.dossier
145
+ else dossierFailure = read.reason
146
+ // The parcel-wide 3DEP terrain warms up on the first look at a parcel
147
+ // (~30 s, terrain_status 'computing'): one more ask for the elevation
148
+ // section alone, after a wait, so the contour lines land on the first
149
+ // drop-in and not the second.
150
+ const el = dossier ? answered<ElevationData>(dossier, 'elevation') : null
151
+ if (dossier && el?.terrain_status === 'computing' && options.terrainDeadlineMs !== 0) {
152
+ await new Promise((r) => setTimeout(r, Math.min(30_000, options.terrainDeadlineMs ?? 20_000)))
153
+ const again = await fetchDossier(provider, {
154
+ address,
155
+ ...(hasCoords ? { latitude: input.latitude, longitude: input.longitude } : {}),
156
+ layers: ['elevation'],
157
+ })
158
+ if (again.ok && again.dossier.layers?.elevation) {
159
+ dossier = {
160
+ ...dossier,
161
+ layers: { ...dossier.layers, elevation: again.dossier.layers.elevation },
162
+ }
163
+ }
164
+ }
165
+ } else dossierFailure = 'skipped'
166
+ const dossierParcel = dossier ? answered<ParcelData>(dossier, 'parcel') : null
167
+ const dossierOrigin: [number, number] | null =
168
+ dossier && Number.isFinite(dossier.point?.lng) && Number.isFinite(dossier.point?.lat)
169
+ ? [dossier.point.lng, dossier.point.lat]
170
+ : null
171
+ const dossierRing =
172
+ dossierParcel && dossierOrigin ? parcelRingMetres(dossierParcel, dossierOrigin) : []
173
+
174
+ let data: ParcelResolveData
175
+ if (dossier && dossierParcel && dossierOrigin && dossierRing.length >= 3) {
176
+ const boundaries = answered<{
177
+ state?: string | null
178
+ zip?: string | null
179
+ county?: { name?: string | null }
180
+ }>(dossier, 'boundaries')
181
+ const zoning = answered<ZoningData>(dossier, 'zoning')
182
+ const state = boundaries?.state ?? input.state
183
+ data = {
184
+ ok: true,
185
+ apn: dossierParcel.parcel_key,
186
+ county: dossierParcel.county?.name ?? boundaries?.county?.name ?? undefined,
187
+ state: state ?? undefined,
188
+ zip: dossierParcel.situs_address?.zip ?? boundaries?.zip ?? undefined,
189
+ zoning: zoning?.district,
190
+ lotAreaSqFt:
191
+ typeof dossierParcel.area_m2 === 'number' ? dossierParcel.area_m2 * 10.7639 : undefined,
192
+ originLngLat: dossierOrigin,
193
+ geocodedBy: 'pascal-map',
194
+ matchPrecision: dossier.address?.precision,
195
+ notes: [
196
+ `Parcel from Pascal Map${dossierParcel.vintage ? ` (${dossierParcel.vintage} roll)` : ''} — the county fabric; DRAFT, not a survey. Confirm corners before staking.`,
197
+ ],
198
+ polygonM: dossierRing.map((p) => [p[0], p[1]] as [number, number]),
199
+ address: {
200
+ street: dossierParcel.situs_address?.line1 ?? input.street,
201
+ city: dossierParcel.situs_address?.city ?? input.city,
202
+ state: state ?? undefined,
203
+ zip: dossierParcel.situs_address?.zip ?? boundaries?.zip ?? input.zip,
204
+ },
205
+ }
206
+ } else {
207
+ try {
208
+ data = (await provider('resolve', {
209
+ address,
210
+ ...(hasCoords
211
+ ? { latitude: input.latitude, longitude: input.longitude, state: input.state }
212
+ : {}),
213
+ })) as ParcelResolveData
214
+ } catch (error) {
215
+ const message = error instanceof Error ? error.message : 'Parcel lookup failed.'
216
+ return { ok: false, error: message, message }
217
+ }
218
+ if (!data.ok || !data.polygonM || data.polygonM.length < 3) {
219
+ const message = data.error ? `No parcel: ${data.error}` : 'No parcel found for that address.'
220
+ return { ok: false, error: data.error ?? 'no parcel', message }
221
+ }
222
+ }
223
+ // what the dossier adds to the patch: the frontage, the zoning, the facts
224
+ const extras = dossier
225
+ ? (() => {
226
+ const zoning = answered<ZoningData>(dossier, 'zoning')
227
+ const zoningSection = dossier.layers?.zoning
228
+ const setbacks = setbacksFromZoning(zoning)
229
+ return {
230
+ frontageSegmentsM:
231
+ dossierParcel && dossierOrigin && data.geocodedBy === 'pascal-map'
232
+ ? frontageSegmentsMetres(dossierParcel, dossierOrigin)
233
+ : [],
234
+ setbacks,
235
+ setbacksSource:
236
+ zoning && setbacks ? setbacksCitation(zoning, zoningSection?.source) : undefined,
237
+ dimensionalNote: zoning?.dimensional_note ?? null,
238
+ zone: zoning?.district,
239
+ facts: siteFactsFromDossier(dossier),
240
+ line: describeDossier(dossier),
241
+ }
242
+ })()
243
+ : null
244
+
245
+ // Streets around the lot — fail-soft, but asked twice: the Overpass
246
+ // mirrors time out now and then, and without the street the front edge
247
+ // falls back to "most north-facing", which on a lot fronting a street to
248
+ // the south turns the whole house round between one run and the next.
249
+ let roads: LotRoad[] | null = null
250
+ let roadsFailure = ''
251
+ if (options.roads !== false && data.originLngLat) {
252
+ for (let attempt = 0; attempt < 2 && !roads; attempt++) {
253
+ const asked = Date.now()
254
+ try {
255
+ const [lng, lat] = data.originLngLat
256
+ const r = (await provider('roads', {
257
+ latitude: lat,
258
+ longitude: lng,
259
+ originLngLat: data.originLngLat,
260
+ radiusM: options.roadsRadiusM,
261
+ })) as RoadsResponse
262
+ if (r.ok && Array.isArray(r.roads)) {
263
+ roads = r.roads
264
+ roadsFailure = ''
265
+ } else roadsFailure = r.reason ?? 'no roads'
266
+ } catch (error) {
267
+ roadsFailure = error instanceof Error ? error.message : 'road lookup failed'
268
+ }
269
+ // a quick miss is worth one more ask; a slow one (every mirror timed out) would only be
270
+ // slow again, and the lot is waiting — the front edge falls back instead
271
+ if (!roads && Date.now() - asked > 4000) break
272
+ }
273
+ } else if (options.roads === false) {
274
+ roadsFailure = 'skipped'
275
+ }
276
+
277
+ const scene = useScene.getState()
278
+ let site = findSiteNode(options.siteId)
279
+ if (!site) {
280
+ site = SiteNode.parse({ id: generateId('site'), type: 'site', name: 'Site' })
281
+ scene.createNodes([{ node: site as AnyNode }])
282
+ }
283
+ const computed = sitePatchFromParcel(site, input, data, roads, undefined, extras)
284
+ if (!computed)
285
+ return {
286
+ ok: false,
287
+ error: 'parcel geometry was unusable',
288
+ message: 'The parcel geometry was unusable.',
289
+ }
290
+ // the surveyed contour lines ride the site with the patch (the site plan
291
+ // draws them over the heightfield's own); absent without 3DEP lines
292
+ const contours = dossier && dossierOrigin ? contourLinesFromDossier(dossier, dossierOrigin) : null
293
+ useScene
294
+ .getState()
295
+ .updateNode(
296
+ site.id as AnyNodeId,
297
+ { ...computed.patch, terrainContours: contours ?? undefined } as Partial<AnyNode>,
298
+ )
299
+
300
+ // The ground over the lot — fail-soft. A sloping lot writes the
301
+ // heightfield; a flat one (or a failed read) clears any terrain the
302
+ // previous lot left behind so the site never shows another parcel's hill.
303
+ let terrain: TerrainSampleSummary | null = null
304
+ let terrainFailure = ''
305
+ if (options.terrain !== false) {
306
+ const read: TerrainSampleResult = data.originLngLat
307
+ ? await sampleLotTerrain(computed.patch.polygon?.points ?? [], data.originLngLat, {
308
+ provider,
309
+ ...(options.terrainDeadlineMs ? { deadlineMs: options.terrainDeadlineMs } : {}),
310
+ })
311
+ : { ok: false, reason: 'no parcel origin' }
312
+ const current = useScene.getState().nodes[site.id as AnyNodeId]
313
+ const { terrainSample: _previous, ...meta } =
314
+ current &&
315
+ typeof current.metadata === 'object' &&
316
+ current.metadata !== null &&
317
+ !Array.isArray(current.metadata)
318
+ ? (current.metadata as Record<string, unknown>)
319
+ : {}
320
+ if (read.ok && read.summary) {
321
+ terrain = read.summary
322
+ useScene.getState().updateNode(
323
+ site.id as AnyNodeId,
324
+ {
325
+ terrain: read.terrain,
326
+ metadata: { ...meta, terrainSample: read.summary },
327
+ } as unknown as Partial<AnyNode>,
328
+ )
329
+ } else {
330
+ terrainFailure = read.reason ?? 'no terrain'
331
+ useScene
332
+ .getState()
333
+ .updateNode(
334
+ site.id as AnyNodeId,
335
+ { terrain: undefined, metadata: meta } as unknown as Partial<AnyNode>,
336
+ )
337
+ }
338
+ } else {
339
+ terrainFailure = 'skipped'
340
+ }
341
+
342
+ // Centre the building on the lot when its footprint fell outside the new ring.
343
+ let recentred = false
344
+ const after = useScene.getState()
345
+ const drawing = buildSitePlanDrawing({
346
+ collections: after.collections,
347
+ installedPlugins: after.installedPlugins,
348
+ materials: after.materials,
349
+ nodes: after.nodes,
350
+ rootNodeIds: after.rootNodeIds,
351
+ })
352
+ const offset = buildingRecentreOffset(drawing.meta.lot, drawing.meta.footprintBounds)
353
+ if (offset && drawing.meta.buildingId) {
354
+ const building = after.nodes[drawing.meta.buildingId as AnyNodeId] as BuildingNode | undefined
355
+ if (building) {
356
+ after.updateNode(building.id, {
357
+ position: [
358
+ building.position[0] + offset[0],
359
+ building.position[1],
360
+ building.position[2] + offset[1],
361
+ ],
362
+ })
363
+ recentred = true
364
+ }
365
+ }
366
+
367
+ const extra = [
368
+ recentred ? 'building re-centred' : '',
369
+ roadsFailure && roadsFailure !== 'skipped' ? `roads unavailable (${roadsFailure})` : '',
370
+ describeTerrainSample(terrain, terrainFailure),
371
+ dossierFailure && dossierFailure !== 'skipped'
372
+ ? `Pascal Map unavailable (${dossierFailure})`
373
+ : '',
374
+ ...(dossier ? dossierHeadline(dossier) : []),
375
+ ]
376
+ return {
377
+ ok: true,
378
+ siteId: site.id,
379
+ summary: computed.summary,
380
+ recentred,
381
+ roadsFailure,
382
+ terrain,
383
+ terrainFailure,
384
+ dossierLine: dossier ? describeDossier(dossier) : '',
385
+ dossierFailure,
386
+ message: describeLotSummary(computed.summary, extra.filter(Boolean)),
387
+ }
388
+ }
389
+
390
+ /** The facts a reader wants on the status line: the flood zone and the wind speed, when the dossier answered them. */
391
+ function dossierHeadline(dossier: Dossier): string[] {
392
+ const out: string[] = []
393
+ const flood = answered<FloodData>(dossier, 'flood')
394
+ if (flood?.zone_at_point?.zone) {
395
+ const z = flood.zone_at_point
396
+ out.push(
397
+ `flood zone ${z.zone}${typeof z.base_flood_elevation_ft === 'number' ? ` (BFE ${z.base_flood_elevation_ft} ft ${z.bfe_datum ?? ''})`.replace(/ \)$/, ')') : ''}${flood.firm_panel?.panel ? `, FIRM ${flood.firm_panel.panel}` : ''}`,
398
+ )
399
+ }
400
+ const code = answered<CodeBasisData>(dossier, 'code_basis')
401
+ if (typeof code?.wind_speed_mph === 'number') {
402
+ out.push(
403
+ `wind ${code.wind_speed_mph} mph${code.wind_borne_debris_region ? ' (debris region)' : ''}${code.climate_zone_iecc ? `, zone ${code.climate_zone_iecc}` : ''}`,
404
+ )
405
+ }
406
+ return out
407
+ }
@@ -0,0 +1,43 @@
1
+ export {
2
+ type CleanRingResult,
3
+ cleanLotRing,
4
+ describeRingCleanup,
5
+ dropDuplicateVertices,
6
+ mergeCollinearVertices,
7
+ squareCornerArcs,
8
+ } from './clean-ring'
9
+ export { type DropInOptions, dropInLot, findSiteNode, type LotDropInResult } from './drop-in'
10
+ export {
11
+ DEFAULT_SETBACKS_FT,
12
+ DEFAULT_SETBACKS_M,
13
+ DEFAULT_SETBACKS_SOURCE,
14
+ type DropInInput,
15
+ describeFrontEdge,
16
+ describeLotSummary,
17
+ type LotRoad,
18
+ type LotSummary,
19
+ type ParcelResolveData,
20
+ STREET_CLASSES,
21
+ sitePatchFromParcel,
22
+ } from './lot-patch'
23
+ export {
24
+ getParcelProvider,
25
+ type ParcelEndpoint,
26
+ type ParcelProvider,
27
+ setParcelProvider,
28
+ useParcelProvider,
29
+ } from './parcel-provider'
30
+ export {
31
+ coarseHeightAt,
32
+ DEFAULT_GRID_N,
33
+ describeTerrainSample,
34
+ fieldFromSamples,
35
+ gridOver,
36
+ localMetresToLngLat,
37
+ MIN_RELIEF_M,
38
+ type SampleGrid,
39
+ type SampleOptions,
40
+ sampleLotTerrain,
41
+ type TerrainSampleResult,
42
+ type TerrainSampleSummary,
43
+ } from './terrain'