@pascal-app/editor 1.0.3 → 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.
- package/package.json +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- 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'
|