@pascal-app/editor 1.0.0-beta.5 → 1.0.1
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/LICENSE +21 -0
- package/bunfig.toml +4 -0
- package/package.json +15 -8
- package/scripts/generate-print-golden-house.ts +89 -0
- package/src/components/editor/bake-exporter.tsx +5 -0
- package/src/components/editor/bake-thumbnail.tsx +4 -1
- package/src/components/editor/camera-dragging-lifecycle.test.ts +41 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +10 -2
- package/src/components/editor/capture-camera-rig.tsx +59 -0
- package/src/components/editor/custom-camera-controls.tsx +20 -11
- package/src/components/editor/editor-layout-mobile.tsx +6 -6
- package/src/components/editor/editor-layout-v2.tsx +9 -2
- package/src/components/editor/export-manager.tsx +175 -33
- package/src/components/editor/first-person/build-collider-world.test.ts +30 -0
- package/src/components/editor/first-person-controls.tsx +180 -12
- package/src/components/editor/floating-action-menu.tsx +55 -51
- package/src/components/editor/floorplan-panel.tsx +99 -15
- package/src/components/editor/grid.tsx +12 -7
- package/src/components/editor/group-actions.ts +34 -50
- package/src/components/editor/group-move-3d.ts +51 -21
- package/src/components/editor/group-transform-shared.test.ts +78 -0
- package/src/components/editor/group-transform-shared.ts +74 -7
- package/src/components/editor/handles/handle-arrow-raycast.test.ts +86 -0
- package/src/components/editor/handles/handle-arrow.tsx +109 -48
- package/src/components/editor/handles/handle-placement.test.ts +111 -0
- package/src/components/editor/handles/handle-placement.ts +69 -0
- package/src/components/editor/handles/linear-resize-drag.ts +78 -0
- package/src/components/editor/handles/resize-snap.test.ts +46 -0
- package/src/components/editor/handles/resize-snap.ts +10 -1
- package/src/components/editor/handles/use-handle-drag.ts +23 -3
- package/src/components/editor/index.tsx +151 -24
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +67 -119
- package/src/components/editor/selection-manager.tsx +153 -42
- package/src/components/editor/snapshot-capture-overlay.tsx +298 -39
- package/src/components/editor/snapshot-capture.test.ts +422 -0
- package/src/components/editor/snapshot-capture.ts +145 -0
- package/src/components/editor/thumbnail-generator.tsx +295 -239
- package/src/components/editor/use-floorplan-background-placement.ts +53 -5
- package/src/components/editor/wall-move-side-handles.tsx +299 -7
- package/src/components/editor-2d/floorplan-group-move.tsx +67 -33
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +3 -1
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +12 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +129 -38
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +14 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +16 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +2 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +52 -3
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +196 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +226 -35
- package/src/components/procedural-items/inspector.tsx +223 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +402 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.ts +371 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +347 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +281 -305
- package/src/components/systems/ceiling/ceiling-system.tsx +3 -1
- package/src/components/systems/roof/roof-edit-system.test.tsx +120 -0
- package/src/components/systems/roof/roof-edit-system.tsx +10 -19
- package/src/components/systems/selection-affordance-manager.tsx +59 -9
- package/src/components/systems/selection-affordance-services.ts +19 -0
- package/src/components/systems/stair/stair-edit-system.tsx +5 -2
- package/src/components/tools/fence/fence-drafting.test.ts +100 -0
- package/src/components/tools/fence/fence-drafting.ts +6 -25
- package/src/components/tools/item/draft-creation.test.ts +12 -0
- package/src/components/tools/item/draft-creation.ts +10 -0
- package/src/components/tools/item/face-host-commit.test.ts +65 -0
- package/src/components/tools/item/face-host-commit.ts +56 -0
- package/src/components/tools/item/face-host-preview.test.ts +77 -0
- package/src/components/tools/item/face-host-preview.ts +82 -0
- package/src/components/tools/item/item-surface-placement.test.ts +175 -0
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +210 -0
- package/src/components/tools/item/move-tool.tsx +4 -3
- package/src/components/tools/item/placement-math.test.ts +11 -1
- package/src/components/tools/item/placement-math.ts +0 -20
- package/src/components/tools/item/placement-strategies.test.ts +331 -4
- package/src/components/tools/item/placement-strategies.ts +304 -171
- package/src/components/tools/item/placement-surface.test.ts +61 -0
- package/src/components/tools/item/placement-surface.ts +24 -0
- package/src/components/tools/item/placement-types.ts +10 -0
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +257 -0
- package/src/components/tools/item/test-face-host.ts +121 -0
- package/src/components/tools/item/use-draft-node.test.tsx +336 -0
- package/src/components/tools/item/use-draft-node.ts +116 -44
- package/src/components/tools/item/use-placement-coordinator.tsx +672 -256
- package/src/components/tools/registered-tool-install-lifecycle.test.tsx +256 -0
- package/src/components/tools/registry/item-surface-child-frame.test.ts +90 -0
- package/src/components/tools/registry/item-surface-move.test.ts +501 -0
- package/src/components/tools/registry/item-surface-move.ts +441 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +257 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +10 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +505 -164
- package/src/components/tools/registry-tool-context.tsx +30 -0
- package/src/components/tools/select/box-select-tool.tsx +3 -1
- package/src/components/tools/select/marquee-footprint.test.ts +53 -0
- package/src/components/tools/select/marquee-footprint.ts +29 -0
- package/src/components/tools/shared/placement-box.tsx +57 -7
- package/src/components/tools/shared/placement-dimension-guides.tsx +121 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +319 -17
- package/src/components/tools/shared/pointer-support-cap.ts +56 -17
- package/src/components/tools/shared/shelf-stickiness.ts +54 -0
- package/src/components/tools/shared/surface-hit.test.ts +56 -0
- package/src/components/tools/shared/surface-hit.ts +23 -0
- package/src/components/tools/shared/surface-rejection.test.tsx +106 -0
- package/src/components/tools/shared/surface-rejection.tsx +29 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +9 -5
- package/src/components/tools/site/terrain-sculpt-tool.tsx +1 -1
- package/src/components/tools/stair/stair-tool.tsx +127 -74
- package/src/components/tools/tool-manager.tsx +25 -8
- package/src/components/tools/wall/wall-drafting.test.ts +320 -2
- package/src/components/tools/wall/wall-drafting.ts +23 -114
- package/src/components/tools/zone/zone-tool.tsx +13 -4
- package/src/components/ui/action-menu/control-modes.tsx +9 -6
- package/src/components/ui/action-menu/index.tsx +11 -1
- package/src/components/ui/command-palette/editor-commands.tsx +63 -13
- package/src/components/ui/command-palette/index.tsx +7 -2
- package/src/components/ui/controls/material-paint-panel.tsx +6 -4
- package/src/components/ui/controls/material-picker.tsx +4 -4
- package/src/components/ui/controls/metric-control.tsx +29 -13
- package/src/components/ui/controls/scene-material-list.tsx +6 -5
- package/src/components/ui/controls/segmented-control.tsx +8 -1
- package/src/components/ui/controls/slider-control.tsx +44 -21
- package/src/components/ui/controls/toggle-control.tsx +18 -5
- package/src/components/ui/controls/tool-options-panel.tsx +126 -0
- package/src/components/ui/floating-level-selector.tsx +103 -10
- package/src/components/ui/helpers/contextual-helper-panel.tsx +9 -0
- package/src/components/ui/helpers/helper-manager.tsx +47 -11
- package/src/components/ui/helpers/item-helper.tsx +1 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +21 -0
- package/src/components/ui/panels/homogeneous-selection.test.ts +80 -0
- package/src/components/ui/panels/homogeneous-selection.ts +47 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +11 -5
- package/src/components/ui/panels/multi-field-value.test.ts +209 -0
- package/src/components/ui/panels/multi-field-value.ts +163 -0
- package/src/components/ui/panels/multi-height-mode.tsx +185 -0
- package/src/components/ui/panels/multi-parametric-inspector.tsx +266 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +48 -37
- package/src/components/ui/panels/node-display.ts +4 -0
- package/src/components/ui/panels/panel-manager.tsx +77 -3
- package/src/components/ui/panels/parametric-field-control.tsx +173 -0
- package/src/components/ui/panels/parametric-field-utils.ts +18 -0
- package/src/components/ui/panels/parametric-inspector.tsx +35 -164
- package/src/components/ui/panels/reference-panel.tsx +34 -4
- package/src/components/ui/primitives/checkbox.tsx +34 -0
- package/src/components/ui/primitives/shortcut-token.tsx +23 -1
- package/src/components/ui/scene-loader.tsx +35 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +502 -31
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +20 -23
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +167 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +8 -1
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +19 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +222 -50
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +2 -7
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +28 -11
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +3 -8
- package/src/components/ui/sidebar/panels/site-panel/tree-node.test.ts +8 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +20 -7
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +224 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +22 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +24 -7
- package/src/components/ui/sidebar/tab-bar.tsx +2 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +17 -2
- package/src/components/ui/snap-target-badge.tsx +2 -0
- package/src/components/viewer/viewer-scene-header.tsx +5 -1
- package/src/components/viewer/viewer-stage.tsx +4 -1
- package/src/components/viewer/viewer-units-panel.tsx +116 -0
- package/src/components/viewer-overlay.tsx +1 -22
- package/src/components/viewer-zone-system.tsx +6 -2
- package/src/components/walkthrough-hud.tsx +19 -10
- package/src/hooks/use-auto-save.test.ts +73 -1
- package/src/hooks/use-auto-save.ts +76 -5
- package/src/hooks/use-ceiling-events.test.ts +144 -0
- package/src/hooks/use-ceiling-events.ts +5 -1
- package/src/hooks/use-drag-action.ts +17 -3
- package/src/hooks/use-grid-events.test.ts +240 -0
- package/src/hooks/use-grid-events.ts +190 -11
- package/src/hooks/use-keyboard.test.ts +244 -0
- package/src/hooks/use-keyboard.ts +132 -63
- package/src/hooks/use-registered-tool-enabled.ts +20 -0
- package/src/hooks/use-save-shortcut.ts +33 -0
- package/src/index.tsx +87 -4
- package/src/lib/active-placement-surface.test.ts +16 -0
- package/src/lib/active-placement-surface.ts +14 -0
- package/src/lib/ceiling-surface-raycast.test.ts +64 -0
- package/src/lib/ceiling-surface-raycast.ts +12 -0
- package/src/lib/contextual-help-extension.ts +17 -0
- package/src/lib/continuation.test.ts +11 -0
- package/src/lib/continuation.ts +8 -1
- package/src/lib/direct-manipulation.test.ts +132 -0
- package/src/lib/direct-manipulation.ts +38 -1
- package/src/lib/editor-api.ts +6 -3
- package/src/lib/elevation-guides.test.ts +28 -1
- package/src/lib/elevation-guides.ts +22 -7
- package/src/lib/export-texture-utils.test.ts +167 -0
- package/src/lib/export-texture-utils.ts +121 -0
- package/src/lib/export-user-data.test.ts +48 -0
- package/src/lib/export-user-data.ts +69 -0
- package/src/lib/floating-menu-scale.test.ts +29 -0
- package/src/lib/floating-menu-scale.ts +14 -0
- package/src/lib/floorplan/apply-alignment.test.ts +47 -2
- package/src/lib/floorplan/apply-alignment.ts +10 -1
- package/src/lib/floorplan/floorplan-export.test.ts +536 -2
- package/src/lib/floorplan/floorplan-export.tsx +120 -19
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +26 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +32 -24
- package/src/lib/floorplan/floorplan-readonly.ts +5 -0
- package/src/lib/floorplan-grid-event-point.test.ts +29 -0
- package/src/lib/floorplan-grid-event-point.ts +13 -0
- package/src/lib/fresh-planar-placement.ts +13 -5
- package/src/lib/glb-export-serialize.test.ts +280 -0
- package/src/lib/glb-export.test.ts +1375 -21
- package/src/lib/glb-export.ts +781 -106
- package/src/lib/grid-event-presentation.ts +16 -0
- package/src/lib/history.test.ts +528 -147
- package/src/lib/history.ts +81 -7
- package/src/lib/host-tree-children.test.ts +27 -0
- package/src/lib/host-tree-children.ts +79 -0
- package/src/lib/interaction/hot-set.test.ts +10 -1
- package/src/lib/interaction/hot-set.ts +3 -1
- package/src/lib/interaction/overlay-policy.test.ts +33 -1
- package/src/lib/interaction/overlay-policy.ts +16 -0
- package/src/lib/interaction/registered-drafting.ts +36 -0
- package/src/lib/interaction/scope.ts +31 -0
- package/src/lib/level-duplication.test.ts +73 -0
- package/src/lib/level-duplication.ts +32 -2
- package/src/lib/level-print-export.test.ts +484 -0
- package/src/lib/level-print-export.ts +526 -0
- package/src/lib/linear-display.test.ts +59 -0
- package/src/lib/linear-display.ts +39 -0
- package/src/lib/local-guide-image.test.ts +24 -0
- package/src/lib/local-guide-image.ts +34 -1
- package/src/lib/local-project-presentation-persistence.test.ts +302 -0
- package/src/lib/local-project-presentation-persistence.ts +335 -0
- package/src/lib/material-paint.ts +2 -0
- package/src/lib/model-export.ts +29 -0
- package/src/lib/node-action-movement.ts +21 -0
- package/src/lib/paint-preview-owner.ts +71 -0
- package/src/lib/planar-cursor-placement.test.ts +178 -1
- package/src/lib/planar-cursor-placement.ts +61 -5
- package/src/lib/plugin-panels.test.ts +92 -1
- package/src/lib/plugin-panels.ts +48 -0
- package/src/lib/portable-export.test.ts +455 -0
- package/src/lib/portable-export.ts +1042 -0
- package/src/lib/print-3mf.test.ts +86 -0
- package/src/lib/print-3mf.ts +161 -0
- package/src/lib/print-content-scope.test.ts +107 -0
- package/src/lib/print-content-scope.ts +90 -0
- package/src/lib/print-export.test.ts +294 -0
- package/src/lib/print-export.ts +735 -0
- package/src/lib/print-feature-thickness.test.ts +147 -0
- package/src/lib/print-feature-thickness.ts +180 -0
- package/src/lib/print-golden-house.test-fixture.ts +352 -0
- package/src/lib/print-golden-house.test.ts +249 -0
- package/src/lib/print-roof-solids.test.ts +133 -0
- package/src/lib/print-roof-solids.ts +630 -0
- package/src/lib/print-shell-compiler-baseline.test.ts +419 -0
- package/src/lib/print-shell-compiler-baseline.ts +225 -0
- package/src/lib/print-shell-compiler-manifold-core.ts +379 -0
- package/src/lib/print-shell-compiler-manifold-worker.ts +200 -0
- package/src/lib/print-shell-compiler-manifold.worker.ts +20 -0
- package/src/lib/print-shell-compiler-mesh-data.ts +32 -0
- package/src/lib/print-shell-compiler-protocol.ts +38 -0
- package/src/lib/print-shell-compiler.ts +273 -0
- package/src/lib/print-wall-solids.test.ts +194 -0
- package/src/lib/print-wall-solids.ts +328 -0
- package/src/lib/rigid-plan-svg-transform.test.ts +35 -0
- package/src/lib/rigid-plan-svg-transform.ts +24 -0
- package/src/lib/scene-clipboard.ts +1 -1
- package/src/lib/scene.ts +22 -8
- package/src/lib/selection-routing.test.ts +105 -1
- package/src/lib/selection-routing.ts +19 -0
- package/src/lib/send-to-app.test.ts +108 -0
- package/src/lib/send-to-app.ts +121 -0
- package/src/lib/sfx/movement-tick.ts +37 -0
- package/src/lib/sfx-bus.ts +4 -0
- package/src/lib/sfx-player.ts +8 -0
- package/src/lib/snapping-mode.test.ts +32 -4
- package/src/lib/snapping-mode.ts +11 -7
- package/src/lib/surface-attachment.ts +77 -0
- package/src/lib/units.ts +254 -0
- package/src/lib/usdz-export.ts +65 -0
- package/src/lib/use-linear-display.ts +8 -32
- package/src/lib/walkthrough-pointer-lock.ts +47 -0
- package/src/store/live-draft-preview-stores.test.ts +3 -0
- package/src/store/terrain-sculpt-mode.test.ts +3 -6
- package/src/store/tool-mode.test.ts +87 -0
- package/src/store/use-camera-hint-focus.ts +48 -0
- package/src/store/use-editor.tsx +299 -188
- package/src/store/use-floorplan-draft-preview.ts +9 -0
- package/src/store/use-handle-group.ts +4 -0
- package/src/store/use-interaction-scope.test.ts +23 -1
- package/src/store/use-interaction-scope.ts +63 -2
- package/src/store/use-placement-preview.ts +56 -3
- package/src/store/use-send-to-app.ts +21 -0
- package/src/components/tools/roof/roof-tool.tsx +0 -656
- package/src/components/ui/helpers/roof-helper.tsx +0 -14
package/src/index.tsx
CHANGED
|
@@ -56,6 +56,7 @@ export {
|
|
|
56
56
|
formatMeasurement,
|
|
57
57
|
MeasurementPill,
|
|
58
58
|
} from './components/editor/measurement-pill'
|
|
59
|
+
export { NodeActionMenu } from './components/editor/node-action-menu'
|
|
59
60
|
// In-world arrow handle primitives (chevron geometry, invisible hit area,
|
|
60
61
|
// shared material, palette + scale constants). Re-exported so kind-owned
|
|
61
62
|
// 3D selection affordances in `@pascal-app/nodes` (duct side-move / height /
|
|
@@ -98,6 +99,11 @@ export {
|
|
|
98
99
|
buildSvgArrowHeadPoints,
|
|
99
100
|
getArcPlanPoint,
|
|
100
101
|
} from './components/editor-2d/svg-paths'
|
|
102
|
+
export type {
|
|
103
|
+
SelectionAffordanceHistoryApi,
|
|
104
|
+
SelectionAffordanceInteractionApi,
|
|
105
|
+
SelectionAffordanceProps,
|
|
106
|
+
} from './components/systems/selection-affordance-services'
|
|
101
107
|
// Phase 5 Stage D transitional exports — pure drafting / angle helpers
|
|
102
108
|
// consumed by kind-owned drag actions in @pascal-app/nodes. Stage F
|
|
103
109
|
// cleanup moves these into @pascal-app/nodes (fence/drafting.ts +
|
|
@@ -113,7 +119,6 @@ export { MoveTool } from './components/tools/item/move-tool'
|
|
|
113
119
|
// `@pascal-app/nodes` (wall curve sagitta snap, door / window placement,
|
|
114
120
|
// item drop) so kinds don't reach into editor internals.
|
|
115
121
|
export {
|
|
116
|
-
calculateCursorRotation,
|
|
117
122
|
calculateItemRotation,
|
|
118
123
|
getSideFromNormal,
|
|
119
124
|
isValidWallSideFace,
|
|
@@ -132,6 +137,8 @@ export {
|
|
|
132
137
|
type PlacementCoordinatorConfig,
|
|
133
138
|
usePlacementCoordinator,
|
|
134
139
|
} from './components/tools/item/use-placement-coordinator'
|
|
140
|
+
export { MoveRegistryNodeTool } from './components/tools/registry/move-registry-node-tool'
|
|
141
|
+
export { useRegistryToolContext } from './components/tools/registry-tool-context'
|
|
135
142
|
export { CursorSphere } from './components/tools/shared/cursor-sphere'
|
|
136
143
|
export { DragBoundingBox } from './components/tools/shared/drag-bounding-box'
|
|
137
144
|
export { getFloorStackPreviewPosition } from './components/tools/shared/floor-stack-preview'
|
|
@@ -144,6 +151,7 @@ export {
|
|
|
144
151
|
resolveLevelConstructionPlane,
|
|
145
152
|
} from './components/tools/shared/horizontal-construction-plane'
|
|
146
153
|
export { PlacementBox } from './components/tools/shared/placement-box'
|
|
154
|
+
export { PlacementDimensionGuides } from './components/tools/shared/placement-dimension-guides'
|
|
147
155
|
// Pointer-decided support surface (deck top vs floor underneath) — the
|
|
148
156
|
// draw tools (wall / fence) ride their grid plane and commit cap on it.
|
|
149
157
|
export {
|
|
@@ -234,6 +242,7 @@ export { SegmentedControl } from './components/ui/controls/segmented-control'
|
|
|
234
242
|
export { SliderControl } from './components/ui/controls/slider-control'
|
|
235
243
|
export { TerrainSculptPanel } from './components/ui/controls/terrain-sculpt-panel'
|
|
236
244
|
export { ToggleControl } from './components/ui/controls/toggle-control'
|
|
245
|
+
export { ToolOptionsPanel } from './components/ui/controls/tool-options-panel'
|
|
237
246
|
export { FloatingLevelSelector } from './components/ui/floating-level-selector'
|
|
238
247
|
export { CATALOG_ITEMS } from './components/ui/item-catalog/catalog-items'
|
|
239
248
|
// Item collections UI — used by the kind-owned ItemPanel in nodes/.
|
|
@@ -254,6 +263,10 @@ export {
|
|
|
254
263
|
DropdownMenuSubTrigger,
|
|
255
264
|
DropdownMenuTrigger,
|
|
256
265
|
} from './components/ui/primitives/dropdown-menu'
|
|
266
|
+
export {
|
|
267
|
+
ShortcutToken,
|
|
268
|
+
shortcutDisplayValue,
|
|
269
|
+
} from './components/ui/primitives/shortcut-token'
|
|
257
270
|
export { useSidebarStore } from './components/ui/primitives/sidebar'
|
|
258
271
|
export { Slider } from './components/ui/primitives/slider'
|
|
259
272
|
export { SceneLoader } from './components/ui/scene-loader'
|
|
@@ -305,6 +318,7 @@ export {
|
|
|
305
318
|
ViewerStageSwitcher,
|
|
306
319
|
type ViewerStageSwitcherProps,
|
|
307
320
|
} from './components/viewer/viewer-stage-switcher'
|
|
321
|
+
export { ViewerUnitsPanel } from './components/viewer/viewer-units-panel'
|
|
308
322
|
export {
|
|
309
323
|
WalkthroughHud,
|
|
310
324
|
type WalkthroughHudProps,
|
|
@@ -333,6 +347,12 @@ export {
|
|
|
333
347
|
resolveCeilingPlanPointSnap,
|
|
334
348
|
} from './lib/ceiling-plan-snap'
|
|
335
349
|
export { EDITOR_LAYER } from './lib/constants'
|
|
350
|
+
export type { ContextualShortcutHint } from './lib/contextual-help'
|
|
351
|
+
export {
|
|
352
|
+
CONTEXTUAL_HELP_NODE_EXTENSION_KEY,
|
|
353
|
+
type ContextualHelpNodeExtension,
|
|
354
|
+
getContextualHelpNodeExtension,
|
|
355
|
+
} from './lib/contextual-help-extension'
|
|
336
356
|
// Helper libs used by the kind-owned roof / stair / elevator panels.
|
|
337
357
|
export {
|
|
338
358
|
CONTINUATION_PROFILES,
|
|
@@ -341,6 +361,7 @@ export {
|
|
|
341
361
|
continuationContextOf,
|
|
342
362
|
nextContinuation,
|
|
343
363
|
} from './lib/continuation'
|
|
364
|
+
export { createEditorApi } from './lib/editor-api'
|
|
344
365
|
export {
|
|
345
366
|
clearStructuralElevationGuide,
|
|
346
367
|
collectElevationSnapTargets,
|
|
@@ -348,6 +369,7 @@ export {
|
|
|
348
369
|
type ElevationGuideSource,
|
|
349
370
|
type ElevationSnapMatch,
|
|
350
371
|
type ElevationSnapTarget,
|
|
372
|
+
publishResolvedElevationGuide,
|
|
351
373
|
publishStructuralElevationGuide,
|
|
352
374
|
resolveElevationSnapMatch,
|
|
353
375
|
resolveStructuralElevationSnap,
|
|
@@ -358,6 +380,8 @@ export {
|
|
|
358
380
|
resolveElevatorSupportLevelId,
|
|
359
381
|
resolveElevatorSupportY,
|
|
360
382
|
} from './lib/elevator-support'
|
|
383
|
+
export type { ExportTextureUtils } from './lib/export-texture-utils'
|
|
384
|
+
export { getFloatingMenuScale } from './lib/floating-menu-scale'
|
|
361
385
|
// Floor-plan stair helpers — the cumulative-transform walk
|
|
362
386
|
// (`computeFloorplanStairSegmentTransforms`) and the rich segment-entry
|
|
363
387
|
// builder (`buildFloorplanStairEntry`) used by the kind-owned stair
|
|
@@ -382,6 +406,10 @@ export type {
|
|
|
382
406
|
FloorplanAnnotationCategory,
|
|
383
407
|
FloorplanAnnotationVisibility,
|
|
384
408
|
} from './lib/floorplan/annotation-visibility'
|
|
409
|
+
export {
|
|
410
|
+
exportFloorplanPdf,
|
|
411
|
+
type FloorplanExportScope,
|
|
412
|
+
} from './lib/floorplan/floorplan-export'
|
|
385
413
|
export {
|
|
386
414
|
createFloorplanContextExtensions,
|
|
387
415
|
FLOORPLAN_CONTEXT_EXTENSION_KEY,
|
|
@@ -407,8 +435,16 @@ export {
|
|
|
407
435
|
type FloorplanMode,
|
|
408
436
|
isFloorplanToolAvailableInMode,
|
|
409
437
|
} from './lib/floorplan/floorplan-mode'
|
|
410
|
-
export {
|
|
411
|
-
|
|
438
|
+
export {
|
|
439
|
+
commitFreshPlacementSubtree,
|
|
440
|
+
createFreshPlacementSubtree,
|
|
441
|
+
} from './lib/fresh-planar-placement'
|
|
442
|
+
export { exportSceneToGlb, type GlbExportOptions } from './lib/glb-export'
|
|
443
|
+
export {
|
|
444
|
+
type EditorGridEvent,
|
|
445
|
+
type GridEventScreenProjection,
|
|
446
|
+
getGridEventScreenProjection,
|
|
447
|
+
} from './lib/grid-event-presentation'
|
|
412
448
|
export {
|
|
413
449
|
getHistoryCommandState,
|
|
414
450
|
type HistoryCommandDelegate,
|
|
@@ -419,15 +455,27 @@ export {
|
|
|
419
455
|
runUndo,
|
|
420
456
|
subscribeHistoryCommandState,
|
|
421
457
|
} from './lib/history'
|
|
458
|
+
export {
|
|
459
|
+
type EditorHostTreeChildren,
|
|
460
|
+
type EditorHostTreeChildrenProps,
|
|
461
|
+
editorHostTreeChildrenRegistry,
|
|
462
|
+
registerEditorHostTreeChildren,
|
|
463
|
+
} from './lib/host-tree-children'
|
|
464
|
+
export {
|
|
465
|
+
DRAFTING_SURFACE_EXTENSION_KEY,
|
|
466
|
+
type DraftingSurfaceExtension,
|
|
467
|
+
} from './lib/interaction/registered-drafting'
|
|
422
468
|
export {
|
|
423
469
|
boundaryReshapeScope,
|
|
424
470
|
curveReshapeScope,
|
|
425
471
|
endpointReshapeScope,
|
|
426
472
|
holeEditScope,
|
|
473
|
+
meshEditScope,
|
|
427
474
|
movingNodeOf,
|
|
428
475
|
scopeNodeId,
|
|
429
476
|
} from './lib/interaction/scope'
|
|
430
477
|
export {
|
|
478
|
+
type ActivePaintMaterial,
|
|
431
479
|
buildResetSurfaceMaterialUpdates,
|
|
432
480
|
buildRoofSurfaceMaterialPatch,
|
|
433
481
|
buildSingleSurfaceMaterialPatch,
|
|
@@ -446,6 +494,14 @@ export {
|
|
|
446
494
|
measurementPolygonLabelAnchor,
|
|
447
495
|
triangulateMeasurementPolygon,
|
|
448
496
|
} from './lib/measurement-label'
|
|
497
|
+
export {
|
|
498
|
+
type LingoUnitSpec,
|
|
499
|
+
lingoUnitSpec,
|
|
500
|
+
type MeasurementHintOptions,
|
|
501
|
+
measurementHint,
|
|
502
|
+
type ParseMeasurementOptions,
|
|
503
|
+
parseMeasurement,
|
|
504
|
+
} from './lib/measurement-parser'
|
|
449
505
|
export {
|
|
450
506
|
buildMeasurementAngleArcPoints,
|
|
451
507
|
cubicMetersToVolumeUnit,
|
|
@@ -467,6 +523,12 @@ export {
|
|
|
467
523
|
metersToLinearUnit,
|
|
468
524
|
squareMetersToAreaUnit,
|
|
469
525
|
} from './lib/measurements'
|
|
526
|
+
export type {
|
|
527
|
+
ModelExport,
|
|
528
|
+
ModelExportArtifact,
|
|
529
|
+
ModelExportFormat,
|
|
530
|
+
ModelExportOptions,
|
|
531
|
+
} from './lib/model-export'
|
|
470
532
|
export { consumePlacementDragRelease } from './lib/placement-drag-release'
|
|
471
533
|
export {
|
|
472
534
|
addFreshPlacementMetadata,
|
|
@@ -485,6 +547,8 @@ export {
|
|
|
485
547
|
editorHostPanelRegistry,
|
|
486
548
|
registerEditorHostPanel,
|
|
487
549
|
} from './lib/plugin-panels'
|
|
550
|
+
export { configureManifoldRuntime } from './lib/print-shell-compiler-manifold-worker'
|
|
551
|
+
export type { ManifoldRuntimeOptions } from './lib/print-shell-compiler-protocol'
|
|
488
552
|
export {
|
|
489
553
|
createQuickMeasurementPointerScheduler,
|
|
490
554
|
quickMeasurementContext,
|
|
@@ -510,6 +574,13 @@ export {
|
|
|
510
574
|
type SlabPlanSnapInput,
|
|
511
575
|
type SlabPlanSnapResult,
|
|
512
576
|
} from './lib/slab-plan-snap'
|
|
577
|
+
export {
|
|
578
|
+
getSnappingModeLabel,
|
|
579
|
+
resolveSnapFlags,
|
|
580
|
+
type SnapContext,
|
|
581
|
+
type SnapFlags,
|
|
582
|
+
type SnappingMode,
|
|
583
|
+
} from './lib/snapping-mode'
|
|
513
584
|
export { duplicateStairSubtree } from './lib/stair-duplication'
|
|
514
585
|
export {
|
|
515
586
|
getBuildingLevelsForLevel,
|
|
@@ -533,6 +604,7 @@ export {
|
|
|
533
604
|
resolveFlattenTarget,
|
|
534
605
|
sculptFieldForSite,
|
|
535
606
|
} from './lib/terrain-sculpt'
|
|
607
|
+
export { exportSceneToUsdz, type UsdzExportOptions } from './lib/usdz-export'
|
|
536
608
|
// `cn` (twMerge + clsx) — used by kind-owned panels in `@pascal-app/
|
|
537
609
|
// nodes` so they don't need their own copy / their own tailwind-merge
|
|
538
610
|
// dependency.
|
|
@@ -548,6 +620,7 @@ export {
|
|
|
548
620
|
export { subscribeCameraPose } from './store/camera-pose-store'
|
|
549
621
|
export { default as useAlignmentGuides } from './store/use-alignment-guides'
|
|
550
622
|
export { default as useAudio } from './store/use-audio'
|
|
623
|
+
export { type CameraHintAction, useCameraHintFocus } from './store/use-camera-hint-focus'
|
|
551
624
|
export { type CommandAction, useCommandRegistry } from './store/use-command-registry'
|
|
552
625
|
export {
|
|
553
626
|
DRAWING_TYPE_OPTIONS,
|
|
@@ -556,17 +629,24 @@ export {
|
|
|
556
629
|
export type {
|
|
557
630
|
CaptureMode,
|
|
558
631
|
FloorplanSelectionTool,
|
|
632
|
+
Mode,
|
|
559
633
|
SnapshotCropMode,
|
|
560
634
|
SnapshotStandardAspect,
|
|
561
635
|
SplitOrientation,
|
|
636
|
+
StructureTool,
|
|
562
637
|
Tool,
|
|
563
638
|
ToolDefaults,
|
|
639
|
+
ToolMode,
|
|
564
640
|
ViewMode,
|
|
565
641
|
WorkspaceMode,
|
|
566
642
|
} from './store/use-editor'
|
|
567
643
|
export {
|
|
644
|
+
armMaterialPaint,
|
|
645
|
+
armToolMode,
|
|
568
646
|
default as useEditor,
|
|
569
647
|
getActiveContinuationContext,
|
|
648
|
+
getActiveSnapContext,
|
|
649
|
+
getActiveSnappingMode,
|
|
570
650
|
getContinuation,
|
|
571
651
|
isAlignmentGuideActive,
|
|
572
652
|
isAngleSnapActive,
|
|
@@ -623,7 +703,10 @@ export {
|
|
|
623
703
|
type PathDraftPoint,
|
|
624
704
|
usePathDraftPreview,
|
|
625
705
|
} from './store/use-path-draft-preview'
|
|
626
|
-
export {
|
|
706
|
+
export {
|
|
707
|
+
default as usePlacementPreview,
|
|
708
|
+
type PlacementPreviewDimension,
|
|
709
|
+
} from './store/use-placement-preview'
|
|
627
710
|
export {
|
|
628
711
|
activateQuickMeasurementHudSource,
|
|
629
712
|
clearQuickMeasurementHudSource,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
clearPlacementSurface,
|
|
5
5
|
getPlacementSurface,
|
|
6
6
|
publishPlacementSurface,
|
|
7
|
+
usesOrientedPlacementPlane,
|
|
7
8
|
} from './active-placement-surface'
|
|
8
9
|
|
|
9
10
|
describe('active placement surface', () => {
|
|
@@ -21,4 +22,19 @@ describe('active placement surface', () => {
|
|
|
21
22
|
|
|
22
23
|
expect(getPlacementSurface()?.projection).toBe('surface')
|
|
23
24
|
})
|
|
25
|
+
|
|
26
|
+
test('copies a stable lattice anchor when one is provided', () => {
|
|
27
|
+
publishPlacementSurface(
|
|
28
|
+
new Vector3(4, 2, 3),
|
|
29
|
+
new Vector3(0, 0, 1),
|
|
30
|
+
'surface',
|
|
31
|
+
new Vector3(1, 2, 3),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
expect(getPlacementSurface()?.anchor?.toArray()).toEqual([1, 2, 3])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('orients the grid to a sloped placement surface', () => {
|
|
38
|
+
expect(usesOrientedPlacementPlane(new Vector3(0, 0.6, 0.8))).toBe(true)
|
|
39
|
+
})
|
|
24
40
|
})
|
|
@@ -11,12 +11,15 @@ import { Vector3 } from 'three'
|
|
|
11
11
|
// readers must consume them within the same frame.
|
|
12
12
|
export type PlacementSurface = {
|
|
13
13
|
point: Vector3
|
|
14
|
+
/** Optional stable origin for the construction lattice. */
|
|
15
|
+
anchor?: Vector3
|
|
14
16
|
normal: Vector3
|
|
15
17
|
projection: 'surface' | 'fixed-plane'
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const surface: PlacementSurface = {
|
|
19
21
|
point: new Vector3(),
|
|
22
|
+
anchor: new Vector3(),
|
|
20
23
|
normal: new Vector3(0, 1, 0),
|
|
21
24
|
projection: 'surface',
|
|
22
25
|
}
|
|
@@ -26,8 +29,15 @@ export function publishPlacementSurface(
|
|
|
26
29
|
point: Vector3,
|
|
27
30
|
normal: Vector3,
|
|
28
31
|
projection: PlacementSurface['projection'] = 'surface',
|
|
32
|
+
anchor?: Vector3,
|
|
29
33
|
): void {
|
|
30
34
|
surface.point.copy(point)
|
|
35
|
+
if (anchor) {
|
|
36
|
+
surface.anchor ??= new Vector3()
|
|
37
|
+
surface.anchor.copy(anchor)
|
|
38
|
+
} else {
|
|
39
|
+
surface.anchor = undefined
|
|
40
|
+
}
|
|
31
41
|
surface.normal.copy(normal)
|
|
32
42
|
surface.projection = projection
|
|
33
43
|
active = true
|
|
@@ -40,3 +50,7 @@ export function clearPlacementSurface(): void {
|
|
|
40
50
|
export function getPlacementSurface(): PlacementSurface | null {
|
|
41
51
|
return active ? surface : null
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
export function usesOrientedPlacementPlane(normal: Vector3): boolean {
|
|
55
|
+
return Math.abs(normal.y) < 0.95
|
|
56
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
BackSide,
|
|
4
|
+
Mesh,
|
|
5
|
+
MeshBasicMaterial,
|
|
6
|
+
Path,
|
|
7
|
+
Raycaster,
|
|
8
|
+
Shape,
|
|
9
|
+
ShapeGeometry,
|
|
10
|
+
Vector3,
|
|
11
|
+
} from 'three'
|
|
12
|
+
import { raycastCeilingUnderside } from './ceiling-surface-raycast'
|
|
13
|
+
|
|
14
|
+
function ceilingMesh() {
|
|
15
|
+
const shape = new Shape()
|
|
16
|
+
shape.moveTo(-2, -2)
|
|
17
|
+
shape.lineTo(2, -2)
|
|
18
|
+
shape.lineTo(2, 2)
|
|
19
|
+
shape.lineTo(-2, 2)
|
|
20
|
+
shape.closePath()
|
|
21
|
+
const hole = new Path()
|
|
22
|
+
hole.moveTo(-0.5, -0.5)
|
|
23
|
+
hole.lineTo(-0.5, 0.5)
|
|
24
|
+
hole.lineTo(0.5, 0.5)
|
|
25
|
+
hole.lineTo(0.5, -0.5)
|
|
26
|
+
hole.closePath()
|
|
27
|
+
shape.holes.push(hole)
|
|
28
|
+
const geometry = new ShapeGeometry(shape).rotateX(-Math.PI / 2)
|
|
29
|
+
const mesh = new Mesh(geometry, new MeshBasicMaterial({ side: BackSide }))
|
|
30
|
+
mesh.position.y = 3
|
|
31
|
+
mesh.updateMatrixWorld(true)
|
|
32
|
+
return mesh
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('ceiling drawing surfaces', () => {
|
|
36
|
+
test('lets drawing pass through the transparent top while picking the underside', () => {
|
|
37
|
+
const ceiling = ceilingMesh()
|
|
38
|
+
for (const side of [-1, 1]) {
|
|
39
|
+
const ray = new Raycaster(new Vector3(1, 3 + side * 2, 0), new Vector3(0, -side, 0))
|
|
40
|
+
const hit = raycastCeilingUnderside(ray, ceiling)[0]
|
|
41
|
+
if (side > 0) {
|
|
42
|
+
expect(hit).toBeUndefined()
|
|
43
|
+
} else {
|
|
44
|
+
expect(hit?.point.y).toBeCloseTo(3)
|
|
45
|
+
expect(hit?.object).toBe(ceiling)
|
|
46
|
+
}
|
|
47
|
+
expect(ceiling.material.side).toBe(BackSide)
|
|
48
|
+
}
|
|
49
|
+
ceiling.geometry.dispose()
|
|
50
|
+
ceiling.material.dispose()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('respects ceiling holes and the polygon boundary from either side', () => {
|
|
54
|
+
const ceiling = ceilingMesh()
|
|
55
|
+
for (const side of [-1, 1]) {
|
|
56
|
+
for (const x of [0, 3]) {
|
|
57
|
+
const ray = new Raycaster(new Vector3(x, 3 + side * 2, 0), new Vector3(0, -side, 0))
|
|
58
|
+
expect(raycastCeilingUnderside(ray, ceiling)).toHaveLength(0)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
ceiling.geometry.dispose()
|
|
62
|
+
ceiling.material.dispose()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BackSide, Mesh, MeshBasicMaterial, type Object3D, type Raycaster } from 'three'
|
|
2
|
+
|
|
3
|
+
const pickingMaterial = new MeshBasicMaterial({ side: BackSide })
|
|
4
|
+
|
|
5
|
+
export function raycastCeilingUnderside(raycaster: Raycaster, ceiling: Object3D) {
|
|
6
|
+
if (!(ceiling instanceof Mesh)) return []
|
|
7
|
+
// Ignore the transparent top and its grid overlay so drawing can reach surfaces below.
|
|
8
|
+
const proxy = new Mesh(ceiling.geometry, pickingMaterial)
|
|
9
|
+
proxy.matrixWorld.copy(ceiling.matrixWorld)
|
|
10
|
+
proxy.layers.mask = ceiling.layers.mask
|
|
11
|
+
return raycaster.intersectObject(proxy, false).map((hit) => ({ ...hit, object: ceiling }))
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NodeDefinition } from '@pascal-app/core'
|
|
2
|
+
import type { ContextualShortcutHint } from './contextual-help'
|
|
3
|
+
|
|
4
|
+
export const CONTEXTUAL_HELP_NODE_EXTENSION_KEY = 'pascal:editor/contextual-help'
|
|
5
|
+
|
|
6
|
+
export type ContextualHelpNodeExtension = {
|
|
7
|
+
subscribe: (onChange: () => void) => () => void
|
|
8
|
+
getHints: (nodeId: string) => ContextualShortcutHint[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getContextualHelpNodeExtension(
|
|
12
|
+
definition: NodeDefinition<any> | undefined,
|
|
13
|
+
): ContextualHelpNodeExtension | undefined {
|
|
14
|
+
return definition?.extensions?.[CONTEXTUAL_HELP_NODE_EXTENSION_KEY] as
|
|
15
|
+
| ContextualHelpNodeExtension
|
|
16
|
+
| undefined
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { CONTINUATION_PROFILES, continuationContextOf, nextContinuation } from './continuation'
|
|
3
|
+
|
|
4
|
+
describe('canopy continuation', () => {
|
|
5
|
+
test('maps the canopy tool to its own single and continuous profile', () => {
|
|
6
|
+
expect(continuationContextOf('lean-to-extension')).toBe('canopy')
|
|
7
|
+
expect(CONTINUATION_PROFILES.canopy.default).toBe('single')
|
|
8
|
+
expect(nextContinuation('canopy', 'single')).toBe('continuous')
|
|
9
|
+
expect(nextContinuation('canopy', 'continuous')).toBe('single')
|
|
10
|
+
})
|
|
11
|
+
})
|
package/src/lib/continuation.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ContinuationContext = 'wall' | 'fence' | 'point' | 'cabinet'
|
|
1
|
+
export type ContinuationContext = 'wall' | 'fence' | 'point' | 'cabinet' | 'canopy'
|
|
2
2
|
export type ContinuationMode = string
|
|
3
3
|
|
|
4
4
|
export const CONTINUATION_PROFILES: Record<
|
|
@@ -42,6 +42,12 @@ export const CONTINUATION_PROFILES: Record<
|
|
|
42
42
|
labels: { single: 'Single cabinet', continuous: 'Continuous run' },
|
|
43
43
|
icons: { single: 'lucide:minus', continuous: 'lucide:waypoints' },
|
|
44
44
|
},
|
|
45
|
+
canopy: {
|
|
46
|
+
options: ['single', 'continuous'],
|
|
47
|
+
default: 'single',
|
|
48
|
+
labels: { single: 'Single canopy', continuous: 'Continuous canopy' },
|
|
49
|
+
icons: { single: 'lucide:minus', continuous: 'lucide:waypoints' },
|
|
50
|
+
},
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
const POINT_KINDS = new Set(['item', 'door', 'window', 'shelf', 'column'])
|
|
@@ -60,5 +66,6 @@ export function continuationContextOf(kind: string): ContinuationContext | null
|
|
|
60
66
|
if (kind === 'wall') return 'wall'
|
|
61
67
|
if (kind === 'fence') return 'fence'
|
|
62
68
|
if (kind === 'cabinet') return 'cabinet'
|
|
69
|
+
if (kind === 'lean-to-extension') return 'canopy'
|
|
63
70
|
return POINT_KINDS.has(kind) ? 'point' : null
|
|
64
71
|
}
|
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
import { z } from 'zod'
|
|
10
10
|
import {
|
|
11
11
|
canDirectMoveNode,
|
|
12
|
+
EDITOR_HANDLE_HIT_AREA_USER_DATA_KEY,
|
|
13
|
+
pointerEventHitsEditorHandle,
|
|
12
14
|
resolveDirectManipulationNode,
|
|
13
15
|
resolveDirectRotationDragDelta,
|
|
14
16
|
resolveMoveActionNode,
|
|
17
|
+
shouldStartDirectMoveDrag,
|
|
15
18
|
snapDirectRotationDelta,
|
|
16
19
|
} from './direct-manipulation'
|
|
17
20
|
|
|
@@ -116,6 +119,91 @@ describe('canDirectMoveNode', () => {
|
|
|
116
119
|
})
|
|
117
120
|
})
|
|
118
121
|
|
|
122
|
+
describe('shouldStartDirectMoveDrag', () => {
|
|
123
|
+
test('arms a plain drag for a kind that opts into direct dragging', () => {
|
|
124
|
+
expect(
|
|
125
|
+
shouldStartDirectMoveDrag({
|
|
126
|
+
allowPlainDrag: true,
|
|
127
|
+
commandModifier: false,
|
|
128
|
+
handleOwnsPointer: false,
|
|
129
|
+
nodeId: 'cabinet_existing',
|
|
130
|
+
selectedIds: [],
|
|
131
|
+
}),
|
|
132
|
+
).toBe(true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('keeps modifier dragging limited to the sole selected node', () => {
|
|
136
|
+
expect(
|
|
137
|
+
shouldStartDirectMoveDrag({
|
|
138
|
+
allowPlainDrag: false,
|
|
139
|
+
commandModifier: true,
|
|
140
|
+
handleOwnsPointer: false,
|
|
141
|
+
nodeId: 'item_selected',
|
|
142
|
+
selectedIds: ['item_selected'],
|
|
143
|
+
}),
|
|
144
|
+
).toBe(true)
|
|
145
|
+
expect(
|
|
146
|
+
shouldStartDirectMoveDrag({
|
|
147
|
+
allowPlainDrag: false,
|
|
148
|
+
commandModifier: true,
|
|
149
|
+
handleOwnsPointer: false,
|
|
150
|
+
nodeId: 'item_other',
|
|
151
|
+
selectedIds: ['item_selected'],
|
|
152
|
+
}),
|
|
153
|
+
).toBe(false)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('does not arm body dragging when a resize handle owns the pointer', () => {
|
|
157
|
+
expect(
|
|
158
|
+
shouldStartDirectMoveDrag({
|
|
159
|
+
allowPlainDrag: true,
|
|
160
|
+
commandModifier: false,
|
|
161
|
+
handleOwnsPointer: true,
|
|
162
|
+
nodeId: 'cabinet_selected',
|
|
163
|
+
selectedIds: ['cabinet_selected'],
|
|
164
|
+
}),
|
|
165
|
+
).toBe(false)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('pointerEventHitsEditorHandle', () => {
|
|
170
|
+
test('keeps a visible resize handle from falling through to a nearer cabinet body', () => {
|
|
171
|
+
expect(
|
|
172
|
+
pointerEventHitsEditorHandle({
|
|
173
|
+
intersections: [
|
|
174
|
+
{ object: { userData: {} } },
|
|
175
|
+
{
|
|
176
|
+
object: {
|
|
177
|
+
userData: { [EDITOR_HANDLE_HIT_AREA_USER_DATA_KEY]: true },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
}),
|
|
182
|
+
).toBe(true)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('recognises a handle when it is the nearest R3F intersection', () => {
|
|
186
|
+
expect(
|
|
187
|
+
pointerEventHitsEditorHandle({
|
|
188
|
+
intersections: [
|
|
189
|
+
{
|
|
190
|
+
object: {
|
|
191
|
+
userData: { [EDITOR_HANDLE_HIT_AREA_USER_DATA_KEY]: true },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{ object: { userData: {} } },
|
|
195
|
+
],
|
|
196
|
+
}),
|
|
197
|
+
).toBe(true)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('does not claim ordinary scene intersections', () => {
|
|
201
|
+
expect(pointerEventHitsEditorHandle({ intersections: [{ object: { userData: {} } }] })).toBe(
|
|
202
|
+
false,
|
|
203
|
+
)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
119
207
|
describe('resolveDirectManipulationNode', () => {
|
|
120
208
|
test('routes proxied members to their assembly for direct transforms', () => {
|
|
121
209
|
const group = {
|
|
@@ -272,4 +360,48 @@ describe('resolveMoveActionNode', () => {
|
|
|
272
360
|
}),
|
|
273
361
|
).toBe(child)
|
|
274
362
|
})
|
|
363
|
+
|
|
364
|
+
test('routes a parent-frame child move to a rotatable assembly parent', () => {
|
|
365
|
+
const parentKind = 'move-action-rotatable-parent-kind-test'
|
|
366
|
+
const childKind = 'move-action-rotatable-child-kind-test'
|
|
367
|
+
registerTestDefinition(parentKind, {
|
|
368
|
+
capabilities: { rotatable: { axes: ['y'], snapAngles: [Math.PI / 4] } },
|
|
369
|
+
})
|
|
370
|
+
registerTestDefinition(childKind, {
|
|
371
|
+
capabilities: {
|
|
372
|
+
movable: {
|
|
373
|
+
axes: ['x', 'z'],
|
|
374
|
+
gridSnap: true,
|
|
375
|
+
parentFrame: {
|
|
376
|
+
resolveParent: (node: AnyNode, nodes: Readonly<Record<string, AnyNode>>) =>
|
|
377
|
+
(node.parentId ? nodes[node.parentId] : null) ?? null,
|
|
378
|
+
parentRotationY: () => 0,
|
|
379
|
+
localToPlan: (_parent: AnyNode, local: readonly [number, number, number]) => [
|
|
380
|
+
local[0],
|
|
381
|
+
local[1],
|
|
382
|
+
local[2],
|
|
383
|
+
],
|
|
384
|
+
planToLocal: (_parent: AnyNode, planX: number, localY: number, planZ: number) => [
|
|
385
|
+
planX,
|
|
386
|
+
localY,
|
|
387
|
+
planZ,
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
})
|
|
393
|
+
const parent = { id: 'move_action_rotatable_run', type: parentKind } as unknown as AnyNode
|
|
394
|
+
const child = {
|
|
395
|
+
id: 'move_action_rotatable_module',
|
|
396
|
+
type: childKind,
|
|
397
|
+
parentId: parent.id,
|
|
398
|
+
} as unknown as AnyNode
|
|
399
|
+
|
|
400
|
+
expect(
|
|
401
|
+
resolveMoveActionNode(child, {
|
|
402
|
+
[parent.id]: parent,
|
|
403
|
+
[child.id]: child,
|
|
404
|
+
}),
|
|
405
|
+
).toBe(parent)
|
|
406
|
+
})
|
|
275
407
|
})
|
|
@@ -45,6 +45,25 @@ const BESPOKE_SELECTION_MOVE_KINDS = new Set([
|
|
|
45
45
|
'liquid-line',
|
|
46
46
|
])
|
|
47
47
|
|
|
48
|
+
export const EDITOR_HANDLE_HIT_AREA_USER_DATA_KEY = 'editorHandleHitArea'
|
|
49
|
+
|
|
50
|
+
export function pointerEventHitsEditorHandle(event: unknown): boolean {
|
|
51
|
+
if (!event || typeof event !== 'object') return false
|
|
52
|
+
const intersections = (
|
|
53
|
+
event as {
|
|
54
|
+
intersections?: readonly {
|
|
55
|
+
object?: { userData?: Record<string, unknown> }
|
|
56
|
+
}[]
|
|
57
|
+
}
|
|
58
|
+
).intersections
|
|
59
|
+
return (
|
|
60
|
+
intersections?.some(
|
|
61
|
+
(intersection) =>
|
|
62
|
+
intersection.object?.userData?.[EDITOR_HANDLE_HIT_AREA_USER_DATA_KEY] === true,
|
|
63
|
+
) ?? false
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
export function canDirectMoveNode(node: AnyNode): boolean {
|
|
49
68
|
// These MEP kinds own move through bespoke selection rigs (latch cubes,
|
|
50
69
|
// directional arrows, grid-driven previews). Sending body drags/clicks
|
|
@@ -64,6 +83,24 @@ export function canDirectMoveNode(node: AnyNode): boolean {
|
|
|
64
83
|
return isMovable(node)
|
|
65
84
|
}
|
|
66
85
|
|
|
86
|
+
export function shouldStartDirectMoveDrag({
|
|
87
|
+
allowPlainDrag,
|
|
88
|
+
commandModifier,
|
|
89
|
+
handleOwnsPointer,
|
|
90
|
+
nodeId,
|
|
91
|
+
selectedIds,
|
|
92
|
+
}: {
|
|
93
|
+
allowPlainDrag: boolean
|
|
94
|
+
commandModifier: boolean
|
|
95
|
+
handleOwnsPointer: boolean
|
|
96
|
+
nodeId: string
|
|
97
|
+
selectedIds: readonly string[]
|
|
98
|
+
}): boolean {
|
|
99
|
+
if (handleOwnsPointer) return false
|
|
100
|
+
if (commandModifier) return selectedIds.length === 1 && selectedIds[0] === nodeId
|
|
101
|
+
return allowPlainDrag && selectedIds.length < 2
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
export function resolveDirectManipulationNode(
|
|
68
105
|
node: AnyNode,
|
|
69
106
|
nodes: Readonly<Record<string, AnyNode | undefined>>,
|
|
@@ -80,7 +117,7 @@ export function resolveMoveActionNode(
|
|
|
80
117
|
): AnyNode {
|
|
81
118
|
const parentFrame = nodeRegistry.get(node.type)?.capabilities?.movable?.parentFrame
|
|
82
119
|
const parent = parentFrame?.resolveParent(node, nodes as Readonly<Record<string, AnyNode>>)
|
|
83
|
-
return parent
|
|
120
|
+
return parent && (parent.type === node.type || canDirectRotateNode(parent)) ? parent : node
|
|
84
121
|
}
|
|
85
122
|
|
|
86
123
|
export function snapDirectRotationDelta(delta: number, free: boolean): number {
|