Merge remote-tracking branch 'origin/main' into feat/paint-slots

# Conflicts:
#	packages/core/src/store/use-scene.ts
#	packages/editor/src/components/editor/index.tsx
This commit is contained in:
Wassim SAMAD
2026-06-18 12:22:26 -04:00
415 changed files with 22805 additions and 1414 deletions
+26 -1
View File
@@ -6,6 +6,7 @@ import type {
RoofSegmentNode,
WallNode,
} from '@pascal-app/core'
import { publishOpeningResizeGuides } from '../shared/opening-guides-runtime'
import { readRoofFaceHeightMax, readRoofFaceWidthMax } from '../shared/roof-opening-host'
import { buildRoofWallOpeningCut } from '../shared/roof-wall-opening-cut'
import { scaleHandleHeight } from './door-math'
@@ -21,6 +22,9 @@ const SIDE_HANDLE_OFFSET = 0.24
const HEIGHT_HANDLE_OFFSET = 0.24
const MIN_DOOR_HEIGHT = 0.5
const MIN_DOOR_WIDTH = 0.3
// How far the move cross floats off the wall face (+Z, the door's facing
// normal) so it's grabbable instead of buried in the leaf/frame.
const MOVE_HANDLE_LIFT = 0.12
function readWallLength(door: DoorNodeType, scene: { get: (id: AnyNodeId) => unknown }): number {
if (!door.wallId) return Number.POSITIVE_INFINITY
@@ -55,6 +59,7 @@ function doorWidthHandle(side: 'left' | 'right'): HandleDescriptor<DoorNodeType>
return readWallLength(n, scene)
},
currentValue: (n) => n.width,
onDrag: (node) => publishOpeningResizeGuides(node, false),
apply: (initial, newWidth) => {
// Anchored edge stays fixed in wall-local coords. Door rotation is
// applied by the inner ride group (the renderer mounts a nested
@@ -97,6 +102,7 @@ function doorHeightHandle(): HandleDescriptor<DoorNodeType> {
return Math.max(MIN_DOOR_HEIGHT, readWallHeight(n, scene) - bottom)
},
currentValue: (n) => n.height,
onDrag: (node) => publishOpeningResizeGuides(node, false),
apply: (initial, newHeight) => {
const bottom = initial.position[1] - initial.height / 2
// Scale the handle so it tracks the door instead of staying glued to a
@@ -114,7 +120,26 @@ function doorHeightHandle(): HandleDescriptor<DoorNodeType> {
}
}
// Press-drag move grip at the door centre, standing in the wall face. Routes
// through the same move tool as the floating Move button (3D
// `affordanceTools.move`, 2D `floorplanMoveTarget`) — wall slide + re-host onto
// another wall — but `engageMoveDrag` commits on release, with no second click.
function doorMoveHandle(): HandleDescriptor<DoorNodeType> {
return {
kind: 'tap-action',
shape: 'move-cross',
plane: 'node-normal',
portal: 'grandparent',
cursor: 'move',
onActivate: (node, _scene, editor) => editor.engageMoveDrag(node),
placement: {
position: () => [0, 0, MOVE_HANDLE_LIFT],
},
}
}
const doorHandles: HandleDescriptor<DoorNodeType>[] = [
doorMoveHandle(),
doorWidthHandle('left'),
doorWidthHandle('right'),
doorHeightHandle(),
@@ -232,7 +257,7 @@ export const doorDefinition: NodeDefinition<typeof DoorNode> = {
presentation: {
label: 'Door',
description: 'A door cut into a wall. Animated open/close state.',
icon: { kind: 'url', src: '/icons/door.png' },
icon: { kind: 'url', src: '/icons/door.webp' },
paletteSection: 'structure',
paletteOrder: 50,
},