@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
package/src/store/use-editor.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type AnyNodeId,
|
|
7
7
|
type BrushSettings,
|
|
8
8
|
type BuildingNode,
|
|
9
|
+
type CameraPose,
|
|
9
10
|
type ChimneyMaterialRole,
|
|
10
11
|
DEFAULT_BRUSH_SETTINGS,
|
|
11
12
|
type DormerSurfaceMaterialRole,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
continuationContextOf,
|
|
29
30
|
nextContinuation,
|
|
30
31
|
} from '../lib/continuation'
|
|
32
|
+
import { hydrationGatedStorage } from '../lib/hydration-gated-storage'
|
|
31
33
|
import {
|
|
32
34
|
type ActivePaintMaterial,
|
|
33
35
|
type PaintableMaterialTarget,
|
|
@@ -41,11 +43,19 @@ import {
|
|
|
41
43
|
normalizeCreatableMeasurementKind,
|
|
42
44
|
} from '../lib/measurement-kind'
|
|
43
45
|
import type { ModelExport } from '../lib/model-export'
|
|
46
|
+
import {
|
|
47
|
+
endPaintSession,
|
|
48
|
+
isPaintErasing,
|
|
49
|
+
type PaintMode,
|
|
50
|
+
paintRegionModeActive,
|
|
51
|
+
usePaintRegionMode,
|
|
52
|
+
} from '../lib/paint-region-mode'
|
|
44
53
|
import {
|
|
45
54
|
cyclePaintScope as cyclePaintScopeValue,
|
|
46
55
|
type PaintHoverInfo,
|
|
47
56
|
type PaintScope,
|
|
48
57
|
} from '../lib/paint-scope'
|
|
58
|
+
import { type RoomKey, sameRoom } from '../lib/room-selection'
|
|
49
59
|
import {
|
|
50
60
|
cycleSnappingModeIn,
|
|
51
61
|
defaultSnappingModeFor,
|
|
@@ -55,17 +65,35 @@ import {
|
|
|
55
65
|
snapContextOf,
|
|
56
66
|
snappingModesFor,
|
|
57
67
|
} from '../lib/snapping-mode'
|
|
68
|
+
import { cameraPoseStore } from './camera-pose-store'
|
|
58
69
|
import { publishNavigationSyncPoseToStore } from './navigation-sync-pose-store'
|
|
59
70
|
import useInteractionScope from './use-interaction-scope'
|
|
71
|
+
import {
|
|
72
|
+
clampViewPaneRatio,
|
|
73
|
+
DEFAULT_VIEW_LAYOUT,
|
|
74
|
+
isViewVisible,
|
|
75
|
+
normalizeViewLayout,
|
|
76
|
+
paneOfView,
|
|
77
|
+
sceneLayout,
|
|
78
|
+
storedViewLayout,
|
|
79
|
+
swapped,
|
|
80
|
+
VIEW_3D,
|
|
81
|
+
type ViewLayout,
|
|
82
|
+
type ViewPaneIndex,
|
|
83
|
+
type VisibleScene,
|
|
84
|
+
viewLayoutFromLegacy,
|
|
85
|
+
visibleScene,
|
|
86
|
+
withoutView,
|
|
87
|
+
withPaneView,
|
|
88
|
+
withSplitToggled,
|
|
89
|
+
withView,
|
|
90
|
+
} from './view-layout'
|
|
60
91
|
|
|
61
92
|
const DEFAULT_ACTIVE_SIDEBAR_PANEL = 'build'
|
|
62
|
-
const DEFAULT_FLOORPLAN_PANE_RATIO = 0.5
|
|
63
|
-
const MIN_FLOORPLAN_PANE_RATIO = 0.15
|
|
64
|
-
const MAX_FLOORPLAN_PANE_RATIO = 0.85
|
|
65
93
|
|
|
66
|
-
export type
|
|
67
|
-
|
|
68
|
-
export type
|
|
94
|
+
export type WorkspaceMode = 'edit' | 'studio' | 'sheets'
|
|
95
|
+
/** @deprecated The stage is views now: read `visibleScene(state)` (`VisibleScene`). */
|
|
96
|
+
export type ViewMode = VisibleScene
|
|
69
97
|
|
|
70
98
|
// Snapshot capture is invoked from two surfaces with different policies.
|
|
71
99
|
// `standard` mirrors the existing user-driven UX — pick region / viewport /
|
|
@@ -120,7 +148,11 @@ function clampCaptureFov(fov: number): number {
|
|
|
120
148
|
return Math.min(Math.max(Math.round(fov), CAPTURE_FOV_MIN), CAPTURE_FOV_MAX)
|
|
121
149
|
}
|
|
122
150
|
|
|
123
|
-
|
|
151
|
+
/**
|
|
152
|
+
* `site` is the outside (property line, terrain, the site panel); `building` is
|
|
153
|
+
* everything on the active level — walls, openings, stairs, items, rooms.
|
|
154
|
+
*/
|
|
155
|
+
export type Phase = 'site' | 'building'
|
|
124
156
|
|
|
125
157
|
/**
|
|
126
158
|
* `terrain-sculpt` is a mode, not a build tool, and that is the whole answer to
|
|
@@ -138,7 +170,7 @@ export type Phase = 'site' | 'structure' | 'furnish'
|
|
|
138
170
|
*/
|
|
139
171
|
export type Mode = 'select' | 'edit' | 'delete' | 'build' | 'material-paint' | 'terrain-sculpt'
|
|
140
172
|
|
|
141
|
-
//
|
|
173
|
+
// Building tools
|
|
142
174
|
type BuiltInStructureTool =
|
|
143
175
|
| 'wall'
|
|
144
176
|
| 'fence'
|
|
@@ -181,13 +213,13 @@ type BuiltInStructureTool =
|
|
|
181
213
|
/** Registry node kinds are valid build tools without central union edits. */
|
|
182
214
|
export type StructureTool = BuiltInStructureTool | (string & {})
|
|
183
215
|
|
|
184
|
-
//
|
|
216
|
+
// Item tools (items and decoration)
|
|
185
217
|
export type FurnishTool = 'item' | 'cabinet'
|
|
186
218
|
|
|
187
219
|
// Site mode tools
|
|
188
220
|
export type SiteTool = 'property-line'
|
|
189
221
|
|
|
190
|
-
// Catalog categories for
|
|
222
|
+
// Catalog categories for the item tool
|
|
191
223
|
export type CatalogCategory =
|
|
192
224
|
| 'furniture'
|
|
193
225
|
| 'appliance'
|
|
@@ -197,8 +229,6 @@ export type CatalogCategory =
|
|
|
197
229
|
| 'window'
|
|
198
230
|
| 'door'
|
|
199
231
|
|
|
200
|
-
export type StructureLayer = 'zones' | 'elements'
|
|
201
|
-
|
|
202
232
|
export type FloorplanSelectionTool = 'click' | 'marquee'
|
|
203
233
|
export type GridSnapStep = 0.5 | 0.25 | 0.1 | 0.05
|
|
204
234
|
|
|
@@ -267,11 +297,20 @@ export type GuideUiState = {
|
|
|
267
297
|
}
|
|
268
298
|
|
|
269
299
|
type EditorState = {
|
|
300
|
+
room: RoomKey | null
|
|
301
|
+
hoveredRoom: RoomKey | null
|
|
302
|
+
selectRoom: (room: RoomKey) => void
|
|
303
|
+
clearRoom: () => void
|
|
304
|
+
setHoveredRoom: (room: RoomKey | null) => void
|
|
270
305
|
phase: Phase
|
|
271
306
|
setPhase: (phase: Phase) => void
|
|
272
307
|
toolMode: ToolMode
|
|
273
308
|
armToolMode: (next: ToolMode) => void
|
|
274
|
-
|
|
309
|
+
/**
|
|
310
|
+
* The paint tool: enter paint mode in `subMode`, else the last painting
|
|
311
|
+
* sub-mode (never erase), picking `material` when given.
|
|
312
|
+
*/
|
|
313
|
+
armMaterialPaint: (material?: ActivePaintMaterial, subMode?: PaintMode) => void
|
|
275
314
|
mode: Mode
|
|
276
315
|
setMode: (mode: Mode) => void
|
|
277
316
|
tool: Tool | null
|
|
@@ -287,8 +326,6 @@ type EditorState = {
|
|
|
287
326
|
setToolDefaults: (tool: Tool, defaults: ToolDefaults | null) => void
|
|
288
327
|
lastMeasurementKind: CreatableMeasurementKind
|
|
289
328
|
setLastMeasurementKind: (kind: CreatableMeasurementKind) => void
|
|
290
|
-
structureLayer: StructureLayer
|
|
291
|
-
setStructureLayer: (layer: StructureLayer) => void
|
|
292
329
|
catalogCategory: CatalogCategory | null
|
|
293
330
|
setCatalogCategory: (category: CatalogCategory | null) => void
|
|
294
331
|
selectedItem: AssetInput | null
|
|
@@ -336,6 +373,8 @@ type EditorState = {
|
|
|
336
373
|
setActivePaintMaterial: (material: ActivePaintMaterial | null) => void
|
|
337
374
|
activePaintTarget: PaintableMaterialTarget
|
|
338
375
|
setActivePaintTarget: (target: PaintableMaterialTarget) => void
|
|
376
|
+
/** Mirror of the paint sub-mode being `erase`, for plugins that read the editor store. */
|
|
377
|
+
paintEraser: boolean
|
|
339
378
|
// Live vertex count of an in-progress polygon draft (slab / ceiling), so the
|
|
340
379
|
// contextual HUD can gate hints on it (e.g. "Finish" only once ≥ 3 points).
|
|
341
380
|
// 0 when not drafting. Not persisted.
|
|
@@ -350,10 +389,6 @@ type EditorState = {
|
|
|
350
389
|
// Cycle the scope within the hovered node's available set and return the new
|
|
351
390
|
// value. Bound to Shift while in paint mode.
|
|
352
391
|
cyclePaintScope: () => PaintScope
|
|
353
|
-
// When true, clicking a surface in paint mode clears it back to its
|
|
354
|
-
// default material instead of applying `activePaintMaterial`.
|
|
355
|
-
paintEraser: boolean
|
|
356
|
-
setPaintEraser: (eraser: boolean) => void
|
|
357
392
|
primeMaterialPaintFromSelection: () => MaterialPaintSelectionSnapshot
|
|
358
393
|
/**
|
|
359
394
|
* Terrain sculpt state. Lives here rather than in the tool component so the
|
|
@@ -418,15 +453,40 @@ type EditorState = {
|
|
|
418
453
|
captureMode: CaptureMode
|
|
419
454
|
isCaptureMode: boolean
|
|
420
455
|
setCaptureMode: (next: boolean | CaptureMode) => void
|
|
421
|
-
//
|
|
456
|
+
// What a scene capture shows: null is the whole building (every level, the
|
|
457
|
+
// default on entering capture); a level id shows that level and the ones
|
|
458
|
+
// below it, like editing. Picking a level moves the viewer's active level;
|
|
459
|
+
// leaving capture puts back the level the editor was on.
|
|
460
|
+
captureLevelId: LevelNode['id'] | null
|
|
461
|
+
setCaptureLevel: (levelId: LevelNode['id'] | null) => void
|
|
462
|
+
// The stage's panes, per workspace (see `view-layout.ts`). Read what is on
|
|
463
|
+
// screen through `activeViewLayout` / `isViewVisible`, never the raw record.
|
|
464
|
+
viewLayouts: Record<string, ViewLayout>
|
|
465
|
+
/** Show a view: focus it if visible, else in the focused pane, or `beside` it. */
|
|
466
|
+
showView: (viewId: string, options?: { beside?: boolean }) => void
|
|
467
|
+
setPaneView: (pane: ViewPaneIndex, viewId: string) => void
|
|
468
|
+
/** Take a view off the stage: a split keeps the other pane, a single pane returns to 3D. */
|
|
469
|
+
closeView: (viewId: string) => void
|
|
470
|
+
/** Replace the current workspace's layout (following a collaborator, tests). */
|
|
471
|
+
setViewLayout: (layout: ViewLayout) => void
|
|
472
|
+
/**
|
|
473
|
+
* @deprecated The scene projection on screen, kept in step with the layout for
|
|
474
|
+
* plugins written before views: `'3d'` also while only other views show. Read
|
|
475
|
+
* `visibleScene(state)` instead.
|
|
476
|
+
*/
|
|
422
477
|
viewMode: ViewMode
|
|
478
|
+
/**
|
|
479
|
+
* @deprecated Shows `mode` as the whole stage (`'split'`: the plan beside 3D).
|
|
480
|
+
* Use `setViewLayout(sceneLayout(mode))`, or `showView` for one view.
|
|
481
|
+
*/
|
|
423
482
|
setViewMode: (mode: ViewMode) => void
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
483
|
+
toggleSplit: () => void
|
|
484
|
+
swapPanes: () => void
|
|
485
|
+
setViewPaneRatio: (ratio: number) => void
|
|
486
|
+
focusViewPane: (pane: ViewPaneIndex) => void
|
|
487
|
+
/** Per-browser pins of the view bar, over each view's `defaultPinned`. */
|
|
488
|
+
pinnedViews: Record<string, boolean>
|
|
489
|
+
setViewPinned: (viewId: string, pinned: boolean) => void
|
|
430
490
|
isFloorplanHovered: boolean
|
|
431
491
|
setFloorplanHovered: (hovered: boolean) => void
|
|
432
492
|
// Toggleable DWV riser-diagram (plumbing isometric) overlay.
|
|
@@ -478,7 +538,6 @@ type EditorState = {
|
|
|
478
538
|
setShow2dVoronoi: (enabled: boolean) => void
|
|
479
539
|
// First-person walkthrough mode (street view)
|
|
480
540
|
isFirstPersonMode: boolean
|
|
481
|
-
_viewModeBeforeFirstPerson: ViewMode | null
|
|
482
541
|
setFirstPersonMode: (enabled: boolean) => void
|
|
483
542
|
// Which first-person controller runs while `isFirstPersonMode` is on. Reset to
|
|
484
543
|
// `walk` whenever first person is left, so the grounded controller stays the
|
|
@@ -502,14 +561,17 @@ type EditorState = {
|
|
|
502
561
|
setCaptureShutterHold: (hold: boolean) => void
|
|
503
562
|
// Workspace mode: 'edit' is the full editing surface; 'studio' is the
|
|
504
563
|
// render/snapshot surface (clean canvas, no editing chrome or selection).
|
|
505
|
-
//
|
|
564
|
+
// Each workspace keeps its own view layout.
|
|
506
565
|
workspaceMode: WorkspaceMode
|
|
507
|
-
_viewModeBeforeStudio: ViewMode | null
|
|
508
566
|
setWorkspaceMode: (mode: WorkspaceMode) => void
|
|
567
|
+
// The rail panel on screen. Setting it picks it for the current workspace.
|
|
568
|
+
// While the rail lacks the pick (a plugin panel registers once the scene's
|
|
569
|
+
// plugins load), the layout shows its first panel and leaves the pick in
|
|
570
|
+
// `sidebarPanels` (`useShownSidebarPanel`).
|
|
509
571
|
activeSidebarPanel: string
|
|
510
572
|
setActiveSidebarPanel: (id: string) => void
|
|
511
|
-
|
|
512
|
-
|
|
573
|
+
// The panel last picked in each workspace: entering one reopens its own.
|
|
574
|
+
sidebarPanels: Partial<Record<WorkspaceMode, string>>
|
|
513
575
|
// Mobile-only: pixel height of the secondary panel sheet while open (0 when closed).
|
|
514
576
|
// Read by the mobile layout so the viewer container can shrink to preview edits.
|
|
515
577
|
mobilePanelSheetHeight: number
|
|
@@ -520,21 +582,20 @@ type EditorState = {
|
|
|
520
582
|
|
|
521
583
|
export type PersistedEditorUiState = Pick<
|
|
522
584
|
EditorState,
|
|
523
|
-
| '
|
|
524
|
-
| 'toolMode'
|
|
525
|
-
| 'mode'
|
|
526
|
-
| 'tool'
|
|
527
|
-
| 'structureLayer'
|
|
528
|
-
| 'catalogCategory'
|
|
529
|
-
| 'isFloorplanOpen'
|
|
530
|
-
| 'viewMode'
|
|
585
|
+
'phase' | 'toolMode' | 'mode' | 'tool' | 'catalogCategory' | 'viewLayouts'
|
|
531
586
|
>
|
|
532
587
|
|
|
588
|
+
/** What blobs written before view layouts stored instead. */
|
|
589
|
+
type LegacyViewModeState = {
|
|
590
|
+
viewMode?: unknown
|
|
591
|
+
isFloorplanOpen?: unknown
|
|
592
|
+
floorplanPaneRatio?: unknown
|
|
593
|
+
}
|
|
594
|
+
|
|
533
595
|
type PersistedEditorLayoutState = Pick<
|
|
534
596
|
EditorState,
|
|
535
|
-
| '
|
|
536
|
-
| '
|
|
537
|
-
| 'splitOrientation'
|
|
597
|
+
| 'sidebarPanels'
|
|
598
|
+
| 'pinnedViews'
|
|
538
599
|
| 'floorplanSelectionTool'
|
|
539
600
|
| 'gridSnapStep'
|
|
540
601
|
| 'magneticSnap'
|
|
@@ -552,16 +613,13 @@ export const DEFAULT_PERSISTED_EDITOR_UI_STATE: PersistedEditorUiState = {
|
|
|
552
613
|
toolMode: { mode: 'select' },
|
|
553
614
|
mode: 'select',
|
|
554
615
|
tool: null,
|
|
555
|
-
structureLayer: 'elements',
|
|
556
616
|
catalogCategory: null,
|
|
557
|
-
|
|
558
|
-
viewMode: '3d',
|
|
617
|
+
viewLayouts: {},
|
|
559
618
|
}
|
|
560
619
|
|
|
561
620
|
export const DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE: PersistedEditorLayoutState = {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
splitOrientation: 'horizontal',
|
|
621
|
+
sidebarPanels: {},
|
|
622
|
+
pinnedViews: {},
|
|
565
623
|
floorplanSelectionTool: 'click',
|
|
566
624
|
gridSnapStep: 0.5,
|
|
567
625
|
magneticSnap: true,
|
|
@@ -570,6 +628,7 @@ export const DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE: PersistedEditorLayoutState =
|
|
|
570
628
|
wall: defaultSnappingModeFor('wall'),
|
|
571
629
|
item: defaultSnappingModeFor('item'),
|
|
572
630
|
polygon: defaultSnappingModeFor('polygon'),
|
|
631
|
+
rotation: defaultSnappingModeFor('rotation'),
|
|
573
632
|
},
|
|
574
633
|
continuationByContext: {
|
|
575
634
|
wall: CONTINUATION_PROFILES.wall.default,
|
|
@@ -589,25 +648,18 @@ type SelectDefaultBuildingAndLevelOptions = {
|
|
|
589
648
|
forceGroundLevel?: boolean
|
|
590
649
|
}
|
|
591
650
|
|
|
592
|
-
function defaultBuildTool(phase: Phase
|
|
593
|
-
|
|
594
|
-
if (phase === 'furnish') return 'item'
|
|
595
|
-
return structureLayer === 'zones' ? 'zone' : 'wall'
|
|
651
|
+
function defaultBuildTool(phase: Phase): StructureTool {
|
|
652
|
+
return phase === 'site' ? 'property-line' : 'wall'
|
|
596
653
|
}
|
|
597
654
|
|
|
598
|
-
function materializeToolMode(
|
|
599
|
-
mode: Mode,
|
|
600
|
-
tool: unknown,
|
|
601
|
-
phase: Phase,
|
|
602
|
-
structureLayer: StructureLayer,
|
|
603
|
-
): ToolMode {
|
|
655
|
+
function materializeToolMode(mode: Mode, tool: unknown, phase: Phase): ToolMode {
|
|
604
656
|
if (mode === 'build') {
|
|
605
657
|
return {
|
|
606
658
|
mode,
|
|
607
659
|
tool:
|
|
608
660
|
typeof tool === 'string' && tool.length > 0
|
|
609
661
|
? (tool as StructureTool)
|
|
610
|
-
: defaultBuildTool(phase
|
|
662
|
+
: defaultBuildTool(phase),
|
|
611
663
|
}
|
|
612
664
|
}
|
|
613
665
|
|
|
@@ -641,7 +693,7 @@ function withMaterializedToolMode(
|
|
|
641
693
|
): PersistedEditorUiState {
|
|
642
694
|
return {
|
|
643
695
|
...state,
|
|
644
|
-
toolMode: materializeToolMode(state.mode, state.tool, state.phase
|
|
696
|
+
toolMode: materializeToolMode(state.mode, state.tool, state.phase),
|
|
645
697
|
}
|
|
646
698
|
}
|
|
647
699
|
|
|
@@ -655,91 +707,82 @@ function normalizeModeForPhase(phase: Phase, mode: Mode | undefined): Mode {
|
|
|
655
707
|
return mode === 'build' || mode === 'delete' || mode === 'material-paint' ? mode : 'select'
|
|
656
708
|
}
|
|
657
709
|
|
|
658
|
-
function
|
|
659
|
-
|
|
660
|
-
|
|
710
|
+
function normalizeViewLayouts(
|
|
711
|
+
state: (Partial<PersistedEditorUiState> & LegacyViewModeState) | null | undefined,
|
|
712
|
+
): Record<string, ViewLayout> {
|
|
713
|
+
const stored = state?.viewLayouts
|
|
714
|
+
if (stored && typeof stored === 'object') {
|
|
715
|
+
return Object.fromEntries(
|
|
716
|
+
Object.entries(stored).map(([workspace, layout]) => [workspace, normalizeViewLayout(layout)]),
|
|
717
|
+
)
|
|
661
718
|
}
|
|
719
|
+
if (state?.viewMode === undefined && state?.isFloorplanOpen === undefined) return {}
|
|
720
|
+
return { edit: viewLayoutFromLegacy(state) }
|
|
721
|
+
}
|
|
662
722
|
|
|
663
|
-
|
|
723
|
+
function normalizeSidebarPanels(value: unknown): Partial<Record<WorkspaceMode, string>> {
|
|
724
|
+
if (!value || typeof value !== 'object') return {}
|
|
725
|
+
return Object.fromEntries(
|
|
726
|
+
Object.entries(value).filter(
|
|
727
|
+
(entry): entry is [string, string] =>
|
|
728
|
+
typeof entry[1] === 'string' && entry[1].trim().length > 0,
|
|
729
|
+
),
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function normalizePinnedViews(value: unknown): Record<string, boolean> {
|
|
734
|
+
if (!value || typeof value !== 'object') return {}
|
|
735
|
+
return Object.fromEntries(
|
|
736
|
+
Object.entries(value).filter(
|
|
737
|
+
(entry): entry is [string, boolean] => typeof entry[1] === 'boolean',
|
|
738
|
+
),
|
|
739
|
+
)
|
|
664
740
|
}
|
|
665
741
|
|
|
666
742
|
export function normalizePersistedEditorUiState(
|
|
667
|
-
state: Partial<PersistedEditorUiState> | null | undefined,
|
|
743
|
+
state: (Partial<PersistedEditorUiState> & LegacyViewModeState) | null | undefined,
|
|
668
744
|
): PersistedEditorUiState {
|
|
669
|
-
|
|
745
|
+
// Before Structure and Furnish merged, `phase` stored either of them.
|
|
746
|
+
const storedPhase: unknown = state?.phase
|
|
747
|
+
const phase: Phase =
|
|
748
|
+
storedPhase === 'building' || storedPhase === 'structure' || storedPhase === 'furnish'
|
|
749
|
+
? 'building'
|
|
750
|
+
: 'site'
|
|
670
751
|
const persistedToolMode = readPersistedToolMode(state)
|
|
671
752
|
let mode = normalizeModeForPhase(phase, persistedToolMode.mode)
|
|
672
753
|
|
|
673
|
-
|
|
674
|
-
let viewMode: ViewMode = '3d'
|
|
675
|
-
if (state?.viewMode === '2d' || state?.viewMode === '3d' || state?.viewMode === 'split') {
|
|
676
|
-
viewMode = state.viewMode
|
|
677
|
-
} else if (state?.isFloorplanOpen) {
|
|
678
|
-
viewMode = 'split'
|
|
679
|
-
}
|
|
680
|
-
const isFloorplanOpen = viewMode !== '3d'
|
|
754
|
+
const viewLayouts = normalizeViewLayouts(state)
|
|
681
755
|
|
|
682
|
-
// Both are persisted independently, and rehydrate goes through
|
|
683
|
-
//
|
|
684
|
-
// wins here for the same reason it does in `
|
|
685
|
-
// hidden canvas is a mode the user cannot use, and reviving one on load
|
|
686
|
-
// worse than reviving it mid-session — nothing on screen explains it.
|
|
687
|
-
|
|
756
|
+
// Both are persisted independently, and rehydrate goes through no setter, so
|
|
757
|
+
// this is the third place the sculpt/3D pair has to be reconciled. The view
|
|
758
|
+
// wins here for the same reason it does in `commitViewLayout`: a brush armed
|
|
759
|
+
// over a hidden canvas is a mode the user cannot use, and reviving one on load
|
|
760
|
+
// is worse than reviving it mid-session — nothing on screen explains it.
|
|
761
|
+
const editLayout = viewLayouts.edit ?? DEFAULT_VIEW_LAYOUT
|
|
762
|
+
if (mode === 'terrain-sculpt' && paneOfView(editLayout, VIEW_3D) === null) mode = 'select'
|
|
688
763
|
|
|
689
764
|
if (phase === 'site') {
|
|
690
765
|
return withMaterializedToolMode({
|
|
691
766
|
phase,
|
|
692
767
|
mode,
|
|
693
768
|
tool: mode === 'build' ? 'property-line' : null,
|
|
694
|
-
structureLayer: 'elements',
|
|
695
769
|
catalogCategory: null,
|
|
696
|
-
|
|
697
|
-
isFloorplanOpen,
|
|
698
|
-
})
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
if (phase === 'furnish') {
|
|
702
|
-
return withMaterializedToolMode({
|
|
703
|
-
phase,
|
|
704
|
-
mode,
|
|
705
|
-
tool: mode === 'build' ? 'item' : null,
|
|
706
|
-
structureLayer: 'elements',
|
|
707
|
-
catalogCategory: mode === 'build' ? (state?.catalogCategory ?? 'furniture') : null,
|
|
708
|
-
viewMode,
|
|
709
|
-
isFloorplanOpen,
|
|
770
|
+
viewLayouts,
|
|
710
771
|
})
|
|
711
772
|
}
|
|
712
773
|
|
|
713
|
-
const structureLayer = state?.structureLayer === 'zones' ? 'zones' : 'elements'
|
|
714
|
-
|
|
715
774
|
if (mode !== 'build') {
|
|
716
775
|
return withMaterializedToolMode({
|
|
717
776
|
phase,
|
|
718
777
|
mode,
|
|
719
778
|
tool: null,
|
|
720
|
-
structureLayer,
|
|
721
|
-
catalogCategory: null,
|
|
722
|
-
viewMode,
|
|
723
|
-
isFloorplanOpen,
|
|
724
|
-
})
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (structureLayer === 'zones') {
|
|
728
|
-
return withMaterializedToolMode({
|
|
729
|
-
phase,
|
|
730
|
-
mode,
|
|
731
|
-
tool: 'zone',
|
|
732
|
-
structureLayer,
|
|
733
779
|
catalogCategory: null,
|
|
734
|
-
|
|
735
|
-
isFloorplanOpen,
|
|
780
|
+
viewLayouts,
|
|
736
781
|
})
|
|
737
782
|
}
|
|
738
783
|
|
|
739
784
|
const tool =
|
|
740
|
-
persistedToolMode.tool &&
|
|
741
|
-
persistedToolMode.tool !== 'property-line' &&
|
|
742
|
-
persistedToolMode.tool !== 'zone'
|
|
785
|
+
persistedToolMode.tool && persistedToolMode.tool !== 'property-line'
|
|
743
786
|
? (persistedToolMode.tool as Tool)
|
|
744
787
|
: 'wall'
|
|
745
788
|
|
|
@@ -747,13 +790,33 @@ export function normalizePersistedEditorUiState(
|
|
|
747
790
|
phase,
|
|
748
791
|
mode,
|
|
749
792
|
tool,
|
|
750
|
-
structureLayer,
|
|
751
793
|
catalogCategory: tool === 'item' ? (state?.catalogCategory ?? null) : null,
|
|
752
|
-
|
|
753
|
-
isFloorplanOpen,
|
|
794
|
+
viewLayouts,
|
|
754
795
|
})
|
|
755
796
|
}
|
|
756
797
|
|
|
798
|
+
/**
|
|
799
|
+
* The UI state a project opens with.
|
|
800
|
+
*
|
|
801
|
+
* Every persisted preference comes back except the armed tool: a project always
|
|
802
|
+
* opens in select mode. The tool is session state, not a preference — re-arming
|
|
803
|
+
* the one the last session happened to leave behind turns the first canvas click
|
|
804
|
+
* of the new one into a wall (or a dropped item) instead of a selection, with
|
|
805
|
+
* nothing on screen to explain why. `partialize` no longer writes it; a blob
|
|
806
|
+
* written before that still carries it, so the rule is enforced on read.
|
|
807
|
+
*/
|
|
808
|
+
export function editorUiStateOnOpen(
|
|
809
|
+
state: (Partial<PersistedEditorUiState> & LegacyViewModeState) | null | undefined,
|
|
810
|
+
): PersistedEditorUiState {
|
|
811
|
+
return {
|
|
812
|
+
...normalizePersistedEditorUiState(state),
|
|
813
|
+
toolMode: DEFAULT_PERSISTED_EDITOR_UI_STATE.toolMode,
|
|
814
|
+
mode: DEFAULT_PERSISTED_EDITOR_UI_STATE.mode,
|
|
815
|
+
tool: DEFAULT_PERSISTED_EDITOR_UI_STATE.tool,
|
|
816
|
+
catalogCategory: DEFAULT_PERSISTED_EDITOR_UI_STATE.catalogCategory,
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
757
820
|
// Validate a persisted per-context mode against that context's allowed set
|
|
758
821
|
// (so e.g. a stale `angles` for items resets), falling back to its default.
|
|
759
822
|
function migrateSnappingMode(value: unknown, context: SnapContext): SnappingMode {
|
|
@@ -800,16 +863,18 @@ function normalizeContinuationByContext(
|
|
|
800
863
|
}
|
|
801
864
|
}
|
|
802
865
|
|
|
803
|
-
function normalizePersistedEditorLayoutState(
|
|
804
|
-
state:
|
|
866
|
+
export function normalizePersistedEditorLayoutState(
|
|
867
|
+
state:
|
|
868
|
+
| (Omit<Partial<PersistedEditorLayoutState>, 'snappingModeByContext'> &
|
|
869
|
+
LegacyContinuationState & {
|
|
870
|
+
snappingModeByContext?: Partial<Record<SnapContext, unknown>>
|
|
871
|
+
})
|
|
872
|
+
| null
|
|
873
|
+
| undefined,
|
|
805
874
|
): PersistedEditorLayoutState {
|
|
806
875
|
return {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
? state.activeSidebarPanel
|
|
810
|
-
: DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
|
811
|
-
floorplanPaneRatio: normalizeFloorplanPaneRatio(state?.floorplanPaneRatio),
|
|
812
|
-
splitOrientation: state?.splitOrientation === 'vertical' ? 'vertical' : 'horizontal',
|
|
876
|
+
sidebarPanels: normalizeSidebarPanels(state?.sidebarPanels),
|
|
877
|
+
pinnedViews: normalizePinnedViews(state?.pinnedViews),
|
|
813
878
|
floorplanSelectionTool: state?.floorplanSelectionTool === 'marquee' ? 'marquee' : 'click',
|
|
814
879
|
gridSnapStep: GRID_SNAP_STEPS.includes(state?.gridSnapStep as GridSnapStep)
|
|
815
880
|
? (state?.gridSnapStep as GridSnapStep)
|
|
@@ -821,6 +886,7 @@ function normalizePersistedEditorLayoutState(
|
|
|
821
886
|
wall: migrateSnappingMode(state?.snappingModeByContext?.wall, 'wall'),
|
|
822
887
|
item: migrateSnappingMode(state?.snappingModeByContext?.item, 'item'),
|
|
823
888
|
polygon: migrateSnappingMode(state?.snappingModeByContext?.polygon, 'polygon'),
|
|
889
|
+
rotation: migrateSnappingMode(state?.snappingModeByContext?.rotation, 'rotation'),
|
|
824
890
|
},
|
|
825
891
|
continuationByContext: normalizeContinuationByContext(state),
|
|
826
892
|
showReferenceFloor: state?.showReferenceFloor === true,
|
|
@@ -845,10 +911,8 @@ export function hasCustomPersistedEditorUiState(
|
|
|
845
911
|
normalizedState.phase !== DEFAULT_PERSISTED_EDITOR_UI_STATE.phase ||
|
|
846
912
|
normalizedState.mode !== DEFAULT_PERSISTED_EDITOR_UI_STATE.mode ||
|
|
847
913
|
normalizedState.tool !== DEFAULT_PERSISTED_EDITOR_UI_STATE.tool ||
|
|
848
|
-
normalizedState.structureLayer !== DEFAULT_PERSISTED_EDITOR_UI_STATE.structureLayer ||
|
|
849
914
|
normalizedState.catalogCategory !== DEFAULT_PERSISTED_EDITOR_UI_STATE.catalogCategory ||
|
|
850
|
-
normalizedState.
|
|
851
|
-
normalizedState.viewMode !== DEFAULT_PERSISTED_EDITOR_UI_STATE.viewMode
|
|
915
|
+
Object.keys(normalizedState.viewLayouts).length > 0
|
|
852
916
|
)
|
|
853
917
|
}
|
|
854
918
|
|
|
@@ -942,10 +1006,29 @@ export function selectSiteFloorplanContext() {
|
|
|
942
1006
|
})
|
|
943
1007
|
}
|
|
944
1008
|
|
|
945
|
-
//
|
|
946
|
-
|
|
947
|
-
//
|
|
948
|
-
let
|
|
1009
|
+
// The editor's active level when capture began (undefined: not in capture).
|
|
1010
|
+
let levelBeforeCapture: LevelNode['id'] | null | undefined
|
|
1011
|
+
// The editor's viewer selection while Preview borrows the shared viewer store.
|
|
1012
|
+
let viewerBeforePreview: {
|
|
1013
|
+
selection: ReturnType<typeof useViewer.getState>['selection']
|
|
1014
|
+
focusedUnitId: ReturnType<typeof useViewer.getState>['focusedUnitId']
|
|
1015
|
+
room: RoomKey | null
|
|
1016
|
+
toolMode: ToolMode
|
|
1017
|
+
cameraPose: CameraPose | null
|
|
1018
|
+
cameraMode: ReturnType<typeof useViewer.getState>['cameraMode']
|
|
1019
|
+
levelMode: ReturnType<typeof useViewer.getState>['levelMode']
|
|
1020
|
+
wallMode: ReturnType<typeof useViewer.getState>['wallMode']
|
|
1021
|
+
hideLevelsAboveSelection: boolean
|
|
1022
|
+
showZones: boolean
|
|
1023
|
+
} | null = null
|
|
1024
|
+
let previewCameraRestore: CameraPose | null = null
|
|
1025
|
+
|
|
1026
|
+
// Preview replaces the canvas; the returning controls consume this after mounting.
|
|
1027
|
+
export function takePreviewCameraRestore(): CameraPose | null {
|
|
1028
|
+
const pose = previewCameraRestore
|
|
1029
|
+
previewCameraRestore = null
|
|
1030
|
+
return pose
|
|
1031
|
+
}
|
|
949
1032
|
|
|
950
1033
|
/**
|
|
951
1034
|
* Hold the interaction scope that belongs to a sustained brush mode.
|
|
@@ -973,6 +1056,19 @@ function syncBrushModeScope(mode: Mode): void {
|
|
|
973
1056
|
scope.endIf((s) => s.kind === 'painting' || s.kind === 'sculpting')
|
|
974
1057
|
if (useEditor.getState().terrainSampling) useEditor.getState().setTerrainSampling(false)
|
|
975
1058
|
}
|
|
1059
|
+
// Erasing and a refused-gesture notice end with the paint session, whatever left it.
|
|
1060
|
+
if (mode !== 'material-paint') endPaintSession()
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* The modes whose click acts on what is selected. In every other mode a click
|
|
1065
|
+
* draws, places, paints, sculpts or deletes, so the ToolMode transition into
|
|
1066
|
+
* one ends the selection — room, nodes and reference — and its panel and
|
|
1067
|
+
* overlay go with it. A flow that needs a target inside such a mode hands it to
|
|
1068
|
+
* the tool itself (paint primes its target from the selection before it goes).
|
|
1069
|
+
*/
|
|
1070
|
+
function keepsSelection(mode: Mode): boolean {
|
|
1071
|
+
return mode === 'select' || mode === 'edit'
|
|
976
1072
|
}
|
|
977
1073
|
|
|
978
1074
|
/**
|
|
@@ -993,110 +1089,86 @@ export function isBrushMode(mode: Mode): boolean {
|
|
|
993
1089
|
const useEditor = create<EditorState>()(
|
|
994
1090
|
persist(
|
|
995
1091
|
(set, get) => ({
|
|
1092
|
+
room: null,
|
|
1093
|
+
hoveredRoom: null,
|
|
1094
|
+
selectRoom: (room) =>
|
|
1095
|
+
set({ room, hoveredRoom: null, selectedReferenceId: null, selectedMaterialTarget: null }),
|
|
1096
|
+
clearRoom: () => set({ room: null, hoveredRoom: null }),
|
|
1097
|
+
setHoveredRoom: (room) => {
|
|
1098
|
+
if (!sameRoom(get().hoveredRoom, room)) set({ hoveredRoom: room })
|
|
1099
|
+
},
|
|
996
1100
|
phase: DEFAULT_PERSISTED_EDITOR_UI_STATE.phase,
|
|
997
1101
|
setPhase: (phase) => {
|
|
998
1102
|
const currentPhase = get().phase
|
|
999
1103
|
if (currentPhase === phase) return
|
|
1000
1104
|
const wasBuilding = get().toolMode.mode === 'build'
|
|
1001
|
-
const structureLayer = phase === 'furnish' ? 'elements' : get().structureLayer
|
|
1002
1105
|
set({
|
|
1003
1106
|
phase,
|
|
1004
|
-
|
|
1005
|
-
|
|
1107
|
+
// Site has no rooms.
|
|
1108
|
+
...(phase === 'site' ? { room: null } : {}),
|
|
1109
|
+
hoveredRoom: null,
|
|
1110
|
+
catalogCategory: null,
|
|
1006
1111
|
})
|
|
1007
1112
|
get().armToolMode(
|
|
1008
|
-
wasBuilding
|
|
1009
|
-
? { mode: 'build', tool: defaultBuildTool(phase, structureLayer) }
|
|
1010
|
-
: { mode: 'select' },
|
|
1113
|
+
wasBuilding ? { mode: 'build', tool: defaultBuildTool(phase) } : { mode: 'select' },
|
|
1011
1114
|
)
|
|
1012
1115
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
selectSiteFloorplanContext()
|
|
1016
|
-
break
|
|
1017
|
-
|
|
1018
|
-
case 'structure':
|
|
1019
|
-
selectDefaultBuildingAndLevel()
|
|
1020
|
-
break
|
|
1021
|
-
|
|
1022
|
-
case 'furnish':
|
|
1023
|
-
selectDefaultBuildingAndLevel()
|
|
1024
|
-
break
|
|
1025
|
-
}
|
|
1116
|
+
if (phase === 'site') selectSiteFloorplanContext()
|
|
1117
|
+
else selectDefaultBuildingAndLevel()
|
|
1026
1118
|
},
|
|
1027
1119
|
toolMode: DEFAULT_PERSISTED_EDITOR_UI_STATE.toolMode,
|
|
1028
1120
|
armToolMode: (requested) => {
|
|
1029
1121
|
const current = get()
|
|
1030
1122
|
let phase = current.phase
|
|
1031
|
-
let structureLayer = current.structureLayer
|
|
1032
|
-
let viewMode = current.viewMode
|
|
1033
|
-
let isFloorplanOpen = current.isFloorplanOpen
|
|
1034
1123
|
const next = materializeToolMode(
|
|
1035
1124
|
requested.mode,
|
|
1036
1125
|
requested.mode === 'build' ? requested.tool : null,
|
|
1037
1126
|
phase,
|
|
1038
|
-
structureLayer,
|
|
1039
1127
|
)
|
|
1040
1128
|
|
|
1041
1129
|
if (next.mode === 'terrain-sculpt') {
|
|
1042
1130
|
phase = 'site'
|
|
1043
|
-
structureLayer = 'elements'
|
|
1044
|
-
if (viewMode === '2d') {
|
|
1045
|
-
viewMode = 'split'
|
|
1046
|
-
isFloorplanOpen = true
|
|
1047
|
-
}
|
|
1048
1131
|
} else if (next.mode === 'build' && next.tool === 'property-line') {
|
|
1049
1132
|
phase = 'site'
|
|
1050
|
-
structureLayer = 'elements'
|
|
1051
|
-
} else if (next.mode === 'build' && next.tool === 'zone') {
|
|
1052
|
-
phase = 'structure'
|
|
1053
|
-
structureLayer = 'zones'
|
|
1054
|
-
} else if (next.mode === 'build' && phase === 'site') {
|
|
1055
|
-
phase = 'structure'
|
|
1056
|
-
structureLayer = 'elements'
|
|
1057
|
-
} else if (next.mode === 'material-paint' && phase === 'site') {
|
|
1058
|
-
phase = 'structure'
|
|
1059
|
-
structureLayer = 'elements'
|
|
1060
|
-
} else if (phase !== 'structure') {
|
|
1061
|
-
structureLayer = 'elements'
|
|
1062
1133
|
}
|
|
1063
1134
|
|
|
1064
1135
|
const phaseChanged = phase !== current.phase
|
|
1136
|
+
const nextTool = next.mode === 'build' ? next.tool : null
|
|
1137
|
+
const endsSelection =
|
|
1138
|
+
!keepsSelection(next.mode) && (next.mode !== current.mode || nextTool !== current.tool)
|
|
1139
|
+
if (next.mode === 'material-paint') get().primeMaterialPaintFromSelection()
|
|
1065
1140
|
set({
|
|
1066
1141
|
toolMode: next,
|
|
1067
1142
|
mode: next.mode,
|
|
1068
|
-
tool:
|
|
1143
|
+
tool: nextTool,
|
|
1144
|
+
...(endsSelection ? { room: null, hoveredRoom: null, selectedReferenceId: null } : {}),
|
|
1069
1145
|
...(phaseChanged ? { phase } : {}),
|
|
1070
|
-
...(structureLayer !== current.structureLayer ? { structureLayer } : {}),
|
|
1071
|
-
...(viewMode !== current.viewMode ? { viewMode } : {}),
|
|
1072
|
-
...(isFloorplanOpen !== current.isFloorplanOpen ? { isFloorplanOpen } : {}),
|
|
1073
|
-
...(next.mode === 'build' && phase === 'furnish' && !current.catalogCategory
|
|
1074
|
-
? { catalogCategory: 'furniture' }
|
|
1075
|
-
: {}),
|
|
1076
1146
|
})
|
|
1147
|
+
// The brush works on the 3D canvas, so arming it opens 3D beside the plan.
|
|
1148
|
+
if (next.mode === 'terrain-sculpt' && !isViewVisible(get(), VIEW_3D)) {
|
|
1149
|
+
get().showView(VIEW_3D, { beside: true })
|
|
1150
|
+
}
|
|
1077
1151
|
|
|
1078
1152
|
if (phaseChanged) {
|
|
1079
1153
|
if (phase === 'site') selectSiteFloorplanContext()
|
|
1080
1154
|
else selectDefaultBuildingAndLevel()
|
|
1081
1155
|
}
|
|
1082
|
-
if (
|
|
1083
|
-
if (next.mode === 'terrain-sculpt') {
|
|
1084
|
-
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
1085
|
-
}
|
|
1156
|
+
if (endsSelection) useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
1086
1157
|
syncBrushModeScope(next.mode)
|
|
1087
1158
|
},
|
|
1088
|
-
armMaterialPaint: (material) => {
|
|
1159
|
+
armMaterialPaint: (material, subMode) => {
|
|
1089
1160
|
get().armToolMode({ mode: 'material-paint' })
|
|
1161
|
+
const paintMode = usePaintRegionMode.getState()
|
|
1162
|
+
paintMode.setMode(subMode ?? paintMode.drawMode)
|
|
1090
1163
|
if (material) get().setActivePaintMaterial(material)
|
|
1091
1164
|
},
|
|
1092
1165
|
mode: DEFAULT_PERSISTED_EDITOR_UI_STATE.mode,
|
|
1093
1166
|
setMode: (mode) => {
|
|
1094
1167
|
if (mode === 'build') {
|
|
1095
|
-
const { phase,
|
|
1168
|
+
const { phase, toolMode } = get()
|
|
1096
1169
|
get().armToolMode({
|
|
1097
1170
|
mode,
|
|
1098
|
-
tool:
|
|
1099
|
-
toolMode.mode === 'build' ? toolMode.tool : defaultBuildTool(phase, structureLayer),
|
|
1171
|
+
tool: toolMode.mode === 'build' ? toolMode.tool : defaultBuildTool(phase),
|
|
1100
1172
|
})
|
|
1101
1173
|
return
|
|
1102
1174
|
}
|
|
@@ -1112,7 +1184,7 @@ const useEditor = create<EditorState>()(
|
|
|
1112
1184
|
get().armToolMode({ mode: 'select' })
|
|
1113
1185
|
return
|
|
1114
1186
|
}
|
|
1115
|
-
get().armToolMode(materializeToolMode(get().mode, null, get().phase
|
|
1187
|
+
get().armToolMode(materializeToolMode(get().mode, null, get().phase))
|
|
1116
1188
|
},
|
|
1117
1189
|
toolDefaults: {},
|
|
1118
1190
|
setToolDefaults: (tool, defaults) =>
|
|
@@ -1127,22 +1199,6 @@ const useEditor = create<EditorState>()(
|
|
|
1127
1199
|
}),
|
|
1128
1200
|
lastMeasurementKind: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.lastMeasurementKind,
|
|
1129
1201
|
setLastMeasurementKind: (kind) => set({ lastMeasurementKind: kind }),
|
|
1130
|
-
structureLayer: DEFAULT_PERSISTED_EDITOR_UI_STATE.structureLayer,
|
|
1131
|
-
setStructureLayer: (layer) => {
|
|
1132
|
-
const wasBuilding = get().toolMode.mode === 'build'
|
|
1133
|
-
set({ structureLayer: layer })
|
|
1134
|
-
get().armToolMode(
|
|
1135
|
-
wasBuilding
|
|
1136
|
-
? { mode: 'build', tool: layer === 'zones' ? 'zone' : 'wall' }
|
|
1137
|
-
: { mode: 'select' },
|
|
1138
|
-
)
|
|
1139
|
-
|
|
1140
|
-
const viewer = useViewer.getState()
|
|
1141
|
-
viewer.setSelection({
|
|
1142
|
-
selectedIds: [],
|
|
1143
|
-
zoneId: null,
|
|
1144
|
-
})
|
|
1145
|
-
},
|
|
1146
1202
|
catalogCategory: DEFAULT_PERSISTED_EDITOR_UI_STATE.catalogCategory,
|
|
1147
1203
|
setCatalogCategory: (category) => set({ catalogCategory: category }),
|
|
1148
1204
|
selectedItem: null,
|
|
@@ -1201,11 +1257,16 @@ const useEditor = create<EditorState>()(
|
|
|
1201
1257
|
},
|
|
1202
1258
|
selectedMaterialTarget: null,
|
|
1203
1259
|
setSelectedMaterialTarget: (target) => set({ selectedMaterialTarget: target }),
|
|
1260
|
+
paintEraser: usePaintRegionMode.getState().mode === 'erase',
|
|
1204
1261
|
activePaintMaterial: null,
|
|
1205
|
-
// Picking a material implies paint, not erase —
|
|
1262
|
+
// Picking a material implies paint, not erase — leave the eraser so the
|
|
1206
1263
|
// next click applies the chosen material.
|
|
1207
|
-
setActivePaintMaterial: (material) =>
|
|
1208
|
-
set({ activePaintMaterial: material
|
|
1264
|
+
setActivePaintMaterial: (material) => {
|
|
1265
|
+
set({ activePaintMaterial: material })
|
|
1266
|
+
if (material && isPaintErasing()) {
|
|
1267
|
+
usePaintRegionMode.getState().setMode(usePaintRegionMode.getState().drawMode)
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1209
1270
|
activePaintTarget: 'wall',
|
|
1210
1271
|
setActivePaintTarget: (target) =>
|
|
1211
1272
|
set((state) =>
|
|
@@ -1224,8 +1285,6 @@ const useEditor = create<EditorState>()(
|
|
|
1224
1285
|
set({ paintScope: next })
|
|
1225
1286
|
return next
|
|
1226
1287
|
},
|
|
1227
|
-
paintEraser: false,
|
|
1228
|
-
setPaintEraser: (eraser) => set({ paintEraser: eraser }),
|
|
1229
1288
|
primeMaterialPaintFromSelection: () => {
|
|
1230
1289
|
const selectedId =
|
|
1231
1290
|
useViewer.getState().selection.selectedIds.length === 1
|
|
@@ -1237,6 +1296,7 @@ const useEditor = create<EditorState>()(
|
|
|
1237
1296
|
selectedId,
|
|
1238
1297
|
}) ?? get().activePaintTarget
|
|
1239
1298
|
const activePaintMaterial = resolveActivePaintMaterialFromSelection({
|
|
1299
|
+
materials: useScene.getState().materials,
|
|
1240
1300
|
nodes: useScene.getState().nodes,
|
|
1241
1301
|
selectedId,
|
|
1242
1302
|
selectedMaterialTarget: get().selectedMaterialTarget,
|
|
@@ -1273,7 +1333,8 @@ const useEditor = create<EditorState>()(
|
|
|
1273
1333
|
canFindNode: false,
|
|
1274
1334
|
setCanFindNode: (canFind) => set({ canFindNode: canFind }),
|
|
1275
1335
|
selectedReferenceId: null,
|
|
1276
|
-
setSelectedReferenceId: (id) =>
|
|
1336
|
+
setSelectedReferenceId: (id) =>
|
|
1337
|
+
set({ selectedReferenceId: id, ...(id ? { room: null, hoveredRoom: null } : {}) }),
|
|
1277
1338
|
referenceScaleActiveGuideId: null,
|
|
1278
1339
|
setReferenceScaleActiveGuideId: (id) => set({ referenceScaleActiveGuideId: id }),
|
|
1279
1340
|
guideUi: {},
|
|
@@ -1318,13 +1379,63 @@ const useEditor = create<EditorState>()(
|
|
|
1318
1379
|
),
|
|
1319
1380
|
isPreviewMode: false,
|
|
1320
1381
|
setPreviewMode: (preview) => {
|
|
1382
|
+
if (preview === get().isPreviewMode) return
|
|
1321
1383
|
if (preview) {
|
|
1384
|
+
const viewer = useViewer.getState()
|
|
1385
|
+
viewerBeforePreview = {
|
|
1386
|
+
selection: viewer.selection,
|
|
1387
|
+
focusedUnitId: viewer.focusedUnitId,
|
|
1388
|
+
room: get().room,
|
|
1389
|
+
toolMode: get().toolMode,
|
|
1390
|
+
cameraPose: cameraPoseStore.getState().pose,
|
|
1391
|
+
cameraMode: viewer.cameraMode,
|
|
1392
|
+
levelMode: viewer.levelMode,
|
|
1393
|
+
wallMode: viewer.wallMode,
|
|
1394
|
+
hideLevelsAboveSelection: viewer.hideLevelsAboveSelection,
|
|
1395
|
+
showZones: viewer.showZones,
|
|
1396
|
+
}
|
|
1397
|
+
previewCameraRestore = null
|
|
1322
1398
|
set({ isPreviewMode: true, catalogCategory: null })
|
|
1323
1399
|
get().armToolMode({ mode: 'select' })
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1400
|
+
// Preview starts on the editor's floor with nothing else picked; what
|
|
1401
|
+
// the visitor selects there is its own and is dropped on the way out.
|
|
1402
|
+
viewer.setSelection({ selectedIds: [], zoneId: null })
|
|
1403
|
+
viewer.setFocusedUnit(null)
|
|
1404
|
+
viewer.setHoveredId(null)
|
|
1326
1405
|
} else {
|
|
1406
|
+
if (get().isFirstPersonMode) get().setFirstPersonMode(false)
|
|
1407
|
+
useViewer.getState().setWalkthroughMode(false)
|
|
1327
1408
|
set({ isPreviewMode: false })
|
|
1409
|
+
const before = viewerBeforePreview
|
|
1410
|
+
viewerBeforePreview = null
|
|
1411
|
+
if (!before) return
|
|
1412
|
+
const viewer = useViewer.getState()
|
|
1413
|
+
const nodes = useScene.getState().nodes
|
|
1414
|
+
const exists = (id: string | null) => id !== null && !!nodes[id as AnyNodeId]
|
|
1415
|
+
get().armToolMode(before.toolMode)
|
|
1416
|
+
set({
|
|
1417
|
+
room:
|
|
1418
|
+
before.room && exists(before.room.zoneId) && exists(before.room.levelId)
|
|
1419
|
+
? before.room
|
|
1420
|
+
: null,
|
|
1421
|
+
hoveredRoom: null,
|
|
1422
|
+
})
|
|
1423
|
+
viewer.setSelection({
|
|
1424
|
+
buildingId: exists(before.selection.buildingId) ? before.selection.buildingId : null,
|
|
1425
|
+
levelId: exists(before.selection.levelId) ? before.selection.levelId : null,
|
|
1426
|
+
zoneId: exists(before.selection.zoneId) ? before.selection.zoneId : null,
|
|
1427
|
+
selectedIds: before.selection.selectedIds.filter(exists),
|
|
1428
|
+
})
|
|
1429
|
+
viewer.setFocusedUnit(exists(before.focusedUnitId) ? before.focusedUnitId : null)
|
|
1430
|
+
viewer.setHoveredId(null)
|
|
1431
|
+
viewer.setCameraMode(before.cameraMode)
|
|
1432
|
+
viewer.setLevelMode(before.levelMode)
|
|
1433
|
+
viewer.setWallMode(before.wallMode)
|
|
1434
|
+
useViewer.setState({
|
|
1435
|
+
hideLevelsAboveSelection: before.hideLevelsAboveSelection,
|
|
1436
|
+
showZones: before.showZones,
|
|
1437
|
+
})
|
|
1438
|
+
previewCameraRestore = before.cameraPose
|
|
1328
1439
|
}
|
|
1329
1440
|
},
|
|
1330
1441
|
captureMode: { mode: 'idle' } as CaptureMode,
|
|
@@ -1333,59 +1444,60 @@ const useEditor = create<EditorState>()(
|
|
|
1333
1444
|
const resolved: CaptureMode =
|
|
1334
1445
|
typeof next === 'boolean' ? { mode: next ? 'standard' : 'idle' } : next
|
|
1335
1446
|
const entering = resolved.mode !== 'idle'
|
|
1447
|
+
const wasCapturing = get().isCaptureMode
|
|
1448
|
+
if (entering && !wasCapturing) {
|
|
1449
|
+
levelBeforeCapture = useViewer.getState().selection.levelId
|
|
1450
|
+
set({ captureLevelId: null })
|
|
1451
|
+
}
|
|
1336
1452
|
// Walk / drone framing is a capture-only camera, so leaving capture always
|
|
1337
|
-
// lands back on orbit.
|
|
1338
|
-
// the
|
|
1453
|
+
// lands back on orbit. Capture frames in 3D alone (`activeViewLayout`)
|
|
1454
|
+
// without touching the stored layout, so leaving it needs no restore.
|
|
1339
1455
|
if (!entering && get().isFirstPersonMode) {
|
|
1340
1456
|
get().setFirstPersonMode(false)
|
|
1341
1457
|
}
|
|
1342
|
-
set(
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1356
|
-
return { captureMode: resolved, isCaptureMode: true }
|
|
1357
|
-
}
|
|
1358
|
-
const restore = viewModeBeforeCapture
|
|
1359
|
-
viewModeBeforeCapture = null
|
|
1360
|
-
if (restore && restore !== '3d') {
|
|
1361
|
-
return {
|
|
1362
|
-
captureMode: resolved,
|
|
1363
|
-
isCaptureMode: false,
|
|
1364
|
-
viewMode: restore,
|
|
1365
|
-
isFloorplanOpen: true,
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
return { captureMode: resolved, isCaptureMode: false }
|
|
1369
|
-
})
|
|
1458
|
+
set(
|
|
1459
|
+
entering
|
|
1460
|
+
? { captureMode: resolved, isCaptureMode: true }
|
|
1461
|
+
: { captureMode: resolved, isCaptureMode: false, captureLevelId: null },
|
|
1462
|
+
)
|
|
1463
|
+
if (!entering && wasCapturing) {
|
|
1464
|
+
const level = levelBeforeCapture
|
|
1465
|
+
levelBeforeCapture = undefined
|
|
1466
|
+
const viewer = useViewer.getState()
|
|
1467
|
+
const exists = level === null || useScene.getState().nodes[level as AnyNodeId]
|
|
1468
|
+
if (level !== undefined && exists && viewer.selection.levelId !== level)
|
|
1469
|
+
viewer.setSelection({ levelId: level })
|
|
1470
|
+
}
|
|
1370
1471
|
},
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
// work in — a mode you cannot use and cannot see how to leave.
|
|
1378
|
-
if (mode === '2d' && get().mode === 'terrain-sculpt') get().setMode('select')
|
|
1472
|
+
captureLevelId: null,
|
|
1473
|
+
setCaptureLevel: (levelId) => {
|
|
1474
|
+
if (!get().isCaptureMode) return
|
|
1475
|
+
set({ captureLevelId: levelId })
|
|
1476
|
+
const viewer = useViewer.getState()
|
|
1477
|
+
if (levelId && viewer.selection.levelId !== levelId) viewer.setSelection({ levelId })
|
|
1379
1478
|
},
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1479
|
+
viewLayouts: DEFAULT_PERSISTED_EDITOR_UI_STATE.viewLayouts,
|
|
1480
|
+
showView: (viewId, options) =>
|
|
1481
|
+
commitViewLayout(withView(storedViewLayout(get()), viewId, options)),
|
|
1482
|
+
setPaneView: (pane, viewId) =>
|
|
1483
|
+
commitViewLayout(withPaneView(storedViewLayout(get()), pane, viewId)),
|
|
1484
|
+
closeView: (viewId) => commitViewLayout(withoutView(storedViewLayout(get()), viewId)),
|
|
1485
|
+
setViewLayout: (layout) => commitViewLayout(normalizeViewLayout(layout)),
|
|
1486
|
+
viewMode: '3d',
|
|
1487
|
+
setViewMode: (mode) => commitViewLayout(sceneLayout(mode)),
|
|
1488
|
+
toggleSplit: () => commitViewLayout(withSplitToggled(storedViewLayout(get()))),
|
|
1489
|
+
swapPanes: () => commitViewLayout(swapped(storedViewLayout(get()))),
|
|
1490
|
+
setViewPaneRatio: (ratio) =>
|
|
1491
|
+
commitViewLayout({ ...storedViewLayout(get()), ratio: clampViewPaneRatio(ratio) }),
|
|
1492
|
+
focusViewPane: (pane) => {
|
|
1493
|
+
const layout = storedViewLayout(get())
|
|
1494
|
+
if (layout.focus !== pane && (pane === 0 || layout.split)) {
|
|
1495
|
+
commitViewLayout({ ...layout, focus: pane })
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
pinnedViews: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.pinnedViews,
|
|
1499
|
+
setViewPinned: (viewId, pinned) =>
|
|
1500
|
+
set((state) => ({ pinnedViews: { ...state.pinnedViews, [viewId]: pinned } })),
|
|
1389
1501
|
isFloorplanHovered: false,
|
|
1390
1502
|
setFloorplanHovered: (hovered) => set({ isFloorplanHovered: hovered }),
|
|
1391
1503
|
isRiserOpen: false,
|
|
@@ -1461,26 +1573,14 @@ const useEditor = create<EditorState>()(
|
|
|
1461
1573
|
show2dVoronoi: false,
|
|
1462
1574
|
setShow2dVoronoi: (enabled) => set({ show2dVoronoi: enabled }),
|
|
1463
1575
|
isFirstPersonMode: false,
|
|
1464
|
-
|
|
1576
|
+
// First person shows 3D alone (`activeViewLayout`); the stored layout
|
|
1577
|
+
// comes back on exit untouched.
|
|
1465
1578
|
setFirstPersonMode: (enabled) => {
|
|
1466
1579
|
if (enabled) {
|
|
1467
|
-
|
|
1468
|
-
set({
|
|
1469
|
-
isFirstPersonMode: true,
|
|
1470
|
-
_viewModeBeforeFirstPerson: currentViewMode,
|
|
1471
|
-
viewMode: '3d',
|
|
1472
|
-
isFloorplanOpen: false,
|
|
1473
|
-
catalogCategory: null,
|
|
1474
|
-
})
|
|
1580
|
+
set({ isFirstPersonMode: true, catalogCategory: null })
|
|
1475
1581
|
get().armToolMode({ mode: 'select' })
|
|
1476
1582
|
} else {
|
|
1477
|
-
|
|
1478
|
-
set({
|
|
1479
|
-
isFirstPersonMode: false,
|
|
1480
|
-
firstPersonMovementMode: 'walk',
|
|
1481
|
-
_viewModeBeforeFirstPerson: null,
|
|
1482
|
-
...(prevMode ? { viewMode: prevMode, isFloorplanOpen: prevMode !== '3d' } : {}),
|
|
1483
|
-
})
|
|
1583
|
+
set({ isFirstPersonMode: false, firstPersonMovementMode: 'walk' })
|
|
1484
1584
|
}
|
|
1485
1585
|
},
|
|
1486
1586
|
firstPersonMovementMode: 'walk' as FirstPersonMovementMode,
|
|
@@ -1498,35 +1598,32 @@ const useEditor = create<EditorState>()(
|
|
|
1498
1598
|
captureShutterHold: false,
|
|
1499
1599
|
setCaptureShutterHold: (hold) => set({ captureShutterHold: hold }),
|
|
1500
1600
|
workspaceMode: 'edit' as WorkspaceMode,
|
|
1501
|
-
_viewModeBeforeStudio: null as ViewMode | null,
|
|
1502
1601
|
setWorkspaceMode: (mode) => {
|
|
1503
1602
|
if (get().workspaceMode === mode) return
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
})
|
|
1603
|
+
// Each workspace reopens on its last pick. One with no pick keeps the
|
|
1604
|
+
// panel open now when its rail has it, else shows its first
|
|
1605
|
+
// (`useShownSidebarPanel`).
|
|
1606
|
+
const activeSidebarPanel = get().sidebarPanels[mode] ?? get().activeSidebarPanel
|
|
1607
|
+
// Every non-'edit' workspace (studio's clean canvas, sheets' paper
|
|
1608
|
+
// space) enters the same way: drop the editing chrome. The view layout
|
|
1609
|
+
// needs no stash — each workspace keeps its own.
|
|
1610
|
+
if (mode !== 'edit') {
|
|
1611
|
+
set({ workspaceMode: mode, activeSidebarPanel, catalogCategory: null })
|
|
1513
1612
|
get().armToolMode({ mode: 'select' })
|
|
1514
1613
|
// Clear selection so no edit affordances bleed into the clean canvas.
|
|
1515
1614
|
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
1516
1615
|
} else {
|
|
1517
|
-
|
|
1518
|
-
set({
|
|
1519
|
-
workspaceMode: 'edit',
|
|
1520
|
-
_viewModeBeforeStudio: null,
|
|
1521
|
-
...(prevMode ? { viewMode: prevMode, isFloorplanOpen: prevMode !== '3d' } : {}),
|
|
1522
|
-
})
|
|
1616
|
+
set({ workspaceMode: 'edit', activeSidebarPanel })
|
|
1523
1617
|
}
|
|
1618
|
+
yieldBrushToHiddenCanvas()
|
|
1524
1619
|
},
|
|
1525
1620
|
activeSidebarPanel: DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
|
1526
|
-
setActiveSidebarPanel: (id) =>
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1621
|
+
setActiveSidebarPanel: (id) =>
|
|
1622
|
+
set((state) => ({
|
|
1623
|
+
activeSidebarPanel: id,
|
|
1624
|
+
sidebarPanels: { ...state.sidebarPanels, [state.workspaceMode]: id },
|
|
1625
|
+
})),
|
|
1626
|
+
sidebarPanels: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.sidebarPanels,
|
|
1530
1627
|
mobilePanelSheetHeight: 0,
|
|
1531
1628
|
setMobilePanelSheetHeight: (px) => set({ mobilePanelSheetHeight: Math.max(0, px) }),
|
|
1532
1629
|
modelExport: null,
|
|
@@ -1534,10 +1631,9 @@ const useEditor = create<EditorState>()(
|
|
|
1534
1631
|
}),
|
|
1535
1632
|
{
|
|
1536
1633
|
name: 'pascal-editor-ui-preferences',
|
|
1634
|
+
storage: hydrationGatedStorage((): boolean => useEditor.persist.hasHydrated()),
|
|
1537
1635
|
merge: (persistedState, currentState) => {
|
|
1538
|
-
const uiState =
|
|
1539
|
-
persistedState as Partial<PersistedEditorState>,
|
|
1540
|
-
)
|
|
1636
|
+
const uiState = editorUiStateOnOpen(persistedState as Partial<PersistedEditorState>)
|
|
1541
1637
|
const layoutState = normalizePersistedEditorLayoutState(
|
|
1542
1638
|
persistedState as Partial<PersistedEditorState>,
|
|
1543
1639
|
)
|
|
@@ -1546,35 +1642,27 @@ const useEditor = create<EditorState>()(
|
|
|
1546
1642
|
...currentState,
|
|
1547
1643
|
...uiState,
|
|
1548
1644
|
...layoutState,
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
toolDefaults: {
|
|
1552
|
-
...currentState.toolDefaults,
|
|
1553
|
-
measurement: { kind: layoutState.lastMeasurementKind },
|
|
1554
|
-
},
|
|
1555
|
-
}
|
|
1556
|
-
: {}),
|
|
1645
|
+
activeSidebarPanel:
|
|
1646
|
+
layoutState.sidebarPanels[currentState.workspaceMode] ?? DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
|
1557
1647
|
}
|
|
1558
1648
|
},
|
|
1559
|
-
// `
|
|
1560
|
-
// — it lives in a separate
|
|
1561
|
-
//
|
|
1562
|
-
// the
|
|
1649
|
+
// `merge` rewrites `mode` wholesale, while the interaction scope a brush
|
|
1650
|
+
// mode holds is not persisted — it lives in a separate store. A paint or
|
|
1651
|
+
// sculpt brush armed before rehydrate would otherwise keep its scope after
|
|
1652
|
+
// the merge resets the mode, leaving selection suppressed for the rest of
|
|
1653
|
+
// the session with no mode on screen to explain it.
|
|
1563
1654
|
onRehydrateStorage: () => (state) => {
|
|
1564
1655
|
if (state) syncBrushModeScope(state.mode)
|
|
1565
1656
|
},
|
|
1657
|
+
// The armed tool (`toolMode` / `mode` / `tool`, and the catalog category
|
|
1658
|
+
// that only exists while a build tool is armed) is deliberately absent:
|
|
1659
|
+
// a project always opens in select mode, so persisting it could only ever
|
|
1660
|
+
// arm a tool nobody asked for on the next load.
|
|
1566
1661
|
partialize: (state) => ({
|
|
1567
1662
|
phase: state.phase,
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
structureLayer: state.structureLayer,
|
|
1572
|
-
catalogCategory: state.catalogCategory,
|
|
1573
|
-
isFloorplanOpen: state.isFloorplanOpen,
|
|
1574
|
-
viewMode: state.viewMode,
|
|
1575
|
-
activeSidebarPanel: state.activeSidebarPanel,
|
|
1576
|
-
floorplanPaneRatio: state.floorplanPaneRatio,
|
|
1577
|
-
splitOrientation: state.splitOrientation,
|
|
1663
|
+
viewLayouts: state.viewLayouts,
|
|
1664
|
+
sidebarPanels: state.sidebarPanels,
|
|
1665
|
+
pinnedViews: state.pinnedViews,
|
|
1578
1666
|
floorplanSelectionTool: state.floorplanSelectionTool,
|
|
1579
1667
|
gridSnapStep: state.gridSnapStep,
|
|
1580
1668
|
magneticSnap: state.magneticSnap,
|
|
@@ -1590,12 +1678,37 @@ const useEditor = create<EditorState>()(
|
|
|
1590
1678
|
),
|
|
1591
1679
|
)
|
|
1592
1680
|
|
|
1681
|
+
/**
|
|
1682
|
+
* The one writer of the stored layout. The view wins over the mode: hiding the
|
|
1683
|
+
* 3D pane leaves the terrain brush unreachable, and a held `sculpting` scope
|
|
1684
|
+
* would keep selection suppressed in a plan the user is trying to work in — a
|
|
1685
|
+
* mode you cannot use and cannot see how to leave.
|
|
1686
|
+
*/
|
|
1687
|
+
function commitViewLayout(layout: ViewLayout): void {
|
|
1688
|
+
useEditor.setState((state) => ({
|
|
1689
|
+
viewLayouts: { ...state.viewLayouts, [state.workspaceMode]: layout },
|
|
1690
|
+
}))
|
|
1691
|
+
yieldBrushToHiddenCanvas()
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function yieldBrushToHiddenCanvas(): void {
|
|
1695
|
+
const state = useEditor.getState()
|
|
1696
|
+
if (state.mode === 'terrain-sculpt' && !isViewVisible(state, VIEW_3D)) state.setMode('select')
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// The deprecated `viewMode` mirror follows every input of `visibleScene`
|
|
1700
|
+
// (layouts, workspace, capture, first person) without each writer knowing it.
|
|
1701
|
+
useEditor.subscribe((state) => {
|
|
1702
|
+
const viewMode = visibleScene(state) ?? '3d'
|
|
1703
|
+
if (state.viewMode !== viewMode) useEditor.setState({ viewMode })
|
|
1704
|
+
})
|
|
1705
|
+
|
|
1593
1706
|
export function armToolMode(next: ToolMode): void {
|
|
1594
1707
|
useEditor.getState().armToolMode(next)
|
|
1595
1708
|
}
|
|
1596
1709
|
|
|
1597
|
-
export function armMaterialPaint(material?: ActivePaintMaterial): void {
|
|
1598
|
-
useEditor.getState().armMaterialPaint(material)
|
|
1710
|
+
export function armMaterialPaint(material?: ActivePaintMaterial, subMode?: PaintMode): void {
|
|
1711
|
+
useEditor.getState().armMaterialPaint(material, subMode)
|
|
1599
1712
|
}
|
|
1600
1713
|
|
|
1601
1714
|
/**
|
|
@@ -1659,6 +1772,7 @@ export function getActiveSnapContext(): SnapContext | null {
|
|
|
1659
1772
|
return node ? nodeRegistry.get(node.type)?.snapProfile : undefined
|
|
1660
1773
|
},
|
|
1661
1774
|
draftDirectionalOf: (typeOrTool) => nodeRegistry.get(typeOrTool)?.snapDraftDirectional ?? true,
|
|
1775
|
+
paintRegion: paintRegionModeActive(editor.mode),
|
|
1662
1776
|
})
|
|
1663
1777
|
}
|
|
1664
1778
|
|
|
@@ -1691,4 +1805,9 @@ export function getActiveSnappingMode(): SnappingMode {
|
|
|
1691
1805
|
return useEditor.getState().snappingModeByContext[context]
|
|
1692
1806
|
}
|
|
1693
1807
|
|
|
1808
|
+
usePaintRegionMode.subscribe((state) => {
|
|
1809
|
+
const paintEraser = state.mode === 'erase'
|
|
1810
|
+
if (useEditor.getState().paintEraser !== paintEraser) useEditor.setState({ paintEraser })
|
|
1811
|
+
})
|
|
1812
|
+
|
|
1694
1813
|
export default useEditor
|