# 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 // 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 3–4) 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 0–180 (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 / (steps−1). 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 M–L (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 3–4:** Grip-Editing (§1.4/§4.3), exakte B-Rep-Booleans im Worker.