@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
|
@@ -1,29 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clearSceneHistory,
|
|
3
|
+
DEFAULT_LEVEL_HEIGHT,
|
|
3
4
|
emitter,
|
|
5
|
+
getArtifactStore,
|
|
6
|
+
getLevelDisplayName,
|
|
7
|
+
LevelNode,
|
|
8
|
+
type AnyNodeId,
|
|
9
|
+
type BuildingNode,
|
|
4
10
|
isNodeKindEnabled,
|
|
5
11
|
nodeRegistry,
|
|
12
|
+
type ParsedBuildJson,
|
|
6
13
|
useRegistryVersion,
|
|
7
14
|
useScene,
|
|
8
|
-
type ParsedBuildJson,
|
|
9
15
|
validateBuildJson,
|
|
10
16
|
} from '@pascal-app/core'
|
|
11
17
|
import { useViewer, viewerPresentationRegistry } from '@pascal-app/viewer'
|
|
12
18
|
import { TreeView, VisualJson } from '@visual-json/react'
|
|
13
19
|
import {
|
|
20
|
+
ArrowDown,
|
|
21
|
+
ArrowUp,
|
|
14
22
|
Camera,
|
|
15
23
|
Check,
|
|
16
|
-
ChevronDown,
|
|
17
24
|
Copy,
|
|
18
25
|
Download,
|
|
26
|
+
Layers,
|
|
27
|
+
ListTree,
|
|
19
28
|
Map as MapIcon,
|
|
20
29
|
Save,
|
|
30
|
+
ChevronDown,
|
|
21
31
|
Send,
|
|
22
32
|
Trash2,
|
|
23
33
|
Upload,
|
|
24
34
|
} from 'lucide-react'
|
|
25
35
|
import {
|
|
26
36
|
type KeyboardEvent,
|
|
37
|
+
type ReactNode,
|
|
27
38
|
type SyntheticEvent,
|
|
28
39
|
useCallback,
|
|
29
40
|
useEffect,
|
|
@@ -33,6 +44,7 @@ import {
|
|
|
33
44
|
useState,
|
|
34
45
|
useSyncExternalStore,
|
|
35
46
|
} from 'react'
|
|
47
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
36
48
|
import {
|
|
37
49
|
exportFloorplanPdf,
|
|
38
50
|
type FloorplanExportScope,
|
|
@@ -52,9 +64,13 @@ import {
|
|
|
52
64
|
} from './../../../../../components/ui/primitives/dialog'
|
|
53
65
|
import { Input } from './../../../../../components/ui/primitives/input'
|
|
54
66
|
import { Switch } from './../../../../../components/ui/primitives/switch'
|
|
67
|
+
import { cn } from './../../../../../lib/utils'
|
|
68
|
+
import { refusedObjectsNotice } from './../../../../../components/editor/group-actions'
|
|
69
|
+
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
70
|
+
import { bringBuildArtifacts } from './../../../../../lib/scene-clipboard'
|
|
55
71
|
import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
|
|
56
|
-
import { type SendToAppStep, useSendToApp } from './../../../../../store/use-send-to-app'
|
|
57
72
|
import useFloorplanMode from './../../../../../store/use-floorplan-mode'
|
|
73
|
+
import { type SendToAppStep, useSendToApp } from './../../../../../store/use-send-to-app'
|
|
58
74
|
import { AudioSettingsDialog } from './audio-settings-dialog'
|
|
59
75
|
import { KeyboardShortcutsDialog } from './keyboard-shortcuts-dialog'
|
|
60
76
|
import { LoadBuildDialog, type PendingImport } from './load-build-dialog'
|
|
@@ -88,6 +104,7 @@ const MODEL_EXPORT_FORMATS = [
|
|
|
88
104
|
{ format: 'usdz', label: 'USDZ' },
|
|
89
105
|
{ format: 'stl', label: 'STL' },
|
|
90
106
|
{ format: 'obj', label: 'OBJ' },
|
|
107
|
+
{ format: 'ifc', label: 'IFC' },
|
|
91
108
|
] as const
|
|
92
109
|
|
|
93
110
|
type ModelExportFormat = (typeof MODEL_EXPORT_FORMATS)[number]['format']
|
|
@@ -223,7 +240,69 @@ export interface ProjectVisibility {
|
|
|
223
240
|
showGuidesPublic: boolean
|
|
224
241
|
}
|
|
225
242
|
|
|
243
|
+
function SettingsSection({
|
|
244
|
+
children,
|
|
245
|
+
description,
|
|
246
|
+
title,
|
|
247
|
+
tone = 'default',
|
|
248
|
+
}: {
|
|
249
|
+
children: ReactNode
|
|
250
|
+
description?: string
|
|
251
|
+
title: string
|
|
252
|
+
tone?: 'default' | 'destructive'
|
|
253
|
+
}) {
|
|
254
|
+
const headingId = useId()
|
|
255
|
+
return (
|
|
256
|
+
<section aria-labelledby={headingId} className="space-y-3 py-5 first:pt-0 last:pb-0">
|
|
257
|
+
<div className="space-y-0.5">
|
|
258
|
+
<h3
|
|
259
|
+
className={cn(
|
|
260
|
+
'font-semibold text-sm',
|
|
261
|
+
tone === 'destructive' ? 'text-destructive' : 'text-foreground',
|
|
262
|
+
)}
|
|
263
|
+
id={headingId}
|
|
264
|
+
>
|
|
265
|
+
{title}
|
|
266
|
+
</h3>
|
|
267
|
+
{description ? <p className="text-muted-foreground text-xs">{description}</p> : null}
|
|
268
|
+
</div>
|
|
269
|
+
{children}
|
|
270
|
+
</section>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function SettingsSubheading({ children }: { children: ReactNode }) {
|
|
275
|
+
return <h4 className="font-medium text-muted-foreground text-xs">{children}</h4>
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function SettingsSwitchRow({
|
|
279
|
+
checked,
|
|
280
|
+
description,
|
|
281
|
+
label,
|
|
282
|
+
onCheckedChange,
|
|
283
|
+
}: {
|
|
284
|
+
checked: boolean
|
|
285
|
+
description: string
|
|
286
|
+
label: string
|
|
287
|
+
onCheckedChange: (checked: boolean) => void
|
|
288
|
+
}) {
|
|
289
|
+
const id = useId()
|
|
290
|
+
return (
|
|
291
|
+
<div className="flex items-center justify-between gap-4 py-1">
|
|
292
|
+
<div className="min-w-0 space-y-0.5">
|
|
293
|
+
<label className="block font-medium text-sm" htmlFor={id}>
|
|
294
|
+
{label}
|
|
295
|
+
</label>
|
|
296
|
+
<p className="text-muted-foreground text-xs">{description}</p>
|
|
297
|
+
</div>
|
|
298
|
+
<Switch checked={checked} id={id} onCheckedChange={onCheckedChange} />
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
226
303
|
export interface SettingsPanelProps {
|
|
304
|
+
/** Merged onto the scrolling root so hosts can restyle padding (e.g. inside a dialog). */
|
|
305
|
+
className?: string
|
|
227
306
|
projectId?: string
|
|
228
307
|
/** Shown as the scene name in apps the scene is sent to (Blender collection name). */
|
|
229
308
|
projectName?: string
|
|
@@ -234,7 +313,124 @@ export interface SettingsPanelProps {
|
|
|
234
313
|
) => Promise<void>
|
|
235
314
|
}
|
|
236
315
|
|
|
316
|
+
function FloorSettingsSection() {
|
|
317
|
+
const selectedBuildingId = useViewer((state) => state.selection.buildingId)
|
|
318
|
+
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
|
319
|
+
const setSelection = useViewer((state) => state.setSelection)
|
|
320
|
+
const createNode = useScene((state) => state.createNode)
|
|
321
|
+
const building = useScene((state) => {
|
|
322
|
+
const selected = selectedBuildingId ? state.nodes[selectedBuildingId] : undefined
|
|
323
|
+
if (selected?.type === 'building') return selected as BuildingNode
|
|
324
|
+
|
|
325
|
+
const site = state.rootNodeIds
|
|
326
|
+
.map((nodeId) => state.nodes[nodeId])
|
|
327
|
+
.find((node) => node?.type === 'site')
|
|
328
|
+
if (site?.type !== 'site') return null
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
site.children
|
|
332
|
+
.map((childId) => state.nodes[childId as AnyNodeId])
|
|
333
|
+
.find((node): node is BuildingNode => node?.type === 'building') ?? null
|
|
334
|
+
)
|
|
335
|
+
})
|
|
336
|
+
const levels = useScene(
|
|
337
|
+
useShallow((state) => {
|
|
338
|
+
if (!building) return []
|
|
339
|
+
|
|
340
|
+
return building.children
|
|
341
|
+
.map((childId) => state.nodes[childId as AnyNodeId])
|
|
342
|
+
.filter((node): node is LevelNode => node?.type === 'level')
|
|
343
|
+
.sort((a, b) => b.level - a.level)
|
|
344
|
+
}),
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if (!building) return null
|
|
348
|
+
|
|
349
|
+
const addLevel = (level: number) => {
|
|
350
|
+
const newLevel = LevelNode.parse({
|
|
351
|
+
level,
|
|
352
|
+
height: DEFAULT_LEVEL_HEIGHT,
|
|
353
|
+
children: [],
|
|
354
|
+
parentId: building.id,
|
|
355
|
+
})
|
|
356
|
+
createNode(newLevel, building.id)
|
|
357
|
+
setSelection({ buildingId: building.id, levelId: newLevel.id })
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const addFloor = () =>
|
|
361
|
+
addLevel(levels.length === 0 ? 0 : Math.max(0, ...levels.map((level) => level.level)) + 1)
|
|
362
|
+
const addBasement = () => addLevel(Math.min(0, ...levels.map((level) => level.level)) - 1)
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<div className="space-y-3">
|
|
366
|
+
<label className="font-medium text-muted-foreground text-xs uppercase">Floors</label>
|
|
367
|
+
<div className="grid grid-cols-2 gap-2">
|
|
368
|
+
<Button className="justify-start" onClick={addFloor} size="sm" type="button" variant="outline">
|
|
369
|
+
<ArrowUp className="size-3.5" />
|
|
370
|
+
Add floor
|
|
371
|
+
</Button>
|
|
372
|
+
<Button
|
|
373
|
+
className="justify-start"
|
|
374
|
+
onClick={addBasement}
|
|
375
|
+
size="sm"
|
|
376
|
+
type="button"
|
|
377
|
+
variant="outline"
|
|
378
|
+
>
|
|
379
|
+
<ArrowDown className="size-3.5" />
|
|
380
|
+
Add basement
|
|
381
|
+
</Button>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div className="space-y-1 rounded-md border p-1">
|
|
385
|
+
{levels.length === 0 ? (
|
|
386
|
+
<div className="px-2 py-2 text-muted-foreground text-xs">No floors yet</div>
|
|
387
|
+
) : (
|
|
388
|
+
levels.map((level) => {
|
|
389
|
+
const isSelected = level.id === selectedLevelId
|
|
390
|
+
const canDelete = level.level !== 0
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<div className="flex items-center gap-1" key={level.id}>
|
|
394
|
+
<button
|
|
395
|
+
className={cn(
|
|
396
|
+
'flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-left text-sm transition-colors',
|
|
397
|
+
isSelected
|
|
398
|
+
? 'bg-accent text-foreground'
|
|
399
|
+
: 'text-muted-foreground hover:bg-accent/60 hover:text-foreground',
|
|
400
|
+
)}
|
|
401
|
+
onClick={() => setSelection({ buildingId: building.id, levelId: level.id })}
|
|
402
|
+
type="button"
|
|
403
|
+
>
|
|
404
|
+
<Layers className="size-3.5 shrink-0" />
|
|
405
|
+
<span className="truncate">{getLevelDisplayName(level)}</span>
|
|
406
|
+
<span className="ml-auto shrink-0 text-muted-foreground text-xs">
|
|
407
|
+
{level.level === 0 ? '0' : level.level > 0 ? `+${level.level}` : level.level}
|
|
408
|
+
</span>
|
|
409
|
+
</button>
|
|
410
|
+
<button
|
|
411
|
+
aria-label={`Remove ${getLevelDisplayName(level)}`}
|
|
412
|
+
className="flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive disabled:cursor-not-allowed disabled:opacity-30"
|
|
413
|
+
disabled={!canDelete}
|
|
414
|
+
onClick={() => deleteLevelWithFallbackSelection(level.id)}
|
|
415
|
+
title={canDelete ? 'Remove floor' : 'The ground floor cannot be removed'}
|
|
416
|
+
type="button"
|
|
417
|
+
>
|
|
418
|
+
<Trash2 className="size-3.5" />
|
|
419
|
+
</button>
|
|
420
|
+
</div>
|
|
421
|
+
)
|
|
422
|
+
})
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
<p className="text-muted-foreground text-xs">
|
|
426
|
+
Select a floor to make it active. The ground floor is always kept as the building datum.
|
|
427
|
+
</p>
|
|
428
|
+
</div>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
237
432
|
export function SettingsPanel({
|
|
433
|
+
className,
|
|
238
434
|
projectId,
|
|
239
435
|
projectName,
|
|
240
436
|
projectVisibility,
|
|
@@ -348,7 +544,16 @@ export function SettingsPanel({
|
|
|
348
544
|
// Materials ride along: nodes reference them by `scene:<id>` slot
|
|
349
545
|
// refs, so a save without the table produces a file whose custom
|
|
350
546
|
// finishes revert to defaults on the very Load Build path below.
|
|
351
|
-
|
|
547
|
+
// The project rides along too: a scripted object's artifacts live there, and
|
|
548
|
+
// loading the file into another project copies them over as a paste does.
|
|
549
|
+
const sceneData = {
|
|
550
|
+
nodes,
|
|
551
|
+
rootNodeIds,
|
|
552
|
+
installedPlugins,
|
|
553
|
+
materials,
|
|
554
|
+
collections,
|
|
555
|
+
projectId: useViewer.getState().projectId,
|
|
556
|
+
}
|
|
352
557
|
const json = JSON.stringify(sceneData, null, 2)
|
|
353
558
|
const blob = new Blob([json], { type: 'application/json' })
|
|
354
559
|
const url = URL.createObjectURL(blob)
|
|
@@ -404,7 +609,20 @@ export function SettingsPanel({
|
|
|
404
609
|
e.target.value = ''
|
|
405
610
|
}
|
|
406
611
|
|
|
407
|
-
const handleConfirmImport = (
|
|
612
|
+
const handleConfirmImport = async (file: ParsedBuildJson) => {
|
|
613
|
+
const targetProjectId = useViewer.getState().projectId
|
|
614
|
+
const artifactStore = getArtifactStore()
|
|
615
|
+
const { build: parsed, refusedIds, refusal } = await bringBuildArtifacts(file)
|
|
616
|
+
if (
|
|
617
|
+
targetProjectId !== useViewer.getState().projectId ||
|
|
618
|
+
artifactStore !== getArtifactStore()
|
|
619
|
+
)
|
|
620
|
+
return
|
|
621
|
+
if (refusal) {
|
|
622
|
+
useFloorplanMode
|
|
623
|
+
.getState()
|
|
624
|
+
.showNotice(refusedObjectsNotice(refusedIds.length, refusal, 'loaded'))
|
|
625
|
+
}
|
|
408
626
|
const currentScene = useScene.getState()
|
|
409
627
|
setScene(
|
|
410
628
|
parsed.nodes as Parameters<typeof setScene>[0],
|
|
@@ -434,7 +652,7 @@ export function SettingsPanel({
|
|
|
434
652
|
// old scene (or land on the empty intermediate `unloadScene` state).
|
|
435
653
|
clearSceneHistory()
|
|
436
654
|
resetSelection()
|
|
437
|
-
setPhase('
|
|
655
|
+
setPhase('building')
|
|
438
656
|
selectDefaultBuildingAndLevel()
|
|
439
657
|
}
|
|
440
658
|
|
|
@@ -496,6 +714,7 @@ export function SettingsPanel({
|
|
|
496
714
|
setModelExportWarning(null)
|
|
497
715
|
try {
|
|
498
716
|
const artifact = await modelExport(format, {
|
|
717
|
+
projectName,
|
|
499
718
|
onlyVisible: exportOnlyVisible,
|
|
500
719
|
excludedNodeTypes,
|
|
501
720
|
includedPresentationIds:
|
|
@@ -591,409 +810,433 @@ export function SettingsPanel({
|
|
|
591
810
|
}
|
|
592
811
|
|
|
593
812
|
return (
|
|
594
|
-
<div
|
|
595
|
-
{
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
<div className="font-medium text-sm">Project ID</div>
|
|
599
|
-
<div className="flex items-center gap-2">
|
|
600
|
-
<Input
|
|
601
|
-
aria-label="Project ID"
|
|
602
|
-
className="font-mono text-xs"
|
|
603
|
-
readOnly
|
|
604
|
-
value={projectId}
|
|
605
|
-
/>
|
|
606
|
-
<Button
|
|
607
|
-
aria-label={projectIdCopyState === 'copied' ? 'Project ID copied' : 'Copy project ID'}
|
|
608
|
-
className="rounded-full"
|
|
609
|
-
onClick={() => void handleCopyProjectId()}
|
|
610
|
-
size="sm"
|
|
611
|
-
type="button"
|
|
612
|
-
variant="outline"
|
|
613
|
-
>
|
|
614
|
-
{projectIdCopyState === 'copied' ? (
|
|
615
|
-
<Check className="size-3.5" />
|
|
616
|
-
) : (
|
|
617
|
-
<Copy className="size-3.5" />
|
|
618
|
-
)}
|
|
619
|
-
{projectIdCopyState === 'copied'
|
|
620
|
-
? 'Copied'
|
|
621
|
-
: projectIdCopyState === 'error'
|
|
622
|
-
? 'Try again'
|
|
623
|
-
: 'Copy'}
|
|
624
|
-
</Button>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
813
|
+
<div
|
|
814
|
+
className={cn(
|
|
815
|
+
'subtle-scrollbar @container min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-contain p-3',
|
|
816
|
+
className,
|
|
627
817
|
)}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
818
|
+
>
|
|
819
|
+
<div className="divide-y divide-border/60">
|
|
820
|
+
<section className="space-y-3 py-5 first:pt-0 last:pb-0">
|
|
821
|
+
<FloorSettingsSection />
|
|
822
|
+
</section>
|
|
823
|
+
{projectId && (
|
|
824
|
+
<SettingsSection
|
|
825
|
+
description="Reference this project from the API, MCP tools, or support."
|
|
826
|
+
title="Project"
|
|
827
|
+
>
|
|
828
|
+
<div className="flex items-center gap-2">
|
|
829
|
+
<Input
|
|
830
|
+
aria-label="Project ID"
|
|
831
|
+
className="font-mono text-xs"
|
|
832
|
+
readOnly
|
|
833
|
+
value={projectId}
|
|
834
|
+
/>
|
|
835
|
+
<Button
|
|
836
|
+
aria-label={
|
|
837
|
+
projectIdCopyState === 'copied' ? 'Project ID copied' : 'Copy project ID'
|
|
838
|
+
}
|
|
839
|
+
className="rounded-full"
|
|
840
|
+
onClick={() => void handleCopyProjectId()}
|
|
841
|
+
size="sm"
|
|
842
|
+
type="button"
|
|
843
|
+
variant="outline"
|
|
844
|
+
>
|
|
845
|
+
{projectIdCopyState === 'copied' ? (
|
|
846
|
+
<Check className="size-3.5" />
|
|
847
|
+
) : (
|
|
848
|
+
<Copy className="size-3.5" />
|
|
849
|
+
)}
|
|
850
|
+
{projectIdCopyState === 'copied'
|
|
851
|
+
? 'Copied'
|
|
852
|
+
: projectIdCopyState === 'error'
|
|
853
|
+
? 'Try again'
|
|
854
|
+
: 'Copy'}
|
|
855
|
+
</Button>
|
|
639
856
|
</div>
|
|
640
|
-
|
|
641
|
-
|
|
857
|
+
</SettingsSection>
|
|
858
|
+
)}
|
|
859
|
+
|
|
860
|
+
{/* Sharing (only for cloud projects) */}
|
|
861
|
+
{projectId && !isLocalProject && (
|
|
862
|
+
<SettingsSection description="Control who can open this project." title="Sharing">
|
|
863
|
+
<SettingsSwitchRow
|
|
642
864
|
checked={!(projectVisibility?.isPrivate ?? false)}
|
|
865
|
+
description={
|
|
866
|
+
projectVisibility?.isPrivate
|
|
867
|
+
? 'Only you can view it.'
|
|
868
|
+
: 'Anyone with the link can view it.'
|
|
869
|
+
}
|
|
870
|
+
label="Public project"
|
|
643
871
|
onCheckedChange={(checked) => handleVisibilityChange('isPrivate', !checked)}
|
|
644
872
|
/>
|
|
645
|
-
|
|
646
|
-
<div className="flex items-center justify-between">
|
|
647
|
-
<div>
|
|
648
|
-
<div className="font-medium text-sm">Show 3D Scans</div>
|
|
649
|
-
<div className="text-muted-foreground text-xs">Visible to public viewers</div>
|
|
650
|
-
</div>
|
|
651
|
-
<Switch
|
|
652
|
-
aria-label="Show 3D scans to public viewers"
|
|
873
|
+
<SettingsSwitchRow
|
|
653
874
|
checked={projectVisibility?.showScansPublic ?? true}
|
|
875
|
+
description="Visible in the public viewer."
|
|
876
|
+
label="Show 3D scans"
|
|
654
877
|
onCheckedChange={(checked) => handleVisibilityChange('showScansPublic', checked)}
|
|
655
878
|
/>
|
|
656
|
-
|
|
657
|
-
<div className="flex items-center justify-between">
|
|
658
|
-
<div>
|
|
659
|
-
<div className="font-medium text-sm">Show Floorplans</div>
|
|
660
|
-
<div className="text-muted-foreground text-xs">Visible to public viewers</div>
|
|
661
|
-
</div>
|
|
662
|
-
<Switch
|
|
663
|
-
aria-label="Show floorplans to public viewers"
|
|
879
|
+
<SettingsSwitchRow
|
|
664
880
|
checked={projectVisibility?.showGuidesPublic ?? true}
|
|
881
|
+
description="Visible in the public viewer."
|
|
882
|
+
label="Show floor plans"
|
|
665
883
|
onCheckedChange={(checked) => handleVisibilityChange('showGuidesPublic', checked)}
|
|
666
884
|
/>
|
|
667
|
-
</
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
<label className="font-medium text-sm" htmlFor={visibleOnlySwitchId}>
|
|
703
|
-
Visible nodes only
|
|
704
|
-
</label>
|
|
705
|
-
<div className="text-muted-foreground text-xs">
|
|
706
|
-
Exclude hidden furniture and other hidden scene nodes
|
|
885
|
+
</SettingsSection>
|
|
886
|
+
)}
|
|
887
|
+
|
|
888
|
+
<SettingsSection description="Rendering options for this editor session." title="Viewer">
|
|
889
|
+
<SettingsSwitchRow
|
|
890
|
+
checked={shadows}
|
|
891
|
+
description="Cast shadows from lights."
|
|
892
|
+
label="Shadows"
|
|
893
|
+
onCheckedChange={(checked) => useViewer.getState().setShadows(checked)}
|
|
894
|
+
/>
|
|
895
|
+
</SettingsSection>
|
|
896
|
+
|
|
897
|
+
<SettingsSection description="Download the scene or hand it to another app." title="Export">
|
|
898
|
+
<div className="space-y-3">
|
|
899
|
+
<SettingsSubheading>3D model</SettingsSubheading>
|
|
900
|
+
<details
|
|
901
|
+
className="group"
|
|
902
|
+
onKeyDownCapture={(event) => {
|
|
903
|
+
// Keep Space available to the disclosure and switches, not canvas panning.
|
|
904
|
+
if (event.code === 'Space') event.stopPropagation()
|
|
905
|
+
}}
|
|
906
|
+
>
|
|
907
|
+
<summary className="flex min-h-10 cursor-pointer list-none items-center justify-between rounded-md border px-3 font-medium text-sm focus-visible:outline-2 focus-visible:outline-ring">
|
|
908
|
+
Export options
|
|
909
|
+
<ChevronDown aria-hidden="true" className="size-4 group-open:rotate-180" />
|
|
910
|
+
</summary>
|
|
911
|
+
<div className="space-y-3 pt-3">
|
|
912
|
+
<div className="flex items-center justify-between gap-4 rounded-md border p-3">
|
|
913
|
+
<div className="min-w-0">
|
|
914
|
+
<label className="font-medium text-sm" htmlFor={visibleOnlySwitchId}>
|
|
915
|
+
Visible nodes only
|
|
916
|
+
</label>
|
|
917
|
+
<div className="text-muted-foreground text-xs">
|
|
918
|
+
Exclude hidden furniture and other hidden scene nodes
|
|
919
|
+
</div>
|
|
707
920
|
</div>
|
|
921
|
+
<Switch
|
|
922
|
+
aria-label="Export visible nodes only"
|
|
923
|
+
checked={exportOnlyVisible}
|
|
924
|
+
id={visibleOnlySwitchId}
|
|
925
|
+
onCheckedChange={setExportOnlyVisible}
|
|
926
|
+
/>
|
|
708
927
|
</div>
|
|
709
|
-
<Switch
|
|
710
|
-
aria-label="Export visible nodes only"
|
|
711
|
-
checked={exportOnlyVisible}
|
|
712
|
-
id={visibleOnlySwitchId}
|
|
713
|
-
onCheckedChange={setExportOnlyVisible}
|
|
714
|
-
/>
|
|
715
|
-
</div>
|
|
716
928
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
<p className="text-muted-foreground text-xs">
|
|
720
|
-
Choose which procedural content is baked into model files. GLB and USDZ use the
|
|
721
|
-
textured portable path; STL and OBJ remain geometry-only.
|
|
722
|
-
</p>
|
|
723
|
-
{exportableNodeTypes.length > 0 ? (
|
|
724
|
-
<div className="space-y-2 pt-1">
|
|
725
|
-
{exportableNodeTypes.map(({ type, label, supportsGeometryOnly }, index) => {
|
|
726
|
-
const switchId = `${includeNodeTypeIdPrefix}-${index}`
|
|
727
|
-
return (
|
|
728
|
-
<div className="flex items-center justify-between gap-4" key={type}>
|
|
729
|
-
<label className="min-w-0 font-medium text-sm" htmlFor={switchId}>
|
|
730
|
-
{label}
|
|
731
|
-
{!supportsGeometryOnly && (
|
|
732
|
-
<span className="text-muted-foreground text-xs"> (GLB/USDZ only)</span>
|
|
733
|
-
)}
|
|
734
|
-
</label>
|
|
735
|
-
<Switch
|
|
736
|
-
aria-label={`Include ${label} in model files`}
|
|
737
|
-
checked={!excludedNodeTypes.includes(type)}
|
|
738
|
-
id={switchId}
|
|
739
|
-
onCheckedChange={(included) => handleNodeTypeInclusion(type, included)}
|
|
740
|
-
/>
|
|
741
|
-
</div>
|
|
742
|
-
)
|
|
743
|
-
})}
|
|
744
|
-
</div>
|
|
745
|
-
) : (
|
|
929
|
+
<fieldset className="space-y-2 rounded-md border p-3">
|
|
930
|
+
<legend className="px-1 font-medium text-sm">Include in file</legend>
|
|
746
931
|
<p className="text-muted-foreground text-xs">
|
|
747
|
-
|
|
932
|
+
Choose which procedural content is baked into model files. GLB and USDZ use the
|
|
933
|
+
textured portable path; STL, OBJ and IFC remain geometry-only.
|
|
748
934
|
</p>
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
935
|
+
{exportableNodeTypes.length > 0 ? (
|
|
936
|
+
<div className="space-y-2 pt-1">
|
|
937
|
+
{exportableNodeTypes.map(({ type, label, supportsGeometryOnly }, index) => {
|
|
938
|
+
const switchId = `${includeNodeTypeIdPrefix}-${index}`
|
|
939
|
+
return (
|
|
940
|
+
<div className="flex items-center justify-between gap-4" key={type}>
|
|
941
|
+
<label className="min-w-0 font-medium text-sm" htmlFor={switchId}>
|
|
942
|
+
{label}
|
|
943
|
+
{!supportsGeometryOnly && (
|
|
944
|
+
<span className="text-muted-foreground text-xs">
|
|
945
|
+
{' '}
|
|
946
|
+
(GLB/USDZ only)
|
|
947
|
+
</span>
|
|
948
|
+
)}
|
|
949
|
+
</label>
|
|
950
|
+
<Switch
|
|
951
|
+
aria-label={`Include ${label} in model files`}
|
|
952
|
+
checked={!excludedNodeTypes.includes(type)}
|
|
953
|
+
id={switchId}
|
|
954
|
+
onCheckedChange={(included) =>
|
|
955
|
+
handleNodeTypeInclusion(type, included)
|
|
956
|
+
}
|
|
957
|
+
/>
|
|
958
|
+
</div>
|
|
959
|
+
)
|
|
960
|
+
})}
|
|
961
|
+
</div>
|
|
962
|
+
) : (
|
|
963
|
+
<p className="text-muted-foreground text-xs">
|
|
964
|
+
No optional procedural content is present.
|
|
965
|
+
</p>
|
|
966
|
+
)}
|
|
757
967
|
<p className="text-muted-foreground text-xs">
|
|
758
|
-
|
|
968
|
+
Viewer surroundings are excluded unless selected separately below.
|
|
759
969
|
</p>
|
|
760
|
-
<div className="space-y-2 pt-1">
|
|
761
|
-
{exportablePresentations.map((contribution, index) => {
|
|
762
|
-
const switchId = `${includePresentationIdPrefix}-${index}`
|
|
763
|
-
const label = contribution.staticExport!.label
|
|
764
|
-
return (
|
|
765
|
-
<div
|
|
766
|
-
className="flex items-center justify-between gap-4"
|
|
767
|
-
key={contribution.id}
|
|
768
|
-
>
|
|
769
|
-
<label className="min-w-0 font-medium text-sm" htmlFor={switchId}>
|
|
770
|
-
{label}
|
|
771
|
-
</label>
|
|
772
|
-
<Switch
|
|
773
|
-
aria-label={`Include ${label} in GLB and USDZ`}
|
|
774
|
-
checked={includedPresentationIds.includes(contribution.id)}
|
|
775
|
-
id={switchId}
|
|
776
|
-
onCheckedChange={(included) =>
|
|
777
|
-
handlePresentationInclusion(contribution.id, included)
|
|
778
|
-
}
|
|
779
|
-
/>
|
|
780
|
-
</div>
|
|
781
|
-
)
|
|
782
|
-
})}
|
|
783
|
-
</div>
|
|
784
970
|
</fieldset>
|
|
785
|
-
|
|
971
|
+
{exportablePresentations.length > 0 ? (
|
|
972
|
+
<fieldset className="space-y-2 rounded-md border p-3">
|
|
973
|
+
<legend className="px-1 font-medium text-sm">Viewer surroundings</legend>
|
|
974
|
+
<p className="text-muted-foreground text-xs">
|
|
975
|
+
Optional static surroundings are included only in GLB and USDZ.
|
|
976
|
+
</p>
|
|
977
|
+
<div className="space-y-2 pt-1">
|
|
978
|
+
{exportablePresentations.map((contribution, index) => {
|
|
979
|
+
const switchId = `${includePresentationIdPrefix}-${index}`
|
|
980
|
+
const label = contribution.staticExport!.label
|
|
981
|
+
return (
|
|
982
|
+
<div
|
|
983
|
+
className="flex items-center justify-between gap-4"
|
|
984
|
+
key={contribution.id}
|
|
985
|
+
>
|
|
986
|
+
<label className="min-w-0 font-medium text-sm" htmlFor={switchId}>
|
|
987
|
+
{label}
|
|
988
|
+
</label>
|
|
989
|
+
<Switch
|
|
990
|
+
aria-label={`Include ${label} in GLB and USDZ`}
|
|
991
|
+
checked={includedPresentationIds.includes(contribution.id)}
|
|
992
|
+
id={switchId}
|
|
993
|
+
onCheckedChange={(included) =>
|
|
994
|
+
handlePresentationInclusion(contribution.id, included)
|
|
995
|
+
}
|
|
996
|
+
/>
|
|
997
|
+
</div>
|
|
998
|
+
)
|
|
999
|
+
})}
|
|
1000
|
+
</div>
|
|
1001
|
+
</fieldset>
|
|
1002
|
+
) : null}
|
|
1003
|
+
</div>
|
|
1004
|
+
</details>
|
|
1005
|
+
|
|
1006
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1007
|
+
{MODEL_EXPORT_FORMATS.map(({ format, label }) => {
|
|
1008
|
+
const isActive = activeModelExport === format
|
|
1009
|
+
return (
|
|
1010
|
+
<Button
|
|
1011
|
+
aria-busy={isActive}
|
|
1012
|
+
className="w-full justify-start gap-2"
|
|
1013
|
+
disabled={
|
|
1014
|
+
activeModelExport !== null || sendToBlenderStep !== null || !modelExport
|
|
1015
|
+
}
|
|
1016
|
+
key={format}
|
|
1017
|
+
onClick={() => void handleModelExport(format, label)}
|
|
1018
|
+
variant="outline"
|
|
1019
|
+
>
|
|
1020
|
+
<Download aria-hidden="true" className="size-4" />
|
|
1021
|
+
{isActive ? `Exporting ${label}…` : `Export ${label}`}
|
|
1022
|
+
</Button>
|
|
1023
|
+
)
|
|
1024
|
+
})}
|
|
786
1025
|
</div>
|
|
787
|
-
</details>
|
|
788
1026
|
|
|
789
|
-
|
|
790
|
-
const isActive = activeModelExport === format
|
|
791
|
-
return (
|
|
1027
|
+
<div className="space-y-2">
|
|
792
1028
|
<Button
|
|
793
|
-
aria-busy={
|
|
1029
|
+
aria-busy={sendToBlenderStep !== null}
|
|
794
1030
|
className="w-full justify-start gap-2"
|
|
795
1031
|
disabled={activeModelExport !== null || sendToBlenderStep !== null || !modelExport}
|
|
796
|
-
|
|
797
|
-
onClick={() => void handleModelExport(format, label)}
|
|
1032
|
+
onClick={() => void handleSendToBlender()}
|
|
798
1033
|
variant="outline"
|
|
799
1034
|
>
|
|
800
|
-
<
|
|
801
|
-
{
|
|
1035
|
+
<Send aria-hidden="true" className="size-4" />
|
|
1036
|
+
{sendToBlenderStep
|
|
1037
|
+
? SEND_TO_BLENDER_STEP_LABEL[sendToBlenderStep]
|
|
1038
|
+
: 'Send to Blender'}
|
|
802
1039
|
</Button>
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
className=
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
{modelExportError}
|
|
849
|
-
</p>
|
|
850
|
-
) : null}
|
|
851
|
-
{modelExportWarning ? (
|
|
852
|
-
<p className="text-foreground text-xs" role="status">
|
|
853
|
-
Warning: {modelExportWarning}
|
|
854
|
-
</p>
|
|
855
|
-
) : null}
|
|
856
|
-
|
|
857
|
-
<PrintExportButton onlyVisible={exportOnlyVisible} />
|
|
858
|
-
</div>
|
|
859
|
-
|
|
860
|
-
<div className="space-y-2">
|
|
861
|
-
<div className="flex items-center justify-between font-medium text-muted-foreground text-xs">
|
|
862
|
-
<span>Floor plan</span>
|
|
863
|
-
<span>{floorplanMode === 'default' ? 'Default mode' : 'Expert mode'}</span>
|
|
1040
|
+
<p className="text-muted-foreground text-xs">
|
|
1041
|
+
Needs the{' '}
|
|
1042
|
+
<a
|
|
1043
|
+
className="underline underline-offset-2"
|
|
1044
|
+
href={BLENDER_ADDON_URL}
|
|
1045
|
+
rel="noreferrer"
|
|
1046
|
+
target="_blank"
|
|
1047
|
+
>
|
|
1048
|
+
Pascal add-on for Blender
|
|
1049
|
+
</a>{' '}
|
|
1050
|
+
running in an open Blender.
|
|
1051
|
+
</p>
|
|
1052
|
+
</div>
|
|
1053
|
+
{sendToBlenderMessage ? (
|
|
1054
|
+
<p
|
|
1055
|
+
className={
|
|
1056
|
+
sendToBlenderMessage.tone === 'error'
|
|
1057
|
+
? 'text-destructive text-xs'
|
|
1058
|
+
: 'text-foreground text-xs'
|
|
1059
|
+
}
|
|
1060
|
+
role={sendToBlenderMessage.tone === 'error' ? 'alert' : 'status'}
|
|
1061
|
+
>
|
|
1062
|
+
{sendToBlenderMessage.text}
|
|
1063
|
+
</p>
|
|
1064
|
+
) : null}
|
|
1065
|
+
|
|
1066
|
+
{activeModelExport ? (
|
|
1067
|
+
<p className="text-muted-foreground text-xs" role="status">
|
|
1068
|
+
Preparing {activeModelExport.toUpperCase()} file…
|
|
1069
|
+
</p>
|
|
1070
|
+
) : null}
|
|
1071
|
+
{modelExportError ? (
|
|
1072
|
+
<p className="text-destructive text-xs" role="alert">
|
|
1073
|
+
{modelExportError}
|
|
1074
|
+
</p>
|
|
1075
|
+
) : null}
|
|
1076
|
+
{modelExportWarning ? (
|
|
1077
|
+
<p className="text-foreground text-xs" role="status">
|
|
1078
|
+
Warning: {modelExportWarning}
|
|
1079
|
+
</p>
|
|
1080
|
+
) : null}
|
|
1081
|
+
|
|
1082
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1083
|
+
<PrintExportButton onlyVisible={exportOnlyVisible} />
|
|
1084
|
+
</div>
|
|
864
1085
|
</div>
|
|
865
|
-
<Button
|
|
866
|
-
aria-busy={activeFloorplanExport === 'full'}
|
|
867
|
-
className="w-full justify-start gap-2"
|
|
868
|
-
disabled={activeFloorplanExport !== null}
|
|
869
|
-
onClick={() => void handleFloorplanExport('full')}
|
|
870
|
-
variant="outline"
|
|
871
|
-
>
|
|
872
|
-
<MapIcon className="size-4" />
|
|
873
|
-
Full floor plan
|
|
874
|
-
</Button>
|
|
875
|
-
<Button
|
|
876
|
-
aria-busy={activeFloorplanExport === 'structure'}
|
|
877
|
-
className="w-full justify-start gap-2"
|
|
878
|
-
disabled={activeFloorplanExport !== null}
|
|
879
|
-
onClick={() => void handleFloorplanExport('structure')}
|
|
880
|
-
variant="outline"
|
|
881
|
-
>
|
|
882
|
-
<MapIcon className="size-4" />
|
|
883
|
-
Structure only
|
|
884
|
-
</Button>
|
|
885
|
-
{activeFloorplanExport ? (
|
|
886
|
-
<p className="text-muted-foreground text-xs" role="status">
|
|
887
|
-
Preparing floor-plan PDF…
|
|
888
|
-
</p>
|
|
889
|
-
) : null}
|
|
890
|
-
{floorplanExportError ? (
|
|
891
|
-
<p className="break-words text-destructive text-xs" role="alert">
|
|
892
|
-
{floorplanExportError}
|
|
893
|
-
</p>
|
|
894
|
-
) : null}
|
|
895
|
-
</div>
|
|
896
|
-
</div>
|
|
897
|
-
|
|
898
|
-
{/* Thumbnail Section (only for cloud projects) */}
|
|
899
|
-
{projectId && !isLocalProject && (
|
|
900
|
-
<div className="space-y-2">
|
|
901
|
-
<label className="font-medium text-muted-foreground text-xs uppercase">Thumbnail</label>
|
|
902
|
-
<Button
|
|
903
|
-
className="w-full justify-start gap-2"
|
|
904
|
-
disabled={isGeneratingThumbnail}
|
|
905
|
-
onClick={handleGenerateThumbnail}
|
|
906
|
-
variant="outline"
|
|
907
|
-
>
|
|
908
|
-
<Camera className="size-4" />
|
|
909
|
-
{isGeneratingThumbnail ? 'Generating...' : 'Generate Thumbnail'}
|
|
910
|
-
</Button>
|
|
911
|
-
</div>
|
|
912
|
-
)}
|
|
913
1086
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1087
|
+
<div className="space-y-3 pt-2">
|
|
1088
|
+
<div className="flex items-center justify-between">
|
|
1089
|
+
<SettingsSubheading>Floor plan</SettingsSubheading>
|
|
1090
|
+
<span className="text-muted-foreground text-xs">
|
|
1091
|
+
{floorplanMode === 'default' ? 'Default mode' : 'Expert mode'}
|
|
1092
|
+
</span>
|
|
1093
|
+
</div>
|
|
1094
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1095
|
+
<Button
|
|
1096
|
+
aria-busy={activeFloorplanExport === 'full'}
|
|
1097
|
+
className="w-full justify-start gap-2"
|
|
1098
|
+
disabled={activeFloorplanExport !== null}
|
|
1099
|
+
onClick={() => void handleFloorplanExport('full')}
|
|
1100
|
+
variant="outline"
|
|
1101
|
+
>
|
|
1102
|
+
<MapIcon className="size-4" />
|
|
1103
|
+
Full floor plan
|
|
1104
|
+
</Button>
|
|
1105
|
+
<Button
|
|
1106
|
+
aria-busy={activeFloorplanExport === 'structure'}
|
|
1107
|
+
className="w-full justify-start gap-2"
|
|
1108
|
+
disabled={activeFloorplanExport !== null}
|
|
1109
|
+
onClick={() => void handleFloorplanExport('structure')}
|
|
1110
|
+
variant="outline"
|
|
1111
|
+
>
|
|
1112
|
+
<MapIcon className="size-4" />
|
|
1113
|
+
Structure only
|
|
1114
|
+
</Button>
|
|
1115
|
+
</div>
|
|
1116
|
+
{activeFloorplanExport ? (
|
|
1117
|
+
<p className="text-muted-foreground text-xs" role="status">
|
|
1118
|
+
Preparing floor-plan PDF…
|
|
1119
|
+
</p>
|
|
1120
|
+
) : null}
|
|
1121
|
+
{floorplanExportError ? (
|
|
1122
|
+
<p className="break-words text-destructive text-xs" role="alert">
|
|
1123
|
+
{floorplanExportError}
|
|
1124
|
+
</p>
|
|
1125
|
+
) : null}
|
|
1126
|
+
</div>
|
|
1127
|
+
</SettingsSection>
|
|
917
1128
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1129
|
+
{/* Thumbnail (only for cloud projects) */}
|
|
1130
|
+
{projectId && !isLocalProject && (
|
|
1131
|
+
<SettingsSection
|
|
1132
|
+
description="Refresh the preview shown in your project list."
|
|
1133
|
+
title="Thumbnail"
|
|
1134
|
+
>
|
|
1135
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1136
|
+
<Button
|
|
1137
|
+
className="w-full justify-start gap-2"
|
|
1138
|
+
disabled={isGeneratingThumbnail}
|
|
1139
|
+
onClick={handleGenerateThumbnail}
|
|
1140
|
+
variant="outline"
|
|
1141
|
+
>
|
|
1142
|
+
<Camera className="size-4" />
|
|
1143
|
+
{isGeneratingThumbnail ? 'Generating…' : 'Generate thumbnail'}
|
|
1144
|
+
</Button>
|
|
1145
|
+
</div>
|
|
1146
|
+
</SettingsSection>
|
|
1147
|
+
)}
|
|
922
1148
|
|
|
923
|
-
<
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
variant="outline"
|
|
1149
|
+
<SettingsSection
|
|
1150
|
+
description="Keep a JSON copy of the scene, or restore one."
|
|
1151
|
+
title="Save and load"
|
|
927
1152
|
>
|
|
928
|
-
<
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
ref={fileInputRef}
|
|
937
|
-
type="file"
|
|
938
|
-
/>
|
|
939
|
-
|
|
940
|
-
<LoadBuildDialog
|
|
941
|
-
onCancel={() => setPendingImport(null)}
|
|
942
|
-
onConfirm={handleConfirmImport}
|
|
943
|
-
pending={pendingImport}
|
|
944
|
-
/>
|
|
945
|
-
</div>
|
|
946
|
-
|
|
947
|
-
{/* Audio Section */}
|
|
948
|
-
<div className="space-y-2">
|
|
949
|
-
<label className="font-medium text-muted-foreground text-xs uppercase">Audio</label>
|
|
950
|
-
<AudioSettingsDialog />
|
|
951
|
-
</div>
|
|
952
|
-
|
|
953
|
-
{/* Keyboard Section */}
|
|
954
|
-
<div className="space-y-2">
|
|
955
|
-
<label className="font-medium text-muted-foreground text-xs uppercase">Keyboard</label>
|
|
956
|
-
<KeyboardShortcutsDialog />
|
|
957
|
-
</div>
|
|
958
|
-
|
|
959
|
-
{/* Scene Graph */}
|
|
960
|
-
<div className="space-y-1">
|
|
961
|
-
<label className="font-medium text-muted-foreground text-xs uppercase">Scene Graph</label>
|
|
962
|
-
<Dialog>
|
|
963
|
-
<DialogTrigger asChild>
|
|
964
|
-
<Button className="h-auto justify-start p-0 text-sm" variant="link">
|
|
965
|
-
Explore scene graph
|
|
1153
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1154
|
+
<Button
|
|
1155
|
+
className="w-full justify-start gap-2"
|
|
1156
|
+
onClick={handleSaveBuild}
|
|
1157
|
+
variant="outline"
|
|
1158
|
+
>
|
|
1159
|
+
<Save className="size-4" />
|
|
1160
|
+
Save build
|
|
966
1161
|
</Button>
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
className="flex h-full min-h-0 w-full min-w-0 *:h-full *:w-full *:overflow-y-auto"
|
|
972
|
-
onContextMenuCapture={blockSceneGraphMutations}
|
|
973
|
-
onDragStartCapture={blockSceneGraphMutations}
|
|
974
|
-
onDropCapture={blockSceneGraphMutations}
|
|
975
|
-
onKeyDownCapture={blockSceneGraphDeletion}
|
|
1162
|
+
<Button
|
|
1163
|
+
className="w-full justify-start gap-2"
|
|
1164
|
+
onClick={() => fileInputRef.current?.click()}
|
|
1165
|
+
variant="outline"
|
|
976
1166
|
>
|
|
977
|
-
<
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
</DialogContent>
|
|
982
|
-
</Dialog>
|
|
983
|
-
</div>
|
|
1167
|
+
<Upload className="size-4" />
|
|
1168
|
+
Load build
|
|
1169
|
+
</Button>
|
|
1170
|
+
</div>
|
|
984
1171
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1172
|
+
<input
|
|
1173
|
+
accept="application/json"
|
|
1174
|
+
className="hidden"
|
|
1175
|
+
onChange={handleFileLoad}
|
|
1176
|
+
ref={fileInputRef}
|
|
1177
|
+
type="file"
|
|
1178
|
+
/>
|
|
1179
|
+
|
|
1180
|
+
<LoadBuildDialog
|
|
1181
|
+
onCancel={() => setPendingImport(null)}
|
|
1182
|
+
onConfirm={handleConfirmImport}
|
|
1183
|
+
pending={pendingImport}
|
|
1184
|
+
/>
|
|
1185
|
+
</SettingsSection>
|
|
1186
|
+
|
|
1187
|
+
<SettingsSection description="Sound levels and keyboard shortcuts." title="Preferences">
|
|
1188
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1189
|
+
<AudioSettingsDialog />
|
|
1190
|
+
<KeyboardShortcutsDialog />
|
|
1191
|
+
</div>
|
|
1192
|
+
</SettingsSection>
|
|
988
1193
|
|
|
989
|
-
<
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
variant="destructive"
|
|
1194
|
+
<SettingsSection
|
|
1195
|
+
description="Inspect how nodes are nested in this scene."
|
|
1196
|
+
title="Scene graph"
|
|
993
1197
|
>
|
|
994
|
-
<
|
|
995
|
-
|
|
996
|
-
|
|
1198
|
+
<Dialog>
|
|
1199
|
+
<DialogTrigger asChild>
|
|
1200
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1201
|
+
<Button className="w-full justify-start gap-2" variant="outline">
|
|
1202
|
+
<ListTree className="size-4" />
|
|
1203
|
+
Explore scene graph
|
|
1204
|
+
</Button>
|
|
1205
|
+
</div>
|
|
1206
|
+
</DialogTrigger>
|
|
1207
|
+
<DialogContent className="h-[80vh] max-w-[95vw] gap-0 overflow-hidden border-0 bg-[#1e1e1e] p-0 shadow-none sm:max-w-5xl">
|
|
1208
|
+
<DialogTitle className="sr-only">Scene Graph</DialogTitle>
|
|
1209
|
+
<div
|
|
1210
|
+
className="flex h-full min-h-0 w-full min-w-0 *:h-full *:w-full *:overflow-y-auto"
|
|
1211
|
+
onContextMenuCapture={blockSceneGraphMutations}
|
|
1212
|
+
onDragStartCapture={blockSceneGraphMutations}
|
|
1213
|
+
onDropCapture={blockSceneGraphMutations}
|
|
1214
|
+
onKeyDownCapture={blockSceneGraphDeletion}
|
|
1215
|
+
>
|
|
1216
|
+
<VisualJson value={sceneGraphValue}>
|
|
1217
|
+
<TreeView showCounts />
|
|
1218
|
+
</VisualJson>
|
|
1219
|
+
</div>
|
|
1220
|
+
</DialogContent>
|
|
1221
|
+
</Dialog>
|
|
1222
|
+
</SettingsSection>
|
|
1223
|
+
|
|
1224
|
+
<SettingsSection
|
|
1225
|
+
description="Removes every node from this scene. This cannot be undone."
|
|
1226
|
+
title="Danger zone"
|
|
1227
|
+
tone="destructive"
|
|
1228
|
+
>
|
|
1229
|
+
<div className="grid gap-2 @sm:grid-cols-2">
|
|
1230
|
+
<Button
|
|
1231
|
+
className="w-full justify-start gap-2"
|
|
1232
|
+
onClick={handleResetToDefault}
|
|
1233
|
+
variant="destructive"
|
|
1234
|
+
>
|
|
1235
|
+
<Trash2 className="size-4" />
|
|
1236
|
+
Clear and start new
|
|
1237
|
+
</Button>
|
|
1238
|
+
</div>
|
|
1239
|
+
</SettingsSection>
|
|
997
1240
|
</div>
|
|
998
1241
|
</div>
|
|
999
1242
|
)
|