feat: move/rotate building + relative positioning for all tools (#220)

Sync monorepo PRs #243 and #248 to the open-source editor.

Move/Rotate Building:
- New move-building-tool with grid snapping, R/T rotation, and Esc cancel
- Floating building action menu with move button
- Building helper with keyboard shortcut hints
- BuildingRenderer now applies position and rotation from node data

Building-Relative Coordinate System:
- GridEvent gains localPosition (building-local coords)
- use-grid-events computes building-local intersection from building mesh
- ToolManager wraps building-relative tools in a building-local <group>
- All tools (wall, slab, ceiling, stair, roof, zone, polygon-editor,
  placement coordinator) updated to use event.localPosition
- Placement coordinator adds worldToBuildingLocal helper for cursor
  position conversion

2D Floorplan Fix:
- SVG layers wrapped in <g transform=rotate(...)> for building rotation
- Pointer-to-plan conversion un-rotates when building is rotated
- Grid events from 2D include localPosition

Store Changes:
- useEditor movingNode union includes BuildingNode
- NodeActionMenu onDelete is now optional (buildings can move but not delete)
This commit is contained in:
Pascal
2026-04-09 14:36:34 -04:00
committed by GitHub
parent 6c3ac3e030
commit 00b8f42899
22 changed files with 521 additions and 107 deletions
+14 -1
View File
@@ -20,7 +20,14 @@ import type { AnyNode } from '../schema/types'
// Base event interfaces
export interface GridEvent {
/** World-space intersection point on the grid plane. */
position: [number, number, number]
/**
* Building-local intersection point — relative to the currently selected building.
* Equals `position` when no building is selected.
* Use this for placing/committing anything that lives inside a building (walls, slabs, items, etc.).
*/
localPosition: [number, number, number]
nativeEvent: ThreeEvent<PointerEvent>
}
@@ -97,6 +104,11 @@ type PresetEvents = {
'preset:thumbnail-updated': { presetId: string; thumbnailUrl: string }
}
type ThumbnailEvents = {
'thumbnail:before-capture': undefined
'thumbnail:after-capture': undefined
}
type EditorEvents = GridEvents &
NodeEvents<'wall', WallEvent> &
NodeEvents<'item', ItemEvent> &
@@ -114,6 +126,7 @@ type EditorEvents = GridEvents &
NodeEvents<'door', DoorEvent> &
CameraControlEvents &
ToolEvents &
PresetEvents
PresetEvents &
ThumbnailEvents
export const emitter = mitt<EditorEvents>()