@pascal-app/editor 1.0.4 → 1.0.5
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/README.md +106 -0
- package/package.json +39 -37
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/bake-thumbnail.tsx +6 -15
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +81 -8
- package/src/components/editor/editor-layout-mobile.tsx +1 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/export-manager.tsx +14 -19
- package/src/components/editor/first-person/build-collider-world.test.ts +269 -0
- package/src/components/editor/first-person/build-collider-world.ts +22 -5
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +89 -2
- package/src/components/editor/first-person/mechanism-targets.ts +52 -0
- package/src/components/editor/first-person-controls.tsx +19 -11
- package/src/components/editor/floor-aware-camera-controls.test.ts +24 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +72 -23
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/group-actions.ts +2 -0
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/index.tsx +166 -79
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-highlight.tsx +13 -5
- package/src/components/editor/room-labels.test.ts +621 -128
- package/src/components/editor/room-labels.tsx +1090 -190
- package/src/components/editor/selection-manager.tsx +38 -117
- package/src/components/editor/slab-hole-highlights.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +3 -2
- package/src/components/editor/snapshot-capture.test.ts +69 -58
- package/src/components/editor/snapshot-capture.ts +7 -1
- package/src/components/editor/thumbnail-generator.test.ts +350 -4
- package/src/components/editor/thumbnail-generator.tsx +128 -54
- package/src/components/editor/use-floorplan-background-placement.ts +3 -1
- package/src/components/editor/views/plugins-view.tsx +1 -8
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +140 -17
- package/src/components/editor/wall-snap-beacon-layer.tsx +5 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +3 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +1 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +29 -7
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +38 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +3 -6
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +94 -94
- package/src/components/tools/item/use-placement-coordinator.tsx +55 -49
- package/src/components/tools/registry/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +63 -29
- package/src/components/tools/tool-manager.tsx +14 -5
- package/src/components/tools/wall/wall-drafting.test.ts +170 -57
- package/src/components/ui/action-menu/action-button.tsx +6 -1
- package/src/components/ui/action-menu/camera-actions.tsx +7 -3
- package/src/components/ui/action-menu/control-modes.tsx +13 -7
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +11 -9
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +10 -3
- package/src/components/ui/command-palette/editor-commands.tsx +3 -12
- package/src/components/ui/command-palette/index.tsx +45 -15
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +150 -45
- package/src/components/ui/helpers/contextual-helper-panel.tsx +32 -7
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -2
- package/src/components/ui/helpers/helper-manager.tsx +7 -1
- package/src/components/ui/helpers/item-helper.tsx +9 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +8 -8
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +6 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +120 -3
- package/src/components/ui/panels/panel-wrapper.tsx +53 -27
- package/src/components/ui/panels/parametric-field-control.tsx +2 -2
- package/src/components/ui/panels/parametric-inspector.tsx +3 -3
- package/src/components/ui/panels/room-panel.tsx +65 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/sidebar.tsx +1 -1
- package/src/components/ui/right-stack.tsx +21 -2
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +3 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +107 -23
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +5 -12
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -10
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/tab-bar.tsx +4 -4
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-rooms.tsx +16 -0
- package/src/hooks/use-auto-save.integration.test.tsx +96 -38
- package/src/hooks/use-auto-save.ts +26 -1
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +65 -16
- package/src/hooks/use-keyboard.ts +29 -12
- package/src/hooks/use-placement-notice.test.ts +24 -1
- package/src/hooks/use-placement-notice.ts +37 -6
- package/src/index.tsx +71 -24
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.ts +9 -2
- package/src/lib/editor-views.ts +35 -1
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-foundation-support.test.ts +1 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +31 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +39 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +4 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/sheet-export.test.ts +24 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +34 -45
- package/src/lib/floorplan/site-plan/coverage.ts +2 -4
- package/src/lib/floorplan/site-plan/front-edge.ts +2 -1
- package/src/lib/floorplan/site-plan/geometry.test.ts +9 -15
- package/src/lib/floorplan/site-plan/geometry.ts +1 -416
- package/src/lib/floorplan/site-plan/index.ts +0 -16
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +117 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -2
- package/src/lib/floorplan/site-plan/site-parts.ts +14 -106
- package/src/lib/floorplan/site-plan/yards.test.ts +2 -1
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +112 -0
- package/src/lib/fresh-planar-placement.ts +57 -3
- package/src/lib/geometry-script/author.ts +6 -1
- package/src/lib/geometry-script/client.ts +8 -1
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +1 -0
- package/src/lib/glb-export.test.ts +187 -162
- package/src/lib/glb-export.ts +4 -1
- package/src/lib/history.test.ts +12 -7
- package/src/lib/interaction/scope.ts +4 -0
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/lot/clean-ring.test.ts +2 -1
- package/src/lib/lot/drop-in.ts +9 -24
- package/src/lib/lot/lot-patch.test.ts +1 -1
- package/src/lib/lot/lot-patch.ts +2 -2
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/orbit-floor-clearance.test.ts +36 -0
- package/src/lib/orbit-floor-clearance.ts +48 -0
- package/src/lib/paint-pass-through.test.ts +11 -0
- package/src/lib/paint-pass-through.ts +4 -2
- package/src/lib/paint-scope.ts +1 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plugin-chat.ts +15 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- package/src/lib/room-assembly-overlay.ts +2 -3
- package/src/lib/room-selection.test.ts +45 -1
- package/src/lib/scene-clipboard.test.ts +246 -1
- package/src/lib/scene-clipboard.ts +44 -14
- package/src/lib/selection-routing.ts +1 -1
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.ts +2 -1
- package/src/lib/viewer-selection.ts +22 -11
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-stair-build-preview.ts +25 -5
- package/src/store/view-layout.test.ts +27 -1
- package/src/store/view-layout.ts +4 -1
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -15
- package/src/components/tools/stair/stair-tool.tsx +0 -682
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyNodeId,
|
|
3
|
+
type CompiledGeometryScript,
|
|
4
|
+
type FinishScriptHost,
|
|
5
|
+
type FinishScriptInputs,
|
|
6
|
+
type FinishScriptTarget,
|
|
7
|
+
finishScriptWrites,
|
|
8
|
+
GEOMETRY_SCRIPT_MIME_TYPES,
|
|
9
|
+
type GeometryScriptParamValue,
|
|
10
|
+
getArtifactStore,
|
|
11
|
+
pauseSceneHistory,
|
|
12
|
+
refreshFinishScripts,
|
|
13
|
+
resumeSceneHistory,
|
|
14
|
+
staleFinishScripts,
|
|
15
|
+
useScene,
|
|
16
|
+
} from '@pascal-app/core'
|
|
17
|
+
import { compileGeometryScriptInWorker } from './client'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Runs a finish script (a room's floor, a wall's face) in the sandbox for what it finishes and
|
|
21
|
+
* stores the GLB and the module text, answering the compile with the stored hashes
|
|
22
|
+
* (`setRoomFloorScript` and `setWallFaceScript` take it with its `inputs`).
|
|
23
|
+
*/
|
|
24
|
+
export async function compileAndStoreFinishScript(
|
|
25
|
+
input: FinishScriptTarget & {
|
|
26
|
+
code: string
|
|
27
|
+
params?: Record<string, GeometryScriptParamValue>
|
|
28
|
+
inputs: FinishScriptInputs
|
|
29
|
+
},
|
|
30
|
+
): Promise<CompiledGeometryScript> {
|
|
31
|
+
const { glb, ...compiled } = await compileGeometryScriptInWorker({
|
|
32
|
+
code: input.code,
|
|
33
|
+
params: input.params,
|
|
34
|
+
inputs: input.inputs,
|
|
35
|
+
})
|
|
36
|
+
const context = {
|
|
37
|
+
nodeId: input.nodeId,
|
|
38
|
+
metadata: { name: input.face ? 'Wall finish' : 'Floor finish', mount: compiled.mount },
|
|
39
|
+
}
|
|
40
|
+
const store = getArtifactStore()
|
|
41
|
+
const [sha256] = await Promise.all([
|
|
42
|
+
store.put(compiled.sha256, glb, 'model/gltf-binary', context),
|
|
43
|
+
store.put(
|
|
44
|
+
compiled.script,
|
|
45
|
+
new TextEncoder().encode(input.code),
|
|
46
|
+
GEOMETRY_SCRIPT_MIME_TYPES.three,
|
|
47
|
+
context,
|
|
48
|
+
),
|
|
49
|
+
])
|
|
50
|
+
return { ...compiled, sha256 }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The editor tab's host for finish scripts: the sandbox compile and the scene's artifact store. */
|
|
54
|
+
export const editorFinishScriptHost: FinishScriptHost = {
|
|
55
|
+
readScript: (sha256) => getArtifactStore().text(sha256),
|
|
56
|
+
build: compileAndStoreFinishScript,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The module text of a finish script, read back from the artifact store. */
|
|
60
|
+
export function finishScriptCode(sha256: string): Promise<string | null> {
|
|
61
|
+
return getArtifactStore().text(sha256)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const SETTLE_MS = 150
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Keeps every finish script built for what it finishes: after a scene change settles, each script
|
|
68
|
+
* whose room's floor the structure re-derived, or whose wall face changed, runs again in the
|
|
69
|
+
* sandbox. The new build is written outside history, as the reconcile's derived writes are, and
|
|
70
|
+
* only while its floor or face is still the one it was built for; undo brings back the room or
|
|
71
|
+
* wall and the script built for it together.
|
|
72
|
+
*/
|
|
73
|
+
export function initFinishScriptRefresh(): () => void {
|
|
74
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
75
|
+
let running = false
|
|
76
|
+
let again = false
|
|
77
|
+
|
|
78
|
+
const run = async () => {
|
|
79
|
+
if (running) {
|
|
80
|
+
again = true
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
running = true
|
|
84
|
+
try {
|
|
85
|
+
const nodes = useScene.getState().nodes
|
|
86
|
+
if (useScene.getState().readOnly || staleFinishScripts(nodes).length === 0) return
|
|
87
|
+
const refresh = await refreshFinishScripts(nodes, editorFinishScriptHost)
|
|
88
|
+
for (const { nodeId, face, error } of refresh.failed)
|
|
89
|
+
console.error(
|
|
90
|
+
`[finish script] ${nodeId}${face ? ` face ${face}` : ''} could not be rebuilt: ${error}`,
|
|
91
|
+
)
|
|
92
|
+
const writes = finishScriptWrites(useScene.getState().nodes, refresh.rebuilt)
|
|
93
|
+
if (writes.length === 0) return
|
|
94
|
+
pauseSceneHistory(useScene)
|
|
95
|
+
try {
|
|
96
|
+
useScene
|
|
97
|
+
.getState()
|
|
98
|
+
.updateNodes(writes.map(({ id, data }) => ({ id: id as AnyNodeId, data })))
|
|
99
|
+
} finally {
|
|
100
|
+
resumeSceneHistory(useScene)
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
running = false
|
|
104
|
+
if (again) {
|
|
105
|
+
again = false
|
|
106
|
+
schedule()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const schedule = () => {
|
|
112
|
+
if (timer) clearTimeout(timer)
|
|
113
|
+
timer = setTimeout(() => {
|
|
114
|
+
timer = null
|
|
115
|
+
void run()
|
|
116
|
+
}, SETTLE_MS)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const unsubscribe = useScene.subscribe((state, previous) => {
|
|
120
|
+
if (state.nodes !== previous.nodes) schedule()
|
|
121
|
+
})
|
|
122
|
+
schedule()
|
|
123
|
+
return () => {
|
|
124
|
+
unsubscribe()
|
|
125
|
+
if (timer) clearTimeout(timer)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -95,21 +95,21 @@ function sceneWithVisibleAndHiddenBoxes(): {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
describe('prepareSceneForExport', () => {
|
|
98
|
-
test.each([
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
98
|
+
test.each([true, false])(
|
|
99
|
+
'omits registered draft geometry absent from the saved document (onlyVisible=%s)',
|
|
100
|
+
(onlyVisible) => {
|
|
101
|
+
const { root, nodes } = sceneWithVisibleAndHiddenBoxes()
|
|
102
|
+
const draft = sceneRegistry.nodes.get('item_hidden')!
|
|
103
|
+
draft.name = 'Unplaced preview'
|
|
104
|
+
nodes.item_hidden!.visible = true
|
|
105
|
+
delete nodes.item_hidden
|
|
106
|
+
|
|
107
|
+
const result = prepareSceneForExport(root, nodes, { onlyVisible })
|
|
108
|
+
expect(result.scene.getObjectByName('Unplaced preview')).toBeUndefined()
|
|
109
|
+
expect(result.scene.children).toHaveLength(1)
|
|
110
|
+
expect(root.getObjectByName('Unplaced preview')).toBe(draft)
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
113
|
|
|
114
114
|
test('skips a refused subtree and reports its warning while exporting other objects', () => {
|
|
115
115
|
const root = new THREE.Group()
|
|
@@ -457,134 +457,133 @@ describe('prepareSceneForExport', () => {
|
|
|
457
457
|
}
|
|
458
458
|
})
|
|
459
459
|
|
|
460
|
-
test.each([
|
|
461
|
-
'
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
],
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
],
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
)
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
prepared.dispose()
|
|
460
|
+
test.each(['sync', 'portable-sync', 'portable-async'] as const)(
|
|
461
|
+
'bakes Site bounds and same-kind sibling reservations through %s hooks',
|
|
462
|
+
async (mode) => {
|
|
463
|
+
const restoreRegistry = nodeRegistry._snapshot()
|
|
464
|
+
try {
|
|
465
|
+
const kind = 'test:site-context-bake'
|
|
466
|
+
const nodeId = 'site_context_bake'
|
|
467
|
+
const site = SiteNode.parse({
|
|
468
|
+
polygon: {
|
|
469
|
+
type: 'polygon',
|
|
470
|
+
points: [
|
|
471
|
+
[0, 0],
|
|
472
|
+
[12, 0],
|
|
473
|
+
[12, 8],
|
|
474
|
+
[0, 8],
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
children: [nodeId, 'declared_sibling', 'linked_sibling', 'other_kind'],
|
|
478
|
+
})
|
|
479
|
+
const otherSite = SiteNode.parse({
|
|
480
|
+
polygon: {
|
|
481
|
+
type: 'polygon',
|
|
482
|
+
points: [
|
|
483
|
+
[0, 0],
|
|
484
|
+
[30, 0],
|
|
485
|
+
[30, 4],
|
|
486
|
+
[0, 4],
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
children: ['other_site_sibling'],
|
|
490
|
+
})
|
|
491
|
+
const buildGeometry = (context: GeometryContext) => {
|
|
492
|
+
const points = (context.parent as SiteNode | null)?.polygon?.points ?? [[0, 0]]
|
|
493
|
+
const xs = points.map(([x]) => x)
|
|
494
|
+
const zs = points.map(([, z]) => z)
|
|
495
|
+
const reservedWidth = context.siblings.reduce(
|
|
496
|
+
(width, sibling) => width + Number(sibling.metadata?.reservedWidth ?? 0),
|
|
497
|
+
0,
|
|
498
|
+
)
|
|
499
|
+
return new THREE.Mesh(
|
|
500
|
+
new THREE.BoxGeometry(
|
|
501
|
+
Math.max(...xs) - Math.min(...xs) - reservedWidth,
|
|
502
|
+
1,
|
|
503
|
+
Math.max(...zs) - Math.min(...zs),
|
|
504
|
+
),
|
|
505
|
+
new THREE.MeshStandardMaterial(),
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
registerNode({
|
|
509
|
+
kind,
|
|
510
|
+
schemaVersion: 1,
|
|
511
|
+
schema: DoorNode,
|
|
512
|
+
category: 'furnish',
|
|
513
|
+
defaults: () => ({}) as never,
|
|
514
|
+
capabilities: {},
|
|
515
|
+
bake: 'replace',
|
|
516
|
+
...(mode === 'portable-async'
|
|
517
|
+
? { bakeGeometryAsync: async (_node, context) => buildGeometry(context) }
|
|
518
|
+
: { bakeGeometry: (_node, context) => buildGeometry(context) }),
|
|
519
|
+
} as AnyNodeDefinition)
|
|
520
|
+
const nodes = {
|
|
521
|
+
[site.id]: site,
|
|
522
|
+
[otherSite.id]: otherSite,
|
|
523
|
+
[nodeId]: {
|
|
524
|
+
id: nodeId,
|
|
525
|
+
type: kind,
|
|
526
|
+
parentId: null,
|
|
527
|
+
visible: true,
|
|
528
|
+
metadata: { reservedWidth: 100 },
|
|
529
|
+
},
|
|
530
|
+
declared_sibling: {
|
|
531
|
+
id: 'declared_sibling',
|
|
532
|
+
type: kind,
|
|
533
|
+
parentId: null,
|
|
534
|
+
metadata: { reservedWidth: 2 },
|
|
535
|
+
},
|
|
536
|
+
pointer_sibling: {
|
|
537
|
+
id: 'pointer_sibling',
|
|
538
|
+
type: kind,
|
|
539
|
+
parentId: site.id,
|
|
540
|
+
metadata: { reservedWidth: 3 },
|
|
541
|
+
},
|
|
542
|
+
linked_sibling: {
|
|
543
|
+
id: 'linked_sibling',
|
|
544
|
+
type: kind,
|
|
545
|
+
parentId: site.id,
|
|
546
|
+
metadata: { reservedWidth: 1 },
|
|
547
|
+
},
|
|
548
|
+
other_kind: {
|
|
549
|
+
id: 'other_kind',
|
|
550
|
+
type: 'test:other-kind',
|
|
551
|
+
parentId: site.id,
|
|
552
|
+
metadata: { reservedWidth: 100 },
|
|
553
|
+
},
|
|
554
|
+
other_site_sibling: {
|
|
555
|
+
id: 'other_site_sibling',
|
|
556
|
+
type: kind,
|
|
557
|
+
parentId: otherSite.id,
|
|
558
|
+
metadata: { reservedWidth: 4 },
|
|
559
|
+
},
|
|
560
|
+
} as unknown as Record<string, AnyNode>
|
|
561
|
+
const root = new THREE.Group()
|
|
562
|
+
const source = new THREE.Group()
|
|
563
|
+
root.add(source)
|
|
564
|
+
sceneRegistry.nodes.set(nodeId, source)
|
|
565
|
+
const prepare = mode === 'sync' ? prepareSceneForExport : prepareSceneForExportAsync
|
|
566
|
+
const exportedSize = async () => {
|
|
567
|
+
const prepared = await prepare(root, nodes)
|
|
568
|
+
try {
|
|
569
|
+
const exported = prepared.scene.getObjectByName(nodeId)!
|
|
570
|
+
return new THREE.Box3().setFromObject(exported).getSize(new THREE.Vector3()).toArray()
|
|
571
|
+
} finally {
|
|
572
|
+
prepared.dispose()
|
|
573
|
+
}
|
|
575
574
|
}
|
|
576
|
-
}
|
|
577
575
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
576
|
+
expect(await exportedSize()).toEqual([6, 1, 8])
|
|
577
|
+
nodes[nodeId]!.parentId = otherSite.id
|
|
578
|
+
expect(await exportedSize()).toEqual([26, 1, 4])
|
|
579
|
+
nodes[nodeId]!.parentId = null
|
|
580
|
+
site.children = site.children.filter((id) => id !== nodeId)
|
|
581
|
+
expect(await exportedSize()).toEqual([0, 1, 0])
|
|
582
|
+
} finally {
|
|
583
|
+
restoreRegistry()
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
)
|
|
588
587
|
|
|
589
588
|
test('selective exports omit a procedural kind without changing the live scene or later exports', () => {
|
|
590
589
|
const restoreRegistry = nodeRegistry._snapshot()
|
|
@@ -2373,29 +2372,32 @@ describe('plugin bake policies through export', () => {
|
|
|
2373
2372
|
test.each([
|
|
2374
2373
|
['sync', 'meadow-bake', ['bakeGeometry']],
|
|
2375
2374
|
['async', 'meadow-bake-async', ['bakeGeometryAsync']],
|
|
2376
|
-
] as const)(
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2375
|
+
] as const)(
|
|
2376
|
+
'%s export strips, replaces and keeps static plugin kinds without touching the live tree',
|
|
2377
|
+
async (mode, bakedName, expectedCalls) => {
|
|
2378
|
+
await withBakePlugin([pluginId], async ({ root, live, nodes, calls }) => {
|
|
2379
|
+
const prepared =
|
|
2380
|
+
mode === 'sync'
|
|
2381
|
+
? prepareSceneForExport(root, nodes)
|
|
2382
|
+
: await prepareSceneForExportAsync(root, nodes)
|
|
2383
|
+
try {
|
|
2384
|
+
const scene = prepared.scene
|
|
2385
|
+
expect(scene.getObjectByName(overlayId)).toBeUndefined()
|
|
2386
|
+
expect(scene.getObjectByName(`${overlayId}-live`)).toBeUndefined()
|
|
2386
2387
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2388
|
+
const meadow = scene.getObjectByName(meadowId)
|
|
2389
|
+
expect(meadow?.getObjectByName(bakedName)).toBeDefined()
|
|
2390
|
+
expect(meadow?.getObjectByName(`${meadowId}-live`)).toBeUndefined()
|
|
2390
2391
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2392
|
+
expect(scene.getObjectByName(rockId)?.getObjectByName(`${rockId}-live`)).toBeDefined()
|
|
2393
|
+
expect(calls).toEqual([...expectedCalls])
|
|
2394
|
+
expectLiveTreeUntouched(live)
|
|
2395
|
+
} finally {
|
|
2396
|
+
prepared.dispose()
|
|
2397
|
+
}
|
|
2398
|
+
})
|
|
2399
|
+
},
|
|
2400
|
+
)
|
|
2399
2401
|
|
|
2400
2402
|
test('an uninstalled plugin contributes nothing to the export', async () => {
|
|
2401
2403
|
await withBakePlugin([], async ({ root, live, nodes, calls }) => {
|
|
@@ -2436,3 +2438,26 @@ test('prepared exports preserve surface identities and IFC subpart roles on regi
|
|
|
2436
2438
|
expect(exported.userData.pascalIfcRole).toBe('railing')
|
|
2437
2439
|
expect(exported.userData.pascalIfcParts).toEqual([{ start: 0, count: 3, role: 'handrail' }])
|
|
2438
2440
|
})
|
|
2441
|
+
|
|
2442
|
+
test('exports a plugin LOD once without invoking its argument-requiring constructor', () => {
|
|
2443
|
+
class PluginLOD extends THREE.LOD {
|
|
2444
|
+
constructor(model: THREE.Mesh) {
|
|
2445
|
+
super()
|
|
2446
|
+
if (!model) throw new Error('Model is required')
|
|
2447
|
+
this.addLevel(model, 0)
|
|
2448
|
+
this.addLevel(model.clone(), 10)
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial())
|
|
2452
|
+
const lod = new PluginLOD(mesh)
|
|
2453
|
+
const root = new THREE.Group()
|
|
2454
|
+
root.add(lod)
|
|
2455
|
+
const { scene } = prepareSceneForExport(root, {})
|
|
2456
|
+
const meshes: THREE.Mesh[] = []
|
|
2457
|
+
scene.traverse((object) => {
|
|
2458
|
+
if (object instanceof THREE.Mesh) meshes.push(object)
|
|
2459
|
+
})
|
|
2460
|
+
expect(meshes).toHaveLength(2)
|
|
2461
|
+
expect(meshes[0]!.geometry).not.toBe(mesh.geometry)
|
|
2462
|
+
expect(lod.levels).toHaveLength(2)
|
|
2463
|
+
})
|
package/src/lib/glb-export.ts
CHANGED
|
@@ -903,7 +903,10 @@ function cloneSceneForExport(
|
|
|
903
903
|
): THREE.Object3D {
|
|
904
904
|
if (excludedObjects.has(source)) return new THREE.Group()
|
|
905
905
|
|
|
906
|
-
|
|
906
|
+
// LOD.copy clones every level even when called through clone(false). Export
|
|
907
|
+
// walks the children itself, and plugin LOD constructors may require inputs.
|
|
908
|
+
const clone =
|
|
909
|
+
source instanceof THREE.LOD ? new THREE.Group().copy(source, false) : source.clone(false)
|
|
907
910
|
clone.userData = cloneExportUserData(source.userData)
|
|
908
911
|
const renderable = source as THREE.Mesh
|
|
909
912
|
const renderableClone = clone as THREE.Mesh
|
package/src/lib/history.test.ts
CHANGED
|
@@ -109,11 +109,10 @@ describe('standalone history source invalidation', () => {
|
|
|
109
109
|
`)
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
test.each([
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
runSourceHistoryTest(`
|
|
112
|
+
test.each([true, false])(
|
|
113
|
+
'discarded preview neighbours rebuild after undo/redo (joined: %s)',
|
|
114
|
+
(joined) => {
|
|
115
|
+
runSourceHistoryTest(`
|
|
117
116
|
const react = await importShared('react')
|
|
118
117
|
mockShared('react', () => ({ ...react, useEffect: () => {} }))
|
|
119
118
|
const frames = []
|
|
@@ -152,7 +151,8 @@ describe('standalone history source invalidation', () => {
|
|
|
152
151
|
assert.deepEqual(Array.from(b.geometry.getAttribute('position').array), canonical)
|
|
153
152
|
}
|
|
154
153
|
`)
|
|
155
|
-
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
156
|
|
|
157
157
|
test('opening reparent dirties old and new walls on undo and redo', () => {
|
|
158
158
|
runSourceHistoryTest(`
|
|
@@ -343,7 +343,12 @@ describe('standalone history source invalidation', () => {
|
|
|
343
343
|
mockShared('react', () => ({ ...react, useEffect: () => {}, useRef: current => ({ current }) }))
|
|
344
344
|
const frames = []
|
|
345
345
|
const fiber = await importShared('@react-three/fiber')
|
|
346
|
-
|
|
346
|
+
// DoorSystem and WindowSystem register their materials for scene capture through useThree.
|
|
347
|
+
mockShared('@react-three/fiber', () => ({
|
|
348
|
+
...fiber,
|
|
349
|
+
useFrame: frame => frames.push(frame),
|
|
350
|
+
useThree: select => select({ scene: {} }),
|
|
351
|
+
}))
|
|
347
352
|
const selector = store => Object.assign(fn => fn(store.getState()), store)
|
|
348
353
|
mockShared('@pascal-app/core', () => ({ ...core, useScene: selector(scene), useLiveNodeOverrides: selector(overrides) }))
|
|
349
354
|
const viewer = await importShared('@pascal-app/viewer')
|
|
@@ -111,6 +111,10 @@ export type InteractionScope =
|
|
|
111
111
|
// (`resolveOverlayPolicy`). A brush that only claimed the scope between
|
|
112
112
|
// pointer-down and -up would flicker all of that back on between dabs.
|
|
113
113
|
| { kind: 'sculpting' }
|
|
114
|
+
// Point and ask: the pointer belongs to pointing at things for the agent, so selection, handles and
|
|
115
|
+
// the floating action menu step back (the same active policy as a brush) for as long as the mode
|
|
116
|
+
// is on. Held by the point-ask session, which leaves the mode if another interaction takes over.
|
|
117
|
+
| { kind: 'pointing' }
|
|
114
118
|
|
|
115
119
|
export type InteractionKind = InteractionScope['kind']
|
|
116
120
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
type AnyNode,
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
BuildingNode,
|
|
6
|
+
LevelNode,
|
|
7
|
+
RoofNode,
|
|
8
|
+
useScene,
|
|
9
|
+
WallNode,
|
|
10
|
+
} from '@pascal-app/core'
|
|
11
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
12
|
+
import { selectPlacedNode } from './level-selection'
|
|
13
|
+
|
|
14
|
+
// A roof drawn over the ground floor's walls lands on the Roof level, which the editor hides while
|
|
15
|
+
// the ground floor is selected: selecting the roof there alone left it out of sight.
|
|
16
|
+
const BUILDING = 'building_select' as BuildingNode['id']
|
|
17
|
+
const GROUND = 'level_ground' as LevelNode['id']
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
const ground = LevelNode.parse({ id: GROUND, parentId: BUILDING, level: 0, children: ['wall_a'] })
|
|
21
|
+
const wall = WallNode.parse({ id: 'wall_a', parentId: GROUND, start: [0, 0], end: [4, 0] })
|
|
22
|
+
const roofLevel = LevelNode.parse({
|
|
23
|
+
id: 'level_roof',
|
|
24
|
+
parentId: BUILDING,
|
|
25
|
+
level: 1,
|
|
26
|
+
children: ['roof_main'],
|
|
27
|
+
metadata: { role: 'roof' },
|
|
28
|
+
})
|
|
29
|
+
const roof = RoofNode.parse({ id: 'roof_main', parentId: roofLevel.id })
|
|
30
|
+
const building = BuildingNode.parse({ id: BUILDING, children: [GROUND, roofLevel.id] })
|
|
31
|
+
useScene.setState({
|
|
32
|
+
nodes: Object.fromEntries(
|
|
33
|
+
[building, ground, wall, roofLevel, roof].map((node) => [node.id, node as AnyNode]),
|
|
34
|
+
),
|
|
35
|
+
rootNodeIds: [BUILDING],
|
|
36
|
+
} as never)
|
|
37
|
+
useViewer.getState().setSelection({ buildingId: BUILDING, levelId: GROUND })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('selectPlacedNode', () => {
|
|
41
|
+
test('a node placed on another level is selected with its level', () => {
|
|
42
|
+
selectPlacedNode('roof_main' as AnyNodeId)
|
|
43
|
+
expect(useViewer.getState().selection).toMatchObject({
|
|
44
|
+
buildingId: BUILDING,
|
|
45
|
+
levelId: 'level_roof',
|
|
46
|
+
selectedIds: ['roof_main'],
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('a node placed on the level in view leaves the level as it is', () => {
|
|
51
|
+
selectPlacedNode('wall_a' as AnyNodeId)
|
|
52
|
+
expect(useViewer.getState().selection).toMatchObject({
|
|
53
|
+
levelId: GROUND,
|
|
54
|
+
selectedIds: ['wall_a'],
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|