ai-chatbot/packages/editor/src/store/use-opening-guides.ts
Aymeric Rabot 6caba97f1b feat(editor): 3D viewport proximity + sill + equal-spacing guides for openings
Wire the opening-guides service into the 3D door/window move tools and render the
wall-plane guides as the spatial twin of the 2D plan guides:

  - sill / head height (floor → bottom edge, top edge → wall top) — windows only
  - edge-to-edge proximity dimensions to the nearest neighbour each side
  - a sill-alignment line + SNAP when a window shares a neighbour's sill / centre
    / top (competes with the 0.5m grid, Shift bypasses) — the chosen
    "snap + guide" behaviour
  - Figma-style equal-spacing "=" badges across a run of openings

Adds `useOpeningGuides` (editor store) + `OpeningGuides3DLayer` (raw THREE.Line
overlays + Html pills, mounted beside Alignment3DGuideLayer) and a thin
`opening-guides-runtime` helper (collect siblings / sill snap / publish / clear)
called from the door + window move-tools at their per-tick `applyPreview` hook;
guides clear on commit / cancel / leave / roof-hover / unmount.

Guides render in the move cursor's building-local frame (reuses `wallLocalToWorld`)
so they track the dragged opening exactly. Codex-reviewed (roof-hover stale-guide
clear, collapsed-dimension suppression). Placement-time guides reuse the same
helper and are the next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:15:52 -04:00

34 lines
1.4 KiB
TypeScript

// Ephemeral store for the 3D opening proximity/alignment guides published by the
// door/window move + placement tools during a drag — the wall-plane counterpart
// of `useAlignmentGuides` (which only carries floor-plane XZ guides). Guides are
// already transformed into the move tool's render frame — the same building-local
// frame as the drag cursor (ToolManager's group) — so the renderer stays dumb.
// Producers clear on commit, cancel, leave, and unmount.
import { create } from 'zustand'
export type OpeningGuideVec3 = [number, number, number]
export type OpeningGuide3D =
// A measured line + distance pill: sill (floor → bottom edge), head (top edge
// → wall top), or along-wall edge-to-edge proximity.
| { kind: 'dimension'; from: OpeningGuideVec3; to: OpeningGuideVec3; value: number }
// A dashed line connecting two openings that share a sill / centre / top.
| { kind: 'align-line'; from: OpeningGuideVec3; to: OpeningGuideVec3 }
// A Figma-style "=" badge marking one gap in an equal-spacing run.
| { kind: 'badge'; at: OpeningGuideVec3; value: number }
type OpeningGuidesState = {
guides: OpeningGuide3D[]
set(guides: OpeningGuide3D[]): void
clear(): void
}
const useOpeningGuides = create<OpeningGuidesState>((set) => ({
guides: [],
set: (guides) => set({ guides }),
clear: () => set({ guides: [] }),
}))
export default useOpeningGuides