@pascal-app/editor 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import '../../../three-types'
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
|
-
import {
|
|
5
|
-
type AnyNodeId,
|
|
6
|
-
emitter,
|
|
7
|
-
type GridEvent,
|
|
8
|
-
sceneRegistry,
|
|
9
|
-
useScene,
|
|
10
|
-
type ZoneNode,
|
|
11
|
-
} from '@pascal-app/core'
|
|
4
|
+
import { type AnyNodeId, emitter, type GridEvent, sceneRegistry, useScene } from '@pascal-app/core'
|
|
12
5
|
import { useViewer } from '@pascal-app/viewer'
|
|
13
6
|
import { useThree } from '@react-three/fiber'
|
|
14
7
|
import { useCallback, useEffect, useRef } from 'react'
|
|
@@ -26,9 +19,10 @@ import {
|
|
|
26
19
|
Vector2,
|
|
27
20
|
Vector3,
|
|
28
21
|
} from 'three'
|
|
22
|
+
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
29
23
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
30
24
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
31
|
-
import
|
|
25
|
+
import useInteractionScope from '../../../store/use-interaction-scope'
|
|
32
26
|
import { CursorSphere } from '../shared/cursor-sphere'
|
|
33
27
|
import { isBoxSelectPointerSuppressed, markBoxSelectHandled } from './box-select-state'
|
|
34
28
|
import { collectSelectableCandidateIds } from './select-candidates'
|
|
@@ -297,6 +291,7 @@ export const PlaneBoxSelectTool: React.FC = () => {
|
|
|
297
291
|
const currentPoint = useRef(new Vector3())
|
|
298
292
|
const pointerDown = useRef(false)
|
|
299
293
|
const isDragging = useRef(false)
|
|
294
|
+
const ownedGesture = useRef<object | null>(null)
|
|
300
295
|
const startClientX = useRef(0)
|
|
301
296
|
const startClientY = useRef(0)
|
|
302
297
|
const gridY = useRef(0)
|
|
@@ -320,11 +315,36 @@ export const PlaneBoxSelectTool: React.FC = () => {
|
|
|
320
315
|
const resetDrag = useCallback(() => {
|
|
321
316
|
pointerDown.current = false
|
|
322
317
|
isDragging.current = false
|
|
323
|
-
|
|
318
|
+
const gesture = ownedGesture.current
|
|
319
|
+
if (gesture)
|
|
320
|
+
useInteractionScope.getState().endIf(() => useInteractionScope.getState().gesture === gesture)
|
|
321
|
+
ownedGesture.current = null
|
|
322
|
+
previousGridPosition.current = null
|
|
323
|
+
if (rectFillRef.current) rectFillRef.current.visible = false
|
|
324
324
|
outlineRef.current.visible = false
|
|
325
325
|
syncPreviewSelectedIds([])
|
|
326
326
|
}, [syncPreviewSelectedIds])
|
|
327
327
|
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
const onCancel = () => {
|
|
330
|
+
if (!pointerDown.current) return
|
|
331
|
+
if (
|
|
332
|
+
(!ownedGesture.current && useInteractionScope.getState().scope.kind === 'idle') ||
|
|
333
|
+
(ownedGesture.current && useInteractionScope.getState().gesture === ownedGesture.current)
|
|
334
|
+
) {
|
|
335
|
+
markToolCancelConsumed()
|
|
336
|
+
}
|
|
337
|
+
resetDrag()
|
|
338
|
+
}
|
|
339
|
+
emitter.on('tool:cancel', onCancel)
|
|
340
|
+
window.addEventListener('pointercancel', resetDrag)
|
|
341
|
+
return () => {
|
|
342
|
+
emitter.off('tool:cancel', onCancel)
|
|
343
|
+
window.removeEventListener('pointercancel', resetDrag)
|
|
344
|
+
resetDrag()
|
|
345
|
+
}
|
|
346
|
+
}, [resetDrag])
|
|
347
|
+
|
|
328
348
|
const raycastToGround = useCallback(
|
|
329
349
|
(event: PointerEvent): Vector3 | null => {
|
|
330
350
|
const rect = gl.domElement.getBoundingClientRect()
|
|
@@ -342,12 +362,11 @@ export const PlaneBoxSelectTool: React.FC = () => {
|
|
|
342
362
|
useEffect(() => {
|
|
343
363
|
const outline = outlineRef.current
|
|
344
364
|
return () => {
|
|
345
|
-
|
|
346
|
-
setPreviewSelectedIds([])
|
|
365
|
+
resetDrag()
|
|
347
366
|
outline.geometry.dispose()
|
|
348
367
|
;(outline.material as LineBasicMaterial).dispose()
|
|
349
368
|
}
|
|
350
|
-
}, [
|
|
369
|
+
}, [resetDrag])
|
|
351
370
|
|
|
352
371
|
useEffect(() => {
|
|
353
372
|
const unsubscribe = useViewer.subscribe((state) => {
|
|
@@ -440,15 +459,7 @@ export const PlaneBoxSelectTool: React.FC = () => {
|
|
|
440
459
|
|
|
441
460
|
const ids = collectNodeIdsInPlaneBounds(bounds)
|
|
442
461
|
const shouldAppend = event.metaKey || event.ctrlKey || event.shiftKey
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (phase === 'structure' && structureLayer === 'zones') {
|
|
446
|
-
if (ids.length > 0) {
|
|
447
|
-
useViewer.getState().setSelection({ zoneId: ids[0] as ZoneNode['id'] })
|
|
448
|
-
} else if (!shouldAppend) {
|
|
449
|
-
useViewer.getState().setSelection({ zoneId: null })
|
|
450
|
-
}
|
|
451
|
-
} else if (shouldAppend) {
|
|
462
|
+
if (shouldAppend) {
|
|
452
463
|
const currentIds = useViewer.getState().selection.selectedIds
|
|
453
464
|
useViewer.getState().setSelection({
|
|
454
465
|
selectedIds: Array.from(new Set([...currentIds, ...ids])),
|
|
@@ -507,6 +518,8 @@ export const PlaneBoxSelectTool: React.FC = () => {
|
|
|
507
518
|
const dy = nativeEvent.clientY - startClientY.current
|
|
508
519
|
if (!isDragging.current && Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
|
|
509
520
|
isDragging.current = true
|
|
521
|
+
useInteractionScope.getState().begin({ kind: 'box-select' })
|
|
522
|
+
ownedGesture.current = useInteractionScope.getState().gesture
|
|
510
523
|
}
|
|
511
524
|
|
|
512
525
|
if (isDragging.current && rectFillRef.current && outlineRef.current) {
|
|
@@ -47,10 +47,7 @@ describe('selectable candidates', () => {
|
|
|
47
47
|
},
|
|
48
48
|
previewSelectedIds: [],
|
|
49
49
|
})
|
|
50
|
-
useEditor.setState({
|
|
51
|
-
phase: 'structure',
|
|
52
|
-
structureLayer: 'elements',
|
|
53
|
-
})
|
|
50
|
+
useEditor.setState({ phase: 'building' })
|
|
54
51
|
})
|
|
55
52
|
|
|
56
53
|
test('includes building-scoped elevators for the active level building', () => {
|
|
@@ -106,4 +103,40 @@ describe('selectable candidates', () => {
|
|
|
106
103
|
|
|
107
104
|
expect(collectSelectableCandidateIds()).toContain('elevator_test')
|
|
108
105
|
})
|
|
106
|
+
|
|
107
|
+
test('Building box selection includes structural and furnishing kinds on the active level', () => {
|
|
108
|
+
for (const category of ['structure', 'furnish'] as const) {
|
|
109
|
+
const kind = `box-select-test-${category}`
|
|
110
|
+
if (!nodeRegistry.has(kind)) {
|
|
111
|
+
registerNode({
|
|
112
|
+
kind,
|
|
113
|
+
schemaVersion: 1,
|
|
114
|
+
schema: z.object({ type: z.literal(kind) }) as never,
|
|
115
|
+
category,
|
|
116
|
+
defaults: () => ({}),
|
|
117
|
+
capabilities: { selectable: {} },
|
|
118
|
+
} as AnyNodeDefinition)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
useScene.setState({
|
|
122
|
+
nodes: {
|
|
123
|
+
level_test: {
|
|
124
|
+
id: 'level_test',
|
|
125
|
+
type: 'level',
|
|
126
|
+
parentId: 'building_test',
|
|
127
|
+
children: ['wall_pick', 'item_pick', 'item_hidden'],
|
|
128
|
+
},
|
|
129
|
+
wall_pick: { id: 'wall_pick', type: 'box-select-test-structure', parentId: 'level_test' },
|
|
130
|
+
item_pick: { id: 'item_pick', type: 'box-select-test-furnish', parentId: 'level_test' },
|
|
131
|
+
item_hidden: {
|
|
132
|
+
id: 'item_hidden',
|
|
133
|
+
type: 'box-select-test-furnish',
|
|
134
|
+
parentId: 'level_test',
|
|
135
|
+
visible: false,
|
|
136
|
+
},
|
|
137
|
+
item_other: { id: 'item_other', type: 'box-select-test-furnish', parentId: 'level_other' },
|
|
138
|
+
} as unknown as Record<string, AnyNode>,
|
|
139
|
+
})
|
|
140
|
+
expect(collectSelectableCandidateIds().sort()).toEqual(['item_pick', 'wall_pick'])
|
|
141
|
+
})
|
|
109
142
|
})
|
|
@@ -19,17 +19,29 @@ function isVisibleSelectableNode(node: AnyNode): boolean {
|
|
|
19
19
|
export function collectSelectableCandidateIds(): string[] {
|
|
20
20
|
const { levelId } = useViewer.getState().selection
|
|
21
21
|
const { nodes } = useScene.getState()
|
|
22
|
-
const { phase
|
|
22
|
+
const { phase } = useEditor.getState()
|
|
23
23
|
const result: string[] = []
|
|
24
24
|
const seen = new Set<string>()
|
|
25
25
|
const addNode = (node: AnyNode | undefined) => {
|
|
26
|
-
if (
|
|
26
|
+
if (
|
|
27
|
+
!node ||
|
|
28
|
+
node.type === 'zone' ||
|
|
29
|
+
seen.has(node.id) ||
|
|
30
|
+
(node as { visible?: boolean }).visible === false
|
|
31
|
+
)
|
|
32
|
+
return
|
|
27
33
|
seen.add(node.id)
|
|
28
34
|
result.push(node.id)
|
|
29
35
|
}
|
|
30
36
|
const visitLevelDescendant = (id: AnyNodeId) => {
|
|
31
37
|
const node = nodes[id]
|
|
32
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
!node ||
|
|
40
|
+
node.type === 'zone' ||
|
|
41
|
+
seen.has(node.id) ||
|
|
42
|
+
(node as { visible?: boolean }).visible === false
|
|
43
|
+
)
|
|
44
|
+
return
|
|
33
45
|
|
|
34
46
|
if (isRegistrySelectable(node.type)) {
|
|
35
47
|
addNode(node)
|
|
@@ -52,14 +64,6 @@ export function collectSelectableCandidateIds(): string[] {
|
|
|
52
64
|
const levelNode = nodes[levelId as AnyNodeId] as LevelNode | undefined
|
|
53
65
|
if (levelNode?.type !== 'level') return []
|
|
54
66
|
|
|
55
|
-
if (phase === 'structure' && structureLayer === 'zones') {
|
|
56
|
-
for (const childId of levelNode.children) {
|
|
57
|
-
const node = nodes[childId as AnyNodeId]
|
|
58
|
-
if (node?.type === 'zone') addNode(node)
|
|
59
|
-
}
|
|
60
|
-
return result
|
|
61
|
-
}
|
|
62
|
-
|
|
63
67
|
for (const childId of levelNode.children) {
|
|
64
68
|
visitLevelDescendant(childId as AnyNodeId)
|
|
65
69
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Html } from '@react-three/drei'
|
|
2
2
|
import type { ThreeElements } from '@react-three/fiber'
|
|
3
3
|
import { forwardRef } from 'react'
|
|
4
|
-
import type
|
|
4
|
+
import { CircleGeometry, CylinderGeometry, type Group, SphereGeometry } from 'three'
|
|
5
5
|
import { furnishTools } from '../../../components/ui/action-menu/furnish-tools'
|
|
6
6
|
import { tools } from '../../../components/ui/action-menu/structure-tools'
|
|
7
7
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
8
|
+
import { wallCursorIcon } from '../../../lib/tool-cursor-icon'
|
|
8
9
|
import useEditor from '../../../store/use-editor'
|
|
9
10
|
import useWallSnapIndicator from '../../../store/use-wall-snap-indicator'
|
|
10
11
|
|
|
@@ -25,6 +26,19 @@ interface CursorSphereProps extends Omit<ThreeElements['group'], 'ref'> {
|
|
|
25
26
|
tooltipContent?: React.ReactNode
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
// One geometry each for the life of the module: the vertical line used to
|
|
30
|
+
// be a `<cylinderGeometry args={[…, height, 8]}>` rebuilt on EVERY height
|
|
31
|
+
// change (the cursor's drop to the floor moves with the pointer), and the
|
|
32
|
+
// ring / dot / tip were built and disposed on every toggle — WebGPU then
|
|
33
|
+
// drew a frame whose vertex buffers had just been destroyed ("Vertex
|
|
34
|
+
// buffer slot 0 required by [RenderPipeline "renderPipeline_MeshBasicMaterial_…"]
|
|
35
|
+
// was not set … DrawIndexed(96 …)" — the 8-segment cylinder's 96 indices).
|
|
36
|
+
// The line is a unit cylinder scaled to `height` instead.
|
|
37
|
+
const DOT_GEOMETRY = new CircleGeometry(0.06, 32)
|
|
38
|
+
const RING_GEOMETRY = new CircleGeometry(0.2, 32)
|
|
39
|
+
const LINE_GEOMETRY = new CylinderGeometry(0.01, 0.01, 1, 8)
|
|
40
|
+
const TIP_GEOMETRY = new SphereGeometry(0.08, 20, 14)
|
|
41
|
+
|
|
28
42
|
export const CursorSphere = forwardRef<Group, CursorSphereProps>(function CursorSphere(
|
|
29
43
|
{
|
|
30
44
|
color = '#818cf8',
|
|
@@ -45,11 +59,14 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
45
59
|
// (green square at a corner); hide the cursor's ground dot/ring so it
|
|
46
60
|
// doesn't sit on top of that glyph.
|
|
47
61
|
const isSnapping = useWallSnapIndicator((s) => s.point !== null)
|
|
62
|
+
const wallMode = useEditor((s) => s.continuationByContext.wall)
|
|
48
63
|
|
|
49
|
-
// Find the icon for the current tool
|
|
50
|
-
let activeToolConfig = null
|
|
64
|
+
// Find the icon for the current tool — the wall shows its Rooms variant.
|
|
65
|
+
let activeToolConfig: { label: string; iconSrc: string } | null | undefined = null
|
|
51
66
|
if (mode === 'build' && tool) {
|
|
52
|
-
if (tool === '
|
|
67
|
+
if (tool === 'wall') {
|
|
68
|
+
activeToolConfig = wallCursorIcon(wallMode)
|
|
69
|
+
} else if (tool === 'item' && catalogCategory) {
|
|
53
70
|
activeToolConfig = furnishTools.find((t) => t.catalogCategory === catalogCategory)
|
|
54
71
|
} else {
|
|
55
72
|
activeToolConfig = tools.find((t) => t.id === tool)
|
|
@@ -67,8 +84,7 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
67
84
|
<group rotation={[-Math.PI / 2, 0, 0]}>
|
|
68
85
|
{/* Center dot — at the ground unless the placement point is elevated */}
|
|
69
86
|
{!dotAtTip && (
|
|
70
|
-
<mesh layers={EDITOR_LAYER} renderOrder={2}>
|
|
71
|
-
<circleGeometry args={[0.06, 32]} />
|
|
87
|
+
<mesh geometry={DOT_GEOMETRY} layers={EDITOR_LAYER} renderOrder={2}>
|
|
72
88
|
<meshBasicMaterial
|
|
73
89
|
color={color}
|
|
74
90
|
depthTest={false}
|
|
@@ -80,8 +96,7 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
80
96
|
)}
|
|
81
97
|
|
|
82
98
|
{/* Outer ring / glow */}
|
|
83
|
-
<mesh layers={EDITOR_LAYER} renderOrder={2}>
|
|
84
|
-
<circleGeometry args={[0.2, 32]} />
|
|
99
|
+
<mesh geometry={RING_GEOMETRY} layers={EDITOR_LAYER} renderOrder={2}>
|
|
85
100
|
<meshBasicMaterial
|
|
86
101
|
color={color}
|
|
87
102
|
depthTest={false}
|
|
@@ -95,8 +110,13 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
95
110
|
|
|
96
111
|
{/* Vertical line */}
|
|
97
112
|
{height > 0 && (
|
|
98
|
-
<mesh
|
|
99
|
-
|
|
113
|
+
<mesh
|
|
114
|
+
geometry={LINE_GEOMETRY}
|
|
115
|
+
layers={EDITOR_LAYER}
|
|
116
|
+
position={[0, height / 2, 0]}
|
|
117
|
+
renderOrder={2}
|
|
118
|
+
scale={[1, height, 1]}
|
|
119
|
+
>
|
|
100
120
|
<meshBasicMaterial
|
|
101
121
|
color={color}
|
|
102
122
|
depthTest={false}
|
|
@@ -110,8 +130,12 @@ export const CursorSphere = forwardRef<Group, CursorSphereProps>(function Cursor
|
|
|
110
130
|
{/* Bright marker dot at the tip of the line — the actual placement
|
|
111
131
|
point, riding at the cursor while the line drops to the floor. */}
|
|
112
132
|
{dotAtTip && height > 0 && (
|
|
113
|
-
<mesh
|
|
114
|
-
|
|
133
|
+
<mesh
|
|
134
|
+
geometry={TIP_GEOMETRY}
|
|
135
|
+
layers={EDITOR_LAYER}
|
|
136
|
+
position={[0, height, 0]}
|
|
137
|
+
renderOrder={2}
|
|
138
|
+
>
|
|
115
139
|
<meshBasicMaterial color={color} depthTest={false} depthWrite={false} />
|
|
116
140
|
</mesh>
|
|
117
141
|
)}
|
|
@@ -458,8 +458,7 @@ export const SiteBoundaryEditor: React.FC = () => {
|
|
|
458
458
|
|
|
459
459
|
const exitSiteEditing = useCallback(() => {
|
|
460
460
|
const editor = useEditor.getState()
|
|
461
|
-
editor.setPhase('
|
|
462
|
-
editor.setStructureLayer('elements')
|
|
461
|
+
editor.setPhase('building')
|
|
463
462
|
editor.setMode('select')
|
|
464
463
|
}, [])
|
|
465
464
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
export const DEFAULT_STAIR_TYPE = 'straight' as const
|
|
2
2
|
export const DEFAULT_STAIR_WIDTH = 1.0
|
|
3
|
-
export const DEFAULT_STAIR_LENGTH = 3.0
|
|
4
|
-
export const DEFAULT_STAIR_HEIGHT = 2.5
|
|
5
|
-
export const DEFAULT_STAIR_STEP_COUNT = 10
|
|
6
3
|
export const DEFAULT_STAIR_ATTACHMENT_SIDE = 'front' as const
|
|
7
4
|
export const DEFAULT_STAIR_FILL_TO_FLOOR = true
|
|
8
5
|
export const DEFAULT_STAIR_THICKNESS = 0.25
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNode,
|
|
3
3
|
collectAlignmentAnchors,
|
|
4
|
-
|
|
4
|
+
createSizedStairFlight,
|
|
5
5
|
createSurfaceOpeningPreviewController,
|
|
6
6
|
DEFAULT_LEVEL_HEIGHT,
|
|
7
7
|
emitter,
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
type LevelNode,
|
|
12
12
|
movingAlignmentAnchors,
|
|
13
13
|
type NodeEvent,
|
|
14
|
+
planStairCreation,
|
|
15
|
+
planStairSizing,
|
|
14
16
|
resolveAlignment,
|
|
15
17
|
resolveFrozenFloorPlacementPatch,
|
|
16
18
|
resolveSupportSlabPatch,
|
|
@@ -23,6 +25,7 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
23
25
|
import { useThree } from '@react-three/fiber'
|
|
24
26
|
import { useEffect, useMemo, useRef } from 'react'
|
|
25
27
|
import * as THREE from 'three'
|
|
28
|
+
import { EDITOR_LAYER } from '../../../lib/constants'
|
|
26
29
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
27
30
|
import {
|
|
28
31
|
resolveStairDestinationLevel,
|
|
@@ -54,11 +57,9 @@ import {
|
|
|
54
57
|
DEFAULT_SPIRAL_TOP_LANDING_MODE,
|
|
55
58
|
DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
56
59
|
DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
57
|
-
DEFAULT_STAIR_LENGTH,
|
|
58
60
|
DEFAULT_STAIR_OPENING_OFFSET,
|
|
59
61
|
DEFAULT_STAIR_RAILING_HEIGHT,
|
|
60
62
|
DEFAULT_STAIR_RAILING_MODE,
|
|
61
|
-
DEFAULT_STAIR_STEP_COUNT,
|
|
62
63
|
DEFAULT_STAIR_THICKNESS,
|
|
63
64
|
DEFAULT_STAIR_TYPE,
|
|
64
65
|
DEFAULT_STAIR_WIDTH,
|
|
@@ -75,19 +76,18 @@ type MoveTriggerEvent = GridEvent | NodeEvent<AnyNode>
|
|
|
75
76
|
* Same algorithm as StairSystem's generateStairSegmentGeometry.
|
|
76
77
|
*/
|
|
77
78
|
function createStairPreviewGeometry(rise: number): THREE.BufferGeometry {
|
|
78
|
-
const riserHeight = rise
|
|
79
|
-
const treadDepth = DEFAULT_STAIR_LENGTH / DEFAULT_STAIR_STEP_COUNT
|
|
79
|
+
const { stepCount, length, riserHeight, going: treadDepth } = planStairSizing(rise)
|
|
80
80
|
|
|
81
81
|
const shape = new THREE.Shape()
|
|
82
82
|
shape.moveTo(0, 0)
|
|
83
83
|
|
|
84
|
-
for (let i = 0; i <
|
|
84
|
+
for (let i = 0; i < stepCount; i++) {
|
|
85
85
|
shape.lineTo(i * treadDepth, (i + 1) * riserHeight)
|
|
86
86
|
shape.lineTo((i + 1) * treadDepth, (i + 1) * riserHeight)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Fill to floor (absoluteHeight = 0)
|
|
90
|
-
shape.lineTo(
|
|
90
|
+
shape.lineTo(length, 0)
|
|
91
91
|
shape.lineTo(0, 0)
|
|
92
92
|
|
|
93
93
|
const geometry = new THREE.ExtrudeGeometry(shape, {
|
|
@@ -110,33 +110,28 @@ function createStairPreviewGeometry(rise: number): THREE.BufferGeometry {
|
|
|
110
110
|
* dropped on, not a constant: the placed stair has no explicit `totalRise`, so
|
|
111
111
|
* this is the height `syncStairRises` immediately converges it to anyway.
|
|
112
112
|
*/
|
|
113
|
-
function
|
|
113
|
+
function resolvePlacedStairBase(
|
|
114
114
|
nodes: Record<string, AnyNode>,
|
|
115
115
|
levelId: LevelNode['id'],
|
|
116
116
|
stair: StairNode,
|
|
117
117
|
supportSurface: PointerSupportSurface | null,
|
|
118
|
+
seed: StairSegmentNode,
|
|
118
119
|
): number {
|
|
119
|
-
//
|
|
120
|
-
// scene yet: the storey height minus whatever slab lifts the drop point,
|
|
121
|
-
// capped by the surface the pointer actually aims at (a floor under an
|
|
122
|
-
// overlapping deck must not elect the deck).
|
|
120
|
+
// Keep the pointer-selected base when an overlapping deck is above the cursor.
|
|
123
121
|
const base = getFloorStackedPosition({
|
|
124
122
|
node: stair,
|
|
125
|
-
nodes,
|
|
123
|
+
nodes: { ...nodes, [seed.id]: { ...seed, parentId: stair.id } },
|
|
126
124
|
position: stair.position,
|
|
127
125
|
rotation: stair.rotation,
|
|
128
126
|
levelId,
|
|
129
127
|
maxElevation: supportSurface?.elevation ?? null,
|
|
130
128
|
})[1]
|
|
131
|
-
return
|
|
129
|
+
return base
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
function createSeedStairSegment(rise: number) {
|
|
135
|
-
return
|
|
133
|
+
return createSizedStairFlight(rise, {
|
|
136
134
|
width: DEFAULT_STAIR_WIDTH,
|
|
137
|
-
length: DEFAULT_STAIR_LENGTH,
|
|
138
|
-
height: rise,
|
|
139
|
-
stepCount: DEFAULT_STAIR_STEP_COUNT,
|
|
140
135
|
attachmentSide: DEFAULT_STAIR_ATTACHMENT_SIDE,
|
|
141
136
|
fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
142
137
|
thickness: DEFAULT_STAIR_THICKNESS,
|
|
@@ -160,15 +155,16 @@ function createDefaultStairNode({
|
|
|
160
155
|
}) {
|
|
161
156
|
return StairNode.parse({
|
|
162
157
|
name,
|
|
158
|
+
parentId: levelId,
|
|
163
159
|
position,
|
|
164
160
|
rotation,
|
|
165
161
|
stairType: DEFAULT_STAIR_TYPE,
|
|
162
|
+
uniformRisers: true,
|
|
166
163
|
fromLevelId: levelId,
|
|
167
164
|
toLevelId: nextLevelId,
|
|
168
165
|
slabOpeningMode: 'destination',
|
|
169
166
|
openingOffset: DEFAULT_STAIR_OPENING_OFFSET,
|
|
170
167
|
width: DEFAULT_STAIR_WIDTH,
|
|
171
|
-
stepCount: DEFAULT_STAIR_STEP_COUNT,
|
|
172
168
|
thickness: DEFAULT_STAIR_THICKNESS,
|
|
173
169
|
fillToFloor: DEFAULT_STAIR_FILL_TO_FLOOR,
|
|
174
170
|
innerRadius: DEFAULT_CURVED_STAIR_INNER_RADIUS,
|
|
@@ -202,7 +198,9 @@ function commitStairPlacement(
|
|
|
202
198
|
|
|
203
199
|
const stairCount = Object.values(nodes).filter((n) => n.type === 'stair').length
|
|
204
200
|
const name = `Staircase ${stairCount + 1}`
|
|
205
|
-
const
|
|
201
|
+
const storeyRise = getLevelFloorToFloorHeight(placementLevelId, nodes)
|
|
202
|
+
if (!(storeyRise > 0)) return
|
|
203
|
+
const seed = createSeedStairSegment(storeyRise)
|
|
206
204
|
|
|
207
205
|
const destinationPlan = resolveStairDestinationLevel({
|
|
208
206
|
createMissing: true,
|
|
@@ -222,10 +220,28 @@ function commitStairPlacement(
|
|
|
222
220
|
}),
|
|
223
221
|
parentId: placementLevelId,
|
|
224
222
|
})
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
const base = resolvePlacedStairBase(nodes, placementLevelId, stair, supportSurface, seed)
|
|
224
|
+
let segment: StairSegmentNode
|
|
225
|
+
try {
|
|
226
|
+
segment = {
|
|
227
|
+
...planStairCreation(
|
|
228
|
+
stair,
|
|
229
|
+
nodes,
|
|
230
|
+
{
|
|
231
|
+
width: seed.width,
|
|
232
|
+
attachmentSide: seed.attachmentSide,
|
|
233
|
+
fillToFloor: seed.fillToFloor,
|
|
234
|
+
thickness: seed.thickness,
|
|
235
|
+
},
|
|
236
|
+
base,
|
|
237
|
+
).flight,
|
|
238
|
+
id: seed.id,
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error instanceof RangeError) return
|
|
242
|
+
throw error
|
|
228
243
|
}
|
|
244
|
+
stair.stepCount = segment.stepCount
|
|
229
245
|
const prospectiveNodes = {
|
|
230
246
|
...nodes,
|
|
231
247
|
[stair.id]: stair,
|
|
@@ -270,6 +286,7 @@ export const StairTool: React.FC = () => {
|
|
|
270
286
|
cameraRef.current = camera
|
|
271
287
|
const cursorRef = useRef<THREE.Group>(null)
|
|
272
288
|
const previewRef = useRef<THREE.Group>(null)
|
|
289
|
+
const previewMeshRef = useRef<THREE.Mesh>(null)
|
|
273
290
|
const rotationRef = useRef(0)
|
|
274
291
|
const supportSurfaceRef = useRef<PointerSupportSurface | null>(null)
|
|
275
292
|
const previousGridPosRef = useRef<[number, number] | null>(null)
|
|
@@ -281,8 +298,10 @@ export const StairTool: React.FC = () => {
|
|
|
281
298
|
)
|
|
282
299
|
const previewRiseRef = useRef(previewRise)
|
|
283
300
|
previewRiseRef.current = previewRise
|
|
284
|
-
const previewGeometry = useMemo(() => createStairPreviewGeometry(
|
|
285
|
-
|
|
301
|
+
const previewGeometry = useMemo(() => createStairPreviewGeometry(DEFAULT_LEVEL_HEIGHT), [])
|
|
302
|
+
const ownedGeometryRef = useRef(previewGeometry)
|
|
303
|
+
const geometryRiseRef = useRef(DEFAULT_LEVEL_HEIGHT)
|
|
304
|
+
useEffect(() => () => ownedGeometryRef.current.dispose(), [])
|
|
286
305
|
|
|
287
306
|
useEffect(() => {
|
|
288
307
|
if (!currentLevelId) return
|
|
@@ -321,7 +340,9 @@ export const StairTool: React.FC = () => {
|
|
|
321
340
|
nodes,
|
|
322
341
|
})
|
|
323
342
|
const nextLevelId = destinationPlan?.toLevel.id ?? placementLevelId
|
|
324
|
-
const
|
|
343
|
+
const storeyRise = getLevelFloorToFloorHeight(placementLevelId, nodes)
|
|
344
|
+
if (!(storeyRise > 0)) return null
|
|
345
|
+
const seed = createSeedStairSegment(storeyRise)
|
|
325
346
|
const stair = createDefaultStairNode({
|
|
326
347
|
name: 'Staircase Preview',
|
|
327
348
|
levelId: placementLevelId,
|
|
@@ -330,10 +351,28 @@ export const StairTool: React.FC = () => {
|
|
|
330
351
|
rotation,
|
|
331
352
|
segmentId: seed.id,
|
|
332
353
|
})
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
354
|
+
const base = resolvePlacedStairBase(nodes, placementLevelId, stair, supportSurface, seed)
|
|
355
|
+
let segment: StairSegmentNode
|
|
356
|
+
try {
|
|
357
|
+
segment = {
|
|
358
|
+
...planStairCreation(
|
|
359
|
+
stair,
|
|
360
|
+
nodes,
|
|
361
|
+
{
|
|
362
|
+
width: seed.width,
|
|
363
|
+
attachmentSide: seed.attachmentSide,
|
|
364
|
+
fillToFloor: seed.fillToFloor,
|
|
365
|
+
thickness: seed.thickness,
|
|
366
|
+
},
|
|
367
|
+
base,
|
|
368
|
+
).flight,
|
|
369
|
+
id: seed.id,
|
|
370
|
+
}
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error instanceof RangeError) return null
|
|
373
|
+
throw error
|
|
336
374
|
}
|
|
375
|
+
stair.stepCount = segment.stepCount
|
|
337
376
|
const previewNodes = {
|
|
338
377
|
...nodes,
|
|
339
378
|
...(destinationPlan?.createdLevel
|
|
@@ -360,11 +399,13 @@ export const StairTool: React.FC = () => {
|
|
|
360
399
|
rotation: number,
|
|
361
400
|
supportSurface: PointerSupportSurface | null,
|
|
362
401
|
) => {
|
|
363
|
-
const key = `${position[0].toFixed(3)},${position[2].toFixed(3)},${rotation.toFixed(4)},${supportSurface?.elevation.toFixed(3) ?? 'none'},${supportSurface?.sourceNodeId ?? 'floor'}`
|
|
402
|
+
const key = `${position[0].toFixed(3)},${position[2].toFixed(3)},${rotation.toFixed(4)},${supportSurface?.elevation.toFixed(3) ?? 'none'},${supportSurface?.sourceNodeId ?? 'floor'},${previewRiseRef.current}`
|
|
364
403
|
if (key === lastPreviewKey) return
|
|
365
404
|
lastPreviewKey = key
|
|
366
|
-
useStairBuildPreview.getState().setPreview([position[0], position[2]], rotation)
|
|
367
405
|
const preview = buildPreviewScene(position, rotation, supportSurface)
|
|
406
|
+
useStairBuildPreview
|
|
407
|
+
.getState()
|
|
408
|
+
.setPreview(preview ? [position[0], position[2]] : null, rotation, preview?.rise ?? null)
|
|
368
409
|
const frozenPatch =
|
|
369
410
|
preview && supportSurface?.sourceNodeId
|
|
370
411
|
? resolveFrozenFloorPlacementPatch(preview.stair, preview.previewNodes, {
|
|
@@ -398,11 +439,17 @@ export const StairTool: React.FC = () => {
|
|
|
398
439
|
}
|
|
399
440
|
|
|
400
441
|
if (previewRef.current) {
|
|
442
|
+
previewRef.current.visible = Boolean(preview)
|
|
401
443
|
previewRef.current.position.set(...visualPosition)
|
|
402
444
|
previewRef.current.rotation.y = rotation
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
445
|
+
if (preview && previewMeshRef.current && preview.rise !== geometryRiseRef.current) {
|
|
446
|
+
const previous = previewMeshRef.current.geometry
|
|
447
|
+
previewMeshRef.current.geometry = createStairPreviewGeometry(preview.rise)
|
|
448
|
+
previous.dispose()
|
|
449
|
+
ownedGeometryRef.current = previewMeshRef.current.geometry
|
|
450
|
+
geometryRiseRef.current = preview.rise
|
|
451
|
+
}
|
|
452
|
+
previewRef.current.scale.y = 1
|
|
406
453
|
}
|
|
407
454
|
|
|
408
455
|
// Forward-facing triangle (editor-side overlay). The run ascends along
|
|
@@ -410,19 +457,20 @@ export const StairTool: React.FC = () => {
|
|
|
410
457
|
// so `reversed` points the triangle out of the entry (where you approach
|
|
411
458
|
// from), sitting just before it — not inside the footprint or at the
|
|
412
459
|
// elevated far end. Centre is the footprint mid-run (origin is the entry).
|
|
460
|
+
if (!preview) {
|
|
461
|
+
useFacingPose.getState().clear()
|
|
462
|
+
openingPreview.clear()
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
const sizing = planStairSizing(preview.rise)
|
|
413
466
|
useFacingPose.getState().set({
|
|
414
467
|
position: visualPosition,
|
|
415
468
|
rotationY: rotation,
|
|
416
|
-
depth:
|
|
417
|
-
center: [0,
|
|
469
|
+
depth: sizing.length,
|
|
470
|
+
center: [0, sizing.length / 2],
|
|
418
471
|
reversed: true,
|
|
419
472
|
})
|
|
420
473
|
|
|
421
|
-
if (!preview) {
|
|
422
|
-
openingPreview.clear()
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
|
|
426
474
|
openingPreview.apply(syncAutoStairOpenings(preview.previewNodes))
|
|
427
475
|
}
|
|
428
476
|
|
|
@@ -625,7 +673,7 @@ export const StairTool: React.FC = () => {
|
|
|
625
673
|
forward-facing triangle is drawn by the editor-side overlay from the
|
|
626
674
|
pose published in `applyDraftPreview`. */}
|
|
627
675
|
<group ref={previewRef}>
|
|
628
|
-
<mesh
|
|
676
|
+
<mesh dispose={null} geometry={previewGeometry} layers={EDITOR_LAYER} ref={previewMeshRef}>
|
|
629
677
|
<meshStandardMaterial color="#818cf8" depthWrite={false} opacity={0.35} transparent />
|
|
630
678
|
</mesh>
|
|
631
679
|
</group>
|