@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
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
SceneEnvironment,
|
|
20
20
|
useViewer,
|
|
21
21
|
Viewer,
|
|
22
|
+
type ViewerImmersiveSession,
|
|
22
23
|
ViewerPresentations,
|
|
23
24
|
} from '@pascal-app/viewer'
|
|
24
25
|
import {
|
|
26
|
+
type CSSProperties,
|
|
25
27
|
memo,
|
|
26
28
|
Profiler,
|
|
27
29
|
type ProfilerOnRenderCallback,
|
|
@@ -37,11 +39,17 @@ import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
|
37
39
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
38
40
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
39
41
|
import { useSaveShortcut } from '../../hooks/use-save-shortcut'
|
|
42
|
+
import { useCeilingEditSessionOwner } from '../../lib/ceiling-edit-session'
|
|
43
|
+
import { showsWholeBuilding, useEditorLevelDisplay } from '../../lib/editor-level-display'
|
|
44
|
+
import { isSceneView } from '../../lib/editor-views'
|
|
45
|
+
import { useGestureLifecycleOwner } from '../../lib/gesture-lifecycle'
|
|
40
46
|
import {
|
|
41
47
|
createLocalProjectPresentationPersistence,
|
|
42
48
|
type LocalProjectPresentationPersistence,
|
|
43
49
|
} from '../../lib/local-project-presentation-persistence'
|
|
44
50
|
import { type ActivePaintMaterial, hasActivePaintMaterial } from '../../lib/material-paint'
|
|
51
|
+
import { usePaintRegionHovering } from '../../lib/paint-region-hover'
|
|
52
|
+
import { resetPaintMode, usePaintRegionMode } from '../../lib/paint-region-mode'
|
|
45
53
|
import {
|
|
46
54
|
applySceneGraphToEditor,
|
|
47
55
|
loadSceneFromLocalStorage,
|
|
@@ -50,16 +58,17 @@ import {
|
|
|
50
58
|
} from '../../lib/scene'
|
|
51
59
|
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
52
60
|
import { useUnitFocusRules } from '../../lib/units'
|
|
61
|
+
import { useViewerFloorDisplay } from '../../lib/viewer-selection'
|
|
53
62
|
import { type CameraHintAction, useCameraHintFocus } from '../../store/use-camera-hint-focus'
|
|
54
63
|
import useEditor from '../../store/use-editor'
|
|
55
64
|
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
56
65
|
import useSessionGroups from '../../store/use-session-groups'
|
|
66
|
+
import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
|
|
57
67
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
58
68
|
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
59
69
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
60
70
|
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
61
71
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
62
|
-
import { ZoneLabelEditorSystem } from '../systems/zone/zone-label-editor-system'
|
|
63
72
|
import { ZoneSystem } from '../systems/zone/zone-system'
|
|
64
73
|
import { BoxSelectTool } from '../tools/select/box-select-tool'
|
|
65
74
|
import { ToolManager } from '../tools/tool-manager'
|
|
@@ -72,6 +81,7 @@ import { PanelManager } from '../ui/panels/panel-manager'
|
|
|
72
81
|
import { ErrorBoundary } from '../ui/primitives/error-boundary'
|
|
73
82
|
import { useSidebarStore } from '../ui/primitives/sidebar'
|
|
74
83
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
|
|
84
|
+
import { RightStack } from '../ui/right-stack'
|
|
75
85
|
import { SceneLoader, SceneLoadFailed } from '../ui/scene-loader'
|
|
76
86
|
import { AppSidebar } from '../ui/sidebar/app-sidebar'
|
|
77
87
|
import type { ExtraPanel } from '../ui/sidebar/icon-rail'
|
|
@@ -79,6 +89,9 @@ import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/set
|
|
|
79
89
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
80
90
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
81
91
|
import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
|
|
92
|
+
import { ViewerHoverLabel } from '../viewer/viewer-hover-label'
|
|
93
|
+
import { ParametricViewerRooms } from '../viewer/viewer-rooms'
|
|
94
|
+
import { ViewerSelectionManager } from '../viewer/viewer-selection-manager'
|
|
82
95
|
import { ViewerStage } from '../viewer/viewer-stage'
|
|
83
96
|
import type { ViewerStageMode } from '../viewer/viewer-stage-modes'
|
|
84
97
|
import { CaptureCameraRig } from './capture-camera-rig'
|
|
@@ -92,21 +105,29 @@ import { FloatingActionMenu } from './floating-action-menu'
|
|
|
92
105
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
93
106
|
import { FloorplanModeCoordinator } from './floorplan-mode-coordinator'
|
|
94
107
|
import { FloorplanPanel } from './floorplan-panel'
|
|
108
|
+
import { FootprintHeightHandle } from './footprint-height-handle'
|
|
95
109
|
import { Grid } from './grid'
|
|
96
110
|
import { GroupFloatingActionMenu } from './group-floating-action-menu'
|
|
97
111
|
import { GroupRotateHandle } from './group-rotate-handle'
|
|
98
112
|
import { GroupSelectionBox3D } from './group-selection-box-3d'
|
|
113
|
+
import { EditorHandleHitPriority } from './handles/handle-hit-priority'
|
|
99
114
|
import { NodeArrowHandles } from './node-arrow-handles'
|
|
100
115
|
import { QuickMeasurementHud } from './quick-measurement-hud'
|
|
101
116
|
import { RiserDiagramPanel } from './riser-diagram-panel'
|
|
117
|
+
import { RoomLabels3D } from './room-labels'
|
|
102
118
|
import { SelectionManager } from './selection-manager'
|
|
103
119
|
import { SiteEdgeLabels } from './site-edge-labels'
|
|
104
120
|
import { SlabHoleHighlights } from './slab-hole-highlights'
|
|
105
121
|
import { SnapshotCaptureOverlay } from './snapshot-capture-overlay'
|
|
106
122
|
import { type SnapshotCameraData, ThumbnailGenerator } from './thumbnail-generator'
|
|
123
|
+
import { VectorEdgeExtractor } from './vector-edge-extractor'
|
|
124
|
+
import { HostView } from './views/host-view'
|
|
125
|
+
import { SceneRegion } from './views/scene-region'
|
|
126
|
+
import { useActiveViewLayout, useEditorViews } from './views/use-editor-views'
|
|
107
127
|
import { WallMeasurementLabel } from './wall-measurement-label'
|
|
108
128
|
import { WallMoveSideHandles } from './wall-move-side-handles'
|
|
109
129
|
import { WallOpeningHighlights } from './wall-opening-highlights'
|
|
130
|
+
import { WallRegionHandles } from './wall-region-handles'
|
|
110
131
|
|
|
111
132
|
const CAMERA_CONTROLS_HINT_DISMISSED_STORAGE_KEY = 'editor-camera-controls-hint-dismissed:v1'
|
|
112
133
|
const PREVIEW_STAGE_SWITCHER_POSITION =
|
|
@@ -116,12 +137,16 @@ const DELETE_CURSOR_BADGE_OFFSET_X = 14
|
|
|
116
137
|
const DELETE_CURSOR_BADGE_OFFSET_Y = 14
|
|
117
138
|
const PAINT_CURSOR_BADGE_COLOR = '#818cf8'
|
|
118
139
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
|
140
|
+
// Neutral: erasing paints no colour.
|
|
141
|
+
const ERASE_CURSOR_BADGE_COLOR = '#e4e4e7'
|
|
119
142
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
|
120
143
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
|
121
144
|
const SCENE_READY_FALLBACK_MS = 8000
|
|
122
145
|
const PRESENTATION_PROJECT_NOT_RESTORED = Symbol('presentation-project-not-restored')
|
|
123
146
|
const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect
|
|
124
|
-
|
|
147
|
+
// `aim`: a region sub-mode off what it draws on — the HUD says where to go; no
|
|
148
|
+
// forbidden sign, since nothing is refused.
|
|
149
|
+
type PaintCursorBadgeState = 'empty' | 'ready' | 'aim' | 'blocked'
|
|
125
150
|
const recordEditorRender: ProfilerOnRenderCallback = (_id, _phase, actualDuration) => {
|
|
126
151
|
if (PERF_OVERLAY_ENABLED) recordPerfSample('react-render', actualDuration)
|
|
127
152
|
}
|
|
@@ -129,6 +154,8 @@ const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
|
129
154
|
default: { visibleColor: 0x00_aa_ff, hiddenColor: 0xf3_ff_47, strength: 5, pulse: true },
|
|
130
155
|
delete: { visibleColor: 0xef_44_44, hiddenColor: 0x99_1b_1b, strength: 6, pulse: false },
|
|
131
156
|
'paint-ready': { visibleColor: 0xf5_9e_0b, hiddenColor: 0xfd_e0_68, strength: 5, pulse: true },
|
|
157
|
+
// Erasing paints nothing: a neutral outline, no paint colour.
|
|
158
|
+
'erase-ready': { visibleColor: 0xe4_e4_e7, hiddenColor: 0xa1_a1_aa, strength: 5, pulse: false },
|
|
132
159
|
'paint-disabled': {
|
|
133
160
|
visibleColor: 0x94_a3_b8,
|
|
134
161
|
hiddenColor: 0x47_55_69,
|
|
@@ -176,11 +203,20 @@ export interface EditorProps {
|
|
|
176
203
|
viewerToolbarLeft?: ReactNode
|
|
177
204
|
viewerToolbarRight?: ReactNode
|
|
178
205
|
/**
|
|
179
|
-
* Full-bleed surface swapped in over the
|
|
180
|
-
*
|
|
181
|
-
*
|
|
206
|
+
* Full-bleed surface swapped in over the whole stage (v2) — e.g. a builder
|
|
207
|
+
* with its own canvas. The scene stays mounted underneath (no WebGL re-init)
|
|
208
|
+
* and the view bar steps aside. Something to show *beside* the scene is a
|
|
209
|
+
* view instead (`registerEditorHostView`).
|
|
182
210
|
*/
|
|
183
211
|
stageOverlay?: ReactNode
|
|
212
|
+
/**
|
|
213
|
+
* Chrome that belongs to the scene (v2), positioned over the 3D and 2D panes
|
|
214
|
+
* with the level selector and tool dock — e.g. a capture bar. Hidden while
|
|
215
|
+
* no scene view is on screen.
|
|
216
|
+
*/
|
|
217
|
+
sceneOverlay?: ReactNode
|
|
218
|
+
/** Show each pane's view bar (v2, default true). Off for a host that presents one fixed view. */
|
|
219
|
+
viewBar?: boolean
|
|
184
220
|
/**
|
|
185
221
|
* Docked below the node inspector (v2). Hosts mount the "save as preset"
|
|
186
222
|
* affordance here so it reads as part of the inspector surface and shows
|
|
@@ -202,6 +238,7 @@ export interface EditorProps {
|
|
|
202
238
|
projectId?: string | null
|
|
203
239
|
|
|
204
240
|
// Persistence — defaults to localStorage when omitted
|
|
241
|
+
guardAgainstSceneWipe?: boolean
|
|
205
242
|
onLoad?: () => Promise<SceneGraph | null>
|
|
206
243
|
onSave?: (scene: SceneGraph, options?: { keepalive?: boolean }) => Promise<void>
|
|
207
244
|
/**
|
|
@@ -234,10 +271,16 @@ export interface EditorProps {
|
|
|
234
271
|
*/
|
|
235
272
|
disablePostFx?: boolean
|
|
236
273
|
|
|
274
|
+
/** Host-provided immersive XR runtime for the main 3D canvas. */
|
|
275
|
+
immersive?: ViewerImmersiveSession
|
|
276
|
+
|
|
237
277
|
// Version preview overlays (rendered by host app)
|
|
238
278
|
sidebarOverlay?: ReactNode
|
|
239
279
|
viewerBanner?: ReactNode
|
|
240
280
|
|
|
281
|
+
/** Shown in the preview header. */
|
|
282
|
+
projectName?: string | null
|
|
283
|
+
|
|
241
284
|
// Panel config (passed through to sidebar panels — v1 only)
|
|
242
285
|
settingsPanelProps?: SettingsPanelProps
|
|
243
286
|
sitePanelProps?: SitePanelProps
|
|
@@ -417,7 +460,7 @@ type CameraControlHint = {
|
|
|
417
460
|
alternativeKeys?: ShortcutKey[]
|
|
418
461
|
}
|
|
419
462
|
|
|
420
|
-
const
|
|
463
|
+
const CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
421
464
|
{
|
|
422
465
|
action: 'Pan',
|
|
423
466
|
keys: [{ value: 'Space' }, { value: 'Left click' }],
|
|
@@ -427,12 +470,6 @@ const EDITOR_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
427
470
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
428
471
|
]
|
|
429
472
|
|
|
430
|
-
const PREVIEW_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
431
|
-
{ action: 'Pan', keys: [{ value: 'Left click' }] },
|
|
432
|
-
{ action: 'Rotate', keys: [{ value: 'Right click' }] },
|
|
433
|
-
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
434
|
-
]
|
|
435
|
-
|
|
436
473
|
const CAMERA_SHORTCUT_KEY_META: Record<string, { icon?: string; label: string; text?: string }> = {
|
|
437
474
|
'Left click': {
|
|
438
475
|
icon: 'ph:mouse-left-click-fill',
|
|
@@ -539,14 +576,8 @@ function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
|
|
|
539
576
|
)
|
|
540
577
|
}
|
|
541
578
|
|
|
542
|
-
function ViewerCanvasControlsHint({
|
|
543
|
-
|
|
544
|
-
onDismiss,
|
|
545
|
-
}: {
|
|
546
|
-
isPreviewMode: boolean
|
|
547
|
-
onDismiss: () => void
|
|
548
|
-
}) {
|
|
549
|
-
const all = isPreviewMode ? PREVIEW_CAMERA_CONTROL_HINTS : EDITOR_CAMERA_CONTROL_HINTS
|
|
579
|
+
function ViewerCanvasControlsHint({ onDismiss }: { onDismiss: () => void }) {
|
|
580
|
+
const all = CAMERA_CONTROL_HINTS
|
|
550
581
|
// A host teaching one gesture at a time narrows this to the one it is asking
|
|
551
582
|
// for, and to nothing once it is done. Null — the default — is all of them.
|
|
552
583
|
const focus = useCameraHintFocus((state) => state.actions)
|
|
@@ -675,26 +706,31 @@ function PaintCursorBadge({
|
|
|
675
706
|
swatchColor,
|
|
676
707
|
swatchImageUrl,
|
|
677
708
|
isEraser,
|
|
709
|
+
isPicker = false,
|
|
678
710
|
}: {
|
|
679
711
|
position: { x: number; y: number }
|
|
680
712
|
state: PaintCursorBadgeState
|
|
681
713
|
swatchColor: string
|
|
682
714
|
swatchImageUrl?: string
|
|
683
715
|
isEraser: boolean
|
|
716
|
+
/** The eyedropper: the swatch is what a click would take. */
|
|
717
|
+
isPicker?: boolean
|
|
684
718
|
}) {
|
|
685
719
|
const accentColor =
|
|
686
720
|
state === 'ready'
|
|
687
721
|
? isEraser
|
|
688
|
-
?
|
|
722
|
+
? ERASE_CURSOR_BADGE_COLOR
|
|
689
723
|
: swatchColor
|
|
690
724
|
: PAINT_CURSOR_BADGE_DISABLED_COLOR
|
|
691
|
-
const iconOpacity = state === 'ready' ? 1 : state === 'blocked' ? 0.62 : 0.42
|
|
725
|
+
const iconOpacity = state === 'ready' ? 1 : state === 'blocked' || state === 'aim' ? 0.62 : 0.42
|
|
692
726
|
const lineHeight = 18
|
|
693
727
|
|
|
694
728
|
return (
|
|
695
729
|
<div
|
|
696
730
|
aria-hidden="true"
|
|
697
731
|
className="pointer-events-none absolute z-40"
|
|
732
|
+
data-paint-cursor-state={state}
|
|
733
|
+
data-paint-cursor-tool={isEraser ? 'erase' : isPicker ? 'pick' : 'paint'}
|
|
698
734
|
style={{
|
|
699
735
|
left: position.x,
|
|
700
736
|
top: position.y,
|
|
@@ -716,29 +752,32 @@ function PaintCursorBadge({
|
|
|
716
752
|
transform: `translate(-50%, calc(-100% - ${lineHeight}px))`,
|
|
717
753
|
}}
|
|
718
754
|
>
|
|
719
|
-
{
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
opacity: iconOpacity
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
755
|
+
{isPicker || isEraser ? (
|
|
756
|
+
// The eyedropper and the eraser carry their own tool, never a paint colour.
|
|
757
|
+
<Icon
|
|
758
|
+
aria-hidden="true"
|
|
759
|
+
className="text-white"
|
|
760
|
+
color="currentColor"
|
|
761
|
+
height={18}
|
|
762
|
+
icon={isEraser ? 'mdi:eraser-variant' : 'lucide:pipette'}
|
|
763
|
+
style={{ opacity: iconOpacity }}
|
|
764
|
+
width={18}
|
|
765
|
+
/>
|
|
766
|
+
) : (
|
|
767
|
+
/* eslint-disable-next-line @next/next/no-img-element */
|
|
768
|
+
<img
|
|
769
|
+
alt=""
|
|
770
|
+
aria-hidden="true"
|
|
771
|
+
className="h-5 w-5 object-contain drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
772
|
+
src="/icons/paint.webp"
|
|
773
|
+
style={{
|
|
774
|
+
filter: state === 'ready' ? undefined : 'grayscale(1)',
|
|
775
|
+
opacity: iconOpacity,
|
|
776
|
+
}}
|
|
777
|
+
/>
|
|
778
|
+
)}
|
|
779
|
+
{state === 'ready' || state === 'aim' ? (
|
|
780
|
+
isEraser ? null : (
|
|
742
781
|
<span
|
|
743
782
|
className="-right-1 -bottom-1 absolute h-3.5 w-3.5 rounded-full border border-white/70 bg-cover bg-center shadow-[0_2px_6px_rgba(0,0,0,0.45)]"
|
|
744
783
|
style={{
|
|
@@ -746,6 +785,7 @@ function PaintCursorBadge({
|
|
|
746
785
|
backgroundImage: swatchImageUrl
|
|
747
786
|
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
748
787
|
: undefined,
|
|
788
|
+
opacity: state === 'aim' ? 0.55 : 1,
|
|
749
789
|
}}
|
|
750
790
|
/>
|
|
751
791
|
)
|
|
@@ -783,6 +823,7 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
783
823
|
isVersionPreviewMode,
|
|
784
824
|
isLoading,
|
|
785
825
|
isFirstPersonMode,
|
|
826
|
+
isXRMode,
|
|
786
827
|
isStudioMode,
|
|
787
828
|
onThumbnailCapture,
|
|
788
829
|
viewerSceneSlot,
|
|
@@ -791,6 +832,7 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
791
832
|
isVersionPreviewMode: boolean
|
|
792
833
|
isLoading: boolean
|
|
793
834
|
isFirstPersonMode: boolean
|
|
835
|
+
isXRMode: boolean
|
|
794
836
|
isStudioMode: boolean
|
|
795
837
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
796
838
|
viewerSceneSlot?: ReactNode
|
|
@@ -807,18 +849,22 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
807
849
|
<>
|
|
808
850
|
<SceneEnvironment />
|
|
809
851
|
{!(isFirstPersonMode || isStudioMode || isCaptureMode) && <SelectionManager />}
|
|
810
|
-
{!noEditing && <BoxSelectTool />}
|
|
852
|
+
{!(noEditing || isXRMode) && <BoxSelectTool />}
|
|
853
|
+
{!noEditing && <EditorHandleHitPriority />}
|
|
811
854
|
{!noEditing && <NodeArrowHandles />}
|
|
855
|
+
{!noEditing && <FootprintHeightHandle />}
|
|
812
856
|
{!noEditing && <GroupRotateHandle />}
|
|
813
857
|
{!noEditing && <GroupSelectionBox3D />}
|
|
814
858
|
{!noEditing && <WallOpeningHighlights />}
|
|
815
859
|
{!noEditing && <SlabHoleHighlights />}
|
|
816
860
|
{!noEditing && <WallMoveSideHandles />}
|
|
861
|
+
{!noEditing && <WallRegionHandles />}
|
|
817
862
|
{!noEditing && <FenceTangentLines3D />}
|
|
818
|
-
{!noEditing && <FloatingActionMenu />}
|
|
819
|
-
{!noEditing && <GroupFloatingActionMenu />}
|
|
820
|
-
{!noEditing && <FloatingBuildingActionMenu />}
|
|
821
|
-
{!
|
|
863
|
+
{!(noEditing || isXRMode) && <FloatingActionMenu />}
|
|
864
|
+
{!(noEditing || isXRMode) && <GroupFloatingActionMenu />}
|
|
865
|
+
{!(noEditing || isXRMode) && <FloatingBuildingActionMenu />}
|
|
866
|
+
{!(noEditing || isXRMode) && <RoomLabels3D />}
|
|
867
|
+
{!(isFirstPersonMode || isXRMode) && <WallMeasurementLabel />}
|
|
822
868
|
<ExportManager />
|
|
823
869
|
{isFirstPersonMode ? <ViewerZoneSystem /> : <ZoneSystem />}
|
|
824
870
|
<CeilingSystem />
|
|
@@ -829,11 +875,11 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
829
875
|
{!(isLoading || isFirstPersonMode) && <SnapAwareGrid />}
|
|
830
876
|
{!(isLoading || noEditing) && <ToolManager />}
|
|
831
877
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
832
|
-
{isCaptureMode && <CaptureCameraRig />}
|
|
833
|
-
<CustomCameraControls />
|
|
834
|
-
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
835
|
-
{!
|
|
836
|
-
<
|
|
878
|
+
{isCaptureMode && !isXRMode && <CaptureCameraRig />}
|
|
879
|
+
{!isXRMode && <CustomCameraControls />}
|
|
880
|
+
{!isXRMode && <ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />}
|
|
881
|
+
{!isXRMode && <VectorEdgeExtractor />}
|
|
882
|
+
{!(isFirstPersonMode || isXRMode) && <SiteEdgeLabels />}
|
|
837
883
|
{presentationsReady ? <ViewerPresentations /> : null}
|
|
838
884
|
{!noEditing && viewerSceneSlot}
|
|
839
885
|
</>
|
|
@@ -926,7 +972,10 @@ function PaintCursorLayer({
|
|
|
926
972
|
}) {
|
|
927
973
|
const mode = useEditor((s) => s.mode)
|
|
928
974
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
929
|
-
const
|
|
975
|
+
const paintMode = usePaintRegionMode((s) => s.mode)
|
|
976
|
+
const picked = usePaintRegionMode((s) => s.picked)
|
|
977
|
+
const paintEraser = paintMode === 'erase'
|
|
978
|
+
const picking = paintMode === 'pick'
|
|
930
979
|
const paintHover = useEditor((s) => s.paintHover)
|
|
931
980
|
const sceneMaterials = useScene((s) => s.materials)
|
|
932
981
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
|
@@ -978,14 +1027,26 @@ function PaintCursorLayer({
|
|
|
978
1027
|
}
|
|
979
1028
|
}, [active, containerRef])
|
|
980
1029
|
|
|
981
|
-
const hasPaint = paintEraser || hasActivePaintMaterial(activePaintMaterial)
|
|
1030
|
+
const hasPaint = paintEraser || picking || hasActivePaintMaterial(activePaintMaterial)
|
|
1031
|
+
// A region sub-mode reads its own surfaces (walls, floors), not the whole-surface hover.
|
|
1032
|
+
const regionMode = paintMode === 'rectangle' || paintMode === 'polygon'
|
|
1033
|
+
const regionHovering = usePaintRegionHovering()
|
|
982
1034
|
const badgeState: PaintCursorBadgeState = !hasPaint
|
|
983
1035
|
? 'empty'
|
|
984
|
-
:
|
|
985
|
-
?
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1036
|
+
: picking
|
|
1037
|
+
? picked
|
|
1038
|
+
? 'ready'
|
|
1039
|
+
: 'aim'
|
|
1040
|
+
: regionMode
|
|
1041
|
+
? regionHovering
|
|
1042
|
+
? 'ready'
|
|
1043
|
+
: 'aim'
|
|
1044
|
+
: paintHover != null
|
|
1045
|
+
? 'ready'
|
|
1046
|
+
: 'blocked'
|
|
1047
|
+
const swatchMaterial = picking ? picked : activePaintMaterial
|
|
1048
|
+
const swatchColor = getActivePaintMaterialSwatchColor(swatchMaterial, sceneMaterials)
|
|
1049
|
+
const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(swatchMaterial, sceneMaterials)
|
|
989
1050
|
|
|
990
1051
|
if (!active || !position) return null
|
|
991
1052
|
|
|
@@ -996,6 +1057,7 @@ function PaintCursorLayer({
|
|
|
996
1057
|
>
|
|
997
1058
|
<PaintCursorBadge
|
|
998
1059
|
isEraser={paintEraser}
|
|
1060
|
+
isPicker={picking}
|
|
999
1061
|
position={{ x: 0, y: 0 }}
|
|
1000
1062
|
state={badgeState}
|
|
1001
1063
|
swatchColor={swatchColor}
|
|
@@ -1005,8 +1067,8 @@ function PaintCursorLayer({
|
|
|
1005
1067
|
)
|
|
1006
1068
|
}
|
|
1007
1069
|
|
|
1008
|
-
// ── Viewer canvas: memoized, subscribes to
|
|
1009
|
-
// This prevents Editor from re-rendering when
|
|
1070
|
+
// ── Viewer canvas: memoized, subscribes to the view layout internally ──
|
|
1071
|
+
// This prevents Editor from re-rendering when the layout changes.
|
|
1010
1072
|
|
|
1011
1073
|
const ViewerCanvas = memo(function ViewerCanvas({
|
|
1012
1074
|
isVersionPreviewMode,
|
|
@@ -1022,6 +1084,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1022
1084
|
viewerSceneSlot,
|
|
1023
1085
|
floorplanSceneSlot,
|
|
1024
1086
|
disablePostFx = false,
|
|
1087
|
+
immersive,
|
|
1025
1088
|
}: {
|
|
1026
1089
|
isVersionPreviewMode: boolean
|
|
1027
1090
|
isLoading: boolean
|
|
@@ -1036,13 +1099,22 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1036
1099
|
viewerSceneSlot?: ReactNode
|
|
1037
1100
|
floorplanSceneSlot?: ReactNode
|
|
1038
1101
|
disablePostFx?: boolean
|
|
1102
|
+
immersive?: ViewerImmersiveSession
|
|
1039
1103
|
}) {
|
|
1040
|
-
const
|
|
1041
|
-
const
|
|
1042
|
-
const setFloorplanPaneRatio = useEditor((s) => s.setFloorplanPaneRatio)
|
|
1104
|
+
const layout = useActiveViewLayout()
|
|
1105
|
+
const views = useEditorViews()
|
|
1043
1106
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1044
1107
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
1045
1108
|
useUnitFocusRules()
|
|
1109
|
+
const wholeBuilding = useEditor((s) =>
|
|
1110
|
+
showsWholeBuilding({
|
|
1111
|
+
isPreviewMode: s.isPreviewMode,
|
|
1112
|
+
isFirstPersonMode,
|
|
1113
|
+
captureMode: s.captureMode,
|
|
1114
|
+
captureLevelId: s.captureLevelId,
|
|
1115
|
+
}),
|
|
1116
|
+
)
|
|
1117
|
+
useEditorLevelDisplay(wholeBuilding)
|
|
1046
1118
|
const presetIsolation = useEditor((s) =>
|
|
1047
1119
|
s.captureMode.mode === 'preset' ? s.captureMode.isolated : null,
|
|
1048
1120
|
)
|
|
@@ -1060,25 +1132,24 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1060
1132
|
setViewerAreaEl(el)
|
|
1061
1133
|
}, [])
|
|
1062
1134
|
const viewer3dRef = useRef<HTMLDivElement>(null)
|
|
1063
|
-
const
|
|
1135
|
+
const isResizingPanes = useRef(false)
|
|
1064
1136
|
|
|
1065
|
-
const
|
|
1137
|
+
const handlePaneDividerDown = useCallback((e: React.PointerEvent) => {
|
|
1066
1138
|
e.preventDefault()
|
|
1067
|
-
|
|
1139
|
+
isResizingPanes.current = true
|
|
1068
1140
|
document.body.style.cursor = 'col-resize'
|
|
1069
1141
|
document.body.style.userSelect = 'none'
|
|
1070
1142
|
}, [])
|
|
1071
1143
|
|
|
1072
1144
|
useEffect(() => {
|
|
1073
1145
|
const handlePointerMove = (e: PointerEvent) => {
|
|
1074
|
-
if (!
|
|
1146
|
+
if (!isResizingPanes.current) return
|
|
1075
1147
|
if (!viewerAreaRef.current) return
|
|
1076
1148
|
const rect = viewerAreaRef.current.getBoundingClientRect()
|
|
1077
|
-
|
|
1078
|
-
setFloorplanPaneRatio(Math.max(0.15, Math.min(0.85, newRatio)))
|
|
1149
|
+
useEditor.getState().setViewPaneRatio((e.clientX - rect.left) / rect.width)
|
|
1079
1150
|
}
|
|
1080
1151
|
const handlePointerUp = () => {
|
|
1081
|
-
|
|
1152
|
+
isResizingPanes.current = false
|
|
1082
1153
|
document.body.style.cursor = ''
|
|
1083
1154
|
document.body.style.userSelect = ''
|
|
1084
1155
|
}
|
|
@@ -1088,7 +1159,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1088
1159
|
window.removeEventListener('pointermove', handlePointerMove)
|
|
1089
1160
|
window.removeEventListener('pointerup', handlePointerUp)
|
|
1090
1161
|
}
|
|
1091
|
-
}, [
|
|
1162
|
+
}, [])
|
|
1092
1163
|
|
|
1093
1164
|
useEffect(() => {
|
|
1094
1165
|
setIsCameraControlsHintVisible(!readCameraControlsHintDismissed())
|
|
@@ -1099,89 +1170,128 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1099
1170
|
writeCameraControlsHintDismissed(true)
|
|
1100
1171
|
}, [])
|
|
1101
1172
|
|
|
1102
|
-
const
|
|
1103
|
-
const
|
|
1173
|
+
const pane2d = paneOfView(layout, VIEW_2D)
|
|
1174
|
+
const pane3d = paneOfView(layout, VIEW_3D)
|
|
1175
|
+
const show3d = pane3d !== null
|
|
1176
|
+
// Panes are grid cells placed by `gridColumn`, in a fixed DOM order: moving a
|
|
1177
|
+
// view to the other pane restyles it rather than reparenting it, so the 3D
|
|
1178
|
+
// canvas keeps its WebGL context and the plan its session through a swap.
|
|
1179
|
+
const paneStyle = (pane: ViewPaneIndex | null): CSSProperties =>
|
|
1180
|
+
pane === null ? { display: 'none' } : { gridColumn: pane + 1, gridRow: 1 }
|
|
1181
|
+
const hostPanes = ([0, 1] as const).filter(
|
|
1182
|
+
(pane) => (pane === 0 || layout.split) && !isSceneView(layout.panes[pane]),
|
|
1183
|
+
)
|
|
1184
|
+
const focusPane = (pane: ViewPaneIndex) => () => {
|
|
1185
|
+
if (layout.split) useEditor.getState().focusViewPane(pane)
|
|
1186
|
+
}
|
|
1104
1187
|
|
|
1105
1188
|
return (
|
|
1106
1189
|
<ErrorBoundary fallback={<EditorSceneCrashFallback />}>
|
|
1107
1190
|
{/* `relative` so the floorplan compass (portaled here to stay visible in
|
|
1108
|
-
|
|
1109
|
-
<div className="relative
|
|
1191
|
+
every layout) can anchor to this container's bottom-left. */}
|
|
1192
|
+
<div className="relative h-full" ref={setViewerAreaNode}>
|
|
1110
1193
|
<QuickMeasurementHud />
|
|
1111
1194
|
<DeleteConfirmationDialog />
|
|
1112
|
-
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
1113
1195
|
<div
|
|
1114
|
-
className="
|
|
1196
|
+
className="absolute inset-0 grid"
|
|
1197
|
+
data-view-split={layout.split || undefined}
|
|
1115
1198
|
style={{
|
|
1116
|
-
|
|
1117
|
-
|
|
1199
|
+
gridTemplateColumns: layout.split
|
|
1200
|
+
? `minmax(0, ${layout.ratio}fr) minmax(0, ${1 - layout.ratio}fr)`
|
|
1201
|
+
: 'minmax(0, 1fr)',
|
|
1202
|
+
gridTemplateRows: 'minmax(0, 1fr)',
|
|
1118
1203
|
}}
|
|
1119
1204
|
>
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
>
|
|
1128
|
-
<
|
|
1205
|
+
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
1206
|
+
<div
|
|
1207
|
+
className="relative min-w-0 overflow-hidden"
|
|
1208
|
+
data-view-pane={pane2d ?? undefined}
|
|
1209
|
+
onPointerDownCapture={pane2d === null ? undefined : focusPane(pane2d)}
|
|
1210
|
+
style={paneStyle(pane2d)}
|
|
1211
|
+
>
|
|
1212
|
+
<div className="h-full w-full overflow-hidden">
|
|
1213
|
+
<FloorplanPanel compassHost={viewerAreaEl} floorplanSceneSlot={floorplanSceneSlot} />
|
|
1129
1214
|
</div>
|
|
1130
|
-
|
|
1131
|
-
</div>
|
|
1215
|
+
</div>
|
|
1132
1216
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
containerRef={viewer3dRef}
|
|
1142
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1143
|
-
/>
|
|
1144
|
-
<PaintCursorLayer
|
|
1145
|
-
containerRef={viewer3dRef}
|
|
1146
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1147
|
-
/>
|
|
1148
|
-
{!showLoader && isCameraControlsHintVisible && !isFirstPersonMode ? (
|
|
1149
|
-
<ViewerCanvasControlsHint
|
|
1150
|
-
isPreviewMode={isPreviewMode}
|
|
1151
|
-
onDismiss={dismissCameraControlsHint}
|
|
1152
|
-
/>
|
|
1153
|
-
) : null}
|
|
1154
|
-
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
1155
|
-
<Viewer
|
|
1156
|
-
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1157
|
-
disablePostFx={disablePostFx}
|
|
1158
|
-
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1159
|
-
isolate={presetIsolation}
|
|
1160
|
-
// Preset captures isolate one subtree and keep the exterior transparent.
|
|
1161
|
-
// Other modes retain the viewer's configured background policy.
|
|
1162
|
-
transparent={presetIsolation === null ? undefined : true}
|
|
1163
|
-
onSceneReadyChange={onSceneReadyChange}
|
|
1164
|
-
renderContext="editor"
|
|
1165
|
-
renderPaused={!show3d && !showLoader}
|
|
1166
|
-
sceneReadyKey={sceneReadyKey}
|
|
1167
|
-
// Walk/drone framing during snapshot capture is camera-only: the
|
|
1168
|
-
// viewer's default selection manager would hover-highlight whatever
|
|
1169
|
-
// the cursor crosses, which orbit capture never does.
|
|
1170
|
-
selectionManager={isFirstPersonMode && !isCaptureMode ? 'default' : 'custom'}
|
|
1217
|
+
{/* 3D viewer — always mounted, hidden via CSS to avoid destroying the WebGL context */}
|
|
1218
|
+
<div
|
|
1219
|
+
className="relative min-w-0 overflow-hidden"
|
|
1220
|
+
data-pascal-viewer-3d
|
|
1221
|
+
data-view-pane={pane3d ?? undefined}
|
|
1222
|
+
onPointerDownCapture={pane3d === null ? undefined : focusPane(pane3d)}
|
|
1223
|
+
ref={viewer3dRef}
|
|
1224
|
+
style={paneStyle(pane3d)}
|
|
1171
1225
|
>
|
|
1172
|
-
<
|
|
1173
|
-
|
|
1174
|
-
isLoading={showLoader}
|
|
1175
|
-
isStudioMode={isStudioMode}
|
|
1226
|
+
<DeleteCursorLayer
|
|
1227
|
+
containerRef={viewer3dRef}
|
|
1176
1228
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1177
|
-
onThumbnailCapture={onThumbnailCapture}
|
|
1178
|
-
presentationsReady={presentationsReady}
|
|
1179
|
-
viewerSceneSlot={viewerSceneSlot}
|
|
1180
1229
|
/>
|
|
1181
|
-
|
|
1230
|
+
<PaintCursorLayer
|
|
1231
|
+
containerRef={viewer3dRef}
|
|
1232
|
+
isVersionPreviewMode={isVersionPreviewMode}
|
|
1233
|
+
/>
|
|
1234
|
+
{!showLoader && isCameraControlsHintVisible && !isFirstPersonMode ? (
|
|
1235
|
+
<ViewerCanvasControlsHint onDismiss={dismissCameraControlsHint} />
|
|
1236
|
+
) : null}
|
|
1237
|
+
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
1238
|
+
<Viewer
|
|
1239
|
+
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1240
|
+
disablePostFx={disablePostFx}
|
|
1241
|
+
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1242
|
+
isolate={presetIsolation}
|
|
1243
|
+
// Preset captures isolate one subtree and keep the exterior transparent.
|
|
1244
|
+
// Other modes retain the viewer's configured background policy.
|
|
1245
|
+
transparent={presetIsolation === null ? undefined : true}
|
|
1246
|
+
onSceneReadyChange={onSceneReadyChange}
|
|
1247
|
+
renderContext="editor"
|
|
1248
|
+
renderPaused={!show3d && !showLoader}
|
|
1249
|
+
sceneReadyKey={sceneReadyKey}
|
|
1250
|
+
immersive={immersive}
|
|
1251
|
+
// Walk/drone framing during snapshot capture is camera-only: the
|
|
1252
|
+
// viewer's default selection manager would hover-highlight whatever
|
|
1253
|
+
// the cursor crosses, which orbit capture never does.
|
|
1254
|
+
selectionManager={isFirstPersonMode && !isCaptureMode ? 'default' : 'custom'}
|
|
1255
|
+
>
|
|
1256
|
+
<ViewerSceneContent
|
|
1257
|
+
isFirstPersonMode={isFirstPersonMode}
|
|
1258
|
+
isLoading={showLoader}
|
|
1259
|
+
isXRMode={immersive != null}
|
|
1260
|
+
isStudioMode={isStudioMode}
|
|
1261
|
+
isVersionPreviewMode={isVersionPreviewMode}
|
|
1262
|
+
onThumbnailCapture={onThumbnailCapture}
|
|
1263
|
+
presentationsReady={presentationsReady}
|
|
1264
|
+
viewerSceneSlot={viewerSceneSlot}
|
|
1265
|
+
/>
|
|
1266
|
+
</Viewer>
|
|
1267
|
+
</div>
|
|
1268
|
+
|
|
1269
|
+
{hostPanes.map((pane) => {
|
|
1270
|
+
const viewId = layout.panes[pane]
|
|
1271
|
+
return (
|
|
1272
|
+
<div
|
|
1273
|
+
className="relative min-w-0 overflow-hidden"
|
|
1274
|
+
data-view-pane={pane}
|
|
1275
|
+
key={viewId}
|
|
1276
|
+
onPointerDownCapture={focusPane(pane)}
|
|
1277
|
+
style={paneStyle(pane)}
|
|
1278
|
+
>
|
|
1279
|
+
<HostView key={viewId} view={views.find((view) => view.id === viewId)} />
|
|
1280
|
+
</div>
|
|
1281
|
+
)
|
|
1282
|
+
})}
|
|
1182
1283
|
</div>
|
|
1284
|
+
|
|
1285
|
+
{layout.split && (
|
|
1286
|
+
<div
|
|
1287
|
+
className="absolute inset-y-0 z-10 flex w-6 -translate-x-1/2 cursor-col-resize items-center justify-center"
|
|
1288
|
+
onPointerDown={handlePaneDividerDown}
|
|
1289
|
+
style={{ left: `${layout.ratio * 100}%` }}
|
|
1290
|
+
>
|
|
1291
|
+
<div className="h-8 w-1 rounded-full bg-neutral-400" />
|
|
1292
|
+
</div>
|
|
1293
|
+
)}
|
|
1183
1294
|
</div>
|
|
1184
|
-
{!(showLoader || isVersionPreviewMode) && <ZoneLabelEditorSystem />}
|
|
1185
1295
|
</ErrorBoundary>
|
|
1186
1296
|
)
|
|
1187
1297
|
})
|
|
@@ -1190,12 +1300,14 @@ function PreviewStage({
|
|
|
1190
1300
|
isFirstPersonMode,
|
|
1191
1301
|
mode,
|
|
1192
1302
|
onModeChange,
|
|
1303
|
+
projectName,
|
|
1193
1304
|
showLoader,
|
|
1194
1305
|
viewerContent,
|
|
1195
1306
|
}: {
|
|
1196
1307
|
isFirstPersonMode: boolean
|
|
1197
1308
|
mode: ViewerStageMode
|
|
1198
1309
|
onModeChange: (mode: ViewerStageMode) => void
|
|
1310
|
+
projectName?: string | null
|
|
1199
1311
|
showLoader: boolean
|
|
1200
1312
|
viewerContent: ReactNode
|
|
1201
1313
|
}) {
|
|
@@ -1211,6 +1323,7 @@ function PreviewStage({
|
|
|
1211
1323
|
[onModeChange],
|
|
1212
1324
|
)
|
|
1213
1325
|
|
|
1326
|
+
useViewerFloorDisplay()
|
|
1214
1327
|
const stageMode = isFirstPersonMode || !hasFloorplan ? '3d' : mode
|
|
1215
1328
|
const stageModes = hasFloorplan && !isFirstPersonMode ? undefined : (['3d'] as const)
|
|
1216
1329
|
|
|
@@ -1222,9 +1335,11 @@ function PreviewStage({
|
|
|
1222
1335
|
<ViewerOverlay
|
|
1223
1336
|
hideBottomBar={stageMode !== '3d'}
|
|
1224
1337
|
onBack={() => useEditor.getState().setPreviewMode(false)}
|
|
1338
|
+
projectName={projectName}
|
|
1225
1339
|
/>
|
|
1226
1340
|
)}
|
|
1227
1341
|
|
|
1342
|
+
{stageMode === '3d' && !isFirstPersonMode ? <ViewerHoverLabel /> : null}
|
|
1228
1343
|
<ViewerStage
|
|
1229
1344
|
className="absolute inset-0"
|
|
1230
1345
|
mode={stageMode}
|
|
@@ -1241,6 +1356,7 @@ function PreviewStage({
|
|
|
1241
1356
|
}
|
|
1242
1357
|
|
|
1243
1358
|
function EditorContent({
|
|
1359
|
+
guardAgainstSceneWipe,
|
|
1244
1360
|
layoutVersion = 'v1',
|
|
1245
1361
|
appMenuButton,
|
|
1246
1362
|
sidebarTop,
|
|
@@ -1249,6 +1365,8 @@ function EditorContent({
|
|
|
1249
1365
|
viewerToolbarLeft,
|
|
1250
1366
|
viewerToolbarRight,
|
|
1251
1367
|
stageOverlay,
|
|
1368
|
+
sceneOverlay,
|
|
1369
|
+
viewBar = true,
|
|
1252
1370
|
inspectorFooter,
|
|
1253
1371
|
multiSelectionFooter,
|
|
1254
1372
|
viewerSceneSlot,
|
|
@@ -1265,8 +1383,10 @@ function EditorContent({
|
|
|
1265
1383
|
onLoaderChange,
|
|
1266
1384
|
onThumbnailCapture,
|
|
1267
1385
|
disablePostFx = false,
|
|
1386
|
+
immersive,
|
|
1268
1387
|
sidebarOverlay,
|
|
1269
1388
|
viewerBanner,
|
|
1389
|
+
projectName,
|
|
1270
1390
|
settingsPanelProps,
|
|
1271
1391
|
sitePanelProps,
|
|
1272
1392
|
extraSidebarPanels,
|
|
@@ -1298,8 +1418,10 @@ function EditorContent({
|
|
|
1298
1418
|
}, [presentationProjectId])
|
|
1299
1419
|
|
|
1300
1420
|
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode || isStudioMode })
|
|
1421
|
+
useGestureLifecycleOwner()
|
|
1301
1422
|
|
|
1302
|
-
const {
|
|
1423
|
+
const { beginSceneLoad, completeSceneLoad, saveNow } = useAutoSave({
|
|
1424
|
+
guardAgainstSceneWipe,
|
|
1303
1425
|
onSave,
|
|
1304
1426
|
onDirty,
|
|
1305
1427
|
onSaveStatusChange,
|
|
@@ -1338,6 +1460,7 @@ function EditorContent({
|
|
|
1338
1460
|
const teardown = initializeEditorRuntime()
|
|
1339
1461
|
return teardown
|
|
1340
1462
|
}, [])
|
|
1463
|
+
useCeilingEditSessionOwner()
|
|
1341
1464
|
|
|
1342
1465
|
useEffect(() => {
|
|
1343
1466
|
void useEditor.persist.rehydrate()
|
|
@@ -1347,6 +1470,7 @@ function EditorContent({
|
|
|
1347
1470
|
useEffect(() => {
|
|
1348
1471
|
useViewer.getState().setProjectId(projectId ?? null)
|
|
1349
1472
|
useFloorplanMode.getState().setProjectId(projectId ?? null)
|
|
1473
|
+
resetPaintMode()
|
|
1350
1474
|
|
|
1351
1475
|
return () => {
|
|
1352
1476
|
useViewer.getState().setProjectId(null)
|
|
@@ -1359,7 +1483,7 @@ function EditorContent({
|
|
|
1359
1483
|
let cancelled = false
|
|
1360
1484
|
|
|
1361
1485
|
async function load(attempt: number) {
|
|
1362
|
-
|
|
1486
|
+
beginSceneLoad()
|
|
1363
1487
|
setSceneLoadError(null)
|
|
1364
1488
|
setHasLoadedInitialScene(false)
|
|
1365
1489
|
setIsViewerSceneReady(false)
|
|
@@ -1374,6 +1498,9 @@ function EditorContent({
|
|
|
1374
1498
|
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
|
|
1375
1499
|
if (!cancelled && attempt === sceneLoadAttempt) {
|
|
1376
1500
|
applySceneGraphToEditor(sceneGraph)
|
|
1501
|
+
// The store holds the loaded graph: autosave resumes now, not on a
|
|
1502
|
+
// frame — a tab loaded while hidden never gets one.
|
|
1503
|
+
completeSceneLoad()
|
|
1377
1504
|
setIsViewerSceneReady(false)
|
|
1378
1505
|
setSceneReadyKey((key) => key + 1)
|
|
1379
1506
|
}
|
|
@@ -1390,9 +1517,11 @@ function EditorContent({
|
|
|
1390
1517
|
setIsSceneLoading(false)
|
|
1391
1518
|
if (!failed) {
|
|
1392
1519
|
setHasLoadedInitialScene(true)
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1520
|
+
// A project opens in select mode. Rehydrate already drops the
|
|
1521
|
+
// persisted tool, but a host's rail panel may arm a mode of its
|
|
1522
|
+
// own when it mounts, and with a restored rail tab that mount can
|
|
1523
|
+
// land after rehydrate.
|
|
1524
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
1396
1525
|
}
|
|
1397
1526
|
}
|
|
1398
1527
|
}
|
|
@@ -1403,7 +1532,7 @@ function EditorContent({
|
|
|
1403
1532
|
return () => {
|
|
1404
1533
|
cancelled = true
|
|
1405
1534
|
}
|
|
1406
|
-
}, [onLoad,
|
|
1535
|
+
}, [onLoad, beginSceneLoad, completeSceneLoad, sceneLoadAttempt])
|
|
1407
1536
|
|
|
1408
1537
|
const retrySceneLoad = useCallback(() => {
|
|
1409
1538
|
setSceneLoadAttempt((attempt) => attempt + 1)
|
|
@@ -1472,10 +1601,13 @@ function EditorContent({
|
|
|
1472
1601
|
|
|
1473
1602
|
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
|
1474
1603
|
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
1604
|
+
const firstPersonWasPreviewRef = useRef(isPreviewMode)
|
|
1475
1605
|
|
|
1476
1606
|
useEffect(() => {
|
|
1477
1607
|
const wasFirstPersonMode = wasFirstPersonModeRef.current
|
|
1608
|
+
const wasPreviewMode = firstPersonWasPreviewRef.current
|
|
1478
1609
|
wasFirstPersonModeRef.current = isFirstPersonMode
|
|
1610
|
+
firstPersonWasPreviewRef.current = isPreviewMode
|
|
1479
1611
|
|
|
1480
1612
|
if (isFirstPersonMode && !wasFirstPersonMode) {
|
|
1481
1613
|
const viewer = useViewer.getState()
|
|
@@ -1494,7 +1626,8 @@ function EditorContent({
|
|
|
1494
1626
|
firstPersonPreviousLevelRef.current = null
|
|
1495
1627
|
viewer.setWalkthroughMode(false)
|
|
1496
1628
|
|
|
1497
|
-
|
|
1629
|
+
// Leaving Preview has already restored the editor's complete selection.
|
|
1630
|
+
if ((wasPreviewMode && !isPreviewMode) || !previousLevelId) return
|
|
1498
1631
|
|
|
1499
1632
|
const previousLevelNode = useScene.getState().nodes[previousLevelId]
|
|
1500
1633
|
if (previousLevelNode?.type === 'level') {
|
|
@@ -1504,7 +1637,7 @@ function EditorContent({
|
|
|
1504
1637
|
selectedIds: [],
|
|
1505
1638
|
})
|
|
1506
1639
|
}
|
|
1507
|
-
}, [isFirstPersonMode])
|
|
1640
|
+
}, [isFirstPersonMode, isPreviewMode])
|
|
1508
1641
|
|
|
1509
1642
|
const previewViewerContent = (
|
|
1510
1643
|
<Viewer
|
|
@@ -1512,16 +1645,18 @@ function EditorContent({
|
|
|
1512
1645
|
disablePostFx={disablePostFx}
|
|
1513
1646
|
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1514
1647
|
renderContext="editor"
|
|
1515
|
-
selectionManager="
|
|
1648
|
+
selectionManager="custom"
|
|
1516
1649
|
>
|
|
1517
1650
|
<ExportManager />
|
|
1518
|
-
<
|
|
1651
|
+
<ViewerSelectionManager />
|
|
1652
|
+
<ParametricViewerRooms />
|
|
1519
1653
|
<CeilingSystem />
|
|
1520
1654
|
<RoofEditSystem />
|
|
1521
1655
|
<StairEditSystem />
|
|
1522
1656
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
1523
1657
|
<CustomCameraControls />
|
|
1524
1658
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
1659
|
+
<VectorEdgeExtractor />
|
|
1525
1660
|
<InteractiveSystem />
|
|
1526
1661
|
{presentationsReady ? <ViewerPresentations /> : null}
|
|
1527
1662
|
</Viewer>
|
|
@@ -1542,6 +1677,7 @@ function EditorContent({
|
|
|
1542
1677
|
showLoader={showLoader}
|
|
1543
1678
|
viewerSceneSlot={viewerSceneSlot}
|
|
1544
1679
|
floorplanSceneSlot={floorplanSceneSlot}
|
|
1680
|
+
immersive={immersive}
|
|
1545
1681
|
/>
|
|
1546
1682
|
)
|
|
1547
1683
|
|
|
@@ -1590,6 +1726,10 @@ function EditorContent({
|
|
|
1590
1726
|
mobileDefaultSnap: 0.5,
|
|
1591
1727
|
mobileIcon: p.icon,
|
|
1592
1728
|
icon: p.icon,
|
|
1729
|
+
noPanel: p.noPanel,
|
|
1730
|
+
onSelect: p.onSelect,
|
|
1731
|
+
stageActive: p.stageActive,
|
|
1732
|
+
onDeselect: p.onDeselect,
|
|
1593
1733
|
})),
|
|
1594
1734
|
]
|
|
1595
1735
|
|
|
@@ -1611,6 +1751,7 @@ function EditorContent({
|
|
|
1611
1751
|
isFirstPersonMode={isFirstPersonMode}
|
|
1612
1752
|
mode={previewStageMode}
|
|
1613
1753
|
onModeChange={setPreviewStageMode}
|
|
1754
|
+
projectName={projectName}
|
|
1614
1755
|
showLoader={visibleLoader}
|
|
1615
1756
|
viewerContent={previewViewerContent}
|
|
1616
1757
|
/>
|
|
@@ -1620,25 +1761,27 @@ function EditorContent({
|
|
|
1620
1761
|
navbarSlot={navbarSlot}
|
|
1621
1762
|
overlays={
|
|
1622
1763
|
<>
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
<
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1764
|
+
<SceneRegion>
|
|
1765
|
+
{!(isCaptureMode || stageOverlay) && <FloatingLevelSelector />}
|
|
1766
|
+
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1767
|
+
<div className="pointer-events-auto">
|
|
1768
|
+
<ActionMenu />
|
|
1769
|
+
</div>
|
|
1770
|
+
)}
|
|
1771
|
+
{sceneOverlay}
|
|
1772
|
+
</SceneRegion>
|
|
1773
|
+
{/* The inspector and the shortcuts card share one right column. */}
|
|
1774
|
+
<RightStack
|
|
1775
|
+
helper={isCaptureMode ? null : <HelperManager />}
|
|
1776
|
+
inspector={
|
|
1777
|
+
isVersionPreviewMode || isCaptureMode || isStudioMode ? null : (
|
|
1778
|
+
<PanelManager
|
|
1779
|
+
inspectorFooter={inspectorFooter}
|
|
1780
|
+
multiSelectionFooter={multiSelectionFooter}
|
|
1781
|
+
/>
|
|
1782
|
+
)
|
|
1783
|
+
}
|
|
1784
|
+
/>
|
|
1642
1785
|
{/* Capture mode drives walk / drone from its own overlay, which
|
|
1643
1786
|
owns the framing chrome — the walkthrough HUD would both
|
|
1644
1787
|
clutter the frame and offer a second, conflicting exit. */}
|
|
@@ -1655,6 +1798,7 @@ function EditorContent({
|
|
|
1655
1798
|
sidebarOverlay={sidebarOverlay}
|
|
1656
1799
|
sidebarTabs={tabBarTabs}
|
|
1657
1800
|
stageOverlay={stageOverlay}
|
|
1801
|
+
viewBar={viewBar}
|
|
1658
1802
|
viewerContent={viewerCanvas}
|
|
1659
1803
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
1660
1804
|
viewerToolbarRight={viewerToolbarRight}
|
|
@@ -1691,6 +1835,7 @@ function EditorContent({
|
|
|
1691
1835
|
isFirstPersonMode={isFirstPersonMode}
|
|
1692
1836
|
mode={previewStageMode}
|
|
1693
1837
|
onModeChange={setPreviewStageMode}
|
|
1838
|
+
projectName={projectName}
|
|
1694
1839
|
showLoader={visibleLoader}
|
|
1695
1840
|
viewerContent={previewViewerContent}
|
|
1696
1841
|
/>
|
|
@@ -1716,12 +1861,7 @@ function EditorContent({
|
|
|
1716
1861
|
<div className="pointer-events-auto">
|
|
1717
1862
|
<ActionMenu />
|
|
1718
1863
|
</div>
|
|
1719
|
-
<
|
|
1720
|
-
<PanelManager />
|
|
1721
|
-
</div>
|
|
1722
|
-
<div className="pointer-events-auto">
|
|
1723
|
-
<HelperManager />
|
|
1724
|
-
</div>
|
|
1864
|
+
<RightStack helper={<HelperManager />} inspector={<PanelManager />} />
|
|
1725
1865
|
<RiserDiagramPanel />
|
|
1726
1866
|
{isFirstPersonMode && (
|
|
1727
1867
|
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|