@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/paint-scope.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNode,
|
|
3
3
|
type AnyNodeId,
|
|
4
|
+
floorStepRole,
|
|
4
5
|
generateSceneMaterialId,
|
|
6
|
+
getWallLevelZones,
|
|
7
|
+
getWallLocalFaceZ,
|
|
8
|
+
getWallZoneSpans,
|
|
5
9
|
type ItemNode,
|
|
6
10
|
type MaterialSchema,
|
|
7
11
|
nodeRegistry,
|
|
12
|
+
parseFloorStepRole,
|
|
13
|
+
parseRoomFinishRole,
|
|
8
14
|
pointInPolygon2D,
|
|
9
15
|
resolveLevelId,
|
|
10
16
|
type SceneMaterial,
|
|
@@ -15,6 +21,7 @@ import {
|
|
|
15
21
|
toSceneMaterialRef,
|
|
16
22
|
useScene,
|
|
17
23
|
type WallNode,
|
|
24
|
+
wallRoomFinishRole,
|
|
18
25
|
} from '@pascal-app/core'
|
|
19
26
|
|
|
20
27
|
/**
|
|
@@ -36,6 +43,8 @@ export type PaintHoverInfo = {
|
|
|
36
43
|
slotLabel: string
|
|
37
44
|
/** Kind noun for the `'object'` label (e.g. "Whole shelf"). */
|
|
38
45
|
nodeNoun: string
|
|
46
|
+
/** Labels a surface names itself, per scope, over the generic ones. */
|
|
47
|
+
labels?: Partial<Record<PaintScope, string>>
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
function nodeHasAsset(node: AnyNode): boolean {
|
|
@@ -46,13 +55,55 @@ function nodeOffersRoomScope(node: AnyNode): boolean {
|
|
|
46
55
|
return nodeRegistry.get(node.type)?.capabilities?.paint?.roomScope === true
|
|
47
56
|
}
|
|
48
57
|
|
|
58
|
+
const isFloorPlate = (node: AnyNode): boolean => node.type === 'slab' && node.boundary === 'auto'
|
|
59
|
+
|
|
60
|
+
const FOOTPRINT_FACE_LABELS: Record<string, string> = {
|
|
61
|
+
edge: 'Floor edge · all around this floor',
|
|
62
|
+
riser: 'Riser · all around this floor',
|
|
63
|
+
underside: 'Underside · all of this floor',
|
|
64
|
+
foundation: 'Foundation · all around this floor',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The scopes a generated floor plate offers for the surface under the cursor.
|
|
69
|
+
* A plate is not an object anyone paints whole: a room's floor is "this
|
|
70
|
+
* surface" (the painted part under the cursor, else the room floor) or the
|
|
71
|
+
* whole room; a step is its doorway or every step of the room; the footprint's
|
|
72
|
+
* faces are one finish all around the floor, so they have nothing to cycle.
|
|
73
|
+
*/
|
|
74
|
+
export function platePaintScopes(
|
|
75
|
+
node: AnyNode,
|
|
76
|
+
role: string,
|
|
77
|
+
): { scopes: PaintScope[]; labels: Partial<Record<PaintScope, string>> } | null {
|
|
78
|
+
if (node.type !== 'slab' || node.boundary !== 'auto') return null
|
|
79
|
+
if (parseFloorStepRole(role))
|
|
80
|
+
return {
|
|
81
|
+
scopes: ['single', 'room'],
|
|
82
|
+
labels: { single: 'This step', room: 'All steps in this room' },
|
|
83
|
+
}
|
|
84
|
+
if (parseRoomFinishRole(role))
|
|
85
|
+
return { scopes: ['single', 'room'], labels: { single: 'This surface', room: 'Whole room' } }
|
|
86
|
+
const fixed = node.plateRole === 'base' ? FOOTPRINT_FACE_LABELS[role] : undefined
|
|
87
|
+
return { scopes: ['single'], labels: fixed ? { single: fixed } : {} }
|
|
88
|
+
}
|
|
89
|
+
|
|
49
90
|
/**
|
|
50
91
|
* The scopes a hovered node offers, derived from the node itself: every node
|
|
51
92
|
* paints `single`; > 1 slot adds `object`; an `asset` adds `matching`; a
|
|
52
93
|
* `roomScope`-declaring kind adds `room`. `slotRoles` is the node's full slot set
|
|
53
|
-
* (declared or mesh-derived), passed in by the caller.
|
|
94
|
+
* (declared or mesh-derived), passed in by the caller. A floor plate's set
|
|
95
|
+
* depends on the surface under the cursor (`role`).
|
|
54
96
|
*/
|
|
55
|
-
export function availablePaintScopes(args: {
|
|
97
|
+
export function availablePaintScopes(args: {
|
|
98
|
+
node: AnyNode
|
|
99
|
+
slotRoles: string[]
|
|
100
|
+
role?: string
|
|
101
|
+
}): PaintScope[] {
|
|
102
|
+
// A wall paints the face under the cursor, or its room's walls: "whole wall"
|
|
103
|
+
// (both faces of one wall, across two rooms) is nobody's surface.
|
|
104
|
+
if (args.node.type === 'wall') return ['single', 'room']
|
|
105
|
+
const plate = args.role === undefined ? null : platePaintScopes(args.node, args.role)
|
|
106
|
+
if (plate) return plate.scopes
|
|
56
107
|
const scopes: PaintScope[] = ['single']
|
|
57
108
|
if (args.slotRoles.length > 1) scopes.push('object')
|
|
58
109
|
if (nodeHasAsset(args.node)) scopes.push('matching')
|
|
@@ -66,7 +117,24 @@ export function cyclePaintScope(scope: PaintScope, scopes: PaintScope[]): PaintS
|
|
|
66
117
|
return list[(index + 1) % list.length] ?? 'single'
|
|
67
118
|
}
|
|
68
119
|
|
|
120
|
+
/** The chip's hover info for `role` on `node`: its scopes and what each is called. */
|
|
121
|
+
export function paintHoverInfo(node: AnyNode, role: string, slotRoles: string[]): PaintHoverInfo {
|
|
122
|
+
return {
|
|
123
|
+
scopes: availablePaintScopes({ node, slotRoles, role }),
|
|
124
|
+
slotLabel: paintSurfaceLabel(node, role),
|
|
125
|
+
nodeNoun: node.type,
|
|
126
|
+
labels: platePaintScopes(node, role)?.labels,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The scope a click applies: the chosen one when the hovered surface offers it. */
|
|
131
|
+
export function effectivePaintScope(scope: PaintScope, scopes: PaintScope[]): PaintScope {
|
|
132
|
+
return scopes.includes(scope) ? scope : 'single'
|
|
133
|
+
}
|
|
134
|
+
|
|
69
135
|
export function paintScopeLabel(scope: PaintScope, info: PaintHoverInfo): string {
|
|
136
|
+
const own = info.labels?.[scope]
|
|
137
|
+
if (own) return own
|
|
70
138
|
switch (scope) {
|
|
71
139
|
case 'object':
|
|
72
140
|
return `Whole ${info.nodeNoun}`
|
|
@@ -91,13 +159,21 @@ export function nodeSlotRoles(node: AnyNode, meshSlotRoles: (node: AnyNode) => s
|
|
|
91
159
|
return meshSlotRoles(node)
|
|
92
160
|
}
|
|
93
161
|
|
|
162
|
+
/**
|
|
163
|
+
* What the paint HUD calls the surface under the cursor: a wall face is the
|
|
164
|
+
* "Face" (the narrow scope), a trim its own name, anything else its slot.
|
|
165
|
+
*/
|
|
166
|
+
export function paintSurfaceLabel(node: AnyNode, role: string): string {
|
|
167
|
+
if (node.type === 'wall' && !WALL_TRIM_ROLE.test(role)) return 'Face'
|
|
168
|
+
return slotDisplayLabel(node, role)
|
|
169
|
+
}
|
|
170
|
+
|
|
94
171
|
/** Display label for the hovered slot — declared label wins, else derived from the id. */
|
|
95
172
|
export function slotDisplayLabel(node: AnyNode, role: string): string {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return declared?.label ?? slotLabelFromId(role)
|
|
173
|
+
const capabilities = nodeRegistry.get(node.type)?.capabilities
|
|
174
|
+
const declared = capabilities?.slots?.(node)?.find((slot) => slot.slotId === role)
|
|
175
|
+
if (declared) return declared.label
|
|
176
|
+
return capabilities?.paint?.roleLabel?.(node, role) ?? slotLabelFromId(role)
|
|
101
177
|
}
|
|
102
178
|
|
|
103
179
|
// ── Fan-out resolution ──────────────────────────────────────────────────────
|
|
@@ -141,17 +217,87 @@ function distanceToPolyline(
|
|
|
141
217
|
return distance
|
|
142
218
|
}
|
|
143
219
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
220
|
+
const WALL_TRIM_ROLE = /^([ab])(Skirting|Crown|ChairRail)$/
|
|
221
|
+
const WALL_ROOM_FACE_ROLE = /^room:(.+)\/([ab])$/
|
|
222
|
+
|
|
223
|
+
/** The same surface on a boundary face: face slots and trims follow the physical face. */
|
|
224
|
+
function wallRoleForRoomFace(role: string, face: 'front' | 'back'): string | null {
|
|
225
|
+
const physical = face === 'front' ? 'a' : 'b'
|
|
226
|
+
if (role === 'a' || role === 'b') return physical
|
|
227
|
+
const trim = WALL_TRIM_ROLE.exec(role)
|
|
228
|
+
return trim ? `${physical}${trim[2]}` : null
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The face a wall paint role sits on, when the role names one. */
|
|
232
|
+
function wallRoleFace(role: string): 'a' | 'b' | null {
|
|
233
|
+
if (role === 'a' || role === 'b') return role
|
|
234
|
+
return (WALL_TRIM_ROLE.exec(role)?.[1] ?? WALL_ROOM_FACE_ROLE.exec(role)?.[2] ?? null) as
|
|
235
|
+
| 'a'
|
|
236
|
+
| 'b'
|
|
237
|
+
| null
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The room (zone) a wall face borders at the hit point, from the zones' boundary spans. */
|
|
241
|
+
function zoneAtWallHit(
|
|
242
|
+
wall: WallNode,
|
|
243
|
+
face: 'a' | 'b',
|
|
244
|
+
point: readonly [number, number],
|
|
245
|
+
nodes: Record<string, AnyNode>,
|
|
246
|
+
): string | null {
|
|
247
|
+
const dx = wall.end[0] - wall.start[0]
|
|
248
|
+
const dz = wall.end[1] - wall.start[1]
|
|
249
|
+
const lengthSquared = dx * dx + dz * dz
|
|
250
|
+
if (lengthSquared < 1e-12) return null
|
|
251
|
+
const t = ((point[0] - wall.start[0]) * dx + (point[1] - wall.start[1]) * dz) / lengthSquared
|
|
252
|
+
const spans = getWallZoneSpans(wall, getWallLevelZones(wall, nodes))
|
|
253
|
+
const span = spans.find(
|
|
254
|
+
(candidate) =>
|
|
255
|
+
candidate.face === face &&
|
|
256
|
+
t >= candidate.t0 - 1e-6 &&
|
|
257
|
+
(t < candidate.t1 || (candidate.t1 >= 1 && t <= 1 + 1e-6)),
|
|
258
|
+
)
|
|
259
|
+
return span?.zoneId ?? null
|
|
260
|
+
}
|
|
148
261
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Room scope on a wall face inside a room paints the room's wall finish: one
|
|
264
|
+
* `room:<zoneId>` commit on the zone. Every wall bordering the room is listed so
|
|
265
|
+
* each previews the change; trims fan out to the face each wall turns to the room.
|
|
266
|
+
*/
|
|
267
|
+
function wallRoomTargets(
|
|
268
|
+
wall: WallNode,
|
|
269
|
+
role: string,
|
|
270
|
+
zoneId: string,
|
|
271
|
+
nodes: Record<string, AnyNode>,
|
|
272
|
+
): Array<{ nodeId: AnyNodeId; role: string }> {
|
|
273
|
+
const zone = nodes[zoneId]
|
|
274
|
+
if (zone?.type !== 'zone') return []
|
|
275
|
+
const walls = [
|
|
276
|
+
wall,
|
|
277
|
+
...zone.boundaryWallIds
|
|
278
|
+
.map((id) => nodes[id])
|
|
279
|
+
.filter((node): node is WallNode => node?.type === 'wall' && node.id !== wall.id),
|
|
280
|
+
]
|
|
281
|
+
const trim = WALL_TRIM_ROLE.exec(role)
|
|
282
|
+
if (trim) {
|
|
283
|
+
const targets = new Map<string, { nodeId: AnyNodeId; role: string }>()
|
|
284
|
+
for (const target of walls) {
|
|
285
|
+
for (const span of getWallZoneSpans(target, [zone])) {
|
|
286
|
+
const targetRole = `${span.face}${trim[2]}`
|
|
287
|
+
targets.set(`${target.id}:${targetRole}`, {
|
|
288
|
+
nodeId: target.id as AnyNodeId,
|
|
289
|
+
role: targetRole,
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return [...targets.values()]
|
|
294
|
+
}
|
|
295
|
+
const roomRole = wallRoomFinishRole(zoneId)
|
|
296
|
+
const targets = walls.map((target) => ({ nodeId: target.id as AnyNodeId, role: roomRole }))
|
|
297
|
+
// A room bounded by one wall still commits through the fan-out path, which
|
|
298
|
+
// routes the room role to the zone; the zone target itself previews nothing.
|
|
299
|
+
if (targets.length === 1) targets.push({ nodeId: zone.id as AnyNodeId, role: roomRole })
|
|
300
|
+
return targets
|
|
155
301
|
}
|
|
156
302
|
|
|
157
303
|
function resolveWallPaintSpace(args: {
|
|
@@ -162,7 +308,13 @@ function resolveWallPaintSpace(args: {
|
|
|
162
308
|
}): Space | null {
|
|
163
309
|
const { wall, wallHit, nodes, spaces } = args
|
|
164
310
|
const levelId = wall.parentId ?? resolveLevelId(wall, nodes)
|
|
165
|
-
const tolerance =
|
|
311
|
+
const tolerance =
|
|
312
|
+
getWallLocalFaceZ(
|
|
313
|
+
{ ...wall, thickness: wall.thickness ?? 0.2 },
|
|
314
|
+
wallHit.face === 'front' ? 'a' : 'b',
|
|
315
|
+
) *
|
|
316
|
+
(wallHit.face === 'front' ? 1 : -1) +
|
|
317
|
+
0.08
|
|
166
318
|
let best: { space: Space; distance: number } | null = null
|
|
167
319
|
|
|
168
320
|
for (const space of Object.values(spaces)) {
|
|
@@ -185,6 +337,7 @@ function boundaryPointKey(point: readonly [number, number]): string {
|
|
|
185
337
|
function boundarySegmentKey(boundary: WallBoundaryFace): string {
|
|
186
338
|
const forward = boundary.points.map(boundaryPointKey).join('|')
|
|
187
339
|
const reverse = [...boundary.points].reverse().map(boundaryPointKey).join('|')
|
|
340
|
+
// biome-ignore lint/complexity/useMathMinMax: lexicographic string comparison
|
|
188
341
|
return `${boundary.wallId}:${forward < reverse ? forward : reverse}`
|
|
189
342
|
}
|
|
190
343
|
|
|
@@ -216,7 +369,13 @@ function connectedExteriorBoundaries(args: {
|
|
|
216
369
|
if (!boundary) return []
|
|
217
370
|
return [{ ...boundary, face: oppositeWallFace(boundary.face) }]
|
|
218
371
|
})
|
|
219
|
-
const tolerance =
|
|
372
|
+
const tolerance =
|
|
373
|
+
getWallLocalFaceZ(
|
|
374
|
+
{ ...wall, thickness: wall.thickness ?? 0.2 },
|
|
375
|
+
wallHit.face === 'front' ? 'a' : 'b',
|
|
376
|
+
) *
|
|
377
|
+
(wallHit.face === 'front' ? 1 : -1) +
|
|
378
|
+
0.08
|
|
220
379
|
const seed = exterior
|
|
221
380
|
.filter((boundary) => boundary.wallId === wall.id && boundary.face === wallHit.face)
|
|
222
381
|
.map((boundary) => ({ boundary, distance: distanceToPolyline(wallHit.point, boundary.points) }))
|
|
@@ -277,7 +436,7 @@ function wallTargetsForBoundaries(args: {
|
|
|
277
436
|
) {
|
|
278
437
|
continue
|
|
279
438
|
}
|
|
280
|
-
const targetRole = wallRoleForRoomFace(role,
|
|
439
|
+
const targetRole = wallRoleForRoomFace(role, boundary.face)
|
|
281
440
|
if (!targetRole) continue
|
|
282
441
|
const key = `${targetWall.id}:${targetRole}`
|
|
283
442
|
targets.set(key, { nodeId: targetWall.id as AnyNodeId, role: targetRole })
|
|
@@ -298,6 +457,20 @@ function polygonCentroid(
|
|
|
298
457
|
return [x / points.length, z / points.length]
|
|
299
458
|
}
|
|
300
459
|
|
|
460
|
+
/**
|
|
461
|
+
* The role one paint click writes under `scope`: a floor step paints its own
|
|
462
|
+
* doorway, and in the room scope every step of its room (`step:<zoneId>`); a
|
|
463
|
+
* room's floor in the room scope is the whole room (`room:<zoneId>/*`).
|
|
464
|
+
* Hover outline, preview and commit all start from this role.
|
|
465
|
+
*/
|
|
466
|
+
export function paintScopeRole(node: AnyNode, role: string, scope: PaintScope): string {
|
|
467
|
+
if (node.type !== 'slab' || scope !== 'room') return role
|
|
468
|
+
const step = parseFloorStepRole(role)
|
|
469
|
+
if (step) return floorStepRole(step.zoneId)
|
|
470
|
+
const floor = isFloorPlate(node) ? parseRoomFinishRole(role) : null
|
|
471
|
+
return floor ? `room:${floor.zoneId}/*` : role
|
|
472
|
+
}
|
|
473
|
+
|
|
301
474
|
/**
|
|
302
475
|
* Expand one paint hit (`node` + resolved `role`) into the full list of
|
|
303
476
|
* (node, role) targets the current `scope` should paint. Returns just the
|
|
@@ -318,8 +491,21 @@ export function resolvePaintScopeTargets(args: {
|
|
|
318
491
|
slotRolesOf: (node: AnyNode) => string[]
|
|
319
492
|
wallHit?: WallPaintHit
|
|
320
493
|
}): Array<{ nodeId: AnyNodeId; role: string }> {
|
|
321
|
-
const { node, role,
|
|
494
|
+
const { node, role, nodes, spaces, slotRolesOf, wallHit } = args
|
|
495
|
+
const plateScopes = platePaintScopes(node, role)?.scopes
|
|
496
|
+
const scope = plateScopes ? effectivePaintScope(args.scope, plateScopes) : args.scope
|
|
322
497
|
const single = [{ nodeId: node.id as AnyNodeId, role }]
|
|
498
|
+
if (node.type === 'slab' && parseFloorStepRole(role)) {
|
|
499
|
+
// A room's steps are drawn by whichever plates carry them.
|
|
500
|
+
const stepRole = paintScopeRole(node, role, scope)
|
|
501
|
+
const targets = Object.values(nodes)
|
|
502
|
+
.filter(
|
|
503
|
+
(other) =>
|
|
504
|
+
other.type === 'slab' && other.parentId === node.parentId && other.boundary === 'auto',
|
|
505
|
+
)
|
|
506
|
+
.map((other) => ({ nodeId: other.id, role: stepRole }))
|
|
507
|
+
return targets.length ? targets : [{ nodeId: node.id as AnyNodeId, role: stepRole }]
|
|
508
|
+
}
|
|
323
509
|
if (scope === 'single') return single
|
|
324
510
|
|
|
325
511
|
// Whole object: paint every slot of the clicked node. Generic across any
|
|
@@ -344,6 +530,13 @@ export function resolvePaintScopeTargets(args: {
|
|
|
344
530
|
if (!wallHit) return single
|
|
345
531
|
const levelId = wall.parentId ?? resolveLevelId(wall, nodes)
|
|
346
532
|
if (!levelId) return single
|
|
533
|
+
const face = wallRoleFace(role) ?? (wallHit.face === 'front' ? 'a' : 'b')
|
|
534
|
+
const zoneId =
|
|
535
|
+
WALL_ROOM_FACE_ROLE.exec(role)?.[1] ?? zoneAtWallHit(wall, face, wallHit.point, nodes)
|
|
536
|
+
if (zoneId) {
|
|
537
|
+
const targets = wallRoomTargets(wall, role, zoneId, nodes)
|
|
538
|
+
if (targets.length > 0) return targets
|
|
539
|
+
}
|
|
347
540
|
const space = resolveWallPaintSpace({ wall, wallHit, nodes, spaces })
|
|
348
541
|
const boundaries = space
|
|
349
542
|
? space.boundaryFaces
|
|
@@ -353,6 +546,24 @@ export function resolvePaintScopeTargets(args: {
|
|
|
353
546
|
}
|
|
354
547
|
|
|
355
548
|
if (node.type === 'slab' && scope === 'room') {
|
|
549
|
+
// A room's floor in the room scope: every finish source of that room (its
|
|
550
|
+
// floor, painted parts, steps and edge) on every plate of the level, as one
|
|
551
|
+
// `room:<zoneId>/*` role the zone commits.
|
|
552
|
+
const floor = parseRoomFinishRole(role)
|
|
553
|
+
if (floor) {
|
|
554
|
+
const roomWide = `room:${floor.zoneId}/*`
|
|
555
|
+
const plates = Object.values(nodes).filter(
|
|
556
|
+
(other) =>
|
|
557
|
+
other.type === 'slab' && other.parentId === node.parentId && other.boundary === 'auto',
|
|
558
|
+
)
|
|
559
|
+
return (plates.length ? plates : [node]).map((plate) => ({
|
|
560
|
+
nodeId: plate.id as AnyNodeId,
|
|
561
|
+
role: roomWide,
|
|
562
|
+
}))
|
|
563
|
+
}
|
|
564
|
+
// A floor plate's own faces have no room scope; below is the hand-drawn
|
|
565
|
+
// slab's spread over the slabs of its room.
|
|
566
|
+
if (isFloorPlate(node)) return single
|
|
356
567
|
const centroid = polygonCentroid((node as SlabNode).polygon)
|
|
357
568
|
if (!centroid) return single
|
|
358
569
|
// Space polygons are per-level footprints, and stacked storeys share a
|
|
@@ -386,7 +597,7 @@ function materialsEqual(a: unknown, b: unknown): boolean {
|
|
|
386
597
|
if (Object.is(a, b)) return true
|
|
387
598
|
if (typeof a !== typeof b || a === null || b === null) return false
|
|
388
599
|
if (Array.isArray(a) || Array.isArray(b)) {
|
|
389
|
-
if (!Array.isArray(a)
|
|
600
|
+
if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false
|
|
390
601
|
return a.every((value, index) => materialsEqual(value, b[index]))
|
|
391
602
|
}
|
|
392
603
|
if (typeof a === 'object') {
|
|
@@ -401,18 +612,49 @@ function materialsEqual(a: unknown, b: unknown): boolean {
|
|
|
401
612
|
return false
|
|
402
613
|
}
|
|
403
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Split a fan-out into slot writes and roles a kind routes elsewhere — a wall's
|
|
617
|
+
* `room:<zoneId>` paints the zone, through the wall's own commit, once per role.
|
|
618
|
+
* Targets without a paint capability (the zone listed for a one-wall room) only
|
|
619
|
+
* ride along for the preview.
|
|
620
|
+
*/
|
|
621
|
+
function partitionFanout(targets: ReadonlyArray<{ nodeId: AnyNodeId; role: string }>) {
|
|
622
|
+
const nodes = useScene.getState().nodes
|
|
623
|
+
const routed = new Map<string, { nodeId: AnyNodeId; role: string }>()
|
|
624
|
+
const slots: Array<{ nodeId: AnyNodeId; role: string }> = []
|
|
625
|
+
for (const target of targets) {
|
|
626
|
+
const node = nodes[target.nodeId]
|
|
627
|
+
if (!node) continue
|
|
628
|
+
const capabilities = nodeRegistry.get(node.type)?.capabilities
|
|
629
|
+
const declared = capabilities?.slots?.(node)
|
|
630
|
+
const isSlot = declared?.length
|
|
631
|
+
? declared.some((slot) => slot.slotId === target.role)
|
|
632
|
+
: !target.role.includes(':')
|
|
633
|
+
const key = target.role.includes(':') ? target.role : `${target.nodeId}:${target.role}`
|
|
634
|
+
if (isSlot) slots.push(target)
|
|
635
|
+
else if (capabilities?.paint?.commit && !routed.has(key)) routed.set(key, target)
|
|
636
|
+
}
|
|
637
|
+
return { routed: [...routed.values()], slots }
|
|
638
|
+
}
|
|
639
|
+
|
|
404
640
|
/**
|
|
405
641
|
* Apply one paint to many slot-model targets in a single undo step. Resolves
|
|
406
642
|
* the slot ref ONCE — a one-off colour creates a single shared scene material
|
|
407
643
|
* for the whole fan-out, not one per node — then writes every `node.slots[role]`
|
|
408
|
-
* (or deletes it, for the eraser) in one `useScene.setState`.
|
|
409
|
-
*
|
|
644
|
+
* (or deletes it, for the eraser) in one `useScene.setState`. Routed roles
|
|
645
|
+
* (see `partitionFanout`) commit through their kind instead.
|
|
410
646
|
*/
|
|
411
647
|
export function commitPaintScopeFanout(
|
|
412
|
-
|
|
648
|
+
fanout: ReadonlyArray<{ nodeId: AnyNodeId; role: string }>,
|
|
413
649
|
material: MaterialSchema | undefined,
|
|
414
650
|
materialPreset: string | undefined,
|
|
415
651
|
): void {
|
|
652
|
+
const { routed, slots: targets } = partitionFanout(fanout)
|
|
653
|
+
for (const { nodeId, role } of routed) {
|
|
654
|
+
const node = useScene.getState().nodes[nodeId]
|
|
655
|
+
const paint = node ? nodeRegistry.get(node.type)?.capabilities?.paint : undefined
|
|
656
|
+
if (node && paint?.commit) paint.commit({ node, role, material, materialPreset })
|
|
657
|
+
}
|
|
416
658
|
if (targets.length === 0) return
|
|
417
659
|
const state = useScene.getState()
|
|
418
660
|
|
package/src/lib/panel-rows.ts
CHANGED
|
@@ -62,12 +62,33 @@ export type PanelRow = { section?: string } & (
|
|
|
62
62
|
|
|
63
63
|
export const PANEL_MODEL_EXTENSION = 'pascal:editor/panel-model'
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* A control the multi-selection inspector renders for every selected node of
|
|
67
|
+
* a kind at once (derived settings the raw parametrics can't express). `value`
|
|
68
|
+
* null means the selection is mixed: no option shows as active.
|
|
69
|
+
*/
|
|
70
|
+
export type MultiPanelControl = {
|
|
71
|
+
id: string
|
|
72
|
+
kind: 'segmented'
|
|
73
|
+
/** The parametrics group title the control sits in. */
|
|
74
|
+
section: string
|
|
75
|
+
label: string
|
|
76
|
+
options: ReadonlyArray<{ value: string; label: string }>
|
|
77
|
+
value: string | null
|
|
78
|
+
/** Applies to every selected node in one undo step. */
|
|
79
|
+
onChange: (value: string) => void
|
|
80
|
+
}
|
|
81
|
+
|
|
65
82
|
export type NodePanelModel<N extends AnyNode = AnyNode> = {
|
|
66
83
|
rows: (context: {
|
|
67
84
|
node: N
|
|
68
85
|
nodes: Record<AnyNodeId, AnyNode>
|
|
69
86
|
update: (patch: Partial<N>) => void
|
|
70
87
|
}) => PanelRow[]
|
|
88
|
+
multiControls?: (context: {
|
|
89
|
+
selection: N[]
|
|
90
|
+
nodes: Record<AnyNodeId, AnyNode>
|
|
91
|
+
}) => MultiPanelControl[]
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
export function getNodePanelModel(definition: AnyNodeDefinition | undefined) {
|
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
useScene,
|
|
7
7
|
} from '@pascal-app/core'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** A field edit with what its kind's parametrics derive and reconcile from it, as updates. */
|
|
10
|
+
export function parametricNodeUpdates(
|
|
10
11
|
nodeId: AnyNodeId,
|
|
11
12
|
requestedPatch: Partial<AnyNode>,
|
|
12
|
-
):
|
|
13
|
-
const
|
|
14
|
-
const node =
|
|
15
|
-
if (!node) return
|
|
13
|
+
): { id: AnyNodeId; data: Partial<AnyNode> }[] {
|
|
14
|
+
const nodes = useScene.getState().nodes
|
|
15
|
+
const node = nodes[nodeId]
|
|
16
|
+
if (!node) return []
|
|
16
17
|
|
|
17
18
|
const parametrics = nodeRegistry.get(node.type)?.parametrics as
|
|
18
19
|
| ParametricDescriptor<AnyNode>
|
|
@@ -31,7 +32,15 @@ export function commitParametricNodeFields(
|
|
|
31
32
|
]
|
|
32
33
|
if (parametrics?.reconcile) {
|
|
33
34
|
const next = { ...node, ...patch } as AnyNode
|
|
34
|
-
updates.push(...parametrics.reconcile(node, next))
|
|
35
|
+
updates.push(...parametrics.reconcile(node, next, { ...nodes, [nodeId]: next }))
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
return updates
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function commitParametricNodeFields(
|
|
41
|
+
nodeId: AnyNodeId,
|
|
42
|
+
requestedPatch: Partial<AnyNode>,
|
|
43
|
+
): void {
|
|
44
|
+
const updates = parametricNodeUpdates(nodeId, requestedPatch)
|
|
45
|
+
if (updates.length) useScene.getState().updateNodes(updates)
|
|
37
46
|
}
|