@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
package/src/lib/glb-export.ts
CHANGED
|
@@ -19,7 +19,9 @@ import {
|
|
|
19
19
|
type WindowNode,
|
|
20
20
|
type ZoneNode,
|
|
21
21
|
} from '@pascal-app/core'
|
|
22
|
+
import { evaluateRecipe } from '@pascal-app/core/procedural-items'
|
|
22
23
|
import {
|
|
24
|
+
decorateProceduralEmission,
|
|
23
25
|
getPascalTextureRef,
|
|
24
26
|
isViewerPresentationTextureBorrowed,
|
|
25
27
|
poseDoorMovingParts,
|
|
@@ -38,6 +40,8 @@ import {
|
|
|
38
40
|
} from 'three/examples/jsm/exporters/GLTFExporter.js'
|
|
39
41
|
import { createExportTextureUtils, type ExportTextureUtils } from './export-texture-utils'
|
|
40
42
|
import { cloneExportUserData } from './export-user-data'
|
|
43
|
+
import { isIfcRolePart } from './ifc-parts'
|
|
44
|
+
import { nodeDisplayLabel } from './node-display-label'
|
|
41
45
|
import {
|
|
42
46
|
type CompressedTextureDecompressor,
|
|
43
47
|
decompressCanonicalNormalMaps,
|
|
@@ -141,6 +145,115 @@ export function nextFrames(): Promise<void> {
|
|
|
141
145
|
})
|
|
142
146
|
}
|
|
143
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Export visibility, shared with the clone pruning: a hidden node hides its
|
|
150
|
+
* subtree, except a Site, which hides only its own ground (`hidesDescendants`).
|
|
151
|
+
*/
|
|
152
|
+
export function createExportVisibility(nodes: Record<string, AnyNode>) {
|
|
153
|
+
const visibility = new Map<string, boolean>()
|
|
154
|
+
const isVisible = (id: string, path: Set<string> = new Set()): boolean => {
|
|
155
|
+
const cached = visibility.get(id)
|
|
156
|
+
if (cached !== undefined) return cached
|
|
157
|
+
const node = nodes[id]
|
|
158
|
+
if (!node) return true
|
|
159
|
+
if (node.visible === false) {
|
|
160
|
+
visibility.set(id, false)
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
const parentId = node.parentId
|
|
164
|
+
const parent = parentId ? nodes[parentId] : undefined
|
|
165
|
+
if (!parentId || path.has(id) || (parent && !hidesDescendants(parent))) {
|
|
166
|
+
visibility.set(id, true)
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
path.add(id)
|
|
170
|
+
const visible = isVisible(parentId, path)
|
|
171
|
+
path.delete(id)
|
|
172
|
+
visibility.set(id, visible)
|
|
173
|
+
return visible
|
|
174
|
+
}
|
|
175
|
+
return isVisible
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export type ExportGeometryScope = Pick<GlbExportOptions, 'onlyVisible' | 'excludedNodeTypes'>
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Included `bake: 'replace'` nodes whose live object holds no mesh yet. These
|
|
182
|
+
* kinds render collectively in the editor and mount their own geometry only
|
|
183
|
+
* while `isExporting` is set; kinds with bake hooks are rebuilt after the
|
|
184
|
+
* clone instead, and excluded or hidden nodes never reach the clone.
|
|
185
|
+
*/
|
|
186
|
+
export function nodesAwaitingExportGeometry(
|
|
187
|
+
nodes: Record<string, AnyNode>,
|
|
188
|
+
scope: ExportGeometryScope = {},
|
|
189
|
+
): string[] {
|
|
190
|
+
const sceneState = useScene.getState()
|
|
191
|
+
const installedPlugins = sceneState.hasExplicitPluginInstallState
|
|
192
|
+
? sceneState.installedPlugins
|
|
193
|
+
: undefined
|
|
194
|
+
const excluded = new Set(scope.excludedNodeTypes)
|
|
195
|
+
const isVisible = createExportVisibility(nodes)
|
|
196
|
+
const typeExcluded = (node: AnyNode): boolean => {
|
|
197
|
+
for (let current: AnyNode | undefined = node, guard = 0; current && guard < 64; guard++) {
|
|
198
|
+
if (excluded.has(current.type)) return true
|
|
199
|
+
current = current.parentId ? nodes[current.parentId] : undefined
|
|
200
|
+
}
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
const pending: string[] = []
|
|
204
|
+
for (const [id, object] of sceneRegistry.nodes) {
|
|
205
|
+
const node = nodes[id]
|
|
206
|
+
if (!node) continue
|
|
207
|
+
const definition = nodeRegistry.get(node.type)
|
|
208
|
+
if (definition?.bake !== 'replace' || definition.bakeGeometry || definition.bakeGeometryAsync) {
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
if (!isNodeKindEnabled(node.type, installedPlugins) || typeExcluded(node)) continue
|
|
212
|
+
if ((scope.onlyVisible ?? true) && !isVisible(id)) continue
|
|
213
|
+
let hasMesh = false
|
|
214
|
+
object.traverse((child) => {
|
|
215
|
+
if ((child as THREE.Mesh).isMesh) hasMesh = true
|
|
216
|
+
})
|
|
217
|
+
if (!hasMesh) pending.push(id)
|
|
218
|
+
}
|
|
219
|
+
return pending
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Two animation frames, or `ms` if frames have stopped (hidden tab, test runner). */
|
|
223
|
+
function nextFramesOrTimeout(ms: number): Promise<void> {
|
|
224
|
+
return new Promise((resolve) => {
|
|
225
|
+
const timer = setTimeout(resolve, ms)
|
|
226
|
+
if (typeof requestAnimationFrame !== 'function') return
|
|
227
|
+
requestAnimationFrame(() =>
|
|
228
|
+
requestAnimationFrame(() => {
|
|
229
|
+
clearTimeout(timer)
|
|
230
|
+
resolve()
|
|
231
|
+
}),
|
|
232
|
+
)
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* After `setExporting(true)`, let one commit land, then wait until every
|
|
238
|
+
* included replace-kind node has mounted its export geometry. Two frames are
|
|
239
|
+
* not enough: on a large scene with a busy main thread the proxies' re-render
|
|
240
|
+
* lands several frames later, and the clone would silently miss every plant.
|
|
241
|
+
* The deadline runs on its own timer, so stalled frames cannot hang an export.
|
|
242
|
+
*/
|
|
243
|
+
export async function waitForExportGeometry(
|
|
244
|
+
nodes: Record<string, AnyNode>,
|
|
245
|
+
scope: ExportGeometryScope = {},
|
|
246
|
+
timeoutMs = 10_000,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const deadline = Date.now() + timeoutMs
|
|
249
|
+
await nextFramesOrTimeout(Math.min(500, timeoutMs))
|
|
250
|
+
while (nodesAwaitingExportGeometry(nodes, scope).length > 0) {
|
|
251
|
+
const remaining = deadline - Date.now()
|
|
252
|
+
if (remaining <= 0) return
|
|
253
|
+
await nextFramesOrTimeout(Math.min(50, remaining))
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
144
257
|
type GltfExtrasDef = {
|
|
145
258
|
extras?: Record<string, unknown>
|
|
146
259
|
}
|
|
@@ -369,7 +482,10 @@ function startSceneExportPreparation(
|
|
|
369
482
|
|
|
370
483
|
for (const [id, original] of registryEntries) {
|
|
371
484
|
const node = nodes[id]
|
|
372
|
-
if (!node)
|
|
485
|
+
if (!node) {
|
|
486
|
+
excludedObjects.add(original)
|
|
487
|
+
continue
|
|
488
|
+
}
|
|
373
489
|
const installedPlugins = sceneState.hasExplicitPluginInstallState
|
|
374
490
|
? sceneState.installedPlugins
|
|
375
491
|
: undefined
|
|
@@ -482,10 +598,18 @@ function finishSceneExportPreparation(preparation: SceneExportPreparation): GlbE
|
|
|
482
598
|
if (clone) identityNodes.add(clone)
|
|
483
599
|
}
|
|
484
600
|
|
|
485
|
-
pruneNonRenderableMeshes(scene, identityNodes)
|
|
601
|
+
const warnings = pruneNonRenderableMeshes(scene, identityNodes)
|
|
486
602
|
sanitizeMaterialGroups(scene, identityNodes)
|
|
487
603
|
convertMaterials(scene, options.textures ?? 'embed', options.purpose ?? 'viewer')
|
|
488
604
|
|
|
605
|
+
for (const [id, original] of registryEntries) {
|
|
606
|
+
const node = nodes[id]
|
|
607
|
+
const clone = cloneByOriginal.get(original)
|
|
608
|
+
if (node?.type !== 'procedural-item' || !clone) continue
|
|
609
|
+
const lights = evaluateRecipe(node.recipe, node.parameters).lights
|
|
610
|
+
decorateProceduralEmission(clone, lights, true)
|
|
611
|
+
}
|
|
612
|
+
|
|
489
613
|
const retainedCloneByOriginal = retainedClones(scene, cloneByOriginal)
|
|
490
614
|
const keepClips = options.animations
|
|
491
615
|
? options.animations === 'keep'
|
|
@@ -493,13 +617,20 @@ function finishSceneExportPreparation(preparation: SceneExportPreparation): GlbE
|
|
|
493
617
|
const animation = keepClips
|
|
494
618
|
? bakeAnimationClips(retainedCloneByOriginal, nodes, registryEntries)
|
|
495
619
|
: { clips: [], clipNamesByNode: new Map<string, string[]>() }
|
|
620
|
+
if (!keepClips) {
|
|
621
|
+
for (const [id, original] of registryEntries) {
|
|
622
|
+
const node = nodes[id]
|
|
623
|
+
const clone = retainedCloneByOriginal.get(original)
|
|
624
|
+
if (node?.type === 'procedural-item' && clone) bakeRegistryAnimationClips(node, clone)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
496
627
|
stampIdentity(scene, retainedCloneByOriginal, nodes, animation.clipNamesByNode, registryEntries)
|
|
497
628
|
|
|
498
629
|
let disposed = false
|
|
499
630
|
return {
|
|
500
631
|
scene,
|
|
501
632
|
animations: animation.clips,
|
|
502
|
-
warnings
|
|
633
|
+
warnings,
|
|
503
634
|
dispose: () => {
|
|
504
635
|
if (disposed) return
|
|
505
636
|
disposed = true
|
|
@@ -715,31 +846,7 @@ function pruneHiddenSceneNodes(
|
|
|
715
846
|
nodes: Record<string, AnyNode>,
|
|
716
847
|
registryEntries: readonly RegistryEntry[],
|
|
717
848
|
) {
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
const isVisible = (id: string, path: Set<string>): boolean => {
|
|
721
|
-
const cached = visibility.get(id)
|
|
722
|
-
if (cached !== undefined) return cached
|
|
723
|
-
|
|
724
|
-
const node = nodes[id]
|
|
725
|
-
if (!node) return true
|
|
726
|
-
if (node.visible === false) {
|
|
727
|
-
visibility.set(id, false)
|
|
728
|
-
return false
|
|
729
|
-
}
|
|
730
|
-
const parentId = node.parentId
|
|
731
|
-
const parent = parentId ? nodes[parentId] : undefined
|
|
732
|
-
if (!parentId || path.has(id) || (parent && !hidesDescendants(parent))) {
|
|
733
|
-
visibility.set(id, true)
|
|
734
|
-
return true
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
path.add(id)
|
|
738
|
-
const visible = isVisible(parentId, path)
|
|
739
|
-
path.delete(id)
|
|
740
|
-
visibility.set(id, visible)
|
|
741
|
-
return visible
|
|
742
|
-
}
|
|
849
|
+
const isVisible = createExportVisibility(nodes)
|
|
743
850
|
|
|
744
851
|
const nodeClones = new Set<THREE.Object3D>()
|
|
745
852
|
for (const [, original] of registryEntries) {
|
|
@@ -748,7 +855,7 @@ function pruneHiddenSceneNodes(
|
|
|
748
855
|
}
|
|
749
856
|
|
|
750
857
|
for (const [id, original] of registryEntries) {
|
|
751
|
-
if (isVisible(id
|
|
858
|
+
if (isVisible(id)) continue
|
|
752
859
|
const clone = cloneByOriginal.get(original)
|
|
753
860
|
if (!clone) continue
|
|
754
861
|
const node = nodes[id]
|
|
@@ -796,7 +903,10 @@ function cloneSceneForExport(
|
|
|
796
903
|
): THREE.Object3D {
|
|
797
904
|
if (excludedObjects.has(source)) return new THREE.Group()
|
|
798
905
|
|
|
799
|
-
|
|
906
|
+
// LOD.copy clones every level even when called through clone(false). Export
|
|
907
|
+
// walks the children itself, and plugin LOD constructors may require inputs.
|
|
908
|
+
const clone =
|
|
909
|
+
source instanceof THREE.LOD ? new THREE.Group().copy(source, false) : source.clone(false)
|
|
800
910
|
clone.userData = cloneExportUserData(source.userData)
|
|
801
911
|
const renderable = source as THREE.Mesh
|
|
802
912
|
const renderableClone = clone as THREE.Mesh
|
|
@@ -902,7 +1012,16 @@ const PLACEHOLDER_MATERIAL = new THREE.MeshBasicMaterial({ visible: false })
|
|
|
902
1012
|
*/
|
|
903
1013
|
function pruneNonRenderableMeshes(root: THREE.Object3D, identityNodes: Set<THREE.Object3D>) {
|
|
904
1014
|
const toRemove: THREE.Object3D[] = []
|
|
1015
|
+
const warnings: string[] = []
|
|
905
1016
|
root.traverse((object) => {
|
|
1017
|
+
if (toRemove.some((ancestor) => isDescendantOf(object, ancestor))) return
|
|
1018
|
+
if (typeof object.userData.pascalExportRefusal === 'string') {
|
|
1019
|
+
warnings.push(
|
|
1020
|
+
`Skipped ${object.name || object.userData.pascalId || 'object'}: ${object.userData.pascalExportRefusal}`,
|
|
1021
|
+
)
|
|
1022
|
+
toRemove.push(object)
|
|
1023
|
+
return
|
|
1024
|
+
}
|
|
906
1025
|
if (object.userData.pascalExport === 'strip') {
|
|
907
1026
|
toRemove.push(object)
|
|
908
1027
|
return
|
|
@@ -961,6 +1080,7 @@ function pruneNonRenderableMeshes(root: THREE.Object3D, identityNodes: Set<THREE
|
|
|
961
1080
|
for (const object of toRemove) {
|
|
962
1081
|
object.removeFromParent()
|
|
963
1082
|
}
|
|
1083
|
+
return warnings
|
|
964
1084
|
}
|
|
965
1085
|
|
|
966
1086
|
/**
|
|
@@ -1342,15 +1462,19 @@ function bakeAnimationClips(
|
|
|
1342
1462
|
const target = cloneByOriginal.get(original)
|
|
1343
1463
|
if (!node || !target) continue
|
|
1344
1464
|
|
|
1465
|
+
// A window or door built from a script carries its clips like an authored item.
|
|
1466
|
+
const scripted = (node.type === 'door' || node.type === 'window') && node.source
|
|
1345
1467
|
const clip =
|
|
1346
1468
|
bakeRegistryAnimationClips(node, target) ??
|
|
1347
|
-
(
|
|
1348
|
-
?
|
|
1349
|
-
: node.type === '
|
|
1350
|
-
?
|
|
1351
|
-
: node.type === '
|
|
1352
|
-
?
|
|
1353
|
-
:
|
|
1469
|
+
(scripted
|
|
1470
|
+
? bakeItemClip(id, target)
|
|
1471
|
+
: node.type === 'door'
|
|
1472
|
+
? bakeDoorClip(id, node, target)
|
|
1473
|
+
: node.type === 'window'
|
|
1474
|
+
? bakeWindowClip(id, node as WindowNode, target)
|
|
1475
|
+
: node.type === 'item'
|
|
1476
|
+
? bakeItemClip(id, target)
|
|
1477
|
+
: null)
|
|
1354
1478
|
|
|
1355
1479
|
if (clip) {
|
|
1356
1480
|
const nodeClips = Array.isArray(clip) ? clip : [clip]
|
|
@@ -1373,44 +1497,47 @@ function bakeRegistryAnimationClips(
|
|
|
1373
1497
|
}
|
|
1374
1498
|
|
|
1375
1499
|
/**
|
|
1376
|
-
* Re-emit
|
|
1377
|
-
* subtree.
|
|
1378
|
-
* since every
|
|
1379
|
-
* cloned node's uuid so
|
|
1380
|
-
* per node (`<id>:
|
|
1500
|
+
* Re-emit an item's clips (a fan's spin, an authored object's motions) onto the
|
|
1501
|
+
* baked subtree. Source clips target the item GLB's nodes by name (`lamp_018`);
|
|
1502
|
+
* since every instance shares those names, we rebind each track to the
|
|
1503
|
+
* specific cloned node's uuid so instances animate independently. Clips are
|
|
1504
|
+
* named per node (`<id>: <name>`; the viewer drives `<id>: loop`).
|
|
1381
1505
|
*/
|
|
1382
|
-
function bakeItemClip(id: string, itemObject: THREE.Object3D): THREE.AnimationClip | null {
|
|
1383
|
-
const
|
|
1384
|
-
if (!
|
|
1506
|
+
function bakeItemClip(id: string, itemObject: THREE.Object3D): THREE.AnimationClip[] | null {
|
|
1507
|
+
const entries = itemClipRegistry.get(id)
|
|
1508
|
+
if (!entries?.length) return null
|
|
1385
1509
|
|
|
1386
|
-
const tracks: THREE.KeyframeTrack[] = []
|
|
1387
1510
|
// The catalog node names (e.g. "lamp_018") repeat across every instance of the
|
|
1388
1511
|
// item, and the glTF export→import roundtrip rebinds clip tracks by node name —
|
|
1389
|
-
// so a shared name would make all fans share one clip. Uniquify
|
|
1390
|
-
// node's name per item once, then bind
|
|
1512
|
+
// so a shared name would make all fans share one clip. Uniquify each targeted
|
|
1513
|
+
// node's name per item once (clips may share targets), then bind by uuid.
|
|
1391
1514
|
const renamed = new Map<string, THREE.Object3D>()
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
const
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1515
|
+
const clips: THREE.AnimationClip[] = []
|
|
1516
|
+
for (const entry of entries) {
|
|
1517
|
+
const tracks: THREE.KeyframeTrack[] = []
|
|
1518
|
+
for (const track of entry.clip.tracks) {
|
|
1519
|
+
const dot = track.name.lastIndexOf('.')
|
|
1520
|
+
if (dot < 0) continue
|
|
1521
|
+
const targetName = track.name.slice(0, dot)
|
|
1522
|
+
const property = track.name.slice(dot + 1)
|
|
1523
|
+
let targetNode = renamed.get(targetName)
|
|
1524
|
+
if (!targetNode) {
|
|
1525
|
+
const found = itemObject.getObjectByName(targetName)
|
|
1526
|
+
if (!found) continue
|
|
1527
|
+
found.name = `${id}__${targetName}`
|
|
1528
|
+
renamed.set(targetName, found)
|
|
1529
|
+
targetNode = found
|
|
1530
|
+
}
|
|
1531
|
+
const retargeted = track.clone()
|
|
1532
|
+
retargeted.name = `${targetNode.uuid}.${property}`
|
|
1533
|
+
tracks.push(retargeted)
|
|
1404
1534
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1535
|
+
if (tracks.length === 0) continue
|
|
1536
|
+
const clip = new THREE.AnimationClip(`${id}: ${entry.name}`, entry.clip.duration, tracks)
|
|
1537
|
+
clip.userData = { loop: entry.loop }
|
|
1538
|
+
clips.push(clip)
|
|
1408
1539
|
}
|
|
1409
|
-
|
|
1410
|
-
if (tracks.length === 0) return null
|
|
1411
|
-
const clip = new THREE.AnimationClip(`${id}: loop`, entry.clip.duration, tracks)
|
|
1412
|
-
clip.userData = { loop: entry.loop }
|
|
1413
|
-
return clip
|
|
1540
|
+
return clips.length > 0 ? clips : null
|
|
1414
1541
|
}
|
|
1415
1542
|
|
|
1416
1543
|
/**
|
|
@@ -1649,42 +1776,6 @@ function bakeWindowClip(
|
|
|
1649
1776
|
* (e.g. `pascalSwingLeaf`, cached-material flags) leaks into glTF extras — the
|
|
1650
1777
|
* file describes itself with exactly the fields a consumer needs.
|
|
1651
1778
|
*/
|
|
1652
|
-
/**
|
|
1653
|
-
* Human-readable label for a baked node, mirroring the viewer's `getNodeName`:
|
|
1654
|
-
* an explicit name wins, items fall back to their catalog asset name, other
|
|
1655
|
-
* kinds to a capitalized type. Levels override this with their display name.
|
|
1656
|
-
*/
|
|
1657
|
-
function nodeDisplayLabel(node: AnyNode): string {
|
|
1658
|
-
if (node.name) return node.name
|
|
1659
|
-
switch (node.type) {
|
|
1660
|
-
case 'item':
|
|
1661
|
-
return (node as { asset?: { name?: string } }).asset?.name || 'Item'
|
|
1662
|
-
case 'wall':
|
|
1663
|
-
return 'Wall'
|
|
1664
|
-
case 'door':
|
|
1665
|
-
return 'Door'
|
|
1666
|
-
case 'window':
|
|
1667
|
-
return 'Window'
|
|
1668
|
-
case 'cabinet':
|
|
1669
|
-
case 'cabinet-module':
|
|
1670
|
-
return 'Cabinet'
|
|
1671
|
-
case 'slab':
|
|
1672
|
-
return 'Slab'
|
|
1673
|
-
case 'ceiling':
|
|
1674
|
-
return 'Ceiling'
|
|
1675
|
-
case 'roof':
|
|
1676
|
-
return 'Roof'
|
|
1677
|
-
case 'fence':
|
|
1678
|
-
return 'Fence'
|
|
1679
|
-
case 'column':
|
|
1680
|
-
return 'Column'
|
|
1681
|
-
case 'stair':
|
|
1682
|
-
return 'Stairs'
|
|
1683
|
-
default:
|
|
1684
|
-
return node.type
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
1779
|
function stampIdentity(
|
|
1689
1780
|
scene: THREE.Object3D,
|
|
1690
1781
|
cloneByOriginal: Map<THREE.Object3D, THREE.Object3D>,
|
|
@@ -1695,8 +1786,43 @@ function stampIdentity(
|
|
|
1695
1786
|
scene.traverse((object) => {
|
|
1696
1787
|
const presentationId = object.userData.pascalPresentationId
|
|
1697
1788
|
const label = object.userData.label
|
|
1789
|
+
const motion = object.userData.proceduralMotion as
|
|
1790
|
+
| {
|
|
1791
|
+
nodeId: string
|
|
1792
|
+
partId: string
|
|
1793
|
+
groupId: string
|
|
1794
|
+
kind: 'hinge' | 'slide' | 'spin'
|
|
1795
|
+
clip?: string
|
|
1796
|
+
activeWindow?: [number, number]
|
|
1797
|
+
}
|
|
1798
|
+
| undefined
|
|
1799
|
+
const surfaceNodeIds = object.userData.surfaceNodeIds
|
|
1800
|
+
const ifcRole = object.userData.pascalIfcRole
|
|
1801
|
+
const ifcParts = object.userData.pascalIfcParts
|
|
1802
|
+
const slotId = object.userData.slotId
|
|
1698
1803
|
object.userData =
|
|
1699
1804
|
typeof presentationId === 'string' ? { pascalPresentationId: presentationId, label } : {}
|
|
1805
|
+
if (
|
|
1806
|
+
Array.isArray(surfaceNodeIds) &&
|
|
1807
|
+
surfaceNodeIds.every((id) => typeof id === 'string' && nodes[id])
|
|
1808
|
+
)
|
|
1809
|
+
object.userData.surfaceNodeIds = surfaceNodeIds
|
|
1810
|
+
if (typeof ifcRole === 'string') object.userData.pascalIfcRole = ifcRole
|
|
1811
|
+
if (Array.isArray(ifcParts) && ifcParts.every(isIfcRolePart))
|
|
1812
|
+
object.userData.pascalIfcParts = ifcParts
|
|
1813
|
+
if (typeof slotId === 'string') object.userData.slotId = slotId
|
|
1814
|
+
if (motion) {
|
|
1815
|
+
object.userData.proceduralMotion = {
|
|
1816
|
+
nodeId: motion.nodeId,
|
|
1817
|
+
partId: motion.partId,
|
|
1818
|
+
groupId: motion.groupId,
|
|
1819
|
+
kind: motion.kind,
|
|
1820
|
+
...(motion.clip && clipNamesByNode.get(motion.nodeId)?.includes(motion.clip)
|
|
1821
|
+
? { clip: motion.clip }
|
|
1822
|
+
: {}),
|
|
1823
|
+
...(motion.activeWindow ? { activeWindow: motion.activeWindow } : {}),
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1700
1826
|
})
|
|
1701
1827
|
|
|
1702
1828
|
for (const [id, original] of registryEntries) {
|
|
@@ -1721,21 +1847,15 @@ function stampIdentity(
|
|
|
1721
1847
|
extras.label = getLevelDisplayName(node as LevelNode)
|
|
1722
1848
|
target.visible = true
|
|
1723
1849
|
}
|
|
1724
|
-
//
|
|
1725
|
-
//
|
|
1726
|
-
//
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
}
|
|
1734
|
-
// Items with a baked ambient clip (a fan's spin) carry the clip name but no
|
|
1735
|
-
// `openable` flag — nothing opens; the clip just loops.
|
|
1736
|
-
if (node.type === 'item') {
|
|
1737
|
-
const clipNames = clipNamesByNode.get(id)
|
|
1738
|
-
if (clipNames?.length) extras.clips = clipNames
|
|
1850
|
+
// Every node that baked clips lists them, whatever its kind, so the viewer
|
|
1851
|
+
// can play them. Only nodes that actually baked an open clip are openable. A
|
|
1852
|
+
// cased opening (no leaf), fixed window, or static cabinet produces no clip,
|
|
1853
|
+
// so it stays unflagged — the file never claims a part opens when nothing
|
|
1854
|
+
// moves; an ambient loop (a fan's spin) is listed without the flag.
|
|
1855
|
+
const clipNames = clipNamesByNode.get(id)
|
|
1856
|
+
if (clipNames?.length) {
|
|
1857
|
+
if (clipNames.some((name) => name.endsWith(': open'))) extras.openable = true
|
|
1858
|
+
extras.clips = clipNames
|
|
1739
1859
|
}
|
|
1740
1860
|
if (node.type === 'zone') {
|
|
1741
1861
|
// Zone fills are stripped from the bake; /viewer rebuilds the room from
|
|
@@ -1743,6 +1863,7 @@ function stampIdentity(
|
|
|
1743
1863
|
// `onlyVisible` keeps it even when the editor had zones hidden at export.
|
|
1744
1864
|
const zone = node as ZoneNode
|
|
1745
1865
|
extras.polygon = zone.polygon
|
|
1866
|
+
if (zone.holes?.length) extras.holes = zone.holes
|
|
1746
1867
|
extras.color = zone.color
|
|
1747
1868
|
target.visible = true
|
|
1748
1869
|
}
|
|
@@ -1754,6 +1875,6 @@ function stampIdentity(
|
|
|
1754
1875
|
extras.rotation = (node as { rotation?: number }).rotation ?? 0
|
|
1755
1876
|
target.visible = true
|
|
1756
1877
|
}
|
|
1757
|
-
target.userData = extras
|
|
1878
|
+
target.userData = { ...target.userData, ...extras }
|
|
1758
1879
|
}
|
|
1759
1880
|
}
|
package/src/lib/history.test.ts
CHANGED
|
@@ -109,11 +109,10 @@ describe('standalone history source invalidation', () => {
|
|
|
109
109
|
`)
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
test.each([
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
runSourceHistoryTest(`
|
|
112
|
+
test.each([true, false])(
|
|
113
|
+
'discarded preview neighbours rebuild after undo/redo (joined: %s)',
|
|
114
|
+
(joined) => {
|
|
115
|
+
runSourceHistoryTest(`
|
|
117
116
|
const react = await importShared('react')
|
|
118
117
|
mockShared('react', () => ({ ...react, useEffect: () => {} }))
|
|
119
118
|
const frames = []
|
|
@@ -152,7 +151,8 @@ describe('standalone history source invalidation', () => {
|
|
|
152
151
|
assert.deepEqual(Array.from(b.geometry.getAttribute('position').array), canonical)
|
|
153
152
|
}
|
|
154
153
|
`)
|
|
155
|
-
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
156
|
|
|
157
157
|
test('opening reparent dirties old and new walls on undo and redo', () => {
|
|
158
158
|
runSourceHistoryTest(`
|
|
@@ -217,6 +217,9 @@ describe('standalone history source invalidation', () => {
|
|
|
217
217
|
const { Group, Mesh, MeshBasicMaterial, BoxGeometry } = await importShared('three')
|
|
218
218
|
const viewer = await importShared('@pascal-app/viewer')
|
|
219
219
|
const { captureChangedNodes, runBatchFrame, resetNodeBatchState } = await import(${JSON.stringify(resolve(import.meta.dir, '../../../nodes/src/shared/node-batch/system.tsx'))})
|
|
220
|
+
// The node batch reads each kind's \`capabilities.batchable\` from the registry.
|
|
221
|
+
const { doorBatchable } = await import(${JSON.stringify(resolve(import.meta.dir, '../../../nodes/src/shared/node-batch/batchable.ts'))})
|
|
222
|
+
core.nodeRegistry._register({ kind: 'door', schemaVersion: 1, capabilities: { batchable: doorBatchable } })
|
|
220
223
|
const root = new Group()
|
|
221
224
|
core.sceneRegistry.nodes.set(level.id, root)
|
|
222
225
|
core.sceneRegistry.byType.level.add(level.id)
|
|
@@ -260,6 +263,9 @@ describe('standalone history source invalidation', () => {
|
|
|
260
263
|
const { Group, Mesh, MeshBasicMaterial, BoxGeometry } = await importShared('three')
|
|
261
264
|
const viewer = await importShared('@pascal-app/viewer')
|
|
262
265
|
const { captureChangedNodes, runBatchFrame, resetNodeBatchState } = await import(${JSON.stringify(resolve(import.meta.dir, '../../../nodes/src/shared/node-batch/system.tsx'))})
|
|
266
|
+
// The node batch reads each kind's \`capabilities.batchable\` from the registry.
|
|
267
|
+
const { doorBatchable } = await import(${JSON.stringify(resolve(import.meta.dir, '../../../nodes/src/shared/node-batch/batchable.ts'))})
|
|
268
|
+
core.nodeRegistry._register({ kind: 'door', schemaVersion: 1, capabilities: { batchable: doorBatchable } })
|
|
263
269
|
const root = new Group()
|
|
264
270
|
core.sceneRegistry.nodes.set(level.id, root)
|
|
265
271
|
core.sceneRegistry.byType.level.add(level.id)
|
|
@@ -337,7 +343,12 @@ describe('standalone history source invalidation', () => {
|
|
|
337
343
|
mockShared('react', () => ({ ...react, useEffect: () => {}, useRef: current => ({ current }) }))
|
|
338
344
|
const frames = []
|
|
339
345
|
const fiber = await importShared('@react-three/fiber')
|
|
340
|
-
|
|
346
|
+
// DoorSystem and WindowSystem register their materials for scene capture through useThree.
|
|
347
|
+
mockShared('@react-three/fiber', () => ({
|
|
348
|
+
...fiber,
|
|
349
|
+
useFrame: frame => frames.push(frame),
|
|
350
|
+
useThree: select => select({ scene: {} }),
|
|
351
|
+
}))
|
|
341
352
|
const selector = store => Object.assign(fn => fn(store.getState()), store)
|
|
342
353
|
mockShared('@pascal-app/core', () => ({ ...core, useScene: selector(scene), useLiveNodeOverrides: selector(overrides) }))
|
|
343
354
|
const viewer = await importShared('@pascal-app/viewer')
|
package/src/lib/history.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from '@pascal-app/core'
|
|
10
10
|
import { markPerfAction } from '@pascal-app/viewer'
|
|
11
11
|
import useInteractionScope from '../store/use-interaction-scope'
|
|
12
|
+
import { cancelGestures } from './gesture-lifecycle'
|
|
12
13
|
import { registeredDraftingConfig } from './interaction/registered-drafting'
|
|
13
14
|
|
|
14
15
|
export type HistoryCommandState = {
|
|
@@ -130,6 +131,9 @@ export function shouldCancelDraftOnHistoryJump(): boolean {
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
export function runUndo(): HistoryCommandResult {
|
|
134
|
+
// A history jump under a live gesture would land on its paused baseline;
|
|
135
|
+
// every history command, not only the keyboard's, ends it first.
|
|
136
|
+
cancelGestures('history')
|
|
133
137
|
if (shouldCancelDraftOnHistoryJump()) emitter.emit('tool:cancel')
|
|
134
138
|
if (historyCommandDelegate) {
|
|
135
139
|
const result = historyCommandDelegate.undo()
|
|
@@ -147,6 +151,9 @@ export function runUndo(): HistoryCommandResult {
|
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
export function runRedo(): HistoryCommandResult {
|
|
154
|
+
// A history jump under a live gesture would land on its paused baseline;
|
|
155
|
+
// every history command, not only the keyboard's, ends it first.
|
|
156
|
+
cancelGestures('history')
|
|
150
157
|
if (shouldCancelDraftOnHistoryJump()) emitter.emit('tool:cancel')
|
|
151
158
|
if (historyCommandDelegate) {
|
|
152
159
|
const result = historyCommandDelegate.redo()
|