@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
|
-
import { Copy, Group, Move, Search, Spline, Trash2, Ungroup } from 'lucide-react'
|
|
4
|
+
import { Copy, Group, Move, PencilRuler, Search, Spline, Trash2, Ungroup } from 'lucide-react'
|
|
5
5
|
import type { MouseEventHandler, PointerEventHandler } from 'react'
|
|
6
6
|
|
|
7
7
|
type NodeActionMenuProps = {
|
|
@@ -10,6 +10,7 @@ type NodeActionMenuProps = {
|
|
|
10
10
|
onDelete?: MouseEventHandler<HTMLButtonElement>
|
|
11
11
|
onDuplicate?: MouseEventHandler<HTMLButtonElement>
|
|
12
12
|
onMove?: MouseEventHandler<HTMLButtonElement>
|
|
13
|
+
onEditMesh?: MouseEventHandler<HTMLButtonElement>
|
|
13
14
|
onCurve?: MouseEventHandler<HTMLButtonElement>
|
|
14
15
|
/** Session group (Ctrl/Cmd+G) — multi-selection floating pill. */
|
|
15
16
|
onGroup?: MouseEventHandler<HTMLButtonElement>
|
|
@@ -27,6 +28,7 @@ export function NodeActionMenu({
|
|
|
27
28
|
onDelete,
|
|
28
29
|
onDuplicate,
|
|
29
30
|
onMove,
|
|
31
|
+
onEditMesh,
|
|
30
32
|
onCurve,
|
|
31
33
|
onGroup,
|
|
32
34
|
onUngroup,
|
|
@@ -65,6 +67,17 @@ export function NodeActionMenu({
|
|
|
65
67
|
<Move className="h-4 w-4" />
|
|
66
68
|
</button>
|
|
67
69
|
)}
|
|
70
|
+
{onEditMesh && (
|
|
71
|
+
<button
|
|
72
|
+
aria-label="Edit mesh"
|
|
73
|
+
className="tooltip-trigger rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
74
|
+
onClick={onEditMesh}
|
|
75
|
+
title="Edit mesh"
|
|
76
|
+
type="button"
|
|
77
|
+
>
|
|
78
|
+
<PencilRuler className="h-4 w-4" />
|
|
79
|
+
</button>
|
|
80
|
+
)}
|
|
68
81
|
{onGroup && (
|
|
69
82
|
<button
|
|
70
83
|
aria-label="Group selection"
|
|
@@ -47,6 +47,7 @@ import { createEditorApi } from '../../lib/editor-api'
|
|
|
47
47
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
48
48
|
import useDirectManipulationFeedback from '../../store/use-direct-manipulation-feedback'
|
|
49
49
|
import useEditor, { isGridSnapActive, isMagneticSnapActive } from '../../store/use-editor'
|
|
50
|
+
import { useHandleGroup } from '../../store/use-handle-group'
|
|
50
51
|
import useInteractionScope, {
|
|
51
52
|
useEndpointReshape,
|
|
52
53
|
useIsCurveReshape,
|
|
@@ -62,7 +63,12 @@ import {
|
|
|
62
63
|
HandleArrow,
|
|
63
64
|
NO_RAYCAST,
|
|
64
65
|
} from './handles/handle-arrow'
|
|
65
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
computeFreezeOffset,
|
|
68
|
+
resolveLinearHandlePosition,
|
|
69
|
+
resolveLinearHandleRotation,
|
|
70
|
+
} from './handles/handle-placement'
|
|
71
|
+
import { createLinearResizeDragBinding, linearResizeFactor } from './handles/linear-resize-drag'
|
|
66
72
|
import { resolveResizeSnapValue } from './handles/resize-snap'
|
|
67
73
|
import { type HandleDragControls, useHandleDrag } from './handles/use-handle-drag'
|
|
68
74
|
|
|
@@ -227,6 +233,9 @@ export function NodeArrowHandles() {
|
|
|
227
233
|
() => (rawNode && liveOverride ? ({ ...rawNode, ...liveOverride } as AnyNode) : rawNode),
|
|
228
234
|
[rawNode, liveOverride],
|
|
229
235
|
)
|
|
236
|
+
const activeGroup = useHandleGroup((s) =>
|
|
237
|
+
s.active && s.active.nodeId === node?.id ? s.active.group : null,
|
|
238
|
+
)
|
|
230
239
|
const def = node ? nodeRegistry.get(node.type) : null
|
|
231
240
|
const descriptorSceneApi = useMemo(() => createSceneApi(useScene), [])
|
|
232
241
|
const descriptors = useMemo(() => {
|
|
@@ -235,17 +244,18 @@ export function NodeArrowHandles() {
|
|
|
235
244
|
typeof def.handles === 'function'
|
|
236
245
|
? def.handles(node as never, descriptorSceneApi)
|
|
237
246
|
: (def.handles as HandleDescriptor[])
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
return all.filter((descriptor) => {
|
|
248
|
+
if (activeGroup)
|
|
249
|
+
return descriptor.kind === 'linear-resize' && descriptor.latchGroup === activeGroup
|
|
250
|
+
if (descriptor.kind === 'translate') return false
|
|
251
|
+
const visible =
|
|
252
|
+
'visible' in descriptor
|
|
253
|
+
? descriptor.visible?.(node as never, descriptorSceneApi)
|
|
254
|
+
: undefined
|
|
255
|
+
if ('shape' in descriptor && descriptor.shape === 'move-cross') return visible === true
|
|
256
|
+
return visible !== false
|
|
257
|
+
})
|
|
258
|
+
}, [node, def, descriptorSceneApi, activeGroup])
|
|
249
259
|
|
|
250
260
|
const shouldRender =
|
|
251
261
|
Boolean(node && descriptors?.length) &&
|
|
@@ -292,19 +302,29 @@ function NodeArrowHandlesForNode({
|
|
|
292
302
|
node: AnyNode
|
|
293
303
|
descriptors: HandleDescriptor[]
|
|
294
304
|
}) {
|
|
305
|
+
const controlledGroup = useHandleGroup((s) =>
|
|
306
|
+
s.active?.nodeId === node.id ? s.active.group : null,
|
|
307
|
+
)
|
|
295
308
|
const parentId = node.parentId ?? null
|
|
296
|
-
const grandparentId = useScene((state) => {
|
|
297
|
-
if (!parentId) return null
|
|
298
|
-
const parent = state.nodes[parentId as AnyNodeId]
|
|
299
|
-
return parent?.parentId ?? null
|
|
300
|
-
})
|
|
301
309
|
|
|
302
310
|
const portalMode: HandlePortal = descriptors.some((d) => d.portal === 'grandparent')
|
|
303
311
|
? 'grandparent'
|
|
304
312
|
: 'parent'
|
|
305
313
|
|
|
314
|
+
const portalTargetResolver = descriptors.find(
|
|
315
|
+
(descriptor) => descriptor.portalTarget !== undefined,
|
|
316
|
+
)?.portalTarget
|
|
317
|
+
const descriptorSceneApi = useMemo(() => createSceneApi(useScene), [])
|
|
318
|
+
|
|
306
319
|
// Portal target: the mesh we createPortal into.
|
|
307
|
-
const portalTargetId =
|
|
320
|
+
const portalTargetId = useScene((state) => {
|
|
321
|
+
if (portalTargetResolver) {
|
|
322
|
+
return portalTargetResolver(node as never, descriptorSceneApi) ?? null
|
|
323
|
+
}
|
|
324
|
+
const parentId = node.parentId ?? null
|
|
325
|
+
if (!parentId || portalMode === 'parent') return parentId
|
|
326
|
+
return state.nodes[parentId as AnyNodeId]?.parentId ?? null
|
|
327
|
+
})
|
|
308
328
|
// Outer wrapper mirrors this mesh's local pose. For 'parent' mode the
|
|
309
329
|
// outer IS the node (so handles + drag math both live in node-local).
|
|
310
330
|
// For 'grandparent' the outer rides the parent and an inner group adds
|
|
@@ -470,7 +490,8 @@ function NodeArrowHandlesForNode({
|
|
|
470
490
|
}
|
|
471
491
|
// Arrows tagged with a latch group stay hidden until that group is open.
|
|
472
492
|
const latchGroup = descriptor.kind === 'linear-resize' ? descriptor.latchGroup : undefined
|
|
473
|
-
if (latchGroup && !openLatchGroups.has(latchGroup))
|
|
493
|
+
if (latchGroup && latchGroup !== controlledGroup && !openLatchGroups.has(latchGroup))
|
|
494
|
+
return null
|
|
474
495
|
return (
|
|
475
496
|
<ArrowHandle
|
|
476
497
|
activeIndex={activeIndex}
|
|
@@ -496,39 +517,6 @@ function NodeArrowHandlesForNode({
|
|
|
496
517
|
)
|
|
497
518
|
}
|
|
498
519
|
|
|
499
|
-
// Offset, in node-local frame, that compensates for `position` drift on
|
|
500
|
-
// the mesh during an asymmetric resize. Width/length L+R recompute
|
|
501
|
-
// `position` so the anchored edge stays world-fixed — the renderer
|
|
502
|
-
// follows that override, the ride object moves, and every arrow under
|
|
503
|
-
// it would drift along with the mesh center. Subtracting this offset
|
|
504
|
-
// from a non-active arrow's local placement undoes that drift so it
|
|
505
|
-
// stays at its pre-drag world position.
|
|
506
|
-
//
|
|
507
|
-
// Rotation drags don't change `position`, so the offset collapses to
|
|
508
|
-
// zero and non-active arrows naturally rotate with the mesh — which is
|
|
509
|
-
// the desired behaviour (the whole rig rotates as a unit).
|
|
510
|
-
function computeFreezeOffset(liveNode: AnyNode, preDragNode: AnyNode): [number, number, number] {
|
|
511
|
-
// Not every node in the union carries a `position` field (sites are the
|
|
512
|
-
// notable holdout — they don't have handles anyway, but TypeScript still
|
|
513
|
-
// requires us to discriminate). Guarded access keeps the freeze logic
|
|
514
|
-
// safe for the few node kinds that lack the field.
|
|
515
|
-
const liveP = (liveNode as { position?: readonly [number, number, number] }).position ?? [0, 0, 0]
|
|
516
|
-
const preP = (preDragNode as { position?: readonly [number, number, number] }).position ?? [
|
|
517
|
-
0, 0, 0,
|
|
518
|
-
]
|
|
519
|
-
const deltaWorldX = liveP[0] - preP[0]
|
|
520
|
-
const deltaWorldY = liveP[1] - preP[1]
|
|
521
|
-
const deltaWorldZ = liveP[2] - preP[2]
|
|
522
|
-
const rotY = (preDragNode as { rotation?: number }).rotation ?? 0
|
|
523
|
-
// World → node-local for Y-axis rotation by rotY (THREE.Object3D
|
|
524
|
-
// rotation-y convention): inverse is rotation by -rotY around +Y.
|
|
525
|
-
const cosR = Math.cos(rotY)
|
|
526
|
-
const sinR = Math.sin(rotY)
|
|
527
|
-
const deltaLocalX = cosR * deltaWorldX - sinR * deltaWorldZ
|
|
528
|
-
const deltaLocalZ = sinR * deltaWorldX + cosR * deltaWorldZ
|
|
529
|
-
return [deltaLocalX, deltaWorldY, deltaLocalZ]
|
|
530
|
-
}
|
|
531
|
-
|
|
532
520
|
function ArrowHandle({
|
|
533
521
|
descriptor,
|
|
534
522
|
liveNode,
|
|
@@ -660,7 +648,7 @@ function LinearArrow({
|
|
|
660
648
|
// for the edge being resized); cleared when the drag ends.
|
|
661
649
|
const onDrag = descriptor.kind === 'linear-resize' ? descriptor.onDrag : undefined
|
|
662
650
|
const placementSceneApi = useMemo(() => createSceneApi(useScene), [])
|
|
663
|
-
const basePosition = descriptor
|
|
651
|
+
const basePosition = resolveLinearHandlePosition(descriptor, node, placementSceneApi, baseScale)
|
|
664
652
|
// `freezeOffset` (in node-local frame) cancels the mesh's `position`
|
|
665
653
|
// drift while another arrow is being dragged — `basePosition` is
|
|
666
654
|
// computed against the pre-drag snapshot, then we subtract the offset
|
|
@@ -710,34 +698,29 @@ function LinearArrow({
|
|
|
710
698
|
getPointerRay(event.nativeEvent.clientX, event.nativeEvent.clientY, _resizeRay),
|
|
711
699
|
) / localToWorldScale
|
|
712
700
|
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
?
|
|
716
|
-
|
|
701
|
+
const linearBinding =
|
|
702
|
+
descriptor.kind === 'linear-resize'
|
|
703
|
+
? createLinearResizeDragBinding({
|
|
704
|
+
descriptor,
|
|
705
|
+
initialNode,
|
|
706
|
+
nodeId,
|
|
707
|
+
sceneApi,
|
|
708
|
+
initialModifiers: { altKey: event.nativeEvent.altKey },
|
|
709
|
+
})
|
|
710
|
+
: null
|
|
711
|
+
const overrideId = linearBinding?.overrideId ?? nodeId
|
|
717
712
|
const initialValue = descriptor.currentValue(initialNode)
|
|
718
713
|
const minBound = resolveBound(descriptor.min, Number.NEGATIVE_INFINITY, initialNode, sceneApi)
|
|
719
714
|
const maxBound = resolveBound(descriptor.max, Number.POSITIVE_INFINITY, initialNode, sceneApi)
|
|
720
|
-
const factor =
|
|
721
|
-
descriptor.kind === 'radial-resize'
|
|
722
|
-
? 1
|
|
723
|
-
: descriptor.anchor === 'center'
|
|
724
|
-
? 2
|
|
725
|
-
: descriptor.anchor === 'min'
|
|
726
|
-
? 1
|
|
727
|
-
: -1
|
|
715
|
+
const factor = linearResizeFactor(descriptor)
|
|
728
716
|
|
|
729
717
|
// Last value an emitted resize tick fired at — a new tick fires only
|
|
730
718
|
// when the (snapped + clamped) value actually changes, so the cue
|
|
731
719
|
// tracks real size steps instead of every sub-pixel pointer jitter.
|
|
732
720
|
let lastTickValue = initialValue
|
|
733
|
-
let previewOverrideIds = new Set<AnyNodeId>()
|
|
734
|
-
|
|
735
721
|
return {
|
|
736
722
|
overrideId,
|
|
737
|
-
commit:
|
|
738
|
-
descriptor.kind === 'linear-resize' && descriptor.commit
|
|
739
|
-
? (patch) => descriptor.commit?.(initialNode, patch, sceneApi)
|
|
740
|
-
: undefined,
|
|
723
|
+
commit: linearBinding?.commit,
|
|
741
724
|
onBegin: () => {
|
|
742
725
|
// Always claim the handle-drag scope so the HUD knows a resize is the
|
|
743
726
|
// active interaction (keeps the idle select hints off-screen). The
|
|
@@ -755,12 +738,9 @@ function LinearArrow({
|
|
|
755
738
|
descriptor.onDragEnd?.(initialNode as never, sceneApi)
|
|
756
739
|
}
|
|
757
740
|
if (onDrag) useOpeningGuides.getState().clear()
|
|
758
|
-
|
|
759
|
-
useLiveNodeOverrides.getState().clear(previewId)
|
|
760
|
-
useScene.getState().markDirty(previewId)
|
|
761
|
-
}
|
|
741
|
+
linearBinding?.clearPreview()
|
|
762
742
|
},
|
|
763
|
-
move: ({ event: moveEvent, getPointerRay: getMovePointerRay }) => {
|
|
743
|
+
move: ({ event: moveEvent, modifiers, getPointerRay: getMovePointerRay }) => {
|
|
764
744
|
const currentPointer =
|
|
765
745
|
closestAxisParameterToRay(
|
|
766
746
|
_resizeOriginW,
|
|
@@ -772,43 +752,27 @@ function LinearArrow({
|
|
|
772
752
|
const linearDescriptor = descriptor.kind === 'linear-resize' ? descriptor : null
|
|
773
753
|
const snappedNext = resolveResizeSnapValue({
|
|
774
754
|
rawValue: rawNext,
|
|
755
|
+
fallbackValue: lastTickValue,
|
|
775
756
|
gridSnapEnabled: linearDescriptor?.gridSnap === true,
|
|
776
|
-
gridSnapActive: isGridSnapActive(),
|
|
757
|
+
gridSnapActive: isGridSnapActive() && !modifiers.altKey,
|
|
777
758
|
gridSnapStep: useEditor.getState().gridSnapStep,
|
|
778
|
-
magneticSnapActive: isMagneticSnapActive(),
|
|
759
|
+
magneticSnapActive: isMagneticSnapActive() && !modifiers.altKey,
|
|
779
760
|
magneticSnap: linearDescriptor?.magneticSnap
|
|
780
761
|
? (value) => linearDescriptor.magneticSnap?.(initialNode, value, sceneApi) ?? value
|
|
781
762
|
: undefined,
|
|
763
|
+
connectionSnapActive: !modifiers.altKey,
|
|
764
|
+
connectionSnap: linearDescriptor?.connectionSnap
|
|
765
|
+
? (value) => linearDescriptor.connectionSnap?.(initialNode, value, sceneApi) ?? value
|
|
766
|
+
: undefined,
|
|
782
767
|
})
|
|
783
768
|
const next = Math.min(maxBound, Math.max(minBound, snappedNext))
|
|
784
769
|
if (next !== lastTickValue) {
|
|
785
770
|
lastTickValue = next
|
|
786
771
|
sfxEmitter.emit('sfx:resize')
|
|
787
772
|
}
|
|
788
|
-
const patch =
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const nextPreviewOverrideIds = replacePreviewOverrideIds(
|
|
792
|
-
previewOverrideIds,
|
|
793
|
-
previewEntries,
|
|
794
|
-
(previewId) => {
|
|
795
|
-
useLiveNodeOverrides.getState().clear(previewId)
|
|
796
|
-
useScene.getState().markDirty(previewId)
|
|
797
|
-
},
|
|
798
|
-
)
|
|
799
|
-
useLiveNodeOverrides
|
|
800
|
-
.getState()
|
|
801
|
-
.setMany(
|
|
802
|
-
previewEntries.map(([id, previewPatch]) => [
|
|
803
|
-
id,
|
|
804
|
-
previewPatch as Record<string, unknown>,
|
|
805
|
-
]),
|
|
806
|
-
)
|
|
807
|
-
for (const [previewId] of previewEntries) {
|
|
808
|
-
useScene.getState().markDirty(previewId)
|
|
809
|
-
}
|
|
810
|
-
previewOverrideIds = nextPreviewOverrideIds
|
|
811
|
-
}
|
|
773
|
+
const patch = linearBinding
|
|
774
|
+
? linearBinding.apply(next, modifiers)
|
|
775
|
+
: (descriptor.apply(initialNode as never, next, sceneApi) as Partial<AnyNode>)
|
|
812
776
|
// Let the kind publish live guides for the edge being resized.
|
|
813
777
|
onDrag?.({ ...(initialNode as object), ...patch } as AnyNode, sceneApi)
|
|
814
778
|
return patch
|
|
@@ -817,23 +781,7 @@ function LinearArrow({
|
|
|
817
781
|
},
|
|
818
782
|
})
|
|
819
783
|
|
|
820
|
-
|
|
821
|
-
// X+Z rotation chain matching DoorHeightArrowHandle. When the handle
|
|
822
|
-
// sits below the node (placement Y < 0, e.g. window bottom arrow),
|
|
823
|
-
// flip the Z rotation so the chevron points outward (downward).
|
|
824
|
-
//
|
|
825
|
-
// For axis === 'x' with `faceNormal` (wall-mounted opening width arrows),
|
|
826
|
-
// roll the blade 90° about its own pointing (X) axis so it stands up from
|
|
827
|
-
// the horizontal XZ plane into the node's facing plane (XY = the wall
|
|
828
|
-
// face) — otherwise the blade is seen edge-on from the front.
|
|
829
|
-
const faceNormalX =
|
|
830
|
-
descriptor.kind === 'linear-resize' && descriptor.axis === 'x' && descriptor.faceNormal === true
|
|
831
|
-
const innerRotation: [number, number, number] =
|
|
832
|
-
descriptor.axis === 'y'
|
|
833
|
-
? [0, Math.PI / 2, position[1] < 0 ? -Math.PI / 2 : Math.PI / 2]
|
|
834
|
-
: faceNormalX
|
|
835
|
-
? [Math.PI / 2, 0, 0]
|
|
836
|
-
: [0, 0, 0]
|
|
784
|
+
const innerRotation = resolveLinearHandleRotation(descriptor, position)
|
|
837
785
|
|
|
838
786
|
// Optional guide decoration — linear handles use it for curved-stair
|
|
839
787
|
// width / inner-radius rings; radial handles use it for the column's
|