@pascal-app/editor 1.0.6 → 1.1.0
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 +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +110 -42
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +77 -68
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +31 -11
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +458 -140
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +23 -1
- package/src/components/editor/index.tsx +150 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +2 -3
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +4 -3
- package/src/components/editor/room-handles-2d.tsx +158 -7
- package/src/components/editor/room-handles.tsx +244 -54
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +33 -17
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +15 -7
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +47 -19
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
- package/src/components/ui/helpers/helper-manager.tsx +136 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
- package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +11 -10
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +9 -5
- package/src/components/ui/panels/panel-wrapper.tsx +63 -127
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +465 -66
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +22 -13
- package/src/components/ui/panels/room-panel.tsx +207 -100
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +62 -5
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15849 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +39331 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +39331 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +39331 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +39331 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +224 -45
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +101 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -2
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +15 -6
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +24 -6
- package/src/lib/measurements.ts +1 -203
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +28 -17
- package/src/lib/room-built-on.ts +21 -17
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +141 -1
- package/src/lib/room-handle-drag.ts +554 -58
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +38 -1
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +11 -5
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +21 -6
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +17 -6
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +44 -0
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +53 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-snap-indicator.ts +15 -2
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -100
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/editor",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Pascal building editor component",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.tsx",
|
|
9
9
|
"./catalog": "./src/components/ui/item-catalog/catalog-items.tsx",
|
|
10
|
+
"./locales": "./src/i18n/locales.ts",
|
|
11
|
+
"./locales/pseudo": "./src/i18n/pseudo-locale.ts",
|
|
10
12
|
"./procedural-items": "./src/components/procedural-items/inspector.tsx",
|
|
11
13
|
"./distribution": "./src/components/tools/distribution/distribution-public.ts"
|
|
12
14
|
},
|
|
@@ -16,23 +18,24 @@
|
|
|
16
18
|
"test": "bun test src"
|
|
17
19
|
},
|
|
18
20
|
"peerDependencies": {
|
|
19
|
-
"@pascal-app/core": "^1.0
|
|
20
|
-
"@pascal-app/viewer": "^1.0
|
|
21
|
+
"@pascal-app/core": "^1.1.0",
|
|
22
|
+
"@pascal-app/viewer": "^1.1.0",
|
|
21
23
|
"@react-three/drei": "^10",
|
|
22
24
|
"@react-three/fiber": "^9",
|
|
23
25
|
"next": ">=15",
|
|
24
26
|
"react": "^18 || ^19",
|
|
25
27
|
"react-dom": "^18 || ^19",
|
|
26
|
-
"three": "^0.
|
|
28
|
+
"three": "^0.187"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
31
|
"@dnd-kit/core": "^6.3.1",
|
|
30
32
|
"@dnd-kit/sortable": "^10.0.0",
|
|
31
33
|
"@dnd-kit/utilities": "^3.2.2",
|
|
32
|
-
"@iconify/react": "^6.0.
|
|
34
|
+
"@iconify/react": "^6.0.4",
|
|
35
|
+
"@lingui/core": "6.12.0",
|
|
33
36
|
"@number-flow/react": "^0.6.2",
|
|
34
|
-
"@pascal-app/geometry-script": "^1.0
|
|
35
|
-
"@pascal-app/ifc-converter": "^1.0
|
|
37
|
+
"@pascal-app/geometry-script": "^1.1.0",
|
|
38
|
+
"@pascal-app/ifc-converter": "^1.1.0",
|
|
36
39
|
"@pascal-app/lingo": "^0.5.0",
|
|
37
40
|
"@radix-ui/react-alert-dialog": "^1.2.1",
|
|
38
41
|
"@radix-ui/react-context-menu": "^2.4.1",
|
|
@@ -53,31 +56,31 @@
|
|
|
53
56
|
"cmdk": "^1.1.1",
|
|
54
57
|
"fflate": "^0.8.3",
|
|
55
58
|
"howler": "^2.2.4",
|
|
56
|
-
"lucide-react": "^1.
|
|
59
|
+
"lucide-react": "^1.69.0",
|
|
57
60
|
"manifold-3d": "3.5.4",
|
|
58
61
|
"mitt": "^3.0.1",
|
|
59
|
-
"motion": "^14.
|
|
62
|
+
"motion": "^14.9.1",
|
|
60
63
|
"nanoid": "^6.0.2",
|
|
61
64
|
"pdfkit": "^0.20.2",
|
|
62
65
|
"tailwind-merge": "^3.7.0",
|
|
63
66
|
"three-bvh-csg": "^0.0.18",
|
|
64
|
-
"three-mesh-bvh": "~0.9.
|
|
67
|
+
"three-mesh-bvh": "~0.9.18",
|
|
65
68
|
"zod": ">=4.6.7 <4.7",
|
|
66
69
|
"zustand": "^5.0.16"
|
|
67
70
|
},
|
|
68
71
|
"devDependencies": {
|
|
69
|
-
"@pascal-app/core": "^1.0
|
|
70
|
-
"@pascal-app/viewer": "^1.0
|
|
72
|
+
"@pascal-app/core": "^1.1.0",
|
|
73
|
+
"@pascal-app/viewer": "^1.1.0",
|
|
71
74
|
"@pascal/typescript-config": "*",
|
|
72
75
|
"@react-three/test-renderer": "^9.1.1",
|
|
73
76
|
"@types/blob-stream": "^0.1.33",
|
|
74
|
-
"@types/bun": "^1.4.
|
|
77
|
+
"@types/bun": "^1.4.4",
|
|
75
78
|
"@types/howler": "^2.2.13",
|
|
76
79
|
"@types/pdfkit": "^0.17.7",
|
|
77
80
|
"@types/react": "19.3.0",
|
|
78
81
|
"@types/react-dom": "19.3.0",
|
|
79
|
-
"@types/three": "^0.
|
|
80
|
-
"fast-xml-parser": "^5.
|
|
82
|
+
"@types/three": "^0.187",
|
|
83
|
+
"fast-xml-parser": "^5.12.0",
|
|
81
84
|
"typescript": "7.0.2"
|
|
82
85
|
},
|
|
83
86
|
"repository": {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import type { MouseEventHandler, ReactNode } from 'react'
|
|
5
|
+
import { toolHintKey } from '../../i18n/node-messages'
|
|
4
6
|
import { cn } from '../../lib/utils'
|
|
5
7
|
import { shortcutDisplayValue } from '../ui/primitives/shortcut-token'
|
|
6
8
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
|
|
@@ -37,6 +39,7 @@ export function ActionMenuButton({
|
|
|
37
39
|
pressed?: boolean
|
|
38
40
|
children: ReactNode
|
|
39
41
|
}) {
|
|
42
|
+
const i18n = usePascalI18n()
|
|
40
43
|
return (
|
|
41
44
|
<Tooltip>
|
|
42
45
|
<TooltipTrigger asChild>
|
|
@@ -72,7 +75,7 @@ export function ActionMenuButton({
|
|
|
72
75
|
className="rounded border border-background/30 px-1 font-mono text-[10px] leading-4 opacity-80"
|
|
73
76
|
key={key}
|
|
74
77
|
>
|
|
75
|
-
{shortcutDisplayValue(key)}
|
|
78
|
+
{toolHintKey(shortcutDisplayValue(key), i18n)}
|
|
76
79
|
</kbd>
|
|
77
80
|
))}
|
|
78
81
|
</span>
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { type AlignmentGuide, sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Html } from '@react-three/drei'
|
|
6
5
|
import { useFrame } from '@react-three/fiber'
|
|
7
6
|
import { memo, useMemo, useRef } from 'react'
|
|
8
|
-
import { BoxGeometry, CircleGeometry, type Group, Vector3 } from 'three'
|
|
7
|
+
import { BoxGeometry, CircleGeometry, type Group, RingGeometry, Vector3 } from 'three'
|
|
9
8
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
10
9
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
11
|
-
import useAlignmentGuides
|
|
10
|
+
import useAlignmentGuides, {
|
|
11
|
+
type DraftGuideLine,
|
|
12
|
+
type DraftGuides,
|
|
13
|
+
} from '../../store/use-alignment-guides'
|
|
14
|
+
import { Html } from './localized-html'
|
|
12
15
|
import { formatMeasurement } from './measurement-pill'
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -22,13 +25,18 @@ import { formatMeasurement } from './measurement-pill'
|
|
|
22
25
|
* the design reference — so they're real 3D geometry, not screen billboards.
|
|
23
26
|
* Only the distance pill is screen-space (`<Html>`).
|
|
24
27
|
*
|
|
25
|
-
* Guide coordinates are XZ meters in the
|
|
26
|
-
* on the
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Guide coordinates are XZ meters in the active building's LOCAL frame —
|
|
29
|
+
* alignment resolves on the building's own axes, the axes its 3D grid is laid
|
|
30
|
+
* on — and the layer is mounted inside the building-local ToolManager group.
|
|
31
|
+
* The whole ribbon is lifted to the active level's WORLD Y each frame so it
|
|
32
|
+
* lies on the floor being edited — not the building base — when floors are
|
|
33
|
+
* stacked.
|
|
34
|
+
*
|
|
35
|
+
* A wall pulled from a wall end (and the run it continues) publishes its
|
|
36
|
+
* projection guides beside them (`useAlignmentGuides.draft`): the extension of
|
|
37
|
+
* the wall it comes from in amber, lines through other walls' ends in indigo,
|
|
38
|
+
* lines through the run's start in emerald, all dashed, with a ringed marker
|
|
39
|
+
* where two of them cross.
|
|
32
40
|
*/
|
|
33
41
|
|
|
34
42
|
const LINE_COLOR = 0x81_8c_f8 // indigo-400 — matches the editor's selection accent (box-select / wall highlights)
|
|
@@ -59,11 +67,33 @@ const dotMaterial = new MeshBasicNodeMaterial({
|
|
|
59
67
|
})
|
|
60
68
|
const DASH_GEOMETRY = new BoxGeometry(1, 1, 1)
|
|
61
69
|
const DOT_GEOMETRY = new CircleGeometry(1, 24)
|
|
70
|
+
const RING_GEOMETRY = new RingGeometry(0.7, 1, 28)
|
|
71
|
+
|
|
72
|
+
// Draft projection guides: one material per kind, shared like the above.
|
|
73
|
+
const draftGuideMaterial = (color: number) =>
|
|
74
|
+
new MeshBasicNodeMaterial({
|
|
75
|
+
color,
|
|
76
|
+
depthTest: false,
|
|
77
|
+
depthWrite: false,
|
|
78
|
+
toneMapped: false,
|
|
79
|
+
transparent: true,
|
|
80
|
+
opacity: 0.9,
|
|
81
|
+
})
|
|
82
|
+
const DRAFT_GUIDE_MATERIALS: Record<DraftGuideLine['kind'], MeshBasicNodeMaterial> = {
|
|
83
|
+
extension: draftGuideMaterial(0xf5_9e_0b), // amber-500
|
|
84
|
+
alignment: guideMaterial,
|
|
85
|
+
'run-start': draftGuideMaterial(0x10_b9_81), // emerald-500 — the pull grip's colour
|
|
86
|
+
}
|
|
87
|
+
const DRAFT_DASH_LEN = 0.14
|
|
88
|
+
const DRAFT_DASH_GAP = 0.12
|
|
89
|
+
const DRAFT_LINE_WIDTH = 0.035
|
|
90
|
+
const INTERSECTION_RADIUS = 0.13
|
|
62
91
|
|
|
63
92
|
type Vec3 = [number, number, number]
|
|
64
93
|
|
|
65
94
|
export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
|
|
66
95
|
const guides = useAlignmentGuides((s) => s.guides)
|
|
96
|
+
const draft = useAlignmentGuides((s) => s.draft)
|
|
67
97
|
const levelId = useViewer((s) => s.selection.levelId)
|
|
68
98
|
const unit = useViewer((s) => s.unit)
|
|
69
99
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
@@ -82,16 +112,77 @@ export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
|
|
|
82
112
|
group.position.y = levelMesh ? levelMesh.getWorldPosition(worldYWork).y : 0
|
|
83
113
|
})
|
|
84
114
|
|
|
85
|
-
if (guides.length === 0) return null
|
|
115
|
+
if (guides.length === 0 && !draft) return null
|
|
86
116
|
return (
|
|
87
117
|
<group ref={groupRef}>
|
|
88
118
|
{guides.map((guide, i) => (
|
|
89
119
|
<GuideLine guide={guide} key={i} metricNotation={metricNotation} unit={unit} />
|
|
90
120
|
))}
|
|
121
|
+
{draft ? <DraftGuidesLayer draft={draft} /> : null}
|
|
91
122
|
</group>
|
|
92
123
|
)
|
|
93
124
|
})
|
|
94
125
|
|
|
126
|
+
/** A wall draft's dashed projection guides and the markers where they cross. */
|
|
127
|
+
function DraftGuidesLayer({ draft }: { draft: DraftGuides }) {
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
{draft.lines.map((line, i) => (
|
|
131
|
+
<DraftLine key={`l${i}`} line={line} />
|
|
132
|
+
))}
|
|
133
|
+
{draft.intersections.map((point, i) => (
|
|
134
|
+
<mesh
|
|
135
|
+
geometry={RING_GEOMETRY}
|
|
136
|
+
key={`x${i}`}
|
|
137
|
+
layers={EDITOR_LAYER}
|
|
138
|
+
material={guideMaterial}
|
|
139
|
+
position={[point[0], GUIDE_Y + 0.002, point[1]]}
|
|
140
|
+
renderOrder={1001}
|
|
141
|
+
rotation={[-Math.PI / 2, 0, 0]}
|
|
142
|
+
scale={[INTERSECTION_RADIUS, INTERSECTION_RADIUS, INTERSECTION_RADIUS]}
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
</>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function DraftLine({ line }: { line: DraftGuideLine }) {
|
|
150
|
+
const [fx, fz] = line.from
|
|
151
|
+
const [tx, tz] = line.to
|
|
152
|
+
const { dashes, angleY } = useMemo(() => {
|
|
153
|
+
const dx = tx - fx
|
|
154
|
+
const dz = tz - fz
|
|
155
|
+
const length = Math.hypot(dx, dz)
|
|
156
|
+
const angle = -Math.atan2(dz, dx)
|
|
157
|
+
if (length < 1e-4) return { dashes: [] as Vec3[], angleY: angle }
|
|
158
|
+
const ux = dx / length
|
|
159
|
+
const uz = dz / length
|
|
160
|
+
const period = Math.max(DRAFT_DASH_LEN + DRAFT_DASH_GAP, length / MAX_DASHES)
|
|
161
|
+
const centres: Vec3[] = []
|
|
162
|
+
for (let d = period / 2; d - DRAFT_DASH_LEN / 2 <= length; d += period) {
|
|
163
|
+
centres.push([fx + ux * d, GUIDE_Y, fz + uz * d])
|
|
164
|
+
}
|
|
165
|
+
return { dashes: centres, angleY: angle }
|
|
166
|
+
}, [fx, fz, tx, tz])
|
|
167
|
+
const material = DRAFT_GUIDE_MATERIALS[line.kind]
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
{dashes.map((centre, i) => (
|
|
171
|
+
<mesh
|
|
172
|
+
geometry={DASH_GEOMETRY}
|
|
173
|
+
key={i}
|
|
174
|
+
layers={EDITOR_LAYER}
|
|
175
|
+
material={material}
|
|
176
|
+
position={centre}
|
|
177
|
+
renderOrder={1000}
|
|
178
|
+
rotation={[0, angleY, 0]}
|
|
179
|
+
scale={[DRAFT_DASH_LEN, 0.002, DRAFT_LINE_WIDTH]}
|
|
180
|
+
/>
|
|
181
|
+
))}
|
|
182
|
+
</>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
95
186
|
function GuideLine({
|
|
96
187
|
guide,
|
|
97
188
|
metricNotation,
|
|
@@ -47,8 +47,9 @@ const THUMBNAIL_BACKDROP = { inner: '#FAFAFA', outer: '#E4E4E4' }
|
|
|
47
47
|
const THUMBNAIL_SHADOW_CATCHER_OPACITY = 0.7
|
|
48
48
|
/** Soft shade hugging the ground-floor walls so the building never floats. */
|
|
49
49
|
const THUMBNAIL_WALL_CONTACT = { opacity: 0.5, spreadM: 0.9 }
|
|
50
|
-
/**
|
|
51
|
-
* prepared-drywall default, which reads as unfinished
|
|
50
|
+
/** A roof's unpainted trim and gable bands render as smooth plaster rather
|
|
51
|
+
* than the screw-dotted prepared-drywall default, which reads as unfinished
|
|
52
|
+
* at card size. Unpainted walls already wear the interior paint. */
|
|
52
53
|
const THUMBNAIL_SLOT_DEFAULT_OVERRIDES = {
|
|
53
54
|
'library:concrete-drywall': 'library:concrete-plaster',
|
|
54
55
|
} as const
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type Camera,
|
|
30
30
|
type OrthographicCamera,
|
|
31
31
|
type PerspectiveCamera,
|
|
32
|
+
Sphere,
|
|
32
33
|
Spherical,
|
|
33
34
|
Vector3,
|
|
34
35
|
} from 'three'
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
orbitFloorMinY,
|
|
63
64
|
overviewEyeY,
|
|
64
65
|
} from '../../lib/orbit-floor-clearance'
|
|
66
|
+
import { orbitNearPlane } from '../../lib/orbit-near-plane'
|
|
65
67
|
import { editorOwnsOneFingerDrag } from '../../lib/touch-gesture-priority'
|
|
66
68
|
import { frameViewerCamera } from '../../lib/viewer-selection'
|
|
67
69
|
import { publishCameraPose } from '../../store/camera-pose-store'
|
|
@@ -73,6 +75,13 @@ import {
|
|
|
73
75
|
} from '../../store/use-interaction-scope'
|
|
74
76
|
import { visibleScene } from '../../store/view-layout'
|
|
75
77
|
import { createCameraDraggingLifecycle } from './camera-dragging-lifecycle'
|
|
78
|
+
import {
|
|
79
|
+
type FitSceneLookAt,
|
|
80
|
+
type PendingFitScene,
|
|
81
|
+
planFitSceneOnEvent,
|
|
82
|
+
planFitSceneOnOrbitResume,
|
|
83
|
+
planLevelFollow,
|
|
84
|
+
} from './fit-scene-framing'
|
|
76
85
|
import { FloorAwareCameraControls } from './floor-aware-camera-controls'
|
|
77
86
|
import { FloorSurface } from './floor-surface'
|
|
78
87
|
|
|
@@ -83,6 +92,7 @@ const tempDelta = new Vector3()
|
|
|
83
92
|
const tempPosition = new Vector3()
|
|
84
93
|
const tempSize = new Vector3()
|
|
85
94
|
const tempTarget = new Vector3()
|
|
95
|
+
const fitSceneSphere = new Sphere()
|
|
86
96
|
const transitionFreezePosition = new Vector3()
|
|
87
97
|
const transitionFreezeTarget = new Vector3()
|
|
88
98
|
const keyboardPanSpherical = new Spherical()
|
|
@@ -119,6 +129,23 @@ function keepEyeOverGround(x: number, eyeY: number, z: number, previousEyeY = ey
|
|
|
119
129
|
return overviewEyeY(eyeY, previousEyeY, groundY, useViewer.getState().eyeUnderGround)
|
|
120
130
|
}
|
|
121
131
|
|
|
132
|
+
function applyFitLookAt(control: CameraControlsImpl, lookAt: FitSceneLookAt) {
|
|
133
|
+
control.setLookAt(
|
|
134
|
+
lookAt.eyeX,
|
|
135
|
+
keepEyeOverGround(lookAt.eyeX, lookAt.eyeY, lookAt.eyeZ, lookAt.eyeHeight),
|
|
136
|
+
lookAt.eyeZ,
|
|
137
|
+
lookAt.targetX,
|
|
138
|
+
lookAt.targetY,
|
|
139
|
+
lookAt.targetZ,
|
|
140
|
+
true,
|
|
141
|
+
)
|
|
142
|
+
if (isOrthographicCamera(control.camera)) {
|
|
143
|
+
fitSceneSphere.center.set(...lookAt.fitSphere.center)
|
|
144
|
+
fitSceneSphere.radius = lookAt.fitSphere.radius
|
|
145
|
+
control.fitToSphere(fitSceneSphere, true)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
122
149
|
function levelOf(nodeId: string): string | null {
|
|
123
150
|
const nodes = useScene.getState().nodes
|
|
124
151
|
let node: AnyNode | undefined = nodes[nodeId as AnyNodeId]
|
|
@@ -406,6 +433,9 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
406
433
|
if (selection.levelId) lastLevelId.current = selection.levelId
|
|
407
434
|
const currentLevelId = lastLevelId.current
|
|
408
435
|
const firstLoad = useRef(true)
|
|
436
|
+
// Survives first-person (orbit unmounted) so scene-ready fit still applies
|
|
437
|
+
// once CameraControls remount.
|
|
438
|
+
const pendingFitSceneRef = useRef<PendingFitScene | null>(null)
|
|
409
439
|
const floorY = useScene((state) =>
|
|
410
440
|
currentLevelId ? getLevelPresentationY(currentLevelId, state.nodes, levelMode) : 0,
|
|
411
441
|
)
|
|
@@ -614,19 +644,32 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
614
644
|
}
|
|
615
645
|
}, [scene, gl, camera])
|
|
616
646
|
|
|
647
|
+
const previousLevelIdRef = useRef<string | null>(null)
|
|
648
|
+
const previousLevelModeRef = useRef(levelMode)
|
|
649
|
+
const previousCameraRef = useRef(camera)
|
|
617
650
|
// `camera` too: drei builds a fresh controls instance (pivot at the origin)
|
|
618
651
|
// when the default camera mounts or changes; it is rested on the floor too.
|
|
619
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: camera re-runs this on a new instance
|
|
620
652
|
useEffect(() => {
|
|
621
653
|
if (isPreviewMode || isFirstPersonMode || isRestoringFirstPersonPose()) return
|
|
622
654
|
// Analytic destination, not `sceneRegistry` mesh position: a level created
|
|
623
655
|
// this frame still sits at y=0 (LevelSystem lerps it later), and a mode
|
|
624
|
-
// switch leaves every level mid-lerp
|
|
656
|
+
// switch leaves every level mid-lerp - the camera must pan to where the
|
|
625
657
|
// level will settle, in the CURRENT presentation mode.
|
|
626
658
|
const targetY = orbitGroundY(currentLevelId, levelMode)
|
|
627
659
|
if (!controls.current) return
|
|
628
|
-
|
|
629
|
-
firstLoad.current
|
|
660
|
+
const plan = planLevelFollow({
|
|
661
|
+
firstLoad: firstLoad.current,
|
|
662
|
+
sceneEmpty: Object.keys(useScene.getState().nodes).length === 0,
|
|
663
|
+
levelChanged: previousLevelIdRef.current !== currentLevelId,
|
|
664
|
+
modeChanged: previousLevelModeRef.current !== levelMode,
|
|
665
|
+
cameraChanged: previousCameraRef.current !== camera,
|
|
666
|
+
})
|
|
667
|
+
firstLoad.current = false
|
|
668
|
+
previousLevelIdRef.current = currentLevelId
|
|
669
|
+
previousLevelModeRef.current = levelMode
|
|
670
|
+
previousCameraRef.current = camera
|
|
671
|
+
if (plan === 'none') return
|
|
672
|
+
if (plan === 'default-pose') {
|
|
630
673
|
controls.current.setLookAt(
|
|
631
674
|
20,
|
|
632
675
|
keepEyeOverGround(20, 20 + targetY, 20, 20),
|
|
@@ -636,6 +679,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
636
679
|
0,
|
|
637
680
|
true,
|
|
638
681
|
)
|
|
682
|
+
return
|
|
639
683
|
}
|
|
640
684
|
controls.current.getOrbitTarget(currentTarget)
|
|
641
685
|
// Idempotence guard: skip when already there — also swallows the thumbnail
|
|
@@ -691,6 +735,19 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
691
735
|
controls.current.floorSurface = diveFloor === null ? floorSurface : null
|
|
692
736
|
}, [camera, currentLevelId, diveFloor, floorMinY, floorSurface, isFirstPersonMode])
|
|
693
737
|
|
|
738
|
+
const flushPendingFitScene = useCallback(() => {
|
|
739
|
+
const plan = planFitSceneOnOrbitResume({
|
|
740
|
+
isPreviewMode,
|
|
741
|
+
isFirstPersonMode: useEditor.getState().isFirstPersonMode,
|
|
742
|
+
hasControls: !!controls.current,
|
|
743
|
+
pending: pendingFitSceneRef.current,
|
|
744
|
+
groundY: orbitGroundY(lastLevelId.current),
|
|
745
|
+
})
|
|
746
|
+
if (plan.action !== 'apply' || !controls.current) return
|
|
747
|
+
pendingFitSceneRef.current = null
|
|
748
|
+
applyFitLookAt(controls.current, plan.lookAt)
|
|
749
|
+
}, [isPreviewMode])
|
|
750
|
+
|
|
694
751
|
const focusNode = useCallback(
|
|
695
752
|
(nodeId: string) => {
|
|
696
753
|
if (isPreviewMode || isFirstPersonMode || !controls.current) return
|
|
@@ -766,8 +823,16 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
766
823
|
}, [camera, isFirstPersonMode, viewportSize])
|
|
767
824
|
|
|
768
825
|
const handleCameraUpdate = useCallback(() => {
|
|
826
|
+
const control = controls.current
|
|
827
|
+
if (control && isPerspectiveCamera(camera)) {
|
|
828
|
+
const near = orbitNearPlane(control.distance)
|
|
829
|
+
if (Math.abs(camera.near - near) > 1e-4) {
|
|
830
|
+
camera.near = near
|
|
831
|
+
camera.updateProjectionMatrix()
|
|
832
|
+
}
|
|
833
|
+
}
|
|
769
834
|
publishCurrentPose()
|
|
770
|
-
}, [publishCurrentPose])
|
|
835
|
+
}, [camera, publishCurrentPose])
|
|
771
836
|
|
|
772
837
|
useEffect(() => {
|
|
773
838
|
publishInitialCameraPose(publishCurrentPose)
|
|
@@ -1054,7 +1119,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1054
1119
|
updateNavigationCursor()
|
|
1055
1120
|
}
|
|
1056
1121
|
|
|
1057
|
-
const onWheel = () => {
|
|
1122
|
+
const onWheel = (event: WheelEvent) => {
|
|
1123
|
+
// Zooming in carries on past the closest orbit, the target with it; zooming out stops at
|
|
1124
|
+
// the farthest, or the eye would drift off the site. Seen before the controls' own listener.
|
|
1125
|
+
if (controls.current) controls.current.infinityDolly = event.deltaY < 0
|
|
1058
1126
|
beginLocalCameraInteraction()
|
|
1059
1127
|
cameraDraggingLifecycle.scheduleEnd()
|
|
1060
1128
|
}
|
|
@@ -1376,37 +1444,22 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1376
1444
|
}
|
|
1377
1445
|
|
|
1378
1446
|
const handleFitScene = ({ bounds }: CameraControlFitSceneEvent) => {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
)
|
|
1447
|
+
const plan = planFitSceneOnEvent({
|
|
1448
|
+
isPreviewMode,
|
|
1449
|
+
isFirstPersonMode,
|
|
1450
|
+
hasControls: !!controls.current,
|
|
1451
|
+
bounds: bounds ?? null,
|
|
1452
|
+
groundY: orbitGroundY(lastLevelId.current),
|
|
1453
|
+
})
|
|
1454
|
+
if (plan.action === 'ignore') return
|
|
1455
|
+
if (plan.action === 'queue') {
|
|
1456
|
+
pendingFitSceneRef.current = plan.pending
|
|
1457
|
+
// Orbit path with a not-yet-attached ref: retry next frame.
|
|
1458
|
+
if (!isFirstPersonMode) requestAnimationFrame(flushPendingFitScene)
|
|
1392
1459
|
return
|
|
1393
1460
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
// Use the longer horizontal extent to size the orbit radius so the whole
|
|
1397
|
-
// footprint sits in view regardless of aspect ratio.
|
|
1398
|
-
const maxExtent = Math.max(w, d)
|
|
1399
|
-
const distance = Math.max(maxExtent * 1.4, 15)
|
|
1400
|
-
const height = Math.max(maxExtent * 0.8, 10)
|
|
1401
|
-
controls.current.setLookAt(
|
|
1402
|
-
cx + distance * 0.7,
|
|
1403
|
-
keepEyeOverGround(cx + distance * 0.7, height + groundY, cz + distance * 0.7, height),
|
|
1404
|
-
cz + distance * 0.7,
|
|
1405
|
-
cx,
|
|
1406
|
-
groundY,
|
|
1407
|
-
cz,
|
|
1408
|
-
true,
|
|
1409
|
-
)
|
|
1461
|
+
pendingFitSceneRef.current = null
|
|
1462
|
+
if (controls.current) applyFitLookAt(controls.current, plan.lookAt)
|
|
1410
1463
|
}
|
|
1411
1464
|
|
|
1412
1465
|
emitter.on('camera-controls:capture', handleNodeCapture)
|
|
@@ -1428,7 +1481,22 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1428
1481
|
emitter.off('camera-controls:orbit-ccw', handleOrbitCCW)
|
|
1429
1482
|
emitter.off('camera-controls:fit-scene', handleFitScene)
|
|
1430
1483
|
}
|
|
1431
|
-
}, [
|
|
1484
|
+
}, [
|
|
1485
|
+
camera,
|
|
1486
|
+
cancelPoseApplication,
|
|
1487
|
+
flushPendingFitScene,
|
|
1488
|
+
focusNode,
|
|
1489
|
+
isPreviewMode,
|
|
1490
|
+
isFirstPersonMode,
|
|
1491
|
+
])
|
|
1492
|
+
|
|
1493
|
+
// Apply a fit that arrived while first-person (orbit unmounted). Wait one
|
|
1494
|
+
// frame so CameraControls can remount and attach its ref.
|
|
1495
|
+
useEffect(() => {
|
|
1496
|
+
if (isFirstPersonMode || isPreviewMode || !pendingFitSceneRef.current) return
|
|
1497
|
+
const frame = requestAnimationFrame(flushPendingFitScene)
|
|
1498
|
+
return () => cancelAnimationFrame(frame)
|
|
1499
|
+
}, [flushPendingFitScene, isFirstPersonMode, isPreviewMode])
|
|
1432
1500
|
|
|
1433
1501
|
const onTransitionStart = useCallback(() => {
|
|
1434
1502
|
cameraDraggingLifecycle.begin()
|
|
@@ -1452,13 +1520,12 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1452
1520
|
}
|
|
1453
1521
|
}, [cameraDraggingLifecycle])
|
|
1454
1522
|
|
|
1455
|
-
//
|
|
1456
|
-
// so the
|
|
1457
|
-
//
|
|
1458
|
-
//
|
|
1459
|
-
// navigation guardrails.
|
|
1523
|
+
// The wheel dollies toward the cursor and, at the closest orbit, carries the
|
|
1524
|
+
// target on (infinity dolly), so the eye goes right up to a window's trim
|
|
1525
|
+
// or through a doorway rather than stopping short of the orbit's pivot.
|
|
1526
|
+
// Preset capture frames a single small subtree and orbits it closer still.
|
|
1460
1527
|
const isPresetCapture = captureMode.mode === 'preset'
|
|
1461
|
-
const minDistance = isPresetCapture ? 0.
|
|
1528
|
+
const minDistance = isPresetCapture ? 0.25 : 0.5
|
|
1462
1529
|
// Over modeled or mapped surroundings (a neighbourhood instead of the
|
|
1463
1530
|
// horizon disc) the camera may pull back far enough to take it all in.
|
|
1464
1531
|
const groundReplaced = useSceneGroundReplacement()
|
|
@@ -1471,6 +1538,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) =
|
|
|
1471
1538
|
return (
|
|
1472
1539
|
<CameraControls
|
|
1473
1540
|
azimuthRotateSpeed={ORBIT_ROTATE_SPEED}
|
|
1541
|
+
dollyToCursor
|
|
1474
1542
|
makeDefault
|
|
1475
1543
|
maxDistance={maxDistance}
|
|
1476
1544
|
impl={FloorAwareCameraControls}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
FenceNode,
|
|
10
10
|
generateId,
|
|
11
11
|
ItemNode,
|
|
12
|
+
isNodeDuplicable,
|
|
12
13
|
nodeRegistry,
|
|
13
14
|
RoofSegmentNode,
|
|
14
15
|
runSceneHistoryDraftWrite,
|
|
@@ -48,7 +49,7 @@ export function copyCollectionIds(copy: AnyNode): CollectionId[] {
|
|
|
48
49
|
* every inspector's Duplicate. The copy is in the collections its source is in.
|
|
49
50
|
*/
|
|
50
51
|
export function duplicateNodeAndPickUp(node: AnyNode) {
|
|
51
|
-
if (!node.parentId) return
|
|
52
|
+
if (!(node.parentId && isNodeDuplicable(node))) return
|
|
52
53
|
if (startZoneRoomTransform(node, 'duplicate')) return
|
|
53
54
|
sfxEmitter.emit('sfx:item-pick')
|
|
54
55
|
const { setMovingNode } = useEditor.getState()
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Html } from '@react-three/drei'
|
|
6
5
|
import { useFrame } from '@react-three/fiber'
|
|
7
6
|
import { memo, useMemo, useRef } from 'react'
|
|
8
7
|
import { BoxGeometry, CircleGeometry, type Group } from 'three'
|
|
9
8
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
10
9
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
11
10
|
import useElevationGuides from '../../store/use-elevation-guides'
|
|
11
|
+
import { Html } from './localized-html'
|
|
12
12
|
import { formatMeasurement } from './measurement-pill'
|
|
13
13
|
|
|
14
14
|
const LINE_COLOR = 0x81_8c_f8
|