@pascal-app/editor 1.0.0-beta.4 → 1.0.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/LICENSE +21 -0
- package/bunfig.toml +4 -0
- package/package.json +15 -8
- package/scripts/generate-print-golden-house.ts +89 -0
- package/src/components/editor/alignment-3d-guide-layer.tsx +12 -3
- package/src/components/editor/bake-exporter.tsx +1 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
- package/src/components/editor/capture-camera-rig.tsx +59 -0
- package/src/components/editor/custom-camera-controls.tsx +20 -11
- package/src/components/editor/editor-layout-mobile.tsx +6 -6
- package/src/components/editor/editor-layout-v2.tsx +17 -5
- package/src/components/editor/elevation-3d-guide-layer.tsx +3 -2
- package/src/components/editor/export-manager.tsx +175 -33
- package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
- package/src/components/editor/first-person-controls.tsx +182 -57
- package/src/components/editor/floating-action-menu.tsx +55 -55
- package/src/components/editor/floorplan-background-selection.test.ts +31 -4
- package/src/components/editor/floorplan-background-selection.ts +27 -7
- package/src/components/editor/floorplan-panel.tsx +81 -57
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-floating-action-menu.tsx +30 -15
- package/src/components/editor/group-move-3d.ts +51 -21
- package/src/components/editor/group-transform-shared.test.ts +78 -0
- package/src/components/editor/group-transform-shared.ts +74 -7
- package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
- package/src/components/editor/handles/handle-arrow.tsx +109 -48
- package/src/components/editor/handles/linear-resize-drag.ts +67 -0
- package/src/components/editor/handles/resize-snap.test.ts +46 -0
- package/src/components/editor/handles/resize-snap.ts +10 -1
- package/src/components/editor/handles/use-handle-drag.ts +18 -1
- package/src/components/editor/index.tsx +252 -46
- package/src/components/editor/measurement-pill.tsx +12 -12
- package/src/components/editor/node-action-menu.tsx +42 -1
- package/src/components/editor/node-arrow-handles.tsx +47 -58
- package/src/components/editor/opening-guides-3d-layer.tsx +13 -4
- package/src/components/editor/selection-manager.tsx +132 -47
- package/src/components/editor/site-edge-labels.tsx +2 -1
- package/src/components/editor/snapshot-capture-overlay.tsx +309 -41
- package/src/components/editor/snapshot-capture.test.ts +422 -0
- package/src/components/editor/snapshot-capture.ts +145 -0
- package/src/components/editor/thumbnail-generator.tsx +295 -232
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-measurement-label.tsx +3 -2
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +23 -16
- package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +204 -114
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
- package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
- package/src/components/systems/roof/roof-edit-system.tsx +10 -19
- package/src/components/systems/selection-affordance-manager.tsx +59 -9
- package/src/components/systems/selection-affordance-services.ts +19 -0
- package/src/components/systems/stair/stair-edit-system.tsx +5 -2
- package/src/components/tools/fence/fence-drafting.test.ts +100 -0
- package/src/components/tools/fence/fence-drafting.ts +6 -25
- package/src/components/tools/item/draft-creation.test.ts +12 -0
- package/src/components/tools/item/draft-creation.ts +10 -0
- package/src/components/tools/item/face-host-commit.test.ts +65 -0
- package/src/components/tools/item/face-host-commit.ts +56 -0
- package/src/components/tools/item/face-host-preview.test.ts +77 -0
- package/src/components/tools/item/face-host-preview.ts +82 -0
- package/src/components/tools/item/move-tool.tsx +4 -3
- package/src/components/tools/item/placement-math.test.ts +11 -1
- package/src/components/tools/item/placement-math.ts +0 -20
- package/src/components/tools/item/placement-strategies.test.ts +494 -0
- package/src/components/tools/item/placement-strategies.ts +192 -24
- package/src/components/tools/item/placement-surface.test.ts +61 -0
- package/src/components/tools/item/placement-surface.ts +24 -0
- package/src/components/tools/item/placement-types.ts +6 -0
- package/src/components/tools/item/test-face-host.ts +121 -0
- package/src/components/tools/item/use-draft-node.test.tsx +336 -0
- package/src/components/tools/item/use-draft-node.ts +26 -4
- package/src/components/tools/item/use-placement-coordinator.tsx +447 -180
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +306 -110
- package/src/components/tools/registry-tool-context.tsx +30 -0
- package/src/components/tools/select/box-select-tool.tsx +3 -1
- package/src/components/tools/select/marquee-footprint.test.ts +53 -0
- package/src/components/tools/select/marquee-footprint.ts +29 -0
- package/src/components/tools/shared/placement-box.tsx +63 -8
- package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
- package/src/components/tools/shared/pointer-support-cap.ts +45 -8
- package/src/components/tools/site/terrain-brush-cursor.tsx +2 -1
- package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
- package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
- package/src/components/tools/stair/stair-tool.tsx +127 -74
- package/src/components/tools/tool-manager.tsx +25 -8
- package/src/components/tools/wall/wall-drafting.test.ts +527 -4
- package/src/components/tools/wall/wall-drafting.ts +55 -462
- package/src/components/ui/action-menu/control-modes.tsx +9 -6
- package/src/components/ui/action-menu/index.tsx +11 -1
- package/src/components/ui/command-palette/editor-commands.tsx +12 -13
- package/src/components/ui/command-palette/index.tsx +7 -2
- package/src/components/ui/controls/material-paint-panel.tsx +6 -4
- package/src/components/ui/controls/material-picker.tsx +4 -4
- package/src/components/ui/controls/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +19 -3
- package/src/components/ui/controls/toggle-control.tsx +18 -5
- package/src/components/ui/controls/tool-options-panel.tsx +126 -0
- package/src/components/ui/floating-level-selector.tsx +23 -6
- package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
- package/src/components/ui/helpers/helper-manager.tsx +47 -11
- package/src/components/ui/helpers/item-helper.tsx +1 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
- package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
- package/src/components/ui/panels/homogeneous-selection.ts +47 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
- package/src/components/ui/panels/multi-field-value.test.ts +209 -0
- package/src/components/ui/panels/multi-field-value.ts +163 -0
- package/src/components/ui/panels/multi-height-mode.tsx +185 -0
- package/src/components/ui/panels/multi-parametric-inspector.tsx +263 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +78 -26
- package/src/components/ui/panels/node-display.ts +4 -0
- package/src/components/ui/panels/panel-manager.tsx +77 -3
- package/src/components/ui/panels/panel-wrapper.tsx +228 -12
- package/src/components/ui/panels/parametric-field-control.tsx +173 -0
- package/src/components/ui/panels/parametric-field-utils.ts +18 -0
- package/src/components/ui/panels/parametric-inspector.tsx +29 -162
- package/src/components/ui/panels/reference-panel.tsx +31 -1
- package/src/components/ui/primitives/shortcut-token.tsx +23 -1
- package/src/components/ui/scene-loader.tsx +35 -0
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +7 -2
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +390 -36
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +31 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +140 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +109 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +128 -46
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
- package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +22 -7
- package/src/components/ui/sidebar/tab-bar.tsx +9 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/viewer/floorplan-compass-button.tsx +50 -0
- package/src/components/viewer/floorplan-preview-geometry.test.ts +107 -0
- package/src/components/viewer/floorplan-preview-geometry.ts +281 -0
- package/src/components/viewer/floorplan-preview-navigation.test.ts +43 -0
- package/src/components/viewer/floorplan-preview-navigation.ts +62 -0
- package/src/components/viewer/floorplan-preview.tsx +1069 -0
- package/src/components/viewer/use-viewer-camera-navigation-sync.ts +140 -0
- package/src/components/viewer/viewer-scene-header.tsx +2 -1
- package/src/components/viewer/viewer-stage-modes.test.ts +38 -0
- package/src/components/viewer/viewer-stage-modes.ts +49 -0
- package/src/components/viewer/viewer-stage-switcher.tsx +95 -0
- package/src/components/viewer/viewer-stage.test.tsx +47 -0
- package/src/components/viewer/viewer-stage.tsx +221 -0
- package/src/components/viewer-overlay.tsx +13 -30
- package/src/components/viewer-zone-system.tsx +6 -2
- package/src/components/walkthrough-hud.tsx +19 -10
- package/src/hooks/use-auto-save.test.ts +122 -1
- package/src/hooks/use-auto-save.ts +119 -13
- package/src/hooks/use-ceiling-events.test.ts +122 -0
- package/src/hooks/use-drag-action.ts +17 -3
- package/src/hooks/use-grid-events.test.ts +240 -0
- package/src/hooks/use-grid-events.ts +190 -11
- package/src/hooks/use-keyboard.test.ts +244 -0
- package/src/hooks/use-keyboard.ts +164 -63
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +109 -4
- package/src/lib/active-placement-surface.test.ts +16 -0
- package/src/lib/active-placement-surface.ts +14 -0
- package/src/lib/ceiling-surface-raycast.test.ts +64 -0
- package/src/lib/ceiling-surface-raycast.ts +12 -0
- package/src/lib/contextual-help-extension.ts +17 -0
- package/src/lib/contextual-help.test.ts +8 -0
- package/src/lib/contextual-help.ts +8 -0
- package/src/lib/continuation.test.ts +11 -0
- package/src/lib/continuation.ts +8 -1
- package/src/lib/direct-manipulation.test.ts +132 -0
- package/src/lib/direct-manipulation.ts +38 -1
- package/src/lib/editor-api.ts +6 -3
- package/src/lib/elevation-guides.test.ts +28 -1
- package/src/lib/elevation-guides.ts +22 -7
- package/src/lib/floating-menu-scale.test.ts +29 -0
- package/src/lib/floating-menu-scale.ts +14 -0
- package/src/lib/floorplan/apply-alignment.test.ts +47 -2
- package/src/lib/floorplan/apply-alignment.ts +10 -1
- package/src/lib/floorplan/floorplan-export.test.ts +397 -1
- package/src/lib/floorplan/floorplan-export.tsx +111 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
- package/src/lib/floorplan/floorplan-readonly.test.ts +47 -0
- package/src/lib/floorplan/floorplan-readonly.ts +85 -0
- package/src/lib/floorplan-grid-event-point.test.ts +29 -0
- package/src/lib/floorplan-grid-event-point.ts +13 -0
- package/src/lib/glb-export.test.ts +1169 -21
- package/src/lib/glb-export.ts +629 -101
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +523 -77
- package/src/lib/history.ts +133 -14
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/inspector-card-mode.test.ts +92 -0
- package/src/lib/inspector-card-mode.ts +60 -0
- package/src/lib/interaction/hot-set.test.ts +10 -1
- package/src/lib/interaction/hot-set.ts +3 -1
- package/src/lib/interaction/overlay-policy.test.ts +33 -1
- package/src/lib/interaction/overlay-policy.ts +16 -0
- package/src/lib/interaction/registered-drafting.ts +36 -0
- package/src/lib/interaction/scope.ts +31 -0
- package/src/lib/level-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- package/src/lib/local-project-presentation-persistence.test.ts +302 -0
- package/src/lib/local-project-presentation-persistence.ts +335 -0
- package/src/lib/material-paint.ts +2 -0
- package/src/lib/measurement-parser.ts +3 -0
- package/src/lib/measurements.test.ts +4 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/paint-scope.test.ts +58 -2
- package/src/lib/paint-scope.ts +8 -2
- package/src/lib/planar-cursor-placement.test.ts +178 -1
- package/src/lib/planar-cursor-placement.ts +61 -5
- package/src/lib/plugin-panels.test.ts +92 -1
- package/src/lib/plugin-panels.ts +48 -0
- package/src/lib/portable-export.test.ts +366 -0
- package/src/lib/portable-export.ts +969 -0
- package/src/lib/print-3mf.test.ts +86 -0
- package/src/lib/print-3mf.ts +161 -0
- package/src/lib/print-content-scope.test.ts +107 -0
- package/src/lib/print-content-scope.ts +90 -0
- package/src/lib/print-export.test.ts +294 -0
- package/src/lib/print-export.ts +735 -0
- package/src/lib/print-feature-thickness.test.ts +147 -0
- package/src/lib/print-feature-thickness.ts +180 -0
- package/src/lib/print-golden-house.test-fixture.ts +352 -0
- package/src/lib/print-golden-house.test.ts +249 -0
- package/src/lib/print-roof-solids.test.ts +133 -0
- package/src/lib/print-roof-solids.ts +630 -0
- package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
- package/src/lib/print-shell-compiler-baseline.ts +225 -0
- package/src/lib/print-shell-compiler-manifold-core.ts +330 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +187 -0
- package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
- package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
- package/src/lib/print-shell-compiler-protocol.ts +30 -0
- package/src/lib/print-shell-compiler.ts +273 -0
- package/src/lib/print-wall-solids.test.ts +194 -0
- package/src/lib/print-wall-solids.ts +328 -0
- package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
- package/src/lib/rigid-plan-svg-transform.ts +24 -0
- package/src/lib/scene.ts +22 -8
- package/src/lib/selection-routing.test.ts +125 -6
- package/src/lib/selection-routing.ts +34 -1
- package/src/lib/session-groups.test.ts +88 -0
- package/src/lib/session-groups.ts +201 -0
- package/src/lib/sfx-bus.ts +4 -0
- package/src/lib/sfx-player.ts +8 -0
- package/src/lib/snapping-mode.test.ts +32 -4
- package/src/lib/snapping-mode.ts +11 -7
- package/src/lib/structured-clone-fallback.test.ts +45 -0
- package/src/lib/structured-clone-fallback.ts +24 -0
- package/src/lib/usdz-export.ts +38 -0
- package/src/lib/walkthrough-pointer-lock.ts +47 -0
- package/src/store/live-draft-preview-stores.test.ts +3 -0
- package/src/store/terrain-sculpt-mode.test.ts +3 -6
- package/src/store/tool-mode.test.ts +87 -0
- package/src/store/use-camera-hint-focus.ts +48 -0
- package/src/store/use-editor.tsx +299 -188
- package/src/store/use-floorplan-draft-preview.ts +9 -0
- package/src/store/use-interaction-scope.test.ts +23 -1
- package/src/store/use-interaction-scope.ts +63 -2
- package/src/store/use-placement-preview.ts +56 -3
- package/src/store/use-session-groups.ts +103 -0
- package/src/components/tools/roof/roof-tool.tsx +0 -656
- package/src/components/ui/helpers/roof-helper.tsx +0 -14
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pascal Group Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bunfig.toml
ADDED
package/package.json
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/editor",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Pascal building editor component",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": "./src/index.tsx",
|
|
8
9
|
"./catalog": "./src/components/ui/item-catalog/catalog-items.tsx"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
|
-
"check-types": "tsgo --noEmit"
|
|
12
|
+
"check-types": "tsgo --noEmit",
|
|
13
|
+
"test": "bun test src"
|
|
12
14
|
},
|
|
13
15
|
"peerDependencies": {
|
|
14
|
-
"@pascal-app/core": "^1.0.0
|
|
15
|
-
"@pascal-app/viewer": "^1.0.0
|
|
16
|
+
"@pascal-app/core": "^1.0.0",
|
|
17
|
+
"@pascal-app/viewer": "^1.0.0",
|
|
16
18
|
"@react-three/drei": "^10",
|
|
17
19
|
"@react-three/fiber": "^9",
|
|
18
20
|
"next": ">=15",
|
|
19
21
|
"react": "^18 || ^19",
|
|
20
22
|
"react-dom": "^18 || ^19",
|
|
21
|
-
"three": "^0.
|
|
23
|
+
"three": "^0.186"
|
|
22
24
|
},
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -44,21 +46,25 @@
|
|
|
44
46
|
"class-variance-authority": "^0.7.1",
|
|
45
47
|
"clsx": "^2.1.1",
|
|
46
48
|
"cmdk": "^1.1.1",
|
|
49
|
+
"fflate": "^0.8.3",
|
|
47
50
|
"howler": "^2.2.4",
|
|
48
51
|
"lucide-react": "^1.7.0",
|
|
52
|
+
"manifold-3d": "3.5.1",
|
|
49
53
|
"mitt": "^3.0.1",
|
|
50
54
|
"motion": "^12.34.3",
|
|
51
55
|
"nanoid": "^5.1.6",
|
|
52
56
|
"pdfkit": "^0.19.1",
|
|
53
57
|
"tailwind-merge": "^3.5.0",
|
|
58
|
+
"three-bvh-csg": "^0.0.18",
|
|
54
59
|
"three-mesh-bvh": "~0.9.8",
|
|
55
|
-
"zod": "
|
|
60
|
+
"zod": ">=4.5.4 <4.6",
|
|
56
61
|
"zustand": "^5.0.11"
|
|
57
62
|
},
|
|
58
63
|
"devDependencies": {
|
|
59
|
-
"@pascal-app/core": "^1.0.0
|
|
60
|
-
"@pascal-app/viewer": "^1.0.0
|
|
64
|
+
"@pascal-app/core": "^1.0.0",
|
|
65
|
+
"@pascal-app/viewer": "^1.0.0",
|
|
61
66
|
"@pascal/typescript-config": "*",
|
|
67
|
+
"@react-three/test-renderer": "^9.1.0",
|
|
62
68
|
"@types/blob-stream": "^0.1.33",
|
|
63
69
|
"@types/bun": "^1.3.0",
|
|
64
70
|
"@types/howler": "^2.2.12",
|
|
@@ -66,6 +72,7 @@
|
|
|
66
72
|
"@types/react": "19.2.2",
|
|
67
73
|
"@types/react-dom": "19.2.2",
|
|
68
74
|
"@types/three": "^0.184.0",
|
|
75
|
+
"fast-xml-parser": "^5.4.2",
|
|
69
76
|
"typescript": "6.0.3"
|
|
70
77
|
}
|
|
71
78
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { prepareSceneForExport } from '../src/lib/glb-export'
|
|
4
|
+
import { exportSceneLevelsForPrint } from '../src/lib/level-print-export'
|
|
5
|
+
import { filterPreparedSceneForPrintContent } from '../src/lib/print-content-scope'
|
|
6
|
+
import { createPrintGoldenHouseFixture } from '../src/lib/print-golden-house.test-fixture'
|
|
7
|
+
import { compileManifoldMeshData } from '../src/lib/print-shell-compiler-manifold-core'
|
|
8
|
+
import { compileSemanticPrintShellWithManifold } from '../src/lib/print-shell-compiler-manifold-worker'
|
|
9
|
+
|
|
10
|
+
const outputArgument = process.argv[2]
|
|
11
|
+
if (!outputArgument) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Usage: bun packages/editor/scripts/generate-print-golden-house.ts <output-directory>',
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const outputDirectory = resolve(outputArgument)
|
|
18
|
+
const fixture = createPrintGoldenHouseFixture()
|
|
19
|
+
|
|
20
|
+
async function sha256(data: Uint8Array): Promise<string> {
|
|
21
|
+
const digest = await crypto.subtle.digest('SHA-256', data)
|
|
22
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const prepared = prepareSceneForExport(fixture.root, fixture.nodes, { onlyVisible: true })
|
|
27
|
+
const structure = filterPreparedSceneForPrintContent(prepared.scene, fixture.nodes, 'structure')
|
|
28
|
+
const compileShell = (source: Parameters<typeof exportSceneLevelsForPrint>[0]) =>
|
|
29
|
+
compileSemanticPrintShellWithManifold(source, fixture.nodes, {
|
|
30
|
+
runner: compileManifoldMeshData,
|
|
31
|
+
})
|
|
32
|
+
const common = {
|
|
33
|
+
scale: 100,
|
|
34
|
+
plinth: { marginMm: 2, thicknessMm: 3 },
|
|
35
|
+
compileShells: true,
|
|
36
|
+
compileShell,
|
|
37
|
+
}
|
|
38
|
+
const threeMf = await exportSceneLevelsForPrint(structure, fixture.nodes, {
|
|
39
|
+
...common,
|
|
40
|
+
format: '3mf',
|
|
41
|
+
})
|
|
42
|
+
const stl = await exportSceneLevelsForPrint(structure, fixture.nodes, {
|
|
43
|
+
...common,
|
|
44
|
+
format: 'stl',
|
|
45
|
+
})
|
|
46
|
+
if (threeMf.report.status === 'blocked' || stl.report.status === 'blocked') {
|
|
47
|
+
throw new Error('The golden house failed print preflight and was not written.')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await mkdir(outputDirectory, { recursive: true })
|
|
51
|
+
const files = [
|
|
52
|
+
{ name: 'pascal-golden-house-levels.3mf', data: threeMf.data },
|
|
53
|
+
{ name: 'pascal-golden-house-levels-stl.zip', data: stl.data },
|
|
54
|
+
]
|
|
55
|
+
for (const file of files) await writeFile(resolve(outputDirectory, file.name), file.data)
|
|
56
|
+
|
|
57
|
+
const manifest = {
|
|
58
|
+
kind: 'pascal-print-golden-house',
|
|
59
|
+
version: 1,
|
|
60
|
+
scale: 100,
|
|
61
|
+
units: 'millimeter',
|
|
62
|
+
files: await Promise.all(
|
|
63
|
+
files.map(async (file) => ({
|
|
64
|
+
name: file.name,
|
|
65
|
+
bytes: file.data.byteLength,
|
|
66
|
+
sha256: await sha256(file.data),
|
|
67
|
+
})),
|
|
68
|
+
),
|
|
69
|
+
parts: threeMf.report.parts.map((part) => ({
|
|
70
|
+
kind: part.kind,
|
|
71
|
+
label: part.label,
|
|
72
|
+
sourceBaseMeters: part.sourceBaseMeters,
|
|
73
|
+
bounds: part.report.bounds,
|
|
74
|
+
triangles: part.report.triangleCount,
|
|
75
|
+
connectedComponentCount: part.report.connectedComponentCount,
|
|
76
|
+
solidComponentCount: part.report.solidComponentCount,
|
|
77
|
+
invertedWinding: part.report.invertedWinding,
|
|
78
|
+
volumeMm3: part.report.volumeMm3,
|
|
79
|
+
minimumFeatureThicknessMm: part.report.minimumFeatureThicknessMm,
|
|
80
|
+
})),
|
|
81
|
+
}
|
|
82
|
+
await writeFile(
|
|
83
|
+
resolve(outputDirectory, 'manifest.json'),
|
|
84
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
85
|
+
)
|
|
86
|
+
process.stdout.write(`${JSON.stringify({ outputDirectory, ...manifest }, null, 2)}\n`)
|
|
87
|
+
} finally {
|
|
88
|
+
fixture.dispose()
|
|
89
|
+
}
|
|
@@ -66,6 +66,7 @@ export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
|
|
|
66
66
|
const guides = useAlignmentGuides((s) => s.guides)
|
|
67
67
|
const levelId = useViewer((s) => s.selection.levelId)
|
|
68
68
|
const unit = useViewer((s) => s.unit)
|
|
69
|
+
const metricNotation = useViewer((s) => s.metricNotation)
|
|
69
70
|
const groupRef = useRef<Group>(null)
|
|
70
71
|
|
|
71
72
|
// Guides carry only XZ in WORLD coords; their Y has to track the active
|
|
@@ -85,16 +86,24 @@ export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() {
|
|
|
85
86
|
return (
|
|
86
87
|
<group ref={groupRef}>
|
|
87
88
|
{guides.map((guide, i) => (
|
|
88
|
-
<GuideLine guide={guide} key={i} unit={unit} />
|
|
89
|
+
<GuideLine guide={guide} key={i} metricNotation={metricNotation} unit={unit} />
|
|
89
90
|
))}
|
|
90
91
|
</group>
|
|
91
92
|
)
|
|
92
93
|
})
|
|
93
94
|
|
|
94
|
-
function GuideLine({
|
|
95
|
+
function GuideLine({
|
|
96
|
+
guide,
|
|
97
|
+
metricNotation,
|
|
98
|
+
unit,
|
|
99
|
+
}: {
|
|
100
|
+
guide: AlignmentGuide
|
|
101
|
+
metricNotation: 'meters' | 'millimeters'
|
|
102
|
+
unit: 'metric' | 'imperial'
|
|
103
|
+
}) {
|
|
95
104
|
const { x: fx, z: fz } = guide.from
|
|
96
105
|
const { x: tx, z: tz } = guide.to
|
|
97
|
-
const distLabel = formatMeasurement(guide.distance, unit)
|
|
106
|
+
const distLabel = formatMeasurement(guide.distance, unit, metricNotation)
|
|
98
107
|
|
|
99
108
|
// Lay out the dash centres along the from→to direction. The ribbon
|
|
100
109
|
// stretches the dash period up if the line is long enough to exceed the
|
|
@@ -31,6 +31,7 @@ export function BakeExporter({
|
|
|
31
31
|
if (!sceneGroup) throw new Error('scene-renderer group not found')
|
|
32
32
|
const buffer = await exportSceneToGlb(sceneGroup, useScene.getState().nodes, {
|
|
33
33
|
textures: 'reference',
|
|
34
|
+
purpose: 'viewer',
|
|
34
35
|
})
|
|
35
36
|
onComplete(buffer)
|
|
36
37
|
} catch (err) {
|
|
@@ -46,4 +46,45 @@ describe('camera dragging lifecycle', () => {
|
|
|
46
46
|
expect(scheduled).toBeNull()
|
|
47
47
|
expect(dragging).toEqual([true, true])
|
|
48
48
|
})
|
|
49
|
+
|
|
50
|
+
test('pause ends damping without rest, cancels wheel fallback and re-arms on resume', () => {
|
|
51
|
+
let dragging = false
|
|
52
|
+
let scheduled: (() => void) | null = null
|
|
53
|
+
const lifecycle = createCameraDraggingLifecycle({
|
|
54
|
+
setDragging: (value) => {
|
|
55
|
+
dragging = value
|
|
56
|
+
},
|
|
57
|
+
schedule: (callback) => {
|
|
58
|
+
scheduled = callback
|
|
59
|
+
return 1 as unknown as ReturnType<typeof globalThis.setTimeout>
|
|
60
|
+
},
|
|
61
|
+
cancel: () => {
|
|
62
|
+
scheduled = null
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
lifecycle.begin()
|
|
67
|
+
expect(dragging).toBe(true)
|
|
68
|
+
lifecycle.setPaused(true)
|
|
69
|
+
expect(dragging).toBe(false)
|
|
70
|
+
lifecycle.begin()
|
|
71
|
+
lifecycle.scheduleEnd()
|
|
72
|
+
expect(dragging).toBe(false)
|
|
73
|
+
expect(scheduled).toBeNull()
|
|
74
|
+
|
|
75
|
+
lifecycle.setPaused(false)
|
|
76
|
+
expect(dragging).toBe(false)
|
|
77
|
+
lifecycle.begin()
|
|
78
|
+
lifecycle.scheduleEnd()
|
|
79
|
+
expect(dragging).toBe(true)
|
|
80
|
+
expect(scheduled).not.toBeNull()
|
|
81
|
+
lifecycle.setPaused(true)
|
|
82
|
+
expect(dragging).toBe(false)
|
|
83
|
+
expect(scheduled).toBeNull()
|
|
84
|
+
lifecycle.setPaused(false)
|
|
85
|
+
lifecycle.begin()
|
|
86
|
+
expect(dragging).toBe(true)
|
|
87
|
+
lifecycle.end()
|
|
88
|
+
expect(dragging).toBe(false)
|
|
89
|
+
})
|
|
49
90
|
})
|
|
@@ -11,6 +11,7 @@ export function createCameraDraggingLifecycle({
|
|
|
11
11
|
schedule?: (callback: () => void, delay: number) => TimerHandle
|
|
12
12
|
cancel?: (timer: TimerHandle) => void
|
|
13
13
|
}) {
|
|
14
|
+
let paused = false
|
|
14
15
|
let releaseTimer: TimerHandle | null = null
|
|
15
16
|
|
|
16
17
|
const clearScheduledEnd = () => {
|
|
@@ -21,7 +22,7 @@ export function createCameraDraggingLifecycle({
|
|
|
21
22
|
|
|
22
23
|
const begin = () => {
|
|
23
24
|
clearScheduledEnd()
|
|
24
|
-
setDragging(true)
|
|
25
|
+
if (!paused) setDragging(true)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const end = () => {
|
|
@@ -31,11 +32,18 @@ export function createCameraDraggingLifecycle({
|
|
|
31
32
|
|
|
32
33
|
const scheduleEnd = () => {
|
|
33
34
|
clearScheduledEnd()
|
|
35
|
+
if (paused) return
|
|
34
36
|
releaseTimer = schedule(() => {
|
|
35
37
|
releaseTimer = null
|
|
36
38
|
setDragging(false)
|
|
37
39
|
}, fallbackMs)
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
const setPaused = (value: boolean) => {
|
|
43
|
+
paused = value
|
|
44
|
+
// Paused controls cannot advance damping to rest/sleep.
|
|
45
|
+
if (paused) end()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { begin, end, scheduleEnd, setPaused }
|
|
41
49
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useThree } from '@react-three/fiber'
|
|
4
|
+
import { useEffect, useRef } from 'react'
|
|
5
|
+
import type { PerspectiveCamera } from 'three'
|
|
6
|
+
import useEditor from '../../store/use-editor'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Owns the main camera's field of view while the snapshot capture overlay is
|
|
10
|
+
* open, and is mounted only for that window. `ThumbnailGenerator` copies the
|
|
11
|
+
* main camera's fov on every shot, so the value written here is what lands in
|
|
12
|
+
* the saved image.
|
|
13
|
+
*
|
|
14
|
+
* The overlay's slider cannot reach the camera itself (it renders outside the
|
|
15
|
+
* canvas), so the store carries the value and this rig applies it.
|
|
16
|
+
*/
|
|
17
|
+
export const CaptureCameraRig = () => {
|
|
18
|
+
const camera = useThree((state) => state.camera)
|
|
19
|
+
const captureFov = useEditor((state) => state.captureFov)
|
|
20
|
+
const appliedFovRef = useRef<number | null>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const perspectiveCamera = camera as PerspectiveCamera
|
|
24
|
+
if (!perspectiveCamera.isPerspectiveCamera) {
|
|
25
|
+
// Orthographic captures have no fov to drive — the overlay hides the
|
|
26
|
+
// control while the store is disarmed.
|
|
27
|
+
useEditor.getState().armCaptureFov(null)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const entryFov = perspectiveCamera.fov
|
|
32
|
+
useEditor.getState().armCaptureFov(entryFov)
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
useEditor.getState().armCaptureFov(null)
|
|
36
|
+
// Walkthrough restores its own pre-entry fov when it unmounts, which can
|
|
37
|
+
// happen in the same commit as this rig (leaving capture also leaves
|
|
38
|
+
// walk/drone). Only put the entry fov back if the camera still holds our
|
|
39
|
+
// last write — otherwise someone else has already re-owned it.
|
|
40
|
+
if (appliedFovRef.current !== null && perspectiveCamera.fov === appliedFovRef.current) {
|
|
41
|
+
perspectiveCamera.fov = entryFov
|
|
42
|
+
perspectiveCamera.updateProjectionMatrix()
|
|
43
|
+
}
|
|
44
|
+
appliedFovRef.current = null
|
|
45
|
+
}
|
|
46
|
+
}, [camera])
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const perspectiveCamera = camera as PerspectiveCamera
|
|
50
|
+
if (!perspectiveCamera.isPerspectiveCamera || captureFov === null) return
|
|
51
|
+
|
|
52
|
+
appliedFovRef.current = captureFov
|
|
53
|
+
if (perspectiveCamera.fov === captureFov) return
|
|
54
|
+
perspectiveCamera.fov = captureFov
|
|
55
|
+
perspectiveCamera.updateProjectionMatrix()
|
|
56
|
+
}, [camera, captureFov])
|
|
57
|
+
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
sceneRegistry,
|
|
10
10
|
useScene,
|
|
11
11
|
} from '@pascal-app/core'
|
|
12
|
-
import { GRID_LAYER, useViewer, ZONE_LAYER } from '@pascal-app/viewer'
|
|
12
|
+
import { GRID_LAYER, getLevelPresentationY, useViewer, ZONE_LAYER } from '@pascal-app/viewer'
|
|
13
13
|
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
|
14
14
|
import { useFrame, useThree } from '@react-three/fiber'
|
|
15
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
15
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
16
16
|
import {
|
|
17
17
|
Box3,
|
|
18
18
|
type Camera,
|
|
@@ -347,7 +347,7 @@ function useFirstPersonCameraPoseRestore(
|
|
|
347
347
|
return useCallback(() => isRestoring.current, [])
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
export const CustomCameraControls = () => {
|
|
350
|
+
export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) => {
|
|
351
351
|
const controls = useRef<CameraControlsImpl | null>(null)
|
|
352
352
|
const pendingAppliedPose = useRef<CameraPoseApplicationPlan | null>(null)
|
|
353
353
|
const activePoseInterpolation = useRef<{
|
|
@@ -366,6 +366,8 @@ export const CustomCameraControls = () => {
|
|
|
366
366
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
367
367
|
const allowUndergroundCamera = useEditor((s) => s.allowUndergroundCamera)
|
|
368
368
|
const selection = useViewer((s) => s.selection)
|
|
369
|
+
const levelMode = useViewer((s) => s.levelMode)
|
|
370
|
+
const renderPaused = useViewer((state) => state.renderPaused)
|
|
369
371
|
const cameraMode = useViewer((state) => state.cameraMode)
|
|
370
372
|
const isRestoringFirstPersonPose = useFirstPersonCameraPoseRestore(
|
|
371
373
|
controls,
|
|
@@ -528,21 +530,24 @@ export const CustomCameraControls = () => {
|
|
|
528
530
|
|
|
529
531
|
useEffect(() => {
|
|
530
532
|
if (isPreviewMode || isFirstPersonMode || isRestoringFirstPersonPose()) return
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
533
|
+
// Analytic destination, not `sceneRegistry` mesh position: a level created
|
|
534
|
+
// this frame still sits at y=0 (LevelSystem lerps it later), and a mode
|
|
535
|
+
// switch leaves every level mid-lerp — the camera must pan to where the
|
|
536
|
+
// level will settle, in the CURRENT presentation mode.
|
|
537
|
+
const targetY = currentLevelId
|
|
538
|
+
? getLevelPresentationY(currentLevelId, useScene.getState().nodes, levelMode)
|
|
539
|
+
: 0
|
|
538
540
|
if (!controls.current) return
|
|
539
541
|
if (firstLoad.current) {
|
|
540
542
|
firstLoad.current = false
|
|
541
543
|
controls.current.setLookAt(20, 20, 20, 0, 0, 0, true)
|
|
542
544
|
}
|
|
543
545
|
controls.current.getTarget(currentTarget)
|
|
546
|
+
// Idempotence guard: skip when already there — also swallows the thumbnail
|
|
547
|
+
// generator's synchronous stacked→restore levelMode round-trip.
|
|
548
|
+
if (Math.abs(currentTarget.y - targetY) < 1e-3) return
|
|
544
549
|
controls.current.moveTo(currentTarget.x, targetY, currentTarget.z, true)
|
|
545
|
-
}, [currentLevelId, isPreviewMode, isFirstPersonMode, isRestoringFirstPersonPose])
|
|
550
|
+
}, [currentLevelId, levelMode, isPreviewMode, isFirstPersonMode, isRestoringFirstPersonPose])
|
|
546
551
|
|
|
547
552
|
useEffect(() => {
|
|
548
553
|
if (isFirstPersonMode || !controls.current) return
|
|
@@ -1274,6 +1279,10 @@ export const CustomCameraControls = () => {
|
|
|
1274
1279
|
cameraDraggingLifecycle.end()
|
|
1275
1280
|
}, [cameraDraggingLifecycle])
|
|
1276
1281
|
|
|
1282
|
+
useLayoutEffect(() => {
|
|
1283
|
+
cameraDraggingLifecycle.setPaused(paused || renderPaused)
|
|
1284
|
+
}, [cameraDraggingLifecycle, paused, renderPaused])
|
|
1285
|
+
|
|
1277
1286
|
const onControlEnd = useCallback(() => {
|
|
1278
1287
|
// A mapped-button tap with zero camera movement never wakes the
|
|
1279
1288
|
// controls, so no rest/sleep follows — clear the dragging flag on
|
|
@@ -83,18 +83,18 @@ export function EditorLayoutMobile({
|
|
|
83
83
|
// desktop "Furnish" action which itself opens the Items panel).
|
|
84
84
|
// - Leaving Items while still furnishing exits the build mode.
|
|
85
85
|
useEffect(() => {
|
|
86
|
-
const { phase, mode,
|
|
86
|
+
const { armToolMode, phase, mode, setPhase } = useEditor.getState()
|
|
87
87
|
if (activePanel === 'ai' && mode === 'build') {
|
|
88
|
-
|
|
88
|
+
armToolMode({ mode: 'select' })
|
|
89
89
|
return
|
|
90
90
|
}
|
|
91
91
|
if (activePanel === 'items') {
|
|
92
92
|
if (phase !== 'furnish') setPhase('furnish')
|
|
93
|
-
if (mode !== 'build')
|
|
93
|
+
if (mode !== 'build') armToolMode({ mode: 'build', tool: 'item' })
|
|
94
94
|
return
|
|
95
95
|
}
|
|
96
96
|
if (phase === 'furnish' && mode === 'build') {
|
|
97
|
-
|
|
97
|
+
armToolMode({ mode: 'select' })
|
|
98
98
|
}
|
|
99
99
|
}, [activePanel])
|
|
100
100
|
|
|
@@ -160,9 +160,9 @@ export function EditorLayoutMobile({
|
|
|
160
160
|
if (current > expandedThreshold) {
|
|
161
161
|
sheetRef.current?.snapTo(SHEET_HANDLE_PX)
|
|
162
162
|
// Closing the sheet disarms any build tool back to select
|
|
163
|
-
const {
|
|
163
|
+
const { armToolMode, mode } = useEditor.getState()
|
|
164
164
|
if (mode === 'build') {
|
|
165
|
-
|
|
165
|
+
armToolMode({ mode: 'select' })
|
|
166
166
|
}
|
|
167
167
|
} else {
|
|
168
168
|
sheetRef.current?.snapTo(defaultPx)
|
|
@@ -77,6 +77,11 @@ function LeftColumn({
|
|
|
77
77
|
// up to the minimum so the panel always returns to a usable size.
|
|
78
78
|
const handleRailClick = useCallback(
|
|
79
79
|
(id: string) => {
|
|
80
|
+
// noPanel tabs drive the stage, not the panel — leave collapse state alone.
|
|
81
|
+
if (tabs.find((t) => t.id === id)?.noPanel) {
|
|
82
|
+
setActivePanel(id)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
80
85
|
if (isCollapsed) {
|
|
81
86
|
setIsCollapsed(false)
|
|
82
87
|
if (width < SIDEBAR_MIN_WIDTH) setWidth(SIDEBAR_MIN_WIDTH)
|
|
@@ -89,7 +94,7 @@ function LeftColumn({
|
|
|
89
94
|
}
|
|
90
95
|
setActivePanel(id)
|
|
91
96
|
},
|
|
92
|
-
[isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel],
|
|
97
|
+
[tabs, isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel],
|
|
93
98
|
)
|
|
94
99
|
|
|
95
100
|
useEffect(() => {
|
|
@@ -126,7 +131,7 @@ function LeftColumn({
|
|
|
126
131
|
onIconClick={handleRailClick}
|
|
127
132
|
tabs={tabs}
|
|
128
133
|
/>
|
|
129
|
-
{!isCollapsed && (
|
|
134
|
+
{!isCollapsed && !tabs.find((t) => t.id === activePanel)?.noPanel && (
|
|
130
135
|
<div
|
|
131
136
|
className="relative flex h-full flex-col"
|
|
132
137
|
style={{
|
|
@@ -183,8 +188,15 @@ function RightColumn({
|
|
|
183
188
|
<div className="pointer-events-auto flex items-center gap-2">{toolbarRight}</div>
|
|
184
189
|
</div>
|
|
185
190
|
)}
|
|
186
|
-
{/* Canvas area
|
|
187
|
-
|
|
191
|
+
{/* Canvas area. `isolate` matters: drei's `<Html>` computes a z-index
|
|
192
|
+
from camera distance and defaults to a range topping out at
|
|
193
|
+
16,777,271, and without a stacking context here those values compete
|
|
194
|
+
directly with the viewer toolbar (z-20), the stage overlay (z-10) and
|
|
195
|
+
the overlay band (z-30) — so an in-scene tool badge painted over all
|
|
196
|
+
three. Isolating pins every in-scene HTML layer inside the canvas,
|
|
197
|
+
where it belongs, and leaves their order relative to each other
|
|
198
|
+
untouched. */}
|
|
199
|
+
<div className="relative isolate flex-1 overflow-hidden">{children}</div>
|
|
188
200
|
{/* Stage overlay — replaces the canvas visually (e.g. studio gallery)
|
|
189
201
|
while keeping it mounted. Sits below the viewer toolbar (z-20) so
|
|
190
202
|
the stage switch stays reachable. */}
|
|
@@ -239,7 +251,7 @@ export function EditorLayoutV2({
|
|
|
239
251
|
overlays={overlays}
|
|
240
252
|
renderTabContent={renderTabContent}
|
|
241
253
|
sidebarOverlay={sidebarOverlay}
|
|
242
|
-
sidebarTabs={sidebarTabs}
|
|
254
|
+
sidebarTabs={sidebarTabs.filter((t) => !t.noPanel)}
|
|
243
255
|
viewerContent={viewerContent}
|
|
244
256
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
245
257
|
viewerToolbarRight={viewerToolbarRight}
|
|
@@ -44,6 +44,7 @@ type Vec3 = [number, number, number]
|
|
|
44
44
|
export const Elevation3DGuideLayer = memo(function Elevation3DGuideLayer() {
|
|
45
45
|
const guide = useElevationGuides((state) => state.guide)
|
|
46
46
|
const unit = useViewer((state) => state.unit)
|
|
47
|
+
const metricNotation = useViewer((state) => state.metricNotation)
|
|
47
48
|
const groupRef = useRef<Group>(null)
|
|
48
49
|
|
|
49
50
|
useFrame(() => {
|
|
@@ -76,8 +77,8 @@ export const Elevation3DGuideLayer = memo(function Elevation3DGuideLayer() {
|
|
|
76
77
|
]
|
|
77
78
|
const elevationText =
|
|
78
79
|
Math.abs(guide.elevation) < 1e-6
|
|
79
|
-
? formatMeasurement(0, unit)
|
|
80
|
-
: `${guide.elevation < 0 ? '-' : '+'}${formatMeasurement(Math.abs(guide.elevation), unit)}`
|
|
80
|
+
? formatMeasurement(0, unit, metricNotation)
|
|
81
|
+
: `${guide.elevation < 0 ? '-' : '+'}${formatMeasurement(Math.abs(guide.elevation), unit, metricNotation)}`
|
|
81
82
|
|
|
82
83
|
return (
|
|
83
84
|
<group ref={groupRef}>
|