@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.
- package/package.json +16 -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 +221 -128
- 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 +37 -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 +536 -487
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/grid.tsx +31 -7
- package/src/components/editor/group-actions.ts +59 -11
- package/src/components/editor/group-floating-action-menu.tsx +19 -9
- package/src/components/editor/group-move-3d.ts +4 -6
- package/src/components/editor/group-rotate-handle.tsx +81 -35
- package/src/components/editor/group-selection-box-3d.tsx +25 -17
- package/src/components/editor/group-transform-shared.test.ts +126 -1
- package/src/components/editor/group-transform-shared.ts +79 -73
- package/src/components/editor/handles/handle-arrow.tsx +72 -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 +272 -131
- package/src/components/editor/index.tsx +316 -176
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +70 -70
- package/src/components/editor/node-arrow-handles.tsx +167 -14
- 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 +132 -0
- 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 +114 -163
- package/src/components/editor/wall-opening-highlights.tsx +2 -3
- 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-group-move.tsx +17 -24
- 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-registered-tool-layer.tsx +33 -12
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
- 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-measurement.tsx +166 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
- 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 +31 -66
- package/src/components/tools/__fixtures__/hosting.ts +10 -0
- package/src/components/tools/item/item-surface-placement.test.ts +3 -9
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +63 -47
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +207 -97
- package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
- 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/item-surface-move.test.ts +10 -2
- package/src/components/tools/registry/item-surface-move.ts +46 -24
- package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
- package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
- 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/affordance-dispatch.test.ts +24 -0
- package/src/components/tools/shared/affordance-dispatch.ts +13 -0
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
- package/src/components/tools/shared/pointer-support-cap.ts +17 -5
- package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
- package/src/components/tools/shared/surface-rejection.tsx +14 -0
- 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 +82 -23
- 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 +101 -91
- package/src/components/ui/controls/material-picker.tsx +8 -67
- 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/controls/slider-control.tsx +76 -31
- 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 +348 -19
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
- 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-height-mode.tsx +27 -21
- 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 +7 -19
- 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/shortcut-token.tsx +4 -0
- 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/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- 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-visibility.test.ts +35 -0
- package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
- package/src/components/viewer/floorplan-preview.tsx +6 -18
- 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 +9 -9
- package/src/hooks/use-auto-save.integration.test.tsx +311 -0
- package/src/hooks/use-auto-save.ts +55 -14
- package/src/hooks/use-ceiling-events.test.ts +1 -0
- package/src/hooks/use-ceiling-events.ts +2 -0
- package/src/hooks/use-grid-events.test.ts +2 -0
- package/src/hooks/use-grid-events.ts +16 -11
- package/src/hooks/use-keyboard.test.ts +182 -0
- package/src/hooks/use-keyboard.ts +122 -91
- 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 +306 -39
- package/src/lib/catalog-panel-model.ts +39 -0
- package/src/lib/catalog-selection.test.ts +27 -0
- package/src/lib/catalog-selection.ts +8 -0
- 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/contextual-help.test.ts +27 -1
- package/src/lib/contextual-help.ts +16 -9
- 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 +15 -9
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +48 -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 +191 -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 +544 -16
- package/src/lib/glb-export.ts +256 -125
- package/src/lib/grid-frame.test.ts +19 -0
- package/src/lib/grid-frame.ts +18 -0
- 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 +27 -1
- package/src/lib/keyboard-pan.test.ts +184 -0
- package/src/lib/keyboard-pan.ts +238 -0
- 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 +74 -0
- package/src/lib/material-paint-panel-model.ts +80 -0
- 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/overlay-transform.test.ts +28 -0
- package/src/lib/overlay-transform.ts +12 -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 +96 -0
- package/src/lib/panel-tool-options.test.ts +34 -0
- package/src/lib/panel-tool-options.ts +46 -0
- package/src/lib/parametric-node-update.ts +37 -0
- 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 +87 -0
- package/src/lib/scene.ts +39 -45
- 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 +17 -2
- package/src/lib/snapping-mode.ts +19 -7
- package/src/lib/spatial-drag-plane.test.ts +53 -0
- package/src/lib/spatial-drag-plane.ts +19 -0
- package/src/lib/spatial-pointer-input.test.ts +72 -0
- package/src/lib/spatial-pointer-input.ts +62 -0
- package/src/lib/spatial-snap.test.ts +47 -0
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/terrain-panel-model.ts +117 -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/world-grid-snap.test.ts +48 -0
- package/src/lib/world-grid-snap.ts +9 -5
- 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/rotation-snapping.test.ts +53 -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 +442 -323
- package/src/store/use-floorplan-draft-preview.ts +23 -1
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-interaction-scope.ts +155 -6
- 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,385 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyNode,
|
|
3
|
+
area,
|
|
4
|
+
type BoundaryNode,
|
|
5
|
+
type BoundarySpan,
|
|
6
|
+
type CeilingNode,
|
|
7
|
+
calculateLevelMiters,
|
|
8
|
+
containsPoint,
|
|
9
|
+
difference,
|
|
10
|
+
type ExtractedRoom,
|
|
11
|
+
extractRooms,
|
|
12
|
+
getWallPlanFootprint,
|
|
13
|
+
intersection,
|
|
14
|
+
isDerivedNode,
|
|
15
|
+
type Polygon,
|
|
16
|
+
roomDrawnFloor,
|
|
17
|
+
type SlabNode,
|
|
18
|
+
type SpanRef,
|
|
19
|
+
type StructureNodes,
|
|
20
|
+
union,
|
|
21
|
+
type WallNode,
|
|
22
|
+
type ZoneNode,
|
|
23
|
+
} from '@pascal-app/core'
|
|
24
|
+
|
|
25
|
+
export type ConstructionPresence = 'present' | 'partial' | 'absent'
|
|
26
|
+
|
|
27
|
+
/** The buttons a row offers, derived with its presence so the two never disagree. */
|
|
28
|
+
export type ConstructionActions = {
|
|
29
|
+
add: boolean
|
|
30
|
+
remove: boolean
|
|
31
|
+
/** Ceiling only: adopt the hand-drawn ceiling over the room as its ceiling. */
|
|
32
|
+
useExisting?: boolean
|
|
33
|
+
/** Ceiling only: swap the hand-drawn ceiling for one generated from the room. */
|
|
34
|
+
replace?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RoomConstructionState = {
|
|
38
|
+
zoneId: string
|
|
39
|
+
name: string
|
|
40
|
+
floor: {
|
|
41
|
+
state: ConstructionPresence
|
|
42
|
+
/** `hasFloor !== false`. */
|
|
43
|
+
intent: boolean
|
|
44
|
+
/** Fraction of the room footprint covered by its plate or manual slabs, 0..1. */
|
|
45
|
+
coverage: number
|
|
46
|
+
actions: ConstructionActions
|
|
47
|
+
}
|
|
48
|
+
walls: {
|
|
49
|
+
state: ConstructionPresence
|
|
50
|
+
/** Wall spans no other room faces: what Remove turns into separators. */
|
|
51
|
+
removable: SpanRef[]
|
|
52
|
+
/** Separator spans: what Add turns into walls. */
|
|
53
|
+
separators: SpanRef[]
|
|
54
|
+
/** Distinct walls this room shares with a neighbour; they stay on Remove. */
|
|
55
|
+
sharedWallIds: string[]
|
|
56
|
+
actions: ConstructionActions
|
|
57
|
+
}
|
|
58
|
+
ceiling: {
|
|
59
|
+
state: ConstructionPresence
|
|
60
|
+
/** `hasCeiling !== false`. */
|
|
61
|
+
intent: boolean
|
|
62
|
+
/** The room's own (derived) ceiling. */
|
|
63
|
+
ceilingId: string | null
|
|
64
|
+
/** Items hanging from the room's own ceiling: its `children`. */
|
|
65
|
+
hostedIds: string[]
|
|
66
|
+
/** Hand-drawn ceilings standing in for a missing room ceiling. */
|
|
67
|
+
manualIds: string[]
|
|
68
|
+
/** Items hanging from those hand-drawn ceilings: their `children`. */
|
|
69
|
+
manualHostedIds: string[]
|
|
70
|
+
actions: ConstructionActions
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Coverage below/above these reads as Absent/Present; anything between is Partial.
|
|
75
|
+
const COVERED = 0.98
|
|
76
|
+
const UNCOVERED = 0.02
|
|
77
|
+
// Mirrors core's ORPHAN_MERGE_COVERAGE_THRESHOLD: a hand-drawn ceiling covering
|
|
78
|
+
// this share of a room stops the reconciler from generating the room's own.
|
|
79
|
+
const MANUAL_CEILING_BLOCKS = 0.6
|
|
80
|
+
const OPENING_HOLES = new Set(['stair', 'elevator'])
|
|
81
|
+
// Without the type guard: a ceiling that is not derived is still a CeilingNode.
|
|
82
|
+
const derived = (node: AnyNode): boolean => isDerivedNode(node)
|
|
83
|
+
|
|
84
|
+
const footprint = (node: { polygon: [number, number][]; holes?: [number, number][][] }) => ({
|
|
85
|
+
outer: node.polygon,
|
|
86
|
+
holes: node.holes ?? [],
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
function coverageOf(zone: ZoneNode, covers: Polygon[]) {
|
|
90
|
+
const room = footprint(zone)
|
|
91
|
+
const total = area([room])
|
|
92
|
+
if (!(total > 0 && covers.length)) return 0
|
|
93
|
+
return Math.min(1, area(intersection(room, union(covers))) / total)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function presence(coverage: number): ConstructionPresence {
|
|
97
|
+
if (coverage >= COVERED) return 'present'
|
|
98
|
+
return coverage > UNCOVERED ? 'partial' : 'absent'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// One extraction per level boundary set: intent edits (name, finishes,
|
|
102
|
+
// hasFloor/hasCeiling) keep every boundary node identical and reuse it.
|
|
103
|
+
const facesByLevel = new Map<string, { boundaries: BoundaryNode[]; faces: ExtractedRoom[] }>()
|
|
104
|
+
|
|
105
|
+
function levelFaces(nodes: StructureNodes, levelId: string) {
|
|
106
|
+
const boundaries = Object.values(nodes).filter(
|
|
107
|
+
(node): node is BoundaryNode =>
|
|
108
|
+
node.parentId === levelId && (node.type === 'wall' || node.type === 'separator'),
|
|
109
|
+
)
|
|
110
|
+
const cached = facesByLevel.get(levelId)
|
|
111
|
+
if (
|
|
112
|
+
cached &&
|
|
113
|
+
cached.boundaries.length === boundaries.length &&
|
|
114
|
+
cached.boundaries.every((node, i) => node === boundaries[i])
|
|
115
|
+
)
|
|
116
|
+
return cached.faces
|
|
117
|
+
const faces = extractRooms(boundaries)
|
|
118
|
+
facesByLevel.set(levelId, { boundaries, faces })
|
|
119
|
+
return faces
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Mirrors core's `roomFace`, which setZoneEdges and lockOutsideFaces use to
|
|
123
|
+
// validate spans: the face with the largest overlap with the zone footprint.
|
|
124
|
+
function zoneFace(zone: ZoneNode, faces: ExtractedRoom[]) {
|
|
125
|
+
let best: ExtractedRoom | undefined
|
|
126
|
+
let bestOverlap = 0
|
|
127
|
+
for (const face of faces) {
|
|
128
|
+
const overlap = area(
|
|
129
|
+
intersection(footprint(zone), { outer: face.referencePolygon, holes: face.holes }),
|
|
130
|
+
)
|
|
131
|
+
if (overlap > bestOverlap) {
|
|
132
|
+
best = face
|
|
133
|
+
bestOverlap = overlap
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return best
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The room's walkable area: its boundary face minus the footprints of the
|
|
141
|
+
* walls on it — the same `clear` polygon the reconciler fits plates and
|
|
142
|
+
* ceilings to. Null when the room has no face.
|
|
143
|
+
*/
|
|
144
|
+
export function roomClearPolygon(nodes: StructureNodes, zoneId: string): Polygon | null {
|
|
145
|
+
const zone = nodes[zoneId]
|
|
146
|
+
if (zone?.type !== 'zone' || !zone.parentId) return null
|
|
147
|
+
const face = zoneFace(zone, levelFaces(nodes, zone.parentId))
|
|
148
|
+
if (!face) return null
|
|
149
|
+
const polygon = { outer: face.referencePolygon, holes: face.holes }
|
|
150
|
+
const wallIds = new Set(face.spans.filter((s) => s.kind === 'wall').map((s) => s.boundaryId))
|
|
151
|
+
if (!wallIds.size) return polygon
|
|
152
|
+
const walls = Object.values(nodes).filter(
|
|
153
|
+
(node): node is WallNode => node.type === 'wall' && node.parentId === zone.parentId,
|
|
154
|
+
)
|
|
155
|
+
const miters = calculateLevelMiters(walls)
|
|
156
|
+
const footprints = walls
|
|
157
|
+
.filter((wall) => wallIds.has(wall.id))
|
|
158
|
+
.map((wall) => getWallPlanFootprint(wall, miters).map(({ x, y }): [number, number] => [x, y]))
|
|
159
|
+
return difference(polygon, union(footprints)).sort((a, b) => area([b]) - area([a]))[0] ?? null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const overlaps = (a: SpanRef, b: SpanRef) =>
|
|
163
|
+
a.boundaryId === b.boundaryId &&
|
|
164
|
+
a.face !== b.face &&
|
|
165
|
+
Math.min(a.t1, b.t1) - Math.max(a.t0, b.t0) > 1e-6
|
|
166
|
+
|
|
167
|
+
const spanRef = ({ boundaryId, face, t0, t1 }: BoundarySpan): SpanRef => ({
|
|
168
|
+
boundaryId,
|
|
169
|
+
face,
|
|
170
|
+
t0,
|
|
171
|
+
t1,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
function wallsState(nodes: StructureNodes, zone: ZoneNode): RoomConstructionState['walls'] {
|
|
175
|
+
const faces = levelFaces(nodes, zone.parentId!)
|
|
176
|
+
const spans = zoneFace(zone, faces)?.spans ?? []
|
|
177
|
+
const wallSpans = spans.filter((span) => span.kind === 'wall')
|
|
178
|
+
// A span is shared when a neighbouring room's face covers the other side of
|
|
179
|
+
// it — the same test setZoneEdges applies before leaving a wall in place.
|
|
180
|
+
const neighbourZones = Object.values(nodes).filter(
|
|
181
|
+
(node): node is ZoneNode =>
|
|
182
|
+
node.type === 'zone' &&
|
|
183
|
+
node.id !== zone.id &&
|
|
184
|
+
node.parentId === zone.parentId &&
|
|
185
|
+
node.spaceRole === 'room',
|
|
186
|
+
)
|
|
187
|
+
const neighbourFaces = new Map<ZoneNode, ExtractedRoom | undefined>()
|
|
188
|
+
const faceOf = (other: ZoneNode) => {
|
|
189
|
+
if (!neighbourFaces.has(other)) neighbourFaces.set(other, zoneFace(other, faces))
|
|
190
|
+
return neighbourFaces.get(other)
|
|
191
|
+
}
|
|
192
|
+
const shared = (span: BoundarySpan) =>
|
|
193
|
+
neighbourZones.some(
|
|
194
|
+
(other) =>
|
|
195
|
+
other.boundaryWallIds.includes(span.boundaryId as ZoneNode['boundaryWallIds'][number]) &&
|
|
196
|
+
faceOf(other)?.spans.some((candidate) => overlaps(candidate, span)),
|
|
197
|
+
)
|
|
198
|
+
const sharedSpans = wallSpans.filter(shared)
|
|
199
|
+
const walls = wallSpans.length
|
|
200
|
+
const removable = wallSpans.filter((span) => !sharedSpans.includes(span)).map(spanRef)
|
|
201
|
+
const separators = spans.filter((span) => span.kind === 'separator').map(spanRef)
|
|
202
|
+
return {
|
|
203
|
+
state: walls === 0 ? 'absent' : walls === spans.length ? 'present' : 'partial',
|
|
204
|
+
removable,
|
|
205
|
+
separators,
|
|
206
|
+
sharedWallIds: [...new Set(sharedSpans.map((span) => span.boundaryId))],
|
|
207
|
+
actions: { add: separators.length > 0, remove: removable.length > 0 },
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The room's floor plates: derived slabs that list the zone. */
|
|
212
|
+
function roomPlates(nodes: StructureNodes, zone: ZoneNode) {
|
|
213
|
+
return Object.values(nodes).filter(
|
|
214
|
+
(node): node is SlabNode =>
|
|
215
|
+
node.type === 'slab' &&
|
|
216
|
+
node.parentId === zone.parentId &&
|
|
217
|
+
isDerivedNode(node) &&
|
|
218
|
+
!!node.zoneIds?.includes(zone.id),
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function floorState(nodes: StructureNodes, zone: ZoneNode): RoomConstructionState['floor'] {
|
|
223
|
+
const intent = zone.hasFloor !== false
|
|
224
|
+
// A drawn-slab floor is the slab: switching the room's floor off would hide
|
|
225
|
+
// nothing, so only the slab itself can go. A floor switched off before still
|
|
226
|
+
// shows the slab it stands on, and can be switched back on.
|
|
227
|
+
const drawn = roomDrawnFloor(nodes, zone.id) !== null
|
|
228
|
+
const actions = { add: !intent, remove: intent && !drawn }
|
|
229
|
+
if (!intent && !drawn) return { state: 'absent', intent, coverage: 0, actions }
|
|
230
|
+
// The walking surface is this room's plate(s) plus any manual slab on the
|
|
231
|
+
// level; stair and elevator holes are openings, not missing floor. Once
|
|
232
|
+
// the room has a plate, another floor's base plate counts too: a room on a
|
|
233
|
+
// separate floor meets its neighbour's floor under the wall they share.
|
|
234
|
+
const listed = roomPlates(nodes, zone).length > 0
|
|
235
|
+
const covers = Object.values(nodes).flatMap((node) => {
|
|
236
|
+
if (node.type !== 'slab' || node.parentId !== zone.parentId) return []
|
|
237
|
+
if (
|
|
238
|
+
isDerivedNode(node) &&
|
|
239
|
+
!node.zoneIds?.includes(zone.id) &&
|
|
240
|
+
!(listed && node.plateRole === 'base')
|
|
241
|
+
)
|
|
242
|
+
return []
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
outer: node.polygon,
|
|
246
|
+
holes: node.holes.filter(
|
|
247
|
+
(_, i) => !OPENING_HOLES.has(node.holeMetadata[i]?.source ?? 'manual'),
|
|
248
|
+
),
|
|
249
|
+
},
|
|
250
|
+
]
|
|
251
|
+
})
|
|
252
|
+
const coverage = coverageOf(zone, covers)
|
|
253
|
+
return { state: presence(coverage), intent, coverage, actions }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const childrenOf = (nodes: StructureNodes, ceilings: CeilingNode[]) =>
|
|
257
|
+
ceilings.flatMap((ceiling) => ceiling.children.filter((id) => nodes[id]))
|
|
258
|
+
|
|
259
|
+
function ceilingState(nodes: StructureNodes, zone: ZoneNode): RoomConstructionState['ceiling'] {
|
|
260
|
+
const intent = zone.hasCeiling !== false
|
|
261
|
+
const ceilings = Object.values(nodes).filter(
|
|
262
|
+
(node): node is CeilingNode => node.type === 'ceiling' && node.parentId === zone.parentId,
|
|
263
|
+
)
|
|
264
|
+
const linked = intent
|
|
265
|
+
? ceilings.find((ceiling) => isDerivedNode(ceiling) && ceiling.zoneId === zone.id)
|
|
266
|
+
: undefined
|
|
267
|
+
if (linked)
|
|
268
|
+
return {
|
|
269
|
+
state: 'present',
|
|
270
|
+
intent,
|
|
271
|
+
ceilingId: linked.id,
|
|
272
|
+
hostedIds: childrenOf(nodes, [linked]),
|
|
273
|
+
manualIds: [],
|
|
274
|
+
manualHostedIds: [],
|
|
275
|
+
actions: { add: false, remove: true },
|
|
276
|
+
}
|
|
277
|
+
// Without its own ceiling, a hand-drawn one over the room stands in for it.
|
|
278
|
+
const manual = ceilings.filter(
|
|
279
|
+
(ceiling) => !derived(ceiling) && coverageOf(zone, [footprint(ceiling)]) > UNCOVERED,
|
|
280
|
+
)
|
|
281
|
+
const base = { intent, ceilingId: null, hostedIds: [] }
|
|
282
|
+
if (!manual.length)
|
|
283
|
+
return {
|
|
284
|
+
...base,
|
|
285
|
+
state: 'absent',
|
|
286
|
+
manualIds: [],
|
|
287
|
+
manualHostedIds: [],
|
|
288
|
+
actions: { add: !intent, remove: false },
|
|
289
|
+
}
|
|
290
|
+
// Clearing the opt-out under a ceiling that covers this much keeps it as the
|
|
291
|
+
// room's ceiling; under a smaller one the reconciler generates a room ceiling.
|
|
292
|
+
const blocks = coverageOf(zone, manual.map(footprint)) >= MANUAL_CEILING_BLOCKS
|
|
293
|
+
return {
|
|
294
|
+
...base,
|
|
295
|
+
// Someone else's surface: the room is only partly its own.
|
|
296
|
+
state: 'partial',
|
|
297
|
+
manualIds: manual.map((ceiling) => ceiling.id),
|
|
298
|
+
manualHostedIds: childrenOf(nodes, manual),
|
|
299
|
+
actions: {
|
|
300
|
+
add: !(intent || blocks),
|
|
301
|
+
remove: true,
|
|
302
|
+
useExisting: !intent && blocks,
|
|
303
|
+
replace: true,
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Floor-standing furnishings; structure (stairs, columns) stays out of it.
|
|
309
|
+
const FLOOR_CONTENT = new Set(['item', 'procedural-item', 'cabinet', 'shelf'])
|
|
310
|
+
const RESTING = 1e-3
|
|
311
|
+
|
|
312
|
+
type Positioned = AnyNode & {
|
|
313
|
+
position: [number, number, number]
|
|
314
|
+
supportSlabId?: string
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* What stands on the room's floor: furnishings pinned to one of its plates
|
|
319
|
+
* (`supportSlabId`, or parented to a plate), plus unpinned ones on the level
|
|
320
|
+
* resting at floor height inside the room's clear polygon. Each comes with
|
|
321
|
+
* the elevation of the plate it stands on, so it can stay put when the plate
|
|
322
|
+
* goes. Children of these (a lamp on a table) move and go with them.
|
|
323
|
+
*/
|
|
324
|
+
export function roomFloorContents(
|
|
325
|
+
nodes: StructureNodes,
|
|
326
|
+
zoneId: string,
|
|
327
|
+
): { id: string; elevation: number }[] {
|
|
328
|
+
const zone = nodes[zoneId]
|
|
329
|
+
if (zone?.type !== 'zone' || !zone.parentId) return []
|
|
330
|
+
const plates = roomPlates(nodes, zone)
|
|
331
|
+
if (!plates.length) return []
|
|
332
|
+
const byId = new Map(plates.map((plate) => [plate.id as string, plate]))
|
|
333
|
+
const clear = roomClearPolygon(nodes, zoneId)
|
|
334
|
+
const plateAt = (point: [number, number]) =>
|
|
335
|
+
plates.find((plate) => containsPoint([footprint(plate)], point)) ?? plates[0]!
|
|
336
|
+
return Object.values(nodes).flatMap((node) => {
|
|
337
|
+
if (!(FLOOR_CONTENT.has(node.type) && 'position' in node)) return []
|
|
338
|
+
const { parentId, position, supportSlabId } = node as Positioned
|
|
339
|
+
const point: [number, number] = [position[0], position[2]]
|
|
340
|
+
let plate = parentId ? byId.get(parentId) : undefined
|
|
341
|
+
if (!plate && parentId === zone.parentId) {
|
|
342
|
+
if (supportSlabId) plate = byId.get(supportSlabId)
|
|
343
|
+
else if (Math.abs(position[1]) <= RESTING && clear && containsPoint([clear], point))
|
|
344
|
+
plate = plateAt(point)
|
|
345
|
+
}
|
|
346
|
+
return plate ? [{ id: node.id, elevation: plate.elevation }] : []
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let memo: { nodes: StructureNodes; states: Map<string, RoomConstructionState | null> } | null = null
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Floor / Walls / Ceiling presence for a room, and the actions each row
|
|
354
|
+
* offers, derived together from intent and the reconciled graph. Pure;
|
|
355
|
+
* memoized per `nodes` identity and zone id.
|
|
356
|
+
*
|
|
357
|
+
* - Floor: Absent when `hasFloor === false` (unless a drawn slab still floors
|
|
358
|
+
* the room: the switch cannot take that away); otherwise the share of the zone
|
|
359
|
+
* footprint covered by plates listing the zone plus manual slabs (stair and
|
|
360
|
+
* elevator holes count as floor) — ≥ 98 % Present, ≤ 2 % Absent, else Partial.
|
|
361
|
+
* - Walls: every boundary span a wall → Present, none → Absent, a mix → Partial.
|
|
362
|
+
* - Ceiling: Present with the room's own (derived) ceiling; Partial when only a
|
|
363
|
+
* hand-drawn ceiling covers the room (Use existing / Replace / Remove);
|
|
364
|
+
* otherwise Absent.
|
|
365
|
+
*/
|
|
366
|
+
export function roomConstructionState(
|
|
367
|
+
nodes: StructureNodes,
|
|
368
|
+
zoneId: string,
|
|
369
|
+
): RoomConstructionState | null {
|
|
370
|
+
if (memo?.nodes !== nodes) memo = { nodes, states: new Map() }
|
|
371
|
+
if (memo.states.has(zoneId)) return memo.states.get(zoneId)!
|
|
372
|
+
const zone = nodes[zoneId]
|
|
373
|
+
const state =
|
|
374
|
+
zone?.type === 'zone' && zone.spaceRole === 'room' && zone.parentId
|
|
375
|
+
? {
|
|
376
|
+
zoneId,
|
|
377
|
+
name: zone.name,
|
|
378
|
+
floor: floorState(nodes, zone),
|
|
379
|
+
walls: wallsState(nodes, zone),
|
|
380
|
+
ceiling: ceilingState(nodes, zone),
|
|
381
|
+
}
|
|
382
|
+
: null
|
|
383
|
+
memo.states.set(zoneId, state)
|
|
384
|
+
return state
|
|
385
|
+
}
|