feat(editor): placement-time + resize-time opening guides
Extend the opening proximity guides to two more interactions:
- PLACEMENT: the door/window placement tools publish the same 3D guides
(sill/head, edge proximity, sill alignment, equal-spacing) while a NEW
opening is being dropped; window placement also snaps its sill to a
neighbour's sill/centre/top (Shift bypass) — so "two windows aligned" reads
during placement, not just move.
- RESIZE: a new `onDrag` hook on the linear-resize handle descriptor lets the
door/window width/height arrows publish live guides for the edge being
resized — proximity to neighbours as the width grows, and the live sill/head
as a window's height changes. The generic LinearArrow stays kind-agnostic;
only door/window declare the hook.
Refactor (Codex review follow-up): one `publishOpeningGuidesForWallEvent`
wrapper now backs all four wall-event publish sites (door/window move +
placement) over a shared `makeWallToWorld`; window placement's repeated
sill-snap is a single `resolvePlacementY` helper. Opening guides clear on
commit / leave / cancel / roof-hover / unmount (mirroring the alignment-guide
lifecycle) and on resize end.
Codex-reviewed — no blockers; lifecycle/leaks, coordinate frame, sill-snap
precedence, and resize disposal confirmed. Typecheck + biome + 23 core + 170
nodes tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6caba97f1b
commit
86e9b3c8bf
@@ -47,6 +47,7 @@ import { createEditorApi } from '../../lib/editor-api'
|
||||
import { sfxEmitter } from '../../lib/sfx-bus'
|
||||
import useDirectManipulationFeedback from '../../store/use-direct-manipulation-feedback'
|
||||
import useEditor from '../../store/use-editor'
|
||||
import useOpeningGuides from '../../store/use-opening-guides'
|
||||
import { suppressBoxSelectForPointer } from '../tools/select/box-select-state'
|
||||
import { formatAngleRadians } from '../tools/shared/segment-angle'
|
||||
import {
|
||||
@@ -593,6 +594,9 @@ function LinearArrow({
|
||||
// floating dimension pill (via `activeHandleDrag`) and its own in-world
|
||||
// chip is suppressed — matches the wall height handle.
|
||||
const measureLabel = descriptor.kind === 'linear-resize' ? descriptor.measureLabel : undefined
|
||||
// Optional per-tick feedback hook (doors/windows publish proximity/sill guides
|
||||
// for the edge being resized); cleared when the drag ends.
|
||||
const onDrag = descriptor.kind === 'linear-resize' ? descriptor.onDrag : undefined
|
||||
const placementSceneApi = useMemo(() => createSceneApi(useScene), [])
|
||||
const basePosition = descriptor.placement.position(node, placementSceneApi)
|
||||
// `freezeOffset` (in node-local frame) cancels the mesh's `position`
|
||||
@@ -675,6 +679,7 @@ function LinearArrow({
|
||||
if (measureLabel) {
|
||||
useEditor.getState().setActiveHandleDrag(null)
|
||||
}
|
||||
if (onDrag) useOpeningGuides.getState().clear()
|
||||
},
|
||||
move: ({ event: moveEvent, getPointerRay: getMovePointerRay }) => {
|
||||
const currentPointer =
|
||||
@@ -690,7 +695,10 @@ function LinearArrow({
|
||||
? snapScalar(rawNext, gridSnapStep)
|
||||
: rawNext
|
||||
const next = Math.min(maxBound, Math.max(minBound, snappedNext))
|
||||
return descriptor.apply(initialNode as never, next, sceneApi) as Partial<AnyNode>
|
||||
const patch = descriptor.apply(initialNode as never, next, sceneApi) as Partial<AnyNode>
|
||||
// Let the kind publish live guides for the edge being resized.
|
||||
onDrag?.({ ...(initialNode as object), ...patch } as AnyNode, sceneApi)
|
||||
return patch
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,8 +43,8 @@ const mid = (a: OpeningGuideVec3, b: OpeningGuideVec3): OpeningGuideVec3 => [
|
||||
/**
|
||||
* Wall-plane proximity / alignment guides for the 3D editor — the spatial twin
|
||||
* of the floor-plan placement dimensions + equal-spacing badges. Subscribes to
|
||||
* `useOpeningGuides` (published by the door/window move tools each drag tick) and
|
||||
* draws sill/head + edge-proximity dimensions, a sill-alignment line, and
|
||||
* `useOpeningGuides` (published by the door/window move, placement, and resize
|
||||
* interactions each drag tick) and draws sill/head + edge-proximity dimensions, a sill-alignment line, and
|
||||
* equal-spacing badges. Coordinates are already in the move tool's render frame
|
||||
* (the producer reuses the cursor's `wallLocalToWorld`, so they share the cursor's
|
||||
* building-local frame), so this layer mounts beside `Alignment3DGuideLayer` and
|
||||
|
||||
Reference in New Issue
Block a user