@pascal-app/editor 1.1.0 → 1.1.2
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 +9 -15
- package/src/components/editor/action-menu-button.tsx +1 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
- package/src/components/editor/custom-camera-controls.tsx +11 -11
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +20 -1981
- package/src/components/editor/floating-action-menu.tsx +1 -1
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +6 -2
- package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
- package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
- package/src/components/editor/floorplan-panel.tsx +26 -22
- package/src/components/editor/floorplan-wall-typing-keys.ts +1 -1
- package/src/components/editor/follow-pascal.test.tsx +1 -2
- package/src/components/editor/follow-pascal.tsx +1 -1
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/group-actions.ts +1 -2
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +1 -2
- package/src/components/editor/group-rotate-handle.tsx +1 -2
- package/src/components/editor/handles/use-handle-drag.ts +1 -2
- package/src/components/editor/index.tsx +24 -14
- package/src/components/editor/inspector-extension-actions.tsx +1 -1
- package/src/components/editor/node-arrow-handles.tsx +1 -1
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/room-controls.tsx +7 -4
- package/src/components/editor/room-handles-2d.tsx +2 -3
- package/src/components/editor/room-handles.tsx +1 -2
- package/src/components/editor/room-highlight.tsx +6 -105
- package/src/components/editor/room-labels.test.ts +8 -822
- package/src/components/editor/room-labels.tsx +40 -1671
- package/src/components/editor/room-webgpu-materials.test.ts +1 -1
- package/src/components/editor/selection-manager.tsx +5 -3
- package/src/components/editor/site-edge-labels.tsx +1 -1
- package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
- package/src/components/editor/use-floorplan-background-placement.ts +2 -1
- package/src/components/editor/views/plugins-view.tsx +1 -1
- package/src/components/editor/views/view-bar.tsx +9 -9
- package/src/components/editor/wall-measurement-label.tsx +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +1 -1
- package/src/components/editor/wall-pull-grip.tsx +2 -2
- package/src/components/editor-2d/floorplan-group-move.tsx +2 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1 -1
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +1 -2
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +1 -2
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +8 -10
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +25 -33
- package/src/components/systems/roof/roof-edit-system.tsx +4 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +1 -1
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +2 -3
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +6 -2
- package/src/components/tools/registry/move-registry-node-tool.tsx +1 -2
- package/src/components/tools/shared/cursor-sphere.tsx +1 -1
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/ui/action-menu/camera-actions.tsx +2 -2
- package/src/components/ui/action-menu/control-modes.tsx +1 -1
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
- package/src/components/ui/action-menu/index.tsx +3 -3
- package/src/components/ui/action-menu/measurement-control.tsx +3 -3
- package/src/components/ui/action-menu/placement.ts +0 -18
- package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
- package/src/components/ui/action-menu/view-toggles.tsx +5 -5
- package/src/components/ui/controls/finish-swatch.tsx +1 -1
- package/src/components/ui/controls/material-paint-panel.tsx +2 -2
- package/src/components/ui/controls/scene-material-list.tsx +2 -2
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +3 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
- package/src/components/ui/helpers/helper-manager.test.tsx +1 -1
- package/src/components/ui/helpers/helper-manager.tsx +1 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.test.tsx +46 -0
- package/src/components/ui/node-icon-image.tsx +33 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
- package/src/components/ui/panels/floor-foundation-panel.tsx +2 -2
- package/src/components/ui/panels/floor-opening-panel.tsx +4 -4
- package/src/components/ui/panels/node-display.ts +1 -1
- package/src/components/ui/panels/panel-manager.tsx +1 -1
- package/src/components/ui/panels/panel-wrapper.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +1 -1
- package/src/components/ui/panels/room-construction-rows.tsx +6 -6
- package/src/components/ui/panels/room-name-options.ts +1 -5
- package/src/components/ui/panels/room-panel.tsx +6 -8
- package/src/components/ui/primitives/color-dot.tsx +2 -2
- package/src/components/ui/primitives/opacity-control.tsx +2 -2
- package/src/components/ui/primitives/sidebar.tsx +3 -3
- package/src/components/ui/right-stack.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -1
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +1 -1
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
- package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
- package/src/components/viewer-overlay.tsx +1 -2
- package/src/hooks/use-auto-frame.ts +1 -2
- package/src/hooks/use-keyboard.ts +1 -2
- package/src/hooks/use-selected-room.ts +5 -7
- package/src/i18n/floor-footprint-name.ts +1 -1
- package/src/i18n/locales/en/messages.po +180 -182
- package/src/i18n/locales/es/messages.po +180 -182
- package/src/i18n/locales/fr/messages.po +180 -182
- package/src/i18n/locales/ja/messages.po +180 -182
- package/src/i18n/locales/zh-CN/messages.po +180 -182
- package/src/i18n/locales.test.ts +1 -1
- package/src/index.tsx +109 -131
- package/src/lib/below-grade.test.tsx +4 -4
- package/src/lib/camera-facing.test.ts +1 -1
- package/src/lib/camera-facing.ts +2 -3
- package/src/lib/editor-level-display.ts +1 -1
- package/src/lib/floor-footprints.test.ts +2 -2
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
- package/src/lib/floorplan/annotation-visibility.ts +1 -1
- package/src/lib/floorplan/floorplan-export.test.ts +1 -1
- package/src/lib/floorplan/floorplan-export.tsx +8 -11
- package/src/lib/floorplan/floorplan-extension.ts +4 -157
- package/src/lib/floorplan/floorplan-mode.ts +2 -1
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
- package/src/lib/floorplan/geometry.ts +2 -112
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +2 -1
- package/src/lib/floorplan/schedules.ts +6 -3
- package/src/lib/floorplan/sheet-export.ts +7 -3
- package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
- package/src/lib/follow-pascal-presentation.ts +1 -1
- package/src/lib/glb-export.ts +1 -1
- package/src/lib/material-paint.ts +1 -1
- package/src/lib/measurements.test.ts +2 -1
- package/src/lib/mezzanine-selection.test.ts +1 -1
- package/src/lib/paint-region-mode.ts +1 -1
- package/src/lib/point-ask/session.ts +1 -2
- package/src/lib/roof-duplication.test.ts +100 -0
- package/src/lib/roof-duplication.ts +30 -5
- package/src/lib/room-handle-drag.ts +2 -12
- package/src/lib/room-name-catalog.test.ts +2 -1
- package/src/lib/room-name-messages.test.ts +2 -1
- package/src/lib/room-name-messages.ts +1 -118
- package/src/lib/room-name-storage.test.ts +2 -1
- package/src/lib/room-selection-commands.ts +2 -2
- package/src/lib/room-selection.test.ts +6 -6
- package/src/lib/room-transform-session.ts +1 -2
- package/src/lib/room-zone-routing.ts +2 -2
- package/src/lib/selection-routing.test.ts +0 -28
- package/src/lib/selection-routing.ts +1 -40
- package/src/lib/sfx-bus.ts +8 -32
- package/src/lib/sfx-player.test.ts +72 -1
- package/src/lib/sfx-player.ts +49 -337
- package/src/lib/viewer-selection.test.ts +38 -10
- package/src/lib/wall-pull.test.ts +1 -1
- package/src/lib/wall-pull.ts +1 -27
- package/src/store/use-editor.tsx +17 -26
- package/src/store/use-floorplan-annotation-visibility.ts +5 -5
- package/src/store/use-wall-region-handles-selection.ts +1 -2
- package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
- package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
- package/src/components/editor/first-person/build-collider-world.ts +0 -549
- package/src/components/editor/first-person/ground-holes.test.ts +0 -128
- package/src/components/editor/first-person/ground-holes.ts +0 -130
- package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
- package/src/components/editor/first-person/mechanism-targets.ts +0 -88
- package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
- package/src/components/editor/first-person/terrain-collider.ts +0 -212
- package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
- package/src/components/editor/floorplan-camera-sync.ts +0 -213
- package/src/components/editor/localized-html.tsx +0 -22
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
- package/src/components/editor-2d/svg-paths.ts +0 -124
- package/src/components/ui/action-menu/action-button.tsx +0 -89
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
- package/src/components/ui/primitives/button.tsx +0 -69
- package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
- package/src/components/ui/primitives/popover.tsx +0 -42
- package/src/components/ui/primitives/switch.tsx +0 -29
- package/src/components/ui/primitives/tooltip.tsx +0 -57
- package/src/components/ui/scene-loader.tsx +0 -73
- package/src/components/viewer/floorplan-compass-button.tsx +0 -67
- package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
- package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
- package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
- package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
- package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
- package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
- package/src/components/viewer/floorplan-preview.tsx +0 -1065
- package/src/components/viewer/render-settings-menu.tsx +0 -294
- package/src/components/viewer/sky-control.tsx +0 -710
- package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
- package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
- package/src/components/viewer/viewer-controls-bar.tsx +0 -496
- package/src/components/viewer/viewer-hover-label.tsx +0 -42
- package/src/components/viewer/viewer-rooms.tsx +0 -248
- package/src/components/viewer/viewer-scene-header.tsx +0 -286
- package/src/components/viewer/viewer-selection-manager.tsx +0 -124
- package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
- package/src/components/viewer/viewer-stage-modes.ts +0 -49
- package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
- package/src/components/viewer/viewer-stage.test.tsx +0 -47
- package/src/components/viewer/viewer-stage.tsx +0 -221
- package/src/components/walkthrough-hud.tsx +0 -193
- package/src/hooks/use-mobile.ts +0 -19
- package/src/i18n/level-name.ts +0 -39
- package/src/i18n/scene-theme-name.ts +0 -16
- package/src/lib/below-grade.ts +0 -133
- package/src/lib/camera-pose.test.ts +0 -237
- package/src/lib/camera-pose.ts +0 -172
- package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
- package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
- package/src/lib/floorplan/floorplan-readonly.ts +0 -95
- package/src/lib/floorplan/marks.test.ts +0 -213
- package/src/lib/floorplan/marks.ts +0 -238
- package/src/lib/measurements.ts +0 -8
- package/src/lib/node-display-label.ts +0 -37
- package/src/lib/room-assembly-overlay.test.ts +0 -275
- package/src/lib/room-assembly-overlay.ts +0 -265
- package/src/lib/room-selection.ts +0 -417
- package/src/lib/scene-bounds.test.ts +0 -327
- package/src/lib/scene-bounds.ts +0 -233
- package/src/lib/typing-target.ts +0 -9
- package/src/lib/viewer-selection.ts +0 -595
- package/src/lib/zone-content.ts +0 -133
- package/src/store/camera-pose-store.test.ts +0 -29
- package/src/store/camera-pose-store.ts +0 -27
- package/src/store/navigation-sync-pose-store.test.ts +0 -34
- package/src/store/navigation-sync-pose-store.ts +0 -27
- package/src/store/use-audio.test.ts +0 -146
- package/src/store/use-audio.tsx +0 -169
- package/src/store/use-first-person-hud.ts +0 -40
- package/src/store/use-follow-pascal.test.ts +0 -62
- package/src/store/use-follow-pascal.ts +0 -53
package/src/lib/below-grade.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AnyNode,
|
|
3
|
-
type AnyNodeId,
|
|
4
|
-
buildingGroundBox,
|
|
5
|
-
type GroundBox,
|
|
6
|
-
type LevelGradeStanding,
|
|
7
|
-
levelGradeStanding,
|
|
8
|
-
levelGroundBox,
|
|
9
|
-
useScene,
|
|
10
|
-
} from '@pascal-app/core'
|
|
11
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
12
|
-
import { useEffect } from 'react'
|
|
13
|
-
|
|
14
|
-
type LevelDisplay = {
|
|
15
|
-
hideLevelsAboveSelection: boolean
|
|
16
|
-
selection: { levelId: string | null; buildingId?: string | null }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The shown level's standing against the ground: the storeys above the selected level are
|
|
21
|
-
* hidden and that level's floor lies under the ground — all of it, or the part of it a
|
|
22
|
-
* hillside stands over — which is then veiled over it.
|
|
23
|
-
*/
|
|
24
|
-
export function gradeStandingShown(
|
|
25
|
-
viewer: LevelDisplay,
|
|
26
|
-
nodes: Record<AnyNodeId, AnyNode>,
|
|
27
|
-
): LevelGradeStanding {
|
|
28
|
-
const { levelId } = viewer.selection
|
|
29
|
-
if (!viewer.hideLevelsAboveSelection || levelId === null) return 'at-grade'
|
|
30
|
-
return levelGradeStanding(nodes, levelId)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Whether the level shown lies below grade, on all of its footprint or part of it. */
|
|
34
|
-
export function belowGradeShown(viewer: LevelDisplay, nodes: Record<AnyNodeId, AnyNode>): boolean {
|
|
35
|
-
return gradeStandingShown(viewer, nodes) !== 'at-grade'
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Keeps the viewer's `belowGrade` and `gradeStanding` on the shown level while the
|
|
40
|
-
* selection, the level display and the scene change. The returned function stops, and the
|
|
41
|
-
* ground stands solid again.
|
|
42
|
-
*/
|
|
43
|
-
export function followBelowGrade(): () => void {
|
|
44
|
-
const update = () => {
|
|
45
|
-
const gradeStanding = gradeStandingShown(useViewer.getState(), useScene.getState().nodes)
|
|
46
|
-
const viewer = useViewer.getState()
|
|
47
|
-
if (
|
|
48
|
-
viewer.gradeStanding !== gradeStanding ||
|
|
49
|
-
viewer.belowGrade !== (gradeStanding !== 'at-grade')
|
|
50
|
-
)
|
|
51
|
-
useViewer.setState({ gradeStanding, belowGrade: gradeStanding !== 'at-grade' })
|
|
52
|
-
}
|
|
53
|
-
update()
|
|
54
|
-
// The flag itself is watched too: the other host's level display, handing over, resets it.
|
|
55
|
-
const stopViewer = useViewer.subscribe((state, previous) => {
|
|
56
|
-
if (
|
|
57
|
-
state.selection.levelId !== previous.selection.levelId ||
|
|
58
|
-
state.hideLevelsAboveSelection !== previous.hideLevelsAboveSelection ||
|
|
59
|
-
state.belowGrade !== previous.belowGrade ||
|
|
60
|
-
state.gradeStanding !== previous.gradeStanding
|
|
61
|
-
)
|
|
62
|
-
update()
|
|
63
|
-
})
|
|
64
|
-
const stopScene = useScene.subscribe((state, previous) => {
|
|
65
|
-
if (state.nodes !== previous.nodes) update()
|
|
66
|
-
})
|
|
67
|
-
return () => {
|
|
68
|
-
stopViewer()
|
|
69
|
-
stopScene()
|
|
70
|
-
useViewer.setState({ belowGrade: false, gradeStanding: 'at-grade' })
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** A host's level display marks a level below grade as shown while `active`. */
|
|
75
|
-
export function useBelowGrade(active: boolean) {
|
|
76
|
-
useEffect(() => (active ? followBelowGrade() : undefined), [active])
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The box the ground window opens for: the shown level while the editor's level display
|
|
81
|
-
* hides what is above it, the whole building when it stands whole (Preview).
|
|
82
|
-
*/
|
|
83
|
-
export function groundWindowBoxShown(
|
|
84
|
-
viewer: LevelDisplay,
|
|
85
|
-
nodes: Record<AnyNodeId, AnyNode>,
|
|
86
|
-
): GroundBox | null {
|
|
87
|
-
const { levelId, buildingId } = viewer.selection
|
|
88
|
-
if (viewer.hideLevelsAboveSelection && levelId !== null) return levelGroundBox(nodes, levelId)
|
|
89
|
-
return buildingGroundBox(nodes, buildingId ?? null)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function sameBox(a: GroundBox | null, b: GroundBox | null): boolean {
|
|
93
|
-
if (a === null || b === null) return a === b
|
|
94
|
-
return (
|
|
95
|
-
a.min.every((value, axis) => value === b.min[axis]) &&
|
|
96
|
-
a.max.every((value, axis) => value === b.max[axis])
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Keeps the viewer's `groundWindowBox` on {@link groundWindowBoxShown} while the selection,
|
|
102
|
-
* the level display and the scene change. The returned function stops, and the window closes.
|
|
103
|
-
*/
|
|
104
|
-
export function followGroundWindowBox(): () => void {
|
|
105
|
-
const update = () => {
|
|
106
|
-
const box = groundWindowBoxShown(useViewer.getState(), useScene.getState().nodes)
|
|
107
|
-
if (!sameBox(useViewer.getState().groundWindowBox, box))
|
|
108
|
-
useViewer.setState({ groundWindowBox: box })
|
|
109
|
-
}
|
|
110
|
-
update()
|
|
111
|
-
const stopViewer = useViewer.subscribe((state, previous) => {
|
|
112
|
-
if (
|
|
113
|
-
state.selection.levelId !== previous.selection.levelId ||
|
|
114
|
-
state.selection.buildingId !== previous.selection.buildingId ||
|
|
115
|
-
state.hideLevelsAboveSelection !== previous.hideLevelsAboveSelection ||
|
|
116
|
-
state.groundWindowBox !== previous.groundWindowBox
|
|
117
|
-
)
|
|
118
|
-
update()
|
|
119
|
-
})
|
|
120
|
-
const stopScene = useScene.subscribe((state, previous) => {
|
|
121
|
-
if (state.nodes !== previous.nodes) update()
|
|
122
|
-
})
|
|
123
|
-
return () => {
|
|
124
|
-
stopViewer()
|
|
125
|
-
stopScene()
|
|
126
|
-
useViewer.setState({ groundWindowBox: null })
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** The ground window follows the shown level or building while `active` (not on the site). */
|
|
131
|
-
export function useGroundWindowBox(active: boolean) {
|
|
132
|
-
useEffect(() => (active ? followGroundWindowBox() : undefined), [active])
|
|
133
|
-
}
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
normalizeCameraPose,
|
|
4
|
-
planCameraPoseApplication,
|
|
5
|
-
publishInitialCameraPose,
|
|
6
|
-
releaseCameraPoseEventSuppression,
|
|
7
|
-
stepCameraPoseInterpolation,
|
|
8
|
-
withCameraPoseDistance,
|
|
9
|
-
} from './camera-pose'
|
|
10
|
-
|
|
11
|
-
describe('camera pose', () => {
|
|
12
|
-
test('normalizes a finite pose without retaining input tuples', () => {
|
|
13
|
-
const position: [number, number, number] = [1, 2, 3]
|
|
14
|
-
const target: [number, number, number] = [4, 5, 6]
|
|
15
|
-
const pose = normalizeCameraPose({
|
|
16
|
-
position,
|
|
17
|
-
target,
|
|
18
|
-
projection: 'perspective',
|
|
19
|
-
fov: 50,
|
|
20
|
-
viewWidth: 12,
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
expect(pose).toEqual({
|
|
24
|
-
fov: 50,
|
|
25
|
-
position,
|
|
26
|
-
projection: 'perspective',
|
|
27
|
-
target,
|
|
28
|
-
viewWidth: 12,
|
|
29
|
-
})
|
|
30
|
-
expect(pose?.position).not.toBe(position)
|
|
31
|
-
expect(pose?.target).not.toBe(target)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('rejects non-finite, malformed, and unknown camera poses', () => {
|
|
35
|
-
expect(normalizeCameraPose(null)).toBeNull()
|
|
36
|
-
expect(
|
|
37
|
-
normalizeCameraPose({
|
|
38
|
-
position: [1, 2],
|
|
39
|
-
target: [4, 5, 6],
|
|
40
|
-
projection: 'perspective',
|
|
41
|
-
}),
|
|
42
|
-
).toBeNull()
|
|
43
|
-
expect(
|
|
44
|
-
normalizeCameraPose({
|
|
45
|
-
position: [1, Number.NaN, 3],
|
|
46
|
-
target: [4, 5, 6],
|
|
47
|
-
projection: 'perspective',
|
|
48
|
-
}),
|
|
49
|
-
).toBeNull()
|
|
50
|
-
expect(
|
|
51
|
-
normalizeCameraPose({
|
|
52
|
-
position: [1, 2, 3],
|
|
53
|
-
target: [4, 5, Number.POSITIVE_INFINITY],
|
|
54
|
-
projection: 'perspective',
|
|
55
|
-
}),
|
|
56
|
-
).toBeNull()
|
|
57
|
-
expect(
|
|
58
|
-
normalizeCameraPose({
|
|
59
|
-
position: [1, 2, 3],
|
|
60
|
-
target: [4, 5, 6],
|
|
61
|
-
projection: 'panoramic',
|
|
62
|
-
}),
|
|
63
|
-
).toBeNull()
|
|
64
|
-
expect(
|
|
65
|
-
normalizeCameraPose({
|
|
66
|
-
position: [1, 2, 3],
|
|
67
|
-
target: [4, 5, 6],
|
|
68
|
-
projection: 'perspective',
|
|
69
|
-
fov: Number.NaN,
|
|
70
|
-
}),
|
|
71
|
-
).toBeNull()
|
|
72
|
-
expect(
|
|
73
|
-
normalizeCameraPose({
|
|
74
|
-
position: [1, 2, 3],
|
|
75
|
-
target: [4, 5, 6],
|
|
76
|
-
projection: 'orthographic',
|
|
77
|
-
viewWidth: 0,
|
|
78
|
-
}),
|
|
79
|
-
).toBeNull()
|
|
80
|
-
expect(
|
|
81
|
-
normalizeCameraPose({
|
|
82
|
-
position: [1, 2, 3],
|
|
83
|
-
target: [4, 5, 6],
|
|
84
|
-
projection: 'orthographic',
|
|
85
|
-
viewWidth: Number.POSITIVE_INFINITY,
|
|
86
|
-
}),
|
|
87
|
-
).toBeNull()
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('plans only perspective fov application and clamps it to a safe range', () => {
|
|
91
|
-
expect(
|
|
92
|
-
planCameraPoseApplication({
|
|
93
|
-
position: [1, 2, 3],
|
|
94
|
-
target: [4, 5, 6],
|
|
95
|
-
projection: 'perspective',
|
|
96
|
-
fov: 0,
|
|
97
|
-
})?.perspectiveFov,
|
|
98
|
-
).toBe(1)
|
|
99
|
-
expect(
|
|
100
|
-
planCameraPoseApplication({
|
|
101
|
-
position: [1, 2, 3],
|
|
102
|
-
target: [4, 5, 6],
|
|
103
|
-
projection: 'perspective',
|
|
104
|
-
fov: 200,
|
|
105
|
-
})?.perspectiveFov,
|
|
106
|
-
).toBe(179)
|
|
107
|
-
expect(
|
|
108
|
-
planCameraPoseApplication({
|
|
109
|
-
position: [1, 2, 3],
|
|
110
|
-
target: [4, 5, 6],
|
|
111
|
-
projection: 'orthographic',
|
|
112
|
-
fov: 70,
|
|
113
|
-
})?.perspectiveFov,
|
|
114
|
-
).toBeNull()
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
test('interpolates toward the latest pose and converges exactly', () => {
|
|
118
|
-
const destination = {
|
|
119
|
-
position: [12, 6, -4] as [number, number, number],
|
|
120
|
-
target: [2, 1, 3] as [number, number, number],
|
|
121
|
-
projection: 'perspective' as const,
|
|
122
|
-
}
|
|
123
|
-
const first = stepCameraPoseInterpolation([0, 0, 0], [0, 0, 0], destination, 0.016)
|
|
124
|
-
expect(first.settled).toBe(false)
|
|
125
|
-
expect(first.position[0]).toBeGreaterThan(0)
|
|
126
|
-
expect(first.position[0]).toBeLessThan(destination.position[0])
|
|
127
|
-
|
|
128
|
-
let position = first.position
|
|
129
|
-
let target = first.target
|
|
130
|
-
let settled = first.settled
|
|
131
|
-
for (let frame = 0; frame < 240 && !settled; frame += 1) {
|
|
132
|
-
const step = stepCameraPoseInterpolation(position, target, destination, 0.016)
|
|
133
|
-
position = step.position
|
|
134
|
-
target = step.target
|
|
135
|
-
settled = step.settled
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
expect(settled).toBe(true)
|
|
139
|
-
expect(position).toEqual(destination.position)
|
|
140
|
-
expect(target).toEqual(destination.target)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test('settles an already-current pose without advancing on an invalid delta', () => {
|
|
144
|
-
const destination = {
|
|
145
|
-
position: [1, 2, 3] as [number, number, number],
|
|
146
|
-
target: [4, 5, 6] as [number, number, number],
|
|
147
|
-
projection: 'orthographic' as const,
|
|
148
|
-
}
|
|
149
|
-
expect(
|
|
150
|
-
stepCameraPoseInterpolation(
|
|
151
|
-
destination.position,
|
|
152
|
-
destination.target,
|
|
153
|
-
destination,
|
|
154
|
-
Number.NaN,
|
|
155
|
-
),
|
|
156
|
-
).toEqual({
|
|
157
|
-
position: destination.position,
|
|
158
|
-
settled: true,
|
|
159
|
-
target: destination.target,
|
|
160
|
-
})
|
|
161
|
-
expect(stepCameraPoseInterpolation([0, 0, 0], [0, 0, 0], destination, Number.NaN)).toEqual({
|
|
162
|
-
position: [0, 0, 0],
|
|
163
|
-
settled: false,
|
|
164
|
-
target: [0, 0, 0],
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
test('publishes the initial pose before any camera control update event', () => {
|
|
169
|
-
let publishCount = 0
|
|
170
|
-
|
|
171
|
-
publishInitialCameraPose(() => {
|
|
172
|
-
publishCount += 1
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
expect(publishCount).toBe(1)
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
test('publishes the settled pose when programmatic interpolation releases suppression', () => {
|
|
179
|
-
const suppression = { current: true }
|
|
180
|
-
let publishCount = 0
|
|
181
|
-
|
|
182
|
-
releaseCameraPoseEventSuppression(suppression, () => {
|
|
183
|
-
publishCount += 1
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
expect(suppression.current).toBe(false)
|
|
187
|
-
expect(publishCount).toBe(1)
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
test('retargets the bounded interpolation owner to the latest pose', () => {
|
|
191
|
-
const first = stepCameraPoseInterpolation(
|
|
192
|
-
[0, 0, 0],
|
|
193
|
-
[0, 0, 0],
|
|
194
|
-
{
|
|
195
|
-
position: [10, 0, 0],
|
|
196
|
-
target: [5, 0, 0],
|
|
197
|
-
projection: 'perspective',
|
|
198
|
-
},
|
|
199
|
-
0.016,
|
|
200
|
-
)
|
|
201
|
-
const retargeted = stepCameraPoseInterpolation(
|
|
202
|
-
first.position,
|
|
203
|
-
first.target,
|
|
204
|
-
{
|
|
205
|
-
position: [-10, 0, 0],
|
|
206
|
-
target: [-5, 0, 0],
|
|
207
|
-
projection: 'perspective',
|
|
208
|
-
},
|
|
209
|
-
0.1,
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
expect(retargeted.position[0]).toBeLessThan(first.position[0])
|
|
213
|
-
expect(retargeted.target[0]).toBeLessThan(first.target[0])
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
test('preserves view direction while adapting distance to the local viewport', () => {
|
|
217
|
-
const pose = {
|
|
218
|
-
position: [4, 6, 12] as [number, number, number],
|
|
219
|
-
target: [1, 2, 3] as [number, number, number],
|
|
220
|
-
projection: 'perspective' as const,
|
|
221
|
-
viewWidth: 20,
|
|
222
|
-
}
|
|
223
|
-
const adapted = withCameraPoseDistance(pose, 5)
|
|
224
|
-
const sourceDirection = pose.position.map((value, index) => value - pose.target[index]!)
|
|
225
|
-
const adaptedDirection = adapted.position.map((value, index) => value - adapted.target[index]!)
|
|
226
|
-
const sourceLength = Math.hypot(...sourceDirection)
|
|
227
|
-
|
|
228
|
-
expect(Math.hypot(...adaptedDirection)).toBeCloseTo(5)
|
|
229
|
-
const expectedDirection = sourceDirection.map((component) => component * (5 / sourceLength))
|
|
230
|
-
expect(adaptedDirection[0]).toBeCloseTo(expectedDirection[0]!)
|
|
231
|
-
expect(adaptedDirection[1]).toBeCloseTo(expectedDirection[1]!)
|
|
232
|
-
expect(adaptedDirection[2]).toBeCloseTo(expectedDirection[2]!)
|
|
233
|
-
expect(adapted.target).toEqual(pose.target)
|
|
234
|
-
expect(adapted.target).not.toBe(pose.target)
|
|
235
|
-
expect(pose.position).toEqual([4, 6, 12])
|
|
236
|
-
})
|
|
237
|
-
})
|
package/src/lib/camera-pose.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import type { CameraPose } from '@pascal-app/core'
|
|
2
|
-
|
|
3
|
-
const MIN_PERSPECTIVE_FOV = 1
|
|
4
|
-
const MAX_PERSPECTIVE_FOV = 179
|
|
5
|
-
const INTERPOLATION_TIME_CONSTANT_SECONDS = 0.08
|
|
6
|
-
const MAX_INTERPOLATION_DELTA_SECONDS = 0.1
|
|
7
|
-
const INTERPOLATION_SETTLE_DISTANCE = 0.001
|
|
8
|
-
|
|
9
|
-
export type CameraPoseApplicationPlan = {
|
|
10
|
-
pose: CameraPose
|
|
11
|
-
perspectiveFov: number | null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type CameraPoseInterpolationStep = {
|
|
15
|
-
position: [number, number, number]
|
|
16
|
-
settled: boolean
|
|
17
|
-
target: [number, number, number]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function publishInitialCameraPose(publishCurrentPose: () => void): void {
|
|
21
|
-
publishCurrentPose()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function releaseCameraPoseEventSuppression(
|
|
25
|
-
suppression: { current: boolean },
|
|
26
|
-
publishCurrentPose: () => void,
|
|
27
|
-
): void {
|
|
28
|
-
suppression.current = false
|
|
29
|
-
publishCurrentPose()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function finiteVectorTuple(value: unknown): [number, number, number] | null {
|
|
33
|
-
if (
|
|
34
|
-
!(
|
|
35
|
-
Array.isArray(value) &&
|
|
36
|
-
value.length === 3 &&
|
|
37
|
-
value.every((component) => typeof component === 'number' && Number.isFinite(component))
|
|
38
|
-
)
|
|
39
|
-
) {
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return [value[0], value[1], value[2]]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function normalizeCameraPose(value: unknown): CameraPose | null {
|
|
47
|
-
if (!(typeof value === 'object' && value !== null)) {
|
|
48
|
-
return null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const candidate = value as Partial<CameraPose>
|
|
52
|
-
const position = finiteVectorTuple(candidate.position)
|
|
53
|
-
const target = finiteVectorTuple(candidate.target)
|
|
54
|
-
if (
|
|
55
|
-
!(
|
|
56
|
-
position &&
|
|
57
|
-
target &&
|
|
58
|
-
(candidate.projection === 'perspective' || candidate.projection === 'orthographic')
|
|
59
|
-
)
|
|
60
|
-
) {
|
|
61
|
-
return null
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
candidate.fov !== undefined &&
|
|
66
|
-
!(typeof candidate.fov === 'number' && Number.isFinite(candidate.fov))
|
|
67
|
-
) {
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
candidate.viewWidth !== undefined &&
|
|
73
|
-
!(
|
|
74
|
-
typeof candidate.viewWidth === 'number' &&
|
|
75
|
-
Number.isFinite(candidate.viewWidth) &&
|
|
76
|
-
candidate.viewWidth > 0
|
|
77
|
-
)
|
|
78
|
-
) {
|
|
79
|
-
return null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
...(candidate.fov === undefined ? {} : { fov: candidate.fov }),
|
|
84
|
-
position,
|
|
85
|
-
projection: candidate.projection,
|
|
86
|
-
target,
|
|
87
|
-
...(candidate.viewWidth === undefined ? {} : { viewWidth: candidate.viewWidth }),
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function planCameraPoseApplication(value: unknown): CameraPoseApplicationPlan | null {
|
|
92
|
-
const pose = normalizeCameraPose(value)
|
|
93
|
-
if (!pose) {
|
|
94
|
-
return null
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
pose,
|
|
99
|
-
perspectiveFov:
|
|
100
|
-
pose.projection === 'perspective' && pose.fov !== undefined
|
|
101
|
-
? Math.min(Math.max(pose.fov, MIN_PERSPECTIVE_FOV), MAX_PERSPECTIVE_FOV)
|
|
102
|
-
: null,
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function interpolateVectorTuple(
|
|
107
|
-
current: [number, number, number],
|
|
108
|
-
destination: [number, number, number],
|
|
109
|
-
alpha: number,
|
|
110
|
-
): { settled: boolean; value: [number, number, number] } {
|
|
111
|
-
const dx = destination[0] - current[0]
|
|
112
|
-
const dy = destination[1] - current[1]
|
|
113
|
-
const dz = destination[2] - current[2]
|
|
114
|
-
const settleDistanceSquared = INTERPOLATION_SETTLE_DISTANCE ** 2
|
|
115
|
-
if (dx * dx + dy * dy + dz * dz <= settleDistanceSquared) {
|
|
116
|
-
return { settled: true, value: [...destination] }
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const value: [number, number, number] = [
|
|
120
|
-
current[0] + dx * alpha,
|
|
121
|
-
current[1] + dy * alpha,
|
|
122
|
-
current[2] + dz * alpha,
|
|
123
|
-
]
|
|
124
|
-
const remainingX = destination[0] - value[0]
|
|
125
|
-
const remainingY = destination[1] - value[1]
|
|
126
|
-
const remainingZ = destination[2] - value[2]
|
|
127
|
-
const settled =
|
|
128
|
-
remainingX * remainingX + remainingY * remainingY + remainingZ * remainingZ <=
|
|
129
|
-
settleDistanceSquared
|
|
130
|
-
|
|
131
|
-
return { settled, value: settled ? [...destination] : value }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function withCameraPoseDistance(pose: CameraPose, distance: number): CameraPose {
|
|
135
|
-
const dx = pose.position[0] - pose.target[0]
|
|
136
|
-
const dy = pose.position[1] - pose.target[1]
|
|
137
|
-
const dz = pose.position[2] - pose.target[2]
|
|
138
|
-
const currentDistance = Math.hypot(dx, dy, dz)
|
|
139
|
-
if (!(Number.isFinite(distance) && distance > 0 && currentDistance > 0)) {
|
|
140
|
-
return { ...pose, position: [...pose.position], target: [...pose.target] }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const scale = distance / currentDistance
|
|
144
|
-
return {
|
|
145
|
-
...pose,
|
|
146
|
-
position: [
|
|
147
|
-
pose.target[0] + dx * scale,
|
|
148
|
-
pose.target[1] + dy * scale,
|
|
149
|
-
pose.target[2] + dz * scale,
|
|
150
|
-
],
|
|
151
|
-
target: [...pose.target],
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function stepCameraPoseInterpolation(
|
|
156
|
-
currentPosition: [number, number, number],
|
|
157
|
-
currentTarget: [number, number, number],
|
|
158
|
-
destination: CameraPose,
|
|
159
|
-
deltaSeconds: number,
|
|
160
|
-
): CameraPoseInterpolationStep {
|
|
161
|
-
const finiteDelta = Number.isFinite(deltaSeconds) ? deltaSeconds : 0
|
|
162
|
-
const elapsed = Math.min(Math.max(finiteDelta, 0), MAX_INTERPOLATION_DELTA_SECONDS)
|
|
163
|
-
const alpha = 1 - Math.exp(-elapsed / INTERPOLATION_TIME_CONSTANT_SECONDS)
|
|
164
|
-
const position = interpolateVectorTuple(currentPosition, destination.position, alpha)
|
|
165
|
-
const target = interpolateVectorTuple(currentTarget, destination.target, alpha)
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
position: position.value,
|
|
169
|
-
settled: position.settled && target.settled,
|
|
170
|
-
target: target.value,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import type { GeometryContext } from '@pascal-app/core'
|
|
3
|
-
import {
|
|
4
|
-
createFloorplanContextExtensions,
|
|
5
|
-
normalizeFloorplanWallDimensionFaces,
|
|
6
|
-
readFloorplanContext,
|
|
7
|
-
} from './floorplan-extension'
|
|
8
|
-
|
|
9
|
-
function context(extensions?: Readonly<Record<string, unknown>>): GeometryContext {
|
|
10
|
-
return {
|
|
11
|
-
resolve: () => undefined,
|
|
12
|
-
children: [],
|
|
13
|
-
siblings: [],
|
|
14
|
-
parent: null,
|
|
15
|
-
extensions,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('floor-plan context extensions', () => {
|
|
20
|
-
test('measures walls to both faces by default', () => {
|
|
21
|
-
expect(readFloorplanContext(context()).wallDimensionFaces).toBe('both')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('carries the Measure walls choice', () => {
|
|
25
|
-
for (const wallDimensionFaces of ['interior', 'exterior', 'both'] as const) {
|
|
26
|
-
const extensions = createFloorplanContextExtensions({ wallDimensionFaces })
|
|
27
|
-
expect(readFloorplanContext(context(extensions)).wallDimensionFaces).toBe(wallDimensionFaces)
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('normalizes stale persisted values, including the retired references, to Both', () => {
|
|
32
|
-
for (const stale of ['finished-faces', 'centerline', 'stud-faces', 'unknown', null]) {
|
|
33
|
-
expect(normalizeFloorplanWallDimensionFaces(stale)).toBe('both')
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('lets presentation code suppress automatic annotation construction', () => {
|
|
38
|
-
expect(readFloorplanContext(context()).automaticDimensions).toBe(true)
|
|
39
|
-
const extensions = createFloorplanContextExtensions({ automaticDimensions: false })
|
|
40
|
-
expect(readFloorplanContext(context(extensions)).automaticDimensions).toBe(false)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { LevelNode, WallNode } from '@pascal-app/core/schema'
|
|
3
|
-
import { buildFloorplanContext } from './floorplan-readonly'
|
|
4
|
-
|
|
5
|
-
const viewState = {
|
|
6
|
-
selected: false,
|
|
7
|
-
unit: 'metric' as const,
|
|
8
|
-
highlighted: false,
|
|
9
|
-
hovered: false,
|
|
10
|
-
moving: false,
|
|
11
|
-
palette: undefined,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('read-only floorplan context', () => {
|
|
15
|
-
test('does not invent siblings for parentless nodes', () => {
|
|
16
|
-
const first = LevelNode.parse({ id: 'level_first', type: 'level' })
|
|
17
|
-
const second = LevelNode.parse({ id: 'level_second', type: 'level' })
|
|
18
|
-
const nodes = { [first.id]: first, [second.id]: second }
|
|
19
|
-
|
|
20
|
-
expect(buildFloorplanContext(first, nodes, viewState).siblings).toEqual([])
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('resolves same-kind siblings from the parent child order', () => {
|
|
24
|
-
const first = WallNode.parse({
|
|
25
|
-
id: 'wall_first',
|
|
26
|
-
type: 'wall',
|
|
27
|
-
parentId: 'level_ground',
|
|
28
|
-
start: [0, 0],
|
|
29
|
-
end: [1, 0],
|
|
30
|
-
})
|
|
31
|
-
const second = WallNode.parse({
|
|
32
|
-
id: 'wall_second',
|
|
33
|
-
type: 'wall',
|
|
34
|
-
parentId: 'level_ground',
|
|
35
|
-
start: [1, 0],
|
|
36
|
-
end: [2, 0],
|
|
37
|
-
})
|
|
38
|
-
const level = LevelNode.parse({
|
|
39
|
-
id: 'level_ground',
|
|
40
|
-
type: 'level',
|
|
41
|
-
children: [first.id, second.id],
|
|
42
|
-
})
|
|
43
|
-
const nodes = { [level.id]: level, [first.id]: first, [second.id]: second }
|
|
44
|
-
|
|
45
|
-
expect(buildFloorplanContext(first, nodes, viewState).siblings).toEqual([second])
|
|
46
|
-
})
|
|
47
|
-
})
|