@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
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { getSceneTheme, useViewer } from '@pascal-app/viewer'
|
|
4
4
|
import { type ReactNode, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
5
|
+
import { useShownSidebarPanel } from '../../lib/sidebar-panel'
|
|
5
6
|
import useEditor from '../../store/use-editor'
|
|
6
7
|
import { MobileTabBar } from '../ui/sidebar/mobile-tab-bar'
|
|
7
8
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
8
9
|
import { BottomSheet, type BottomSheetHandle } from './bottom-sheet'
|
|
10
|
+
import { useSceneSpan } from './views/scene-region'
|
|
11
|
+
import { ViewTabs } from './views/view-bar'
|
|
9
12
|
|
|
10
13
|
const MIN_SNAP = 0
|
|
11
14
|
const MAX_SNAP = 1
|
|
@@ -40,6 +43,7 @@ export interface EditorLayoutMobileProps {
|
|
|
40
43
|
viewerToolbarRight?: ReactNode
|
|
41
44
|
viewerContent: ReactNode
|
|
42
45
|
overlays?: ReactNode
|
|
46
|
+
viewBar?: boolean
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
export function EditorLayoutMobile({
|
|
@@ -51,9 +55,11 @@ export function EditorLayoutMobile({
|
|
|
51
55
|
viewerToolbarRight,
|
|
52
56
|
viewerContent,
|
|
53
57
|
overlays,
|
|
58
|
+
viewBar = true,
|
|
54
59
|
}: EditorLayoutMobileProps) {
|
|
60
|
+
const hasScene = useSceneSpan() !== null
|
|
55
61
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
56
|
-
const activePanel =
|
|
62
|
+
const activePanel = useShownSidebarPanel(sidebarTabs)
|
|
57
63
|
const setActivePanel = useEditor((s) => s.setActiveSidebarPanel)
|
|
58
64
|
const panelSheetHeight = useEditor((s) => s.mobilePanelSheetHeight)
|
|
59
65
|
const isDark = useViewer((s) => getSceneTheme(s.sceneTheme).appearance === 'dark')
|
|
@@ -70,30 +76,18 @@ export function EditorLayoutMobile({
|
|
|
70
76
|
|
|
71
77
|
const currentTab = sidebarTabs.find((t) => t.id === activePanel)
|
|
72
78
|
|
|
73
|
-
//
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (sidebarTabs.length > 0 && !sidebarTabs.some((t) => t.id === activePanel)) {
|
|
76
|
-
setActivePanel(sidebarTabs[0]!.id)
|
|
77
|
-
}
|
|
78
|
-
}, [sidebarTabs, activePanel, setActivePanel])
|
|
79
|
-
|
|
80
|
-
// Sync editor phase / mode with the active tab:
|
|
79
|
+
// Sync the editor mode with the active tab:
|
|
81
80
|
// - Entering Chat always drops to Select (chat is a composing context).
|
|
82
|
-
// -
|
|
83
|
-
//
|
|
84
|
-
//
|
|
81
|
+
// - Leaving Items with the item tool still armed exits the build mode.
|
|
82
|
+
// Arming the item tool on entering Items belongs to the tap (`handleTabPress`):
|
|
83
|
+
// a tab restored on load opens in select mode.
|
|
85
84
|
useEffect(() => {
|
|
86
|
-
const { armToolMode,
|
|
85
|
+
const { armToolMode, mode, tool } = useEditor.getState()
|
|
87
86
|
if (activePanel === 'ai' && mode === 'build') {
|
|
88
87
|
armToolMode({ mode: 'select' })
|
|
89
88
|
return
|
|
90
89
|
}
|
|
91
|
-
if (activePanel
|
|
92
|
-
if (phase !== 'furnish') setPhase('furnish')
|
|
93
|
-
if (mode !== 'build') armToolMode({ mode: 'build', tool: 'item' })
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
if (phase === 'furnish' && mode === 'build') {
|
|
90
|
+
if (activePanel !== 'items' && mode === 'build' && tool === 'item') {
|
|
97
91
|
armToolMode({ mode: 'select' })
|
|
98
92
|
}
|
|
99
93
|
}, [activePanel])
|
|
@@ -148,9 +142,17 @@ export function EditorLayoutMobile({
|
|
|
148
142
|
if (middleH <= 0) return
|
|
149
143
|
const tab = sidebarTabs.find((t) => t.id === id)
|
|
150
144
|
if (!tab) return
|
|
145
|
+
if (tab.onSelect) {
|
|
146
|
+
tab.onSelect()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
151
149
|
const defaultPx = getDefaultSnap(tab) * middleH
|
|
152
150
|
if (id !== activePanel) {
|
|
153
151
|
setActivePanel(id)
|
|
152
|
+
// Tapping Items arms the item tool, as F does on desktop.
|
|
153
|
+
if (id === 'items' && useEditor.getState().mode !== 'build') {
|
|
154
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
|
|
155
|
+
}
|
|
154
156
|
sheetRef.current?.snapTo(defaultPx)
|
|
155
157
|
return
|
|
156
158
|
}
|
|
@@ -223,19 +225,29 @@ export function EditorLayoutMobile({
|
|
|
223
225
|
{/* Viewer column: sized by committed sheet height */}
|
|
224
226
|
<div className="absolute inset-x-0 top-0 overflow-hidden" style={{ height: viewerHeight }}>
|
|
225
227
|
<div className="relative h-full w-full">
|
|
226
|
-
{(viewerToolbarLeft || viewerToolbarRight) && !isCaptureMode && (
|
|
228
|
+
{(viewBar || viewerToolbarLeft || viewerToolbarRight) && !isCaptureMode && (
|
|
227
229
|
<div className="pointer-events-none absolute top-3 right-3 left-3 z-20 flex items-center justify-between gap-2">
|
|
228
|
-
<div className="
|
|
229
|
-
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
<div className="flex items-center gap-2">
|
|
231
|
+
{viewBar && (
|
|
232
|
+
<div data-view-bar={0}>
|
|
233
|
+
<ViewTabs className="shadow-elevation-4" pane={0} />
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
<div className="pointer-events-auto flex items-center gap-2">
|
|
237
|
+
{viewerToolbarLeft}
|
|
238
|
+
</div>
|
|
233
239
|
</div>
|
|
240
|
+
{hasScene && (
|
|
241
|
+
<div className="pointer-events-auto flex items-center gap-2">
|
|
242
|
+
{viewerToolbarRight}
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
234
245
|
</div>
|
|
235
246
|
)}
|
|
236
247
|
<div className="relative h-full w-full overflow-hidden">{viewerContent}</div>
|
|
237
248
|
{overlays && (
|
|
238
249
|
<div
|
|
250
|
+
data-viewer-bounds
|
|
239
251
|
className="pointer-events-none absolute inset-0 z-30"
|
|
240
252
|
style={{ transform: 'translateZ(0)' }}
|
|
241
253
|
>
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
import { type ReactNode, useCallback, useEffect, useRef } from 'react'
|
|
4
4
|
import { useIsMobile } from '../../hooks/use-mobile'
|
|
5
|
+
import {
|
|
6
|
+
openSidebarPanel,
|
|
7
|
+
SIDEBAR_MIN_WIDTH,
|
|
8
|
+
setSidebarTabIds,
|
|
9
|
+
showSidebarPanel,
|
|
10
|
+
useShownSidebarPanel,
|
|
11
|
+
} from '../../lib/sidebar-panel'
|
|
5
12
|
import useEditor from '../../store/use-editor'
|
|
6
13
|
|
|
7
14
|
import { useSidebarStore } from '../ui/primitives/sidebar'
|
|
8
15
|
import { IconRail, type SidebarTab } from '../ui/sidebar/tab-bar'
|
|
9
16
|
import { EditorLayoutMobile } from './editor-layout-mobile'
|
|
17
|
+
import { useSceneSpan } from './views/scene-region'
|
|
18
|
+
import { ViewBarRow } from './views/view-bar'
|
|
10
19
|
|
|
11
|
-
const SIDEBAR_MIN_WIDTH = 300
|
|
12
20
|
const SIDEBAR_MAX_WIDTH = 800
|
|
13
21
|
const SIDEBAR_COLLAPSE_THRESHOLD = 220
|
|
14
22
|
// Matches the `w-14` rail in <IconRail>; the resize math is relative to it.
|
|
@@ -31,27 +39,49 @@ function LeftColumn({
|
|
|
31
39
|
const setWidth = useSidebarStore((s) => s.setWidth)
|
|
32
40
|
const isDragging = useSidebarStore((s) => s.isDragging)
|
|
33
41
|
const setIsDragging = useSidebarStore((s) => s.setIsDragging)
|
|
34
|
-
const activePanel =
|
|
42
|
+
const activePanel = useShownSidebarPanel(tabs)
|
|
35
43
|
const setActivePanel = useEditor((s) => s.setActiveSidebarPanel)
|
|
36
44
|
|
|
37
45
|
const isResizing = useRef(false)
|
|
38
46
|
|
|
39
|
-
//
|
|
47
|
+
// Publish the rail's tabs so keyboard shortcuts (B, P) can open a panel.
|
|
40
48
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
setSidebarTabIds(tabs.map((tab) => tab.id))
|
|
50
|
+
return () => setSidebarTabIds([])
|
|
51
|
+
}, [tabs])
|
|
52
|
+
|
|
53
|
+
// A stage entry whose surface has left the stage (its view closed from the
|
|
54
|
+
// view bar) hands the rail back to the panel that was open before it.
|
|
55
|
+
const lastPanelTab = useRef<string | null>(null)
|
|
56
|
+
const activeTab = tabs.find((t) => t.id === activePanel)
|
|
57
|
+
if (activeTab && !activeTab.noPanel) lastPanelTab.current = activeTab.id
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (activeTab?.stageActive !== false) return
|
|
60
|
+
const fallback = tabs.find((t) => t.id === lastPanelTab.current) ?? tabs.find((t) => !t.noPanel)
|
|
61
|
+
if (fallback) setActivePanel(fallback.id)
|
|
62
|
+
}, [activeTab, tabs, setActivePanel])
|
|
45
63
|
|
|
46
|
-
// Leaving the items tab
|
|
64
|
+
// Leaving the items tab with the item tool in hand drops back to select mode
|
|
47
65
|
useEffect(() => {
|
|
48
66
|
if (activePanel === 'items') return
|
|
49
|
-
const {
|
|
50
|
-
if (
|
|
67
|
+
const { mode, tool, setMode } = useEditor.getState()
|
|
68
|
+
if (mode === 'build' && tool === 'item') {
|
|
51
69
|
setMode('select')
|
|
52
70
|
}
|
|
53
71
|
}, [activePanel])
|
|
54
72
|
|
|
73
|
+
// Paint mode entered without the panel (command palette, a material's "paint
|
|
74
|
+
// with") brings the host's Paint panel along, so the rail reflects the mode.
|
|
75
|
+
useEffect(
|
|
76
|
+
() =>
|
|
77
|
+
useEditor.subscribe((state, prev) => {
|
|
78
|
+
if (state.mode !== prev.mode && state.mode === 'material-paint') {
|
|
79
|
+
openSidebarPanel(['paint'])
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
82
|
+
[],
|
|
83
|
+
)
|
|
84
|
+
|
|
55
85
|
// Closing (collapsing) the sidebar disarms any build tool back to select
|
|
56
86
|
useEffect(() => {
|
|
57
87
|
if (!isCollapsed) return
|
|
@@ -72,20 +102,20 @@ function LeftColumn({
|
|
|
72
102
|
[setIsDragging],
|
|
73
103
|
)
|
|
74
104
|
|
|
75
|
-
// Rail click: reopen a collapsed panel, collapse when
|
|
76
|
-
// tab, otherwise switch tabs.
|
|
77
|
-
// up to the minimum so the panel always returns to a usable size.
|
|
105
|
+
// Rail click: reopen a collapsed panel (at a usable width), collapse when
|
|
106
|
+
// re-clicking the open tab, otherwise switch tabs.
|
|
78
107
|
const handleRailClick = useCallback(
|
|
79
108
|
(id: string) => {
|
|
109
|
+
const tab = tabs.find((t) => t.id === id)
|
|
110
|
+
if (id !== activePanel) tabs.find((t) => t.id === activePanel)?.onDeselect?.()
|
|
111
|
+
tab?.onSelect?.()
|
|
80
112
|
// noPanel tabs drive the stage, not the panel — leave collapse state alone.
|
|
81
|
-
if (
|
|
113
|
+
if (tab?.noPanel) {
|
|
82
114
|
setActivePanel(id)
|
|
83
115
|
return
|
|
84
116
|
}
|
|
85
117
|
if (isCollapsed) {
|
|
86
|
-
|
|
87
|
-
if (width < SIDEBAR_MIN_WIDTH) setWidth(SIDEBAR_MIN_WIDTH)
|
|
88
|
-
setActivePanel(id)
|
|
118
|
+
showSidebarPanel(id)
|
|
89
119
|
return
|
|
90
120
|
}
|
|
91
121
|
if (id === activePanel) {
|
|
@@ -94,7 +124,7 @@ function LeftColumn({
|
|
|
94
124
|
}
|
|
95
125
|
setActivePanel(id)
|
|
96
126
|
},
|
|
97
|
-
[tabs, isCollapsed,
|
|
127
|
+
[tabs, isCollapsed, activePanel, setIsCollapsed, setActivePanel],
|
|
98
128
|
)
|
|
99
129
|
|
|
100
130
|
useEffect(() => {
|
|
@@ -159,59 +189,97 @@ function LeftColumn({
|
|
|
159
189
|
|
|
160
190
|
// ── Right column: viewer area with toolbar ───────────────────────────────────
|
|
161
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Host toolbars floating over the stage. The right one carries scene controls
|
|
194
|
+
* (wall mode, display), so it sits at the right edge of the scene panes; the
|
|
195
|
+
* left one floats only when there is no view bar row to carry it.
|
|
196
|
+
*/
|
|
197
|
+
function StageToolbar({
|
|
198
|
+
toolbarLeft,
|
|
199
|
+
toolbarRight,
|
|
200
|
+
}: {
|
|
201
|
+
toolbarLeft?: ReactNode
|
|
202
|
+
toolbarRight?: ReactNode
|
|
203
|
+
}) {
|
|
204
|
+
const scene = useSceneSpan()
|
|
205
|
+
return (
|
|
206
|
+
<div className="pointer-events-none absolute top-3 right-0 left-0 z-20 h-8">
|
|
207
|
+
{toolbarLeft && (
|
|
208
|
+
<div className="pointer-events-auto absolute top-0 left-3 flex items-center gap-2">
|
|
209
|
+
{toolbarLeft}
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
{toolbarRight && scene && (
|
|
213
|
+
<div
|
|
214
|
+
className="pointer-events-auto absolute top-0 flex items-center gap-2"
|
|
215
|
+
style={{ right: `calc(${(1 - scene.right) * 100}% + 12px)` }}
|
|
216
|
+
>
|
|
217
|
+
{toolbarRight}
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
162
224
|
function RightColumn({
|
|
163
225
|
toolbarLeft,
|
|
164
226
|
toolbarRight,
|
|
227
|
+
viewBar,
|
|
165
228
|
children,
|
|
166
229
|
overlays,
|
|
167
230
|
stageOverlay,
|
|
168
231
|
}: {
|
|
169
232
|
toolbarLeft?: ReactNode
|
|
170
233
|
toolbarRight?: ReactNode
|
|
234
|
+
viewBar: boolean
|
|
171
235
|
children: ReactNode
|
|
172
236
|
overlays?: ReactNode
|
|
173
237
|
stageOverlay?: ReactNode
|
|
174
238
|
}) {
|
|
175
239
|
return (
|
|
176
|
-
<div
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
240
|
+
<div className="relative flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
241
|
+
{/* The view bar row sits above the stage, one bar per pane; the host's
|
|
242
|
+
left toolbar rides after the first pane's views. */}
|
|
243
|
+
{viewBar && <ViewBarRow leading={toolbarLeft} />}
|
|
244
|
+
<div
|
|
245
|
+
className="relative flex min-h-0 flex-1 flex-col overflow-hidden"
|
|
246
|
+
style={{
|
|
247
|
+
borderTopLeftRadius: 16,
|
|
248
|
+
clipPath: 'inset(0 0 0 0 round 16px 0 0 0)',
|
|
249
|
+
boxShadow: '-4px -2px 16px rgba(0, 0, 0, 0.08), -1px 0 4px rgba(0, 0, 0, 0.04)',
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
{((!viewBar && toolbarLeft) || toolbarRight) && (
|
|
253
|
+
<StageToolbar
|
|
254
|
+
toolbarLeft={viewBar ? undefined : toolbarLeft}
|
|
255
|
+
toolbarRight={toolbarRight}
|
|
256
|
+
/>
|
|
257
|
+
)}
|
|
258
|
+
{/* Canvas area. `isolate` matters: drei's `<Html>` computes a z-index
|
|
259
|
+
from camera distance and defaults to a range topping out at
|
|
260
|
+
16,777,271, and without a stacking context here those values compete
|
|
261
|
+
directly with the viewer toolbar (z-20), the stage overlay (z-10) and
|
|
262
|
+
the overlay band (z-30) — so an in-scene tool badge painted over all
|
|
263
|
+
three. Isolating pins every in-scene HTML layer inside the canvas,
|
|
264
|
+
where it belongs, and leaves their order relative to each other
|
|
265
|
+
untouched. */}
|
|
266
|
+
<div className="relative isolate flex-1 overflow-hidden">{children}</div>
|
|
267
|
+
{/* Stage overlay — replaces the whole stage visually (e.g. the item
|
|
268
|
+
builder) while keeping the canvas mounted. The view bar row steps
|
|
269
|
+
aside for it (see `EditorLayoutV2`); the host's toolbars stay above. */}
|
|
270
|
+
{stageOverlay && <div className="absolute inset-0 z-10">{stageOverlay}</div>}
|
|
271
|
+
{/* Overlays scoped to the stage. `data-viewer-bounds` marks the
|
|
272
|
+
draggable region the floating inspector clamps itself to. */}
|
|
273
|
+
{overlays && (
|
|
274
|
+
<div
|
|
275
|
+
className="pointer-events-none absolute inset-0 z-30"
|
|
276
|
+
data-viewer-bounds
|
|
277
|
+
style={{ transform: 'translateZ(0)' }}
|
|
278
|
+
>
|
|
279
|
+
{overlays}
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
215
283
|
</div>
|
|
216
284
|
)
|
|
217
285
|
}
|
|
@@ -228,6 +296,8 @@ export interface EditorLayoutV2Props {
|
|
|
228
296
|
viewerContent: ReactNode
|
|
229
297
|
overlays?: ReactNode
|
|
230
298
|
stageOverlay?: ReactNode
|
|
299
|
+
/** Show each pane's view bar. Off for hosts that present one fixed view. */
|
|
300
|
+
viewBar?: boolean
|
|
231
301
|
}
|
|
232
302
|
|
|
233
303
|
export function EditorLayoutV2({
|
|
@@ -240,6 +310,7 @@ export function EditorLayoutV2({
|
|
|
240
310
|
viewerContent,
|
|
241
311
|
overlays,
|
|
242
312
|
stageOverlay,
|
|
313
|
+
viewBar = true,
|
|
243
314
|
}: EditorLayoutV2Props) {
|
|
244
315
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
245
316
|
const isMobile = useIsMobile()
|
|
@@ -251,8 +322,9 @@ export function EditorLayoutV2({
|
|
|
251
322
|
overlays={overlays}
|
|
252
323
|
renderTabContent={renderTabContent}
|
|
253
324
|
sidebarOverlay={sidebarOverlay}
|
|
254
|
-
sidebarTabs={sidebarTabs.filter((t) => !t.noPanel)}
|
|
325
|
+
sidebarTabs={sidebarTabs.filter((t) => !t.noPanel || t.onSelect)}
|
|
255
326
|
viewerContent={viewerContent}
|
|
327
|
+
viewBar={viewBar}
|
|
256
328
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
257
329
|
viewerToolbarRight={viewerToolbarRight}
|
|
258
330
|
/>
|
|
@@ -278,6 +350,7 @@ export function EditorLayoutV2({
|
|
|
278
350
|
stageOverlay={stageOverlay}
|
|
279
351
|
toolbarLeft={isCaptureMode ? undefined : viewerToolbarLeft}
|
|
280
352
|
toolbarRight={isCaptureMode ? undefined : viewerToolbarRight}
|
|
353
|
+
viewBar={viewBar && !isCaptureMode && !stageOverlay}
|
|
281
354
|
>
|
|
282
355
|
{viewerContent}
|
|
283
356
|
</RightColumn>
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { emitter
|
|
3
|
+
import { emitter } from '@pascal-app/core'
|
|
4
4
|
import { disposeObject3DResources, snapLevelsToTruePositions, useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import { useThree } from '@react-three/fiber'
|
|
6
6
|
import { useEffect } from 'react'
|
|
7
7
|
import * as THREE from 'three'
|
|
8
8
|
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'
|
|
9
9
|
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
exportSceneToGlb,
|
|
12
|
+
prepareSceneForExport,
|
|
13
|
+
waitForExportGeometry,
|
|
14
|
+
} from '../../lib/glb-export'
|
|
15
|
+
import { exportPreparedSceneToIfc, ifcFileName } from '../../lib/ifc-export'
|
|
11
16
|
import { exportSceneLevelsForPrint } from '../../lib/level-print-export'
|
|
12
17
|
import type { ModelExport, ModelExportArtifact } from '../../lib/model-export'
|
|
13
18
|
import {
|
|
@@ -20,6 +25,7 @@ import { filterPreparedSceneForPrintContent } from '../../lib/print-content-scop
|
|
|
20
25
|
import { exportSceneToPrintStl, mergePrintExportDiagnostics } from '../../lib/print-export'
|
|
21
26
|
import { applySemanticPrintFeatureThickness } from '../../lib/print-feature-thickness'
|
|
22
27
|
import { compileSemanticPrintShellWithManifold } from '../../lib/print-shell-compiler-manifold-worker'
|
|
28
|
+
import { getSavedSceneDocument } from '../../lib/scene'
|
|
23
29
|
import { exportSceneToUsdz } from '../../lib/usdz-export'
|
|
24
30
|
import useEditor from '../../store/use-editor'
|
|
25
31
|
|
|
@@ -51,7 +57,10 @@ export function ExportManager() {
|
|
|
51
57
|
const setModelExport = useEditor((state) => state.setModelExport)
|
|
52
58
|
|
|
53
59
|
useEffect(() => {
|
|
54
|
-
const exportFn: ModelExport = async (format = 'glb',
|
|
60
|
+
const exportFn: ModelExport = async (format = 'glb', requested = {}) => {
|
|
61
|
+
// Resolve once: scene preparation prunes hidden nodes by default, and the
|
|
62
|
+
// IFC writer must drop the same nodes it rebuilds parametrically.
|
|
63
|
+
const options = { ...requested, onlyVisible: requested.onlyVisible ?? true }
|
|
55
64
|
// Find the scene renderer group by name
|
|
56
65
|
const sceneGroup = scene.getObjectByName('scene-renderer')
|
|
57
66
|
if (!sceneGroup) {
|
|
@@ -67,11 +76,12 @@ export function ExportManager() {
|
|
|
67
76
|
// without it every plant exports as its raycast collider, a white box).
|
|
68
77
|
useViewer.getState().setExporting(true)
|
|
69
78
|
try {
|
|
70
|
-
await
|
|
79
|
+
await waitForExportGeometry(getSavedSceneDocument().nodes, options)
|
|
80
|
+
const { nodes, collections } = getSavedSceneDocument()
|
|
71
81
|
|
|
72
82
|
if (format === 'glb') {
|
|
73
83
|
const warnings: string[] = []
|
|
74
|
-
const buffer = await exportSceneToGlb(sceneGroup,
|
|
84
|
+
const buffer = await exportSceneToGlb(sceneGroup, nodes, {
|
|
75
85
|
...options,
|
|
76
86
|
onWarning: (warning) => warnings.push(warning),
|
|
77
87
|
})
|
|
@@ -81,7 +91,7 @@ export function ExportManager() {
|
|
|
81
91
|
|
|
82
92
|
if (format === 'usdz') {
|
|
83
93
|
const warnings: string[] = []
|
|
84
|
-
const data = await exportSceneToUsdz(sceneGroup,
|
|
94
|
+
const data = await exportSceneToUsdz(sceneGroup, nodes, {
|
|
85
95
|
...options,
|
|
86
96
|
onWarning: (warning) => warnings.push(warning),
|
|
87
97
|
})
|
|
@@ -95,12 +105,11 @@ export function ExportManager() {
|
|
|
95
105
|
// window, so the export snapshots the clean building, then restore.
|
|
96
106
|
emitter.emit('thumbnail:before-capture', undefined)
|
|
97
107
|
const restoreLevels = snapLevelsToTruePositions()
|
|
98
|
-
const nodes = useScene.getState().nodes
|
|
99
108
|
let prepared: ReturnType<typeof prepareSceneForExport>
|
|
100
109
|
try {
|
|
101
110
|
prepared = prepareSceneForExport(sceneGroup, nodes, {
|
|
102
111
|
...options,
|
|
103
|
-
requireSynchronousBake: format === 'stl' || format === 'obj',
|
|
112
|
+
requireSynchronousBake: format === 'stl' || format === 'obj' || format === 'ifc',
|
|
104
113
|
})
|
|
105
114
|
} finally {
|
|
106
115
|
restoreLevels()
|
|
@@ -206,6 +215,23 @@ export function ExportManager() {
|
|
|
206
215
|
return finishArtifact(blob, `model_${date}.stl`, options.download)
|
|
207
216
|
}
|
|
208
217
|
|
|
218
|
+
if (format === 'ifc') {
|
|
219
|
+
const { data, warnings } = exportPreparedSceneToIfc(exportScene, nodes, {
|
|
220
|
+
collections,
|
|
221
|
+
projectName: options.projectName,
|
|
222
|
+
onlyVisible: options.onlyVisible,
|
|
223
|
+
excludedNodeTypes: options.excludedNodeTypes,
|
|
224
|
+
})
|
|
225
|
+
const blob = new Blob([data], { type: 'application/x-step' })
|
|
226
|
+
return finishArtifact(
|
|
227
|
+
blob,
|
|
228
|
+
ifcFileName(options.projectName, `model_${date}`),
|
|
229
|
+
options.download,
|
|
230
|
+
undefined,
|
|
231
|
+
warnings,
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
209
235
|
if (format === 'obj') {
|
|
210
236
|
const exporter = new OBJExporter()
|
|
211
237
|
const result = exporter.parse(exportScene)
|
|
@@ -93,6 +93,57 @@ describe('buildFirstPersonColliderWorldFromRegistry', () => {
|
|
|
93
93
|
world?.dispose()
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
+
test('keeps a procedural static body but excludes moving groups', () => {
|
|
97
|
+
registerColliderDefinition('shelf', ShelfNode, 'furnish')
|
|
98
|
+
const shelf = ShelfNode.parse({ id: 'shelf_motion_test' })
|
|
99
|
+
setSceneNodes([shelf])
|
|
100
|
+
mountNode(shelf, [1, 1, 1], [0, 0.5, 0])
|
|
101
|
+
const root = sceneRegistry.nodes.get(shelf.id)!
|
|
102
|
+
const motion = new Group()
|
|
103
|
+
motion.userData.proceduralMotion = { nodeId: shelf.id, partId: 'drawer' }
|
|
104
|
+
const movingMesh = new Mesh(new BoxGeometry(1, 1, 1), new MeshBasicMaterial())
|
|
105
|
+
movingMesh.position.set(5, 0.5, 0)
|
|
106
|
+
motion.add(movingMesh)
|
|
107
|
+
root.add(motion)
|
|
108
|
+
root.updateMatrixWorld(true)
|
|
109
|
+
|
|
110
|
+
const world = buildFirstPersonColliderWorldFromRegistry()!
|
|
111
|
+
expect(world.bounds?.min.x).toBeCloseTo(-0.5)
|
|
112
|
+
expect(world.bounds?.max.x).toBeCloseTo(0.5)
|
|
113
|
+
world.dispose()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('an idle joint tree collides with its static split parts, not its merged rest pose', () => {
|
|
117
|
+
registerColliderDefinition('shelf', ShelfNode, 'furnish')
|
|
118
|
+
const shelf = ShelfNode.parse({ id: 'shelf_rest_test' })
|
|
119
|
+
setSceneNodes([shelf])
|
|
120
|
+
mountRegistryGroup(shelf)
|
|
121
|
+
const root = sceneRegistry.nodes.get(shelf.id)!
|
|
122
|
+
const box = (x: number) => {
|
|
123
|
+
const mesh = new Mesh(new BoxGeometry(1, 1, 1), new MeshBasicMaterial())
|
|
124
|
+
mesh.position.set(x, 0.5, 0)
|
|
125
|
+
return mesh
|
|
126
|
+
}
|
|
127
|
+
// The rest pose merges the body with the gate leaf at x = 5; the hidden split draws them apart.
|
|
128
|
+
const rest = new Group()
|
|
129
|
+
rest.userData.pascalProceduralRest = true
|
|
130
|
+
rest.add(box(0), box(5))
|
|
131
|
+
const split = new Group()
|
|
132
|
+
split.userData.pascalProceduralSplit = true
|
|
133
|
+
split.visible = false
|
|
134
|
+
const motion = new Group()
|
|
135
|
+
motion.userData.proceduralMotion = { nodeId: shelf.id, partId: 'gate' }
|
|
136
|
+
motion.add(box(5))
|
|
137
|
+
split.add(box(0), motion)
|
|
138
|
+
root.add(rest, split)
|
|
139
|
+
root.updateMatrixWorld(true)
|
|
140
|
+
|
|
141
|
+
const world = buildFirstPersonColliderWorldFromRegistry()!
|
|
142
|
+
expect(world.bounds?.min.x).toBeCloseTo(-0.5)
|
|
143
|
+
expect(world.bounds?.max.x).toBeCloseTo(0.5)
|
|
144
|
+
world.dispose()
|
|
145
|
+
})
|
|
146
|
+
|
|
96
147
|
test('standing clearance and floor hits survive a slab source joining and leaving a batch', () => {
|
|
97
148
|
registerColliderDefinition('slab', SlabNode, 'structure', 'floor')
|
|
98
149
|
const slab = SlabNode.parse({ id: 'slab_clearance_batch', polygon: [] })
|
|
@@ -356,8 +356,12 @@ function collectColliderGeometriesFromNode(
|
|
|
356
356
|
visitedMeshes.add(object)
|
|
357
357
|
|
|
358
358
|
// Prune hidden subtrees — children of an invisible group never render,
|
|
359
|
-
// so they must not collide either (see isEffectivelyVisible).
|
|
360
|
-
|
|
359
|
+
// so they must not collide either (see isEffectivelyVisible). An idle joint
|
|
360
|
+
// tree draws a merged rest pose that includes its moving parts; its hidden
|
|
361
|
+
// split group holds the same static parts apart from them.
|
|
362
|
+
if (object.userData.pascalProceduralRest) return
|
|
363
|
+
if (!object.visible && !object.userData.pascalProceduralSplit) return
|
|
364
|
+
if (object.userData.proceduralMotion) return
|
|
361
365
|
|
|
362
366
|
if (
|
|
363
367
|
isMesh(object) &&
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
type AnyNode,
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
registerNode,
|
|
6
|
+
sceneRegistry,
|
|
7
|
+
useInteractive,
|
|
8
|
+
useScene,
|
|
9
|
+
} from '@pascal-app/core'
|
|
10
|
+
import { Object3D } from 'three'
|
|
11
|
+
import { mechanismHudInteract, mechanismTargetIds } from './mechanism-targets'
|
|
12
|
+
|
|
13
|
+
const switchMechanism = {
|
|
14
|
+
has: (node: AnyNode) => (node as unknown as { joints: number }).joints > 0,
|
|
15
|
+
isOn: (node: AnyNode, state: ReturnType<typeof useInteractive.getState>) =>
|
|
16
|
+
Boolean(state.mechanisms[node.id]),
|
|
17
|
+
set: (node: AnyNode, on: boolean) => useInteractive.getState().setMechanism(node.id, on),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const mounted: [string, string][] = []
|
|
21
|
+
|
|
22
|
+
function mount(kind: string, id: string, fields: Record<string, unknown> = {}): AnyNode {
|
|
23
|
+
const node = { object: 'node', id, type: kind, ...fields } as unknown as AnyNode
|
|
24
|
+
useScene.setState((state) => ({ nodes: { ...state.nodes, [id]: node } }) as never)
|
|
25
|
+
sceneRegistry.nodes.set(id, new Object3D())
|
|
26
|
+
sceneRegistry.byType[kind]!.add(id)
|
|
27
|
+
mounted.push([kind, id])
|
|
28
|
+
return node
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function registerKind(capabilities: Record<string, unknown>): string {
|
|
32
|
+
const kind = `test:walk-${crypto.randomUUID()}`
|
|
33
|
+
registerNode({
|
|
34
|
+
kind,
|
|
35
|
+
schemaVersion: 1,
|
|
36
|
+
category: 'furnish',
|
|
37
|
+
defaults: () => ({}),
|
|
38
|
+
capabilities,
|
|
39
|
+
} as never)
|
|
40
|
+
return kind
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
for (const [kind, id] of mounted.splice(0)) {
|
|
45
|
+
sceneRegistry.nodes.delete(id)
|
|
46
|
+
sceneRegistry.byType[kind]!.delete(id)
|
|
47
|
+
useInteractive.getState().removeMechanism(id as AnyNodeId)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('walkthrough mechanism targets', () => {
|
|
52
|
+
test('any mounted node whose kind declares a mechanism is a target, once', () => {
|
|
53
|
+
const kind = registerKind({ mechanism: switchMechanism })
|
|
54
|
+
mount(kind, 'articulated_1', { joints: 2 })
|
|
55
|
+
mount(kind, 'articulated_still', { joints: 0 })
|
|
56
|
+
mount(registerKind({}), 'plain_1')
|
|
57
|
+
const covered = mount(kind, 'articulated_covered', { joints: 1 })
|
|
58
|
+
|
|
59
|
+
expect(mechanismTargetIds(new Set([covered.id]))).toEqual(['articulated_1' as AnyNodeId])
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('the HUD names the node and says what E will do', () => {
|
|
63
|
+
const kind = registerKind({ mechanism: switchMechanism })
|
|
64
|
+
const node = mount(kind, 'articulated_hud', { joints: 1, name: 'Desk lamp' })
|
|
65
|
+
expect(mechanismHudInteract(node)).toEqual({ label: 'Desk lamp', verb: 'turn on' })
|
|
66
|
+
useInteractive.getState().setMechanism(node.id, true)
|
|
67
|
+
expect(mechanismHudInteract(node)).toEqual({ label: 'Desk lamp', verb: 'turn off' })
|
|
68
|
+
|
|
69
|
+
const lid = mount(registerKind({ mechanism: { ...switchMechanism, verb: 'open' } }), 'lid_1', {
|
|
70
|
+
joints: 1,
|
|
71
|
+
})
|
|
72
|
+
expect(mechanismHudInteract(lid)).toEqual({ label: 'item', verb: 'open' })
|
|
73
|
+
expect(mechanismHudInteract(mount(registerKind({}), 'plain_hud'))).toBeNull()
|
|
74
|
+
})
|
|
75
|
+
})
|