Dach-Schichtlogik: RoofType/Layer[] wie Decken (3D mehrschichtig)

Kernthema der BIM-Tiefe (Designdoc docs/design/bim-elements-depth-study.md, P0):
Dächer bekommen einen mehrschichtigen Aufbau analog WallType/CeilingType.

- Modell: RoofType { layers: Layer[] }, Project.roofTypes, Roof.roofTypeId
  (Roof.thickness bleibt einschichtiger Fallback). Resolver roofLayers()/
  roofTotalThickness() — wiederverwendet den bestehenden Layer-Typ.
- 3D (emitRoofs): je Schicht ein eigenes Mesh mit Bauteilfarbe, entlang der
  Flächennormalen unter die vorige gestapelt (Eindeckung aussen → Verkleidung
  innen). Ohne roofTypeId einschichtiger thickness-Slab (Default-Dachfarbe).
- UI: Dachtyp-Dropdown im Dach-Panel (leer = einschichtig). Default-Dachtyp
  'Steildach gedämmt' in sampleProject.
- +2 Tests (Schicht-Meshes/Stapelung; einschichtig = Slab+Giebel).

Offen (Designdoc): 2D-Schnitt-Poché der Dachschichten, RoofType-ResourceManager-
Tab, Traufe/Ortgang getrennt. 668/668 grün; 3D-Optik vom Nutzer in Tauri prüfen.
This commit is contained in:
2026-07-10 02:03:55 +02:00
parent e99bb2413f
commit 3a986ecd2f
8 changed files with 197 additions and 26 deletions
+12
View File
@@ -202,6 +202,18 @@ export const sampleProject: Project = {
],
},
],
roofTypes: [
{
id: "roof-warm",
name: "Steildach gedämmt",
// Aussen (Eindeckung) → innen (Verkleidung); Bauteile aus der Bibliothek.
layers: [
{ componentId: "brick", thickness: 0.04 }, // Eindeckung (Ziegel, Platzhalter)
{ componentId: "insulation", thickness: 0.22, jointLineStyleId: "thin" }, // Sparren/Dämmung
{ componentId: "render-int", thickness: 0.0125 }, // Innenverkleidung (GK)
],
},
],
// Bauteil-Typen (Bibliothek) — Tür/Fenster/Treppe, analog Wand-/Deckenstile.
// Elemente referenzieren sie per `typeId`; ohne Referenz gilt Inline-Verhalten.
doorTypes: [
+49 -1
View File
@@ -276,6 +276,21 @@ export interface CeilingType {
layers: Layer[];
}
/**
* Ein Dachtyp (Dachaufbau) = geordneter Schichtaufbau EINES Daches, senkrecht
* zur Dachfläche gestapelt (aussen/Eindeckung → innen/Verkleidung) — das
* geneigte Gegenstück zu `WallType`/`CeilingType`. Nutzt denselben `Layer`-Typ.
* Typische Folge: Eindeckung (Ziegel/Blech) → Konterlattung/Lattung → Unterdach
* → Sparren/Dämmung → Dampfbremse → Innenverkleidung. Ein Dach ohne `roofTypeId`
* fällt auf die einschichtige `Roof.thickness` zurück (rückwärtskompatibel).
*/
export interface RoofType {
id: string;
name: string;
/** Aussen (Eindeckung) → innen (Verkleidung), analog `WallType.layers`. */
layers: Layer[];
}
// ── Bauteil-Typen: Tür / Fenster / Treppe ──────────────────────────────────
// Wiederverwendbare Stile (Presets) für Türen, Fenster und Treppen — analog
// `WallType`/`CeilingType` (Bibliothek im Projekt, referenziert per `typeId`).
@@ -1053,8 +1068,18 @@ export interface Roof {
ridgeAxis: "x" | "y";
/** Traufhöhe (absolutes Z, Meter); fehlt ⇒ Oberkante des Geschosses. */
baseElevation?: number;
/** Dachdicke (Meter, senkrecht zur Fläche gemessen). */
/**
* Dachdicke (Meter, senkrecht zur Fläche) — Fallback/Override. Ohne
* {@link roofTypeId} ist es die einschichtige Gesamtdicke; mit `roofTypeId`
* bleibt es als reiner Fallback bestehen (die Schichtsumme des Typs gilt).
*/
thickness: number;
/**
* Verweis auf einen Dachtyp (Dachaufbau) aus `Project.roofTypes` — mehrschichtig
* (Eindeckung/Lattung/Dämmung/Verkleidung), analog `Ceiling.ceilingTypeId`.
* Fehlt er, gilt die einschichtige {@link thickness}.
*/
roofTypeId?: string;
/** Optionale Übersteuerung der Strich-/Umrissfarbe; sonst Kategorie-Farbe. */
color?: string;
}
@@ -1950,6 +1975,11 @@ export interface Project {
* `Ceiling.wallTypeId` gegen `wallTypes` auf (siehe `getCeilingType`).
*/
ceilingTypes?: CeilingType[];
/**
* Dachtypen (Dachaufbauten) — mehrschichtig, analog `ceilingTypes`. Optional;
* Dächer ohne `roofTypeId` lösen über die einschichtige `Roof.thickness` auf.
*/
roofTypes?: RoofType[];
/**
* Türtypen-Bibliothek (Türstile), analog `wallTypes`. Optional, damit
* bestehende Projekte/Tests ohne `doorTypes` gültig bleiben; Türen ohne
@@ -2110,6 +2140,24 @@ export const ceilingThickness = (project: Project, ceiling: Ceiling): number =>
}
};
/**
* Schichtaufbau eines Daches (aussen/Eindeckung → innen/Verkleidung). Mit
* {@link Roof.roofTypeId} die Schichten des Dachtyps; sonst eine EINSCHICHTIGE
* synthetische Schicht aus {@link Roof.thickness} (leere `componentId` ⇒ der
* Renderer nutzt seine Default-Dachfarbe).
*/
export const roofLayers = (project: Project, roof: Roof): Layer[] => {
if (roof.roofTypeId) {
const rt = (project.roofTypes ?? []).find((t) => t.id === roof.roofTypeId);
if (rt && rt.layers.length > 0) return rt.layers;
}
return [{ componentId: "", thickness: Math.max(0, roof.thickness) }];
};
/** Gesamtdicke eines Daches (Meter, senkrecht zur Fläche) = Summe der Schichten. */
export const roofTotalThickness = (project: Project, roof: Roof): number =>
roofLayers(project, roof).reduce((s, l) => s + Math.max(0, l.thickness), 0);
/**
* Löst den {@link DoorType} einer Tür-Öffnung auf, oder `undefined` (keine
* `typeId` bzw. unbekannt ⇒ Inline-Verhalten). Bewusst NICHT werfend — der