Files
DOSSIER-STANDALONE/src/tools/types.ts
T
karim 3d2d4d6321 2D-Plan-Renderer auf WebGL2 (GPU) + akkumulierter Funktionsstand
Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung
(konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im
Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text-
Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback,
falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform)
fuer fluessige Interaktion ohne React-Re-Render je Frame.

Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM
(Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext-
Import, Tauri-Compute-Boundary-PoC).
2026-07-02 00:12:39 +02:00

202 lines
6.9 KiB
TypeScript

// Tool-System für das aktive Zeichnen im Grundriss (docs/design/drawing-tools.md).
//
// Werkzeuge sind reine Funktionen über einen internen `ToolState`
// (Discriminated Union je Werkzeug). Sie greifen NUR über `commit(project)`
// immutabel auf das Modell zu und schreiben nie Plan-Primitive — die Ansicht
// bleibt abgeleitet. Bezeichner englisch, UI-Text via t().
import type { DrawingLevel, Project, Vec2 } from "../model/types";
/** Werkzeug-Identität. */
export type ToolId =
| "select"
| "wall"
| "ceiling"
| "window"
| "door"
| "stair"
| "room"
| "line"
| "polyline"
| "rect";
// ── Snapping ───────────────────────────────────────────────────────────────
export type SnapKind =
| "endpoint"
| "midpoint"
| "intersection"
| "center"
| "quadrant"
| "onEdge"
| "grid"
| "ortho"
| "extension";
export interface SnapResult {
/** Gefangener Punkt (Meter). */
point: Vec2;
kind: SnapKind;
/** Quell-Bezugspunkt (für Hilfslinien bei ortho/extension), optional. */
refA?: Vec2;
/** Bildschirm-Distanz Cursor→Snap (px) — für die Auswahl des Besten. */
distPx: number;
}
export interface SnapSettings {
enabled: boolean;
endpoint: boolean;
midpoint: boolean;
intersection: boolean;
center: boolean;
onEdge: boolean;
grid: boolean;
/** Rasterweite in Metern. */
gridSize: number;
/** Ortho/Winkelraster (Shift erzwingt zusätzlich). */
ortho: boolean;
/** Winkelraster in Grad (z. B. 90 = H/V). */
angleStep: number;
/** Fangradius am Bildschirm in Pixeln. */
tolerancePx: number;
}
/** Default-Snap-Einstellungen (endpoint/midpoint/intersection/onEdge + grid). */
export const DEFAULT_SNAP: SnapSettings = {
enabled: true,
endpoint: true,
midpoint: true,
intersection: true,
center: false,
onEdge: true,
grid: true,
gridSize: 0.1,
ortho: false,
angleStep: 90,
tolerancePx: 12,
};
// ── Werkzeug-Schnittstelle ──────────────────────────────────────────────────
/** Live-Kontext, den ein Werkzeug bei jedem Schritt erhält. */
export interface ToolContext {
project: Project;
/** Aktives Geschoss/die aktive Zeichnungsebene (Ziel der neuen Elemente). */
level: DrawingLevel;
/** Default-Kategorie-Code für neue Elemente. */
defaultCategoryCode: string;
/** Aktiver Wandtyp für das Wand-Werkzeug. */
activeWallTypeId: string;
/** Aktiver Linienstil-Code für 2D-Primitive. */
activeLineStyleId: string;
}
/** Ein an einer Modellposition ausgelöstes Pointer-Ereignis (fertig gesnappt). */
export interface ToolPointer {
/** Roher Modellpunkt (vor Snapping), in Metern. */
raw: Vec2;
/** Gesnappter Punkt + Marker-Info; null = kein Snap. */
snap: SnapResult | null;
/** Effektiver Punkt = snap?.point ?? raw. */
point: Vec2;
shift: boolean;
ctrl: boolean;
alt: boolean;
/** 0 = links, 2 = rechts. */
button: number;
}
/**
* Leichte Vorschau-Form (Rubber-Band). Bewusst NICHT der volle `Primitive`-Typ
* (kein HatchRender nötig); PlanView zeichnet sie mit einer Vorschau-CSS-Klasse
* (gestrichelt, Akzentfarbe). Modell-Meter; PlanView projiziert via toScreen.
*/
export type DraftShape =
| { kind: "line"; a: Vec2; b: Vec2 }
| { kind: "poly"; pts: Vec2[]; closed?: boolean };
/** Darstellbare Vorschau (Rubber-Band). */
export interface ToolDraft {
/** Vorschau-Formen (gestrichelt/halbtransparent gezeichnet). */
preview: DraftShape[];
/** Bereits gesetzte „feste" Stützpunkte (kleine Quadrate). */
vertices: Vec2[];
/** Aktiver Snap (für den Bildschirm-Marker), optional. */
snap?: SnapResult | null;
/** Optionaler Maß-/Winkel-Text am Cursor (z. B. „3.20 m · 90°"). */
hud?: { at: Vec2; text: string };
}
/** Was ein Werkzeug-Schritt nach außen meldet. */
export interface ToolResult {
/** Neuer Vorschau-Zustand; null = nichts zu zeigen. */
draft: ToolDraft | null;
/** Bei Abschluss: Mutation, die App über setProject anwendet. */
commit?: (p: Project) => Project;
/** true → Werkzeug ist fertig und kehrt in seinen Ruhezustand zurück. */
done?: boolean;
}
/**
* Interner Zustand eines Werkzeugs. Basis-Form; jedes Werkzeug verfeinert sie zu
* einer eigenen Discriminated Union (z. B. WallState) und castet beim Eintritt.
*/
export interface ToolState {
phase: string;
}
/** Die Werkzeug-Schnittstelle (reine Funktionen über einen internen State). */
export interface Tool {
id: ToolId;
/** UI-Label-Key (i18n), z. B. „tool.wall". */
labelKey: string;
/** Statuszeilen-Hinweis-Key je Phase. */
hintKey: (state: ToolState) => string;
/** Nur auf Geschossen aktiv? (Wand braucht ein Geschoss.) */
floorOnly?: boolean;
/** Initialer Ruhezustand. */
init(): ToolState;
/** Klick (Punkt setzen). */
onClick(state: ToolState, p: ToolPointer, ctx: ToolContext): [ToolState, ToolResult];
/** Bewegung (Hover/Drag): nur Vorschau, nie Commit. */
onMove(state: ToolState, p: ToolPointer, ctx: ToolContext): [ToolState, ToolResult];
/** Doppelklick/Enter/Rechtsklick: mehrteilige Werkzeuge abschließen. */
onCommitGesture(state: ToolState, ctx: ToolContext): [ToolState, ToolResult];
/** Esc: Entwurf verwerfen, im selben Werkzeug bleiben. */
onCancel(state: ToolState): [ToolState, ToolResult];
/** Backspace: letzten gesetzten Punkt zurücknehmen (optional). */
onUndoPoint?(state: ToolState, ctx: ToolContext): [ToolState, ToolResult];
}
// ── PlanView-Brücke ──────────────────────────────────────────────────────────
// PlanView meldet rohe Modellpunkte + die aktuelle Pixel/Meter-Skala nach oben;
// der ToolController (App) snappt, fährt das Werkzeug und hält den `draft`,
// den PlanView als Overlay zeichnet. So bleibt die Snap-/Modell-Logik außerhalb
// der reinen Darstellungs-/Eingabeschicht.
export interface ToolMods {
shift: boolean;
ctrl: boolean;
alt: boolean;
}
export interface ToolHandlers {
/** Hover/Drag: roher Modellpunkt (Meter) + Pixel/Meter + Modifikatoren. */
onToolMove(raw: Vec2, pxPerMeter: number, mods: ToolMods): void;
/** Klick (Punkt setzen). */
onToolClick(raw: Vec2, pxPerMeter: number, mods: ToolMods): void;
/** Doppelklick / Rechtsklick: mehrteiliges Werkzeug abschließen. */
onToolCommit(): void;
/** Aktuelle Vorschau (vom Controller gehalten). */
draft: ToolDraft | null;
}
// ── ID-Vergabe ───────────────────────────────────────────────────────────────
let idCounter = 0;
/** Neue eindeutige ID, konsistent mit der Praxis in App (`prefix-Date-counter`). */
export function uniqueId(prefix: string): string {
idCounter += 1;
return `${prefix}-${Date.now()}-${idCounter}`;
}