@pascal-app/editor 0.5.1 → 0.7.0
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/package.json +12 -7
- package/src/components/editor/bottom-sheet.tsx +149 -0
- package/src/components/editor/custom-camera-controls.tsx +75 -7
- package/src/components/editor/editor-layout-mobile.tsx +264 -0
- package/src/components/editor/editor-layout-v2.tsx +29 -0
- package/src/components/editor/first-person/build-collider-world.ts +365 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +795 -0
- package/src/components/editor/first-person-controls.tsx +496 -143
- package/src/components/editor/floating-action-menu.tsx +281 -83
- package/src/components/editor/floating-building-action-menu.tsx +4 -3
- package/src/components/editor/floorplan-background-selection.ts +113 -0
- package/src/components/editor/floorplan-panel.tsx +10442 -3275
- package/src/components/editor/index.tsx +270 -20
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/selection-manager.tsx +766 -12
- package/src/components/editor/site-edge-labels.tsx +9 -3
- package/src/components/editor/thumbnail-generator.tsx +350 -157
- package/src/components/editor/use-floorplan-background-placement.ts +257 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +171 -0
- package/src/components/editor/use-floorplan-scene-data.ts +189 -0
- package/src/components/editor/wall-measurement-label.tsx +377 -58
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +95 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +160 -0
- package/src/components/editor-2d/floorplan-hotkey-handlers.tsx +92 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +119 -0
- package/src/components/editor-2d/renderers/floorplan-marquee-layer.tsx +58 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +197 -0
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +113 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +474 -0
- package/src/components/editor-2d/svg-paths.ts +119 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +272 -0
- package/src/components/systems/roof/roof-edit-system.tsx +5 -5
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +1 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +2 -0
- package/src/components/tools/ceiling/ceiling-tool.tsx +5 -5
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +257 -0
- package/src/components/tools/column/column-tool.tsx +97 -0
- package/src/components/tools/column/move-column-tool.tsx +105 -0
- package/src/components/tools/door/door-tool.tsx +19 -0
- package/src/components/tools/door/move-door-tool.tsx +38 -8
- package/src/components/tools/fence/curve-fence-tool.tsx +179 -0
- package/src/components/tools/fence/fence-drafting.ts +27 -8
- package/src/components/tools/fence/fence-tool.tsx +159 -3
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +438 -0
- package/src/components/tools/fence/move-fence-tool.tsx +102 -27
- package/src/components/tools/item/move-tool.tsx +19 -1
- package/src/components/tools/item/placement-math.ts +44 -7
- package/src/components/tools/item/placement-strategies.ts +111 -33
- package/src/components/tools/item/placement-types.ts +7 -0
- package/src/components/tools/item/use-draft-node.ts +2 -0
- package/src/components/tools/item/use-placement-coordinator.tsx +701 -61
- package/src/components/tools/roof/move-roof-tool.tsx +111 -43
- package/src/components/tools/shared/polygon-editor.tsx +244 -29
- package/src/components/tools/shared/segment-angle.ts +156 -0
- package/src/components/tools/slab/move-slab-tool.tsx +182 -0
- package/src/components/tools/slab/slab-boundary-editor.tsx +1 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +2 -0
- package/src/components/tools/spawn/move-spawn-tool.tsx +101 -0
- package/src/components/tools/spawn/spawn-tool.tsx +130 -0
- package/src/components/tools/stair/stair-tool.tsx +11 -3
- package/src/components/tools/tool-manager.tsx +30 -3
- package/src/components/tools/wall/curve-wall-tool.tsx +176 -0
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +423 -0
- package/src/components/tools/wall/move-wall-tool.tsx +356 -0
- package/src/components/tools/wall/wall-drafting.ts +348 -17
- package/src/components/tools/wall/wall-tool.tsx +134 -2
- package/src/components/tools/window/move-window-tool.tsx +28 -0
- package/src/components/tools/window/window-tool.tsx +17 -0
- package/src/components/ui/action-menu/camera-actions.tsx +37 -33
- package/src/components/ui/action-menu/control-modes.tsx +37 -5
- package/src/components/ui/action-menu/index.tsx +91 -1
- package/src/components/ui/action-menu/structure-tools.tsx +2 -0
- package/src/components/ui/action-menu/view-toggles.tsx +424 -35
- package/src/components/ui/command-palette/editor-commands.tsx +27 -5
- package/src/components/ui/command-palette/index.tsx +0 -1
- package/src/components/ui/controls/material-picker.tsx +189 -169
- package/src/components/ui/controls/slider-control.tsx +88 -26
- package/src/components/ui/floating-level-selector.tsx +286 -55
- package/src/components/ui/helpers/helper-manager.tsx +5 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1121 -1219
- package/src/components/ui/item-catalog/item-catalog.tsx +42 -175
- package/src/components/ui/level-duplicate-dialog.tsx +115 -0
- package/src/components/ui/panels/ceiling-panel.tsx +47 -27
- package/src/components/ui/panels/column-panel.tsx +715 -0
- package/src/components/ui/panels/door-panel.tsx +986 -294
- package/src/components/ui/panels/fence-panel.tsx +55 -12
- package/src/components/ui/panels/item-panel.tsx +5 -5
- package/src/components/ui/panels/mobile-panel-sheet.tsx +108 -0
- package/src/components/ui/panels/mobile-selection-bar.tsx +100 -0
- package/src/components/ui/panels/node-display.ts +39 -0
- package/src/components/ui/panels/paint-panel.tsx +138 -0
- package/src/components/ui/panels/panel-manager.tsx +241 -30
- package/src/components/ui/panels/panel-wrapper.tsx +48 -39
- package/src/components/ui/panels/reference-panel.tsx +243 -9
- package/src/components/ui/panels/roof-panel.tsx +30 -62
- package/src/components/ui/panels/roof-segment-panel.tsx +8 -23
- package/src/components/ui/panels/slab-panel.tsx +46 -24
- package/src/components/ui/panels/spawn-panel.tsx +155 -0
- package/src/components/ui/panels/stair-panel.tsx +117 -69
- package/src/components/ui/panels/stair-segment-panel.tsx +13 -27
- package/src/components/ui/panels/wall-panel.tsx +71 -17
- package/src/components/ui/panels/window-panel.tsx +665 -146
- package/src/components/ui/sidebar/mobile-tab-bar.tsx +46 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +77 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +138 -56
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +82 -0
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +12 -6
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/tab-bar.tsx +3 -0
- package/src/components/ui/viewer-toolbar.tsx +96 -2
- package/src/components/viewer-overlay.tsx +25 -19
- package/src/hooks/use-auto-frame.ts +45 -0
- package/src/hooks/use-contextual-tools.ts +14 -13
- package/src/hooks/use-keyboard.ts +67 -9
- package/src/hooks/use-mobile.ts +12 -12
- package/src/index.tsx +2 -1
- package/src/lib/door-interaction.ts +88 -0
- package/src/lib/floorplan/geometry.ts +263 -0
- package/src/lib/floorplan/index.ts +38 -0
- package/src/lib/floorplan/items.ts +179 -0
- package/src/lib/floorplan/selection-tool.ts +231 -0
- package/src/lib/floorplan/stairs.ts +478 -0
- package/src/lib/floorplan/types.ts +57 -0
- package/src/lib/floorplan/walls.ts +23 -0
- package/src/lib/guide-events.ts +10 -0
- package/src/lib/history.ts +20 -0
- package/src/lib/level-duplication.test.ts +72 -0
- package/src/lib/level-duplication.ts +153 -0
- package/src/lib/local-guide-image.ts +42 -0
- package/src/lib/material-paint.ts +284 -0
- package/src/lib/roof-duplication.ts +214 -0
- package/src/lib/scene-bounds.test.ts +183 -0
- package/src/lib/scene-bounds.ts +169 -0
- package/src/lib/sfx-player.ts +96 -13
- package/src/lib/stair-duplication.ts +126 -0
- package/src/lib/window-interaction.ts +86 -0
- package/src/store/use-editor.tsx +279 -15
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
type AttachmentSide,
|
|
7
|
-
type MaterialSchema,
|
|
8
7
|
type StairSegmentNode,
|
|
9
8
|
StairSegmentNode as StairSegmentNodeSchema,
|
|
10
9
|
type StairSegmentType,
|
|
@@ -16,8 +15,6 @@ import { useCallback } from 'react'
|
|
|
16
15
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
17
16
|
import useEditor from '../../../store/use-editor'
|
|
18
17
|
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
19
|
-
import { MaterialPicker } from '../controls/material-picker'
|
|
20
|
-
import { MetricControl } from '../controls/metric-control'
|
|
21
18
|
import { PanelSection } from '../controls/panel-section'
|
|
22
19
|
import { SegmentedControl } from '../controls/segmented-control'
|
|
23
20
|
import { SliderControl } from '../controls/slider-control'
|
|
@@ -35,25 +32,24 @@ const ATTACHMENT_SIDE_OPTIONS: { label: string; value: AttachmentSide }[] = [
|
|
|
35
32
|
]
|
|
36
33
|
|
|
37
34
|
export function StairSegmentPanel() {
|
|
38
|
-
const
|
|
35
|
+
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
|
39
36
|
const setSelection = useViewer((s) => s.setSelection)
|
|
40
|
-
const nodes = useScene((s) => s.nodes)
|
|
41
37
|
const updateNode = useScene((s) => s.updateNode)
|
|
42
38
|
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
43
39
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
: undefined
|
|
40
|
+
const node = useScene((s) =>
|
|
41
|
+
selectedId ? (s.nodes[selectedId as AnyNode['id']] as StairSegmentNode | undefined) : undefined,
|
|
42
|
+
)
|
|
48
43
|
|
|
49
|
-
//
|
|
50
|
-
|
|
44
|
+
// Boolean selector — re-renders only when this segment's position among the
|
|
45
|
+
// parent stair's children flips to/from "first".
|
|
46
|
+
const isFirstSegment = useScene((s) => {
|
|
51
47
|
if (!node?.parentId) return true
|
|
52
|
-
const parent = nodes[node.parentId as AnyNodeId]
|
|
48
|
+
const parent = s.nodes[node.parentId as AnyNodeId]
|
|
53
49
|
if (!parent || parent.type !== 'stair') return true
|
|
54
50
|
const children = (parent as any).children ?? []
|
|
55
51
|
return children[0] === node.id
|
|
56
|
-
})
|
|
52
|
+
})
|
|
57
53
|
|
|
58
54
|
const handleUpdate = useCallback(
|
|
59
55
|
(updates: Partial<StairSegmentNode>) => {
|
|
@@ -63,13 +59,6 @@ export function StairSegmentPanel() {
|
|
|
63
59
|
[selectedId, updateNode],
|
|
64
60
|
)
|
|
65
61
|
|
|
66
|
-
const handleMaterialChange = useCallback(
|
|
67
|
-
(material: MaterialSchema) => {
|
|
68
|
-
handleUpdate({ material })
|
|
69
|
-
},
|
|
70
|
-
[handleUpdate],
|
|
71
|
-
)
|
|
72
|
-
|
|
73
62
|
const handleClose = useCallback(() => {
|
|
74
63
|
setSelection({ selectedIds: [] })
|
|
75
64
|
}, [setSelection])
|
|
@@ -124,7 +113,7 @@ export function StairSegmentPanel() {
|
|
|
124
113
|
}
|
|
125
114
|
}, [selectedId, node, setSelection])
|
|
126
115
|
|
|
127
|
-
if (!node
|
|
116
|
+
if (!(node && node.type === 'stair-segment' && selectedId)) return null
|
|
128
117
|
|
|
129
118
|
return (
|
|
130
119
|
<PanelWrapper
|
|
@@ -243,7 +232,7 @@ export function StairSegmentPanel() {
|
|
|
243
232
|
</PanelSection>
|
|
244
233
|
|
|
245
234
|
<PanelSection title="Position">
|
|
246
|
-
<
|
|
235
|
+
<SliderControl
|
|
247
236
|
label="X"
|
|
248
237
|
max={50}
|
|
249
238
|
min={-50}
|
|
@@ -257,7 +246,7 @@ export function StairSegmentPanel() {
|
|
|
257
246
|
unit="m"
|
|
258
247
|
value={Math.round(node.position[0] * 100) / 100}
|
|
259
248
|
/>
|
|
260
|
-
<
|
|
249
|
+
<SliderControl
|
|
261
250
|
label="Y"
|
|
262
251
|
max={50}
|
|
263
252
|
min={-50}
|
|
@@ -271,7 +260,7 @@ export function StairSegmentPanel() {
|
|
|
271
260
|
unit="m"
|
|
272
261
|
value={Math.round(node.position[1] * 100) / 100}
|
|
273
262
|
/>
|
|
274
|
-
<
|
|
263
|
+
<SliderControl
|
|
275
264
|
label="Z"
|
|
276
265
|
max={50}
|
|
277
266
|
min={-50}
|
|
@@ -331,9 +320,6 @@ export function StairSegmentPanel() {
|
|
|
331
320
|
/>
|
|
332
321
|
</ActionGroup>
|
|
333
322
|
</PanelSection>
|
|
334
|
-
<PanelSection title="Material">
|
|
335
|
-
<MaterialPicker onChange={handleMaterialChange} value={node.material} />
|
|
336
|
-
</PanelSection>
|
|
337
323
|
</PanelWrapper>
|
|
338
324
|
)
|
|
339
325
|
}
|
|
@@ -3,25 +3,49 @@
|
|
|
3
3
|
import {
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
|
-
|
|
6
|
+
getClampedWallCurveOffset,
|
|
7
|
+
getMaxWallCurveOffset,
|
|
8
|
+
getWallCurveLength,
|
|
9
|
+
normalizeWallCurveOffset,
|
|
7
10
|
useScene,
|
|
8
11
|
type WallNode,
|
|
9
12
|
} from '@pascal-app/core'
|
|
10
13
|
import { useViewer } from '@pascal-app/viewer'
|
|
14
|
+
import { Move, Spline } from 'lucide-react'
|
|
11
15
|
import { useCallback } from 'react'
|
|
12
|
-
import {
|
|
16
|
+
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
17
|
+
import useEditor from '../../../store/use-editor'
|
|
18
|
+
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
13
19
|
import { PanelSection } from '../controls/panel-section'
|
|
14
20
|
import { SliderControl } from '../controls/slider-control'
|
|
15
21
|
import { PanelWrapper } from './panel-wrapper'
|
|
16
22
|
|
|
17
23
|
export function WallPanel() {
|
|
18
|
-
const
|
|
24
|
+
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
|
19
25
|
const setSelection = useViewer((s) => s.setSelection)
|
|
20
|
-
const nodes = useScene((s) => s.nodes)
|
|
21
26
|
const updateNode = useScene((s) => s.updateNode)
|
|
27
|
+
const setMovingNode = useEditor((s) => s.setMovingNode)
|
|
28
|
+
const setCurvingWall = useEditor((s) => s.setCurvingWall)
|
|
22
29
|
|
|
23
|
-
const
|
|
24
|
-
|
|
30
|
+
const node = useScene((s) =>
|
|
31
|
+
selectedId ? (s.nodes[selectedId as AnyNode['id']] as WallNode | undefined) : undefined,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Boolean selector — re-renders only when this specific wall's child
|
|
35
|
+
// composition crosses the "has a door/window/wall-item" threshold.
|
|
36
|
+
const hasWallChildrenBlockingCurve = useScene((s) => {
|
|
37
|
+
if (!node) return false
|
|
38
|
+
return (node.children ?? []).some((childId) => {
|
|
39
|
+
const child = s.nodes[childId as AnyNodeId]
|
|
40
|
+
if (!child) return false
|
|
41
|
+
if (child.type === 'door' || child.type === 'window') return true
|
|
42
|
+
if (child.type === 'item') {
|
|
43
|
+
const attachTo = child.asset?.attachTo
|
|
44
|
+
return attachTo === 'wall' || attachTo === 'wall-side'
|
|
45
|
+
}
|
|
46
|
+
return false
|
|
47
|
+
})
|
|
48
|
+
})
|
|
25
49
|
|
|
26
50
|
const handleUpdate = useCallback(
|
|
27
51
|
(updates: Partial<WallNode>) => {
|
|
@@ -55,25 +79,34 @@ export function WallPanel() {
|
|
|
55
79
|
[node, handleUpdate],
|
|
56
80
|
)
|
|
57
81
|
|
|
58
|
-
const handleMaterialChange = useCallback(
|
|
59
|
-
(material: MaterialSchema) => {
|
|
60
|
-
handleUpdate({ material })
|
|
61
|
-
},
|
|
62
|
-
[handleUpdate],
|
|
63
|
-
)
|
|
64
|
-
|
|
65
82
|
const handleClose = useCallback(() => {
|
|
66
83
|
setSelection({ selectedIds: [] })
|
|
67
84
|
}, [setSelection])
|
|
68
85
|
|
|
69
|
-
|
|
86
|
+
const handleMove = useCallback(() => {
|
|
87
|
+
if (!node) return
|
|
88
|
+
sfxEmitter.emit('sfx:item-pick')
|
|
89
|
+
setMovingNode(node)
|
|
90
|
+
setSelection({ selectedIds: [] })
|
|
91
|
+
}, [node, setMovingNode, setSelection])
|
|
92
|
+
|
|
93
|
+
const handleCurve = useCallback(() => {
|
|
94
|
+
if (!node) return
|
|
95
|
+
sfxEmitter.emit('sfx:item-pick')
|
|
96
|
+
setCurvingWall(node)
|
|
97
|
+
setSelection({ selectedIds: [] })
|
|
98
|
+
}, [node, setCurvingWall, setSelection])
|
|
99
|
+
|
|
100
|
+
if (!(node && node.type === 'wall' && selectedId)) return null
|
|
70
101
|
|
|
71
102
|
const dx = node.end[0] - node.start[0]
|
|
72
103
|
const dz = node.end[1] - node.start[1]
|
|
73
|
-
const length =
|
|
104
|
+
const length = getWallCurveLength(node)
|
|
74
105
|
|
|
75
106
|
const height = node.height ?? 2.5
|
|
76
107
|
const thickness = node.thickness ?? 0.1
|
|
108
|
+
const curveOffset = getClampedWallCurveOffset(node)
|
|
109
|
+
const maxCurveOffset = getMaxWallCurveOffset(node)
|
|
77
110
|
|
|
78
111
|
return (
|
|
79
112
|
<PanelWrapper
|
|
@@ -113,10 +146,31 @@ export function WallPanel() {
|
|
|
113
146
|
unit="m"
|
|
114
147
|
value={Math.round(thickness * 1000) / 1000}
|
|
115
148
|
/>
|
|
149
|
+
{!hasWallChildrenBlockingCurve && (
|
|
150
|
+
<SliderControl
|
|
151
|
+
label="Curve"
|
|
152
|
+
max={Math.max(0.01, maxCurveOffset)}
|
|
153
|
+
min={-Math.max(0.01, maxCurveOffset)}
|
|
154
|
+
onChange={(v) => handleUpdate({ curveOffset: normalizeWallCurveOffset(node, v) })}
|
|
155
|
+
precision={2}
|
|
156
|
+
step={0.1}
|
|
157
|
+
unit="m"
|
|
158
|
+
value={Math.round(curveOffset * 100) / 100}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
116
161
|
</PanelSection>
|
|
117
162
|
|
|
118
|
-
<PanelSection title="
|
|
119
|
-
<
|
|
163
|
+
<PanelSection title="Actions">
|
|
164
|
+
<ActionGroup>
|
|
165
|
+
<ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
|
|
166
|
+
{!hasWallChildrenBlockingCurve && (
|
|
167
|
+
<ActionButton
|
|
168
|
+
icon={<Spline className="h-3.5 w-3.5" />}
|
|
169
|
+
label="Curve"
|
|
170
|
+
onClick={handleCurve}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
173
|
+
</ActionGroup>
|
|
120
174
|
</PanelSection>
|
|
121
175
|
</PanelWrapper>
|
|
122
176
|
)
|