@pascal-app/editor 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ViewerPresentations,
|
|
24
24
|
} from '@pascal-app/viewer'
|
|
25
25
|
import {
|
|
26
|
+
type CSSProperties,
|
|
26
27
|
memo,
|
|
27
28
|
Profiler,
|
|
28
29
|
type ProfilerOnRenderCallback,
|
|
@@ -38,11 +39,17 @@ import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
|
38
39
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
39
40
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
40
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'
|
|
41
46
|
import {
|
|
42
47
|
createLocalProjectPresentationPersistence,
|
|
43
48
|
type LocalProjectPresentationPersistence,
|
|
44
49
|
} from '../../lib/local-project-presentation-persistence'
|
|
45
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'
|
|
46
53
|
import {
|
|
47
54
|
applySceneGraphToEditor,
|
|
48
55
|
loadSceneFromLocalStorage,
|
|
@@ -51,16 +58,17 @@ import {
|
|
|
51
58
|
} from '../../lib/scene'
|
|
52
59
|
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
53
60
|
import { useUnitFocusRules } from '../../lib/units'
|
|
61
|
+
import { useViewerFloorDisplay } from '../../lib/viewer-selection'
|
|
54
62
|
import { type CameraHintAction, useCameraHintFocus } from '../../store/use-camera-hint-focus'
|
|
55
63
|
import useEditor from '../../store/use-editor'
|
|
56
64
|
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
57
65
|
import useSessionGroups from '../../store/use-session-groups'
|
|
66
|
+
import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
|
|
58
67
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
59
68
|
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
60
69
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
61
70
|
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
62
71
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
63
|
-
import { ZoneLabelEditorSystem } from '../systems/zone/zone-label-editor-system'
|
|
64
72
|
import { ZoneSystem } from '../systems/zone/zone-system'
|
|
65
73
|
import { BoxSelectTool } from '../tools/select/box-select-tool'
|
|
66
74
|
import { ToolManager } from '../tools/tool-manager'
|
|
@@ -73,6 +81,7 @@ import { PanelManager } from '../ui/panels/panel-manager'
|
|
|
73
81
|
import { ErrorBoundary } from '../ui/primitives/error-boundary'
|
|
74
82
|
import { useSidebarStore } from '../ui/primitives/sidebar'
|
|
75
83
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
|
|
84
|
+
import { RightStack } from '../ui/right-stack'
|
|
76
85
|
import { SceneLoader, SceneLoadFailed } from '../ui/scene-loader'
|
|
77
86
|
import { AppSidebar } from '../ui/sidebar/app-sidebar'
|
|
78
87
|
import type { ExtraPanel } from '../ui/sidebar/icon-rail'
|
|
@@ -80,6 +89,9 @@ import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/set
|
|
|
80
89
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
81
90
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
82
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'
|
|
83
95
|
import { ViewerStage } from '../viewer/viewer-stage'
|
|
84
96
|
import type { ViewerStageMode } from '../viewer/viewer-stage-modes'
|
|
85
97
|
import { CaptureCameraRig } from './capture-camera-rig'
|
|
@@ -93,21 +105,29 @@ import { FloatingActionMenu } from './floating-action-menu'
|
|
|
93
105
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
94
106
|
import { FloorplanModeCoordinator } from './floorplan-mode-coordinator'
|
|
95
107
|
import { FloorplanPanel } from './floorplan-panel'
|
|
108
|
+
import { FootprintHeightHandle } from './footprint-height-handle'
|
|
96
109
|
import { Grid } from './grid'
|
|
97
110
|
import { GroupFloatingActionMenu } from './group-floating-action-menu'
|
|
98
111
|
import { GroupRotateHandle } from './group-rotate-handle'
|
|
99
112
|
import { GroupSelectionBox3D } from './group-selection-box-3d'
|
|
113
|
+
import { EditorHandleHitPriority } from './handles/handle-hit-priority'
|
|
100
114
|
import { NodeArrowHandles } from './node-arrow-handles'
|
|
101
115
|
import { QuickMeasurementHud } from './quick-measurement-hud'
|
|
102
116
|
import { RiserDiagramPanel } from './riser-diagram-panel'
|
|
117
|
+
import { RoomLabels3D } from './room-labels'
|
|
103
118
|
import { SelectionManager } from './selection-manager'
|
|
104
119
|
import { SiteEdgeLabels } from './site-edge-labels'
|
|
105
120
|
import { SlabHoleHighlights } from './slab-hole-highlights'
|
|
106
121
|
import { SnapshotCaptureOverlay } from './snapshot-capture-overlay'
|
|
107
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'
|
|
108
127
|
import { WallMeasurementLabel } from './wall-measurement-label'
|
|
109
128
|
import { WallMoveSideHandles } from './wall-move-side-handles'
|
|
110
129
|
import { WallOpeningHighlights } from './wall-opening-highlights'
|
|
130
|
+
import { WallRegionHandles } from './wall-region-handles'
|
|
111
131
|
|
|
112
132
|
const CAMERA_CONTROLS_HINT_DISMISSED_STORAGE_KEY = 'editor-camera-controls-hint-dismissed:v1'
|
|
113
133
|
const PREVIEW_STAGE_SWITCHER_POSITION =
|
|
@@ -117,12 +137,16 @@ const DELETE_CURSOR_BADGE_OFFSET_X = 14
|
|
|
117
137
|
const DELETE_CURSOR_BADGE_OFFSET_Y = 14
|
|
118
138
|
const PAINT_CURSOR_BADGE_COLOR = '#818cf8'
|
|
119
139
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
|
140
|
+
// Neutral: erasing paints no colour.
|
|
141
|
+
const ERASE_CURSOR_BADGE_COLOR = '#e4e4e7'
|
|
120
142
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
|
121
143
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
|
122
144
|
const SCENE_READY_FALLBACK_MS = 8000
|
|
123
145
|
const PRESENTATION_PROJECT_NOT_RESTORED = Symbol('presentation-project-not-restored')
|
|
124
146
|
const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect
|
|
125
|
-
|
|
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'
|
|
126
150
|
const recordEditorRender: ProfilerOnRenderCallback = (_id, _phase, actualDuration) => {
|
|
127
151
|
if (PERF_OVERLAY_ENABLED) recordPerfSample('react-render', actualDuration)
|
|
128
152
|
}
|
|
@@ -130,6 +154,8 @@ const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
|
130
154
|
default: { visibleColor: 0x00_aa_ff, hiddenColor: 0xf3_ff_47, strength: 5, pulse: true },
|
|
131
155
|
delete: { visibleColor: 0xef_44_44, hiddenColor: 0x99_1b_1b, strength: 6, pulse: false },
|
|
132
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 },
|
|
133
159
|
'paint-disabled': {
|
|
134
160
|
visibleColor: 0x94_a3_b8,
|
|
135
161
|
hiddenColor: 0x47_55_69,
|
|
@@ -177,11 +203,20 @@ export interface EditorProps {
|
|
|
177
203
|
viewerToolbarLeft?: ReactNode
|
|
178
204
|
viewerToolbarRight?: ReactNode
|
|
179
205
|
/**
|
|
180
|
-
* Full-bleed surface swapped in over the
|
|
181
|
-
*
|
|
182
|
-
*
|
|
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`).
|
|
183
210
|
*/
|
|
184
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
|
|
185
220
|
/**
|
|
186
221
|
* Docked below the node inspector (v2). Hosts mount the "save as preset"
|
|
187
222
|
* affordance here so it reads as part of the inspector surface and shows
|
|
@@ -243,6 +278,9 @@ export interface EditorProps {
|
|
|
243
278
|
sidebarOverlay?: ReactNode
|
|
244
279
|
viewerBanner?: ReactNode
|
|
245
280
|
|
|
281
|
+
/** Shown in the preview header. */
|
|
282
|
+
projectName?: string | null
|
|
283
|
+
|
|
246
284
|
// Panel config (passed through to sidebar panels — v1 only)
|
|
247
285
|
settingsPanelProps?: SettingsPanelProps
|
|
248
286
|
sitePanelProps?: SitePanelProps
|
|
@@ -422,7 +460,7 @@ type CameraControlHint = {
|
|
|
422
460
|
alternativeKeys?: ShortcutKey[]
|
|
423
461
|
}
|
|
424
462
|
|
|
425
|
-
const
|
|
463
|
+
const CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
426
464
|
{
|
|
427
465
|
action: 'Pan',
|
|
428
466
|
keys: [{ value: 'Space' }, { value: 'Left click' }],
|
|
@@ -432,12 +470,6 @@ const EDITOR_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
432
470
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
433
471
|
]
|
|
434
472
|
|
|
435
|
-
const PREVIEW_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
436
|
-
{ action: 'Pan', keys: [{ value: 'Left click' }] },
|
|
437
|
-
{ action: 'Rotate', keys: [{ value: 'Right click' }] },
|
|
438
|
-
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
439
|
-
]
|
|
440
|
-
|
|
441
473
|
const CAMERA_SHORTCUT_KEY_META: Record<string, { icon?: string; label: string; text?: string }> = {
|
|
442
474
|
'Left click': {
|
|
443
475
|
icon: 'ph:mouse-left-click-fill',
|
|
@@ -544,14 +576,8 @@ function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
|
|
|
544
576
|
)
|
|
545
577
|
}
|
|
546
578
|
|
|
547
|
-
function ViewerCanvasControlsHint({
|
|
548
|
-
|
|
549
|
-
onDismiss,
|
|
550
|
-
}: {
|
|
551
|
-
isPreviewMode: boolean
|
|
552
|
-
onDismiss: () => void
|
|
553
|
-
}) {
|
|
554
|
-
const all = isPreviewMode ? PREVIEW_CAMERA_CONTROL_HINTS : EDITOR_CAMERA_CONTROL_HINTS
|
|
579
|
+
function ViewerCanvasControlsHint({ onDismiss }: { onDismiss: () => void }) {
|
|
580
|
+
const all = CAMERA_CONTROL_HINTS
|
|
555
581
|
// A host teaching one gesture at a time narrows this to the one it is asking
|
|
556
582
|
// for, and to nothing once it is done. Null — the default — is all of them.
|
|
557
583
|
const focus = useCameraHintFocus((state) => state.actions)
|
|
@@ -680,26 +706,31 @@ function PaintCursorBadge({
|
|
|
680
706
|
swatchColor,
|
|
681
707
|
swatchImageUrl,
|
|
682
708
|
isEraser,
|
|
709
|
+
isPicker = false,
|
|
683
710
|
}: {
|
|
684
711
|
position: { x: number; y: number }
|
|
685
712
|
state: PaintCursorBadgeState
|
|
686
713
|
swatchColor: string
|
|
687
714
|
swatchImageUrl?: string
|
|
688
715
|
isEraser: boolean
|
|
716
|
+
/** The eyedropper: the swatch is what a click would take. */
|
|
717
|
+
isPicker?: boolean
|
|
689
718
|
}) {
|
|
690
719
|
const accentColor =
|
|
691
720
|
state === 'ready'
|
|
692
721
|
? isEraser
|
|
693
|
-
?
|
|
722
|
+
? ERASE_CURSOR_BADGE_COLOR
|
|
694
723
|
: swatchColor
|
|
695
724
|
: PAINT_CURSOR_BADGE_DISABLED_COLOR
|
|
696
|
-
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
|
|
697
726
|
const lineHeight = 18
|
|
698
727
|
|
|
699
728
|
return (
|
|
700
729
|
<div
|
|
701
730
|
aria-hidden="true"
|
|
702
731
|
className="pointer-events-none absolute z-40"
|
|
732
|
+
data-paint-cursor-state={state}
|
|
733
|
+
data-paint-cursor-tool={isEraser ? 'erase' : isPicker ? 'pick' : 'paint'}
|
|
703
734
|
style={{
|
|
704
735
|
left: position.x,
|
|
705
736
|
top: position.y,
|
|
@@ -721,29 +752,32 @@ function PaintCursorBadge({
|
|
|
721
752
|
transform: `translate(-50%, calc(-100% - ${lineHeight}px))`,
|
|
722
753
|
}}
|
|
723
754
|
>
|
|
724
|
-
{
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
opacity: iconOpacity
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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 : (
|
|
747
781
|
<span
|
|
748
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)]"
|
|
749
783
|
style={{
|
|
@@ -751,6 +785,7 @@ function PaintCursorBadge({
|
|
|
751
785
|
backgroundImage: swatchImageUrl
|
|
752
786
|
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
753
787
|
: undefined,
|
|
788
|
+
opacity: state === 'aim' ? 0.55 : 1,
|
|
754
789
|
}}
|
|
755
790
|
/>
|
|
756
791
|
)
|
|
@@ -815,16 +850,20 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
815
850
|
<SceneEnvironment />
|
|
816
851
|
{!(isFirstPersonMode || isStudioMode || isCaptureMode) && <SelectionManager />}
|
|
817
852
|
{!(noEditing || isXRMode) && <BoxSelectTool />}
|
|
853
|
+
{!noEditing && <EditorHandleHitPriority />}
|
|
818
854
|
{!noEditing && <NodeArrowHandles />}
|
|
855
|
+
{!noEditing && <FootprintHeightHandle />}
|
|
819
856
|
{!noEditing && <GroupRotateHandle />}
|
|
820
857
|
{!noEditing && <GroupSelectionBox3D />}
|
|
821
858
|
{!noEditing && <WallOpeningHighlights />}
|
|
822
859
|
{!noEditing && <SlabHoleHighlights />}
|
|
823
860
|
{!noEditing && <WallMoveSideHandles />}
|
|
861
|
+
{!noEditing && <WallRegionHandles />}
|
|
824
862
|
{!noEditing && <FenceTangentLines3D />}
|
|
825
863
|
{!(noEditing || isXRMode) && <FloatingActionMenu />}
|
|
826
864
|
{!(noEditing || isXRMode) && <GroupFloatingActionMenu />}
|
|
827
865
|
{!(noEditing || isXRMode) && <FloatingBuildingActionMenu />}
|
|
866
|
+
{!(noEditing || isXRMode) && <RoomLabels3D />}
|
|
828
867
|
{!(isFirstPersonMode || isXRMode) && <WallMeasurementLabel />}
|
|
829
868
|
<ExportManager />
|
|
830
869
|
{isFirstPersonMode ? <ViewerZoneSystem /> : <ZoneSystem />}
|
|
@@ -839,8 +878,8 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
839
878
|
{isCaptureMode && !isXRMode && <CaptureCameraRig />}
|
|
840
879
|
{!isXRMode && <CustomCameraControls />}
|
|
841
880
|
{!isXRMode && <ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />}
|
|
881
|
+
{!isXRMode && <VectorEdgeExtractor />}
|
|
842
882
|
{!(isFirstPersonMode || isXRMode) && <SiteEdgeLabels />}
|
|
843
|
-
<InteractiveSystem />
|
|
844
883
|
{presentationsReady ? <ViewerPresentations /> : null}
|
|
845
884
|
{!noEditing && viewerSceneSlot}
|
|
846
885
|
</>
|
|
@@ -933,7 +972,10 @@ function PaintCursorLayer({
|
|
|
933
972
|
}) {
|
|
934
973
|
const mode = useEditor((s) => s.mode)
|
|
935
974
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
936
|
-
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'
|
|
937
979
|
const paintHover = useEditor((s) => s.paintHover)
|
|
938
980
|
const sceneMaterials = useScene((s) => s.materials)
|
|
939
981
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
|
@@ -985,14 +1027,26 @@ function PaintCursorLayer({
|
|
|
985
1027
|
}
|
|
986
1028
|
}, [active, containerRef])
|
|
987
1029
|
|
|
988
|
-
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()
|
|
989
1034
|
const badgeState: PaintCursorBadgeState = !hasPaint
|
|
990
1035
|
? 'empty'
|
|
991
|
-
:
|
|
992
|
-
?
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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)
|
|
996
1050
|
|
|
997
1051
|
if (!active || !position) return null
|
|
998
1052
|
|
|
@@ -1003,6 +1057,7 @@ function PaintCursorLayer({
|
|
|
1003
1057
|
>
|
|
1004
1058
|
<PaintCursorBadge
|
|
1005
1059
|
isEraser={paintEraser}
|
|
1060
|
+
isPicker={picking}
|
|
1006
1061
|
position={{ x: 0, y: 0 }}
|
|
1007
1062
|
state={badgeState}
|
|
1008
1063
|
swatchColor={swatchColor}
|
|
@@ -1012,8 +1067,8 @@ function PaintCursorLayer({
|
|
|
1012
1067
|
)
|
|
1013
1068
|
}
|
|
1014
1069
|
|
|
1015
|
-
// ── Viewer canvas: memoized, subscribes to
|
|
1016
|
-
// 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.
|
|
1017
1072
|
|
|
1018
1073
|
const ViewerCanvas = memo(function ViewerCanvas({
|
|
1019
1074
|
isVersionPreviewMode,
|
|
@@ -1046,12 +1101,20 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1046
1101
|
disablePostFx?: boolean
|
|
1047
1102
|
immersive?: ViewerImmersiveSession
|
|
1048
1103
|
}) {
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
const setFloorplanPaneRatio = useEditor((s) => s.setFloorplanPaneRatio)
|
|
1104
|
+
const layout = useActiveViewLayout()
|
|
1105
|
+
const views = useEditorViews()
|
|
1052
1106
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1053
1107
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
1054
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)
|
|
1055
1118
|
const presetIsolation = useEditor((s) =>
|
|
1056
1119
|
s.captureMode.mode === 'preset' ? s.captureMode.isolated : null,
|
|
1057
1120
|
)
|
|
@@ -1069,25 +1132,24 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1069
1132
|
setViewerAreaEl(el)
|
|
1070
1133
|
}, [])
|
|
1071
1134
|
const viewer3dRef = useRef<HTMLDivElement>(null)
|
|
1072
|
-
const
|
|
1135
|
+
const isResizingPanes = useRef(false)
|
|
1073
1136
|
|
|
1074
|
-
const
|
|
1137
|
+
const handlePaneDividerDown = useCallback((e: React.PointerEvent) => {
|
|
1075
1138
|
e.preventDefault()
|
|
1076
|
-
|
|
1139
|
+
isResizingPanes.current = true
|
|
1077
1140
|
document.body.style.cursor = 'col-resize'
|
|
1078
1141
|
document.body.style.userSelect = 'none'
|
|
1079
1142
|
}, [])
|
|
1080
1143
|
|
|
1081
1144
|
useEffect(() => {
|
|
1082
1145
|
const handlePointerMove = (e: PointerEvent) => {
|
|
1083
|
-
if (!
|
|
1146
|
+
if (!isResizingPanes.current) return
|
|
1084
1147
|
if (!viewerAreaRef.current) return
|
|
1085
1148
|
const rect = viewerAreaRef.current.getBoundingClientRect()
|
|
1086
|
-
|
|
1087
|
-
setFloorplanPaneRatio(Math.max(0.15, Math.min(0.85, newRatio)))
|
|
1149
|
+
useEditor.getState().setViewPaneRatio((e.clientX - rect.left) / rect.width)
|
|
1088
1150
|
}
|
|
1089
1151
|
const handlePointerUp = () => {
|
|
1090
|
-
|
|
1152
|
+
isResizingPanes.current = false
|
|
1091
1153
|
document.body.style.cursor = ''
|
|
1092
1154
|
document.body.style.userSelect = ''
|
|
1093
1155
|
}
|
|
@@ -1097,7 +1159,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1097
1159
|
window.removeEventListener('pointermove', handlePointerMove)
|
|
1098
1160
|
window.removeEventListener('pointerup', handlePointerUp)
|
|
1099
1161
|
}
|
|
1100
|
-
}, [
|
|
1162
|
+
}, [])
|
|
1101
1163
|
|
|
1102
1164
|
useEffect(() => {
|
|
1103
1165
|
setIsCameraControlsHintVisible(!readCameraControlsHintDismissed())
|
|
@@ -1108,91 +1170,128 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1108
1170
|
writeCameraControlsHintDismissed(true)
|
|
1109
1171
|
}, [])
|
|
1110
1172
|
|
|
1111
|
-
const
|
|
1112
|
-
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
|
+
}
|
|
1113
1187
|
|
|
1114
1188
|
return (
|
|
1115
1189
|
<ErrorBoundary fallback={<EditorSceneCrashFallback />}>
|
|
1116
1190
|
{/* `relative` so the floorplan compass (portaled here to stay visible in
|
|
1117
|
-
|
|
1118
|
-
<div className="relative
|
|
1191
|
+
every layout) can anchor to this container's bottom-left. */}
|
|
1192
|
+
<div className="relative h-full" ref={setViewerAreaNode}>
|
|
1119
1193
|
<QuickMeasurementHud />
|
|
1120
1194
|
<DeleteConfirmationDialog />
|
|
1121
|
-
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
1122
1195
|
<div
|
|
1123
|
-
className="
|
|
1196
|
+
className="absolute inset-0 grid"
|
|
1197
|
+
data-view-split={layout.split || undefined}
|
|
1124
1198
|
style={{
|
|
1125
|
-
|
|
1126
|
-
|
|
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)',
|
|
1127
1203
|
}}
|
|
1128
1204
|
>
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
>
|
|
1137
|
-
<
|
|
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} />
|
|
1138
1214
|
</div>
|
|
1139
|
-
|
|
1140
|
-
</div>
|
|
1215
|
+
</div>
|
|
1141
1216
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
containerRef={viewer3dRef}
|
|
1151
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1152
|
-
/>
|
|
1153
|
-
<PaintCursorLayer
|
|
1154
|
-
containerRef={viewer3dRef}
|
|
1155
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1156
|
-
/>
|
|
1157
|
-
{!showLoader && isCameraControlsHintVisible && !isFirstPersonMode ? (
|
|
1158
|
-
<ViewerCanvasControlsHint
|
|
1159
|
-
isPreviewMode={isPreviewMode}
|
|
1160
|
-
onDismiss={dismissCameraControlsHint}
|
|
1161
|
-
/>
|
|
1162
|
-
) : null}
|
|
1163
|
-
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
1164
|
-
<Viewer
|
|
1165
|
-
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1166
|
-
disablePostFx={disablePostFx}
|
|
1167
|
-
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1168
|
-
isolate={presetIsolation}
|
|
1169
|
-
// Preset captures isolate one subtree and keep the exterior transparent.
|
|
1170
|
-
// Other modes retain the viewer's configured background policy.
|
|
1171
|
-
transparent={presetIsolation === null ? undefined : true}
|
|
1172
|
-
onSceneReadyChange={onSceneReadyChange}
|
|
1173
|
-
renderContext="editor"
|
|
1174
|
-
renderPaused={!show3d && !showLoader}
|
|
1175
|
-
sceneReadyKey={sceneReadyKey}
|
|
1176
|
-
immersive={immersive}
|
|
1177
|
-
// Walk/drone framing during snapshot capture is camera-only: the
|
|
1178
|
-
// viewer's default selection manager would hover-highlight whatever
|
|
1179
|
-
// the cursor crosses, which orbit capture never does.
|
|
1180
|
-
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)}
|
|
1181
1225
|
>
|
|
1182
|
-
<
|
|
1183
|
-
|
|
1184
|
-
isLoading={showLoader}
|
|
1185
|
-
isXRMode={immersive != null}
|
|
1186
|
-
isStudioMode={isStudioMode}
|
|
1226
|
+
<DeleteCursorLayer
|
|
1227
|
+
containerRef={viewer3dRef}
|
|
1187
1228
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1188
|
-
onThumbnailCapture={onThumbnailCapture}
|
|
1189
|
-
presentationsReady={presentationsReady}
|
|
1190
|
-
viewerSceneSlot={viewerSceneSlot}
|
|
1191
1229
|
/>
|
|
1192
|
-
|
|
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
|
+
})}
|
|
1193
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
|
+
)}
|
|
1194
1294
|
</div>
|
|
1195
|
-
{!(showLoader || isVersionPreviewMode) && <ZoneLabelEditorSystem />}
|
|
1196
1295
|
</ErrorBoundary>
|
|
1197
1296
|
)
|
|
1198
1297
|
})
|
|
@@ -1201,12 +1300,14 @@ function PreviewStage({
|
|
|
1201
1300
|
isFirstPersonMode,
|
|
1202
1301
|
mode,
|
|
1203
1302
|
onModeChange,
|
|
1303
|
+
projectName,
|
|
1204
1304
|
showLoader,
|
|
1205
1305
|
viewerContent,
|
|
1206
1306
|
}: {
|
|
1207
1307
|
isFirstPersonMode: boolean
|
|
1208
1308
|
mode: ViewerStageMode
|
|
1209
1309
|
onModeChange: (mode: ViewerStageMode) => void
|
|
1310
|
+
projectName?: string | null
|
|
1210
1311
|
showLoader: boolean
|
|
1211
1312
|
viewerContent: ReactNode
|
|
1212
1313
|
}) {
|
|
@@ -1222,6 +1323,7 @@ function PreviewStage({
|
|
|
1222
1323
|
[onModeChange],
|
|
1223
1324
|
)
|
|
1224
1325
|
|
|
1326
|
+
useViewerFloorDisplay()
|
|
1225
1327
|
const stageMode = isFirstPersonMode || !hasFloorplan ? '3d' : mode
|
|
1226
1328
|
const stageModes = hasFloorplan && !isFirstPersonMode ? undefined : (['3d'] as const)
|
|
1227
1329
|
|
|
@@ -1233,9 +1335,11 @@ function PreviewStage({
|
|
|
1233
1335
|
<ViewerOverlay
|
|
1234
1336
|
hideBottomBar={stageMode !== '3d'}
|
|
1235
1337
|
onBack={() => useEditor.getState().setPreviewMode(false)}
|
|
1338
|
+
projectName={projectName}
|
|
1236
1339
|
/>
|
|
1237
1340
|
)}
|
|
1238
1341
|
|
|
1342
|
+
{stageMode === '3d' && !isFirstPersonMode ? <ViewerHoverLabel /> : null}
|
|
1239
1343
|
<ViewerStage
|
|
1240
1344
|
className="absolute inset-0"
|
|
1241
1345
|
mode={stageMode}
|
|
@@ -1261,6 +1365,8 @@ function EditorContent({
|
|
|
1261
1365
|
viewerToolbarLeft,
|
|
1262
1366
|
viewerToolbarRight,
|
|
1263
1367
|
stageOverlay,
|
|
1368
|
+
sceneOverlay,
|
|
1369
|
+
viewBar = true,
|
|
1264
1370
|
inspectorFooter,
|
|
1265
1371
|
multiSelectionFooter,
|
|
1266
1372
|
viewerSceneSlot,
|
|
@@ -1280,6 +1386,7 @@ function EditorContent({
|
|
|
1280
1386
|
immersive,
|
|
1281
1387
|
sidebarOverlay,
|
|
1282
1388
|
viewerBanner,
|
|
1389
|
+
projectName,
|
|
1283
1390
|
settingsPanelProps,
|
|
1284
1391
|
sitePanelProps,
|
|
1285
1392
|
extraSidebarPanels,
|
|
@@ -1311,8 +1418,9 @@ function EditorContent({
|
|
|
1311
1418
|
}, [presentationProjectId])
|
|
1312
1419
|
|
|
1313
1420
|
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode || isStudioMode })
|
|
1421
|
+
useGestureLifecycleOwner()
|
|
1314
1422
|
|
|
1315
|
-
const {
|
|
1423
|
+
const { beginSceneLoad, completeSceneLoad, saveNow } = useAutoSave({
|
|
1316
1424
|
guardAgainstSceneWipe,
|
|
1317
1425
|
onSave,
|
|
1318
1426
|
onDirty,
|
|
@@ -1352,6 +1460,7 @@ function EditorContent({
|
|
|
1352
1460
|
const teardown = initializeEditorRuntime()
|
|
1353
1461
|
return teardown
|
|
1354
1462
|
}, [])
|
|
1463
|
+
useCeilingEditSessionOwner()
|
|
1355
1464
|
|
|
1356
1465
|
useEffect(() => {
|
|
1357
1466
|
void useEditor.persist.rehydrate()
|
|
@@ -1361,6 +1470,7 @@ function EditorContent({
|
|
|
1361
1470
|
useEffect(() => {
|
|
1362
1471
|
useViewer.getState().setProjectId(projectId ?? null)
|
|
1363
1472
|
useFloorplanMode.getState().setProjectId(projectId ?? null)
|
|
1473
|
+
resetPaintMode()
|
|
1364
1474
|
|
|
1365
1475
|
return () => {
|
|
1366
1476
|
useViewer.getState().setProjectId(null)
|
|
@@ -1373,7 +1483,7 @@ function EditorContent({
|
|
|
1373
1483
|
let cancelled = false
|
|
1374
1484
|
|
|
1375
1485
|
async function load(attempt: number) {
|
|
1376
|
-
|
|
1486
|
+
beginSceneLoad()
|
|
1377
1487
|
setSceneLoadError(null)
|
|
1378
1488
|
setHasLoadedInitialScene(false)
|
|
1379
1489
|
setIsViewerSceneReady(false)
|
|
@@ -1388,6 +1498,9 @@ function EditorContent({
|
|
|
1388
1498
|
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
|
|
1389
1499
|
if (!cancelled && attempt === sceneLoadAttempt) {
|
|
1390
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()
|
|
1391
1504
|
setIsViewerSceneReady(false)
|
|
1392
1505
|
setSceneReadyKey((key) => key + 1)
|
|
1393
1506
|
}
|
|
@@ -1404,9 +1517,11 @@ function EditorContent({
|
|
|
1404
1517
|
setIsSceneLoading(false)
|
|
1405
1518
|
if (!failed) {
|
|
1406
1519
|
setHasLoadedInitialScene(true)
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
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' })
|
|
1410
1525
|
}
|
|
1411
1526
|
}
|
|
1412
1527
|
}
|
|
@@ -1417,7 +1532,7 @@ function EditorContent({
|
|
|
1417
1532
|
return () => {
|
|
1418
1533
|
cancelled = true
|
|
1419
1534
|
}
|
|
1420
|
-
}, [onLoad,
|
|
1535
|
+
}, [onLoad, beginSceneLoad, completeSceneLoad, sceneLoadAttempt])
|
|
1421
1536
|
|
|
1422
1537
|
const retrySceneLoad = useCallback(() => {
|
|
1423
1538
|
setSceneLoadAttempt((attempt) => attempt + 1)
|
|
@@ -1486,10 +1601,13 @@ function EditorContent({
|
|
|
1486
1601
|
|
|
1487
1602
|
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
|
1488
1603
|
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
1604
|
+
const firstPersonWasPreviewRef = useRef(isPreviewMode)
|
|
1489
1605
|
|
|
1490
1606
|
useEffect(() => {
|
|
1491
1607
|
const wasFirstPersonMode = wasFirstPersonModeRef.current
|
|
1608
|
+
const wasPreviewMode = firstPersonWasPreviewRef.current
|
|
1492
1609
|
wasFirstPersonModeRef.current = isFirstPersonMode
|
|
1610
|
+
firstPersonWasPreviewRef.current = isPreviewMode
|
|
1493
1611
|
|
|
1494
1612
|
if (isFirstPersonMode && !wasFirstPersonMode) {
|
|
1495
1613
|
const viewer = useViewer.getState()
|
|
@@ -1508,7 +1626,8 @@ function EditorContent({
|
|
|
1508
1626
|
firstPersonPreviousLevelRef.current = null
|
|
1509
1627
|
viewer.setWalkthroughMode(false)
|
|
1510
1628
|
|
|
1511
|
-
|
|
1629
|
+
// Leaving Preview has already restored the editor's complete selection.
|
|
1630
|
+
if ((wasPreviewMode && !isPreviewMode) || !previousLevelId) return
|
|
1512
1631
|
|
|
1513
1632
|
const previousLevelNode = useScene.getState().nodes[previousLevelId]
|
|
1514
1633
|
if (previousLevelNode?.type === 'level') {
|
|
@@ -1518,7 +1637,7 @@ function EditorContent({
|
|
|
1518
1637
|
selectedIds: [],
|
|
1519
1638
|
})
|
|
1520
1639
|
}
|
|
1521
|
-
}, [isFirstPersonMode])
|
|
1640
|
+
}, [isFirstPersonMode, isPreviewMode])
|
|
1522
1641
|
|
|
1523
1642
|
const previewViewerContent = (
|
|
1524
1643
|
<Viewer
|
|
@@ -1526,16 +1645,18 @@ function EditorContent({
|
|
|
1526
1645
|
disablePostFx={disablePostFx}
|
|
1527
1646
|
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1528
1647
|
renderContext="editor"
|
|
1529
|
-
selectionManager="
|
|
1648
|
+
selectionManager="custom"
|
|
1530
1649
|
>
|
|
1531
1650
|
<ExportManager />
|
|
1532
|
-
<
|
|
1651
|
+
<ViewerSelectionManager />
|
|
1652
|
+
<ParametricViewerRooms />
|
|
1533
1653
|
<CeilingSystem />
|
|
1534
1654
|
<RoofEditSystem />
|
|
1535
1655
|
<StairEditSystem />
|
|
1536
1656
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
1537
1657
|
<CustomCameraControls />
|
|
1538
1658
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
1659
|
+
<VectorEdgeExtractor />
|
|
1539
1660
|
<InteractiveSystem />
|
|
1540
1661
|
{presentationsReady ? <ViewerPresentations /> : null}
|
|
1541
1662
|
</Viewer>
|
|
@@ -1605,6 +1726,10 @@ function EditorContent({
|
|
|
1605
1726
|
mobileDefaultSnap: 0.5,
|
|
1606
1727
|
mobileIcon: p.icon,
|
|
1607
1728
|
icon: p.icon,
|
|
1729
|
+
noPanel: p.noPanel,
|
|
1730
|
+
onSelect: p.onSelect,
|
|
1731
|
+
stageActive: p.stageActive,
|
|
1732
|
+
onDeselect: p.onDeselect,
|
|
1608
1733
|
})),
|
|
1609
1734
|
]
|
|
1610
1735
|
|
|
@@ -1626,6 +1751,7 @@ function EditorContent({
|
|
|
1626
1751
|
isFirstPersonMode={isFirstPersonMode}
|
|
1627
1752
|
mode={previewStageMode}
|
|
1628
1753
|
onModeChange={setPreviewStageMode}
|
|
1754
|
+
projectName={projectName}
|
|
1629
1755
|
showLoader={visibleLoader}
|
|
1630
1756
|
viewerContent={previewViewerContent}
|
|
1631
1757
|
/>
|
|
@@ -1635,25 +1761,27 @@ function EditorContent({
|
|
|
1635
1761
|
navbarSlot={navbarSlot}
|
|
1636
1762
|
overlays={
|
|
1637
1763
|
<>
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
<
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
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
|
+
/>
|
|
1657
1785
|
{/* Capture mode drives walk / drone from its own overlay, which
|
|
1658
1786
|
owns the framing chrome — the walkthrough HUD would both
|
|
1659
1787
|
clutter the frame and offer a second, conflicting exit. */}
|
|
@@ -1670,6 +1798,7 @@ function EditorContent({
|
|
|
1670
1798
|
sidebarOverlay={sidebarOverlay}
|
|
1671
1799
|
sidebarTabs={tabBarTabs}
|
|
1672
1800
|
stageOverlay={stageOverlay}
|
|
1801
|
+
viewBar={viewBar}
|
|
1673
1802
|
viewerContent={viewerCanvas}
|
|
1674
1803
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
1675
1804
|
viewerToolbarRight={viewerToolbarRight}
|
|
@@ -1706,6 +1835,7 @@ function EditorContent({
|
|
|
1706
1835
|
isFirstPersonMode={isFirstPersonMode}
|
|
1707
1836
|
mode={previewStageMode}
|
|
1708
1837
|
onModeChange={setPreviewStageMode}
|
|
1838
|
+
projectName={projectName}
|
|
1709
1839
|
showLoader={visibleLoader}
|
|
1710
1840
|
viewerContent={previewViewerContent}
|
|
1711
1841
|
/>
|
|
@@ -1731,12 +1861,7 @@ function EditorContent({
|
|
|
1731
1861
|
<div className="pointer-events-auto">
|
|
1732
1862
|
<ActionMenu />
|
|
1733
1863
|
</div>
|
|
1734
|
-
<
|
|
1735
|
-
<PanelManager />
|
|
1736
|
-
</div>
|
|
1737
|
-
<div className="pointer-events-auto">
|
|
1738
|
-
<HelperManager />
|
|
1739
|
-
</div>
|
|
1864
|
+
<RightStack helper={<HelperManager />} inspector={<PanelManager />} />
|
|
1740
1865
|
<RiserDiagramPanel />
|
|
1741
1866
|
{isFirstPersonMode && (
|
|
1742
1867
|
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|