Files
DOSSIER-STANDALONE/docs/design/elements.md
T
karim ca859c4aa4 Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge
Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit
Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem,
dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en)
sowie Projektdokumentation und Probe-Harness.
2026-06-30 20:52:27 +02:00

425 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design — Bauteile (Elements)
> Teil der Standalone-Architektur — siehe [../../ARCHITECTURE.md](../../ARCHITECTURE.md).
> Output/Pläne: [plans-output.md](plans-output.md). Ressourcen/Stile:
> [resources-graphics.md](resources-graphics.md).
Dieses Dokument legt die **Daten**, die **Generierung** (3D-Geometrie + Plan-
Symbolik) und das **Grip-Editing** je Bauteil fest und übersetzt DOSSIERs
`elemente.py` (7244 LOC, Monolith) in **kleine Bauteil-Module** (`src/model/
elements/wall.ts`, `opening.ts`, …). Bezeichner englisch, Prosa deutsch, Meter.
DOSSIERs Architektur dort: pro Element eine **Achse/Outline-Source** (editierbar)
+ ein **auto-generiertes Volumen** (`wand_axis`+`wand_volume`, Outline+Brep).
Browser-Äquivalent: das **semantische Element ist die Source**; Geometrie wird per
`generate*()` **abgeleitet** (nie persistiert). Das ist sauberer als DOSSIERs
zwei-Objekt-Modell und kennt kein Cache-Stale.
---
## 0. Gemeinsames Fundament
```ts
// src/model/elements/base.ts
interface ElementBase {
id: string;
type: ElementType; // "wall" | "window" | "door" | "slab" | "stair" | "roof"
// | "column" | "beam" | "space" | "draw2d"
floorId: string; // Zeichnungsebene (Geschoss); bei gehosteten via Host
categoryCode: string; // Ebene (Grafik-Kategorie), z.B. "20"
styleId?: string; // optionaler Element-Override-Stil (resources-graphics.md)
name?: string;
}
```
**Geometrie-Konvention** (aus CONVENTIONS.md, im Spike etabliert): Wand-Normale
`n = leftNormal(u) = (-u.y, u.x)`; bei CCW-Wicklung zeigt `+n` nach innen.
Schichten werden außen (`-T/2`) → innen (`+T/2`) gestapelt (`generatePlan.addWallPoche`,
`Viewport3D.addWallMeshes`).
**Detailgrad** (LoD) — DOSSIERs `darstellung` (`auto|einfach|standard|detail`):
```ts
type DetailLevel = "coarse" | "medium" | "fine"; // einfach | standard | detail
// Auflösung: Element-Wert "auto" → Dokument-/Snapshot-Wert; sonst Element-Wert.
function resolveDetail(el: ElementBase, doc: { detailLevel: DetailLevel }): DetailLevel
```
≙ DOSSIER `_resolve_oeff_darstellung` + `get_aktive_darstellung`. Steuert, *wie
viel* Symbolik gezeichnet wird (1:500 Rechteck → 1:50 Glas/Sims/Schwenkbogen).
**Generierungs-Signaturen** (jedes Modul exportiert beides):
```ts
function build3d(project, el, ctx): THREE.Object3D // Volumen (Schichten/Brep)
function generatePlan(project, el, ctx, lod): Primitive[] // Schnittflächen + Symbol
// ctx trägt baseElevation, joins, sichtbare Codes, resolver für Components/Styles
```
---
## 1. Wand (Wall) — mehrschichtig
### 1.1 Daten
```ts
interface Wall extends ElementBase {
type: "wall";
start: Vec2; end: Vec2; // Achse (Centerline) im Grundriss [im Spike]
wallTypeId: string; // → WallType.layers (außen→innen)
height: number;
reference: "mid" | "left" | "right"; // Referenzlage der Achse (DOSSIER _wand_referenz)
baseOffset?: number; // UK relativ zu OKFF (default 0)
topOffset?: number; // OK-Override (default = floorHeight)
jointRole?: "auto" | "through" | "butt"; // T-Stoss-Rolle (DOSSIER wand_joint_rolle)
// Mehrsegment-Wände (Polyline): optional axisPoints statt start/end
axisPoints?: Vec2[];
}
```
`reference` verschiebt die Achse auf Außenkante/Mitte (DOSSIER
`_wall_offsets_from_referenz`): hilft beim Modellieren *und* beim Import fremder
Pläne (ROADMAP §11). Offsets: `mid → [+T/2, -T/2]`, `left → [0, -T]`, `right → [+T, 0]`.
### 1.2 Generierung — 3D + Plan (Status: im Spike, einschichtig→mehrschichtig ✅)
Beide Sichten extrudieren/füllen **dasselbe gehrte Band-Polygon** pro Schicht.
Heute schon vorhanden:
- `geometry.clippedBand(start, end, offA, offB, startCut, endCut)` — Band mit
Gehrungsschnitt.
- `generatePlan.addWallPoche` — pro Schicht ein gefülltes Polygon (Component-Fill +
Schraffur), Öffnungen ausgespart.
- `Viewport3D.addLayerPrism` — dasselbe Polygon via `ExtrudeGeometry`.
### 1.3 Wand-Verschneidung (Joins) — Risiko #1
**Status: L-Ecken-Gehrung ✅** (`joins.computeJoins``miterLine`, robust gegen
Wicklung + ungleiche Dicken). **Offen: Prioritäts-T-/X-Stöße** bei mehrschichtigen
Wänden.
DOSSIERs gelöste Logik (`elemente._t_junction_layer_overrides`, `_wand_should_apply_t_miter`),
die wir portieren:
1. **Knoten finden:** Endpunkte auf Gitter runden (`roundKey`, existiert),
gruppieren. `==1` freies Ende, `==2` L-Ecke (Gehrung, ✅), `>2` T/X.
2. **Through-Wand bestimmen:** an einem T-Stoß läuft genau **eine** Wand durch.
Auswahl nach `jointRole` (DOSSIER-Regel), sonst nach Component-`joinPriority`:
```
my.role="through" → ich laufe durch (kein Miter)
my.role="butt" → ich stoße an (Miter)
beide "auto" → höhere joinPriority = Through-Wand
```
3. **Schicht-Durchdringung (Backbone):** **nur das Material mit der höchsten
gemeinsamen `joinPriority`** in *beiden* Wänden läuft durch und unioniert
(T-Form). Beispiel ROADMAP §2d: Beton (800) läuft mittig durch; Putze (100)
verbinden sich seitlich, gehen aber nirgends durch den Beton. Alle Nicht-
Backbone-Schichten der anstoßenden Wand mitern an der Through-Außenkante
(`standard_miter`). Ergebnis: gleichfarbige Außenlagen bilden automatisch
saubere L-Stöße.
```ts
// joins.ts — Erweiterung der bestehenden API
interface WallCuts { startCut: Line|null; endCut: Line|null;
// neu: pro-Schicht Overrides am T-Stoss
layerExtensions?: number[]; // wie weit jede Schicht in Through-Body drillt
layerMiters?: (Line|null)[]; // pro-Schicht Mitre (null = Backbone, läuft durch)
}
function computeJoins(project, walls): Map<string, WallCuts> // erweitert
```
**Implementierungsplan (stufenweise, Risiko #1):**
- (a) ✅ L-Gehrung bleibt.
- (b) T-Stoß ohne Schichten: Backbone = ganze Wand; Through union, Stem mitert.
- (c) T-Stoß mit Schichten: Backbone-Material-Logik wie oben (Port von
`_t_junction_layer_overrides`).
- (d) X-Stoß: paarweise als zwei T behandeln.
- **Booleans:** Union/Extension der Backbone-Säule via **OpenCascade.js/Manifold**
im Worker (`workers/geometry.worker.ts`), nur für 3D + exakten B-Rep-Export; der
2D-Plan bleibt rein analytisch (Polygon-Clipping, kein Kernel) — schnell.
- **Validierung:** Screenshot-Probe der T-Ecke (Beton durch, Putz seitlich).
### 1.4 Grip-Editing (Risiko #L, Phase 34)
DOSSIER: Display-Conduit zeichnet dicke Marker an Achs-Endpunkten, MouseCallback
fängt Klick → `GetPoint` mit Snap → `_replace_axis_vertex` → Volumen regeneriert
(`wand_grips.py`). Browser-Port:
- **Marker:** SVG-Kreise (r ≈ 7 px) an Endpunkten/Knicks der *selektierten* Wand,
als Overlay über dem Plan (unabhängig von Ebenen-Sichtbarkeit) — exakt DOSSIERs
Conduit-Idee.
- **Hit-Test:** Pointer-Distanz < 14 px (DOSSIER `_HIT_RADIUS_PX`).
- **Drag:** `pointerdown` auf Marker → Live-Preview-Linien zu Nachbar-Vertices →
Snap (Endpunkt/Ortho/Raster) → `pointerup` → `store.apply(p => wall.start = newPt)`.
Abgeleitete Sichten (Plan + 3D) re-derivieren reaktiv — kein manuelles Regen.
- Funktioniert für Line (2 Grips) und Polyline (jeder Knick ein Grip), wie DOSSIER.
---
## 2. Öffnungen (Window / Door) — gehostet, LoD
### 2.1 Daten
```ts
interface OpeningBase extends ElementBase {
hostWallId: string; // Host-Wand (Geschoss ergibt sich daraus) [im Spike]
position: number; // Abstand entlang Wandachse vom Wand-Start (m)
width: number; height: number;
reference: "mid" | "left" | "right"; // Lage des Klickpunkts in der Öffnung
detailLevel: DetailLevel | "auto";
frame?: { width: number; depth: number; pos: "outer"|"mid"|"inner"; offset: number };
outerSide: "left" | "right"; // welche Wandseite ist außen
}
interface Window extends OpeningBase {
type: "window";
sill: number; // Brüstungshöhe
sashes: 1|2|3|4; // Flügelzahl
sillProfileOut?: "none"|"narrow"|"standard"|"wide"; // Sims außen (DOSSIER _OEFF_SIMS_STYLES)
sillProfileIn?: "none"|"narrow"|"standard"|"wide";
glass: boolean;
}
interface Door extends OpeningBase {
type: "door";
swing: "left" | "right"; // Anschlagseite [im Spike]
hinge: "start" | "end"; // Scharnierpfosten [im Spike]
openAngle: number; // Plan-Öffnungswinkel 0180 (default 90)
doorType: "normal" | "wall-opening"; // Wandöffnung = ohne Blatt
frameType: "casing" | "block"; // Zarge | Blockrahmen
lintel?: "none"|"inner"|"outer"|"both";// Sturzlinien-Anzeige (DOSSIER _OEFF_STURZ)
}
```
Felder 1:1 aus DOSSIERs `_OEFF_*`-Keys + `_OEFF_STYLE_FIELDS`. Presets (Fenster
Standard/Gross/Bandlage, Tür Innen/Eingang/Verglast, Wandöffnung) als
Style-Katalog (resources-graphics.md), seed wie `_OEFF_DEFAULT_STYLES`.
### 2.2 Host-Beziehung (Risiko #2)
Die Öffnung kennt ihre Wand (`hostWallId`); ihre Geometrie wird **relativ zur
Wandachse** berechnet (`opening.axisFrame(wall, position)` → Punkt + Tangente +
Normale, ≙ DOSSIER `_oeff_axis_frame`). Verschiebt sich die Wand, folgt die
Öffnung automatisch (sie hält keinen absoluten Punkt). Beim Plan/3D wird die
Wand an `[position, position+width]` ausgespart — steht im Spike (`addWallPoche`
Segmentierung, `addWallMeshes` Sturz).
### 2.3 Generierung nach LoD
| LoD | Plan-Symbol | 3D |
|---|---|---|
| **coarse** (1:200/500) | Öffnung als Lücke + dünne Linie | Aussparung, kein Rahmen |
| **medium** (1:100) | + Rahmenlinien, Tür-Schwenkbogen (`addDoorSymbol` ✅), Sturz gestrichelt | Aussparung + einfacher Rahmen-Quader |
| **fine** (1:50) | + Glas-Doppellinie, Sims, Flügel-Teilung, Anschlag | Rahmen + Blatt + Glas (transparent) + Sims (DOSSIER `_OEFF_PIECE_DEFS`) |
- **Tür-Schwenkbogen:** im Spike (`generatePlan.addDoorSymbol` — Blatt + Arc).
Ausbau: `openAngle`, lichte vs. volle Breite je LoD (Port von
`_make_tuer_swing_curves`).
- **3D-Stücke** (Rahmen/Glas/Flügel/Sims/Sturz) ≙ DOSSIER `_make_oeffnung_pieces`
/ `_OEFF_PIECE_DEFS` — jeweils eigene Component (Farbe + Transparenz: Glas
α≈0.88, IOR 1.5). Pieces landen auf Unter-Ebenen von `21 Türen/Fenster`.
---
## 3. Decke / Boden (Slab) — mit Aussparungen
### 3.1 Daten
```ts
interface Slab extends ElementBase {
type: "slab";
boundary: Vec2[]; // geschlossener Umriss (CCW)
slabTypeId: string; // mehrschichtig (analog WallType)
openings?: Vec2[][]; // Aussparungen: Treppenauge, Schacht, Kamin (DOSSIER aussp)
ukOverride?: number; okOverride?: number; // UK/OK statt auto (Abhängung, schräge Brüstung)
}
```
### 3.2 Generierung
- **Z-Auflösung:** `okOverride ?? (baseElevation_oberes_Geschoss)`,
`ukOverride ?? (ok - thickness)` — Port von `_resolve_decke_z`. Decke sitzt
standardmäßig zwischen zwei Geschossen.
- **3D:** `boundary` als `THREE.Shape`, Aussparungen als `shape.holes` (`THREE.Path`),
`ExtrudeGeometry` über die Schichten (≙ `_make_decke_volume(outline, holes)`).
- **Plan:** im Schnitt unter `cutHeight` meist nur Kante; Aussparungs-Ränder als
Linien; geschnittene Decke (in Schnitt-Ansicht) bekommt Schraffur.
- **Aussparung↔Decke:** Aussparung als geschlossene Curve, die räumlich in der
Decke liegt (`_find_decke_containing_point` / `_find_aussparungen_for_decke`).
Bei uns: `Slab.openings` direkt im Slab — keine separate Source nötig (einfacher
als DOSSIERs Parent-Child).
---
## 4. Treppe (Stair) — Typen, Lauflinie, geschossübergreifend
### 4.1 Daten
```ts
interface Stair extends ElementBase {
type: "stair";
kind: "straight" | "l-shaped" | "spiral"; // gerade | L | Wendel (DOSSIER _TREPPE_ARTEN)
run: Vec2[]; // Lauflinien-Stützpunkte (gerade: 2; L: 3; Wendel: Zentrum+Start)
width: number;
reference: "mid" | "left" | "right"; // Lage der Lauflinie zur Treppe
steps: number; // Anzahl Steigungen
mode: "solid" | "flat" | "slab-edge"; // massiv | flach | Plattenrand
runSlabThickness?: number; // Lauf-Plattendicke
floorEndId?: string; // Zielgeschoss (geschossübergreifend, Risiko #6)
heightOverride?: number; ukOverride?: number;
rules?: { riser:[lo,hi,on]; tread:[lo,hi,on]; stepGo:[lo,hi,on] }; // SIA-Komfortregeln
lockRiser?: { value: number }; // Schrittmass-Lock (S fix, N passt sich an)
// Plan-Symbol-Flags (DOSSIER _KEY_TREPPE_SHOW_*)
show?: { treads; runLine; outline; breakLine };
upperDashed?: boolean; // obere Stufen gestrichelt (über Schnitthöhe)
arrowStyle?: "classic"|"filled"|"double"|"line";
}
```
### 4.2 Generierung
- **Steigung/Auftritt:** `riser = height/steps`; `tread` aus Lauflinienlänge /
(steps1). SIA-Komfort: `2·riser + tread ∈ [0.60, 0.65]` (DOSSIER
`_TREPPE_SOLL_DEFAULT`). Lock: ist `lockRiser` gesetzt, wird `steps` neu
berechnet statt `riser` zu ändern.
- **3D je `kind`:** gerade → Stapel von Tritt-Quadern oder massive Rampe;
L → zwei Läufe + Podest (`podestMin`); Wendel → um Zentrum rotierte Tritte
(Port `_make_treppe_*_preview` / Volume-Funktionen). `mode` steuert massiv vs.
Lauf-Platte.
- **Geschossübergreifend (Risiko #6):** Höhe = `(baseElevation[floorEndId] -
baseElevation[floorId])` falls `floorEndId` gesetzt; sonst Geschosshöhe. Treppe
taucht dann in beiden Geschoss-Grundrissen auf (mit Schnitt an `cutHeight`).
- **Plan-Symbol (normgerecht):** Lauflinie mit **Auf-/Abpfeil** (`arrowStyle`),
Stufenkanten, Bruchlinie an `cutHeight` (untere durchgezogen, obere gestrichelt
via `upperDashed`), Außenkante. ≙ DOSSIERs 2D-Treppensymbol; liegt auf Ebene
`40 Treppen`/`41 Treppen-2D`.
### 4.3 Grip-Editing
Lauflinien-Stützpunkte als Grips (wie Wand-Vertices, §1.4); Ziehen ändert
Geometrie + Stufenzahl reaktiv.
---
## 5. Dach (Roof)
### 5.1 Daten
```ts
interface Roof extends ElementBase {
type: "roof";
outline: Vec2[]; // Grundriss-Umriss
roofType: "mono" | "gable" | "hip" | "mansard"; // Pult|Sattel|Walm|Mansarde
thickness: number;
slope: number; // Grad (Hauptneigung)
eaveIndex?: number; // Index der Traufkante (Pult)
ridge?: "long" | "short"; // Firstrichtung (Sattel)
// Mansarde:
slopeLower?: number; kinkHeight?: number;
mansardVariant?: "hip" | "gable" | "hip-gable";
}
```
### 5.2 Generierung
Port von DOSSIERs `_make_pultdach/_satteldach/_walmdach/_mansardendach*` +
`_thicken_roof_inward`. Aufwand ML (Mansarde später). Reihenfolge: Pult →
Sattel → Walm → Mansarde. 3D als Brep/Mesh über OpenCascade.js (Worker), da
Schräg-Verschneidung Booleans braucht. Plan: Firstlinien + Traufe + ggf.
Höhenkoten.
---
## 6. Tragwerk (Column / Beam)
### 6.1 Daten
```ts
interface ProfileDef {
shape: "square"|"rect"|"round"|"i-beam"|"tube"; // DOSSIER _TRAG_PROFILE
b?: number; h?: number; d?: number; t?: number; // Breite/Höhe/Durchm./Wanddicke
angle: number; // Rotation um Z
}
interface Column extends ElementBase { type:"column"; point: Vec2; profile: ProfileDef;
uk?: number; ok?: number; }
interface Beam extends ElementBase { type:"beam"; axis:[Vec2,Vec2]; profile: ProfileDef;
zTop?: number; // hängt unter Decken-OK (zTop = ok der Decke)
}
```
### 6.2 Generierung
- **Querschnitt:** `profileCurve(shape, b,h,d,t, angle)` (Port `_trag_profile_curve`)
→ für Stütze entlang Z extrudieren (`_make_stuetze_volume`), für Träger entlang
der Achse (`_make_traeger_volume`, Profil in der Schnitt-Ebene).
- **Träger achs-basiert unter Decke:** `zTop` default = OK der darüberliegenden
Decke → Unterzug folgt automatisch (weniger Update-Fehler, ROADMAP §11).
- Stützen liegen auf `25 Stützen`, Träger auf `35 Träger`.
---
## 7. Raum (Space) — SIA-416 + Stempel
### 7.1 Daten
```ts
interface Space extends ElementBase {
type: "space";
boundary: Vec2[]; // geschlossener Umriss
number?: string; spaceName?: string; function?: string;
sia?: "" | "HNF"|"NNF"|"VF"|"FF"|"GF"|"AGF"; // SIA-416-Klasse
persons?: number; // Personenbelegung (Brandschutz)
areaRounding: "exact"|"0.01"|"0.1"|"0.5"|"1";
stamp: StampConfig; // Raumstempel-Layout (s.u.)
fill?: string; // Füll-Hatch-Id (optional)
}
interface StampConfig { // ≙ DOSSIER Stempel-Builder
layout: FieldId[][]; // Zeilen × Felder, z.B. [["number","name"],["function"],["area"]]
font; bold; italic; textHeight; textMode:"fixed"|"scale"; align:"left"|"mid"|"right";
offset: Vec2; // Stempel-Position relativ zum Centroid (User-Move)
}
type FieldId = "number"|"name"|"function"|"area"|"sia";
```
### 7.2 Generierung & Bilanz
- **Fläche:** Shoelace-Formel über `boundary`, gerundet nach `areaRounding`
(`_resolve_raum_rundung`). Umfang analog.
- **Stempel:** als SVG-Text-Block aus `layout`-Zeilen am Centroid + `offset`
(User kann verschieben; Offset persistiert wie DOSSIER `stamp_dx/dy`).
`textMode:"scale"` → Texthöhe in Paper-mm × Massstab (plans-output.md).
- **SIA-Färbung:** über die **Overrides-Engine** (regelbasiert), nicht hartcodiert
— DOSSIER `_build_sia_preset_rules` erzeugt 4 Regeln `userString sia == hnf|nnf|vf|ff`
→ Farbe + Solid-Hatch. Bei uns: ein Override-Preset „SIA-416" (resources-graphics.md),
das auf `space.sia` matcht. Toggle = Preset aktivieren.
- **SIA-Bilanz + CSV:** `panels/SiaBalance.tsx` summiert Flächen je Klasse je
Geschoss → Tabelle + CSV-Export (`HNF/NNF/VF/FF/GF/AGF`). Pflicht für
CH-Flächennachweis (ROADMAP ⭐).
---
## 8. Werkzeuge (Tools) — ersetzt Rhino-Command-Aliases
DOSSIER hat pro Bauteil ein Command-Alias (`rhino/aliases/cmd/wand.py`, `tuer.py`,
`treppe.py`, …) das `GetPoint`-Interaktionen fährt. Browser: ein **Tool-Interface**
mit Pointer-Handlern + Snap.
```ts
interface Tool {
id: ToolId;
onPointerDown(pt: Vec2, snap: SnapResult, state): void;
onPointerMove(pt: Vec2, snap: SnapResult, state): Primitive[]; // Live-Preview
onPointerUp(pt: Vec2, snap: SnapResult, state): void;
commit(store): void; // ruft store.apply()
}
```
| Tool | DOSSIER-Alias | Kurzbeschrieb |
|---|---|---|
| `wall` | `cmd/wand` | Achse zeichnen (Linie/Polyline), Dicke/Referenz/Typ aus „last used" |
| `door`/`window` | `cmd/tuer`,`fenster` | Punkt auf Wandachse → hosten (Snap an Wand) |
| `slab` | `cmd/decke` | Umriss klicken; Aussparung als Loch |
| `stair` | `cmd/treppe` | Lauflinie + Breite + Stufen |
| `roof` | `cmd/dach` | Umriss + Typ + Neigung |
| `column`/`beam` | `cmd/stuetze`,`traeger` | Punkt / Achse + Profil |
| `space` | `cmd/raum` | Umriss → Fläche auto, Stempel |
| `draw2d` | `cmd/symbol`,`stempel` | Linie/Polyline/Rect/Kreis/Bogen/Text auf `60 Plangrafik` |
| `pipette` | `cmd/pipette` | Stil/Typ von Element übernehmen |
**Snap-Engine** (`tools/snap.ts`): Endpunkt, Mitte, Schnitt, senkrecht, Raster,
Ortho — ersetzt Rhinos OSnap. T-Snap an andere Wandachsen (Port
`_t_snap_to_wand_axis`, `_snap_endpoint_to_other_wand_axis`) sorgt für saubere
Knoten.
---
## 9. Element-Übersicht (BIM-Tree)
`panels/ElementTree.tsx`: Baum Geschoss → Bauteiltyp → Element, mit Suche und
Shift-Klick = Zoom (DOSSIER ELEMENTE-ÜBERSICHT). Inhaltsverzeichnis bei 100+
Elementen — reine Ableitung aus `project.elements`.
---
## 10. Reihenfolge der Umsetzung (verweist auf ROADMAP-Phasen)
1. **Phase 1:** Wand mehrschichtig ✅ + L-Gehrung ✅ → **Prio-T-Stoß** (§1.3);
Tür/Fenster gehostet (§2); Decke + Aussparung (§3); Wand-Referenzlage (§1.1);
Element-Übersicht (§9).
2. **Phase 2:** Treppe (§4), Dach (§5), Tragwerk (§6), SIA-Räume + Stempel (§7),
Stil-Kataloge.
3. **Phase 34:** Grip-Editing (§1.4/§4.3), exakte B-Rep-Booleans im Worker.