@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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function precisionForStep(step: number): number {
|
|
2
|
+
if (step <= 0) return 0
|
|
3
|
+
return Math.max(0, Math.ceil(-Math.log10(step)))
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function prettifyKey(key: string): string {
|
|
7
|
+
const spaced = key.replace(/([A-Z])/g, ' $1').toLowerCase()
|
|
8
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function prettifyEnumValue(value: string): string {
|
|
12
|
+
return value
|
|
13
|
+
.split(/[-_\s]/)
|
|
14
|
+
.map((word, i) =>
|
|
15
|
+
i === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word.toLowerCase(),
|
|
16
|
+
)
|
|
17
|
+
.join(' ')
|
|
18
|
+
}
|
|
@@ -20,9 +20,7 @@ import { collectZoneContentIds } from '../../../lib/zone-content'
|
|
|
20
20
|
import useEditor from '../../../store/use-editor'
|
|
21
21
|
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
22
22
|
import { PanelSection } from '../controls/panel-section'
|
|
23
|
-
import {
|
|
24
|
-
import { SliderControl } from '../controls/slider-control'
|
|
25
|
-
import { ToggleControl } from '../controls/toggle-control'
|
|
23
|
+
import { ParametricFieldControl } from './parametric-field-control'
|
|
26
24
|
import { InspectorFooterContext, PanelWrapper } from './panel-wrapper'
|
|
27
25
|
|
|
28
26
|
/**
|
|
@@ -67,7 +65,7 @@ export function ParametricInspector({
|
|
|
67
65
|
const node = scene.nodes[selectedId]
|
|
68
66
|
if (parametrics?.derive && node) {
|
|
69
67
|
const next = { ...node, ...patch } as AnyNode
|
|
70
|
-
patch = { ...patch, ...parametrics.derive(next, patch) }
|
|
68
|
+
patch = { ...patch, ...parametrics.derive(next, patch, node as AnyNode) }
|
|
71
69
|
}
|
|
72
70
|
// Bundle the edited node + any reconcile follow-ups into ONE
|
|
73
71
|
// updateNodes call so a single inspector edit is a single undo step.
|
|
@@ -133,7 +131,7 @@ export function ParametricInspector({
|
|
|
133
131
|
return (
|
|
134
132
|
<InspectorFooterContext.Provider value={footer}>
|
|
135
133
|
<Suspense fallback={null}>
|
|
136
|
-
<CustomPanel />
|
|
134
|
+
<CustomPanelSlot Component={CustomPanel} nodeId={selectedId} />
|
|
137
135
|
</Suspense>
|
|
138
136
|
</InspectorFooterContext.Provider>
|
|
139
137
|
)
|
|
@@ -159,7 +157,11 @@ export function ParametricInspector({
|
|
|
159
157
|
width={320}
|
|
160
158
|
>
|
|
161
159
|
{parametrics.groups.map((group, gi) => (
|
|
162
|
-
<PanelSection
|
|
160
|
+
<PanelSection
|
|
161
|
+
defaultExpanded={group.defaultExpanded}
|
|
162
|
+
key={`${nodeType}-${group.label}-${gi}`}
|
|
163
|
+
title={group.label}
|
|
164
|
+
>
|
|
163
165
|
{group.fields.map((field, fi) => (
|
|
164
166
|
<FieldRenderer
|
|
165
167
|
key={`field-${gi}-${fi}-${String(field.key)}`}
|
|
@@ -172,12 +174,12 @@ export function ParametricInspector({
|
|
|
172
174
|
))}
|
|
173
175
|
{TrailingSection && (
|
|
174
176
|
<Suspense fallback={null}>
|
|
175
|
-
<TrailingSection />
|
|
177
|
+
<CustomPanelSlot Component={TrailingSection} nodeId={selectedId} />
|
|
176
178
|
</Suspense>
|
|
177
179
|
)}
|
|
178
180
|
{(canMove || canDelete || (parametrics.actions && parametrics.actions.length > 0)) && (
|
|
179
181
|
<PanelSection title="Actions">
|
|
180
|
-
<ActionGroup className={isZone ? 'flex-col' : undefined}>
|
|
182
|
+
<ActionGroup className={isZone ? 'flex-col' : parametrics.actions?.length ? 'grid grid-cols-2 gap-2' : undefined}>
|
|
181
183
|
{canMove && (
|
|
182
184
|
<ActionButton icon={<Move className="h-4 w-4" />} label="Move" onClick={handleMove} />
|
|
183
185
|
)}
|
|
@@ -272,14 +274,31 @@ function renderIcon(ref: IconRef | undefined): React.ReactNode | undefined {
|
|
|
272
274
|
|
|
273
275
|
// Cache lazy custom panel components by their loader so React.lazy isn't
|
|
274
276
|
// re-invoked across renders.
|
|
275
|
-
const customPanelCache = new WeakMap<() => Promise<unknown>, ComponentType
|
|
277
|
+
const customPanelCache = new WeakMap<() => Promise<unknown>, ComponentType<{ node: AnyNode }>>()
|
|
276
278
|
|
|
277
|
-
function resolveCustomPanel(
|
|
279
|
+
function resolveCustomPanel(
|
|
280
|
+
loader: () => Promise<{ default: ComponentType<any> }>,
|
|
281
|
+
): ComponentType<{ node: AnyNode }> {
|
|
278
282
|
const cached = customPanelCache.get(loader)
|
|
279
283
|
if (cached) return cached
|
|
280
284
|
const Comp = lazy(loader)
|
|
281
|
-
customPanelCache.set(loader, Comp as ComponentType)
|
|
282
|
-
return Comp as ComponentType
|
|
285
|
+
customPanelCache.set(loader, Comp as ComponentType<{ node: AnyNode }>)
|
|
286
|
+
return Comp as ComponentType<{ node: AnyNode }>
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Subscribe to the full node only where the custom panel contract needs it.
|
|
290
|
+
// Keeping this below ParametricInspector preserves the inspector's narrow
|
|
291
|
+
// per-field subscriptions while ensuring lazy panels receive their live node.
|
|
292
|
+
function CustomPanelSlot({
|
|
293
|
+
Component,
|
|
294
|
+
nodeId,
|
|
295
|
+
}: {
|
|
296
|
+
Component: ComponentType<{ node: AnyNode }>
|
|
297
|
+
nodeId: AnyNodeId
|
|
298
|
+
}) {
|
|
299
|
+
const node = useScene((s) => s.nodes[nodeId])
|
|
300
|
+
if (!node) return null
|
|
301
|
+
return <Component node={node as AnyNode} />
|
|
283
302
|
}
|
|
284
303
|
|
|
285
304
|
// ─── Per-field renderers ─────────────────────────────────────────────
|
|
@@ -311,136 +330,11 @@ function FieldRenderer({ field, nodeId, onUpdate }: FieldRendererProps) {
|
|
|
311
330
|
})
|
|
312
331
|
if (!visible) return null
|
|
313
332
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const num = typeof value === 'number' ? value : 0
|
|
317
|
-
const step = field.step ?? 0.01
|
|
318
|
-
const precision = precisionForStep(step)
|
|
319
|
-
return (
|
|
320
|
-
<SliderControl
|
|
321
|
-
label={prettifyKey(key)}
|
|
322
|
-
max={field.max}
|
|
323
|
-
min={field.min}
|
|
324
|
-
onChange={(next) => onUpdate({ [key]: next } as Partial<AnyNode>)}
|
|
325
|
-
precision={precision}
|
|
326
|
-
step={step}
|
|
327
|
-
unit={field.unit ?? ''}
|
|
328
|
-
value={num}
|
|
329
|
-
/>
|
|
330
|
-
)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
case 'boolean': {
|
|
334
|
-
const checked = value === true
|
|
335
|
-
return (
|
|
336
|
-
<ToggleControl
|
|
337
|
-
checked={checked}
|
|
338
|
-
label={prettifyKey(key)}
|
|
339
|
-
onChange={(next) => onUpdate({ [key]: next } as Partial<AnyNode>)}
|
|
340
|
-
/>
|
|
341
|
-
)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
case 'enum': {
|
|
345
|
-
const str = typeof value === 'string' ? value : (field.options[0] ?? '')
|
|
346
|
-
if (field.display === 'segmented') {
|
|
347
|
-
return (
|
|
348
|
-
<SegmentedControl
|
|
349
|
-
onChange={(next) => onUpdate({ [key]: next } as Partial<AnyNode>)}
|
|
350
|
-
options={field.options.map((opt) => ({ label: prettifyEnumValue(opt), value: opt }))}
|
|
351
|
-
value={str}
|
|
352
|
-
/>
|
|
353
|
-
)
|
|
354
|
-
}
|
|
355
|
-
return (
|
|
356
|
-
<div className="flex items-center justify-between px-3 py-2">
|
|
357
|
-
<span className="text-foreground/80 text-xs">{prettifyKey(key)}</span>
|
|
358
|
-
<select
|
|
359
|
-
className="rounded-md border border-border/50 bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
|
|
360
|
-
onChange={(e) => onUpdate({ [key]: e.target.value } as Partial<AnyNode>)}
|
|
361
|
-
value={str}
|
|
362
|
-
>
|
|
363
|
-
{field.options.map((opt) => (
|
|
364
|
-
<option key={opt} value={opt}>
|
|
365
|
-
{prettifyEnumValue(opt)}
|
|
366
|
-
</option>
|
|
367
|
-
))}
|
|
368
|
-
</select>
|
|
369
|
-
</div>
|
|
370
|
-
)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
case 'color': {
|
|
374
|
-
const str = typeof value === 'string' ? value : '#888888'
|
|
375
|
-
return (
|
|
376
|
-
<div className="flex items-center justify-between px-3 py-2">
|
|
377
|
-
<span className="text-foreground/80 text-xs">{prettifyKey(key)}</span>
|
|
378
|
-
<div className="flex items-center gap-2">
|
|
379
|
-
<input
|
|
380
|
-
className="h-6 w-8 cursor-pointer rounded border border-border/50 bg-transparent"
|
|
381
|
-
onChange={(e) => onUpdate({ [key]: e.target.value } as Partial<AnyNode>)}
|
|
382
|
-
type="color"
|
|
383
|
-
value={str}
|
|
384
|
-
/>
|
|
385
|
-
<input
|
|
386
|
-
className="w-20 rounded-md border border-border/50 bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
|
|
387
|
-
onChange={(e) => onUpdate({ [key]: e.target.value } as Partial<AnyNode>)}
|
|
388
|
-
type="text"
|
|
389
|
-
value={str}
|
|
390
|
-
/>
|
|
391
|
-
</div>
|
|
392
|
-
</div>
|
|
393
|
-
)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
case 'vec3': {
|
|
397
|
-
const v = Array.isArray(value) && value.length >= 3
|
|
398
|
-
? (value as [number, number, number])
|
|
399
|
-
: [0, 0, 0]
|
|
400
|
-
const axes: Array<{ label: string; index: 0 | 1 | 2 }> = [
|
|
401
|
-
{ label: 'X', index: 0 },
|
|
402
|
-
{ label: 'Y', index: 1 },
|
|
403
|
-
{ label: 'Z', index: 2 },
|
|
404
|
-
]
|
|
405
|
-
return (
|
|
406
|
-
<>
|
|
407
|
-
{axes.map(({ label, index }) => {
|
|
408
|
-
// v is a [number, number, number] tuple; the explicit local
|
|
409
|
-
// resolves TS's noUncheckedIndexedAccess concern that v[index]
|
|
410
|
-
// could be undefined.
|
|
411
|
-
const axisValue = v[index] ?? 0
|
|
412
|
-
return (
|
|
413
|
-
<SliderControl
|
|
414
|
-
key={`${key}-${label}`}
|
|
415
|
-
label={label}
|
|
416
|
-
max={axisValue + 5}
|
|
417
|
-
min={axisValue - 5}
|
|
418
|
-
onChange={(next) => {
|
|
419
|
-
const updated = [...v] as [number, number, number]
|
|
420
|
-
updated[index] = next
|
|
421
|
-
onUpdate({ [key]: updated } as Partial<AnyNode>)
|
|
422
|
-
}}
|
|
423
|
-
precision={2}
|
|
424
|
-
step={0.05}
|
|
425
|
-
unit="m"
|
|
426
|
-
value={Math.round(axisValue * 100) / 100}
|
|
427
|
-
/>
|
|
428
|
-
)
|
|
429
|
-
})}
|
|
430
|
-
</>
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
case 'custom':
|
|
435
|
-
// The field owns its rendering and update logic — used for
|
|
436
|
-
// derived values (length from start/end), dynamic-bounded
|
|
437
|
-
// sliders (curve sagitta), composed editors.
|
|
438
|
-
return <CustomFieldRenderer Comp={field.component} nodeId={nodeId} onUpdate={onUpdate} />
|
|
439
|
-
|
|
440
|
-
default:
|
|
441
|
-
// material / ref / unrecognized kinds — not implemented in v1.
|
|
442
|
-
return null
|
|
333
|
+
if (field.kind === 'custom') {
|
|
334
|
+
return <CustomFieldRenderer Comp={field.component} nodeId={nodeId} onUpdate={onUpdate} />
|
|
443
335
|
}
|
|
336
|
+
|
|
337
|
+
return <ParametricFieldControl field={field} onChange={onUpdate} value={value} />
|
|
444
338
|
}
|
|
445
339
|
|
|
446
340
|
function CustomFieldRenderer({
|
|
@@ -459,26 +353,3 @@ function CustomFieldRenderer({
|
|
|
459
353
|
if (!node) return null
|
|
460
354
|
return <Comp node={node} onUpdate={onUpdate} />
|
|
461
355
|
}
|
|
462
|
-
|
|
463
|
-
// ─── helpers ─────────────────────────────────────────────────────────
|
|
464
|
-
|
|
465
|
-
function precisionForStep(step: number): number {
|
|
466
|
-
if (step <= 0) return 0
|
|
467
|
-
return Math.max(0, Math.ceil(-Math.log10(step)))
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function prettifyKey(key: string): string {
|
|
471
|
-
// 'bracketStyle' → 'Bracket style'
|
|
472
|
-
const spaced = key.replace(/([A-Z])/g, ' $1').toLowerCase()
|
|
473
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function prettifyEnumValue(value: string): string {
|
|
477
|
-
// 'minimal' → 'Minimal'; 'roof-segment' → 'Roof segment'
|
|
478
|
-
return value
|
|
479
|
-
.split(/[-_\s]/)
|
|
480
|
-
.map((word, i) =>
|
|
481
|
-
i === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word.toLowerCase(),
|
|
482
|
-
)
|
|
483
|
-
.join(' ')
|
|
484
|
-
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
EyeOff,
|
|
14
14
|
LocateFixed,
|
|
15
15
|
Lock,
|
|
16
|
+
Move,
|
|
16
17
|
RotateCcw,
|
|
17
18
|
Ruler,
|
|
18
19
|
Trash2,
|
|
@@ -144,6 +145,12 @@ export function ReferencePanel() {
|
|
|
144
145
|
guideEmitter.emit('guide:cancel-reference-scale')
|
|
145
146
|
}, [])
|
|
146
147
|
|
|
148
|
+
const handleMoveScan = useCallback(() => {
|
|
149
|
+
if (node?.type !== 'scan') return
|
|
150
|
+
useEditor.getState().setMovingNode(node as never)
|
|
151
|
+
setSelectedReferenceId(null)
|
|
152
|
+
}, [node, setSelectedReferenceId])
|
|
153
|
+
|
|
147
154
|
useEffect(() => {
|
|
148
155
|
if (node?.type !== 'guide' || !node.url.startsWith('asset://')) {
|
|
149
156
|
setIsAssetMissing(false)
|
|
@@ -172,7 +179,7 @@ export function ReferencePanel() {
|
|
|
172
179
|
return (
|
|
173
180
|
<PanelWrapper
|
|
174
181
|
onClose={handleClose}
|
|
175
|
-
title={node.name || (isScan ? '
|
|
182
|
+
title={node.name || (isScan ? 'Capture' : 'Guide Image')}
|
|
176
183
|
width={300}
|
|
177
184
|
>
|
|
178
185
|
{!isScan && (
|
|
@@ -329,6 +336,29 @@ export function ReferencePanel() {
|
|
|
329
336
|
</>
|
|
330
337
|
)}
|
|
331
338
|
|
|
339
|
+
{isScan && (
|
|
340
|
+
<PanelSection title="Capture">
|
|
341
|
+
<ActionGroup>
|
|
342
|
+
<ActionButton
|
|
343
|
+
icon={<Move className="h-3.5 w-3.5" />}
|
|
344
|
+
label="Move"
|
|
345
|
+
onClick={handleMoveScan}
|
|
346
|
+
/>
|
|
347
|
+
<ActionButton
|
|
348
|
+
icon={
|
|
349
|
+
node.visible === false ? (
|
|
350
|
+
<EyeOff className="h-3.5 w-3.5" />
|
|
351
|
+
) : (
|
|
352
|
+
<Eye className="h-3.5 w-3.5" />
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
label={node.visible === false ? 'Show' : 'Hide'}
|
|
356
|
+
onClick={() => handleUpdate({ visible: node.visible === false })}
|
|
357
|
+
/>
|
|
358
|
+
</ActionGroup>
|
|
359
|
+
</PanelSection>
|
|
360
|
+
)}
|
|
361
|
+
|
|
332
362
|
<PanelSection title="Position">
|
|
333
363
|
<SliderControl
|
|
334
364
|
label={
|
|
@@ -346,7 +376,7 @@ export function ReferencePanel() {
|
|
|
346
376
|
precision={2}
|
|
347
377
|
step={0.1}
|
|
348
378
|
unit="m"
|
|
349
|
-
value={
|
|
379
|
+
value={node.position[0]}
|
|
350
380
|
/>
|
|
351
381
|
<SliderControl
|
|
352
382
|
label={
|
|
@@ -364,7 +394,7 @@ export function ReferencePanel() {
|
|
|
364
394
|
precision={2}
|
|
365
395
|
step={0.1}
|
|
366
396
|
unit="m"
|
|
367
|
-
value={
|
|
397
|
+
value={node.position[1]}
|
|
368
398
|
/>
|
|
369
399
|
<SliderControl
|
|
370
400
|
label={
|
|
@@ -382,7 +412,7 @@ export function ReferencePanel() {
|
|
|
382
412
|
precision={2}
|
|
383
413
|
step={0.1}
|
|
384
414
|
unit="m"
|
|
385
|
-
value={
|
|
415
|
+
value={node.position[2]}
|
|
386
416
|
/>
|
|
387
417
|
</PanelSection>
|
|
388
418
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Check } from 'lucide-react'
|
|
4
|
+
import type { ButtonHTMLAttributes } from 'react'
|
|
5
|
+
import { cn } from './../../../lib/utils'
|
|
6
|
+
|
|
7
|
+
interface CheckboxProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
|
|
8
|
+
checked: boolean
|
|
9
|
+
onCheckedChange: (checked: boolean) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Checkbox({ checked, onCheckedChange, className, ...props }: CheckboxProps) {
|
|
13
|
+
return (
|
|
14
|
+
<button
|
|
15
|
+
aria-checked={checked}
|
|
16
|
+
className={cn(
|
|
17
|
+
'flex h-3.5 w-3.5 shrink-0 cursor-pointer items-center justify-center rounded-[4px] border transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring',
|
|
18
|
+
checked
|
|
19
|
+
? 'border-primary bg-primary text-primary-foreground'
|
|
20
|
+
: 'border-border bg-background/60 text-transparent hover:border-foreground/40',
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
role="checkbox"
|
|
24
|
+
type="button"
|
|
25
|
+
{...props}
|
|
26
|
+
onClick={(event) => {
|
|
27
|
+
event.stopPropagation()
|
|
28
|
+
onCheckedChange(!checked)
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<Check className="h-2.5 w-2.5" strokeWidth={3} />
|
|
32
|
+
</button>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -33,6 +33,28 @@ const COMMAND_VALUES = new Set(['Cmd/Ctrl', 'Cmd', 'Command', 'Meta'])
|
|
|
33
33
|
const IS_MAC =
|
|
34
34
|
typeof navigator !== 'undefined' && navigator.platform.toUpperCase().includes('MAC')
|
|
35
35
|
|
|
36
|
+
// Keys whose printed name and their symbol differ. Kept beside the token rather
|
|
37
|
+
// than in any one consumer so every surface that prints a shortcut — the
|
|
38
|
+
// Keyboard Shortcuts dialog, the community getting-started guide — resolves the
|
|
39
|
+
// same glyph for the same key.
|
|
40
|
+
//
|
|
41
|
+
// Only symbols people actually read are listed. The arrows are unambiguous, and
|
|
42
|
+
// ⌘ is printed on the key it means. The rest are not: ⎋, ␣ and ⌫ are typographic
|
|
43
|
+
// conventions that no keyboard prints, so they read as "some symbol" rather than
|
|
44
|
+
// as Escape, Space and Delete. Those spell their names out instead — and for
|
|
45
|
+
// Delete, the name the current platform actually puts on the keycap.
|
|
46
|
+
const KEY_DISPLAY_MAP: Record<string, string> = {
|
|
47
|
+
'Arrow Down': '↓',
|
|
48
|
+
'Arrow Up': '↑',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The glyph to print for a shortcut key on the current platform. */
|
|
52
|
+
function shortcutDisplayValue(value: string): string {
|
|
53
|
+
if (COMMAND_VALUES.has(value)) return IS_MAC ? '⌘' : 'Ctrl'
|
|
54
|
+
if (value === 'Delete / Backspace') return IS_MAC ? 'Delete' : 'Backspace'
|
|
55
|
+
return KEY_DISPLAY_MAP[value] ?? value
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
type ShortcutTokenProps = React.ComponentProps<'kbd'> & {
|
|
37
59
|
value: string
|
|
38
60
|
displayValue?: string
|
|
@@ -97,4 +119,4 @@ function ShortcutToken({ className, displayValue, value, ...props }: ShortcutTok
|
|
|
97
119
|
)
|
|
98
120
|
}
|
|
99
121
|
|
|
100
|
-
export { ShortcutToken }
|
|
122
|
+
export { ShortcutToken, shortcutDisplayValue }
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
4
|
import { cn } from '../../lib/utils'
|
|
5
|
+
import { Button } from './primitives/button'
|
|
5
6
|
|
|
6
7
|
const LOADERS = [
|
|
7
8
|
'pascal-loader-1',
|
|
@@ -36,3 +37,37 @@ export function SceneLoader({ className, fullScreen = false }: SceneLoaderProps)
|
|
|
36
37
|
</div>
|
|
37
38
|
)
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
interface SceneLoadFailedProps {
|
|
42
|
+
className?: string
|
|
43
|
+
onRetry: () => void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Replaces the loader when the host could not deliver the scene. Rendered
|
|
48
|
+
* INSTEAD of falling back to an empty default scene: a session that shows
|
|
49
|
+
* scaffold nodes after a failed load autosaves that scaffold over the real
|
|
50
|
+
* project (prod scene-wipe class, 2026-09-02).
|
|
51
|
+
*/
|
|
52
|
+
export function SceneLoadFailed({ className, onRetry }: SceneLoadFailedProps) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
'z-100 flex flex-col items-center justify-center gap-4 bg-background/90 px-6 text-center backdrop-blur-md',
|
|
57
|
+
'absolute inset-0',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
role="alert"
|
|
61
|
+
>
|
|
62
|
+
<div className="flex flex-col gap-1">
|
|
63
|
+
<p className="font-medium text-foreground text-sm">This project couldn't be loaded</p>
|
|
64
|
+
<p className="text-muted-foreground text-sm">
|
|
65
|
+
Nothing was changed. Check your connection and try again.
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
<Button className="rounded-full" onClick={onRetry} size="sm" type="button">
|
|
69
|
+
Try again
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|