Fix spawn floorplan move handle

This commit is contained in:
Aymeric Rabot
2026-06-10 14:11:50 -04:00
parent 3a1ba56246
commit 5fd9be05df
4 changed files with 48 additions and 0 deletions
@@ -31,6 +31,7 @@ describe('spawn definition', () => {
expect(spawnDefinition.schemaVersion).toBe(1)
expect(spawnDefinition.category).toBe('site')
expect(spawnDefinition.schema).toBe(SpawnNode)
expect(typeof spawnDefinition.floorplanMoveTarget).toBe('function')
})
test('defaults() returns a value that the schema accepts', () => {
@@ -111,6 +112,7 @@ describe('spawn definition', () => {
const flat = flattenFloorplan(geometry)
expect(flat.some((entry) => entry.kind === 'path' && entry.stroke === '#818cf8')).toBe(true)
expect(flat.some((entry) => entry.kind === 'move-handle')).toBe(true)
expect(flat.some((entry) => entry.kind === 'rotate-arrow')).toBe(true)
})
+2
View File
@@ -1,6 +1,7 @@
import type { HandleDescriptor, NodeDefinition, SpawnNode as SpawnNodeType } from '@pascal-app/core'
import { buildSpawnFloorplan } from './floorplan'
import { spawnRotateAffordance } from './floorplan-affordances'
import { spawnFloorplanMoveTarget } from './floorplan-move'
import { spawnParametrics } from './parametrics'
import { SpawnNode } from './schema'
@@ -92,6 +93,7 @@ export const spawnDefinition: NodeDefinition<typeof SpawnNode> = {
// delete. Legacy spawn click handlers in FloorplanNodeLayer become
// dead code once Phase 6 cleanup removes the [] entries path.
floorplan: buildSpawnFloorplan,
floorplanMoveTarget: spawnFloorplanMoveTarget,
floorplanAffordances: {
'spawn-rotate': spawnRotateAffordance,
},
@@ -0,0 +1,39 @@
import {
type AnyNodeId,
type FloorplanMoveTarget,
type FloorplanMoveTargetSession,
type SpawnNode,
snapScalar,
useScene,
} from '@pascal-app/core'
import { getSegmentGridStep } from '@pascal-app/editor'
export const spawnFloorplanMoveTarget: FloorplanMoveTarget<SpawnNode> = ({ node }) => {
const spawnId = node.id as AnyNodeId
const startY = node.position[1]
const originalPosition: [number, number, number] = [...node.position]
let lastPosition: [number, number, number] | null = null
const session: FloorplanMoveTargetSession = {
affectedIds: [spawnId],
apply({ planPoint, modifiers }) {
const step = getSegmentGridStep()
const snap = (value: number) => (modifiers.shiftKey ? value : snapScalar(value, step))
const next: [number, number, number] = [snap(planPoint[0]), startY, snap(planPoint[1])]
if (lastPosition && lastPosition[0] === next[0] && lastPosition[2] === next[2]) return
lastPosition = next
useScene.getState().updateNodes([{ id: spawnId, data: { position: next } }])
},
canCommit() {
if (!lastPosition) return false
return lastPosition[0] !== originalPosition[0] || lastPosition[2] !== originalPosition[2]
},
commit() {
if (!lastPosition) return
useScene.getState().updateNodes([{ id: spawnId, data: { position: lastPosition } }])
},
}
return session
}
+5
View File
@@ -113,6 +113,11 @@ export function buildSpawnFloorplan(node: SpawnNode, ctx: GeometryContext): Floo
]
if (isSelected) {
children.push({
kind: 'move-handle',
point: [px, pz],
})
const cornerLocalX = 0.34 + ROTATE_ARROW_CORNER_OFFSET
const cornerLocalZ = 0.34 + ROTATE_ARROW_CORNER_OFFSET
const [cornerX, cornerZ] = rotatePlanVector(cornerLocalX, cornerLocalZ, planRotation)