@pascal-app/editor 1.0.3 → 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 +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- 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 +275 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- 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 +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- 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/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +409 -197
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +663 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
- 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/editor-2d/svg-paths.ts +12 -15
- 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/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- 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 +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/item-surface-move.test.ts +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 +66 -30
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- 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 +14 -19
- 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 +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- 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/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- 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 +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +790 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -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 +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- 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/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- 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 +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- 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/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +284 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +230 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- 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 +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +187 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +392 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +122 -0
- package/src/lib/paint-pass-through.ts +78 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -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/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +265 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +1017 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- 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/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +580 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- 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-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- 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-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +201 -0
- package/src/store/view-layout.ts +199 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/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 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
type AnyNode,
|
|
4
|
+
type AnyNodeDefinition,
|
|
5
|
+
BuildingNode,
|
|
6
|
+
DoorNode,
|
|
7
|
+
ItemNode,
|
|
8
|
+
LevelNode,
|
|
9
|
+
loadPlugin,
|
|
10
|
+
nodeRegistry,
|
|
11
|
+
RoofNode,
|
|
12
|
+
RoofSegmentNode,
|
|
13
|
+
WallNode,
|
|
14
|
+
} from '@pascal-app/core'
|
|
15
|
+
import type { RevealEvent } from '@pascal-app/viewer'
|
|
16
|
+
import { openingExterior, wallExterior } from './follow-pascal-exterior.test-fixture'
|
|
17
|
+
import { stayOnLevel, wantFromEvent } from './follow-pascal-targets'
|
|
18
|
+
|
|
19
|
+
// What the camera looks at for each thing the reveal says: a floor when its walls rise, a window when
|
|
20
|
+
// it is cut in, an item when it drops, the roof from above as it assembles, and the whole house when
|
|
21
|
+
// the build is finished.
|
|
22
|
+
|
|
23
|
+
function house(at: [number, number, number] = [0, 0, 0]) {
|
|
24
|
+
const building = BuildingNode.parse({ id: 'building_h', position: at })
|
|
25
|
+
const level = LevelNode.parse({ id: 'level_h', parentId: building.id, level: 0, height: 2.7 })
|
|
26
|
+
const roofLevel = LevelNode.parse({ id: 'level_roof', parentId: building.id, level: 1 })
|
|
27
|
+
const corners: [number, number][] = [
|
|
28
|
+
[0, 0],
|
|
29
|
+
[10, 0],
|
|
30
|
+
[10, 8],
|
|
31
|
+
[0, 8],
|
|
32
|
+
]
|
|
33
|
+
const walls = corners.map((start, index) =>
|
|
34
|
+
WallNode.parse({
|
|
35
|
+
id: `wall_${index}`,
|
|
36
|
+
parentId: level.id,
|
|
37
|
+
start,
|
|
38
|
+
end: corners[(index + 1) % 4]!,
|
|
39
|
+
frontSide: 'interior',
|
|
40
|
+
backSide: 'exterior',
|
|
41
|
+
height: 2.7,
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
const door = DoorNode.parse({
|
|
45
|
+
id: 'door_h',
|
|
46
|
+
parentId: walls[0]!.id,
|
|
47
|
+
wallId: walls[0]!.id,
|
|
48
|
+
position: [3, 1.05, 0],
|
|
49
|
+
width: 0.9,
|
|
50
|
+
height: 2.1,
|
|
51
|
+
})
|
|
52
|
+
const item = ItemNode.parse({
|
|
53
|
+
id: 'item_h',
|
|
54
|
+
parentId: level.id,
|
|
55
|
+
position: [4, 0, 3],
|
|
56
|
+
asset: {
|
|
57
|
+
id: 'chair',
|
|
58
|
+
category: 'furniture',
|
|
59
|
+
name: 'Chair',
|
|
60
|
+
thumbnail: '',
|
|
61
|
+
src: '/items/chair/model.glb',
|
|
62
|
+
dimensions: [0.6, 0.9, 0.6],
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
const segment = RoofSegmentNode.parse({
|
|
66
|
+
id: 'rseg_h',
|
|
67
|
+
parentId: 'roof_h',
|
|
68
|
+
roofType: 'gable',
|
|
69
|
+
width: 10,
|
|
70
|
+
depth: 8,
|
|
71
|
+
pitch: 30,
|
|
72
|
+
wallHeight: 0,
|
|
73
|
+
})
|
|
74
|
+
const roof = RoofNode.parse({
|
|
75
|
+
id: 'roof_h',
|
|
76
|
+
parentId: roofLevel.id,
|
|
77
|
+
position: [5, 0, 4],
|
|
78
|
+
children: [segment.id],
|
|
79
|
+
})
|
|
80
|
+
const list = [building, level, roofLevel, ...walls, door, item, roof, segment] as AnyNode[]
|
|
81
|
+
const nodes = Object.fromEntries(list.map((node) => [node.id, { ...node }])) as Record<
|
|
82
|
+
string,
|
|
83
|
+
any
|
|
84
|
+
>
|
|
85
|
+
nodes.building_h.children = [level.id, roofLevel.id]
|
|
86
|
+
nodes.level_h.children = [...walls.map((wall) => wall.id), item.id]
|
|
87
|
+
nodes.level_roof.children = [roof.id]
|
|
88
|
+
nodes.wall_0.children = [door.id]
|
|
89
|
+
return nodes as Record<string, AnyNode>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const group = (
|
|
93
|
+
type: 'start' | 'land' | 'settle',
|
|
94
|
+
phase: string,
|
|
95
|
+
ids: string[],
|
|
96
|
+
levelId: string | null = 'level_h',
|
|
97
|
+
) =>
|
|
98
|
+
({ type, phase, levelId, nodeIds: ids, source: 'agent', instant: false, atMs: 0 }) as RevealEvent
|
|
99
|
+
const started = (id: string, phase: string, style = 'drop') =>
|
|
100
|
+
({
|
|
101
|
+
type: 'node-start',
|
|
102
|
+
id,
|
|
103
|
+
phase,
|
|
104
|
+
levelId: 'level_h',
|
|
105
|
+
style,
|
|
106
|
+
source: 'agent',
|
|
107
|
+
atMs: 0,
|
|
108
|
+
}) as RevealEvent
|
|
109
|
+
|
|
110
|
+
// The roof declares that it lifts for the furnishing; the kinds are the nodes package's, so a
|
|
111
|
+
// fixture of the same kind stands in for them here.
|
|
112
|
+
let restoreRegistry: () => void = () => {}
|
|
113
|
+
beforeAll(async () => {
|
|
114
|
+
restoreRegistry = nodeRegistry._snapshot()
|
|
115
|
+
nodeRegistry._reset()
|
|
116
|
+
await loadPlugin({
|
|
117
|
+
id: 'fixture:follow',
|
|
118
|
+
apiVersion: 1,
|
|
119
|
+
nodes: [
|
|
120
|
+
{
|
|
121
|
+
kind: 'roof',
|
|
122
|
+
schemaVersion: 1,
|
|
123
|
+
schema: RoofNode,
|
|
124
|
+
category: 'structure',
|
|
125
|
+
defaults: () => ({}),
|
|
126
|
+
capabilities: {
|
|
127
|
+
reveal: {
|
|
128
|
+
phase: 'roof',
|
|
129
|
+
style: 'assemble',
|
|
130
|
+
height: 4,
|
|
131
|
+
clears: { for: 'furnishing', height: 2.4 },
|
|
132
|
+
},
|
|
133
|
+
exterior: true,
|
|
134
|
+
},
|
|
135
|
+
geometry: () => null,
|
|
136
|
+
} as unknown as AnyNodeDefinition,
|
|
137
|
+
{
|
|
138
|
+
kind: 'wall',
|
|
139
|
+
schemaVersion: 1,
|
|
140
|
+
schema: WallNode,
|
|
141
|
+
category: 'structure',
|
|
142
|
+
defaults: () => ({}),
|
|
143
|
+
capabilities: {
|
|
144
|
+
reveal: { phase: 'structure', style: 'rise' },
|
|
145
|
+
revealOnEdit: ['start', 'end', 'curveOffset'],
|
|
146
|
+
exterior: wallExterior,
|
|
147
|
+
},
|
|
148
|
+
geometry: () => null,
|
|
149
|
+
} as unknown as AnyNodeDefinition,
|
|
150
|
+
{
|
|
151
|
+
kind: 'door',
|
|
152
|
+
schemaVersion: 1,
|
|
153
|
+
schema: DoorNode,
|
|
154
|
+
category: 'structure',
|
|
155
|
+
defaults: () => ({}),
|
|
156
|
+
capabilities: {
|
|
157
|
+
reveal: { phase: 'openings', style: 'cut' },
|
|
158
|
+
revealOnEdit: ['position'],
|
|
159
|
+
exterior: openingExterior,
|
|
160
|
+
},
|
|
161
|
+
geometry: () => null,
|
|
162
|
+
} as unknown as AnyNodeDefinition,
|
|
163
|
+
],
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
afterAll(() => restoreRegistry())
|
|
167
|
+
|
|
168
|
+
describe('what the camera looks at', () => {
|
|
169
|
+
test('a floor, whole, when its slabs and walls start', () => {
|
|
170
|
+
for (const phase of ['foundation', 'structure', 'circulation']) {
|
|
171
|
+
const want = wantFromEvent(group('start', phase, ['wall_0']), house(), 1)!
|
|
172
|
+
expect(want.kind).toBe('floor')
|
|
173
|
+
expect(want.box.min[0]).toBeLessThanOrEqual(0)
|
|
174
|
+
expect(want.box.max[0]).toBeGreaterThanOrEqual(10)
|
|
175
|
+
expect(want.box.max[2]).toBeGreaterThanOrEqual(8)
|
|
176
|
+
expect(want.at).toBe(1)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('a wall the agent edited is looked at, close up: it is not a floor being built', () => {
|
|
181
|
+
const nodes = house()
|
|
182
|
+
const edited = { ...started('wall_0', 'structure', 'rise'), changed: true } as RevealEvent
|
|
183
|
+
const want = wantFromEvent(edited, nodes, 0)
|
|
184
|
+
expect(want?.kind).toBe('detail')
|
|
185
|
+
expect(want?.levelId).toBe('level_h')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('the walls themselves are not targets: the floor already is', () => {
|
|
189
|
+
expect(wantFromEvent(started('wall_1', 'structure', 'rise'), house(), 0)).toBeNull()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('a window or a door close up, square to the wall it is in, from outside', () => {
|
|
193
|
+
const want = wantFromEvent(started('door_h', 'openings', 'cut'), house(), 2)!
|
|
194
|
+
expect(want.kind).toBe('detail')
|
|
195
|
+
expect(want.box.max[1] - want.box.min[1]).toBeLessThan(2.5)
|
|
196
|
+
expect(want.normal).toEqual([0, -1])
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('an item as furniture, from above', () => {
|
|
200
|
+
const want = wantFromEvent(started('item_h', 'furnishing'), house(), 3)!
|
|
201
|
+
expect(want.kind).toBe('furnish')
|
|
202
|
+
expect(want.normal).toBeUndefined()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('furniture is seen from above the roof that lifts for it', () => {
|
|
206
|
+
const want = wantFromEvent(started('item_h', 'furnishing'), house(), 3)!
|
|
207
|
+
// The roof's top is above 2.7 m; it lifts 2.4 m for the furniture, and the eye clears that.
|
|
208
|
+
expect(want.eyeAbove).toBeGreaterThan(2.7 + 2.4)
|
|
209
|
+
// A house with no roof has nothing to clear.
|
|
210
|
+
const bare = house()
|
|
211
|
+
delete (bare as Record<string, unknown>).roof_h
|
|
212
|
+
delete (bare as Record<string, unknown>).rseg_h
|
|
213
|
+
expect(wantFromEvent(started('item_h', 'furnishing'), bare, 3)!.eyeAbove).toBeUndefined()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('the whole house once the furniture has landed, before the roof comes back down', () => {
|
|
217
|
+
const want = wantFromEvent(group('settle', 'furnishing', ['item_h']), house(), 6)!
|
|
218
|
+
expect(want.kind).toBe('group')
|
|
219
|
+
expect(want.box.max[1]).toBeGreaterThan(3)
|
|
220
|
+
expect(want.box.max[0]).toBeGreaterThanOrEqual(10)
|
|
221
|
+
// Still looked down through the lifted roof, not from within it.
|
|
222
|
+
expect(want.eyeAbove).toBeGreaterThan(2.7 + 2.4)
|
|
223
|
+
// The walls settling say nothing about where to look.
|
|
224
|
+
expect(wantFromEvent(group('settle', 'structure', ['wall_0']), house(), 6)).toBeNull()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test('the whole house, for a person who stays on one level: that level whole, with no roof to clear and no floor of its own to change', () => {
|
|
228
|
+
const nodes = house()
|
|
229
|
+
const whole = wantFromEvent(group('settle', 'furnishing', ['item_h']), nodes, 6)!
|
|
230
|
+
expect(whole.levelId).toBeUndefined()
|
|
231
|
+
const stay = stayOnLevel(whole, nodes, 'level_h')
|
|
232
|
+
expect(stay.kind).toBe('floor')
|
|
233
|
+
expect(stay.levelId).toBe('level_h')
|
|
234
|
+
expect(stay.eyeAbove).toBeUndefined()
|
|
235
|
+
expect(stay.at).toBe(6)
|
|
236
|
+
// The level's own box, not the house's: the roof level over it is not in it.
|
|
237
|
+
expect(stay.box.max[1]).toBeLessThan(whole.box.max[1])
|
|
238
|
+
// A level with nothing to frame still stays where it is.
|
|
239
|
+
expect(stayOnLevel(whole, nodes, 'level_nowhere').levelId).toBe('level_nowhere')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('a piece the agent moved is looked at like one it placed, and its settling does not pull back to the whole house', () => {
|
|
243
|
+
const moved = { ...started('item_h', 'furnishing', 'glide'), changed: true } as RevealEvent
|
|
244
|
+
const want = wantFromEvent(moved, house(), 3)!
|
|
245
|
+
expect(want.kind).toBe('furnish')
|
|
246
|
+
expect(want.eyeAbove).toBeGreaterThan(2.7 + 2.4)
|
|
247
|
+
// The build around it is not finishing: the camera stays where the piece is.
|
|
248
|
+
const settled = { ...group('settle', 'furnishing', ['item_h']), changed: true } as RevealEvent
|
|
249
|
+
expect(wantFromEvent(settled, house(), 6)).toBeNull()
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('the roof, from the group that assembles it', () => {
|
|
253
|
+
const want = wantFromEvent(
|
|
254
|
+
group('start', 'roof', ['roof_h', 'rseg_h'], 'level_roof'),
|
|
255
|
+
house(),
|
|
256
|
+
4,
|
|
257
|
+
)!
|
|
258
|
+
expect(want.kind).toBe('roof')
|
|
259
|
+
expect(want.box.max[1]).toBeGreaterThan(2.7)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test('where the building stands, not at the origin', () => {
|
|
263
|
+
const want = wantFromEvent(started('door_h', 'openings', 'cut'), house([20, 0, 30]), 0)!
|
|
264
|
+
expect(want.box.min[0]).toBeGreaterThan(20)
|
|
265
|
+
expect(want.box.min[2]).toBeGreaterThan(29)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
test('the whole house, roof and all, when the build is finished', () => {
|
|
269
|
+
const want = wantFromEvent(
|
|
270
|
+
{
|
|
271
|
+
type: 'finale',
|
|
272
|
+
id: 'roof_h',
|
|
273
|
+
phase: 'roof',
|
|
274
|
+
levelId: 'level_roof',
|
|
275
|
+
source: 'agent',
|
|
276
|
+
atMs: 0,
|
|
277
|
+
} as RevealEvent,
|
|
278
|
+
house(),
|
|
279
|
+
5,
|
|
280
|
+
)!
|
|
281
|
+
expect(want.kind).toBe('group')
|
|
282
|
+
expect(want.box.max[1]).toBeGreaterThan(3)
|
|
283
|
+
expect(want.box.max[0]).toBeGreaterThanOrEqual(10)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('an undo is followed: what goes first is looked at, group by group', () => {
|
|
287
|
+
const undone = (phase: string, nodeIds: string[], levelId: string | null = 'level_h') =>
|
|
288
|
+
({
|
|
289
|
+
type: 'retract-start',
|
|
290
|
+
phase,
|
|
291
|
+
levelId,
|
|
292
|
+
nodeIds,
|
|
293
|
+
source: null,
|
|
294
|
+
atMs: 0,
|
|
295
|
+
}) as RevealEvent
|
|
296
|
+
const furniture = wantFromEvent(undone('furnishing', ['item_h']), house(), 1)!
|
|
297
|
+
expect(furniture.kind).toBe('furnish')
|
|
298
|
+
// Seen from above the roof that is still up for it.
|
|
299
|
+
expect(furniture.eyeAbove).toBeGreaterThan(2.7 + 2.4)
|
|
300
|
+
const roof = wantFromEvent(undone('roof', ['roof_h', 'rseg_h'], 'level_roof'), house(), 2)!
|
|
301
|
+
expect(roof.kind).toBe('roof')
|
|
302
|
+
expect(roof.box.max[1]).toBeGreaterThan(2.7)
|
|
303
|
+
const openings = wantFromEvent(undone('openings', ['door_h']), house(), 3)!
|
|
304
|
+
expect(openings.kind).toBe('group')
|
|
305
|
+
expect(openings.box.max[1]).toBeLessThan(2.5)
|
|
306
|
+
// The walls going: the floor they stand on is what is looked at, by the walls themselves.
|
|
307
|
+
const walls = wantFromEvent(undone('structure', ['wall_0', 'wall_2']), house(), 4)!
|
|
308
|
+
expect(walls.kind).toBe('floor')
|
|
309
|
+
expect(walls.box.max[0]).toBeGreaterThanOrEqual(10)
|
|
310
|
+
// A group whose nodes are no longer in the scene says nothing about where to look.
|
|
311
|
+
expect(wantFromEvent(undone('structure', ['wall_gone']), house(), 5)).toBeNull()
|
|
312
|
+
// The end of a group says nothing.
|
|
313
|
+
expect(
|
|
314
|
+
wantFromEvent(
|
|
315
|
+
{ ...undone('structure', ['wall_0']), type: 'retract-end' } as RevealEvent,
|
|
316
|
+
house(),
|
|
317
|
+
6,
|
|
318
|
+
),
|
|
319
|
+
).toBeNull()
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('nothing to look at for what says nothing about the scene', () => {
|
|
323
|
+
expect(wantFromEvent(group('land', 'structure', ['wall_0']), house(), 0)).toBeNull()
|
|
324
|
+
expect(wantFromEvent(group('settle', 'structure', ['wall_0']), house(), 0)).toBeNull()
|
|
325
|
+
expect(
|
|
326
|
+
wantFromEvent(
|
|
327
|
+
{
|
|
328
|
+
type: 'complete',
|
|
329
|
+
nodeCount: 1,
|
|
330
|
+
durationMs: 1,
|
|
331
|
+
source: null,
|
|
332
|
+
instant: false,
|
|
333
|
+
atMs: 0,
|
|
334
|
+
} as RevealEvent,
|
|
335
|
+
house(),
|
|
336
|
+
0,
|
|
337
|
+
),
|
|
338
|
+
).toBeNull()
|
|
339
|
+
// A node that is gone is no target either.
|
|
340
|
+
expect(wantFromEvent(started('item_gone', 'furnishing'), house(), 0)).toBeNull()
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// The owner, 15 October: the cutaway hid the exterior wall the agent was bending. A change seen from
|
|
345
|
+
// outside is looked at from outdoors, with the walls up; the rest keeps the cutaway.
|
|
346
|
+
describe('what is seen from outside', () => {
|
|
347
|
+
const changed = (id: string, phase: string, style = 'rise') =>
|
|
348
|
+
({ ...started(id, phase, style), changed: true }) as RevealEvent
|
|
349
|
+
|
|
350
|
+
test('an exterior wall the agent bent is an outside step, framed from its exterior face', () => {
|
|
351
|
+
const want = wantFromEvent(changed('wall_0', 'structure'), house(), 0)!
|
|
352
|
+
expect(want.exterior).toBe(true)
|
|
353
|
+
// wall_0 runs along +x with its exterior on the back side: it faces -z.
|
|
354
|
+
expect(want.normal).toEqual([0, -1])
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test('a window moved on an exterior wall is an outside step; a chair moved is not', () => {
|
|
358
|
+
const nodes = house()
|
|
359
|
+
expect(wantFromEvent(changed('door_h', 'openings', 'cut'), nodes, 0)!.exterior).toBe(true)
|
|
360
|
+
expect(
|
|
361
|
+
wantFromEvent(changed('item_h', 'furnishing', 'drop'), nodes, 0)!.exterior,
|
|
362
|
+
).toBeUndefined()
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
test('an interior wall the agent bent keeps the cutaway', () => {
|
|
366
|
+
const nodes = house() as Record<string, any>
|
|
367
|
+
nodes.wall_1 = { ...nodes.wall_1, frontSide: 'interior', backSide: 'interior' }
|
|
368
|
+
const want = wantFromEvent(changed('wall_1', 'structure'), nodes, 0)!
|
|
369
|
+
expect(want.exterior).toBeUndefined()
|
|
370
|
+
expect(want.normal).toBeUndefined()
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test('an opening on an interior wall keeps the cutaway', () => {
|
|
374
|
+
const nodes = house() as Record<string, any>
|
|
375
|
+
nodes.wall_0 = { ...nodes.wall_0, frontSide: 'interior', backSide: 'interior' }
|
|
376
|
+
expect(wantFromEvent(changed('door_h', 'openings', 'cut'), nodes, 0)!.exterior).toBeUndefined()
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// The owner, 15 Oct: the outside view is for edits and moves only; a build is followed as it was.
|
|
380
|
+
test('a build says nothing of outside: a roof assembling, an opening coming in, a floor of walls', () => {
|
|
381
|
+
const nodes = house()
|
|
382
|
+
for (const event of [
|
|
383
|
+
group('start', 'roof', ['roof_h'], 'level_roof'),
|
|
384
|
+
group('start', 'openings', ['door_h']),
|
|
385
|
+
group('start', 'structure', ['wall_0', 'wall_1']),
|
|
386
|
+
started('door_h', 'openings', 'cut'),
|
|
387
|
+
started('item_h', 'furnishing'),
|
|
388
|
+
{ ...group('start', 'roof', ['roof_h'], 'level_roof'), type: 'retract-start' } as RevealEvent,
|
|
389
|
+
])
|
|
390
|
+
expect(wantFromEvent(event, nodes, 0)?.exterior).toBeUndefined()
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('the same pieces edited or moved are outside steps', () => {
|
|
394
|
+
const nodes = house()
|
|
395
|
+
const edited = (id: string, phase: string) =>
|
|
396
|
+
({ ...started(id, phase), changed: true }) as RevealEvent
|
|
397
|
+
expect(wantFromEvent(edited('door_h', 'openings'), nodes, 0)!.exterior).toBe(true)
|
|
398
|
+
expect(
|
|
399
|
+
wantFromEvent(
|
|
400
|
+
{ ...group('start', 'roof', ['roof_h'], 'level_roof'), changed: true } as RevealEvent,
|
|
401
|
+
nodes,
|
|
402
|
+
0,
|
|
403
|
+
)!.exterior,
|
|
404
|
+
).toBe(true)
|
|
405
|
+
})
|
|
406
|
+
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { type AnyNode, type AnyNodeId, findLevelAncestorId, nodeRegistry } from '@pascal-app/core'
|
|
2
|
+
import { sceneViewBounds, sceneViewFacing } from '@pascal-app/core/agent-operations'
|
|
3
|
+
import type { RevealEvent } from '@pascal-app/viewer'
|
|
4
|
+
import type { Box, FollowWant } from './follow-pascal'
|
|
5
|
+
import { anyExterior } from './follow-pascal-exterior'
|
|
6
|
+
|
|
7
|
+
type Nodes = Readonly<Record<string, AnyNode>>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The box `view_scene` would frame for `id`, in the site (a building away from the origin is framed
|
|
11
|
+
* where it stands): the same bounds the agent's own views take, so the person sees what the agent
|
|
12
|
+
* would look at. Null for a node there is nothing to frame of.
|
|
13
|
+
*/
|
|
14
|
+
function boxOf(nodes: Nodes, id: string): Box | null {
|
|
15
|
+
try {
|
|
16
|
+
const { min, max } = sceneViewBounds(nodes, id)
|
|
17
|
+
return { min, max }
|
|
18
|
+
} catch {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function unionOf(boxes: readonly (Box | null)[]): Box | null {
|
|
24
|
+
const known = boxes.filter((box): box is Box => box !== null)
|
|
25
|
+
const first = known[0]
|
|
26
|
+
if (!first) return null
|
|
27
|
+
const min: Box['min'] = [...first.min]
|
|
28
|
+
const max: Box['max'] = [...first.max]
|
|
29
|
+
for (const box of known) {
|
|
30
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
31
|
+
min[axis] = Math.min(min[axis]!, box.min[axis]!)
|
|
32
|
+
max[axis] = Math.max(max[axis]!, box.max[axis]!)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { min, max }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildingOf(nodes: Nodes, id: string): string | null {
|
|
39
|
+
let node = nodes[id]
|
|
40
|
+
for (let guard = 0; node && guard < 32; guard += 1) {
|
|
41
|
+
if (node.type === 'building') return node.id
|
|
42
|
+
node = node.parentId ? nodes[node.parentId] : undefined
|
|
43
|
+
}
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A roof that lifts for the furniture is looked down through: the eye clears its lifted top by this. */
|
|
48
|
+
const LIFTED_ROOF_MARGIN = 1.5
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* How high the eye must stand to look at something in a building: above every roof of it that lifts
|
|
52
|
+
* out of the way of the furnishing, where it is lifted to. Undefined when nothing lifts.
|
|
53
|
+
*/
|
|
54
|
+
function clearanceAbove(nodes: Nodes, id: string): number | undefined {
|
|
55
|
+
const building = buildingOf(nodes, id)
|
|
56
|
+
if (!building) return undefined
|
|
57
|
+
let highest: number | undefined
|
|
58
|
+
for (const node of Object.values(nodes)) {
|
|
59
|
+
if (node.type !== 'roof' || buildingOf(nodes, node.id) !== building) continue
|
|
60
|
+
const clears = nodeRegistry.get(node.type)?.capabilities?.reveal?.clears
|
|
61
|
+
const box = boxOf(nodes, node.id)
|
|
62
|
+
if (!(clears && box)) continue
|
|
63
|
+
highest = Math.max(highest ?? Number.NEGATIVE_INFINITY, box.max[1] + clears.height)
|
|
64
|
+
}
|
|
65
|
+
return highest === undefined ? undefined : highest + LIFTED_ROOF_MARGIN
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The building `id` is in, roof and all, seen as a group; the eye clears a roof that is lifted. */
|
|
69
|
+
function wholeHouse(nodes: Nodes, id: string, at: number): FollowWant | null {
|
|
70
|
+
const building = buildingOf(nodes, id)
|
|
71
|
+
if (!building) return null
|
|
72
|
+
const roofs = Object.values(nodes).filter(
|
|
73
|
+
(node) => node.type === 'roof' && buildingOf(nodes, node.id) === building,
|
|
74
|
+
)
|
|
75
|
+
const box = unionOf([boxOf(nodes, building), ...roofs.map((roof) => boxOf(nodes, roof.id))])
|
|
76
|
+
if (!box) return null
|
|
77
|
+
const eyeAbove = clearanceAbove(nodes, id)
|
|
78
|
+
return { kind: 'group', box, ...(eyeAbove === undefined ? {} : { eyeAbove }), at }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A look at the whole house (the furniture has landed; a build has finished), made a look at the
|
|
83
|
+
* level the person is already on: the level whole, as when its floor starts, with no floors above
|
|
84
|
+
* to unhide and no lifted roof to clear.
|
|
85
|
+
*/
|
|
86
|
+
export function stayOnLevel(want: FollowWant, nodes: Nodes, levelId: string): FollowWant {
|
|
87
|
+
const box = boxOf(nodes, levelId)
|
|
88
|
+
return box ? { kind: 'floor', box, levelId, at: want.at } : { ...want, levelId }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* What the camera should look at for something the reveal said, or null when it says nothing about
|
|
93
|
+
* where to look. A floor, whole, when its slabs and walls start (the walls are not targets one by
|
|
94
|
+
* one: the floor already is); each opening close up from outside; each piece of furniture from above;
|
|
95
|
+
* the roof from the group that assembles it; the whole house, roof and all, when it is finished. An
|
|
96
|
+
* undo's reverse play is followed the same way, group by group, on what goes.
|
|
97
|
+
*/
|
|
98
|
+
export function wantFromEvent(event: RevealEvent, nodes: Nodes, at: number): FollowWant | null {
|
|
99
|
+
let want = targetFromEvent(event, nodes, at)
|
|
100
|
+
if (want && (want.kind === 'detail' || want.kind === 'furnish' || want.kind === 'roof')) {
|
|
101
|
+
const nodeIds = 'id' in event ? [event.id] : 'nodeIds' in event ? event.nodeIds : []
|
|
102
|
+
want = { ...want, nodeIds }
|
|
103
|
+
}
|
|
104
|
+
if (want && exteriorStep(event, nodes)) want = { ...want, exterior: true }
|
|
105
|
+
if (!want || event.type === 'complete' || event.type === 'finale' || event.type === 'settle')
|
|
106
|
+
return want
|
|
107
|
+
let level = event.levelId ? nodes[event.levelId] : undefined
|
|
108
|
+
if (level?.type !== 'level') {
|
|
109
|
+
const id = 'id' in event ? event.id : event.nodeIds[0]
|
|
110
|
+
const levelId = id ? findLevelAncestorId(id as AnyNodeId, nodes) : null
|
|
111
|
+
level = levelId ? nodes[levelId] : undefined
|
|
112
|
+
}
|
|
113
|
+
return level?.type === 'level' ? { ...want, levelId: level.id } : want
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether the step is a change seen from outside, not through a cutaway into a room: a piece the
|
|
118
|
+
* agent edited or moved that stands outdoors (a wall with an exterior face, an opening in one, a
|
|
119
|
+
* roof, the ground). Only edits and moves: a build is followed as it always was, whatever it builds
|
|
120
|
+
* (the owner's call, 15 Oct), and furnishing and interior walls are not outside either.
|
|
121
|
+
*/
|
|
122
|
+
function exteriorStep(event: RevealEvent, nodes: Nodes): boolean {
|
|
123
|
+
if (!('changed' in event) || event.changed !== true) return false
|
|
124
|
+
const ids = 'id' in event ? [event.id] : 'nodeIds' in event ? [...event.nodeIds] : []
|
|
125
|
+
return anyExterior(nodes, ids)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function targetFromEvent(event: RevealEvent, nodes: Nodes, at: number): FollowWant | null {
|
|
129
|
+
switch (event.type) {
|
|
130
|
+
case 'start': {
|
|
131
|
+
// Hidden floors settle instantly and say only the group, not each node's start.
|
|
132
|
+
if (event.instant && (event.phase === 'openings' || event.phase === 'furnishing')) {
|
|
133
|
+
const box = unionOf(event.nodeIds.map((id) => boxOf(nodes, id)))
|
|
134
|
+
return box ? { kind: event.phase === 'openings' ? 'detail' : 'furnish', box, at } : null
|
|
135
|
+
}
|
|
136
|
+
if (event.phase === 'roof') {
|
|
137
|
+
const box = unionOf(event.nodeIds.map((id) => boxOf(nodes, id)))
|
|
138
|
+
return box ? { kind: 'roof', box, at } : null
|
|
139
|
+
}
|
|
140
|
+
if (
|
|
141
|
+
event.phase !== 'foundation' &&
|
|
142
|
+
event.phase !== 'structure' &&
|
|
143
|
+
event.phase !== 'circulation'
|
|
144
|
+
)
|
|
145
|
+
return null
|
|
146
|
+
const box =
|
|
147
|
+
(event.levelId ? boxOf(nodes, event.levelId) : null) ??
|
|
148
|
+
unionOf(event.nodeIds.map((id) => boxOf(nodes, id)))
|
|
149
|
+
return box ? { kind: 'floor', box, at } : null
|
|
150
|
+
}
|
|
151
|
+
case 'node-start': {
|
|
152
|
+
if (event.phase === 'openings') {
|
|
153
|
+
const box = boxOf(nodes, event.id)
|
|
154
|
+
if (!box) return null
|
|
155
|
+
const normal = sceneViewFacing(nodes, event.id)
|
|
156
|
+
return { kind: 'detail', box, ...(normal ? { normal } : {}), at }
|
|
157
|
+
}
|
|
158
|
+
if (event.phase === 'furnishing') {
|
|
159
|
+
const box = boxOf(nodes, event.id)
|
|
160
|
+
if (!box) return null
|
|
161
|
+
const eyeAbove = clearanceAbove(nodes, event.id)
|
|
162
|
+
return { kind: 'furnish', box, ...(eyeAbove === undefined ? {} : { eyeAbove }), at }
|
|
163
|
+
}
|
|
164
|
+
// A piece the agent edited (a wall bent, a slab resized) plays again: the camera goes to it,
|
|
165
|
+
// from the side that faces outdoors when it has one.
|
|
166
|
+
if (event.changed) {
|
|
167
|
+
const box = boxOf(nodes, event.id)
|
|
168
|
+
if (!box) return null
|
|
169
|
+
const normal = sceneViewFacing(nodes, event.id)
|
|
170
|
+
return { kind: 'detail', box, ...(normal ? { normal } : {}), at }
|
|
171
|
+
}
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
case 'settle': {
|
|
175
|
+
// A piece the agent moved: nothing is coming down on the camera, it stays on the piece.
|
|
176
|
+
if (event.changed) return null
|
|
177
|
+
// The furniture has landed: pull back to the whole house while the roof is still up, so the
|
|
178
|
+
// roof does not come down on a camera that is close above it.
|
|
179
|
+
const id = event.phase === 'furnishing' ? event.nodeIds[0] : undefined
|
|
180
|
+
return id ? wholeHouse(nodes, id, at) : null
|
|
181
|
+
}
|
|
182
|
+
case 'finale':
|
|
183
|
+
return wholeHouse(nodes, event.id, at)
|
|
184
|
+
case 'retract-start': {
|
|
185
|
+
// An undo plays the build backwards: the camera is on what goes, a phase at a time.
|
|
186
|
+
const box = unionOf(event.nodeIds.map((id) => boxOf(nodes, id)))
|
|
187
|
+
if (!box) return null
|
|
188
|
+
if (event.phase === 'roof') return { kind: 'roof', box, at }
|
|
189
|
+
if (event.phase === 'furnishing') {
|
|
190
|
+
const eyeAbove = clearanceAbove(nodes, event.nodeIds[0] ?? '')
|
|
191
|
+
return { kind: 'furnish', box, ...(eyeAbove === undefined ? {} : { eyeAbove }), at }
|
|
192
|
+
}
|
|
193
|
+
return { kind: event.phase === 'openings' ? 'group' : 'floor', box, at }
|
|
194
|
+
}
|
|
195
|
+
default:
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
}
|