Skip to content

editor: building rotation, alt-click single wall, world-grid alignment, tap-to-engage move#388

Merged
wass08 merged 35 commits into
pascalorg:mainfrom
sudhir9297:fix/mon-jun-8
Jun 9, 2026
Merged

editor: building rotation, alt-click single wall, world-grid alignment, tap-to-engage move#388
wass08 merged 35 commits into
pascalorg:mainfrom
sudhir9297:fix/mon-jun-8

Conversation

@sudhir9297

Copy link
Copy Markdown
Contributor

What does this PR do?

A batch of editor improvements and architectural cleanups landing the fix/mon-jun-8 line of work:

  • Alt-click single wall placement. Holding Alt on the click that finishes a wall commits a single segment and clears the draft instead of chaining off the new endpoint. No-Alt remains continuous. Wired in both the 3D wall tool and the 2D floor-plan placement hook.
  • Building rotation polish. Drops the 15° step button on the floating building action menu and the building-pivot helper that only existed to support it. Selected-group rotate handle remains the way to rotate a building. Earlier in the branch: consistent rotation pivot + 2D position sync, and a revert of drag-rotate / building-XZ grid chasing that proved unstable.
  • Tap-to-engage 3D move grip. The 3D translate handle now hands the node to the registry move tool — the same path the floating-menu Move button takes via engageMove. One move flow drives both entry points: green bounding box, cursor follow, alignment guides, R/T rotation, click-to-commit. Adds a generic dragBounds capability on the node registry; elevator and stair opt in so the bounding box wraps just the shaft / footprint instead of the full mesh tree.
  • 2D alignment in building-local frame + world-grid wall snap. The 2D alignment-guide layer now mounts inside the rotated scene <g> and reads from an editor-local store; the 3D pipeline keeps its own world-frame store. Pill labels counter-rotate so they stay upright under building rotation. Wall drafting picks up an optional gridSnap override so drafts land on the visible world-XZ grid even when the active building is rotated. Earlier on the branch: world-frame alignment guides and an upright floor-plan text geometry.
  • Roof / ridge / column / shelf placement polish. Roof outer silhouette contributes to alignment-guide candidates. Ridge vent locks to its segment ridge during placement and follows live overrides afterwards. Column and shelf preview a cursor sphere during placement.
  • Architecture review fix-ups. Drops a dead onRotate slot on NodeActionMenu, and memoizes the dragBounds snapshot in MoveRegistryNodeTool so it captures useScene once per node at drag-start instead of reading every render.

How to test

  1. bun dev and open the editor.
  2. Wall placement — start the wall tool, click once, move the cursor, click a second time with Alt held: a single wall lands and the next click starts a fresh segment. Without Alt, clicking continues to chain segments. Verify in both 3D and the 2D floor-plan panel.
  3. Building rotation — select a building and rotate it via the group rotate handle. Confirm the floating action menu no longer shows a rotate button, only Move. The selected-group rotate handle should still work and the building should not drift across rotations.
  4. 2D alignment under building rotation — rotate a building, then move an item / wall / slab in the 2D floor-plan. Red alignment guides should follow the rotated scene; distance pills should stay upright. Wall drafts should snap to the visible world-axis grid lines, not the building-local grid.
  5. 3D move grip — select an elevator (or stair), click the move-cross gizmo: it should hand control to the registry move tool (green bounding box wrapping the shaft / footprint). Same flow as clicking Move in the floating action menu.
  6. Misc placements — drop a column and a shelf: cursor sphere preview should appear during placement. Place a roof + ridge vent: the vent should lock to the ridge during placement and respond to live segment edits after.
  7. bun check-types — should pass.

Screenshots / screen recording

N/A — primarily interactive behavior; will add a recording on request.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

sudhir9297 and others added 30 commits May 19, 2026 02:59
Items (e.g. solar panels) can now be placed on sloped roof surfaces.
The placement system computes euler rotation from the roof surface
normal so items sit flush on the slope instead of going inside.

- Add roofStrategy to placement-strategies with enter/move/click/leave
- Wire roof:enter/move/click/leave events in the placement coordinator
- Add calculateRoofRotation in placement-math using surface normals
- Support full 3D cursor rotation for sloped surfaces
- Items on roofs are parented to the level with world-space rotation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iders after

Placement tool clamps the cursor to the segment's ridge line for all roof
types (gable, hip, shed, gambrel, dutch, mansard; flat rejected). Renderer
re-derives Y from the live surface plus a small lift, and treats stored
position[1] / position[2] as user offsets so the inspector sliders move
the vent after placement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roof has no centred-box footprint (it's the union of its `roof-segment`
children) so the capability bridge needs the resolved AABB directly. We
build it from the children's corners in roof-local space, then transform
to world coords. Roofs only contribute as static candidates — the
move-roof tool drives them by origin, so the relocatable-box path never
applies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ene rotation

Floor-plan text labels were rotating with the 90° default scene rotation,
making zone names read sideways. Add an `upright` flag to text geometry:
when set, the registry layer counter-rotates the label by sceneRotationDeg
around its anchor so it reads horizontally on screen. Zone labels opt in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n paint mode

Paint cursor swaps the inline icon+label chip for a stacked badge — a
glowing accent stem pointing down to the hit point with the paint icon
above — so the cue reads as "pointer" rather than "tooltip". Switches
the accent to indigo. The 2D floor-plan now also shows the paint-icon
overlay and routes pointer interactions to the painter when in
material-paint mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Rotate button to the building action menu (and a generic `onRotate`
slot to NodeActionMenu). Each click rotates the building 90° CW around
its world-bbox center, mirroring the offset compensation
`MoveBuildingContent` uses during R/T-rotate drags — so the building
spins in place instead of orbiting its (often off-centre) origin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the placement affordance other floor-placed tools already have
so users see the snap point as a discrete dot alongside the ghost mesh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next.js moved generated route types under `.next/dev/types/` — regenerate
the reference so the type-check resolves them again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…follow

While dragging a roof-segment resize handle (width / depth / pitch /
wallHeight / rotation), the new dimensions stream through
useLiveNodeOverrides and only flush to the store on release — so the
ridge vent was snapping to the new ridge only after the drag ended.
Merge segment overrides into the renderer's segment view so the vent
rides the resize in real time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cuts the floating building menu's rotate-step from 90° to 15° so each
click nudges the building rather than slamming it to the next cardinal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ng rotation

Brings the 2D floor-plan alignment + grid behaviour in line with 3D so
guides and snap follow the world XZ grid regardless of how the active
building is rotated.

Core: adds resolveAlignmentInBuildingWorld + snapWorldXZToBuildingLocal,
plus a shared BuildingPose type, so callers can resolve in world space
while staying in building-local coords downstream.

Editor: introduces packages/editor/src/lib/world-grid-snap.ts as the
single entry point for tools that need world-frame alignment / grid
snap (resolveAlignmentForActiveBuilding, getActiveBuildingPose,
snapWorldXZForActiveBuilding, snapBuildingLocalToWorldGrid). Honours
useLiveTransforms and resolves the active building via level.parentId
first so the helper stays in sync with the floor-plan panel.

Alignment store unification: applyFloorplanAlignment and the registry
move overlay now publish guides in world XZ — the same frame the 3D
tools already use — so the shared useAlignmentGuides store carries one
consistent coordinate system. The 2D layer renders world XZ via the
fixed world → SVG transform (a 90° rotation around the building's
world position; independent of building rotation), so guides come out
parallel to the world-axis-aligned floor-plan grid in every case.

Drafting + move sessions across walls, fences, slabs, ceilings,
columns, items, shelves, roofs, stairs, zones and elevators are
re-routed through the new helpers so cursor placement, grid snap,
draft endpoints, and move commits all land on the world XZ grid
even when the building has been rotated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two behaviours added recently were causing the world grid to slide
around during building edits. Reverts both, keeping the rest of the
world-frame alignment work intact.

- floating-building-action-menu / node-action-menu: roll the rotate
  button back to a single click that increments rotation by 15°
  around the bbox center (state at 3eb02fd). The drag-rotate from
  b50c0d5 streamed live position updates per frame, which the
  floor plan and grid both followed.
- grid: stop chasing the active building's world XZ each frame
  (pre-c00a2469 behaviour). The grid stays anchored at world (0, 0)
  so it doesn't visibly sweep when the building origin moves —
  during a drag, an R/T rotation in the move tool, or a click
  rotate that re-anchors the origin to the bbox-pivot. Y still
  lerps to the active level's floor height.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Several connected fixes to building rotate/move so the 3D mesh, the
2D floor plan, and alignment guides all stay visually consistent
across rotations and drags.

Rotation-invariant pivot
- New `getBuildingLocalBboxCenter` helper walks descendant mesh
  geometry bounds in the building's LOCAL frame, returning a
  centroid that doesn't depend on the building's current world
  rotation. The previous approach derived the pivot from the world
  AABB at the current rotation, which made the local pivot subtly
  drift each time the building was rotated.
- Floating menu click-rotate and move-tool R/T rotation now seed
  their pivot from this helper, so successive rotations stay
  pinned at the same world point.

Move-tool stability
- R/T rotation always pivots around the current world bbox center,
  even before the cursor has moved onto the grid (previously it
  fell back to rotating around the origin in that case).
- Commit (`onGridClick`) now writes the mesh's actual on-screen
  pose instead of recomputing it from the click event's grid
  coords, removing a one-cell snap on release.

Alignment resolver
- Tie-break is now perp-first, primary-second. The previous order
  was reliable when wall faces shared an exact axis-aligned coord
  (pre-rotation), but post-rotation float deltas meant primary
  never tied and the resolver could lock onto the far corner of a
  candidate. Perp-first picks the visually closest point.

2D ↔ 3D sync
- The floor-plan SVG scene group now applies a live `translate(...)`
  derived from `(committedBuildingPosition → live buildingPosition)`,
  in addition to the existing live rotation. Building-local content
  slides in lockstep with the 3D mesh during a drag.
- The alignment-guide layer projects from world XZ using the
  COMMITTED building position, so guides stay locked to their world
  coordinates while the building slides past them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	packages/editor/src/components/editor/floorplan-panel.tsx
#	packages/editor/src/components/tools/elevator/elevator-tool.tsx
#	packages/editor/src/components/tools/registry/move-registry-node-tool.tsx
#	packages/editor/src/components/tools/shared/polygon-editor.tsx
#	packages/editor/src/components/tools/stair/stair-tool.tsx
#	packages/editor/src/components/tools/tool-manager.tsx
#	packages/editor/src/components/tools/wall/wall-drafting.ts
#	packages/editor/src/index.tsx
#	packages/editor/src/lib/floorplan/apply-alignment.ts
#	packages/nodes/src/building/move-tool.tsx
#	packages/nodes/src/ceiling/move-tool.tsx
#	packages/nodes/src/ceiling/tool.tsx
#	packages/nodes/src/column/floorplan-move.ts
#	packages/nodes/src/column/move-tool.tsx
#	packages/nodes/src/column/tool.tsx
#	packages/nodes/src/fence/actions/move-endpoint.ts
#	packages/nodes/src/fence/tool.tsx
#	packages/nodes/src/item/floorplan-move.ts
#	packages/nodes/src/ridge-vent/move-tool.tsx
#	packages/nodes/src/shared/move-roof-tool.tsx
#	packages/nodes/src/shared/polygon-centroid-move.ts
#	packages/nodes/src/shelf/tool.tsx
#	packages/nodes/src/slab/tool.tsx
#	packages/nodes/src/wall/move-endpoint-tool.tsx
#	packages/nodes/src/wall/tool.tsx
Holding Alt on the click that completes a segment now stops drafting
instead of starting a new segment from the just-placed endpoint. Same
behaviour wired in both the 3D wall tool and the 2D floor-plan
placement hook (which already passed `singleWall: event.altKey` —
the 2D handler was ignoring the flag).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the rotate button on the floating building menu and the
`getBuildingLocalBboxCenter` pivot helper that only existed to support
it. The selected-group rotate handle remains the way to rotate a
building.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sudhir9297 and others added 5 commits June 9, 2026 14:32
…dev/types

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ngage

The translate gizmo on the 3D bounding box now hands the node to the
registry move tool (same path as the floating action menu's Move
button) instead of running its own plane-drag. One move flow — green
bounding box, cursor follow, alignment guides, R/T rotation,
click-to-commit — drives both entry points.

Adds a `dragBounds` capability on the node registry so kinds whose
rendered mesh contains extras (per-level landing assemblies on an
elevator, etc.) can declare the box the user thinks of as "the thing
being dragged." Elevator and stair opt in; everything else still
auto-measures. `MoveRoofTool` now shows the green box for whole-stair
and whole-roof moves, and aligns roofs by their footprint corners
(previously only stairs did). Slab floor-plan moves clear
`autoFromWalls` on commit so space-detection doesn't snap the slab
back, via a new `extraCommitFields` hook on the polygon-centroid
move target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d wall snap

The 2D floor-plan alignment layer now mounts inside the rotated scene
<g> and reads from an editor-local `useAlignmentGuides` store —
guides are stored in building-local meters and follow the SVG
transform that already carries the rest of the floor-plan geometry.
Pill labels are counter-rotated by the scene rotation so they stay
upright under building rotation. The 3D pipeline keeps its own
world-frame store; the two surfaces no longer share frames.

Wall drafting picks up an optional `gridSnap` override so the 2D
floor-plan can snap drafts onto the world-XZ grid even when the
active building is rotated — without it, a rotated building's
drafted wall would chase the local grid and miss the visible lines.
`snapPointTo45Degrees` and `snapWallDraftPointDetailed` route through
the override when supplied; otherwise the prior local-axis grid snap
is unchanged. The floor-plan panel wires `snapBuildingLocalToWorldGrid`
through the placement hook and exports `WALL_GRID_STEP` to support it.

Also fixes the minor/major grid stroke palette swap so minor lines
render with the minor palette and majors with the major palette.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the unreachable `onRotate` prop, `RotateCw` import, and
rotate button from `NodeActionMenu` — its only caller was the
floating building menu's 15° rotate, which was removed in d65fd5f.

Memoizes `MoveRegistryNodeTool`'s `dragBounds` lookup so it captures
the scene snapshot once per node instead of re-reading
`useScene.getState().nodes` on every render. Bounds depend only on
`node` (locked for the tool's lifetime) and start-time sibling state
(elevator shaft height from the level set), so a one-shot snapshot is
the right semantics — and it avoids implying a live subscription that
isn't there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auto-fixes from `bun check:fix`:
- import-order normalization in editor/nodes/core
- unused `snapToGrid` / `useLayoutEffect` imports dropped
- minor whitespace/wrapping tweaks

No behavioural change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wass08 wass08 self-assigned this Jun 9, 2026
@wass08 wass08 merged commit 555cf06 into pascalorg:main Jun 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants