@pascal-app/editor 1.0.3 → 1.0.5
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/README.md +106 -0
- package/package.json +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +275 -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 +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -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-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- 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 +409 -197
- 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/riser-diagram-panel.tsx +14 -7
- 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 +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -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 +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- 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 +663 -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.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -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 +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- 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 +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- 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 +319 -233
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- 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/roof/roof-edit-system.tsx +1 -0
- 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/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- 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 +148 -95
- 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 +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
- 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/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +14 -19
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- 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/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- 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 +28 -22
- 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 +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- 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 +161 -5
- 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 +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- 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 +790 -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/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- 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 +181 -223
- 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 +13 -37
- 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 +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- 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-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -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 +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- 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 +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -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/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- 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 +284 -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 +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -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/pdfkit-standard-fonts.d.ts +5 -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 +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -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 +230 -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 +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -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 +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -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 +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- 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 +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- 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 +187 -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 +392 -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/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -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 +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -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 +122 -0
- package/src/lib/paint-pass-through.ts +78 -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 +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- 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 +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- 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 +265 -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 +1017 -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 +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- 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 +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -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/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -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 +580 -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-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -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-changes.ts +35 -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-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- 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-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -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 +201 -0
- package/src/store/view-layout.ts +199 -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/render-current-markup.tsx +16 -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/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- 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,
|
|
@@ -590,25 +648,18 @@ type SelectDefaultBuildingAndLevelOptions = {
|
|
|
590
648
|
forceGroundLevel?: boolean
|
|
591
649
|
}
|
|
592
650
|
|
|
593
|
-
function defaultBuildTool(phase: Phase
|
|
594
|
-
|
|
595
|
-
if (phase === 'furnish') return 'item'
|
|
596
|
-
return structureLayer === 'zones' ? 'zone' : 'wall'
|
|
651
|
+
function defaultBuildTool(phase: Phase): StructureTool {
|
|
652
|
+
return phase === 'site' ? 'property-line' : 'wall'
|
|
597
653
|
}
|
|
598
654
|
|
|
599
|
-
function materializeToolMode(
|
|
600
|
-
mode: Mode,
|
|
601
|
-
tool: unknown,
|
|
602
|
-
phase: Phase,
|
|
603
|
-
structureLayer: StructureLayer,
|
|
604
|
-
): ToolMode {
|
|
655
|
+
function materializeToolMode(mode: Mode, tool: unknown, phase: Phase): ToolMode {
|
|
605
656
|
if (mode === 'build') {
|
|
606
657
|
return {
|
|
607
658
|
mode,
|
|
608
659
|
tool:
|
|
609
660
|
typeof tool === 'string' && tool.length > 0
|
|
610
661
|
? (tool as StructureTool)
|
|
611
|
-
: defaultBuildTool(phase
|
|
662
|
+
: defaultBuildTool(phase),
|
|
612
663
|
}
|
|
613
664
|
}
|
|
614
665
|
|
|
@@ -642,7 +693,7 @@ function withMaterializedToolMode(
|
|
|
642
693
|
): PersistedEditorUiState {
|
|
643
694
|
return {
|
|
644
695
|
...state,
|
|
645
|
-
toolMode: materializeToolMode(state.mode, state.tool, state.phase
|
|
696
|
+
toolMode: materializeToolMode(state.mode, state.tool, state.phase),
|
|
646
697
|
}
|
|
647
698
|
}
|
|
648
699
|
|
|
@@ -656,91 +707,82 @@ function normalizeModeForPhase(phase: Phase, mode: Mode | undefined): Mode {
|
|
|
656
707
|
return mode === 'build' || mode === 'delete' || mode === 'material-paint' ? mode : 'select'
|
|
657
708
|
}
|
|
658
709
|
|
|
659
|
-
function
|
|
660
|
-
|
|
661
|
-
|
|
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
|
+
)
|
|
662
718
|
}
|
|
719
|
+
if (state?.viewMode === undefined && state?.isFloorplanOpen === undefined) return {}
|
|
720
|
+
return { edit: viewLayoutFromLegacy(state) }
|
|
721
|
+
}
|
|
663
722
|
|
|
664
|
-
|
|
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
|
+
)
|
|
665
740
|
}
|
|
666
741
|
|
|
667
742
|
export function normalizePersistedEditorUiState(
|
|
668
|
-
state: Partial<PersistedEditorUiState> | null | undefined,
|
|
743
|
+
state: (Partial<PersistedEditorUiState> & LegacyViewModeState) | null | undefined,
|
|
669
744
|
): PersistedEditorUiState {
|
|
670
|
-
|
|
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'
|
|
671
751
|
const persistedToolMode = readPersistedToolMode(state)
|
|
672
752
|
let mode = normalizeModeForPhase(phase, persistedToolMode.mode)
|
|
673
753
|
|
|
674
|
-
|
|
675
|
-
let viewMode: ViewMode = '3d'
|
|
676
|
-
if (state?.viewMode === '2d' || state?.viewMode === '3d' || state?.viewMode === 'split') {
|
|
677
|
-
viewMode = state.viewMode
|
|
678
|
-
} else if (state?.isFloorplanOpen) {
|
|
679
|
-
viewMode = 'split'
|
|
680
|
-
}
|
|
681
|
-
const isFloorplanOpen = viewMode !== '3d'
|
|
754
|
+
const viewLayouts = normalizeViewLayouts(state)
|
|
682
755
|
|
|
683
|
-
// Both are persisted independently, and rehydrate goes through
|
|
684
|
-
//
|
|
685
|
-
// wins here for the same reason it does in `
|
|
686
|
-
// hidden canvas is a mode the user cannot use, and reviving one on load
|
|
687
|
-
// worse than reviving it mid-session — nothing on screen explains it.
|
|
688
|
-
|
|
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'
|
|
689
763
|
|
|
690
764
|
if (phase === 'site') {
|
|
691
765
|
return withMaterializedToolMode({
|
|
692
766
|
phase,
|
|
693
767
|
mode,
|
|
694
768
|
tool: mode === 'build' ? 'property-line' : null,
|
|
695
|
-
structureLayer: 'elements',
|
|
696
769
|
catalogCategory: null,
|
|
697
|
-
|
|
698
|
-
isFloorplanOpen,
|
|
699
|
-
})
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (phase === 'furnish') {
|
|
703
|
-
return withMaterializedToolMode({
|
|
704
|
-
phase,
|
|
705
|
-
mode,
|
|
706
|
-
tool: mode === 'build' ? 'item' : null,
|
|
707
|
-
structureLayer: 'elements',
|
|
708
|
-
catalogCategory: mode === 'build' ? (state?.catalogCategory ?? 'furniture') : null,
|
|
709
|
-
viewMode,
|
|
710
|
-
isFloorplanOpen,
|
|
770
|
+
viewLayouts,
|
|
711
771
|
})
|
|
712
772
|
}
|
|
713
773
|
|
|
714
|
-
const structureLayer = state?.structureLayer === 'zones' ? 'zones' : 'elements'
|
|
715
|
-
|
|
716
774
|
if (mode !== 'build') {
|
|
717
775
|
return withMaterializedToolMode({
|
|
718
776
|
phase,
|
|
719
777
|
mode,
|
|
720
778
|
tool: null,
|
|
721
|
-
structureLayer,
|
|
722
|
-
catalogCategory: null,
|
|
723
|
-
viewMode,
|
|
724
|
-
isFloorplanOpen,
|
|
725
|
-
})
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (structureLayer === 'zones') {
|
|
729
|
-
return withMaterializedToolMode({
|
|
730
|
-
phase,
|
|
731
|
-
mode,
|
|
732
|
-
tool: 'zone',
|
|
733
|
-
structureLayer,
|
|
734
779
|
catalogCategory: null,
|
|
735
|
-
|
|
736
|
-
isFloorplanOpen,
|
|
780
|
+
viewLayouts,
|
|
737
781
|
})
|
|
738
782
|
}
|
|
739
783
|
|
|
740
784
|
const tool =
|
|
741
|
-
persistedToolMode.tool &&
|
|
742
|
-
persistedToolMode.tool !== 'property-line' &&
|
|
743
|
-
persistedToolMode.tool !== 'zone'
|
|
785
|
+
persistedToolMode.tool && persistedToolMode.tool !== 'property-line'
|
|
744
786
|
? (persistedToolMode.tool as Tool)
|
|
745
787
|
: 'wall'
|
|
746
788
|
|
|
@@ -748,13 +790,33 @@ export function normalizePersistedEditorUiState(
|
|
|
748
790
|
phase,
|
|
749
791
|
mode,
|
|
750
792
|
tool,
|
|
751
|
-
structureLayer,
|
|
752
793
|
catalogCategory: tool === 'item' ? (state?.catalogCategory ?? null) : null,
|
|
753
|
-
|
|
754
|
-
isFloorplanOpen,
|
|
794
|
+
viewLayouts,
|
|
755
795
|
})
|
|
756
796
|
}
|
|
757
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
|
+
|
|
758
820
|
// Validate a persisted per-context mode against that context's allowed set
|
|
759
821
|
// (so e.g. a stale `angles` for items resets), falling back to its default.
|
|
760
822
|
function migrateSnappingMode(value: unknown, context: SnapContext): SnappingMode {
|
|
@@ -811,12 +873,8 @@ export function normalizePersistedEditorLayoutState(
|
|
|
811
873
|
| undefined,
|
|
812
874
|
): PersistedEditorLayoutState {
|
|
813
875
|
return {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
? state.activeSidebarPanel
|
|
817
|
-
: DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
|
818
|
-
floorplanPaneRatio: normalizeFloorplanPaneRatio(state?.floorplanPaneRatio),
|
|
819
|
-
splitOrientation: state?.splitOrientation === 'vertical' ? 'vertical' : 'horizontal',
|
|
876
|
+
sidebarPanels: normalizeSidebarPanels(state?.sidebarPanels),
|
|
877
|
+
pinnedViews: normalizePinnedViews(state?.pinnedViews),
|
|
820
878
|
floorplanSelectionTool: state?.floorplanSelectionTool === 'marquee' ? 'marquee' : 'click',
|
|
821
879
|
gridSnapStep: GRID_SNAP_STEPS.includes(state?.gridSnapStep as GridSnapStep)
|
|
822
880
|
? (state?.gridSnapStep as GridSnapStep)
|
|
@@ -853,10 +911,8 @@ export function hasCustomPersistedEditorUiState(
|
|
|
853
911
|
normalizedState.phase !== DEFAULT_PERSISTED_EDITOR_UI_STATE.phase ||
|
|
854
912
|
normalizedState.mode !== DEFAULT_PERSISTED_EDITOR_UI_STATE.mode ||
|
|
855
913
|
normalizedState.tool !== DEFAULT_PERSISTED_EDITOR_UI_STATE.tool ||
|
|
856
|
-
normalizedState.structureLayer !== DEFAULT_PERSISTED_EDITOR_UI_STATE.structureLayer ||
|
|
857
914
|
normalizedState.catalogCategory !== DEFAULT_PERSISTED_EDITOR_UI_STATE.catalogCategory ||
|
|
858
|
-
normalizedState.
|
|
859
|
-
normalizedState.viewMode !== DEFAULT_PERSISTED_EDITOR_UI_STATE.viewMode
|
|
915
|
+
Object.keys(normalizedState.viewLayouts).length > 0
|
|
860
916
|
)
|
|
861
917
|
}
|
|
862
918
|
|
|
@@ -950,10 +1006,29 @@ export function selectSiteFloorplanContext() {
|
|
|
950
1006
|
})
|
|
951
1007
|
}
|
|
952
1008
|
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
//
|
|
956
|
-
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
|
+
}
|
|
957
1032
|
|
|
958
1033
|
/**
|
|
959
1034
|
* Hold the interaction scope that belongs to a sustained brush mode.
|
|
@@ -981,6 +1056,19 @@ function syncBrushModeScope(mode: Mode): void {
|
|
|
981
1056
|
scope.endIf((s) => s.kind === 'painting' || s.kind === 'sculpting')
|
|
982
1057
|
if (useEditor.getState().terrainSampling) useEditor.getState().setTerrainSampling(false)
|
|
983
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'
|
|
984
1072
|
}
|
|
985
1073
|
|
|
986
1074
|
/**
|
|
@@ -1001,110 +1089,86 @@ export function isBrushMode(mode: Mode): boolean {
|
|
|
1001
1089
|
const useEditor = create<EditorState>()(
|
|
1002
1090
|
persist(
|
|
1003
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
|
+
},
|
|
1004
1100
|
phase: DEFAULT_PERSISTED_EDITOR_UI_STATE.phase,
|
|
1005
1101
|
setPhase: (phase) => {
|
|
1006
1102
|
const currentPhase = get().phase
|
|
1007
1103
|
if (currentPhase === phase) return
|
|
1008
1104
|
const wasBuilding = get().toolMode.mode === 'build'
|
|
1009
|
-
const structureLayer = phase === 'furnish' ? 'elements' : get().structureLayer
|
|
1010
1105
|
set({
|
|
1011
1106
|
phase,
|
|
1012
|
-
|
|
1013
|
-
|
|
1107
|
+
// Site has no rooms.
|
|
1108
|
+
...(phase === 'site' ? { room: null } : {}),
|
|
1109
|
+
hoveredRoom: null,
|
|
1110
|
+
catalogCategory: null,
|
|
1014
1111
|
})
|
|
1015
1112
|
get().armToolMode(
|
|
1016
|
-
wasBuilding
|
|
1017
|
-
? { mode: 'build', tool: defaultBuildTool(phase, structureLayer) }
|
|
1018
|
-
: { mode: 'select' },
|
|
1113
|
+
wasBuilding ? { mode: 'build', tool: defaultBuildTool(phase) } : { mode: 'select' },
|
|
1019
1114
|
)
|
|
1020
1115
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
selectSiteFloorplanContext()
|
|
1024
|
-
break
|
|
1025
|
-
|
|
1026
|
-
case 'structure':
|
|
1027
|
-
selectDefaultBuildingAndLevel()
|
|
1028
|
-
break
|
|
1029
|
-
|
|
1030
|
-
case 'furnish':
|
|
1031
|
-
selectDefaultBuildingAndLevel()
|
|
1032
|
-
break
|
|
1033
|
-
}
|
|
1116
|
+
if (phase === 'site') selectSiteFloorplanContext()
|
|
1117
|
+
else selectDefaultBuildingAndLevel()
|
|
1034
1118
|
},
|
|
1035
1119
|
toolMode: DEFAULT_PERSISTED_EDITOR_UI_STATE.toolMode,
|
|
1036
1120
|
armToolMode: (requested) => {
|
|
1037
1121
|
const current = get()
|
|
1038
1122
|
let phase = current.phase
|
|
1039
|
-
let structureLayer = current.structureLayer
|
|
1040
|
-
let viewMode = current.viewMode
|
|
1041
|
-
let isFloorplanOpen = current.isFloorplanOpen
|
|
1042
1123
|
const next = materializeToolMode(
|
|
1043
1124
|
requested.mode,
|
|
1044
1125
|
requested.mode === 'build' ? requested.tool : null,
|
|
1045
1126
|
phase,
|
|
1046
|
-
structureLayer,
|
|
1047
1127
|
)
|
|
1048
1128
|
|
|
1049
1129
|
if (next.mode === 'terrain-sculpt') {
|
|
1050
1130
|
phase = 'site'
|
|
1051
|
-
structureLayer = 'elements'
|
|
1052
|
-
if (viewMode === '2d') {
|
|
1053
|
-
viewMode = 'split'
|
|
1054
|
-
isFloorplanOpen = true
|
|
1055
|
-
}
|
|
1056
1131
|
} else if (next.mode === 'build' && next.tool === 'property-line') {
|
|
1057
1132
|
phase = 'site'
|
|
1058
|
-
structureLayer = 'elements'
|
|
1059
|
-
} else if (next.mode === 'build' && next.tool === 'zone') {
|
|
1060
|
-
phase = 'structure'
|
|
1061
|
-
structureLayer = 'zones'
|
|
1062
|
-
} else if (next.mode === 'build' && phase === 'site') {
|
|
1063
|
-
phase = 'structure'
|
|
1064
|
-
structureLayer = 'elements'
|
|
1065
|
-
} else if (next.mode === 'material-paint' && phase === 'site') {
|
|
1066
|
-
phase = 'structure'
|
|
1067
|
-
structureLayer = 'elements'
|
|
1068
|
-
} else if (phase !== 'structure') {
|
|
1069
|
-
structureLayer = 'elements'
|
|
1070
1133
|
}
|
|
1071
1134
|
|
|
1072
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()
|
|
1073
1140
|
set({
|
|
1074
1141
|
toolMode: next,
|
|
1075
1142
|
mode: next.mode,
|
|
1076
|
-
tool:
|
|
1143
|
+
tool: nextTool,
|
|
1144
|
+
...(endsSelection ? { room: null, hoveredRoom: null, selectedReferenceId: null } : {}),
|
|
1077
1145
|
...(phaseChanged ? { phase } : {}),
|
|
1078
|
-
...(structureLayer !== current.structureLayer ? { structureLayer } : {}),
|
|
1079
|
-
...(viewMode !== current.viewMode ? { viewMode } : {}),
|
|
1080
|
-
...(isFloorplanOpen !== current.isFloorplanOpen ? { isFloorplanOpen } : {}),
|
|
1081
|
-
...(next.mode === 'build' && phase === 'furnish' && !current.catalogCategory
|
|
1082
|
-
? { catalogCategory: 'furniture' }
|
|
1083
|
-
: {}),
|
|
1084
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
|
+
}
|
|
1085
1151
|
|
|
1086
1152
|
if (phaseChanged) {
|
|
1087
1153
|
if (phase === 'site') selectSiteFloorplanContext()
|
|
1088
1154
|
else selectDefaultBuildingAndLevel()
|
|
1089
1155
|
}
|
|
1090
|
-
if (
|
|
1091
|
-
if (next.mode === 'terrain-sculpt') {
|
|
1092
|
-
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
1093
|
-
}
|
|
1156
|
+
if (endsSelection) useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
1094
1157
|
syncBrushModeScope(next.mode)
|
|
1095
1158
|
},
|
|
1096
|
-
armMaterialPaint: (material) => {
|
|
1159
|
+
armMaterialPaint: (material, subMode) => {
|
|
1097
1160
|
get().armToolMode({ mode: 'material-paint' })
|
|
1161
|
+
const paintMode = usePaintRegionMode.getState()
|
|
1162
|
+
paintMode.setMode(subMode ?? paintMode.drawMode)
|
|
1098
1163
|
if (material) get().setActivePaintMaterial(material)
|
|
1099
1164
|
},
|
|
1100
1165
|
mode: DEFAULT_PERSISTED_EDITOR_UI_STATE.mode,
|
|
1101
1166
|
setMode: (mode) => {
|
|
1102
1167
|
if (mode === 'build') {
|
|
1103
|
-
const { phase,
|
|
1168
|
+
const { phase, toolMode } = get()
|
|
1104
1169
|
get().armToolMode({
|
|
1105
1170
|
mode,
|
|
1106
|
-
tool:
|
|
1107
|
-
toolMode.mode === 'build' ? toolMode.tool : defaultBuildTool(phase, structureLayer),
|
|
1171
|
+
tool: toolMode.mode === 'build' ? toolMode.tool : defaultBuildTool(phase),
|
|
1108
1172
|
})
|
|
1109
1173
|
return
|
|
1110
1174
|
}
|
|
@@ -1120,7 +1184,7 @@ const useEditor = create<EditorState>()(
|
|
|
1120
1184
|
get().armToolMode({ mode: 'select' })
|
|
1121
1185
|
return
|
|
1122
1186
|
}
|
|
1123
|
-
get().armToolMode(materializeToolMode(get().mode, null, get().phase
|
|
1187
|
+
get().armToolMode(materializeToolMode(get().mode, null, get().phase))
|
|
1124
1188
|
},
|
|
1125
1189
|
toolDefaults: {},
|
|
1126
1190
|
setToolDefaults: (tool, defaults) =>
|
|
@@ -1135,22 +1199,6 @@ const useEditor = create<EditorState>()(
|
|
|
1135
1199
|
}),
|
|
1136
1200
|
lastMeasurementKind: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.lastMeasurementKind,
|
|
1137
1201
|
setLastMeasurementKind: (kind) => set({ lastMeasurementKind: kind }),
|
|
1138
|
-
structureLayer: DEFAULT_PERSISTED_EDITOR_UI_STATE.structureLayer,
|
|
1139
|
-
setStructureLayer: (layer) => {
|
|
1140
|
-
const wasBuilding = get().toolMode.mode === 'build'
|
|
1141
|
-
set({ structureLayer: layer })
|
|
1142
|
-
get().armToolMode(
|
|
1143
|
-
wasBuilding
|
|
1144
|
-
? { mode: 'build', tool: layer === 'zones' ? 'zone' : 'wall' }
|
|
1145
|
-
: { mode: 'select' },
|
|
1146
|
-
)
|
|
1147
|
-
|
|
1148
|
-
const viewer = useViewer.getState()
|
|
1149
|
-
viewer.setSelection({
|
|
1150
|
-
selectedIds: [],
|
|
1151
|
-
zoneId: null,
|
|
1152
|
-
})
|
|
1153
|
-
},
|
|
1154
1202
|
catalogCategory: DEFAULT_PERSISTED_EDITOR_UI_STATE.catalogCategory,
|
|
1155
1203
|
setCatalogCategory: (category) => set({ catalogCategory: category }),
|
|
1156
1204
|
selectedItem: null,
|
|
@@ -1209,11 +1257,16 @@ const useEditor = create<EditorState>()(
|
|
|
1209
1257
|
},
|
|
1210
1258
|
selectedMaterialTarget: null,
|
|
1211
1259
|
setSelectedMaterialTarget: (target) => set({ selectedMaterialTarget: target }),
|
|
1260
|
+
paintEraser: usePaintRegionMode.getState().mode === 'erase',
|
|
1212
1261
|
activePaintMaterial: null,
|
|
1213
|
-
// Picking a material implies paint, not erase —
|
|
1262
|
+
// Picking a material implies paint, not erase — leave the eraser so the
|
|
1214
1263
|
// next click applies the chosen material.
|
|
1215
|
-
setActivePaintMaterial: (material) =>
|
|
1216
|
-
set({ activePaintMaterial: material
|
|
1264
|
+
setActivePaintMaterial: (material) => {
|
|
1265
|
+
set({ activePaintMaterial: material })
|
|
1266
|
+
if (material && isPaintErasing()) {
|
|
1267
|
+
usePaintRegionMode.getState().setMode(usePaintRegionMode.getState().drawMode)
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1217
1270
|
activePaintTarget: 'wall',
|
|
1218
1271
|
setActivePaintTarget: (target) =>
|
|
1219
1272
|
set((state) =>
|
|
@@ -1232,8 +1285,6 @@ const useEditor = create<EditorState>()(
|
|
|
1232
1285
|
set({ paintScope: next })
|
|
1233
1286
|
return next
|
|
1234
1287
|
},
|
|
1235
|
-
paintEraser: false,
|
|
1236
|
-
setPaintEraser: (eraser) => set({ paintEraser: eraser }),
|
|
1237
1288
|
primeMaterialPaintFromSelection: () => {
|
|
1238
1289
|
const selectedId =
|
|
1239
1290
|
useViewer.getState().selection.selectedIds.length === 1
|
|
@@ -1245,6 +1296,7 @@ const useEditor = create<EditorState>()(
|
|
|
1245
1296
|
selectedId,
|
|
1246
1297
|
}) ?? get().activePaintTarget
|
|
1247
1298
|
const activePaintMaterial = resolveActivePaintMaterialFromSelection({
|
|
1299
|
+
materials: useScene.getState().materials,
|
|
1248
1300
|
nodes: useScene.getState().nodes,
|
|
1249
1301
|
selectedId,
|
|
1250
1302
|
selectedMaterialTarget: get().selectedMaterialTarget,
|
|
@@ -1281,7 +1333,8 @@ const useEditor = create<EditorState>()(
|
|
|
1281
1333
|
canFindNode: false,
|
|
1282
1334
|
setCanFindNode: (canFind) => set({ canFindNode: canFind }),
|
|
1283
1335
|
selectedReferenceId: null,
|
|
1284
|
-
setSelectedReferenceId: (id) =>
|
|
1336
|
+
setSelectedReferenceId: (id) =>
|
|
1337
|
+
set({ selectedReferenceId: id, ...(id ? { room: null, hoveredRoom: null } : {}) }),
|
|
1285
1338
|
referenceScaleActiveGuideId: null,
|
|
1286
1339
|
setReferenceScaleActiveGuideId: (id) => set({ referenceScaleActiveGuideId: id }),
|
|
1287
1340
|
guideUi: {},
|
|
@@ -1326,13 +1379,63 @@ const useEditor = create<EditorState>()(
|
|
|
1326
1379
|
),
|
|
1327
1380
|
isPreviewMode: false,
|
|
1328
1381
|
setPreviewMode: (preview) => {
|
|
1382
|
+
if (preview === get().isPreviewMode) return
|
|
1329
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
|
|
1330
1398
|
set({ isPreviewMode: true, catalogCategory: null })
|
|
1331
1399
|
get().armToolMode({ mode: 'select' })
|
|
1332
|
-
//
|
|
1333
|
-
|
|
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)
|
|
1334
1405
|
} else {
|
|
1406
|
+
if (get().isFirstPersonMode) get().setFirstPersonMode(false)
|
|
1407
|
+
useViewer.getState().setWalkthroughMode(false)
|
|
1335
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
|
|
1336
1439
|
}
|
|
1337
1440
|
},
|
|
1338
1441
|
captureMode: { mode: 'idle' } as CaptureMode,
|
|
@@ -1341,59 +1444,60 @@ const useEditor = create<EditorState>()(
|
|
|
1341
1444
|
const resolved: CaptureMode =
|
|
1342
1445
|
typeof next === 'boolean' ? { mode: next ? 'standard' : 'idle' } : next
|
|
1343
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
|
+
}
|
|
1344
1452
|
// Walk / drone framing is a capture-only camera, so leaving capture always
|
|
1345
|
-
// lands back on orbit.
|
|
1346
|
-
// the
|
|
1453
|
+
// lands back on orbit. Capture frames in 3D alone (`activeViewLayout`)
|
|
1454
|
+
// without touching the stored layout, so leaving it needs no restore.
|
|
1347
1455
|
if (!entering && get().isFirstPersonMode) {
|
|
1348
1456
|
get().setFirstPersonMode(false)
|
|
1349
1457
|
}
|
|
1350
|
-
set(
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
}
|
|
1364
|
-
return { captureMode: resolved, isCaptureMode: true }
|
|
1365
|
-
}
|
|
1366
|
-
const restore = viewModeBeforeCapture
|
|
1367
|
-
viewModeBeforeCapture = null
|
|
1368
|
-
if (restore && restore !== '3d') {
|
|
1369
|
-
return {
|
|
1370
|
-
captureMode: resolved,
|
|
1371
|
-
isCaptureMode: false,
|
|
1372
|
-
viewMode: restore,
|
|
1373
|
-
isFloorplanOpen: true,
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
return { captureMode: resolved, isCaptureMode: false }
|
|
1377
|
-
})
|
|
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
|
+
}
|
|
1378
1471
|
},
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
// work in — a mode you cannot use and cannot see how to leave.
|
|
1386
|
-
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 })
|
|
1387
1478
|
},
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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 } })),
|
|
1397
1501
|
isFloorplanHovered: false,
|
|
1398
1502
|
setFloorplanHovered: (hovered) => set({ isFloorplanHovered: hovered }),
|
|
1399
1503
|
isRiserOpen: false,
|
|
@@ -1469,26 +1573,14 @@ const useEditor = create<EditorState>()(
|
|
|
1469
1573
|
show2dVoronoi: false,
|
|
1470
1574
|
setShow2dVoronoi: (enabled) => set({ show2dVoronoi: enabled }),
|
|
1471
1575
|
isFirstPersonMode: false,
|
|
1472
|
-
|
|
1576
|
+
// First person shows 3D alone (`activeViewLayout`); the stored layout
|
|
1577
|
+
// comes back on exit untouched.
|
|
1473
1578
|
setFirstPersonMode: (enabled) => {
|
|
1474
1579
|
if (enabled) {
|
|
1475
|
-
|
|
1476
|
-
set({
|
|
1477
|
-
isFirstPersonMode: true,
|
|
1478
|
-
_viewModeBeforeFirstPerson: currentViewMode,
|
|
1479
|
-
viewMode: '3d',
|
|
1480
|
-
isFloorplanOpen: false,
|
|
1481
|
-
catalogCategory: null,
|
|
1482
|
-
})
|
|
1580
|
+
set({ isFirstPersonMode: true, catalogCategory: null })
|
|
1483
1581
|
get().armToolMode({ mode: 'select' })
|
|
1484
1582
|
} else {
|
|
1485
|
-
|
|
1486
|
-
set({
|
|
1487
|
-
isFirstPersonMode: false,
|
|
1488
|
-
firstPersonMovementMode: 'walk',
|
|
1489
|
-
_viewModeBeforeFirstPerson: null,
|
|
1490
|
-
...(prevMode ? { viewMode: prevMode, isFloorplanOpen: prevMode !== '3d' } : {}),
|
|
1491
|
-
})
|
|
1583
|
+
set({ isFirstPersonMode: false, firstPersonMovementMode: 'walk' })
|
|
1492
1584
|
}
|
|
1493
1585
|
},
|
|
1494
1586
|
firstPersonMovementMode: 'walk' as FirstPersonMovementMode,
|
|
@@ -1506,35 +1598,32 @@ const useEditor = create<EditorState>()(
|
|
|
1506
1598
|
captureShutterHold: false,
|
|
1507
1599
|
setCaptureShutterHold: (hold) => set({ captureShutterHold: hold }),
|
|
1508
1600
|
workspaceMode: 'edit' as WorkspaceMode,
|
|
1509
|
-
_viewModeBeforeStudio: null as ViewMode | null,
|
|
1510
1601
|
setWorkspaceMode: (mode) => {
|
|
1511
1602
|
if (get().workspaceMode === mode) return
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
})
|
|
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 })
|
|
1521
1612
|
get().armToolMode({ mode: 'select' })
|
|
1522
1613
|
// Clear selection so no edit affordances bleed into the clean canvas.
|
|
1523
1614
|
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
1524
1615
|
} else {
|
|
1525
|
-
|
|
1526
|
-
set({
|
|
1527
|
-
workspaceMode: 'edit',
|
|
1528
|
-
_viewModeBeforeStudio: null,
|
|
1529
|
-
...(prevMode ? { viewMode: prevMode, isFloorplanOpen: prevMode !== '3d' } : {}),
|
|
1530
|
-
})
|
|
1616
|
+
set({ workspaceMode: 'edit', activeSidebarPanel })
|
|
1531
1617
|
}
|
|
1618
|
+
yieldBrushToHiddenCanvas()
|
|
1532
1619
|
},
|
|
1533
1620
|
activeSidebarPanel: DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
|
1534
|
-
setActiveSidebarPanel: (id) =>
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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,
|
|
1538
1627
|
mobilePanelSheetHeight: 0,
|
|
1539
1628
|
setMobilePanelSheetHeight: (px) => set({ mobilePanelSheetHeight: Math.max(0, px) }),
|
|
1540
1629
|
modelExport: null,
|
|
@@ -1542,10 +1631,9 @@ const useEditor = create<EditorState>()(
|
|
|
1542
1631
|
}),
|
|
1543
1632
|
{
|
|
1544
1633
|
name: 'pascal-editor-ui-preferences',
|
|
1634
|
+
storage: hydrationGatedStorage((): boolean => useEditor.persist.hasHydrated()),
|
|
1545
1635
|
merge: (persistedState, currentState) => {
|
|
1546
|
-
const uiState =
|
|
1547
|
-
persistedState as Partial<PersistedEditorState>,
|
|
1548
|
-
)
|
|
1636
|
+
const uiState = editorUiStateOnOpen(persistedState as Partial<PersistedEditorState>)
|
|
1549
1637
|
const layoutState = normalizePersistedEditorLayoutState(
|
|
1550
1638
|
persistedState as Partial<PersistedEditorState>,
|
|
1551
1639
|
)
|
|
@@ -1554,35 +1642,27 @@ const useEditor = create<EditorState>()(
|
|
|
1554
1642
|
...currentState,
|
|
1555
1643
|
...uiState,
|
|
1556
1644
|
...layoutState,
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
toolDefaults: {
|
|
1560
|
-
...currentState.toolDefaults,
|
|
1561
|
-
measurement: { kind: layoutState.lastMeasurementKind },
|
|
1562
|
-
},
|
|
1563
|
-
}
|
|
1564
|
-
: {}),
|
|
1645
|
+
activeSidebarPanel:
|
|
1646
|
+
layoutState.sidebarPanels[currentState.workspaceMode] ?? DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
|
1565
1647
|
}
|
|
1566
1648
|
},
|
|
1567
|
-
// `
|
|
1568
|
-
// — it lives in a separate
|
|
1569
|
-
//
|
|
1570
|
-
// 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.
|
|
1571
1654
|
onRehydrateStorage: () => (state) => {
|
|
1572
1655
|
if (state) syncBrushModeScope(state.mode)
|
|
1573
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.
|
|
1574
1661
|
partialize: (state) => ({
|
|
1575
1662
|
phase: state.phase,
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
structureLayer: state.structureLayer,
|
|
1580
|
-
catalogCategory: state.catalogCategory,
|
|
1581
|
-
isFloorplanOpen: state.isFloorplanOpen,
|
|
1582
|
-
viewMode: state.viewMode,
|
|
1583
|
-
activeSidebarPanel: state.activeSidebarPanel,
|
|
1584
|
-
floorplanPaneRatio: state.floorplanPaneRatio,
|
|
1585
|
-
splitOrientation: state.splitOrientation,
|
|
1663
|
+
viewLayouts: state.viewLayouts,
|
|
1664
|
+
sidebarPanels: state.sidebarPanels,
|
|
1665
|
+
pinnedViews: state.pinnedViews,
|
|
1586
1666
|
floorplanSelectionTool: state.floorplanSelectionTool,
|
|
1587
1667
|
gridSnapStep: state.gridSnapStep,
|
|
1588
1668
|
magneticSnap: state.magneticSnap,
|
|
@@ -1598,12 +1678,37 @@ const useEditor = create<EditorState>()(
|
|
|
1598
1678
|
),
|
|
1599
1679
|
)
|
|
1600
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
|
+
|
|
1601
1706
|
export function armToolMode(next: ToolMode): void {
|
|
1602
1707
|
useEditor.getState().armToolMode(next)
|
|
1603
1708
|
}
|
|
1604
1709
|
|
|
1605
|
-
export function armMaterialPaint(material?: ActivePaintMaterial): void {
|
|
1606
|
-
useEditor.getState().armMaterialPaint(material)
|
|
1710
|
+
export function armMaterialPaint(material?: ActivePaintMaterial, subMode?: PaintMode): void {
|
|
1711
|
+
useEditor.getState().armMaterialPaint(material, subMode)
|
|
1607
1712
|
}
|
|
1608
1713
|
|
|
1609
1714
|
/**
|
|
@@ -1667,6 +1772,7 @@ export function getActiveSnapContext(): SnapContext | null {
|
|
|
1667
1772
|
return node ? nodeRegistry.get(node.type)?.snapProfile : undefined
|
|
1668
1773
|
},
|
|
1669
1774
|
draftDirectionalOf: (typeOrTool) => nodeRegistry.get(typeOrTool)?.snapDraftDirectional ?? true,
|
|
1775
|
+
paintRegion: paintRegionModeActive(editor.mode),
|
|
1670
1776
|
})
|
|
1671
1777
|
}
|
|
1672
1778
|
|
|
@@ -1699,4 +1805,9 @@ export function getActiveSnappingMode(): SnappingMode {
|
|
|
1699
1805
|
return useEditor.getState().snappingModeByContext[context]
|
|
1700
1806
|
}
|
|
1701
1807
|
|
|
1808
|
+
usePaintRegionMode.subscribe((state) => {
|
|
1809
|
+
const paintEraser = state.mode === 'erase'
|
|
1810
|
+
if (useEditor.getState().paintEraser !== paintEraser) useEditor.setState({ paintEraser })
|
|
1811
|
+
})
|
|
1812
|
+
|
|
1702
1813
|
export default useEditor
|