@pascal-app/editor 0.4.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 +62 -0
- package/src/components/editor/custom-camera-controls.tsx +387 -0
- package/src/components/editor/editor-layout-v2.tsx +220 -0
- package/src/components/editor/export-manager.tsx +78 -0
- package/src/components/editor/first-person-controls.tsx +249 -0
- package/src/components/editor/floating-action-menu.tsx +231 -0
- package/src/components/editor/floorplan-panel.tsx +9609 -0
- package/src/components/editor/grid.tsx +161 -0
- package/src/components/editor/index.tsx +928 -0
- package/src/components/editor/node-action-menu.tsx +66 -0
- package/src/components/editor/preset-thumbnail-generator.tsx +125 -0
- package/src/components/editor/selection-manager.tsx +897 -0
- package/src/components/editor/site-edge-labels.tsx +90 -0
- package/src/components/editor/thumbnail-generator.tsx +166 -0
- package/src/components/editor/wall-measurement-label.tsx +258 -0
- package/src/components/feedback-dialog.tsx +265 -0
- package/src/components/pascal-radio.tsx +280 -0
- package/src/components/preview-button.tsx +16 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +77 -0
- package/src/components/systems/roof/roof-edit-system.tsx +69 -0
- package/src/components/systems/stair/stair-edit-system.tsx +69 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +320 -0
- package/src/components/systems/zone/zone-system.tsx +87 -0
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +42 -0
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +47 -0
- package/src/components/tools/ceiling/ceiling-tool.tsx +465 -0
- package/src/components/tools/door/door-math.ts +110 -0
- package/src/components/tools/door/door-tool.tsx +293 -0
- package/src/components/tools/door/move-door-tool.tsx +373 -0
- package/src/components/tools/item/item-tool.tsx +26 -0
- package/src/components/tools/item/move-tool.tsx +90 -0
- package/src/components/tools/item/placement-math.ts +85 -0
- package/src/components/tools/item/placement-strategies.ts +556 -0
- package/src/components/tools/item/placement-types.ts +117 -0
- package/src/components/tools/item/use-draft-node.ts +227 -0
- package/src/components/tools/item/use-placement-coordinator.tsx +877 -0
- package/src/components/tools/roof/move-roof-tool.tsx +288 -0
- package/src/components/tools/roof/roof-tool.tsx +318 -0
- package/src/components/tools/select/box-select-tool.tsx +626 -0
- package/src/components/tools/shared/cursor-sphere.tsx +119 -0
- package/src/components/tools/shared/polygon-editor.tsx +361 -0
- package/src/components/tools/site/site-boundary-editor.tsx +42 -0
- package/src/components/tools/slab/slab-boundary-editor.tsx +42 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +47 -0
- package/src/components/tools/slab/slab-tool.tsx +322 -0
- package/src/components/tools/stair/stair-defaults.ts +7 -0
- package/src/components/tools/stair/stair-tool.tsx +194 -0
- package/src/components/tools/tool-manager.tsx +120 -0
- package/src/components/tools/wall/wall-drafting.ts +140 -0
- package/src/components/tools/wall/wall-tool.tsx +210 -0
- package/src/components/tools/window/move-window-tool.tsx +410 -0
- package/src/components/tools/window/window-math.ts +117 -0
- package/src/components/tools/window/window-tool.tsx +303 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +39 -0
- package/src/components/tools/zone/zone-tool.tsx +364 -0
- package/src/components/ui/action-menu/action-button.tsx +59 -0
- package/src/components/ui/action-menu/camera-actions.tsx +74 -0
- package/src/components/ui/action-menu/control-modes.tsx +240 -0
- package/src/components/ui/action-menu/furnish-tools.tsx +102 -0
- package/src/components/ui/action-menu/index.tsx +152 -0
- package/src/components/ui/action-menu/structure-tools.tsx +100 -0
- package/src/components/ui/action-menu/view-toggles.tsx +397 -0
- package/src/components/ui/command-palette/editor-commands.tsx +396 -0
- package/src/components/ui/command-palette/index.tsx +730 -0
- package/src/components/ui/controls/action-button.tsx +33 -0
- package/src/components/ui/controls/material-picker.tsx +194 -0
- package/src/components/ui/controls/metric-control.tsx +262 -0
- package/src/components/ui/controls/panel-section.tsx +65 -0
- package/src/components/ui/controls/segmented-control.tsx +45 -0
- package/src/components/ui/controls/slider-control.tsx +245 -0
- package/src/components/ui/controls/toggle-control.tsx +38 -0
- package/src/components/ui/floating-level-selector.tsx +355 -0
- package/src/components/ui/helpers/ceiling-helper.tsx +20 -0
- package/src/components/ui/helpers/helper-manager.tsx +33 -0
- package/src/components/ui/helpers/item-helper.tsx +40 -0
- package/src/components/ui/helpers/roof-helper.tsx +16 -0
- package/src/components/ui/helpers/slab-helper.tsx +20 -0
- package/src/components/ui/helpers/wall-helper.tsx +20 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1580 -0
- package/src/components/ui/item-catalog/item-catalog.tsx +219 -0
- package/src/components/ui/panels/ceiling-panel.tsx +230 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +356 -0
- package/src/components/ui/panels/door-panel.tsx +600 -0
- package/src/components/ui/panels/item-panel.tsx +306 -0
- package/src/components/ui/panels/panel-manager.tsx +59 -0
- package/src/components/ui/panels/panel-wrapper.tsx +80 -0
- package/src/components/ui/panels/presets/presets-popover.tsx +511 -0
- package/src/components/ui/panels/reference-panel.tsx +177 -0
- package/src/components/ui/panels/roof-panel.tsx +262 -0
- package/src/components/ui/panels/roof-segment-panel.tsx +326 -0
- package/src/components/ui/panels/slab-panel.tsx +228 -0
- package/src/components/ui/panels/stair-panel.tsx +304 -0
- package/src/components/ui/panels/stair-segment-panel.tsx +339 -0
- package/src/components/ui/panels/wall-panel.tsx +123 -0
- package/src/components/ui/panels/window-panel.tsx +441 -0
- package/src/components/ui/primitives/button.tsx +69 -0
- package/src/components/ui/primitives/card.tsx +75 -0
- package/src/components/ui/primitives/color-dot.tsx +61 -0
- package/src/components/ui/primitives/context-menu.tsx +227 -0
- package/src/components/ui/primitives/dialog.tsx +129 -0
- package/src/components/ui/primitives/dropdown-menu.tsx +228 -0
- package/src/components/ui/primitives/error-boundary.tsx +52 -0
- package/src/components/ui/primitives/input.tsx +21 -0
- package/src/components/ui/primitives/number-input.tsx +187 -0
- package/src/components/ui/primitives/opacity-control.tsx +79 -0
- package/src/components/ui/primitives/popover.tsx +42 -0
- package/src/components/ui/primitives/separator.tsx +28 -0
- package/src/components/ui/primitives/sheet.tsx +130 -0
- package/src/components/ui/primitives/shortcut-token.tsx +64 -0
- package/src/components/ui/primitives/sidebar.tsx +855 -0
- package/src/components/ui/primitives/skeleton.tsx +13 -0
- package/src/components/ui/primitives/slider.tsx +58 -0
- package/src/components/ui/primitives/switch.tsx +29 -0
- package/src/components/ui/primitives/tooltip.tsx +57 -0
- package/src/components/ui/scene-loader.tsx +40 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +103 -0
- package/src/components/ui/sidebar/icon-rail.tsx +147 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +100 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +438 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +188 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +80 -0
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +126 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +64 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +1543 -0
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +98 -0
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +117 -0
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +65 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +214 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +96 -0
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +216 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +115 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +342 -0
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +271 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +106 -0
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +64 -0
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +87 -0
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +167 -0
- package/src/components/ui/sidebar/tab-bar.tsx +39 -0
- package/src/components/ui/slider-demo.tsx +36 -0
- package/src/components/ui/slider.tsx +81 -0
- package/src/components/ui/viewer-toolbar.tsx +342 -0
- package/src/components/viewer-overlay.tsx +499 -0
- package/src/components/viewer-zone-system.tsx +48 -0
- package/src/contexts/presets-context.tsx +121 -0
- package/src/hooks/use-auto-save.ts +194 -0
- package/src/hooks/use-contextual-tools.ts +52 -0
- package/src/hooks/use-grid-events.ts +106 -0
- package/src/hooks/use-keyboard.ts +214 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-reduced-motion.ts +20 -0
- package/src/index.tsx +33 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/level-selection.ts +31 -0
- package/src/lib/scene.ts +394 -0
- package/src/lib/sfx/index.ts +2 -0
- package/src/lib/sfx-bus.ts +49 -0
- package/src/lib/sfx-player.ts +60 -0
- package/src/lib/utils.ts +43 -0
- package/src/store/use-audio.tsx +45 -0
- package/src/store/use-command-registry.ts +36 -0
- package/src/store/use-editor.tsx +522 -0
- package/src/store/use-palette-view-registry.ts +45 -0
- package/src/store/use-upload.ts +90 -0
- package/src/three-types.ts +3 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DoorNode,
|
|
3
|
+
ItemNode,
|
|
4
|
+
RoofNode,
|
|
5
|
+
RoofSegmentNode,
|
|
6
|
+
StairNode,
|
|
7
|
+
StairSegmentNode,
|
|
8
|
+
WindowNode,
|
|
9
|
+
} from '@pascal-app/core'
|
|
10
|
+
import { Vector3 } from 'three'
|
|
11
|
+
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
12
|
+
import useEditor from '../../../store/use-editor'
|
|
13
|
+
import { MoveDoorTool } from '../door/move-door-tool'
|
|
14
|
+
import { MoveRoofTool } from '../roof/move-roof-tool'
|
|
15
|
+
import { MoveWindowTool } from '../window/move-window-tool'
|
|
16
|
+
import type { PlacementState } from './placement-types'
|
|
17
|
+
import { useDraftNode } from './use-draft-node'
|
|
18
|
+
import { usePlacementCoordinator } from './use-placement-coordinator'
|
|
19
|
+
|
|
20
|
+
function getInitialState(node: {
|
|
21
|
+
asset: { attachTo?: string }
|
|
22
|
+
parentId: string | null
|
|
23
|
+
}): PlacementState {
|
|
24
|
+
const attachTo = node.asset.attachTo
|
|
25
|
+
if (attachTo === 'wall' || attachTo === 'wall-side') {
|
|
26
|
+
return { surface: 'wall', wallId: node.parentId, ceilingId: null, surfaceItemId: null }
|
|
27
|
+
}
|
|
28
|
+
if (attachTo === 'ceiling') {
|
|
29
|
+
return { surface: 'ceiling', wallId: null, ceilingId: node.parentId, surfaceItemId: null }
|
|
30
|
+
}
|
|
31
|
+
return { surface: 'floor', wallId: null, ceilingId: null, surfaceItemId: null }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function MoveItemContent({ movingNode }: { movingNode: ItemNode }) {
|
|
35
|
+
const draftNode = useDraftNode()
|
|
36
|
+
|
|
37
|
+
const meta =
|
|
38
|
+
typeof movingNode.metadata === 'object' && movingNode.metadata !== null
|
|
39
|
+
? (movingNode.metadata as Record<string, unknown>)
|
|
40
|
+
: {}
|
|
41
|
+
const isNew = !!meta.isNew
|
|
42
|
+
|
|
43
|
+
const cursor = usePlacementCoordinator({
|
|
44
|
+
asset: movingNode.asset,
|
|
45
|
+
draftNode,
|
|
46
|
+
// Duplicates start fresh in floor mode; wall/ceiling draft is created lazily by ensureDraft
|
|
47
|
+
initialState: isNew
|
|
48
|
+
? { surface: 'floor', wallId: null, ceilingId: null, surfaceItemId: null }
|
|
49
|
+
: getInitialState(movingNode),
|
|
50
|
+
// Preserve the original item's scale so Y-position calculations use the correct height
|
|
51
|
+
defaultScale: isNew ? movingNode.scale : undefined,
|
|
52
|
+
initDraft: (gridPosition) => {
|
|
53
|
+
if (isNew) {
|
|
54
|
+
// Duplicate: use the same create() path as ItemTool so ghost rendering works correctly.
|
|
55
|
+
// Floor items get a draft immediately; wall/ceiling items are created lazily on surface entry.
|
|
56
|
+
gridPosition.copy(new Vector3(...movingNode.position))
|
|
57
|
+
if (!movingNode.asset.attachTo) {
|
|
58
|
+
draftNode.create(gridPosition, movingNode.asset, movingNode.rotation, movingNode.scale)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
draftNode.adopt(movingNode)
|
|
62
|
+
gridPosition.copy(new Vector3(...movingNode.position))
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
onCommitted: () => {
|
|
66
|
+
sfxEmitter.emit('sfx:item-place')
|
|
67
|
+
useEditor.getState().setMovingNode(null)
|
|
68
|
+
return false
|
|
69
|
+
},
|
|
70
|
+
onCancel: () => {
|
|
71
|
+
draftNode.destroy()
|
|
72
|
+
useEditor.getState().setMovingNode(null)
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return <>{cursor}</>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const MoveTool: React.FC = () => {
|
|
80
|
+
const movingNode = useEditor((state) => state.movingNode)
|
|
81
|
+
|
|
82
|
+
if (!movingNode) return null
|
|
83
|
+
if (movingNode.type === 'door') return <MoveDoorTool node={movingNode as DoorNode} />
|
|
84
|
+
if (movingNode.type === 'window') return <MoveWindowTool node={movingNode as WindowNode} />
|
|
85
|
+
if (movingNode.type === 'roof' || movingNode.type === 'roof-segment')
|
|
86
|
+
return <MoveRoofTool node={movingNode as RoofNode | RoofSegmentNode} />
|
|
87
|
+
if (movingNode.type === 'stair' || movingNode.type === 'stair-segment')
|
|
88
|
+
return <MoveRoofTool node={movingNode as StairNode | StairSegmentNode} />
|
|
89
|
+
return <MoveItemContent movingNode={movingNode as ItemNode} />
|
|
90
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isObject } from '@pascal-app/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Snaps a position to 0.5 grid, with an offset to align item edges to grid lines.
|
|
5
|
+
* For items with dimensions like 2.5, the center would be at 1.25 from the edge,
|
|
6
|
+
* which doesn't align with 0.5 grid. This adds an offset so edges align instead.
|
|
7
|
+
*/
|
|
8
|
+
export function snapToGrid(position: number, dimension: number): number {
|
|
9
|
+
const halfDim = dimension / 2
|
|
10
|
+
const needsOffset = Math.abs(((halfDim * 2) % 1) - 0.5) < 0.01
|
|
11
|
+
const offset = needsOffset ? 0.25 : 0
|
|
12
|
+
return Math.round((position - offset) * 2) / 2 + offset
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Snap a value to 0.5 increments (used for wall-local positions).
|
|
17
|
+
*/
|
|
18
|
+
export function snapToHalf(value: number): number {
|
|
19
|
+
return Math.round(value * 2) / 2
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Calculate cursor rotation in WORLD space from wall normal and orientation.
|
|
24
|
+
*/
|
|
25
|
+
export function calculateCursorRotation(
|
|
26
|
+
normal: [number, number, number] | undefined,
|
|
27
|
+
wallStart: [number, number],
|
|
28
|
+
wallEnd: [number, number],
|
|
29
|
+
): number {
|
|
30
|
+
if (!normal) return 0
|
|
31
|
+
|
|
32
|
+
// Wall direction angle in world XZ plane
|
|
33
|
+
const wallAngle = Math.atan2(wallEnd[1] - wallStart[1], wallEnd[0] - wallStart[0])
|
|
34
|
+
|
|
35
|
+
// In local wall space, front face has normal.z < 0, back face has normal.z > 0
|
|
36
|
+
if (normal[2] < 0) {
|
|
37
|
+
return -wallAngle
|
|
38
|
+
}
|
|
39
|
+
return Math.PI - wallAngle
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Calculate item rotation in WALL-LOCAL space from normal.
|
|
44
|
+
* Items are children of the wall mesh, so their rotation is relative to wall's local space.
|
|
45
|
+
*/
|
|
46
|
+
export function calculateItemRotation(normal: [number, number, number] | undefined): number {
|
|
47
|
+
if (!normal) return 0
|
|
48
|
+
|
|
49
|
+
return normal[2] > 0 ? 0 : Math.PI
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Determine which side of the wall based on the normal vector.
|
|
54
|
+
* In wall-local space, the wall runs along X-axis, so the normal points along Z-axis.
|
|
55
|
+
* Positive Z normal = 'front', Negative Z normal = 'back'
|
|
56
|
+
*/
|
|
57
|
+
export function getSideFromNormal(normal: [number, number, number] | undefined): 'front' | 'back' {
|
|
58
|
+
if (!normal) return 'front'
|
|
59
|
+
return normal[2] >= 0 ? 'front' : 'back'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if the normal indicates a valid wall side face (front or back).
|
|
64
|
+
* Filters out top face and thickness edges.
|
|
65
|
+
*
|
|
66
|
+
* In wall-local geometry space (after ExtrudeGeometry + rotateX):
|
|
67
|
+
* - X axis: along wall direction
|
|
68
|
+
* - Y axis: up (height)
|
|
69
|
+
* - Z axis: perpendicular to wall (thickness direction)
|
|
70
|
+
*
|
|
71
|
+
* So valid side faces have normals pointing in ±Z direction (local space).
|
|
72
|
+
*/
|
|
73
|
+
export function isValidWallSideFace(normal: [number, number, number] | undefined): boolean {
|
|
74
|
+
if (!normal) return false
|
|
75
|
+
return Math.abs(normal[2]) > 0.7
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Strip the `isTransient` flag from node metadata before committing.
|
|
80
|
+
*/
|
|
81
|
+
export function stripTransient(meta: any): any {
|
|
82
|
+
if (!isObject(meta)) return meta
|
|
83
|
+
const { isTransient, ...rest } = meta as Record<string, any>
|
|
84
|
+
return rest
|
|
85
|
+
}
|