@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,551 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AnyNode,
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
type FloorFoundationPatch,
|
|
7
|
+
floorFootprintName,
|
|
8
|
+
floorFootprintSupportClass,
|
|
9
|
+
upperFloorHeightControl,
|
|
10
|
+
getLevelDisplayName,
|
|
11
|
+
getLevelElevations,
|
|
12
|
+
getStoredLevelHeight,
|
|
13
|
+
type LevelNode,
|
|
14
|
+
MIN_GROUND_FLOOR_THICKNESS,
|
|
15
|
+
MIN_SLAB_THICKNESS,
|
|
16
|
+
type SlabNode,
|
|
17
|
+
useScene,
|
|
18
|
+
} from '@pascal-app/core'
|
|
19
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
20
|
+
import { useRef, useState } from 'react'
|
|
21
|
+
import {
|
|
22
|
+
applyFloorFoundation,
|
|
23
|
+
beginFootprintHeightPreview,
|
|
24
|
+
beginFootprintThicknessPreview,
|
|
25
|
+
type FootprintPreset,
|
|
26
|
+
footprintHeightMinimum,
|
|
27
|
+
footprintHeightPatch,
|
|
28
|
+
footprintHeightValue,
|
|
29
|
+
footprintPreset,
|
|
30
|
+
footprintsToMoveTogether,
|
|
31
|
+
floorTopAboveGround,
|
|
32
|
+
presetPatch,
|
|
33
|
+
THICK_FLOOR_HINT,
|
|
34
|
+
thickFloorAdvice,
|
|
35
|
+
} from '../../../lib/floor-footprints'
|
|
36
|
+
import { formatLinearMeasurement } from '../../../lib/measurements'
|
|
37
|
+
import { FinishSwatch } from '../controls/finish-swatch'
|
|
38
|
+
import { MetricControl } from '../controls/metric-control'
|
|
39
|
+
import { SegmentedControl } from '../controls/segmented-control'
|
|
40
|
+
import { Button } from '../primitives/button'
|
|
41
|
+
import { PanelWrapper } from './panel-wrapper'
|
|
42
|
+
|
|
43
|
+
/** Unpainted finishes, as the plate renders them (nodes' slab slot defaults). */
|
|
44
|
+
const EDGE_DEFAULT = '#cccccc'
|
|
45
|
+
const FOUNDATION_DEFAULT = 'library:concrete-raw'
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A side section through the footprint's edge, top to bottom in the order it
|
|
49
|
+
* is built. On the ground: the floor top, the slab, the foundation when the
|
|
50
|
+
* footprint is raised on one, the ground — the slab always sits on the
|
|
51
|
+
* foundation (or the ground), so the top is their sum. Upstairs: the floor
|
|
52
|
+
* plate resting on the walls of the storey below. Drawn to a readable scale,
|
|
53
|
+
* not to size.
|
|
54
|
+
*/
|
|
55
|
+
export function FloorSection({
|
|
56
|
+
supported,
|
|
57
|
+
raised,
|
|
58
|
+
lift = 0,
|
|
59
|
+
liftLabel = '',
|
|
60
|
+
foundationHeight = 0,
|
|
61
|
+
foundationLabel = '',
|
|
62
|
+
topLabel,
|
|
63
|
+
thickness,
|
|
64
|
+
thicknessLabel,
|
|
65
|
+
}: {
|
|
66
|
+
/** Upstairs (or over a basement): it rests on the walls below, never on a foundation. */
|
|
67
|
+
supported: boolean
|
|
68
|
+
/** On a foundation (ground-bearing footprints only). */
|
|
69
|
+
raised: boolean
|
|
70
|
+
/** Upstairs: meters the top sits above resting on the walls below. */
|
|
71
|
+
lift?: number
|
|
72
|
+
liftLabel?: string
|
|
73
|
+
/** On the ground: the foundation's height, meters. */
|
|
74
|
+
foundationHeight?: number
|
|
75
|
+
foundationLabel?: string
|
|
76
|
+
/** On the ground: the floor top above the ground (foundation + slab). */
|
|
77
|
+
topLabel?: string
|
|
78
|
+
thickness: number
|
|
79
|
+
thicknessLabel: string
|
|
80
|
+
}) {
|
|
81
|
+
const base = 100
|
|
82
|
+
const foundation = !supported && raised ? Math.min(40, 14 + foundationHeight * 30) : 0
|
|
83
|
+
const band = Math.max(7, Math.min(16, thickness * 60))
|
|
84
|
+
// Upstairs a lift moves the plate off its default position (on the walls below).
|
|
85
|
+
const upstairsLift =
|
|
86
|
+
supported && Math.abs(lift) > 0.005
|
|
87
|
+
? lift > 0
|
|
88
|
+
? Math.min(18, 6 + lift * 30)
|
|
89
|
+
: -Math.min(10, 4 + -lift * 30)
|
|
90
|
+
: 0
|
|
91
|
+
const bandBottom = base - foundation - upstairsLift
|
|
92
|
+
const bandTop = bandBottom - band
|
|
93
|
+
// Upstairs the lift is measured from resting on the walls below; on the
|
|
94
|
+
// ground the top is measured from the ground.
|
|
95
|
+
const measureFrom = supported ? base - band : base
|
|
96
|
+
const measured = supported ? Math.abs(lift) > 0.005 : true
|
|
97
|
+
const measureLabel = supported ? liftLabel : (topLabel ?? '')
|
|
98
|
+
const left = 64
|
|
99
|
+
const right = 164
|
|
100
|
+
const labels: { y: number; text: string; key: string }[] = [
|
|
101
|
+
{ key: 'top', y: bandTop - 9, text: 'Floor top' },
|
|
102
|
+
{
|
|
103
|
+
key: 'plate',
|
|
104
|
+
y: bandTop + band / 2,
|
|
105
|
+
text: supported ? `Floor plate · ${thicknessLabel}` : `Slab · ${thicknessLabel}`,
|
|
106
|
+
},
|
|
107
|
+
...(foundation
|
|
108
|
+
? [
|
|
109
|
+
{
|
|
110
|
+
key: 'foundation',
|
|
111
|
+
y: bandBottom + foundation / 2,
|
|
112
|
+
text: foundationLabel ? `Foundation · ${foundationLabel}` : 'Foundation',
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
: []),
|
|
116
|
+
{ key: 'base', y: base + 9, text: supported ? 'Walls below' : 'Ground' },
|
|
117
|
+
]
|
|
118
|
+
return (
|
|
119
|
+
<figure className="flex flex-col gap-1" data-floor-section>
|
|
120
|
+
<svg
|
|
121
|
+
aria-label={
|
|
122
|
+
supported
|
|
123
|
+
? `Floor plate ${thicknessLabel} thick, resting on the walls below`
|
|
124
|
+
: raised
|
|
125
|
+
? `Slab ${thicknessLabel} thick on a ${foundationLabel} foundation, floor top ${topLabel ?? ''} above the ground`
|
|
126
|
+
: `Slab ${thicknessLabel} thick on the ground, floor top ${topLabel ?? ''} above the ground`
|
|
127
|
+
}
|
|
128
|
+
className="h-auto w-full text-muted-foreground"
|
|
129
|
+
role="img"
|
|
130
|
+
viewBox="0 0 280 118"
|
|
131
|
+
>
|
|
132
|
+
{supported ? (
|
|
133
|
+
// The walls of the storey below, under the plate's edge.
|
|
134
|
+
<g data-section-walls-below>
|
|
135
|
+
<rect
|
|
136
|
+
className="fill-muted"
|
|
137
|
+
height={118 - bandBottom}
|
|
138
|
+
stroke="currentColor"
|
|
139
|
+
strokeOpacity="0.4"
|
|
140
|
+
width="14"
|
|
141
|
+
x={left + 6}
|
|
142
|
+
y={bandBottom}
|
|
143
|
+
/>
|
|
144
|
+
<line
|
|
145
|
+
opacity="0.6"
|
|
146
|
+
stroke="currentColor"
|
|
147
|
+
strokeDasharray="3 3"
|
|
148
|
+
x1="8"
|
|
149
|
+
x2={right}
|
|
150
|
+
y1={base}
|
|
151
|
+
y2={base}
|
|
152
|
+
/>
|
|
153
|
+
</g>
|
|
154
|
+
) : (
|
|
155
|
+
<g data-section-ground>
|
|
156
|
+
<line stroke="currentColor" strokeWidth="1" x1="8" x2="272" y1={base} y2={base} />
|
|
157
|
+
{Array.from({ length: 22 }, (_, i) => (
|
|
158
|
+
<line
|
|
159
|
+
key={i}
|
|
160
|
+
opacity="0.35"
|
|
161
|
+
stroke="currentColor"
|
|
162
|
+
strokeWidth="1"
|
|
163
|
+
x1={12 + i * 12}
|
|
164
|
+
x2={6 + i * 12}
|
|
165
|
+
y1={base + 1}
|
|
166
|
+
y2={base + 7}
|
|
167
|
+
/>
|
|
168
|
+
))}
|
|
169
|
+
</g>
|
|
170
|
+
)}
|
|
171
|
+
{/* A wall standing on the floor. */}
|
|
172
|
+
<rect
|
|
173
|
+
className="fill-muted"
|
|
174
|
+
height={Math.min(26, bandTop - 12)}
|
|
175
|
+
stroke="currentColor"
|
|
176
|
+
strokeOpacity="0.4"
|
|
177
|
+
width="14"
|
|
178
|
+
x={left + 6}
|
|
179
|
+
y={bandTop - Math.min(26, bandTop - 12)}
|
|
180
|
+
/>
|
|
181
|
+
{foundation > 0 && (
|
|
182
|
+
<rect
|
|
183
|
+
data-section-foundation
|
|
184
|
+
fill="#8a8a8a"
|
|
185
|
+
height={foundation}
|
|
186
|
+
stroke="currentColor"
|
|
187
|
+
strokeOpacity="0.5"
|
|
188
|
+
width={right - left - 8}
|
|
189
|
+
x={left + 4}
|
|
190
|
+
y={bandBottom}
|
|
191
|
+
/>
|
|
192
|
+
)}
|
|
193
|
+
<rect
|
|
194
|
+
className="fill-foreground"
|
|
195
|
+
data-section-band
|
|
196
|
+
height={band}
|
|
197
|
+
width={right - left}
|
|
198
|
+
x={left}
|
|
199
|
+
y={bandTop}
|
|
200
|
+
/>
|
|
201
|
+
{/* The floor top. */}
|
|
202
|
+
<line
|
|
203
|
+
className="text-foreground"
|
|
204
|
+
stroke="currentColor"
|
|
205
|
+
strokeWidth="1.5"
|
|
206
|
+
x1={left - 4}
|
|
207
|
+
x2={right + 4}
|
|
208
|
+
y1={bandTop}
|
|
209
|
+
y2={bandTop}
|
|
210
|
+
/>
|
|
211
|
+
{measured && measureLabel && (
|
|
212
|
+
<g className="text-foreground" data-section-height>
|
|
213
|
+
<line stroke="currentColor" strokeWidth="1" x1="50" x2="50" y1={bandTop} y2={measureFrom} />
|
|
214
|
+
<line stroke="currentColor" strokeWidth="1" x1="45" x2="55" y1={bandTop} y2={bandTop} />
|
|
215
|
+
<line stroke="currentColor" strokeWidth="1" x1="45" x2="55" y1={measureFrom} y2={measureFrom} />
|
|
216
|
+
{supported && (
|
|
217
|
+
<line
|
|
218
|
+
opacity="0.6"
|
|
219
|
+
stroke="currentColor"
|
|
220
|
+
strokeDasharray="2 3"
|
|
221
|
+
x1="55"
|
|
222
|
+
x2={left}
|
|
223
|
+
y1={measureFrom}
|
|
224
|
+
y2={measureFrom}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
<text
|
|
228
|
+
className="fill-foreground font-mono"
|
|
229
|
+
dominantBaseline="middle"
|
|
230
|
+
fontSize="10"
|
|
231
|
+
textAnchor="end"
|
|
232
|
+
x="42"
|
|
233
|
+
y={(bandTop + measureFrom) / 2}
|
|
234
|
+
>
|
|
235
|
+
{measureLabel}
|
|
236
|
+
</text>
|
|
237
|
+
</g>
|
|
238
|
+
)}
|
|
239
|
+
{labels.map((label) => (
|
|
240
|
+
<g key={label.key}>
|
|
241
|
+
<line
|
|
242
|
+
opacity="0.6"
|
|
243
|
+
stroke="currentColor"
|
|
244
|
+
x1={right + 4}
|
|
245
|
+
x2={right + 12}
|
|
246
|
+
y1={label.y}
|
|
247
|
+
y2={label.y}
|
|
248
|
+
/>
|
|
249
|
+
<text
|
|
250
|
+
className="fill-foreground"
|
|
251
|
+
dominantBaseline="middle"
|
|
252
|
+
fontSize="10"
|
|
253
|
+
x={right + 16}
|
|
254
|
+
y={label.y}
|
|
255
|
+
>
|
|
256
|
+
{label.text}
|
|
257
|
+
</text>
|
|
258
|
+
</g>
|
|
259
|
+
))}
|
|
260
|
+
</svg>
|
|
261
|
+
<figcaption className="text-right text-[10px] text-muted-foreground/70">
|
|
262
|
+
Not to scale
|
|
263
|
+
</figcaption>
|
|
264
|
+
</figure>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
type Notice = {
|
|
269
|
+
message: string
|
|
270
|
+
/** The footprints to move together, and the change, when that would be accepted. */
|
|
271
|
+
together: { ids: string[]; patch: FloorFoundationPatch; lower: boolean } | null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function moveTogetherLabel(count: number, lower: boolean) {
|
|
275
|
+
const verb = lower ? 'Lower' : 'Raise'
|
|
276
|
+
return count === 2 ? `${verb} both` : `${verb} all ${count}`
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** "Floor plate · Ground floor", upstairs "Floor plate · Floor 1 · rests on Ground floor". */
|
|
280
|
+
export function floorPlateKicker(nodes: Record<string, AnyNode>, plate: SlabNode): string {
|
|
281
|
+
const level = plate.parentId ? nodes[plate.parentId] : undefined
|
|
282
|
+
const levelName = level?.type === 'level' ? ` · ${getLevelDisplayName(level)}` : ''
|
|
283
|
+
// A supported floor rests on the storey right below it.
|
|
284
|
+
let below: LevelNode | null = null
|
|
285
|
+
if (level?.type === 'level' && floorFootprintSupportClass(nodes, plate) === 'supported')
|
|
286
|
+
for (const node of Object.values(nodes))
|
|
287
|
+
if (
|
|
288
|
+
node.type === 'level' &&
|
|
289
|
+
node.parentId === level.parentId &&
|
|
290
|
+
node.level < level.level &&
|
|
291
|
+
(!below || node.level > below.level)
|
|
292
|
+
)
|
|
293
|
+
below = node
|
|
294
|
+
const restsOn = below ? ` · rests on ${getLevelDisplayName(below)}` : ''
|
|
295
|
+
return `Floor plate${levelName}${restsOn}`
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* What the panel shows for a footprint. An upper floor rests on the walls
|
|
300
|
+
* below: its height is its floor top, and raising it thickens the plate (the
|
|
301
|
+
* two stay in sync); it has no ground preset and no foundation. A footprint on
|
|
302
|
+
* the ground has two inputs, the slab thickness and the foundation height (0 on
|
|
303
|
+
* the ground); its floor top above the ground is their sum, read-only.
|
|
304
|
+
*/
|
|
305
|
+
export function floorFoundationModel(nodes: Record<string, AnyNode>, plate: SlabNode) {
|
|
306
|
+
const upper = upperFloorHeightControl(nodes, plate) !== null
|
|
307
|
+
const height = footprintHeightValue(nodes, plate)
|
|
308
|
+
const levelBase = plate.parentId
|
|
309
|
+
? (getLevelElevations(nodes as Record<AnyNodeId, AnyNode>).get(plate.parentId as AnyNodeId)
|
|
310
|
+
?.baseY ?? 0)
|
|
311
|
+
: 0
|
|
312
|
+
return {
|
|
313
|
+
upper,
|
|
314
|
+
kicker: floorPlateKicker(nodes, plate),
|
|
315
|
+
preset: upper ? ('ground' as const) : footprintPreset(nodes, plate),
|
|
316
|
+
/** Upstairs: the floor top (level-local). On the ground: the foundation height. */
|
|
317
|
+
height,
|
|
318
|
+
/** Upstairs: the floor top above the ground. On the ground: above the ground (foundation + slab). */
|
|
319
|
+
floorTop: upper ? levelBase + height : floorTopAboveGround(nodes, plate),
|
|
320
|
+
minHeight: footprintHeightMinimum(nodes, plate),
|
|
321
|
+
minThickness: upper ? MIN_SLAB_THICKNESS : MIN_GROUND_FLOOR_THICKNESS,
|
|
322
|
+
underFloor: !upper,
|
|
323
|
+
thickFloor: thickFloorAdvice(nodes, plate),
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* "Floor & foundation" for one footprint (the house, a shed, an upper storey),
|
|
329
|
+
* in one column in the order it is built: the section, the slab (thickness,
|
|
330
|
+
* edge finish), then what is under the floor — on a ground-bearing footprint
|
|
331
|
+
* only — and the floor top that follows. Scrubbing the foundation height moves
|
|
332
|
+
* the building live and lands as one undo step.
|
|
333
|
+
*/
|
|
334
|
+
export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClose: () => void }) {
|
|
335
|
+
const nodes = useScene((state) => state.nodes)
|
|
336
|
+
const unit = useViewer((s) => s.unit)
|
|
337
|
+
const metricNotation = useViewer((s) => s.metricNotation)
|
|
338
|
+
const [notice, setNotice] = useState<Notice | null>(null)
|
|
339
|
+
const scrub = useRef<ReturnType<typeof beginFootprintHeightPreview> | null>(null)
|
|
340
|
+
const thicknessScrub = useRef<ReturnType<typeof beginFootprintThicknessPreview> | null>(null)
|
|
341
|
+
const plate = nodes[node.id as AnyNodeId]
|
|
342
|
+
if (plate?.type !== 'slab' || plate.plateRole !== 'base') return null
|
|
343
|
+
const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation)
|
|
344
|
+
const { upper, kicker, preset, height, floorTop, minHeight, minThickness, underFloor, thickFloor } =
|
|
345
|
+
floorFoundationModel(nodes, plate)
|
|
346
|
+
const level = plate.parentId ? nodes[plate.parentId as AnyNodeId] : undefined
|
|
347
|
+
const maxHeight = level?.type === 'level' ? getStoredLevelHeight(level) - 0.5 : 3
|
|
348
|
+
// A refused change says why, in the panel's words, never as an error; when
|
|
349
|
+
// only the shared-storey rule refused it, it offers to move every footprint
|
|
350
|
+
// under that storey together (checked once the change settles, not per frame).
|
|
351
|
+
const report = (message: string | null, patch?: FloorFoundationPatch) => {
|
|
352
|
+
if (!message) return setNotice(null)
|
|
353
|
+
const current = useScene.getState().nodes
|
|
354
|
+
const together = patch ? footprintsToMoveTogether(current, plate.id, patch) : null
|
|
355
|
+
const latest = current[plate.id as AnyNodeId]
|
|
356
|
+
const lower =
|
|
357
|
+
latest?.type !== 'slab'
|
|
358
|
+
? false
|
|
359
|
+
: patch?.thickness !== undefined
|
|
360
|
+
? patch.thickness < latest.thickness
|
|
361
|
+
: patch?.foundationHeight !== undefined
|
|
362
|
+
? patch.foundationHeight < footprintHeightValue(current, latest)
|
|
363
|
+
: patch?.floorHeight == null ||
|
|
364
|
+
patch.floorHeight < (latest.floorHeight ?? latest.elevation)
|
|
365
|
+
setNotice({ message, together: together && patch ? { ids: together, patch, lower } : null })
|
|
366
|
+
}
|
|
367
|
+
const heightPatchFor = (value: number) => {
|
|
368
|
+
const current = useScene.getState().nodes
|
|
369
|
+
const latest = current[plate.id as AnyNodeId]
|
|
370
|
+
return latest?.type === 'slab' ? footprintHeightPatch(current, latest, value) : undefined
|
|
371
|
+
}
|
|
372
|
+
// A scrub previews live and lands once on release; a typed value lands directly.
|
|
373
|
+
const previewHeight = (value: number) => {
|
|
374
|
+
scrub.current ??= beginFootprintHeightPreview(plate.id)
|
|
375
|
+
report(scrub.current.preview(value))
|
|
376
|
+
}
|
|
377
|
+
const commitHeight = (value: number) => {
|
|
378
|
+
const session = scrub.current ?? beginFootprintHeightPreview(plate.id)
|
|
379
|
+
scrub.current = null
|
|
380
|
+
const message = session.commit(value)
|
|
381
|
+
report(message, message ? heightPatchFor(value) : undefined)
|
|
382
|
+
}
|
|
383
|
+
// Upstairs the underside stays on the walls below: the thickness is the one
|
|
384
|
+
// control, and it moves the floor top (and the storeys above) with it.
|
|
385
|
+
const underside = height - plate.thickness
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<PanelWrapper
|
|
389
|
+
icon="/icons/floor.webp"
|
|
390
|
+
kicker={kicker}
|
|
391
|
+
onClose={onClose}
|
|
392
|
+
title={floorFootprintName(nodes, plate)}
|
|
393
|
+
>
|
|
394
|
+
<div
|
|
395
|
+
className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm"
|
|
396
|
+
data-floor-foundation={plate.id}
|
|
397
|
+
data-support={upper ? 'supported' : 'ground-bearing'}
|
|
398
|
+
>
|
|
399
|
+
<FloorSection
|
|
400
|
+
foundationHeight={upper ? 0 : height}
|
|
401
|
+
foundationLabel={length(height)}
|
|
402
|
+
raised={preset === 'raised'}
|
|
403
|
+
supported={upper}
|
|
404
|
+
thickness={plate.thickness}
|
|
405
|
+
thicknessLabel={length(plate.thickness)}
|
|
406
|
+
topLabel={upper ? undefined : length(floorTop)}
|
|
407
|
+
/>
|
|
408
|
+
<section className="flex flex-col gap-1.5" data-floor-plate-group>
|
|
409
|
+
<h3 className="font-medium text-muted-foreground text-xs">
|
|
410
|
+
{upper ? 'Floor plate' : 'Slab'}
|
|
411
|
+
</h3>
|
|
412
|
+
{upper ? (
|
|
413
|
+
<div title="A thicker floor grows upward: the rooms below keep their height.">
|
|
414
|
+
<MetricControl
|
|
415
|
+
className="h-9 text-xs"
|
|
416
|
+
label="Floor thickness"
|
|
417
|
+
max={Math.max(MIN_SLAB_THICKNESS, maxHeight - underside)}
|
|
418
|
+
min={Math.max(MIN_SLAB_THICKNESS, minHeight - underside)}
|
|
419
|
+
onChange={(next) => previewHeight(underside + next)}
|
|
420
|
+
onCommit={(next) => commitHeight(underside + next)}
|
|
421
|
+
precision={2}
|
|
422
|
+
restoreOnCommit={false}
|
|
423
|
+
step={0.01}
|
|
424
|
+
unit="m"
|
|
425
|
+
value={plate.thickness}
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
) : (
|
|
429
|
+
<div title="The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.">
|
|
430
|
+
<MetricControl
|
|
431
|
+
className="h-9 text-xs"
|
|
432
|
+
label="Slab thickness"
|
|
433
|
+
max={1}
|
|
434
|
+
min={minThickness}
|
|
435
|
+
onChange={(next) => {
|
|
436
|
+
thicknessScrub.current ??= beginFootprintThicknessPreview(plate.id)
|
|
437
|
+
report(thicknessScrub.current.preview(Math.max(minThickness, next)))
|
|
438
|
+
}}
|
|
439
|
+
onCommit={(value) => {
|
|
440
|
+
const session =
|
|
441
|
+
thicknessScrub.current ?? beginFootprintThicknessPreview(plate.id)
|
|
442
|
+
thicknessScrub.current = null
|
|
443
|
+
const patch = { thickness: Math.max(minThickness, value) }
|
|
444
|
+
const message = session.commit(patch.thickness)
|
|
445
|
+
report(message, message ? patch : undefined)
|
|
446
|
+
}}
|
|
447
|
+
precision={2}
|
|
448
|
+
restoreOnCommit={false}
|
|
449
|
+
step={0.01}
|
|
450
|
+
unit="m"
|
|
451
|
+
value={plate.thickness}
|
|
452
|
+
/>
|
|
453
|
+
</div>
|
|
454
|
+
)}
|
|
455
|
+
{upper && (
|
|
456
|
+
<p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
|
|
457
|
+
Floor top +{length(floorTop)}
|
|
458
|
+
</p>
|
|
459
|
+
)}
|
|
460
|
+
<FinishSwatch
|
|
461
|
+
fallback={EDGE_DEFAULT}
|
|
462
|
+
hint="The floor's outside edge, all around the footprint."
|
|
463
|
+
label="Edge finish"
|
|
464
|
+
node={plate}
|
|
465
|
+
role="edge"
|
|
466
|
+
value={plate.slots?.edge}
|
|
467
|
+
/>
|
|
468
|
+
{thickFloor && (
|
|
469
|
+
<p className="text-muted-foreground text-xs" data-thick-floor-hint>
|
|
470
|
+
{THICK_FLOOR_HINT}
|
|
471
|
+
</p>
|
|
472
|
+
)}
|
|
473
|
+
</section>
|
|
474
|
+
{underFloor && (
|
|
475
|
+
<section className="flex flex-col gap-1.5" data-under-floor-group>
|
|
476
|
+
<h3 className="font-medium text-muted-foreground text-xs">Under the floor</h3>
|
|
477
|
+
<SegmentedControl
|
|
478
|
+
onChange={(next: FootprintPreset) => {
|
|
479
|
+
if (next === preset) return
|
|
480
|
+
const patch = presetPatch(nodes, plate, next)
|
|
481
|
+
report(applyFloorFoundation(plate.id, patch), patch)
|
|
482
|
+
}}
|
|
483
|
+
options={[
|
|
484
|
+
{ label: 'On the ground', value: 'ground' },
|
|
485
|
+
{ label: 'Raised on a foundation', value: 'raised' },
|
|
486
|
+
]}
|
|
487
|
+
value={preset}
|
|
488
|
+
/>
|
|
489
|
+
{preset === 'raised' && (
|
|
490
|
+
<>
|
|
491
|
+
<div title="The foundation stands on the ground; the slab sits on it.">
|
|
492
|
+
<MetricControl
|
|
493
|
+
className="h-9 text-xs"
|
|
494
|
+
label="Foundation height"
|
|
495
|
+
max={Math.max(0.05, maxHeight)}
|
|
496
|
+
min={minHeight}
|
|
497
|
+
onChange={previewHeight}
|
|
498
|
+
onCommit={commitHeight}
|
|
499
|
+
precision={2}
|
|
500
|
+
restoreOnCommit={false}
|
|
501
|
+
step={0.05}
|
|
502
|
+
unit="m"
|
|
503
|
+
value={height}
|
|
504
|
+
/>
|
|
505
|
+
</div>
|
|
506
|
+
<FinishSwatch
|
|
507
|
+
fallback={FOUNDATION_DEFAULT}
|
|
508
|
+
label="Foundation finish"
|
|
509
|
+
node={plate}
|
|
510
|
+
role="foundation"
|
|
511
|
+
value={
|
|
512
|
+
typeof plate.foundation?.material === 'string'
|
|
513
|
+
? plate.foundation.material
|
|
514
|
+
: undefined
|
|
515
|
+
}
|
|
516
|
+
/>
|
|
517
|
+
</>
|
|
518
|
+
)}
|
|
519
|
+
<p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
|
|
520
|
+
Floor top {length(floorTop)}
|
|
521
|
+
</p>
|
|
522
|
+
</section>
|
|
523
|
+
)}
|
|
524
|
+
{notice && (
|
|
525
|
+
<div
|
|
526
|
+
className="flex flex-col items-start gap-2 rounded-lg bg-accent/30 px-2.5 py-2"
|
|
527
|
+
data-floor-foundation-notice
|
|
528
|
+
>
|
|
529
|
+
<p className="text-muted-foreground text-xs" role="status">
|
|
530
|
+
{notice.message}
|
|
531
|
+
</p>
|
|
532
|
+
{notice.together && (
|
|
533
|
+
<Button
|
|
534
|
+
className="rounded-full"
|
|
535
|
+
onClick={() => {
|
|
536
|
+
const { ids, patch } = notice.together!
|
|
537
|
+
report(applyFloorFoundation(ids, patch))
|
|
538
|
+
}}
|
|
539
|
+
size="sm"
|
|
540
|
+
type="button"
|
|
541
|
+
variant="outline"
|
|
542
|
+
>
|
|
543
|
+
{moveTogetherLabel(notice.together.ids.length, notice.together.lower)}
|
|
544
|
+
</Button>
|
|
545
|
+
)}
|
|
546
|
+
</div>
|
|
547
|
+
)}
|
|
548
|
+
</div>
|
|
549
|
+
</PanelWrapper>
|
|
550
|
+
)
|
|
551
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
adjacentLevelId,
|
|
6
|
+
area,
|
|
7
|
+
floorOpeningHints,
|
|
8
|
+
getLevelDisplayName,
|
|
9
|
+
useScene,
|
|
10
|
+
} from '@pascal-app/core'
|
|
11
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
12
|
+
import {
|
|
13
|
+
deleteFloorOpening,
|
|
14
|
+
finishFloorOpening,
|
|
15
|
+
openingRoom,
|
|
16
|
+
} from '../../../lib/floor-edit-session'
|
|
17
|
+
import { formatAreaLabel } from '../../../lib/measurements'
|
|
18
|
+
import { Button } from '../primitives/button'
|
|
19
|
+
import { Switch } from '../primitives/switch'
|
|
20
|
+
import { PanelWrapper } from './panel-wrapper'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A floor (or ceiling) opening: where it is, what it cuts, and the switch for
|
|
24
|
+
* the adjacent surface — "Also cut the ceiling below" for an opening drawn on
|
|
25
|
+
* a floor, "Also cut the floor above" for one drawn on a ceiling. A hatch in a
|
|
26
|
+
* mezzanine cuts only the mezzanine. Its shape is edited with the corner
|
|
27
|
+
* handles in the scene. Done and Delete return to the room.
|
|
28
|
+
*/
|
|
29
|
+
export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
30
|
+
const nodes = useScene((state) => state.nodes)
|
|
31
|
+
const unit = useViewer((s) => s.unit)
|
|
32
|
+
const readOnly = useScene((state) => state.readOnly)
|
|
33
|
+
const opening = nodes[openingId as AnyNodeId]
|
|
34
|
+
if (opening?.type !== 'floor-opening') return null
|
|
35
|
+
const onCeiling = opening.drawnOn === 'ceiling'
|
|
36
|
+
const zoneId = openingRoom(nodes, opening)
|
|
37
|
+
const zone = zoneId ? nodes[zoneId as AnyNodeId] : undefined
|
|
38
|
+
const roomName = zone?.type === 'zone' ? zone.name?.trim() || 'Room' : null
|
|
39
|
+
const level = opening.parentId ? nodes[opening.parentId as AnyNodeId] : undefined
|
|
40
|
+
const adjacentId = opening.parentId
|
|
41
|
+
? adjacentLevelId(nodes, opening.parentId, onCeiling ? 1 : -1)
|
|
42
|
+
: undefined
|
|
43
|
+
const adjacent = adjacentId ? nodes[adjacentId as AnyNodeId] : undefined
|
|
44
|
+
const hosted = !!opening.hostZoneId
|
|
45
|
+
const owned =
|
|
46
|
+
(opening.source === 'stair' || opening.source === 'elevator') &&
|
|
47
|
+
!!opening.ownerId &&
|
|
48
|
+
nodes[opening.ownerId as AnyNodeId]?.type === opening.source
|
|
49
|
+
const hints = floorOpeningHints(nodes, opening)
|
|
50
|
+
const title = opening.source === 'stair'
|
|
51
|
+
? 'Stair opening'
|
|
52
|
+
: opening.source === 'elevator'
|
|
53
|
+
? 'Elevator opening'
|
|
54
|
+
: onCeiling ? 'Ceiling opening' : 'Floor opening'
|
|
55
|
+
const kicker = [
|
|
56
|
+
'Opening',
|
|
57
|
+
roomName ? `in ${roomName}` : null,
|
|
58
|
+
level?.type === 'level' ? getLevelDisplayName(level) : null,
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(' · ')
|
|
62
|
+
const switchLabel = onCeiling ? 'Also cut the floor above' : 'Also cut the ceiling below'
|
|
63
|
+
const setAdjacent = (next: boolean) =>
|
|
64
|
+
useScene.getState().updateNode(opening.id, { cutsAdjacent: next })
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<PanelWrapper
|
|
68
|
+
icon="/icons/floor.webp"
|
|
69
|
+
kicker={kicker}
|
|
70
|
+
onClose={() => finishFloorOpening(opening.id)}
|
|
71
|
+
title={title}
|
|
72
|
+
>
|
|
73
|
+
<div className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm" data-floor-opening-panel={opening.id}>
|
|
74
|
+
<p className="text-muted-foreground text-xs">
|
|
75
|
+
{formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2)}
|
|
76
|
+
{owned ? ' · Shape follows its owner.' : ' · Drag its corners to change the shape.'}
|
|
77
|
+
</p>
|
|
78
|
+
{owned && (
|
|
79
|
+
<button
|
|
80
|
+
className="text-left text-xs text-primary underline"
|
|
81
|
+
data-opening-owner={opening.ownerId}
|
|
82
|
+
onClick={() => useViewer.getState().setSelection({ selectedIds: [opening.ownerId!] })}
|
|
83
|
+
type="button"
|
|
84
|
+
>
|
|
85
|
+
Owned by {opening.source === 'stair' ? 'Staircase' : 'Elevator'}
|
|
86
|
+
</button>
|
|
87
|
+
)}
|
|
88
|
+
{hosted ? (
|
|
89
|
+
<p className="text-muted-foreground text-xs" data-opening-hosted>
|
|
90
|
+
A hatch in the mezzanine: it cuts only the mezzanine’s floor.
|
|
91
|
+
</p>
|
|
92
|
+
) : owned ? null : (
|
|
93
|
+
<label className="flex items-start gap-3" data-opening-adjacent>
|
|
94
|
+
<span className="flex flex-1 flex-col gap-0.5">
|
|
95
|
+
<span className="font-medium text-[13px] text-foreground">{switchLabel}</span>
|
|
96
|
+
<span className="text-muted-foreground text-xs">
|
|
97
|
+
{adjacent?.type === 'level'
|
|
98
|
+
? `On ${getLevelDisplayName(adjacent)}, as one void with this opening.`
|
|
99
|
+
: onCeiling
|
|
100
|
+
? 'There is no floor above this storey.'
|
|
101
|
+
: 'There is no storey below.'}
|
|
102
|
+
</span>
|
|
103
|
+
</span>
|
|
104
|
+
<Switch
|
|
105
|
+
aria-label={switchLabel}
|
|
106
|
+
checked={opening.cutsAdjacent && adjacent?.type === 'level'}
|
|
107
|
+
disabled={readOnly || adjacent?.type !== 'level'}
|
|
108
|
+
onCheckedChange={setAdjacent}
|
|
109
|
+
/>
|
|
110
|
+
</label>
|
|
111
|
+
)}
|
|
112
|
+
{hints.map((hint) => (
|
|
113
|
+
<p className="text-muted-foreground text-xs" data-opening-hint={hint.code} key={hint.code}>
|
|
114
|
+
{hint.message}
|
|
115
|
+
</p>
|
|
116
|
+
))}
|
|
117
|
+
<div className="flex items-center gap-2">
|
|
118
|
+
<Button
|
|
119
|
+
className="flex-1 rounded-full"
|
|
120
|
+
data-opening-done
|
|
121
|
+
onClick={() => finishFloorOpening(opening.id)}
|
|
122
|
+
size="sm"
|
|
123
|
+
type="button"
|
|
124
|
+
variant="outline"
|
|
125
|
+
>
|
|
126
|
+
Done
|
|
127
|
+
</Button>
|
|
128
|
+
{!owned && <Button
|
|
129
|
+
className="flex-1 rounded-full"
|
|
130
|
+
data-opening-delete
|
|
131
|
+
disabled={readOnly}
|
|
132
|
+
onClick={() => deleteFloorOpening(opening.id)}
|
|
133
|
+
size="sm"
|
|
134
|
+
type="button"
|
|
135
|
+
variant="outline"
|
|
136
|
+
>
|
|
137
|
+
Delete
|
|
138
|
+
</Button>}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</PanelWrapper>
|
|
142
|
+
)
|
|
143
|
+
}
|