d0b9d22141
Die Text-Formatier-Gruppe der Oberleiste formatierte bisher nur den Raum-Stempel. Jetzt ist auch ein selektierter Freitext/Textspalten-Element (Drawing2D shape "text") ein Formatier-Ziel: - Neues optionales Feld Drawing2D-Text.marks (Marks) für einheitliche Formatierung (Schriftfamilie, fett/kursiv/unterstrichen, Farbe). - App.textTarget adaptiert den Text über docFromText/plainText an die Rich-Text-Leiste; die GRÖSSE bleibt bewusst über die Modell-Höhe (Meter), nicht über pt (massstabslos wäre falsch) — sizePt wird verworfen. - generatePlan/PlanView rendern die Marks (fontFamily/-weight/-style/ textDecoration); marks.color hat Vorrang vor der Kategorie-Farbe. +3 Tests. tsc + vitest grün.
2184 lines
89 KiB
TypeScript
2184 lines
89 KiB
TypeScript
// Das semantische Gebäudemodell — der Kern von BIM.
|
||
// Bauteile haben Bedeutung, nicht nur Form. Eine Tür "kennt" ihre Wand,
|
||
// eine Wand "kennt" ihren mehrschichtigen Aufbau (WallType).
|
||
//
|
||
// Dokumentmodell (nach DOSSIER): zwei UNABHÄNGIGE Achsen.
|
||
// 1. Zeichnungsebenen (DrawingLevel) = oberste Schnitte: Geschosse +
|
||
// Schnitte/Ansichten.
|
||
// 2. Ebenen (LayerCategory) = Grafik-Kategorie-Schema, das für jedes
|
||
// Geschoss gilt. Ein Element lebt auf einer Kategorie (code) UND einem
|
||
// Geschoss.
|
||
|
||
export type Vec2 = { x: number; y: number };
|
||
|
||
// SIA-416-Blatt-Kategorie eines Raums. Der reine Rechenkern (geometry/roomArea)
|
||
// definiert den Typ; hier nur der Typ-Import (kein Zyklus zur Laufzeit).
|
||
import type { SiaCategory } from "../geometry/roomArea";
|
||
export type { SiaCategory } from "../geometry/roomArea";
|
||
// Rich-Text-Dokument für den Raum-Stempel (frei editierbarer Teil).
|
||
import type { RichTextDoc, Align, Marks } from "../text/richText";
|
||
export type { RichTextDoc } from "../text/richText";
|
||
// Ansichts-Enums für Ausschnitte/View-Snapshots (Ansichtstyp, Kamera-Preset,
|
||
// Detailgrad). Rein type-only importiert — zur Laufzeit erased, kein Zyklus
|
||
// (TopBar importiert seinerseits nur `Project` als Typ). Wie SiaCategory oben.
|
||
import type { DetailLevel, View3d, ViewType } from "../ui/TopBar";
|
||
|
||
// ── Ressourcen-Bibliotheken (Vectorworks-/DOSSIER-Stil) ────────────────────
|
||
// Verwaltete, per id verwiesene Stil-Ressourcen. Verweis-Kette:
|
||
// Component → Hatch → LineStyle.
|
||
// Darstellung wird beim Rendern aus diesen Ressourcen aufgelöst, nie in die
|
||
// Geometrie eingebacken (siehe docs/design/resources-graphics.md).
|
||
|
||
/** Ein wiederverwendbarer Linienstil (Line Manager). */
|
||
export interface LineStyle {
|
||
id: string;
|
||
name: string;
|
||
/**
|
||
* @deprecated Die Strichstärke gehört NICHT mehr in den Linienstil — es gibt nur
|
||
* „Vollinie/Strich", die tatsächliche Dicke wird per Element-Attribut (bzw.
|
||
* Ebene/Default) aufgelöst. Das Feld bleibt für die Backward-Compat-Auflösung
|
||
* erhalten und dient den Renderern noch als Fallback für die Muster-/Fugen-Stärke
|
||
* (siehe `resolveHatch` → `HatchRender.lineWeight`). Der Linien-Detail-Editor
|
||
* zeigt es nicht mehr an; neue Linienstile bekommen einen Haarlinien-Default.
|
||
* Die echte per-Element-Strichstärken-Auflösung (Attribut → Ebene → Default)
|
||
* folgt separat mit dem By-Layer/By-Object-Refactor — NICHT hier.
|
||
*/
|
||
weight: number;
|
||
/** Farbe (hex). */
|
||
color: string;
|
||
/**
|
||
* Strichmuster in Millimetern, alternierend AN/AUS (`[on, off, on, off, …]`),
|
||
* loopend; `null` = durchgezogen (Volllinie). Konvention des modularen
|
||
* Segment-Systems (siehe `src/ui/lineSegments.ts`): ein AN-Wert von 0 bedeutet
|
||
* einen PUNKT (Dot) — er wird nur mit runder Strichkappe sichtbar (die
|
||
* betroffene Linie erhält dann `stroke-linecap: round`). So bilden sich
|
||
* Volllinie/Strichlinie/Punktlinie/Strich-Punkt und frei modulare Folgen aus
|
||
* Strich/Punkt/Lücke ab. Additiv — kein zusätzliches Feld nötig.
|
||
*/
|
||
dash: number[] | null;
|
||
/**
|
||
* Linien-Typ (additiv, Default `undefined` ⇒ "dash"):
|
||
* • "dash" — gerade Linie mit `dash`-Strichmuster (bestehendes Verhalten).
|
||
* • "zigzag" — der Strich variiert in Y (Zickzack/Welle), Parameter in
|
||
* `zigzag`. Fehlt `kind`, gilt „dash"; die heutigen Renderer ignorieren
|
||
* `kind` und zeichnen weiterhin gerade Striche (Backward-Compat).
|
||
* • "custom" — ein frei gezeichnetes Motiv (offene Polylinie in einer
|
||
* Einheitszelle), das sich entlang der Linie wiederholt/loopt. Parameter in
|
||
* `motif`. „zigzag" bleibt der eigene, parametrische Sonderfall.
|
||
*/
|
||
kind?: "dash" | "zigzag" | "custom";
|
||
/**
|
||
* Parameter der Zickzack-/Wellen-Linie (nur `kind==="zigzag"`), beide in mm
|
||
* Papier: `amplitude` = Ausschlag quer zur Linie, `wavelength` = Periodenlänge
|
||
* entlang der Linie. Fehlt es, wird die Linie gerade gezeichnet.
|
||
*/
|
||
zigzag?: { amplitude: number; wavelength: number };
|
||
/**
|
||
* Frei gezeichnetes Wiederhol-Motiv (nur `kind==="custom"`): eine OFFENE
|
||
* Polylinie in einer Einheitszelle. `points` sind die Stützpunkte in mm Papier;
|
||
* `x` läuft 0..`length` (mm entlang der Linie = Wiederhollänge), `y` = senkrechter
|
||
* Versatz zur Linienachse (mm, +/−). Das Motiv wird alle `length` entlang der
|
||
* Linie gekachelt. Fehlt es, wird die Linie gerade gezeichnet.
|
||
*/
|
||
motif?: { points: Vec2[]; length: number };
|
||
}
|
||
|
||
/** Mögliche Schraffur-Muster im Plan/Schnitt. */
|
||
export type HatchPattern =
|
||
| "none"
|
||
| "solid"
|
||
| "insulation"
|
||
| "diagonal"
|
||
| "crosshatch";
|
||
|
||
/** Eine wiederverwendbare Schraffur (Hatch Manager). */
|
||
export interface HatchStyle {
|
||
id: string;
|
||
name: string;
|
||
/**
|
||
* Schraffur-Typ (additiv, Default `undefined` ⇒ "vector"):
|
||
* • "vector" — Musterlinien (die bestehenden `pattern`/`scale`/`angle`/
|
||
* `relativeToWall`/`lineStyleId` sind die Vektor-Parameter). Untermodus
|
||
* über `lines` (parallel vs. random).
|
||
* • "image" — ein Bild wird als Muster (Pattern-Fill) geladen; Parameter in
|
||
* `image` (Skalierung/Verzerrung/Rotation).
|
||
* Fehlt `kind`, gilt „vector"; heutige Renderer ignorieren `kind` und zeichnen
|
||
* weiterhin nach `pattern` (Backward-Compat).
|
||
*/
|
||
kind?: "vector" | "image";
|
||
/**
|
||
* Untermodus einer Vektor-Schraffur (nur `kind` fehlt/"vector", Default
|
||
* `undefined` ⇒ "parallel"):
|
||
* • "parallel" — regelmäßige Teilung (bestehendes Verhalten).
|
||
* • "random" — zufällig verteilte Striche (z. B. Kies/Splitt).
|
||
*/
|
||
lines?: "parallel" | "random";
|
||
/**
|
||
* Bild-Muster (nur `kind==="image"`). `src` = Data-URL oder Asset-Referenz;
|
||
* `scaleX`/`scaleY` = unabhängige Verzerrung in Breite/Höhe (L×B), `rotation`
|
||
* = Drehung in Grad. Bei `kind==="vector"` ungenutzt.
|
||
*/
|
||
image?: { src: string; scaleX: number; scaleY: number; rotation: number };
|
||
/** Muster-Typ. */
|
||
pattern: HatchPattern;
|
||
/** Grundmaßstab des Musters (1 = Standardteilung). */
|
||
scale: number;
|
||
/** Drehung des Musters in Grad. */
|
||
angle: number;
|
||
/**
|
||
* Wenn `true`, ist `angle` NICHT bildschirmfest, sondern relativ zur Achse der
|
||
* schraffierten Wand: das Muster dreht mit der Wandorientierung mit (z. B. eine
|
||
* Diagonale, die stets 45° zur Wand steht, oder Dämmungsstriche quer durch die
|
||
* Wanddicke). Nur die Wand-Poché wertet dies aus; ohne Wandkontext (z. B. Decke)
|
||
* degradiert es zu einem absoluten `angle`.
|
||
*/
|
||
relativeToWall?: boolean;
|
||
/**
|
||
* @deprecated Schraffuren tragen im Zielmodell KEINE eigene Farbe mehr — die
|
||
* Muster-/Vordergrundfarbe kommt vom Bauteil (`Component.foreground`) bzw. der
|
||
* Attribut-Überschreibung. Feld bleibt für die Backward-Compat-Auflösung als
|
||
* LETZTER Fallback erhalten (siehe Farb-Resolve-Reihenfolge in
|
||
* `plan/generatePlan.ts` → `resolveHatch`). Bei `pattern==="solid"` ist es die
|
||
* Vollfüllfarbe, bei `pattern==="none"` ungenutzt.
|
||
*/
|
||
color: string;
|
||
/** Optionaler Linienstil für die Musterlinien (Line Manager). */
|
||
lineStyleId?: string;
|
||
/**
|
||
* Steuer-Parameter der Random-Vektor-Schraffur (`kind`/"vector" + `lines`===
|
||
* "random", z. B. Kies/Splitt). ALLE additiv & optional — fehlen sie, gilt das
|
||
* bestehende deterministische Streu-Verhalten (Seed aus der Flächen-Bounding-Box,
|
||
* Dichte/Länge aus `scale`). Die Streuung bleibt bei gleichem `seed` + gleicher
|
||
* Fläche reproduzierbar über ALLE Renderpfade (single render truth); KEIN
|
||
* `Math.random()` zur Renderzeit.
|
||
*/
|
||
/** Expliziter Streu-Seed. Wird in den Flächen-Seed eingemischt; „Neu würfeln" setzt einen neuen Wert. */
|
||
seed?: number;
|
||
/** Streudichte als Multiplikator auf den Grundabstand (Default 1; >1 = dichter). */
|
||
density?: number;
|
||
/** Minimale Strichlänge in mm (Papier). Fehlt es, greift die `scale`-abhängige Default-Länge. */
|
||
lengthMin?: number;
|
||
/** Maximale Strichlänge in mm (Papier). Fehlt es, greift die `scale`-abhängige Default-Länge. */
|
||
lengthMax?: number;
|
||
}
|
||
|
||
/**
|
||
* PBR-Material-Karten eines Bauteils (3D-Texturierung). Jede URL verweist auf
|
||
* ein Bild — entweder ein eingebautes Bibliotheks-Asset unter
|
||
* `/assets/materials/...` ODER eine per Nutzer-Upload erzeugte Object-/Data-URL.
|
||
* Fehlende Karten werden weggelassen (z. B. nur `color` bei einem Upload).
|
||
* `sizeM` ist die physische Kantenlänge EINER Texturkachel in Metern (für die
|
||
* korrekte Skalierung der Wiederholung), Default 1.0 m.
|
||
*/
|
||
export interface ComponentMaterial {
|
||
/** Optionaler Verweis auf ein Bibliotheks-Asset (`MaterialAsset.id`). */
|
||
libraryId?: string;
|
||
/** Albedo-/Farb-Karte (map). */
|
||
color?: string;
|
||
/** Normal-Karte (normalMap) — erzeugt die Oberflächentiefe. */
|
||
normal?: string;
|
||
/** Rauheits-Karte (roughnessMap). */
|
||
roughness?: string;
|
||
/** Metallizitäts-Karte (metalnessMap). */
|
||
metalness?: string;
|
||
/** Höhen-/Displacement-Karte (displacementMap, dezent angewandt). */
|
||
displacement?: string;
|
||
/** Ambient-Occlusion-Karte (aoMap). */
|
||
ao?: string;
|
||
/** Physische Kachelgröße in Metern (Default 1.0). */
|
||
sizeM?: number;
|
||
}
|
||
|
||
/**
|
||
* Ein Bauteil-Material (Component Manager) — vereint Plan-Schnittdarstellung
|
||
* (Schraffur) und 3D-Erscheinung. Ersetzt das frühere `Material`.
|
||
*/
|
||
export interface Component {
|
||
id: string;
|
||
name: string;
|
||
/**
|
||
* Füllfarbe im Grundriss (Poché) und 3D-Diffusfarbe. Bleibt bestehen; im
|
||
* Zielmodell (Vordergrund/Hintergrund, s. u.) dient sie als Fallback für
|
||
* `background`. Migrationsabsicht: neue Projekte setzen `foreground`/
|
||
* `background`, Alt-Projekte fallen weiterhin auf `color` zurück.
|
||
*/
|
||
color: string;
|
||
/**
|
||
* Vordergrundfarbe = Farbe der Muster-/Schraffurlinien (die Schraffur selbst
|
||
* trägt keine Farbe mehr). Optional; fehlt sie, greift die Resolve-Kette
|
||
* (Attribut-Override ?? Component.foreground ?? HatchStyle.color-Fallback).
|
||
*/
|
||
foreground?: string;
|
||
/**
|
||
* Hintergrundfarbe = Füllung (Poché). Optional; fehlt sie, gilt als Fallback
|
||
* `color`. Migrationsabsicht: `background` ⇐ `color`.
|
||
*/
|
||
background?: string;
|
||
/** Schnitt-Schraffur → Hatch Manager. Gilt, wo das Bauteil echt geschnitten ist. */
|
||
hatchId: string;
|
||
/**
|
||
* Ansichts-Schraffur → Hatch Manager. Gilt, wo das Bauteil frontal/ungeschnitten
|
||
* gesehen wird (z. B. Deckenpoché im Grundriss: die Decke liegt über der
|
||
* horizontalen Schnittebene und wird von unten gesehen, nicht aufgeschnitten).
|
||
* Leer/`undefined` ⇒ keine Schraffur (weiss).
|
||
*/
|
||
viewHatchId?: string;
|
||
/** Optionale 3D-Textur (vorerst ignoriert). */
|
||
texture3d?: string;
|
||
/**
|
||
* Optionales echtes PBR-Material für die 3D-Texturierung (Bibliothek ODER
|
||
* Upload). Fehlt es, bleibt das heutige Verhalten (matte Farbe). Wirkt nur im
|
||
* Render-Modus „textured".
|
||
*/
|
||
material?: ComponentMaterial;
|
||
/**
|
||
* Optionales Kürzel für Wandtyp-Labels (z. B. „BET", „HLZ", „GKB", „DAE").
|
||
* Wird von `wallTypeLabel()` genutzt, um Wandtyp-Dropdowns kompakt darzustellen
|
||
* (z. B. „BET 24" statt dem vollen Namen). Fehlt es, greift der Name.
|
||
*/
|
||
abbrev?: string;
|
||
/** Verschneidungs-Rang: höher läuft am Stoß durch (Backbone). */
|
||
joinPriority: number;
|
||
}
|
||
|
||
/** Eine Schicht eines mehrschichtigen Bauteils. */
|
||
export interface Layer {
|
||
/** Verweis auf das Bauteil-Material (Component Manager). */
|
||
componentId: string;
|
||
/** Schichtdicke in Metern. */
|
||
thickness: number;
|
||
/**
|
||
* Linienstil (LineStyle) der Schichtfuge an der INNEREN Kante DIESER Schicht —
|
||
* also der Fuge zwischen dieser und der nächst-inneren Schicht. Bei der
|
||
* innersten Schicht ungenutzt (ihre innere Kante ist der Wand-Innenumriss).
|
||
* Fehlt das Feld, wird die Standard-Haarlinie (0.02 mm) gezeichnet.
|
||
*/
|
||
jointLineStyleId?: string;
|
||
}
|
||
|
||
/** Ein Wandtyp = geordneter Schichtaufbau (außen → innen). */
|
||
export interface WallType {
|
||
id: string;
|
||
name: string;
|
||
layers: Layer[];
|
||
}
|
||
|
||
/**
|
||
* Ein Deckentyp (Deckenstil) = geordneter Schichtaufbau EINER Decke, liegend
|
||
* gestapelt (oben → unten) — das horizontale Gegenstück zum `WallType`. Nutzt
|
||
* denselben `Layer`-Typ (Bauteil + Dicke + optionaler Schichtfugen-Linienstil),
|
||
* damit Fuge/Schraffur-Auflösung identisch zur Wand bleiben. Eine „einschichtige"
|
||
* Decke (SOLID) ist einfach ein Deckentyp mit genau einer Schicht.
|
||
*/
|
||
export interface CeilingType {
|
||
id: string;
|
||
name: string;
|
||
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`).
|
||
// Getrennte Typen (kein gemeinsames „OpeningType"), damit türspezifische und
|
||
// fensterspezifische Parameter sauber je Gattung wohnen. Ein Element ohne
|
||
// `typeId` behält sein heutiges Inline-Verhalten (rückwärtskompatibel).
|
||
|
||
// Detailstufe der Bauteil-Darstellung (Grundriss + 3D) nutzt das bestehende
|
||
// {@link DetailLevel}-Vokabular ("grob" | "mittel" | "fein", aus ui/TopBar) —
|
||
// bewusst KEIN zweites Detail-Enum, damit Ansicht und Bauteil dieselbe Skala
|
||
// teilen. Ausgewertet an der Symbol-/Mesh-Erzeugung (Phase 2).
|
||
|
||
/**
|
||
* Ein Türtyp (Türstil) — wiederverwendbares Preset für {@link Opening} mit
|
||
* `kind: "door"`. Bündelt Bauart, Blattausführung und Standardmaße. Werte am
|
||
* einzelnen Element (Breite/Höhe/Anschlag) übersteuern die Typ-Defaults.
|
||
*/
|
||
export interface DoorType {
|
||
id: string;
|
||
name: string;
|
||
/**
|
||
* Bauart: normaler Drehflügel, reine Wandöffnung (kein Blatt/Schwenk),
|
||
* Schiebetür. Ersetzt/ergänzt das Inline-Feld `Opening.doorType`.
|
||
*/
|
||
kind: "dreh" | "schiebe" | "wandoeffnung";
|
||
/** Anzahl Türblätter (1 = einflügelig, 2 = zweiflügelig). */
|
||
leafCount: 1 | 2;
|
||
/** Blatt-Ausführung (3D/Detail): glatt, Kassette, Glasfüllung. */
|
||
leafStyle: "glatt" | "kassette" | "glas";
|
||
/** Glasanteil des Blatts (0..1); nur bei `leafStyle: "glas"` relevant. */
|
||
glazingRatio?: number;
|
||
/** Zargen-/Rahmenstärke quer zur Wand (Meter). */
|
||
frameThickness: number;
|
||
/** Rahmen-/Zargen-Tiefe in Wandrichtung (Meter); fehlt ⇒ aus Wanddicke. */
|
||
frameDepth?: number;
|
||
/**
|
||
* Rahmenart:
|
||
* • "zarge" — Zarge, umschliesst die Laibung (schmales Profil, in der
|
||
* Wandlaibung sitzend — der Standard im Innenausbau).
|
||
* • "blockrahmen" — Blockrahmen, sitzt als kräftiges Rechteckprofil VOR/auf
|
||
* der Laibung (typisch bei Aussentüren/älterem Bestand).
|
||
* Fehlt es, gilt "zarge".
|
||
*/
|
||
frameKind?: "zarge" | "blockrahmen";
|
||
/**
|
||
* Ansichtsbreite des Rahmenprofils (Meter) — die sichtbare Rahmen-Randbreite
|
||
* in der Öffnungsebene (Elevation), NICHT die Tiefe quer zur Wand. Fehlt es,
|
||
* gilt ein schmaler Default (~0.06 m). Steuert 2D-Rahmenkontur + 3D-Rahmen.
|
||
*/
|
||
frameWidth?: number;
|
||
/**
|
||
* Schichteinzug: Abstand der Rahmen-Vorderkante von einer Wandfläche (Meter).
|
||
* 0/fehlt ⇒ bündig. > 0 ⇒ der Rahmen sitzt um diesen Betrag in die Wand
|
||
* zurückgesetzt (z. B. hinter die Aussenschale eines mehrschichtigen Aufbaus).
|
||
* Gemessen von der über {@link insetFace} gewählten Fläche.
|
||
*/
|
||
insetFromFace?: number;
|
||
/** Von welcher Wandfläche der {@link insetFromFace} gemessen wird; Default "aussen". */
|
||
insetFace?: "aussen" | "innen";
|
||
/**
|
||
* Oberlicht: Höhe eines festen, verglasten Oberlichts über dem Türblatt
|
||
* (Meter). 0/fehlt ⇒ kein Oberlicht. Das Oberlicht sitzt oberhalb eines
|
||
* Kämpfers innerhalb derselben lichten Öffnungshöhe.
|
||
*/
|
||
transomHeight?: number;
|
||
/** Default-Lichtbreite (Meter) für neu platzierte Türen dieses Typs. */
|
||
defaultWidth: number;
|
||
/** Default-Lichthöhe (Meter). */
|
||
defaultHeight: number;
|
||
/** Bodenschwelle/Anschlag zeichnen (3D/Detail). */
|
||
threshold?: boolean;
|
||
}
|
||
|
||
/**
|
||
* Ein Fenstertyp (Fensterstil) — wiederverwendbares Preset für {@link Opening}
|
||
* mit `kind: "window"`. Bündelt Öffnungsart, Flügelzahl, Verglasung und
|
||
* Standardmaße inkl. Brüstungshöhe.
|
||
*/
|
||
export interface WindowType {
|
||
id: string;
|
||
name: string;
|
||
/** Öffnungsart: Dreh, Kipp, Dreh-Kipp, fest verglast, Schiebe. */
|
||
kind: "dreh" | "kipp" | "drehkipp" | "fest" | "schiebe";
|
||
/** Anzahl Flügel (1–4) → Mittelpfosten = wingCount − 1 (vgl. `Opening.wingCount`). */
|
||
wingCount: number;
|
||
/**
|
||
* Anzahl horizontaler Felder (Kämpfer-Zeilen): 1 = keine Querteilung, 2 = ein
|
||
* Kämpfer, usw. Zusammen mit {@link wingCount} (Spalten) ergibt sich das
|
||
* Sprossen-/Flügelraster. Fehlt es, gilt 1.
|
||
*/
|
||
mullionRows?: number;
|
||
/** Verglasung: Einfach/Zweifach/Dreifach (3D-Scheibenzahl). */
|
||
glazing: "einfach" | "zweifach" | "dreifach";
|
||
/** Rahmenstärke quer zur Wand (Meter). */
|
||
frameThickness: number;
|
||
/** Rahmen-Tiefe in Wandrichtung (Meter); fehlt ⇒ aus Wanddicke. */
|
||
frameDepth?: number;
|
||
/**
|
||
* Ansichtsbreite des Rahmen-/Flügelprofils (Meter) — sichtbare Randbreite in
|
||
* der Öffnungsebene. Fehlt es, gilt ein schmaler Default (~0.06 m). Steuert die
|
||
* 2D-Rahmen-/Sprossenkontur und den 3D-Rahmen.
|
||
*/
|
||
frameWidth?: number;
|
||
/**
|
||
* Schichteinzug: Abstand der Rahmen-Vorderkante von einer Wandfläche (Meter).
|
||
* 0/fehlt ⇒ bündig; > 0 ⇒ der Rahmen sitzt zurückgesetzt (klassisch hinter der
|
||
* Aussenschale eines mehrschichtigen Wandaufbaus). Von {@link insetFace} gemessen.
|
||
*/
|
||
insetFromFace?: number;
|
||
/** Von welcher Wandfläche der {@link insetFromFace} gemessen wird; Default "aussen". */
|
||
insetFace?: "aussen" | "innen";
|
||
/**
|
||
* Oberlicht: Höhe eines festen, verglasten Oberlichts über dem Hauptflügel
|
||
* (Meter), abgeteilt durch einen Kämpfer. 0/fehlt ⇒ kein Oberlicht.
|
||
*/
|
||
transomHeight?: number;
|
||
/** Default-Brüstungshöhe über Wand-UK (Meter). */
|
||
defaultSillHeight: number;
|
||
/** Default-Lichtbreite (Meter). */
|
||
defaultWidth: number;
|
||
/** Default-Lichthöhe (Meter). */
|
||
defaultHeight: number;
|
||
/** Fensterbank zeichnen (Detail): keine / innen / aussen / beide. */
|
||
sillBoard?: "keine" | "innen" | "aussen" | "beide";
|
||
}
|
||
|
||
/**
|
||
* Ein Treppentyp (Treppenstil) — wiederverwendbares Preset für {@link Stair}.
|
||
* Bündelt Tragart, Stufenausbildung und Geländer. Geometrische Grundform
|
||
* (gerade/L/Wendel) + Lauflänge/Stufenzahl bleiben am einzelnen Element.
|
||
*/
|
||
export interface StairType {
|
||
id: string;
|
||
name: string;
|
||
/**
|
||
* Tragart:
|
||
* • "massiv" — Vollblock bis zur Treppen-UK (Treppe auf Erdreich/Sockel).
|
||
* • "beton" — Ortbeton-Laufplatte mit SCHRÄGER, offener Untersicht
|
||
* (die klassische „normale Betontreppe" zwischen zwei Geschossen).
|
||
* • "wange" — Wangentreppe (schwebende Tritte, offen).
|
||
* • "aufgesattelt" — aufgesattelte Tritte (mit Setzstufen, offen).
|
||
* • "spindel" — Spindel-/Wendeltreppe (offen).
|
||
*/
|
||
structure: "massiv" | "beton" | "wange" | "aufgesattelt" | "spindel";
|
||
/** Setzstufen geschlossen (true) oder offen (false, durchsichtige Tritte). */
|
||
closedRisers: boolean;
|
||
/** Trittstufendicke (Meter). */
|
||
treadThickness: number;
|
||
/** Trittkanten-Überstand / Nase (Meter); fehlt ⇒ 0. */
|
||
nosing?: number;
|
||
/** Handlauf/Geländer: keine / links / rechts / beide (in Laufrichtung). */
|
||
railing?: "keine" | "links" | "rechts" | "beide";
|
||
/** Default-Laufbreite (Meter) für neu platzierte Treppen dieses Typs. */
|
||
defaultWidth: number;
|
||
}
|
||
|
||
// ── Grafische Overrides (Regel-Engine) ─────────────────────────────────────
|
||
// ArchiCAD-/Vectorworks-Stil: benannte Regeln `condition → actions`, die beim
|
||
// RENDERN als oberste Schicht über die By-Layer/By-Object-Attribut-Auflösung
|
||
// gelegt werden (reines Rendering-Overlay — die Elementdaten bleiben
|
||
// unverändert, jederzeit reversibel). Regeln werden additiv angewendet: pro
|
||
// Aktions-Feld gewinnt die OBERSTE (erste) aktive Regel, die es setzt.
|
||
// Auswertung: `src/overrides/engine.ts`; Einhängepunkt: `plan/generatePlan.ts`.
|
||
|
||
/**
|
||
* Bedingungs-Typ einer Override-Regel — wogegen der Wert verglichen wird:
|
||
* • "layer_name" — Name ODER Code der Ebene (LayerCategory) des Elements.
|
||
* • "object_name" — Element-/Typ-Name (z. B. Wandtyp-Name, Raum-Name,
|
||
* Öffnungs-Label, Drawing2D-Formname).
|
||
* Ein `user_string`-Tag (DOSSIER-Rhino) existiert am Element noch nicht und
|
||
* ist deshalb bewusst NICHT enthalten (kein Schein-Feature).
|
||
*/
|
||
export type OverrideConditionType = "layer_name" | "object_name";
|
||
|
||
/** Vergleichs-Operator einer Override-Bedingung (case-insensitiv). */
|
||
export type OverrideOperator =
|
||
| "equals"
|
||
| "contains"
|
||
| "starts_with"
|
||
| "not_equals";
|
||
|
||
/** Bedingung einer Override-Regel. */
|
||
export interface OverrideCondition {
|
||
type: OverrideConditionType;
|
||
operator: OverrideOperator;
|
||
/** Vergleichswert (Freitext, case-insensitiv verglichen). */
|
||
value: string;
|
||
}
|
||
|
||
/**
|
||
* Aktionen einer Override-Regel — jede optional (Teilaktionen sind erlaubt;
|
||
* fehlende Felder lassen die reguläre Auflösung unberührt).
|
||
*/
|
||
export interface OverrideActions {
|
||
/** Strich-/Umrandungsfarbe (hex). */
|
||
color?: string;
|
||
/** Strichstärke in mm Papier. */
|
||
lineweight?: number;
|
||
/** Linienstil (Line Manager) — wirkt, wo Elemente einen LineStyle tragen (Drawing2D). */
|
||
linetypeId?: string;
|
||
}
|
||
|
||
/**
|
||
* Eine grafische Override-Regel. Lebt in `Project.overrideRules` (Reihenfolge
|
||
* = Priorität, oben gewinnt) und ist einzeln aktivier-/deaktivierbar.
|
||
*/
|
||
export interface OverrideRule {
|
||
id: string;
|
||
name: string;
|
||
/** Deaktivierte Regeln werden bei der Auswertung übersprungen. */
|
||
enabled: boolean;
|
||
condition: OverrideCondition;
|
||
actions: OverrideActions;
|
||
}
|
||
|
||
// ── Parametrische Wände (Parametric Walls) ────────────────────────────────
|
||
// Parametrische Wand-Regeln generieren automatisch Wall[]-Arrays — analog zu
|
||
// FreeCAD BIM. Sie leben in Project.resources.parametricWalls[] und werden
|
||
// durch `resolveParametricWall()` in `src/model/parametricWalls.ts` aufgelöst.
|
||
// Ausgabe: normale Wall[]-Objekte (kein neuer Elementtyp).
|
||
|
||
/**
|
||
* Eine parametrische Wand-Regel — generiert automatisch Wall[]-Einträge für
|
||
* ein gegebenes Geschoss. Lebt in der Ressourcen-Bibliothek des Projekts.
|
||
* Wird über `resolveParametricWall()` aufgelöst, nicht zur Laufzeit gespeichert.
|
||
*/
|
||
export interface ParametricWall {
|
||
id: string;
|
||
/** Anzeigename, z. B. „Wohnbau-Raster 3m". */
|
||
name: string;
|
||
/** Optionale Beschreibung (für den Ressourcen-Manager). */
|
||
description?: string;
|
||
/**
|
||
* Geordnete Liste der anzuwendenden Regeln. Spätere Regeln können die
|
||
* Ausgabe früherer Regeln verfeinern (z. B. Dickenzuweisung nach Raster).
|
||
*/
|
||
rules: ParametricRule[];
|
||
/**
|
||
* Rückfall-Wandtyp, falls eine Regel keinen eigenen `wallTypeId` nennt.
|
||
* Muss auf einen gültigen WallType im Projekt verweisen.
|
||
*/
|
||
defaultWallTypeId: string;
|
||
}
|
||
|
||
/**
|
||
* Eine einzelne parametrische Regel — eine Strategie zur Wandplatzierung oder
|
||
* -verfeinerung. Regeln werden als Discriminated Union kodiert; der `type`-Tag
|
||
* bestimmt, welche Felder verfügbar sind.
|
||
*/
|
||
export type ParametricRule =
|
||
| GridRule
|
||
| ModuleRule
|
||
| ConditionalThicknessRule
|
||
| ReferenceLineRule
|
||
| SequenceRule;
|
||
|
||
/**
|
||
* Raster-Regel: generiert Wände entlang gleichmäßiger X-/Y-Achsen.
|
||
* Typischer Anwendungsfall: Tragwerks-Achsraster (z. B. 3 m Abstand).
|
||
*
|
||
* Ablauf (Engine):
|
||
* 1. Rasterachsen aus `spacing` oder (später) verlinkter Grid-Ressource.
|
||
* 2. Je Achse eine Wand von Rand zu Rand (begrenzt durch `boundaryId`).
|
||
* 3. Wandtyp, Referenzlinie und Höhe gemäß Regelfelder.
|
||
*/
|
||
export interface GridRule {
|
||
type: "grid";
|
||
/**
|
||
* Optionaler Verweis auf eine Grid-Ressource (Phase 3). Für MVP wird
|
||
* stattdessen `spacing` genutzt.
|
||
*/
|
||
gridId?: string;
|
||
/** Rasterabstand in Metern (Default: 3.0). Wird genutzt, wenn kein `gridId`. */
|
||
spacing?: number;
|
||
/**
|
||
* Achsrichtungen: „x" = nur horizontale Wände, „y" = nur vertikale,
|
||
* „both" = Vollraster.
|
||
*/
|
||
directions: "x" | "y" | "both";
|
||
/**
|
||
* Optionaler Verweis auf eine Drawing2D-Grenze (als Clipping-Polygon).
|
||
* Fehlt er, reicht das Raster über den sichtbaren Bereich des Geschosses.
|
||
*/
|
||
boundaryId?: string;
|
||
/** Optionale Wandtyp-Übersteuerung; sonst `defaultWallTypeId`. */
|
||
wallTypeId?: string;
|
||
/** Lage der Wandachse über die Dicke (Vectorworks-Stil). */
|
||
referenceLine?: WallReferenceLine;
|
||
/** Optionale Höhenübersteuerung in Metern; sonst Geschosshöhe. */
|
||
height?: number;
|
||
}
|
||
|
||
/**
|
||
* Modul-Regel: teilt eine Referenzspanne in proportionale Felder auf.
|
||
* Typischer Anwendungsfall: Tragwerks-Joche (z. B. 6 m-Module in einem Bauteil).
|
||
*
|
||
* Ablauf (Engine):
|
||
* 1. Gesamtspanne aus `referenceWallId` oder Geschossausdehnung ableiten.
|
||
* 2. In Module der Größe `moduleSize` unterteilen.
|
||
* 3. Querwände an jedem Modulteilungspunkt setzen.
|
||
*/
|
||
export interface ModuleRule {
|
||
type: "module";
|
||
/** Modulmaß in Metern (z. B. 6.0, 3.6). */
|
||
moduleSize: number;
|
||
/** Ausrichtung der Trennwände: „x" = quer zur X-Achse, „y" = quer zur Y-Achse. */
|
||
direction: "x" | "y";
|
||
/**
|
||
* Optionaler Verweis auf eine Referenzwand, die die Spannweite definiert.
|
||
* Fehlt er, wird die Geschoss-Ausdehnung genutzt (Phase 3: Achsen-Referenz).
|
||
*/
|
||
referenceWallId?: string;
|
||
/** Optionale Wandtyp-Übersteuerung; sonst `defaultWallTypeId`. */
|
||
wallTypeId?: string;
|
||
/** Lage der Wandachse über die Dicke. */
|
||
referenceLine?: WallReferenceLine;
|
||
/** Optionale Höhenübersteuerung in Metern. */
|
||
height?: number;
|
||
}
|
||
|
||
/**
|
||
* Bedingte-Dicken-Regel: weist bereits generierten Wänden einen anderen
|
||
* Wandtyp zu, wenn eine Bedingung erfüllt ist.
|
||
* Typischer Anwendungsfall: Außenwände erhalten einen anderen Aufbau als Innenwände.
|
||
*
|
||
* Ablauf (Engine):
|
||
* Bestehende Wände aus `existingWalls` filtern → `wallTypeId` ändern.
|
||
* Gibt modifizierte Kopien zurück (keine Mutation).
|
||
*/
|
||
export interface ConditionalThicknessRule {
|
||
type: "conditional-thickness";
|
||
/**
|
||
* Bedingung für den Treffer:
|
||
* • „exterior" — Wand liegt am Außenrand (ermittelt via Grenzpolygon).
|
||
* • „interior" — Wand liegt im Inneren.
|
||
* • „bearing" — tragende Wand (über Tag oder Wandtyp-Rang).
|
||
* • beliebiger String — benutzerdefiniertes Tag (Phase 3: Wall.tags[]).
|
||
*/
|
||
condition: "exterior" | "interior" | "bearing" | string;
|
||
/** Ziel-Wandtyp, der bei Treffer gesetzt wird. */
|
||
wallTypeId: string;
|
||
/**
|
||
* Verknüpfungslogik für mehrere Bedingungen (Phase 3: mehrere `condition`-Felder).
|
||
* Vorerst ungenutzt; Default ist implizites „or".
|
||
*/
|
||
logic?: "and" | "or";
|
||
}
|
||
|
||
/**
|
||
* Referenzlinien-Regel: setzt die `referenceLine`-Eigenschaft bei passenden
|
||
* Wänden einheitlich (Vectorworks-Stil).
|
||
* Typischer Anwendungsfall: Alle Außenwände auf „left" (linke Fläche = Fassade).
|
||
*
|
||
* Ablauf (Engine):
|
||
* Bestehende Wände aus `existingWalls` filtern → `referenceLine` setzen.
|
||
* Gibt modifizierte Kopien zurück.
|
||
*/
|
||
export interface ReferenceLineRule {
|
||
type: "reference-line";
|
||
/** Neue Lage der Wandachse, die einheitlich gesetzt wird. */
|
||
referenceLine: WallReferenceLine;
|
||
/**
|
||
* Filterziel:
|
||
* • „all" — alle Wände im aktuellen Satz.
|
||
* • „exterior" — nur Außenwände (wie bei ConditionalThicknessRule).
|
||
* • beliebiger String — benutzerdefiniertes Tag (Phase 3).
|
||
*/
|
||
target: "all" | "exterior" | string;
|
||
}
|
||
|
||
/**
|
||
* Sequenz-Regel: fasst mehrere Unterregeln zusammen und wendet sie geordnet an.
|
||
* Jede Unterregel kann die Ausgabe der vorherigen verfeinern.
|
||
* Typischer Anwendungsfall: Raster → bedingte Dicke → Referenzlinie als atomare Einheit.
|
||
*
|
||
* Ablauf (Engine):
|
||
* Regeln in `rules` werden sequenziell ausgeführt; das Ergebnis jeder Regel wird
|
||
* als `existingWalls` der nächsten übergeben. `stopOnMatch` bricht ab, sobald
|
||
* eine Unterregel mindestens eine Wand generiert hat.
|
||
*/
|
||
export interface SequenceRule {
|
||
type: "sequence";
|
||
/** Unterregeln, in Ausführungsreihenfolge. */
|
||
rules: ParametricRule[];
|
||
/**
|
||
* Wenn `true`: Abbruch nach der ersten Unterregel, die mindestens eine Wand
|
||
* generiert/verändert. Ähnlich wie ein Short-Circuit-Fallback.
|
||
*/
|
||
stopOnMatch?: boolean;
|
||
}
|
||
|
||
/**
|
||
* Art einer Zeichnungsebene.
|
||
* • "floor" — Geschoss, trägt Bauteile.
|
||
* • "section" — Schnitt (abgeleitete Projektion entlang einer Linie).
|
||
* • "elevation" — Ansicht (abgeleitete Projektion entlang einer Linie).
|
||
* • "drawing" — freie 2D-Zeichnung, nicht an ein Geschoss gebunden.
|
||
*/
|
||
export type DrawingLevelKind = "floor" | "section" | "elevation" | "drawing";
|
||
|
||
/**
|
||
* Eine Zeichnungsebene (DrawingLevel) — eine oberste Schnittebene des
|
||
* Dokuments. Ein Geschoss trägt die Bauteile (über `floorId`); ein
|
||
* Schnitt/Ansicht ist eine abgeleitete Projektion (vorerst Platzhalter); eine
|
||
* reine Zeichnung ist eine freie 2D-Ebene ohne Geschossbezug.
|
||
*
|
||
* Geschoss nutzt floorHeight/cutHeight/baseElevation; Schnitt/Ansicht nutzen
|
||
* linePoints/directionSign; "drawing" nutzt keines dieser Felder.
|
||
*/
|
||
export interface DrawingLevel {
|
||
id: string;
|
||
name: string;
|
||
kind: DrawingLevelKind;
|
||
/** Sichtbarkeit der Zeichnungsebene im Navigator. */
|
||
visible: boolean;
|
||
/** Gesperrt (keine Bearbeitung). */
|
||
locked: boolean;
|
||
/** Lichte Geschosshöhe in Metern (nur Geschoss). */
|
||
floorHeight?: number;
|
||
/** Schnitthöhe über OKFF in Metern (nur Geschoss, für den Grundriss). */
|
||
cutHeight?: number;
|
||
/** Oberkante Fertigfußboden in Metern (nur Geschoss). */
|
||
baseElevation?: number;
|
||
/** Schnitt-/Ansichtslinie im Grundriss (nur Schnitt/Ansicht). */
|
||
linePoints?: [Vec2, Vec2];
|
||
/** Blickrichtung relativ zur Schnittlinie (nur Schnitt/Ansicht). */
|
||
directionSign?: 1 | -1;
|
||
}
|
||
|
||
/**
|
||
* Eine Ebene (LayerCategory) — ein Knoten im Grafik-Kategorie-Baum. Das
|
||
* Schema gilt geschossübergreifend; Elemente verweisen über `code` darauf.
|
||
*/
|
||
export interface LayerCategory {
|
||
/** Eindeutiger Kategorie-Code, z. B. "20" für Wände. */
|
||
code: string;
|
||
name: string;
|
||
/** Darstellungsfarbe (hex). */
|
||
color: string;
|
||
/** Linienstärke in Millimetern. */
|
||
lw: number;
|
||
visible: boolean;
|
||
locked: boolean;
|
||
/** Optionale Standard-Schraffur der Kategorie. */
|
||
hatch?: string;
|
||
/** Unterkategorien (Baum). */
|
||
children?: LayerCategory[];
|
||
}
|
||
|
||
/**
|
||
* Quelle eines vererbbaren Attributs (Vordergrund/Hintergrund/Strichstärke/
|
||
* Schraffur), wenn KEIN expliziter Wert am Element gesetzt ist:
|
||
* • "layer" — „Nach Ebene": die LayerCategory des Elements erzwingt den Wert
|
||
* (`color`/`lw`/`hatch`).
|
||
* • "object" — „Nach Bauteil": erbt vom Bauteil (Component) bzw. dessen
|
||
* bisheriger Fallback-Kette. Das ist auch der Default, wenn das Source-Feld
|
||
* fehlt (`undefined`) — damit bleibt das heutige Verhalten unverändert.
|
||
* Ein gesetzter expliziter Wert (z. B. `foreground`) gewinnt IMMER, unabhängig
|
||
* von der Source (s. Resolve-Reihenfolge in `plan/generatePlan.ts`).
|
||
*/
|
||
export type AttributeSource = "layer" | "object";
|
||
|
||
/**
|
||
* Lage der Wandachse (Referenzlinie) über die Dicke der Wand — analog
|
||
* Vectorworks. Gemessen relativ zur Laufrichtung (start→end) mit der
|
||
* leftNormal-Konvention `n = (-u.y, u.x)`:
|
||
* • "center" — Achse mittig (Default = heutiges Verhalten).
|
||
* • "left" — Achse liegt auf der linken Wandfläche (+n-Seite, „außen").
|
||
* • "right" — Achse liegt auf der rechten Wandfläche (−n-Seite, „innen").
|
||
*/
|
||
export type WallReferenceLine = "left" | "center" | "right";
|
||
|
||
/**
|
||
* Vertikale Bindung einer Wandkante (UK/OK).
|
||
* • "floor" — an ein Geschoss gebunden: Z = baseElevation des Geschosses
|
||
* (+ optionalem `offset`). Stapelt automatisch mit dem Geschoss.
|
||
* • "custom" — fester absoluter Z-Wert (Meter).
|
||
*/
|
||
export type VerticalAnchor =
|
||
| { mode: "floor"; floorId: string; offset?: number }
|
||
| { mode: "custom"; z: number };
|
||
|
||
/** Eine Wand, definiert über ihre Achse (Centerline) und ihren Typ. */
|
||
export interface Wall {
|
||
id: string;
|
||
type: "wall";
|
||
/** Zugehörige Zeichnungsebene (Geschoss). */
|
||
floorId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "20" für Wände. */
|
||
categoryCode: string;
|
||
/** Achs-Startpunkt im Grundriss (Meter). */
|
||
start: Vec2;
|
||
/** Achs-Endpunkt im Grundriss (Meter). */
|
||
end: Vec2;
|
||
/** Verweis auf den (mehrschichtigen) Wandtyp. */
|
||
wallTypeId: string;
|
||
/** Wandhöhe in Metern. */
|
||
height: number;
|
||
/**
|
||
* Optionale Übersteuerung der Strich-/Umrandungsfarbe der Wand; sonst gilt
|
||
* die Kategorie-Farbe. Übersteuert NUR die Linienfarbe (Umriss/Schichtfugen),
|
||
* nicht die Schicht-Füllfarben/Schraffuren.
|
||
*/
|
||
color?: string;
|
||
/**
|
||
* Attribut-Override der Muster-/Schraffurfarbe (Vordergrund) DIESER Wand-
|
||
* Instanz. `undefined` = „Nach System" (erben → Component.foreground →
|
||
* HatchStyle.color-Fallback). Höchste Priorität in der Farb-Resolve-Kette.
|
||
*/
|
||
foreground?: string;
|
||
/**
|
||
* Attribut-Override der Füllfarbe (Hintergrund/Poché) DIESER Wand-Instanz.
|
||
* `undefined` = „Nach System" (erben → Component.background → Component.color).
|
||
*/
|
||
background?: string;
|
||
/**
|
||
* Attribut-Override der Strichstärke (mm Papier) DIESER Wand-Instanz (Umriss/
|
||
* Schichtfugen). `undefined` = „Nach System" (erben → `strokeWeightSource`).
|
||
*/
|
||
strokeWeight?: number;
|
||
/**
|
||
* Attribut-Override der Schnitt-Schraffur (Hatch Manager) DIESER Wand-Instanz;
|
||
* überschreibt die Schraffur ALLER Schichten einheitlich. `undefined` =
|
||
* „Nach System" (erben → `hatchSource`).
|
||
*/
|
||
hatchId?: string;
|
||
/**
|
||
* Quelle des Vordergrunds, wenn kein expliziter `foreground`-Wert gesetzt ist:
|
||
* "layer" erzwingt die Ebenenfarbe (LayerCategory.color), "object"/`undefined`
|
||
* (Default) erbt vom Bauteil (heutiges Verhalten).
|
||
*/
|
||
foregroundSource?: AttributeSource;
|
||
/** Quelle des Hintergrunds, analog zu {@link Wall.foregroundSource}. */
|
||
backgroundSource?: AttributeSource;
|
||
/**
|
||
* Quelle der Strichstärke, wenn kein explizites `strokeWeight` gesetzt ist:
|
||
* "layer" erzwingt `LayerCategory.lw`, "object"/`undefined` (Default) fällt auf
|
||
* die bisherige Kategorie-/Fallback-Strichstärke zurück (heutiges Verhalten).
|
||
*/
|
||
strokeWeightSource?: AttributeSource;
|
||
/**
|
||
* Quelle der Schraffur, wenn kein explizites `hatchId` gesetzt ist: "layer"
|
||
* erzwingt `LayerCategory.hatch`, "object"/`undefined` (Default) erbt vom
|
||
* Bauteil (heutiges Verhalten).
|
||
*/
|
||
hatchSource?: AttributeSource;
|
||
/**
|
||
* Lage der Wandachse über die Dicke. Fehlt sie, gilt "center" (= heutiges
|
||
* Verhalten: Schichten symmetrisch −T/2 … +T/2 um die Achse).
|
||
*/
|
||
referenceLine?: WallReferenceLine;
|
||
/**
|
||
* Freier Referenz-Versatz der Achse entlang der +n-Normalen von der Wandmitte
|
||
* (Meter). Ist er gesetzt, ÜBERSTEUERT er {@link referenceLine} — genutzt, um
|
||
* bei mehrschichtigen Wänden eine SCHICHTTRENNLINIE (Fuge zwischen zwei
|
||
* Schichten) als Referenzlinie zu wählen. +T/2 = Aussenfläche, 0 = Mitte,
|
||
* −T/2 = Innenfläche; ein interner Fugenwert liegt dazwischen.
|
||
*/
|
||
referenceOffset?: number;
|
||
/**
|
||
* Vertikale Bindung der Unterkante (UK). Fehlt sie, sitzt die UK auf der
|
||
* baseElevation des zugehörigen Geschosses (= heutiges Verhalten).
|
||
*/
|
||
bottom?: VerticalAnchor;
|
||
/**
|
||
* Vertikale Bindung der Oberkante (OK). Fehlt sie, ergibt sich die OK aus
|
||
* UK + `height` (= heutiges Verhalten). „custom" setzt einen absoluten
|
||
* Z-Wert; „floor" bindet die OK an ein (z. B. nächsthöheres) Geschoss.
|
||
*/
|
||
top?: VerticalAnchor;
|
||
/**
|
||
* TERMINIERUNGS-Regel am horizontalen Deckenanschluss (Zuschnitt, NICHT
|
||
* Priorität — bewusst getrennt von `Component.joinPriority`/der Schnitt-
|
||
* Boolean-Dominanz gehalten). Steuert, ob ein Wandband, das eine dominante
|
||
* Deckenschicht durchstösst, oberhalb der Decke „wieder auftaucht":
|
||
* • `undefined`/"both" — heutiges Verhalten: die dominante Deckenschicht
|
||
* stanzt nur ihr z-Band aus, das Wandband bleibt oben UND unten erhalten.
|
||
* • "below" — die Wand ENDET an der Decke: nur der Teil UNTER dem höchst-
|
||
* gelegenen dominanten Cut bleibt (Regelfall, Wand steigt von unten in die
|
||
* Decke). Der oberhalb der Decke stehende Rest wird verworfen.
|
||
* • "above" — spiegelbildlich: nur der Teil ÜBER dem tiefsten dominanten Cut
|
||
* bleibt (Brüstung/Attika, die von oben an die Decke stösst).
|
||
* Additiv; Alt-Projekte laden unverändert (Default = "both").
|
||
*/
|
||
sliceTermination?: SliceTermination;
|
||
}
|
||
|
||
/** Terminierungs-Regel einer Wand am Deckenanschluss (siehe {@link Wall.sliceTermination}). */
|
||
export type SliceTermination = "both" | "below" | "above";
|
||
|
||
/**
|
||
* Eine Decke (Slab) — ein geschossgebundenes, mehrschichtiges Flächenbauteil,
|
||
* definiert über einen GESCHLOSSENEN Umriss (Polygon) im Grundriss und eine
|
||
* Dicke. Analog zur Wand trägt sie ihren Schichtaufbau über einen `wallTypeId`
|
||
* (die Bauteil-/Schraffur-/Materialauflösung ist identisch); eine optionale
|
||
* `thickness` übersteuert die Gesamtdicke des Typs (Meter).
|
||
*
|
||
* Vertikale Lage: die OBERKANTE (OK) der Decke. Fehlt `top`, liegt die OK an der
|
||
* Oberkante des Geschosses (baseElevation + floorHeight — also bündig mit dem
|
||
* Wandkopf); die Decke wächst um `thickness` nach UNTEN. `top: custom` setzt eine
|
||
* absolute Z-Höhe, `top: floor` bindet die OK an ein Geschoss. Optional übersteuert
|
||
* `bottom` die UNTERKANTE (UK) direkt (analog zur Wand); fehlt `bottom`, bleibt es
|
||
* beim heutigen Verhalten UK = OK − `thickness`.
|
||
*/
|
||
export interface Ceiling {
|
||
id: string;
|
||
type: "ceiling";
|
||
/** Zugehörige Zeichnungsebene (Geschoss). */
|
||
floorId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "30" für Decken. */
|
||
categoryCode: string;
|
||
/**
|
||
* Geschlossener Umriss im Grundriss (Meter). Der Schlusspunkt wird NICHT
|
||
* dupliziert (die letzte Kante läuft von outline[n-1] zu outline[0]).
|
||
*/
|
||
outline: Vec2[];
|
||
/**
|
||
* LEGACY-Verweis auf einen Aufbau-Typ aus `wallTypes` (Rückwärtskompatibilität
|
||
* für Projekte von vor den dedizierten Deckenstilen). Nur wirksam, wenn
|
||
* `ceilingTypeId` fehlt.
|
||
*/
|
||
wallTypeId: string;
|
||
/**
|
||
* Verweis auf einen dedizierten Deckentyp (Deckenstil) aus `ceilingTypes` —
|
||
* der reguläre Weg für SOLID (1 Schicht) und MEHRSCHICHTIG (>1 Schicht). Hat
|
||
* Vorrang vor `wallTypeId`, falls gesetzt und im Projekt auflösbar.
|
||
*/
|
||
ceilingTypeId?: string;
|
||
/** Optionale Übersteuerung der Gesamtdicke in Metern (sonst Typ-Dicke). */
|
||
thickness?: number;
|
||
/**
|
||
* Optionale Übersteuerung der Strich-/Umrandungsfarbe; sonst gilt die
|
||
* Kategorie-Farbe.
|
||
*/
|
||
color?: string;
|
||
/**
|
||
* Attribut-Override der Muster-/Schraffurfarbe (Vordergrund) DIESER Decken-
|
||
* Instanz. `undefined` = „Nach System" (erben → Component.foreground →
|
||
* HatchStyle.color-Fallback).
|
||
*/
|
||
foreground?: string;
|
||
/**
|
||
* Attribut-Override der Füllfarbe (Hintergrund/Poché) DIESER Decken-Instanz.
|
||
* `undefined` = „Nach System" (erben → Component.background → Component.color).
|
||
*/
|
||
background?: string;
|
||
/**
|
||
* Attribut-Override der Strichstärke (mm Papier) DIESER Decken-Instanz.
|
||
* `undefined` = „Nach System" (erben → `strokeWeightSource`).
|
||
*/
|
||
strokeWeight?: number;
|
||
/**
|
||
* Attribut-Override der Schraffur (Hatch Manager) DIESER Decken-Instanz;
|
||
* überschreibt sowohl die Schnitt- als auch die Ansichts-Schraffur des
|
||
* Bauteils. `undefined` = „Nach System" (erben → `hatchSource`).
|
||
*/
|
||
hatchId?: string;
|
||
/** Quelle des Vordergrunds, analog zu {@link Wall.foregroundSource}. */
|
||
foregroundSource?: AttributeSource;
|
||
/** Quelle des Hintergrunds, analog zu {@link Wall.backgroundSource}. */
|
||
backgroundSource?: AttributeSource;
|
||
/** Quelle der Strichstärke, analog zu {@link Wall.strokeWeightSource}. */
|
||
strokeWeightSource?: AttributeSource;
|
||
/** Quelle der Schraffur, analog zu {@link Wall.hatchSource}. */
|
||
hatchSource?: AttributeSource;
|
||
/**
|
||
* Vertikale Bindung der OBERKANTE (OK). Fehlt sie, sitzt die OK an der
|
||
* Oberkante des Geschosses (baseElevation + floorHeight).
|
||
*/
|
||
top?: VerticalAnchor;
|
||
/**
|
||
* Vertikale Bindung der UNTERKANTE (UK). Fehlt sie, ergibt sich die UK aus
|
||
* OK − `thickness` (= heutiges Verhalten). „custom" setzt einen absoluten
|
||
* Z-Wert; „floor" bindet die UK an ein Geschoss.
|
||
*/
|
||
bottom?: VerticalAnchor;
|
||
}
|
||
|
||
/** Schwenkrichtung der Tür relativ zur Wandachse. */
|
||
export type SwingSide = "left" | "right";
|
||
|
||
/**
|
||
* Eine Öffnung (Fenster oder Tür), gehostet in einer Wand. Sie „kennt" ihre Wand
|
||
* (`hostWallId`) und liegt über `position` (Abstand vom Wand-Startpunkt entlang
|
||
* der Achse) relativ zur Wand — bewegt sich die Wand, folgt die Öffnung, weil die
|
||
* Weltkoordinaten beim Rendern IMMER aus der Wandachse abgeleitet werden.
|
||
*
|
||
* Vertikale Lage (relativ zur Wand-Unterkante, UK):
|
||
* • Tür — sitzt am Boden, `sillHeight` = 0, Höhe = lichte Türhöhe.
|
||
* • Fenster— Brüstung `sillHeight` > 0, Öffnung reicht von sillHeight bis
|
||
* sillHeight + height.
|
||
*
|
||
* Türspezifisch: `hinge` (Anschlagpfosten) + `swing` (Aufschlagseite) + optional
|
||
* `swingAngle` (Öffnungswinkel des Blatts in Grad, Default 90) + `openingDir`
|
||
* (Aufschlag nach innen/außen — kippt den Schwenkbogen auf die andere Achsseite).
|
||
* Fenster nutzen optional `frameDepth`/`frameThickness` für die 3D-Rahmenstärke.
|
||
*/
|
||
export interface Opening {
|
||
id: string;
|
||
type: "opening";
|
||
/** Wirts-Wand; das Geschoss ergibt sich aus der Wand. */
|
||
hostWallId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "21" für Türen/Fenster. */
|
||
categoryCode: string;
|
||
/** Fenster oder Tür. */
|
||
kind: "window" | "door";
|
||
/** Abstand des ersten Pfostens vom Wand-Startpunkt entlang der Achse (Meter). */
|
||
position: number;
|
||
/** Lichte Öffnungsbreite in Metern. */
|
||
width: number;
|
||
/** Lichte Öffnungshöhe in Metern. */
|
||
height: number;
|
||
/** Brüstungshöhe (Unterkante der Öffnung) über der Wand-UK; 0 bei Türen. */
|
||
sillHeight: number;
|
||
/**
|
||
* Nur Fenster: Anzahl der Flügel (1–4). Bei > 1 werden im Plan `wingCount − 1`
|
||
* Mittelpfosten (Querlinien quer zur Öffnungsrichtung) gleichmäßig innerhalb des
|
||
* Rahmens gezeichnet — analog den Mittelpfosten in `_make_oeffnung_pieces` des
|
||
* Rhino-Plugins (`oeff_fluegel`). Fehlt es, gilt 1 (= heutiges Verhalten: keine
|
||
* Pfosten).
|
||
*/
|
||
wingCount?: number;
|
||
/**
|
||
* Nur Tür: Tür-Typ (analog `oeff_tuer_typ` im Rhino-Plugin).
|
||
* • "normal" — Türblatt + Schwenkbogen (Default = heutiges Verhalten).
|
||
* • "wandoeffnung" — reiner Wanddurchbruch: KEIN Türblatt, KEIN Schwenkbogen;
|
||
* nur die Öffnung/Laibung (Wandlücke + ggf. Sturz-/Anschlaglinien) bleibt.
|
||
* Fehlt es, gilt "normal".
|
||
*/
|
||
doorType?: "normal" | "wandoeffnung";
|
||
/** Nur Tür: an welchem Pfosten das Scharnier sitzt. */
|
||
hinge?: "start" | "end";
|
||
/** Nur Tür: auf welche Seite der Wandachse das Blatt aufschlägt. */
|
||
swing?: SwingSide;
|
||
/** Nur Tür: Öffnungswinkel des Blatts in Grad (Default 90). */
|
||
swingAngle?: number;
|
||
/** Nur Tür: Aufschlagrichtung (nach innen/außen); Default "in". */
|
||
openingDir?: "in" | "out";
|
||
/** Optionale Rahmenstärke (quer zur Wand) in Metern für die 3D-Darstellung. */
|
||
frameThickness?: number;
|
||
/**
|
||
* Nur Tür: Sturzlinien (SIA, gestrichelt) quer über die Öffnung an der Wand-
|
||
* Innen- und/oder Aussenkante. Zeigt die Überkopf-Projektion des Sturzes.
|
||
* "keine" → keine Sturzlinien
|
||
* "innen" → eine Linie an der Wand-Innenkante
|
||
* "aussen"→ eine Linie an der Wand-Aussenkante
|
||
* "beide" → beide Linien (Default wenn nicht gesetzt)
|
||
*/
|
||
lintelLines?: "keine" | "innen" | "aussen" | "beide";
|
||
/**
|
||
* Referenz auf einen Bauteil-Typ (Bibliothek): {@link DoorType} bei
|
||
* `kind: "door"`, {@link WindowType} bei `kind: "window"`. Fehlt sie, gilt
|
||
* das heutige Inline-Verhalten (rückwärtskompatibel). Typ-Defaults liefern
|
||
* Bauart/Rahmen/Verglasung; Element-Felder (Breite/Höhe/…) übersteuern.
|
||
*/
|
||
typeId?: string;
|
||
/**
|
||
* Optionale per-Element-Übersteuerung der Detailstufe (grob/normal/
|
||
* detailliert); fehlt sie, gilt die Ansichts-/Projekt-Detailstufe.
|
||
*/
|
||
detailLevel?: DetailLevel;
|
||
/**
|
||
* Optionale Übersteuerung der Strich-/Symbolfarbe; sonst gilt die
|
||
* Kategorie-Farbe.
|
||
*/
|
||
color?: string;
|
||
}
|
||
|
||
/**
|
||
* Grundform einer Treppe (DOSSIER: gerade / L / Wendel).
|
||
* • "straight" — ein gerader Lauf (Lauflinie = Start → Richtung).
|
||
* • "L" — zwei rechtwinklige Läufe mit Zwischenpodest an der Ecke.
|
||
* • "spiral" — Wendeltreppe um ein Zentrum (keilförmige Tritte).
|
||
*/
|
||
export type StairShape = "straight" | "L" | "spiral";
|
||
|
||
/**
|
||
* Eine Treppe (Treppe/Stair) — ein geschossübergreifendes Bauteil, das über die
|
||
* Geschosshöhe (OKFF → OKFF des nächsten Geschosses) steigt. Definiert über eine
|
||
* Grundform (gerade / L / Wendel), eine Basis-Geometrie, die Laufbreite und die
|
||
* Stufung (Steigungshöhe/Auftrittstiefe, aus der Stufenanzahl abgeleitet).
|
||
*
|
||
* Basis-Geometrie je Grundform:
|
||
* • straight — `start` + `dir` (Einheitsrichtung) + `runLength` (Lauflänge).
|
||
* • L — `start` + `dir` (erster Lauf) + `runLength` (erster Lauf) +
|
||
* `run2Length` (zweiter Lauf) + `turn` (+1 = links, −1 = rechts abbiegen).
|
||
* Das Zwischenpodest sitzt am Ende des ersten Laufs (quadratisch, `width`).
|
||
* • spiral — `center` + `radius` (Innenradius zur Lauflinie) + `sweep`
|
||
* (Gesamtwinkel in Grad, +/− = Drehrichtung) + `start` (Startpunkt des ersten
|
||
* Tritts am äußeren Rand, definiert die Anfangsrichtung).
|
||
*
|
||
* Vertikale Lage: die UNTERKANTE (`baseZ`, abgeleitet aus dem Geschoss-OKFF) plus
|
||
* `totalRise` (Default = Geschosshöhe des zugehörigen Geschosses, sodass die
|
||
* Treppe genau ins nächste Geschoss steigt). `stepCount` Tritte/Setzstufen
|
||
* verteilen `totalRise` gleichmäßig; die Steigungshöhe = totalRise/stepCount, die
|
||
* Auftrittstiefe ergibt sich aus Lauflänge/(stepCount−1). Eine SIA-nahe
|
||
* Schrittregel (2·Steigung + Auftritt ≈ 0.63 m) liefert die Default-Stufenzahl.
|
||
*/
|
||
export interface Stair {
|
||
id: string;
|
||
type: "stair";
|
||
/** Zugehörige Zeichnungsebene (Geschoss), von dem die Treppe aufsteigt. */
|
||
floorId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "40" für Treppen. */
|
||
categoryCode: string;
|
||
/** Grundform (gerade / L / Wendel). */
|
||
shape: StairShape;
|
||
/** Startpunkt der Lauflinie im Grundriss (Meter). */
|
||
start: Vec2;
|
||
/** Einheits-Laufrichtung des (ersten) Laufs im Grundriss. */
|
||
dir: Vec2;
|
||
/** Lauflänge des (ersten) Laufs in Metern (entlang `dir`). */
|
||
runLength: number;
|
||
/** Nur L: Lauflänge des zweiten Laufs in Metern. */
|
||
run2Length?: number;
|
||
/** Nur L: Abbiegerichtung des zweiten Laufs (+1 = links, −1 = rechts). */
|
||
turn?: 1 | -1;
|
||
/** Nur Wendel: Zentrum der Wendeltreppe (Meter). */
|
||
center?: Vec2;
|
||
/** Nur Wendel: Radius (Meter) von der Mitte zur Lauflinie. */
|
||
radius?: number;
|
||
/** Nur Wendel: Gesamt-Drehwinkel in Grad (+ = CCW, − = CW). */
|
||
sweep?: number;
|
||
/** Laufbreite in Metern (quer zur Laufrichtung). */
|
||
width: number;
|
||
/**
|
||
* Lage der gespeicherten Achse (`start`/`dir` bzw. Wendel-Bogen) über die
|
||
* Laufbreite — analog `treppe_referenz` im Rhino-Plugin. Bestimmt, ob die Achse
|
||
* die LINKE, MITTLERE oder RECHTE Kante der Treppe repräsentiert (in
|
||
* Laufrichtung gesehen). Fehlt sie, gilt "mitte" (= heutiges Verhalten: Achse =
|
||
* Treppen-Mitte).
|
||
* • "links" — Achse = linke Kante; die Treppe liegt rechts der Achse.
|
||
* • "mitte" — Achse = Mitte (Default).
|
||
* • "rechts" — Achse = rechte Kante; die Treppe liegt links der Achse.
|
||
* Die Lauflinie samt Pfeil bleibt IMMER auf der visuellen Treppen-Mitte
|
||
* (nicht auf der Referenzkante) — siehe `stairGeometry`.
|
||
*/
|
||
referenz?: "links" | "mitte" | "rechts";
|
||
/**
|
||
* Gesamt-Steighöhe in Metern (OKFF → OKFF nächstes Geschoss). Fehlt sie, gilt
|
||
* beim Auflösen die Geschosshöhe des zugehörigen Geschosses.
|
||
*/
|
||
totalRise?: number;
|
||
/** Anzahl der Steigungen (Setzstufen). Die Trittanzahl = stepCount (letzte =
|
||
* Austritt aufs obere Geschoss). Steigungshöhe = totalRise / stepCount. */
|
||
stepCount: number;
|
||
/**
|
||
* Laufrichtung „aufwärts": true = die Lauflinie steigt von `start` in Richtung
|
||
* `dir` (Default). false kehrt Auf-/Abpfeil um (Treppe steigt zum Start hin).
|
||
*/
|
||
up?: boolean;
|
||
/**
|
||
* Referenz auf einen {@link StairType} (Bibliothek). Fehlt sie, gilt das
|
||
* heutige Inline-Verhalten (rückwärtskompatibel). Typ-Defaults liefern
|
||
* Tragart/Stufenausbildung/Geländer; Element-Felder übersteuern.
|
||
*/
|
||
typeId?: string;
|
||
/**
|
||
* Optionale per-Element-Übersteuerung der Detailstufe (grob/normal/
|
||
* detailliert); fehlt sie, gilt die Ansichts-/Projekt-Detailstufe.
|
||
*/
|
||
detailLevel?: DetailLevel;
|
||
/**
|
||
* Optionale Übersteuerung der Strich-/Umrandungsfarbe; sonst gilt die
|
||
* Kategorie-Farbe.
|
||
*/
|
||
color?: string;
|
||
}
|
||
|
||
/**
|
||
* Ein Raum (Raum/Room) — eine SIA-416-Fläche auf einem Geschoss, definiert über
|
||
* einen GESCHLOSSENEN Umriss (Polygon, lichte Innenkontur) im Grundriss. Trägt
|
||
* eine SIA-416-Blatt-Kategorie (HNF/NNF/VF/FF/KGF), einen Namen und eine Farbe.
|
||
*
|
||
* Fläche/Umfang/Schwerpunkt werden NICHT gespeichert, sondern bei jedem Rendern
|
||
* aus `boundary` über den reinen Rechenkern (geometry/roomArea) abgeleitet —
|
||
* so sind sie nie veraltet. `stampAnchor` (optional) setzt den Ankerpunkt des
|
||
* Raum-Stempels; fehlt er, gilt der Flächenschwerpunkt (Centroid).
|
||
*/
|
||
export interface Room {
|
||
id: string;
|
||
type: "room";
|
||
/** Zugehörige Zeichnungsebene (Geschoss). */
|
||
floorId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "45" für Räume. */
|
||
categoryCode: string;
|
||
/** SIA-416-Blatt-Kategorie (HNF/NNF/VF/FF/KGF). */
|
||
siaCategory: SiaCategory;
|
||
/** Raum-Name, z. B. „Wohnen". */
|
||
name: string;
|
||
/**
|
||
* Geschlossener Umriss (lichte Innenkontur) im Grundriss (Meter). Der
|
||
* Schlusspunkt wird NICHT dupliziert (die letzte Kante läuft von boundary[n-1]
|
||
* zu boundary[0]).
|
||
*/
|
||
boundary: Vec2[];
|
||
/** Strich-/Füllfarbe des Raums (hex). */
|
||
color: string;
|
||
/**
|
||
* Anker des Raum-Stempels (Meter). Wird bei der Erstellung EINMAL auf den
|
||
* Zentroid gesetzt und danach nie automatisch neu berechnet — der Stempel ist
|
||
* frei verschiebbar und bleibt beim Ändern der Kontur/Fläche an seiner Stelle.
|
||
* Fehlt er (Alt-Daten), gilt beim Rendern der Centroid.
|
||
*/
|
||
stampAnchor?: Vec2;
|
||
/**
|
||
* Frei editierbarer Rich-Text des Raum-Stempels (Name + Notizen, mit Fett/
|
||
* Kursiv/Grösse/Farbe/Ausrichtung). Fehlt er, gilt der Raum-Name als einfacher
|
||
* Text. Die LIVE-Flächenzeile wird beim Rendern separat darunter gesetzt.
|
||
*/
|
||
stampDoc?: RichTextDoc;
|
||
/**
|
||
* Strukturierter Raum-Stempel (Feldmodell). Ist er gesetzt, hat er Vorrang vor
|
||
* `stampDoc`/`name`: der Stempel-Text wird aus den Feldern gebaut, die Live-
|
||
* Zeilen (Bodenfläche/Nutzung) aus den Flags. Fehlt er, gilt der Alt-Pfad.
|
||
*/
|
||
stamp?: RoomStamp;
|
||
}
|
||
|
||
/**
|
||
* Feldmodell des Raum-Stempels. Ersetzt den freien Text durch benannte Felder;
|
||
* daraus baut roomStamp.ts sowohl das gerenderte Rich-Text-Dokument als auch die
|
||
* Live-Zeilen. Minimal gehalten (MVP).
|
||
*
|
||
* Folge-Arbeit: Fensterfläche (braucht Öffnung-in-Raum-Geometrie) — hier bewusst
|
||
* NICHT enthalten.
|
||
*/
|
||
export interface RoomStamp {
|
||
/** Raumnummer (kleiner Präfix in Zeile 1). */
|
||
number?: string;
|
||
/** Raumname (Zeile 1). */
|
||
name: string;
|
||
/** Raumname Zeile 2 (in Listen mit `name` zu einem Namen zusammengezogen). */
|
||
nameLine2?: string;
|
||
/** Bodenfläche anzeigen (Live-Zeile). */
|
||
showFloorArea: boolean;
|
||
/** Präfix vor der Bodenfläche, z. B. "BF " (Default leer). */
|
||
floorAreaPrefix?: string;
|
||
/** Nutzung (HNF/… · Bezeichnung) anzeigen (Live-Zeile). */
|
||
showUsage: boolean;
|
||
/** Ausrichtung der Namenszeile (Zeile 1). Fehlt sie, gilt „links". */
|
||
nameAlign?: Align;
|
||
/** Ausrichtung der zweiten Namenszeile. Fehlt sie, gilt „links". */
|
||
line2Align?: Align;
|
||
/** Ausrichtung der Bodenflächen-Zeile. Fehlt sie, gilt „zentriert" (Alt-Verhalten). */
|
||
floorAreaAlign?: Align;
|
||
/** Ausrichtung der Nutzungs-Zeile. Fehlt sie, gilt „zentriert" (Alt-Verhalten). */
|
||
usageAlign?: Align;
|
||
/**
|
||
* Personenzahl (Live-Zeile), z. B. für Nutzungsauflagen. Fehlt/undefined:
|
||
* keine Personenzahl-Zeile (Alt-Verhalten).
|
||
*/
|
||
occupancy?: number;
|
||
/**
|
||
* Rundungsschritt der angezeigten Bodenfläche in m² (z. B. 0.5 → auf halbe
|
||
* m² gerundet). Fehlt er, gilt das Alt-Verhalten: 2 Nachkommastellen ohne
|
||
* Schrittrundung.
|
||
*/
|
||
roundingStep?: number;
|
||
}
|
||
|
||
/** Eine Tür, gehostet in einer Wand. Ihr Geschoss ergibt sich aus der Wand. */
|
||
export interface Door {
|
||
id: string;
|
||
type: "door";
|
||
hostWallId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "21" für Türen/Fenster. */
|
||
categoryCode: string;
|
||
/** Abstand des Türanschlags (erster Pfosten) vom Wand-Startpunkt, in Metern. */
|
||
position: number;
|
||
/** Türbreite (lichte Öffnung) in Metern. */
|
||
width: number;
|
||
/** Türhöhe in Metern. */
|
||
height: number;
|
||
/** Auf welche Seite der Wandachse die Tür aufschlägt. */
|
||
swing: SwingSide;
|
||
/** An welchem Pfosten das Scharnier sitzt. */
|
||
hinge: "start" | "end";
|
||
}
|
||
|
||
// ── Freie 2D-Zeichengeometrie (Drawing2D) ──────────────────────────────────
|
||
// Ein semantisches 2D-Element (wie Wall/Door), das beim Rendern abgeleitet wird
|
||
// (keine vorab erzeugten Plan-Primitive). Siehe docs/design/drawing-tools.md §7.
|
||
|
||
/** Geometrie-Form eines 2D-Zeichenelements. */
|
||
export type Drawing2DGeom =
|
||
| { shape: "line"; a: Vec2; b: Vec2 }
|
||
| { shape: "polyline"; pts: Vec2[]; closed: boolean }
|
||
| { shape: "rect"; min: Vec2; max: Vec2 }
|
||
| { shape: "circle"; center: Vec2; r: number }
|
||
| { shape: "arc"; center: Vec2; r: number; a0: number; a1: number }
|
||
| {
|
||
shape: "text";
|
||
at: Vec2;
|
||
text: string;
|
||
height: number;
|
||
angle: number;
|
||
/**
|
||
* Optionale Spaltenbreite in Metern (Textspalte/Absatztext). Ist sie gesetzt,
|
||
* wird der Text beim Rendern wortweise auf diese Breite umgebrochen; fehlt
|
||
* sie, bleibt es einzeiliger Text (heutiges Verhalten).
|
||
*/
|
||
width?: number;
|
||
/**
|
||
* Optionale einheitliche Formatierung des ganzen Texts (Schriftfamilie, fett/
|
||
* kursiv, Farbe …) — gesetzt über die Text-Formatier-Gruppe der Oberleiste,
|
||
* wenn dieser Text selektiert ist. Die Grösse bleibt bewusst über `height`
|
||
* (Modell-Meter) geführt, nicht über `marks.sizePt`.
|
||
*/
|
||
marks?: Marks;
|
||
};
|
||
|
||
/** Ein freies 2D-Zeichenelement auf einer Zeichnungsebene. */
|
||
export interface Drawing2D {
|
||
id: string;
|
||
type: "drawing2d";
|
||
/** Zeichnungsebene (Geschoss ODER freie 2D-Ebene). */
|
||
levelId: string;
|
||
/** Grafik-Kategorie (Ebene) — liefert Farbe/Strichstärke als Default. */
|
||
categoryCode: string;
|
||
geom: Drawing2DGeom;
|
||
/** Optionaler Linienstil (Line Manager); sonst Kategorie-Default. */
|
||
lineStyleId?: string;
|
||
/** Optionale Schraffur für geschlossene Formen (Hatch Manager). */
|
||
hatchId?: string;
|
||
/**
|
||
* Attribut-Override der Muster-/Schraffurfarbe (Vordergrund) DIESER 2D-Form.
|
||
* `undefined` = „Nach System" (erben → HatchStyle.color-Fallback). Betrifft die
|
||
* Farbe der Schraffur-Musterlinien einer gefüllten Fläche (nicht den Umriss,
|
||
* der über `color`/`lineStyleId` läuft).
|
||
*/
|
||
foreground?: string;
|
||
/**
|
||
* Attribut-Override der Füllfarbe (Hintergrund) DIESER 2D-Form. `undefined` =
|
||
* „Nach System". Synonym/Nachfolger von `fillColor`; ist `background` gesetzt,
|
||
* hat es Vorrang vor `fillColor` (der Poché-Hintergrund der geschlossenen Form).
|
||
*/
|
||
background?: string;
|
||
/** Optionale explizite Strichfarbe; sonst Kategorie-Farbe. */
|
||
color?: string;
|
||
/**
|
||
* Optionale Vollton-Füllfarbe für geschlossene Formen (getrennt von der
|
||
* Strichfarbe `color`). Fehlt sie, ist die Fläche transparent (nur Schraffur
|
||
* bzw. ungefüllt).
|
||
*/
|
||
fillColor?: string;
|
||
/**
|
||
* Optionale direkte Strichstärke-Übersteuerung in Millimetern; hat Vorrang
|
||
* vor dem LineStyle-Gewicht und der Kategorie-Strichstärke.
|
||
*/
|
||
weightMm?: number;
|
||
/**
|
||
* Quelle des Vordergrunds (Schraffur-Musterfarbe), wenn kein explizites
|
||
* `foreground` gesetzt ist: "layer" erzwingt die Ebenenfarbe
|
||
* (LayerCategory.color), "object"/`undefined` (Default) = heutiges Verhalten
|
||
* (kein Bauteil-Bezug bei Drawing2D → HatchStyle.color-Fallback).
|
||
*/
|
||
foregroundSource?: AttributeSource;
|
||
/** Quelle des Hintergrunds (Füllfarbe), analog zu `foregroundSource`. */
|
||
backgroundSource?: AttributeSource;
|
||
/**
|
||
* Quelle der Strichstärke, wenn kein explizites `weightMm` gesetzt ist:
|
||
* "layer" erzwingt `LayerCategory.lw` (unter Umgehung des LineStyle-Gewichts),
|
||
* "object"/`undefined` (Default) = heutige Kette (LineStyle.weight ?? Kategorie).
|
||
*/
|
||
strokeWeightSource?: AttributeSource;
|
||
/** Quelle der Schraffur, wenn kein explizites `hatchId` gesetzt ist: "layer"
|
||
* erzwingt `LayerCategory.hatch`, "object"/`undefined` (Default) = heutiges
|
||
* Verhalten (ohne `hatchId` keine Schraffur). */
|
||
hatchSource?: AttributeSource;
|
||
}
|
||
|
||
/**
|
||
* Ein Kanten-/Seiten-Griff eines selektierten Elements (zusätzlich zu den
|
||
* Eckpunkt-Griffen). Liegt am Mittelpunkt einer Seite (Modell-Meter) und zeigt
|
||
* mit `normal` als Einheitsvektor nach AUSSEN (vom Element weg). `aIndex`/
|
||
* `bIndex` sind die beiden Vertex-Indizes der Kante — passend zur Indizierung
|
||
* von `drawingVertices`/`moveGripOf`. Ziehen verschiebt BEIDE Vertices senkrecht
|
||
* zur Kante (Form wächst/schrumpft an dieser Seite).
|
||
*/
|
||
export interface EdgeGrip {
|
||
mid: Vec2;
|
||
normal: Vec2;
|
||
aIndex: number;
|
||
bIndex: number;
|
||
/**
|
||
* Freie Kanten-Verschiebung: gesetzt bei OFFENER Geometrie (Linie/offene
|
||
* Polylinie). Das Segment folgt dem vollen Cursor-Delta (nicht nur der Normale),
|
||
* beide Endpunkte wandern mit, die Nachbar-Segmente dehnen sich nach. Bei
|
||
* geschlossenen Formen/Wänden fehlt das Flag → senkrechte (parallel-)Verschiebung
|
||
* entlang der Außennormale wie bisher.
|
||
*/
|
||
free?: boolean;
|
||
}
|
||
|
||
export type Element = Wall | Ceiling | Opening | Door | Stair | Room | Drawing2D;
|
||
|
||
// ── Extrudierter Körper (truck-Integration, docs/design/truck-plan.md) ─────
|
||
// Ein per `extrude`-Befehl aus einem geschlossenen 2D-Profil (Polylinie-Ring/
|
||
// Rechteck) erzeugter 3D-Körper. Die eigentliche B-Rep-Extrusion (truck-WASM,
|
||
// `engine/truckSolid.ts`) läuft NICHT hier, sondern asynchron in
|
||
// `toWalls3d.ts` (emitExtrudedSolids) — dieser Typ hält nur die Rohdaten.
|
||
|
||
/** Ein extrudierter Körper: geschlossenes Profil (Modell-Meter) + Höhe. */
|
||
export interface ExtrudedSolid {
|
||
id: string;
|
||
type: "extrudedSolid";
|
||
/** Geschoss, dessen `baseElevation` die UK des Körpers bestimmt. */
|
||
levelId: string;
|
||
/**
|
||
* Geschlossenes Profil in der XY-Ebene (Grundriss), ≥3 Punkte. Bei einem
|
||
* Kreis-Profil (`circle` gesetzt) eine Tessellierung des Kreises (48-Eck) —
|
||
* für 2D-Footprint/Auswahl/bbox/Verschieben; die 3D-Extrusion nutzt in dem
|
||
* Fall stattdessen `circle` (echte runde truck-Extrusion, `extrudeCircle`).
|
||
*/
|
||
points: Vec2[];
|
||
/** Extrusionshöhe in Metern (> 0), nach +Z ab der Geschoss-UK. */
|
||
height: number;
|
||
/**
|
||
* Gesetzt, wenn das Profil ein Kreis ist (aus einem `Drawing2D` mit
|
||
* `shape:"circle"`) — `points` bleibt die Tessellierung, `toWalls3d.ts`
|
||
* (emitExtrudedSolids) nutzt `circle` für die runde 3D-Extrusion.
|
||
*/
|
||
circle?: { center: Vec2; r: number };
|
||
/**
|
||
* Verjüngung 0 (Prisma, Default/fehlend) … 1 (Spitze — Kegel bei Kreis-,
|
||
* Pyramide bei Polygon-Profil). Linear zum Profil-Schwerpunkt skaliert.
|
||
*/
|
||
taper?: number;
|
||
}
|
||
|
||
// ── Stütze (Column, Tragwerk) ──────────────────────────────────────────────
|
||
// Eine Stütze ist im Kern eine PLATZIERTE PROFIL-EXTRUSION: ein an `position`
|
||
// gesetztes 2D-Profil (Rechteck oder Kreis), um `rotation` gedreht, über `height`
|
||
// nach oben extrudiert. Anders als der generische `ExtrudedSolid` trägt sie
|
||
// vollständige BIM-Attribute (Geschoss, Kategorie, optionales Bauteil, UK/OK-
|
||
// Anker) — sie lebt daher in `Project.columns` mit eigenem Selektionskanal.
|
||
|
||
/**
|
||
* Querschnitt einer Stütze im Grundriss (Modell-Meter), lokal um `position`.
|
||
* • "rect" — Rechteck `width` (X, quer) × `depth` (Y, längs), vor Rotation.
|
||
* • "round" — Kreis mit `radius`.
|
||
*/
|
||
export type ColumnProfile =
|
||
| { kind: "rect"; width: number; depth: number }
|
||
| { kind: "round"; radius: number };
|
||
|
||
/**
|
||
* Eine Stütze (Column) — ein geschossgebundenes Tragwerk-Bauteil (Kategorie
|
||
* „50"), definiert über einen Einfügepunkt `position`, ein Profil, eine Drehung
|
||
* und eine Höhe. Vertikale Lage analog Wand (UK/OK-Anker, sonst Geschoss-UK +
|
||
* `height`).
|
||
*/
|
||
export interface Column {
|
||
id: string;
|
||
type: "column";
|
||
/** Zugehörige Zeichnungsebene (Geschoss). */
|
||
floorId: string;
|
||
/** Grafik-Kategorie (Ebene), z. B. "50" für Tragwerk. */
|
||
categoryCode: string;
|
||
/** Einfügepunkt (Profil-Mitte) im Grundriss (Meter). */
|
||
position: Vec2;
|
||
/** Querschnitt (Rechteck oder Kreis). */
|
||
profile: ColumnProfile;
|
||
/** Drehung des Profils um `position` (Radiant, CCW; Default 0). */
|
||
rotation: number;
|
||
/** Höhe in Metern (> 0), nach +Z ab der UK. */
|
||
height: number;
|
||
/** Optionales tragendes Bauteil (Component) für Poché/3D-Farbe/Schraffur. */
|
||
componentId?: string;
|
||
/**
|
||
* Vertikale Bindung der Unterkante (UK). Fehlt sie, sitzt die UK auf der
|
||
* baseElevation des zugehörigen Geschosses (= Wand-Default).
|
||
*/
|
||
bottom?: VerticalAnchor;
|
||
/**
|
||
* Vertikale Bindung der Oberkante (OK). Fehlt sie, ergibt sich die OK aus
|
||
* UK + `height`.
|
||
*/
|
||
top?: VerticalAnchor;
|
||
/**
|
||
* Optionale Übersteuerung der Strich-/Umrandungsfarbe; sonst gilt die
|
||
* Kategorie-Farbe.
|
||
*/
|
||
color?: string;
|
||
}
|
||
|
||
// ── Kontext-Geometrie (importiert / abgeleitet, NICHT semantisch) ───────────
|
||
// Importierte Geometrie ist „dumme" KONTEXT-Geometrie (Anzeige + späteres
|
||
// Snap-Ziel), KEIN Teil des semantischen BIM-Modells. Sie lebt in einer eigenen
|
||
// Schicht `Project.context` und wird beim Rendern wie eine Referenz behandelt.
|
||
// Bewusst three-frei: nur rohe Buffer-Daten (positions/indices), three-Objekte
|
||
// entstehen erst im Viewport.
|
||
|
||
/**
|
||
* Ein importiertes Dreiecks-Mesh (z. B. aus DXF 3DFACE/POLYFACE/MESH). Rohe
|
||
* BufferGeometry-Daten: `positions` = flaches Array (x,y,z, x,y,z, …) in Metern,
|
||
* `indices` = Dreiecks-Indizes (je 3 ein Dreieck). Keine three-Objekte.
|
||
*/
|
||
export interface ImportedMesh {
|
||
id: string;
|
||
type: "importedMesh";
|
||
name: string;
|
||
/** Ursprünglicher DXF-Layer-Name (für spätere Kategorisierung). */
|
||
layer?: string;
|
||
positions: number[];
|
||
indices: number[];
|
||
}
|
||
|
||
/**
|
||
* Eine einzelne Kontur (Höhenlinie / Polylinie) auf konstanter Höhe `z`. `pts`
|
||
* sind 2D-Stützpunkte (x,y) in Metern; `closed` schließt den Linienzug.
|
||
*/
|
||
export interface Contour {
|
||
z: number;
|
||
pts: Vec2[];
|
||
closed: boolean;
|
||
/** Ursprünglicher DXF-Layer-Name (für die Kategorisierung beim 2D-Import). */
|
||
layer?: string;
|
||
/**
|
||
* Gefüllte Fläche (aus einer DXF-HATCH). Beim Drawing-Import wird daraus eine
|
||
* geschlossene, gefüllte 2D-Form (Vollton-Füllung); sonst nur ein Umriss.
|
||
*/
|
||
filled?: boolean;
|
||
/**
|
||
* Wahre Kurvengeometrie (aus CIRCLE/ARC). `pts` bleibt tesselliert (Kontext/
|
||
* 3D), aber der Drawing-Import baut daraus eine GLATTE `{shape:"circle"|"arc"}`-
|
||
* Form statt eines Vielecks. Winkel in RADIANT (CCW); Kreis = a0..a1 über 2π.
|
||
*/
|
||
curve?: {
|
||
kind: "circle" | "arc";
|
||
cx: number;
|
||
cy: number;
|
||
r: number;
|
||
a0: number;
|
||
a1: number;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Ein importiertes Text-Element (DXF TEXT/MTEXT). `at` = Einfügepunkt in Metern,
|
||
* `height` = Schrifthöhe in Modell-Metern, `angle` = Drehung in RADIANT (CCW).
|
||
* Wird beim Drawing-Import zu einem `{shape:"text"}`-Drawing2D.
|
||
*/
|
||
export interface ImportedText {
|
||
at: Vec2;
|
||
text: string;
|
||
height: number;
|
||
angle: number;
|
||
/** Ursprünglicher DXF-Layer-Name (für die Kategorisierung beim 2D-Import). */
|
||
layer?: string;
|
||
}
|
||
|
||
/** Ein Satz Konturen (z. B. alle Höhenlinien eines DXF-Imports). */
|
||
export interface ContourSet {
|
||
id: string;
|
||
type: "contourSet";
|
||
name: string;
|
||
/** Ursprünglicher DXF-Layer-Name (für spätere Kategorisierung). */
|
||
layer?: string;
|
||
contours: Contour[];
|
||
}
|
||
|
||
/**
|
||
* Ein TIN-Geländemodell, abgeleitet aus Konturen (Delaunay über (x,y), Z aus der
|
||
* jeweiligen Kontur-Höhe). `positions` = flaches (x,y,z…)-Array in Metern,
|
||
* `indices` = Dreiecks-Indizes. Gelände ist NICHT semantisch (Kontext-Schicht).
|
||
*/
|
||
export interface TerrainMesh {
|
||
id: string;
|
||
type: "terrainMesh";
|
||
name: string;
|
||
positions: number[];
|
||
indices: number[];
|
||
}
|
||
|
||
/** Ein Kontext-Objekt: importiertes Mesh, Konturen-Satz oder abgeleitetes TIN. */
|
||
export type ContextObject = ImportedMesh | ContourSet | TerrainMesh;
|
||
|
||
/** Das gesamte Projekt. */
|
||
/**
|
||
* Ein Ausschnitt (View-Snapshot) — eine benannte, gespeicherte Ansicht, die den
|
||
* kompletten Darstellungszustand einfängt und per Klick wiederherstellt
|
||
* (DOSSIER A2, ROADMAP §2c/§11). KEIN neuer Zustand, sondern ein Container, der
|
||
* bereits vorhandene Bausteine KOMPONIERT + benennt: Ansicht (Typ/Kamera/
|
||
* Massstab/Detail), Sichtbarkeit (Ebenen-/Zeichnungskombination, gleiches
|
||
* Payload-Format wie {@link https LayerCombo}/`DrawingCombo`), aktive Overrides
|
||
* und das aktive Geschoss. Lebt im DOKUMENT (`Project.viewSnapshots`), nicht im
|
||
* localStorage — Ausschnitte gehören zum Projekt (Export/Teilen).
|
||
*/
|
||
export interface ViewSnapshot {
|
||
id: string;
|
||
name: string;
|
||
/**
|
||
* LEGACY: name-basierte Flach-Gruppierung (Phase 1). Wird von der neuen
|
||
* Ordner-/Baum-UI nicht mehr genutzt, bleibt aber für Alt-Projekte gültig.
|
||
* Neue Ausschnitte tragen stattdessen `folderId` (echte Ordner-Referenz).
|
||
*/
|
||
folder?: string;
|
||
/**
|
||
* Ordner (Baumstruktur) des Ausschnitte-Panels, in dem der Ausschnitt liegt
|
||
* ({@link ViewSnapshotFolder}). Fehlt er (oder verweist er ins Leere), liegt
|
||
* der Ausschnitt auf der Wurzelebene. Additiv — Alt-Projekte laden unverändert.
|
||
*/
|
||
folderId?: string;
|
||
// ── Ansicht ──────────────────────────────────────────────────────────────
|
||
/** Ansichtstyp (Grundriss / Perspektive). */
|
||
viewType: ViewType;
|
||
/** Kanonischer 3D-Blickwinkel (Front/Top/Iso/… bzw. freie Perspektive). */
|
||
view3d: View3d;
|
||
/** Sichtwinkel (FOV) der 3D-Perspektivkamera in Grad. Optional (nur 3D relevant). */
|
||
fov?: number;
|
||
/** Papier-Massstab „1:N" (der Nenner). */
|
||
scaleDenominator: number;
|
||
/** Detailgrad der Darstellung (grob/mittel/fein). */
|
||
detail: DetailLevel;
|
||
// ── Zustand ──────────────────────────────────────────────────────────────
|
||
/** Aktives Geschoss/Zeichnungsebene beim Erfassen. */
|
||
activeLevelId: string;
|
||
/** Ebenen-Sichtbarkeit: Kategorie-`code` → sichtbar (wie `LayerCombo.codes`). */
|
||
layerVisibility: Record<string, boolean>;
|
||
/** Zeichnungsebenen-Sichtbarkeit: DrawingLevel-`id` → sichtbar (wie `DrawingCombo.ids`). */
|
||
drawingVisibility: Record<string, boolean>;
|
||
/**
|
||
* Ids der beim Erfassen AKTIVEN (`enabled`) Override-Regeln. Beim
|
||
* Wiederherstellen wird jede Regel des Projekts auf `enabled = ids.includes(id)`
|
||
* gesetzt. Optional, damit hand-/altangelegte Snapshots ohne Feld gültig sind.
|
||
*/
|
||
enabledOverrideRuleIds?: string[];
|
||
}
|
||
|
||
/**
|
||
* Ein Ordner im Ausschnitte-Baum (DOSSIER A2). Ausschnitte verweisen per
|
||
* `folderId` auf ihren Ordner; `parentId` erlaubt Verschachtelung (Ordner in
|
||
* Ordner). Fehlt `parentId`, liegt der Ordner auf der Wurzelebene. Additiv —
|
||
* Alt-Projekte laden unverändert (spiegelbildlich zu {@link LayoutFolder}).
|
||
*/
|
||
export interface ViewSnapshotFolder {
|
||
id: string;
|
||
name: string;
|
||
/** Übergeordneter Ordner; ohne → Wurzelebene. */
|
||
parentId?: string;
|
||
}
|
||
|
||
// ── Layout-Blätter mit Masterlayout (DOSSIER A3, ROADMAP §11) ───────────────
|
||
//
|
||
// Ein „Layout" ist ein Druck-/Plan-Blatt (A4/A3), auf dem mehrere Viewports
|
||
// platziert werden; jeder Viewport ist an einen Ausschnitt ({@link ViewSnapshot})
|
||
// gebunden und rendert dessen Plan im gewählten Massstab. Ein Masterlayout
|
||
// liefert die gemeinsamen Blatt-Elemente (Titelblock/Rahmen), die die Layouts
|
||
// erben. Alles lebt im DOKUMENT (`Project.layouts`/`Project.masterLayouts`),
|
||
// nicht im localStorage — Layouts gehören zum Projekt. Additiv: bestehende
|
||
// Projekte ohne diese Felder bleiben unverändert gültig.
|
||
|
||
/**
|
||
* Papierformat eines Layout-Blatts (Blattmasse in mm identisch zum PDF-Export).
|
||
* ISO-216-Reihen A0–A6 und B0–B6; Alt-Projekte (nur A4/A3) laden unverändert.
|
||
*/
|
||
export type LayoutPaperFormat =
|
||
| "a0"
|
||
| "a1"
|
||
| "a2"
|
||
| "a3"
|
||
| "a4"
|
||
| "a5"
|
||
| "a6"
|
||
| "b0"
|
||
| "b1"
|
||
| "b2"
|
||
| "b3"
|
||
| "b4"
|
||
| "b5"
|
||
| "b6";
|
||
/** Ausrichtung eines Layout-Blatts. */
|
||
export type LayoutOrientation = "portrait" | "landscape";
|
||
|
||
/**
|
||
* Einfache Textfelder des Titelblocks (Master). Alle optional — leere Felder
|
||
* werden beim Auflösen aus dem Projekt/Layout mit sinnvollen Defaults gefüllt
|
||
* (Projektname ← `Project.name`, Blattname ← `Layout.name`, Datum ← heute).
|
||
*/
|
||
export interface LayoutTitleBlock {
|
||
projectName?: string;
|
||
sheetName?: string;
|
||
scale?: string;
|
||
date?: string;
|
||
author?: string;
|
||
}
|
||
|
||
/**
|
||
* Masterlayout — gemeinsame Blatt-Elemente (Titelblock + optionaler Rahmen), die
|
||
* einzelne {@link Layout}s per `masterId` erben. Eine Änderung am Master schlägt
|
||
* auf alle Layouts durch, die ihn referenzieren (InDesign-/ArchiCAD-Muster).
|
||
*/
|
||
export interface MasterLayout {
|
||
id: string;
|
||
name: string;
|
||
paper: LayoutPaperFormat;
|
||
orientation: LayoutOrientation;
|
||
/** Titelblock-Textfelder (Vorlage; leere Felder werden aufgelöst/aufgefüllt). */
|
||
titleBlock: LayoutTitleBlock;
|
||
/**
|
||
* Ordner (Master-Baum), in dem das Masterlayout liegt ({@link LayoutFolder} mit
|
||
* `kind:"master"`). Fehlt er (oder verweist auf einen gelöschten/Layout-Ordner),
|
||
* liegt der Master auf der Wurzelebene. Additiv — Alt-Projekte laden unverändert.
|
||
*/
|
||
folderId?: string;
|
||
/** Blattrahmen zeichnen (Randlinie). Optional, Default `true`. */
|
||
border?: boolean;
|
||
/**
|
||
* Freie Blattbreite in mm. Ist sie (zusammen mit `customHeightMm`) gesetzt,
|
||
* überschreibt sie `paper`/A4·A3 (die effektive Blattgrösse ist dann
|
||
* `customWidthMm`×`customHeightMm`, `orientation` wird ignoriert). Fehlt sie,
|
||
* gilt `paper`+`orientation` wie bisher. Additiv — Alt-Projekte laden unverändert.
|
||
*/
|
||
customWidthMm?: number;
|
||
/** Freie Blatthöhe in mm (siehe {@link MasterLayout.customWidthMm}). */
|
||
customHeightMm?: number;
|
||
}
|
||
|
||
/**
|
||
* Ein platzierter Viewport auf einem Layout-Blatt — Position/Grösse in Papier-mm,
|
||
* gebunden an einen Ausschnitt (`snapshotId`). `scaleDenominator` übersteuert
|
||
* optional den Massstab des Ausschnitts (Massstab pro Viewport).
|
||
*/
|
||
export interface LayoutViewport {
|
||
id: string;
|
||
/** Id des gebundenen Ausschnitts ({@link ViewSnapshot}). */
|
||
snapshotId: string;
|
||
/** Linke Kante auf dem Blatt (mm, von links). */
|
||
xMm: number;
|
||
/** Obere Kante auf dem Blatt (mm, von oben). */
|
||
yMm: number;
|
||
/** Breite des Viewport-Rahmens auf dem Blatt (mm). */
|
||
widthMm: number;
|
||
/** Höhe des Viewport-Rahmens auf dem Blatt (mm). */
|
||
heightMm: number;
|
||
/** Optionaler Massstab-Nenner (1:N); übersteuert `snapshot.scaleDenominator`. */
|
||
scaleDenominator?: number;
|
||
}
|
||
|
||
/**
|
||
* Eine 2D-Annotation (Linie/Rechteck/Text), direkt auf dem Layout-Blatt in
|
||
* Papier-mm-Koordinaten gezeichnet — eine vom Viewport-Baum unabhängige
|
||
* Zusatzschicht (Markup). `color`/`weightMm` sind optional (Editor-Defaults:
|
||
* `#111111` / 0.25 mm); Text trägt statt `weightMm` seine Zeilenhöhe.
|
||
*/
|
||
export type LayoutAnnotation =
|
||
| {
|
||
id: string;
|
||
kind: "line";
|
||
x1Mm: number;
|
||
y1Mm: number;
|
||
x2Mm: number;
|
||
y2Mm: number;
|
||
color?: string;
|
||
weightMm?: number;
|
||
}
|
||
| {
|
||
id: string;
|
||
kind: "rect";
|
||
xMm: number;
|
||
yMm: number;
|
||
widthMm: number;
|
||
heightMm: number;
|
||
color?: string;
|
||
weightMm?: number;
|
||
}
|
||
| {
|
||
id: string;
|
||
kind: "text";
|
||
xMm: number;
|
||
yMm: number;
|
||
text: string;
|
||
heightMm: number;
|
||
color?: string;
|
||
};
|
||
|
||
/**
|
||
* Flaches Patch-Objekt für {@link LayoutAnnotation} — alle Felder ALLER Arten
|
||
* optional (statt eines pro Art unterscheidenden Unions), damit CRUD/Handler
|
||
* unabhängig von der konkreten `kind` bleiben (der Aufrufer setzt ohnehin nur
|
||
* die zur gewählten Annotation passenden Felder).
|
||
*/
|
||
export type LayoutAnnotationPatch = Partial<{
|
||
x1Mm: number;
|
||
y1Mm: number;
|
||
x2Mm: number;
|
||
y2Mm: number;
|
||
xMm: number;
|
||
yMm: number;
|
||
widthMm: number;
|
||
heightMm: number;
|
||
text: string;
|
||
color: string;
|
||
weightMm: number;
|
||
}>;
|
||
|
||
/**
|
||
* Ein Layout-Blatt — Papierformat/Ausrichtung, optionaler Master (`masterId`)
|
||
* und die platzierten Viewports.
|
||
*/
|
||
export interface Layout {
|
||
id: string;
|
||
name: string;
|
||
paper: LayoutPaperFormat;
|
||
orientation: LayoutOrientation;
|
||
/** Referenz auf ein {@link MasterLayout}; ohne → kein Master (blanko Blatt). */
|
||
masterId?: string;
|
||
/**
|
||
* Ordner (Baumstruktur), in dem das Layout liegt ({@link LayoutFolder}). Fehlt
|
||
* er (oder verweist auf einen gelöschten Ordner), liegt das Layout auf der
|
||
* Wurzelebene. Additiv — Alt-Projekte laden unverändert.
|
||
*/
|
||
folderId?: string;
|
||
/**
|
||
* Freie Blattbreite in mm — überschreibt `paper`/A4·A3, siehe
|
||
* {@link MasterLayout.customWidthMm}. Bei gesetztem `masterId` erbt das Blatt
|
||
* ohnehin die Master-Grösse; eigene Custom-Werte gelten nur ohne Master.
|
||
*/
|
||
customWidthMm?: number;
|
||
/** Freie Blatthöhe in mm (siehe {@link Layout.customWidthMm}). */
|
||
customHeightMm?: number;
|
||
viewports: LayoutViewport[];
|
||
/**
|
||
* 2D-Markups direkt auf dem Blatt (Linie/Rechteck/Text), siehe
|
||
* {@link LayoutAnnotation}. Additiv — Alt-Layouts ohne dieses Feld laden
|
||
* unverändert (Editor/CRUD behandeln ein fehlendes Array wie ein leeres).
|
||
*/
|
||
annotations?: LayoutAnnotation[];
|
||
}
|
||
|
||
/**
|
||
* Ein Ordner im Layouts-Baum (DOSSIER A3). Layouts verweisen per `folderId` auf
|
||
* ihren Ordner; `parentId` erlaubt Verschachtelung (Ordner in Ordner). Fehlt
|
||
* `parentId`, liegt der Ordner auf der Wurzelebene. Masterlayouts liegen NICHT
|
||
* in Ordnern (sie sind Vorlagen). Additiv — Alt-Projekte laden unverändert.
|
||
*/
|
||
export interface LayoutFolder {
|
||
id: string;
|
||
name: string;
|
||
/** Übergeordneter Ordner; ohne → Wurzelebene. */
|
||
parentId?: string;
|
||
/**
|
||
* Baum-Zugehörigkeit: `"layout"`-Ordner tragen Layouts, `"master"`-Ordner
|
||
* tragen Masterlayouts. Die beiden Bäume bleiben getrennt (ein Master-Ordner
|
||
* erscheint nie im Layout-Baum und umgekehrt). Fehlt das Feld, gilt `"layout"`
|
||
* (rückwärtskompatibel — Alt-Projekte ohne `kind` bleiben Layout-Ordner).
|
||
*/
|
||
kind?: "layout" | "master";
|
||
}
|
||
|
||
export interface Project {
|
||
id: string;
|
||
name: string;
|
||
/** Linienstil-Bibliothek (Line Manager). */
|
||
lineStyles: LineStyle[];
|
||
/** Schraffur-Bibliothek (Hatch Manager). */
|
||
hatches: HatchStyle[];
|
||
/** Bauteil-Material-Bibliothek (Component Manager). */
|
||
components: Component[];
|
||
wallTypes: WallType[];
|
||
/**
|
||
* Deckentypen (Deckenstile) — dediziert für Decken, analog `wallTypes`.
|
||
* Optional, damit bestehende Projekte/Tests ohne `ceilingTypes` gültig
|
||
* bleiben; Decken ohne `ceilingTypeId` lösen weiterhin über das LEGACY-Feld
|
||
* `Ceiling.wallTypeId` gegen `wallTypes` auf (siehe `getCeilingType`).
|
||
*/
|
||
ceilingTypes?: CeilingType[];
|
||
/**
|
||
* Türtypen-Bibliothek (Türstile), analog `wallTypes`. Optional, damit
|
||
* bestehende Projekte/Tests ohne `doorTypes` gültig bleiben; Türen ohne
|
||
* `typeId` verhalten sich wie bisher (Inline-Felder).
|
||
*/
|
||
doorTypes?: DoorType[];
|
||
/**
|
||
* Fenstertypen-Bibliothek (Fensterstile). Optional (siehe `doorTypes`).
|
||
*/
|
||
windowTypes?: WindowType[];
|
||
/**
|
||
* Treppentypen-Bibliothek (Treppenstile). Optional (siehe `doorTypes`).
|
||
*/
|
||
stairTypes?: StairType[];
|
||
/** Oberste Schnitte: Geschosse + Schnitte/Ansichten. */
|
||
drawingLevels: DrawingLevel[];
|
||
/** Grafik-Kategorie-Baum (geschossübergreifend). */
|
||
layers: LayerCategory[];
|
||
walls: Wall[];
|
||
/**
|
||
* Decken (Slabs) — geschossgebundene Flächenbauteile mit geschlossenem Umriss.
|
||
* Optional, damit bestehende Projekte/Tests ohne `ceilings` gültig bleiben
|
||
* (Default: leer behandeln).
|
||
*/
|
||
ceilings?: Ceiling[];
|
||
doors: Door[];
|
||
/**
|
||
* Öffnungen (Fenster/Türen), gehostet in Wänden. Optional, damit bestehende
|
||
* Projekte/Tests ohne `openings` gültig bleiben (Default: leer behandeln).
|
||
*/
|
||
openings?: Opening[];
|
||
/**
|
||
* Treppen (Treppe) — geschossübergreifende Bauteile (gerade/L/Wendel). Optional,
|
||
* damit bestehende Projekte/Tests ohne `stairs` gültig bleiben (Default: leer).
|
||
*/
|
||
stairs?: Stair[];
|
||
/**
|
||
* Räume (SIA-416-Flächen) — geschossgebundene Flächen mit geschlossenem Umriss.
|
||
* Optional, damit bestehende Projekte/Tests ohne `rooms` gültig bleiben
|
||
* (Default: leer behandeln).
|
||
*/
|
||
rooms?: Room[];
|
||
/** Freie 2D-Zeichengeometrie (Line/Polyline/Rect/Circle/Arc/Text). */
|
||
drawings2d: Drawing2D[];
|
||
/**
|
||
* Kontext-Schicht: importierte/abgeleitete „dumme" Geometrie (Meshes,
|
||
* Konturen, Gelände-TIN) — NICHT semantisch. Optional, damit bestehende
|
||
* Projekte/Tests ohne `context` gültig bleiben (Default: leer behandeln).
|
||
*/
|
||
context?: ContextObject[];
|
||
/**
|
||
* Extrudierte Körper (truck-Integration) — geschlossene 2D-Profile mit Höhe,
|
||
* siehe {@link ExtrudedSolid}. Optional, damit bestehende Projekte/Tests ohne
|
||
* `extrudedSolids` gültig bleiben (Default: leer behandeln).
|
||
*/
|
||
extrudedSolids?: ExtrudedSolid[];
|
||
/**
|
||
* Stützen (Columns) — geschossgebundene Tragwerk-Bauteile (platzierte
|
||
* Profil-Extrusionen), siehe {@link Column}. Optional, damit bestehende
|
||
* Projekte/Tests ohne `columns` gültig bleiben (Default: leer behandeln).
|
||
*/
|
||
columns?: Column[];
|
||
/**
|
||
* Bibliothek parametrischer Wand-Regelwerke. Optional, damit bestehende
|
||
* Projekte ohne `parametricWalls` gültig bleiben (Default: leer behandeln).
|
||
* Wird durch `resolveParametricWall()` in `src/model/parametricWalls.ts`
|
||
* aufgelöst — generiert Wall[]-Objekte bei Bedarf.
|
||
*/
|
||
parametricWalls?: ParametricWall[];
|
||
/**
|
||
* Grafische Override-Regeln (Regel-Engine, Reihenfolge = Priorität, oben
|
||
* gewinnt) — reines Rendering-Overlay, siehe {@link OverrideRule}. Optional,
|
||
* damit bestehende Projekte/Tests ohne `overrideRules` gültig bleiben
|
||
* (Default: leer behandeln).
|
||
*/
|
||
overrideRules?: OverrideRule[];
|
||
/**
|
||
* Ausschnitte / View-Snapshots (DOSSIER A2) — benannte, wiederherstellbare
|
||
* Ansichten (siehe {@link ViewSnapshot}). Optional, damit bestehende
|
||
* Projekte/Tests ohne `viewSnapshots` unverändert gültig bleiben (Default:
|
||
* leer behandeln). Gehört ins Dokument (nicht localStorage).
|
||
*/
|
||
viewSnapshots?: ViewSnapshot[];
|
||
/**
|
||
* Ordner des Ausschnitte-Baums (DOSSIER A2) — Gruppierung der
|
||
* {@link ViewSnapshot}s per `folderId`. Optional, damit bestehende
|
||
* Projekte/Tests ohne `viewSnapshotFolders` gültig bleiben (Default: leer →
|
||
* alle Ausschnitte auf Wurzelebene).
|
||
*/
|
||
viewSnapshotFolders?: ViewSnapshotFolder[];
|
||
/**
|
||
* Layout-Blätter (DOSSIER A3) — Druck-/Plan-Blätter mit platzierten Viewports
|
||
* (siehe {@link Layout}). Optional, damit bestehende Projekte/Tests ohne
|
||
* `layouts` gültig bleiben (Default: leer behandeln). Gehört ins Dokument.
|
||
*/
|
||
layouts?: Layout[];
|
||
/**
|
||
* Masterlayouts (DOSSIER A3) — gemeinsame Blatt-Elemente (Titelblock/Rahmen),
|
||
* die einzelne {@link Layout}s erben (siehe {@link MasterLayout}). Optional,
|
||
* damit bestehende Projekte/Tests ohne `masterLayouts` gültig bleiben.
|
||
*/
|
||
masterLayouts?: MasterLayout[];
|
||
/**
|
||
* Ordner des Layouts-Baums (DOSSIER A3) — Gruppierung der {@link Layout}s per
|
||
* `folderId`. Optional, damit bestehende Projekte/Tests ohne `layoutFolders`
|
||
* gültig bleiben (Default: leer → alle Layouts auf Wurzelebene).
|
||
*/
|
||
layoutFolders?: LayoutFolder[];
|
||
/**
|
||
* Referenzhöhe des Erdgeschosses in Metern über Meer (m ü. M.), editierbar
|
||
* im Einstellungs-Fenster. Optional, damit bestehende Projekte ohne diesen
|
||
* Wert gültig bleiben (Default: unbestimmt/0). NUR Speicherfeld — die
|
||
* eigentliche Verwendung (Terrain-Draping/reale Höhen relativ dazu, siehe
|
||
* HANDOVER GEO-BLOCK) ist ein separater, späterer Task und liest dieses
|
||
* Feld noch nicht.
|
||
*/
|
||
referenceElevationMasl?: number;
|
||
}
|
||
|
||
// ── Helfer ───────────────────────────────────────────────────────────────
|
||
|
||
export const getWallType = (project: Project, wall: Wall): WallType => {
|
||
const wt = project.wallTypes.find((t) => t.id === wall.wallTypeId);
|
||
if (!wt) throw new Error(`Unbekannter Wandtyp: ${wall.wallTypeId}`);
|
||
return wt;
|
||
};
|
||
|
||
/**
|
||
* Liefert den Aufbau-Typ einer Decke oder wirft. Bevorzugt den dedizierten
|
||
* Deckentyp (`ceilingTypeId` → `ceilingTypes`); fehlt er, fällt die Auflösung
|
||
* auf das LEGACY-Feld `wallTypeId` → `wallTypes` zurück (Rückwärtskompatibilität
|
||
* mit Projekten von vor den Deckenstilen — dort trug die Decke ihren Aufbau
|
||
* direkt über einen WallType).
|
||
*/
|
||
export const getCeilingType = (project: Project, ceiling: Ceiling): CeilingType | WallType => {
|
||
if (ceiling.ceilingTypeId) {
|
||
const ct = (project.ceilingTypes ?? []).find((t) => t.id === ceiling.ceilingTypeId);
|
||
if (ct) return ct;
|
||
}
|
||
const wt = project.wallTypes.find((t) => t.id === ceiling.wallTypeId);
|
||
if (!wt) throw new Error(`Unbekannter Deckentyp: ${ceiling.ceilingTypeId ?? ceiling.wallTypeId}`);
|
||
return wt;
|
||
};
|
||
|
||
/**
|
||
* Gesamtdicke einer Decke (Meter): eine explizite `thickness`-Übersteuerung hat
|
||
* Vorrang, sonst die Summe der Schichtdicken ihres Aufbau-Typs (siehe
|
||
* `getCeilingType`).
|
||
*/
|
||
export const ceilingThickness = (project: Project, ceiling: Ceiling): number => {
|
||
if (ceiling.thickness != null && ceiling.thickness > 0) return ceiling.thickness;
|
||
try {
|
||
return wallTypeThickness(getCeilingType(project, ceiling));
|
||
} catch {
|
||
return 0.2;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Löst den {@link DoorType} einer Tür-Öffnung auf, oder `undefined` (keine
|
||
* `typeId` bzw. unbekannt ⇒ Inline-Verhalten). Bewusst NICHT werfend — der
|
||
* Typ ist eine optionale Übersteuerung, kein Pflichtbezug wie beim Wandtyp.
|
||
*/
|
||
export const getDoorType = (project: Project, opening: Opening): DoorType | undefined =>
|
||
opening.typeId ? (project.doorTypes ?? []).find((t) => t.id === opening.typeId) : undefined;
|
||
|
||
/** Löst den {@link WindowType} einer Fenster-Öffnung auf, oder `undefined`. */
|
||
export const getWindowType = (project: Project, opening: Opening): WindowType | undefined =>
|
||
opening.typeId ? (project.windowTypes ?? []).find((t) => t.id === opening.typeId) : undefined;
|
||
|
||
/** Löst den {@link StairType} einer Treppe auf, oder `undefined`. */
|
||
export const getStairType = (project: Project, stair: Stair): StairType | undefined =>
|
||
stair.typeId ? (project.stairTypes ?? []).find((t) => t.id === stair.typeId) : undefined;
|
||
|
||
/** Alle Öffnungen einer Wand (leere Liste, wenn keine oder `openings` fehlt). */
|
||
export const openingsOfWall = (project: Project, wallId: string): Opening[] =>
|
||
(project.openings ?? []).filter((o) => o.hostWallId === wallId);
|
||
|
||
/** Menschenlesbarer Standardname einer Öffnung (Fenster/Tür + Breite×Höhe). */
|
||
export const openingLabel = (o: Opening): string =>
|
||
`${o.kind === "door" ? "Tür" : "Fenster"} ${(o.width * 100).toFixed(0)}×${(
|
||
o.height * 100
|
||
).toFixed(0)}`;
|
||
|
||
/** Alle Treppen eines Geschosses (leere Liste, wenn keine oder `stairs` fehlt). */
|
||
export const stairsOfFloor = (project: Project, floorId: string): Stair[] =>
|
||
(project.stairs ?? []).filter((s) => s.floorId === floorId);
|
||
|
||
/** Menschenlesbarer Standardname einer Treppe (Grundform + Stufenanzahl). */
|
||
export const stairLabel = (s: Stair): string => {
|
||
const shape =
|
||
s.shape === "straight" ? "Gerade" : s.shape === "L" ? "L-Treppe" : "Wendel";
|
||
return `${shape} ${s.stepCount} STG`;
|
||
};
|
||
|
||
/** Alle Stützen eines Geschosses (leere Liste, wenn keine oder `columns` fehlt). */
|
||
export const columnsOfFloor = (project: Project, floorId: string): Column[] =>
|
||
(project.columns ?? []).filter((c) => c.floorId === floorId);
|
||
|
||
/** Menschenlesbarer Standardname einer Stütze (Profil + Masse in cm). */
|
||
export const columnLabel = (c: Column): string => {
|
||
if (c.profile.kind === "round") {
|
||
return `Stütze Ø${(c.profile.radius * 200).toFixed(0)}`;
|
||
}
|
||
return `Stütze ${(c.profile.width * 100).toFixed(0)}×${(c.profile.depth * 100).toFixed(0)}`;
|
||
};
|
||
|
||
/** Alle Räume eines Geschosses (leere Liste, wenn keine oder `rooms` fehlt). */
|
||
export const roomsOfFloor = (project: Project, floorId: string): Room[] =>
|
||
(project.rooms ?? []).filter((r) => r.floorId === floorId);
|
||
|
||
/** Liefert ein Bauteil-Material (Component) per ID oder wirft. */
|
||
export const getComponent = (project: Project, id: string): Component => {
|
||
const c = project.components.find((co) => co.id === id);
|
||
if (!c) throw new Error(`Unbekanntes Bauteil-Material: ${id}`);
|
||
return c;
|
||
};
|
||
|
||
/** Liefert eine Schraffur (HatchStyle) per ID oder wirft. */
|
||
export const getHatch = (project: Project, id: string): HatchStyle => {
|
||
const h = project.hatches.find((ht) => ht.id === id);
|
||
if (!h) throw new Error(`Unbekannte Schraffur: ${id}`);
|
||
return h;
|
||
};
|
||
|
||
/** Liefert einen Linienstil (LineStyle) per ID oder wirft. */
|
||
export const getLineStyle = (project: Project, id: string): LineStyle => {
|
||
const l = project.lineStyles.find((ls) => ls.id === id);
|
||
if (!l) throw new Error(`Unbekannter Linienstil: ${id}`);
|
||
return l;
|
||
};
|
||
|
||
/** Liefert eine Zeichnungsebene (Geschoss) per ID oder wirft. */
|
||
export const getFloor = (project: Project, id: string): DrawingLevel => {
|
||
const g = project.drawingLevels.find((z) => z.id === id);
|
||
if (!g) throw new Error(`Unbekanntes Geschoss: ${id}`);
|
||
return g;
|
||
};
|
||
|
||
/**
|
||
* Stapelt baseElevation der Geschosse in Dokumentreihenfolge: Das erste
|
||
* Geschoss beginnt bei 0, jedes weitere bei baseElevation + floorHeight des
|
||
* vorigen Geschosses. Nicht-Geschoss-Ebenen behalten baseElevation undefined.
|
||
* Liefert eine neue Liste (mit neuen Geschoss-Objekten); die Eingabe bleibt
|
||
* unverändert.
|
||
*/
|
||
export const recomputeFloorElevations = (
|
||
levels: DrawingLevel[],
|
||
): DrawingLevel[] => {
|
||
let nextBase = 0;
|
||
return levels.map((level) => {
|
||
if (level.kind !== "floor") {
|
||
return { ...level, baseElevation: undefined };
|
||
}
|
||
const baseElevation = nextBase;
|
||
nextBase = baseElevation + (level.floorHeight ?? 0);
|
||
return { ...level, baseElevation };
|
||
});
|
||
};
|
||
|
||
/** Flacht den Kategorie-Baum (Tiefensuche) in eine Liste ab. */
|
||
export const flattenCategories = (cats: LayerCategory[]): LayerCategory[] => {
|
||
const out: LayerCategory[] = [];
|
||
const walk = (list: LayerCategory[]) => {
|
||
for (const c of list) {
|
||
out.push(c);
|
||
if (c.children) walk(c.children);
|
||
}
|
||
};
|
||
walk(cats);
|
||
return out;
|
||
};
|
||
|
||
/**
|
||
* Liefert die LayerCategory zu einem Kategorie-Code (Baum durchsucht), oder
|
||
* `undefined`, falls der Code auf keine Ebene verweist (verwaister
|
||
* `categoryCode`). Nicht-werfend, damit die Attribut-Resolve-Kette
|
||
* (`plan/generatePlan.ts`) robust auf einen fehlenden Ebenen-Wert zurückfallen
|
||
* kann (→ Bauteil-Fallback).
|
||
*/
|
||
export const getLayerCategory = (project: Project, code: string): LayerCategory | undefined =>
|
||
flattenCategories(project.layers).find((c) => c.code === code);
|
||
|
||
/** Menge aller Codes sichtbarer Kategorien (Baum berücksichtigt). */
|
||
export const collectVisibleCodes = (layers: LayerCategory[]): Set<string> => {
|
||
const codes = new Set<string>();
|
||
for (const c of flattenCategories(layers)) {
|
||
if (c.visible) codes.add(c.code);
|
||
}
|
||
return codes;
|
||
};
|
||
|
||
/** Gesamtdicke eines Wandtyps = Summe der Schichtdicken. */
|
||
export const wallTypeThickness = (wt: WallType): number =>
|
||
wt.layers.reduce((sum, l) => sum + l.thickness, 0);
|
||
|
||
/**
|
||
* Kompakter Anzeigetext für einen Wandtyp im Dropdown (VW-Stil).
|
||
* - Einschichtig mit Kürzel: „BET 24" (Kürzel + Dicke in cm)
|
||
* - Mehrschichtig mit Kürzeln: „GKB·BET·DAE" + Gesamtdicke „(25.5)"
|
||
* - Ohne Kürzel: Name bleibt — kein Rückfall auf rohen Namen, nur keine Kurzform.
|
||
*
|
||
* `project.components` wird genutzt, um `Component.abbrev` aufzulösen.
|
||
* Ist kein abbrev gesetzt, erscheint der volle Name unverändert.
|
||
*/
|
||
export function wallTypeLabel(
|
||
wt: WallType | CeilingType,
|
||
components: Component[],
|
||
): string {
|
||
const resolve = (id: string) => components.find((c) => c.id === id);
|
||
const totalCm = +(wallTypeThickness(wt) * 100).toFixed(1);
|
||
// cm-Zahl: "24" statt "24.0", "17.5" bleibt "17.5"
|
||
const cmStr = totalCm % 1 === 0 ? String(totalCm | 0) : String(totalCm);
|
||
|
||
// Alle Schichten haben ein Kürzel → Kurzform bauen
|
||
const abbrevs = wt.layers.map((l) => resolve(l.componentId)?.abbrev ?? "");
|
||
const allHaveAbbrev = abbrevs.every((a) => a.length > 0);
|
||
if (allHaveAbbrev) {
|
||
if (wt.layers.length === 1) return `${abbrevs[0]} ${cmStr}`;
|
||
return `${abbrevs.join("·")} (${cmStr})`;
|
||
}
|
||
// Fallback: voller Name
|
||
return wt.name;
|
||
}
|
||
|
||
/** Formatiert Meter mit zwei Nachkommastellen, z. B. "0.35 m". */
|
||
export const formatM = (meters: number): string => meters.toFixed(2) + " m";
|
||
|
||
/**
|
||
* Standard-Stiftstärken (mm Papier bei 100 %), Vorgabeliste für Linienstil-/
|
||
* Strichstärke-Eingaben. Bedeutung: Breite auf dem Papier — die Linien skalieren
|
||
* mit dem Massstab (non-scaling-stroke). Werte sind Vorschläge; man darf abweichen.
|
||
*/
|
||
export const PEN_WEIGHTS: number[] = [
|
||
0.02, 0.1, 0.13, 0.18, 0.25, 0.35, 0.5, 0.7, 1.0, 1.4, 2.0,
|
||
];
|