Files
DOSSIER-STANDALONE/src/plan/generatePlan/shared.ts
T
karim 85be7173e1 Korrektur: Wand/Decke bleiben bei "Nach Bauteil" als Default, nur
Drawing2D wechselt auf "Nach Ebene"

Nutzer-Korrektur zum vorigen Commit: "eine Wand usw soll weiterhin
nach Bauteil haben und die weisser Grund und schwarzer Vordergrund
haben. Also nach Bauteil. 2D Elemente haben aber bei Attribute kein
nach Bauteil!!!" -- der vorige Commit hatte den Default global (auch
für Wand/Decke) auf "Nach Ebene" umgestellt, was die neutrale SIA-
Poché-Konvention (weisser Grund/schwarzer Vordergrund über die
Bauteil-Kette) durch die rohe Ebenenfarbe ersetzt hätte.

resolveForeground/resolveBackground/resolveHatchId/resolveStrokeWeight
(plan/generatePlan/shared.ts) sind zurückgesetzt auf ihr ursprüngliches
Verhalten: `source === "layer"` (fehlend/"object" ⇒ weiterhin Bauteil-
Kette, DEFAULT bei Wand/Decke). Der elementart-abhängige Default sitzt
jetzt an den AUFRUFERN statt im generischen Resolver:
  • Wand/Decke (selectionInfo.ts): rohes Source-Feld unverändert
    durchgereicht -- Default bleibt "Nach Bauteil".
  • Drawing2D (selectionInfo.ts drawingSelection): `d.foregroundSource
    ?? "layer"` usw. VOR dem Resolver -- Default wird dort explizit
    "Nach Ebene" (kein eigenes Bauteil, "Nach Bauteil" bietet das Panel
    für 2D-Elemente ohnehin nicht mehr an, s. vorletzter Commit).
  • AttributesPanel.tsx uiSourceOf() bekommt einen isDrawing-Parameter
    für denselben elementart-abhängigen Default in der Dropdown-
    Anzeige.

+Tests in shared.resolve.test.ts auf die jetzt korrekten Erwartungen
umgeschrieben (Default bleibt Bauteil, explizites "layer" liefert die
Kategorie, Drawing2D-Aufrufer-Mapping separat geprüft). tsc/vitest
934/934 grün.
2026-08-22 01:05:20 +02:00

533 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Gemeinsame Grundlage des Grundriss-Generators: das `Primitive`-Vokabular
// (das EINZIGE Austauschformat zwischen `generatePlan()`/`generateSectionPlan()`
// und allen Bauteil-Domänen unter `generatePlan/`), die By-Layer/By-Object-
// Attribut-Auflösung (`resolve*`) sowie die Papier-mm-/Tinten-Konstanten, die
// über mehrere Domänen hinweg identisch gelten müssen (SIA-Poché, Haarlinien).
//
// Jede Datei unter `generatePlan/` importiert von hier, NIE von `../generatePlan`
// selbst — das hielte die Orchestrator-Datei (`generatePlan.ts`) andernfalls in
// einem Zirkelimport mit jeder einzelnen Domäne.
import type {
AttributeSource,
Component,
HatchPattern,
LayerCategory,
Project,
Vec2,
} from "../../model/types";
import { getHatch, getLineStyle } from "../../model/types";
import type { Marks, RichTextDoc } from "../../text/richText";
import type { RoomStampLine } from "../../model/roomStamp";
/**
* Detailgrad der Plan-Darstellung (Oberleiste „grob/mittel/fein"). Steuert hier
* konkret zwei Dinge im Grundriss:
* • Tür-/Fenstersymbole: grob = nur Öffnungs-Lücke + gerade Türblatt-Linie;
* mittel = + Schwenkbogen; fein = + Rahmen/Anschlag-Striche.
* • Schraffuren + Linienfeinheit: grob = vereinfachte Poché (eine Sammelfläche
* ohne Schraffur, dickere Umrisslinie); mittel = aktueller mehrschichtiger
* Aufbau mit Schraffuren; fein = volle Schraffuren + dünnere Linien.
*
* NOCH OFFEN (folgt, sobald die Features existieren — hier bewusst KEIN
* Schein-Effekt): Bemaßung/Beschriftung werden mit dem Detailgrad auf-/abblenden
* (Maßketten, Raumstempel), und die 3D-Geometrie-Auflösung (z. B. Profil-
* Rundungen, Tritt-/Setzstufen) wird ebenfalls dem Detailgrad folgen, sobald
* Bemaßung bzw. eine feinere 3D-Tessellierung implementiert sind.
*/
export type DetailLevel = "grob" | "mittel" | "fein";
/** Stroke-Farbe der Schicht-Polygone (neutrales Schwarz, geteilte Tinte mit der Schraffur). */
export const POCHE_STROKE = "#1a1a1a";
/** Tinte für den Schwarz-Weiss-Modus: einheitliches Dunkel für alle Striche. */
export const MONO_INK = "#111111";
// ── Linienstärken in PAPIER-Millimeter (vgl. docs/design/plans-output.md §3.2) ──
// Alle Stricharten sind in mm Papier definiert; die PlanView rechnet sie über
// dpi = 96 in konstante Bildschirm-Pixel um (non-scaling-stroke), damit Linien
// beim Zoomen ihre Papiergröße behalten und der Massstab stimmt.
/** Default-Schichtfuge zwischen den Materialschichten in mm — Haarlinie (0.02),
* gleiche Stärke wie die Schraffur-Tinte. Pro Fuge im Wandstil überschreibbar
* (z. B. 0.13, wenn zwei massive Materialien aufeinandertreffen). */
export const LAYER_LINE_MM = 0.02;
/** Haarlinie für Tür-/Fenstersymbole (Flügel, Bogen, Rahmen, Glas) in mm. */
export const SYMBOL_HAIRLINE_MM = 0.02;
/** Strichmuster (mm Papier) für ÜBERKOPF-Ansichtslinien: Bauteile über der
* Grundriss-Schnittebene (Decken-/Slab-Überstände, Unterzüge) werden nach
* BIM-Konvention gestrichelt gezeichnet (Aufsicht auf ein Bauteil über einem). */
export const OVERHEAD_DASH: number[] = [0.4, 0.25];
/** Strichstärken der Dach-Grundrisslinien (mm Papier): Traufe = mittlere
* Haarlinie, First etwas kräftiger als die Traufe, Grat dazwischen, Knick dünn
* + gestrichelt (vgl. addRoof). */
export const ROOF_EAVES_MM = 0.13;
export const ROOF_RIDGE_MM = 0.25;
export const ROOF_HIP_MM = 0.18;
export const ROOF_BREAK_MM = 0.09;
/** Strichmuster (mm Papier) der Dach-Knicklinie (Mansarde). */
export const ROOF_BREAK_DASH: number[] = [0.12, 0.08];
/** Stärke der Wand-Umrisslinie je Detailgrad, als Faktor auf die Ebenen-lw. */
export const OUTLINE_DETAIL_FACTOR: Record<DetailLevel, number> = {
grob: 1.6, // dickere Sammellinie
mittel: 1.0, // wie die Kategorie-Strichstärke
fein: 0.6, // feiner
};
/** Stärke der Schichtfugen je Detailgrad (mittel/fein; grob hat keine Fugen). */
export const LAYER_DETAIL_FACTOR: Record<DetailLevel, number> = {
grob: 1.0,
mittel: 1.0,
fein: 0.7,
};
/** Default-Umrandungs-Strichstärke (mm), falls die Kategorie keine lw trägt
(Grundeinstellung für „normale Elemente"). */
export const WALL_FALLBACK_MM = 0.18;
/**
* Aufgelöste Schraffur eines Polygons — alle Werte stammen aus einer
* HatchStyle-Ressource (Muster/Maßstab/Winkel/Farbe) plus dem optional
* verknüpften LineStyle (Linienstärke/Strichmuster der Musterlinien). Die
* PlanView ist damit rein darstellend und kennt das Projekt nicht.
*/
export interface HatchRender {
pattern: HatchPattern;
scale: number;
angle: number;
/**
* AUFGELÖSTE Farbe der Musterlinien bzw. der Vollfüllung (pattern==="solid").
* Ergebnis der Farb-Resolve-Kette (siehe {@link resolveHatch}): Vordergrund =
* (Attribut-Override) ?? (Component.foreground) ?? (HatchStyle.color, Backward-
* Compat) ?? Default. Der heutige SVG-/GL-/DXF-Renderer liest weiterhin nur
* dieses Feld — die Kette ist zur Renderzeit bereits kollabiert.
*/
color: string;
/** Linienstärke der Musterlinien in mm. */
lineWeight: number;
/** Strichmuster der Musterlinien in mm; null = durchgezogen. */
dash: number[] | null;
/**
* Schraffur-Typ (additiv). `undefined`/"vector" ⇒ Muster nach `pattern` (heutiges
* Verhalten); "image" ⇒ Bild-Muster (`image`). Bestehende Renderer ignorieren
* `kind` und zeichnen nach `pattern` → unbekannte kinds fallen auf heute zurück.
*/
kind?: "vector" | "image";
/** Vektor-Untermodus (additiv). `undefined`/"parallel" ⇒ regelmäßig; "random" ⇒ zufällig. */
lines?: "parallel" | "random";
/** Bild-Muster-Parameter (nur `kind==="image"`); durchgereicht aus dem HatchStyle. */
image?: { src: string; scaleX: number; scaleY: number; rotation: number };
/**
* Steuer-Parameter der Random-Vektor-Streuung (nur `lines==="random"`), 1:1 aus
* dem HatchStyle durchgereicht. Alle optional — die eigentliche Erzeugung/
* Default-Auflösung liegt zentral in `glPlan/glPlanHatch.ts` (single render truth).
*/
seed?: number;
density?: number;
lengthMin?: number;
lengthMax?: number;
}
/**
* Render-Entscheidung je Kategorie-Code für den Grundriss (vgl. ItemDisplay aus
* dem Panel-Darstellungsmodus). `render`=false blendet die Kategorie ganz aus,
* `greyed`=true zeichnet sie gedimmt, `locked`=true (nur Modus „grey_locked")
* verbietet die Anwahl (siehe App.tsx onPlanSelect). Wird kein Resolver
* übergeben, gilt für jede sichtbare Kategorie {render:true, greyed:false,
* locked:false} (bisheriges Verhalten).
*/
export interface CategoryDisplay {
render: boolean;
greyed: boolean;
locked: boolean;
}
/** Funktion, die je Kategorie-Code die Render-Entscheidung liefert. */
export type CategoryDisplayResolver = (code: string) => CategoryDisplay;
/** Standard-Resolver: jede (bereits gefilterte) Kategorie normal zeichnen. */
export const SHOW_ALL: CategoryDisplayResolver = () => ({ render: true, greyed: false, locked: false });
export type Primitive =
| {
kind: "polygon";
pts: Vec2[];
/** Füllfarbe (Component-Poché) bzw. "none" für reine Umrisslinien. */
fill: string;
stroke: string;
/** Umriss-Strichstärke in PAPIER-Millimeter (siehe oben). */
strokeWidthMm: number;
hatch: HatchRender;
/** Gedimmt zeichnen (Darstellungsmodus „andere grau"). */
greyed?: boolean;
/**
* Kanten-Indizes, die NICHT umrissen (gestrichen) werden dürfen — die Füllung
* bleibt das volle Polygon. Kante `i` läuft von `pts[i]` nach `pts[(i+1)%n]`.
* Damit werden die inneren Gehrungs-/Stoß-Stirnflächen an einer Wandecke
* unterdrückt: die Diagonale, an der GLEICHFARBIGE Schichten zweier Wände
* aneinanderstoßen, würde sonst als sichtbare 45°-Naht erscheinen und der
* Umriss würde als Barbe über den Eck-Apex hinausschießen (Gehrungs-Spitze).
* Längskanten (Schichtfugen zwischen VERSCHIEDENEN Materialien) bleiben
* gestrichen — nur die Stoß-Stirnkanten am Knoten entfallen.
*/
noStrokeEdges?: number[];
/**
* ID der Wand, zu der dieses Schicht-/Umriss-Polygon gehört (für die
* Links-Klick-Auswahl: die PlanView markiert ALLE Polygone derselben
* Wand und meldet die wallId nach oben). Reine Umriss-/Symbol-Primitive
* ohne Wandbezug lassen das Feld weg.
*/
wallId?: string;
/**
* ID des 2D-Zeichenelements (Drawing2D), falls dieses gefüllte Polygon eine
* geschlossene 2D-Form ist (für die Links-Klick-Auswahl der Fläche).
*/
drawingId?: string;
/**
* ID der Decke (Ceiling), falls dieses Polygon eine Decken-Fläche/-Umriss
* ist (für die Links-Klick-Auswahl der Decke).
*/
ceilingId?: string;
/** ID der Öffnung (Fenster/Tür), z. B. der Fensterrahmen (Auswahl). */
openingId?: string;
/** ID der Treppe (Stair), falls dieses Polygon ein Tritt/Podest ist (Auswahl). */
stairId?: string;
/** ID des Raums (Room), falls dieses Polygon eine Raum-Fläche ist (Auswahl). */
roomId?: string;
/**
* ID des extrudierten Körpers (truck-Integration), falls dieses Polygon der
* Grundriss-Footprint einer Extrusion ist (für eine spätere Links-Klick-
* Auswahl — heute nur zur Nachvollziehbarkeit getaggt, noch nicht verdrahtet).
*/
extrudedSolidId?: string;
/**
* ID des Kontext-Objekts (importiertes Gebäude/Terrain, `project.context`),
* falls dieses (unsichtbare) Pick-Band dessen Grundriss-Bounding-Box
* umhüllt (Links-Klick-Auswahl — analog `sectionLineId`, s. u.).
*/
contextObjectId?: string;
/**
* ID der Stütze (Column), falls dieses Polygon der Grundriss-Querschnitt
* (Poché) einer Stütze ist (für die Links-Klick-Auswahl der Fläche).
*/
columnId?: string;
/** ID des Dachs (Roof), falls dieses (unsichtbare) Polygon der Traufe-Umriss ist (Auswahl). */
roofId?: string;
/**
* ID der Schnitt-/Ansichtslinie (DrawingLevel), falls dieses (unsichtbare)
* Pick-Band das Schnittführungs-Symbol umhüllt (für die Links-Klick-Auswahl
* der Schnittlinie im Grundriss).
*/
sectionLineId?: string;
}
| {
kind: "line";
a: Vec2;
b: Vec2;
cls: string;
/** Strichstärke in mm Papier (non-scaling). */
weightMm: number;
/** Strichmuster in mm Papier; null/undefined = durchgezogen. */
dash?: number[] | null;
/**
* Zickzack-/Wellen-Parameter (aus einem LineStyle mit `kind==="zigzag"`),
* beide in mm Papier. Ist es gesetzt, zeichnen die Renderer die Linie als
* Zickzack-Pfad statt als gerade (ggf. gestrichelte) Linie — ADDITIV, ohne
* die bestehenden Strich/Dash-Linien zu verändern (Feld fehlt ⇒ heute).
*/
zigzag?: { amplitude: number; wavelength: number };
/**
* Frei gezeichnetes Wiederhol-Motiv (aus einem LineStyle mit
* `kind==="custom"`): eine offene Polylinie in einer Einheitszelle, `points`
* in mm Papier, `length` = Wiederhollänge in mm Papier. Ist es gesetzt,
* kacheln die Renderer das Motiv entlang der Linie (analog `zigzag`) —
* ADDITIV; fehlt es, bleibt die Linie gerade/gestrichelt.
*/
motif?: { points: Vec2[]; length: number };
/** Optionale explizite Strichfarbe (überschreibt die CSS-Klasse). */
color?: string;
/** ID des 2D-Zeichenelements (für Links-Klick-Auswahl von Drawing2D). */
drawingId?: string;
/** ID der Öffnung (für Links-Klick-Auswahl von Fenster/Tür-Symbolen). */
openingId?: string;
/** ID der Treppe (für Links-Klick-Auswahl von Treppen-Symbollinien). */
stairId?: string;
greyed?: boolean;
}
| {
kind: "arc";
center: Vec2;
from: Vec2;
to: Vec2;
r: number;
cls: string;
/** Strichstärke in mm Papier (non-scaling). */
weightMm: number;
/** Strichmuster in mm Papier; null/undefined = durchgezogen. */
dash?: number[] | null;
/** ID der Öffnung (für Links-Klick-Auswahl von Tür-Schwenkbögen). */
openingId?: string;
/** ID der Treppe (für Links-Klick-Auswahl von Wendel-Outline-Bögen). */
stairId?: string;
greyed?: boolean;
}
| {
kind: "text";
/** Ankerpunkt im Modell (Meter). */
at: Vec2;
/**
* Frei editierbarer Stempel-Text (Rich-Text). Wird zuerst gezeichnet;
* `align` je Absatz steuert die Ausrichtung.
*/
doc: RichTextDoc;
/**
* LIVE-Zusatzzeilen (z. B. Fläche „24.30 m²" + SIA-Tag), UNTER dem Doc
* gezeichnet — unabhängig vom editierten Text, stets aktuell. Jede Zeile
* trägt ihre eigene Ausrichtung (`align`).
*/
extraLines: RoomStampLine[];
/** Basis-Schriftgrösse in PUNKT (für Runs ohne eigene Grösse + Zusatzzeilen). */
basePt: number;
/** Textfarbe (hex) als Default. */
color: string;
/** ID des Raums (Room), zu dem dieser Stempel gehört (Auswahl). */
roomId?: string;
greyed?: boolean;
}
| {
// Schlichter, modellverankerter Einzeltext (aus Drawing2D `{shape:"text"}`,
// z. B. DXF-Import). Anders als `kind:"text"` (Raum-Stempel, RichText) trägt
// er nur einen String mit Höhe in Metern + Drehung — rein SVG-gerendert.
kind: "drawingText";
/** Ankerpunkt im Modell (Meter). */
at: Vec2;
text: string;
/** Schrifthöhe in Modell-Metern. */
heightM: number;
/** Drehung in RADIANT (CCW im Modell). */
angle: number;
color: string;
/** Optionale einheitliche Formatierung (Schriftfamilie/fett/kursiv/…). */
marks?: Marks;
/**
* Optionale Spaltenbreite in Modell-Metern (Textspalte): ist sie gesetzt,
* bricht der Renderer den Text wortweise auf diese Breite um. Fehlt sie,
* bleibt es einzeiliger Text.
*/
wrapWidth?: number;
/**
* Reiches Textfeld (mehrere Absätze, gemischte Formatierung) — Vorrang vor
* `text`/`marks` beim Rendern, s. `Drawing2DGeom["text"].doc`. Bricht bei
* gesetztem `wrapWidth` wortweise um (Run-Grenzen bleiben erhalten).
*/
doc?: RichTextDoc;
/** ID des 2D-Zeichenelements (für Links-Klick-Auswahl). */
drawingId?: string;
greyed?: boolean;
}
| {
// Eingebettetes Rasterbild (aus Drawing2D `{shape:"image"}`, Foto/Logo/
// gerasterte PDF-Seite) — achsparalleles Rechteck, Data-URL als Quelle.
kind: "drawingImage";
min: Vec2;
max: Vec2;
src: string;
opacity: number;
drawingId?: string;
greyed?: boolean;
}
| {
// Glatter Kreis (aus Drawing2D `{shape:"circle"}`, z. B. DXF-CIRCLE) — als
// echtes `<circle>` gerendert (rund bei jedem Zoom), nicht als Vieleck.
kind: "drawingCircle";
center: Vec2;
/** Radius in Modell-Metern. */
r: number;
/** Vollton-Füllfarbe oder "none". */
fill: string;
stroke: string;
weightMm: number;
dash?: number[] | null;
drawingId?: string;
greyed?: boolean;
}
| {
// Glatter Bogen (aus Drawing2D `{shape:"arc"}`, z. B. DXF-ARC) — als
// SVG-Bogenpfad gerendert; Winkel in RADIANT (CCW im Modell).
kind: "drawingArc";
center: Vec2;
r: number;
a0: number;
a1: number;
stroke: string;
weightMm: number;
dash?: number[] | null;
drawingId?: string;
greyed?: boolean;
};
/**
* Löst eine HatchStyle-Ressource (per id) zu einer darstellungsfertigen
* {@link HatchRender} auf. Der Linienstil der Musterlinien wird, falls gesetzt,
* aus dem Line Manager nachgeschlagen; sonst gilt ein dünner Default.
*
* VORDERGRUND (Muster-/Schraffurlinienfarbe bzw. Vollfüllung): AKTUELL fest auf
* {@link HATCH_INK} gezwungen (Nutzer-Vorgabe 2026-07-04) — Schnitt- WIE
* Oberflächen-/Ansichtsschraffur zeigen dieselbe dunkle Tinte, unabhängig von
* Material-/Bauteilfarbe, HatchStyle.color oder einem By-Layer/By-Object-
* Vordergrund-Override. `_foreground` bleibt Teil der Signatur — alle Aufrufer
* reichen weiterhin den über {@link resolveForeground} aufgelösten Bauteil-
* Vordergrund durch (z. B. für eine spätere Wiederherstellung der materialtreuen
* Schraffur) —, fließt aber bewusst nicht mehr in `color` ein.
*/
export function resolveHatch(
project: Project,
hatchId: string,
wallAngleDeg?: number,
_foreground?: string,
): HatchRender {
const h = getHatch(project, hatchId);
const ls = h.lineStyleId ? getLineStyle(project, h.lineStyleId) : null;
// Wandbezogener Winkel: `HatchRender.angle` wird von allen Renderern in der
// SVG-Konvention (im Uhrzeigersinn, Bildschirm-Y nach unten) verstanden — der
// Modell-Renderer negiert ihn in `toModelAngleRad`. Die Wandachse `wallAngleDeg`
// ist dagegen ein MODELL-Winkel (atan2 der Richtung, gegen den Uhrzeigersinn,
// Y nach oben). Wegen der Y-Spiegelung entspricht die Wandrichtung am Bildschirm
// dem Winkel −wallAngleDeg; das Muster folgt der Wand also, indem der
// Wandwinkel SUBTRAHIERT wird. Ohne Wandkontext (Decke) bleibt `angle` absolut.
const angle =
h.relativeToWall && wallAngleDeg != null ? h.angle - wallAngleDeg : h.angle;
return {
pattern: h.pattern,
scale: h.scale,
angle,
// Fest auf die Monochrom-Tinte gezwungen (siehe JSDoc oben) — weder
// `_foreground` noch `HatchStyle.color` (Material-/Bauteilfarbe) fließen ein.
color: HATCH_INK,
lineWeight: ls?.weight ?? 0.13,
dash: ls?.dash ?? null,
kind: h.kind,
lines: h.lines,
image: h.image,
// Random-Streu-Parameter unverändert durchreichen (Erzeugung in glPlanHatch).
seed: h.seed,
density: h.density,
lengthMin: h.lengthMin,
lengthMax: h.lengthMax,
};
}
/**
* Kollabiert die Vordergrund-Farbkette (Muster-/Schraffurlinienfarbe) eines
* Bauteils zu einem einzelnen Wert für {@link resolveHatch}. AUFLÖSUNGSREIHENFOLGE
* (By-Layer/By-Object, siehe `docs`/Attribut-Panel):
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.color`) ??
* (`Component.foreground`) ?? `undefined`.
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" — bei WAND/DECKE bewusst der
* DEFAULT (die Bauteil-Kette liefert die neutrale SIA-Poché, s.
* {@link resolveBackground}). Ein Drawing2D hat KEIN eigenes Bauteil und
* bietet „Nach Bauteil" im Attribut-Panel gar nicht erst an — dort wird ein
* fehlendes Source-Feld vom AUFRUFER (`selectionInfo.ts`) explizit auf
* `"layer"` gemappt, bevor er hier ankommt (dieser Resolver selbst bleibt
* Bauteil-Default, unabhängig von der Elementart).
*/
export function resolveForeground(
comp: Component,
override?: string,
category?: LayerCategory,
source?: AttributeSource,
): string | undefined {
if (override != null) return override;
if (source === "layer") return category?.color ?? comp.foreground;
return comp.foreground;
}
/**
* Kollabiert die Hintergrund-/Füllfarbkette (Poché-Override) eines Bauteils zu
* einem einzelnen Wert, exakt analog zu {@link resolveForeground}:
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.color`) ??
* (`Component.background`) ?? `undefined`.
* `undefined` als Endergebnis heißt „kein Override gesetzt" — Aufrufer fallen
* dann auf ihren eigenen Default zurück (bei Wand/Decke die neutrale SIA-
* Poché-Farbe HATCH_INK/HATCH_PAPER, NICHT `Component.color` — die reale
* Bauteilfarbe soll die Schnitt-Konvention nicht überschreiben, nur ein
* bewusster Override). Genau DIESE neutrale Poché ist bei Wand/Decke der
* gewünschte Default („weisser Grund, schwarzer Vordergrund") — deshalb
* bleibt „Nach Bauteil" dort der Default, nicht „Nach Ebene".
*/
export function resolveBackground(
comp: Component,
override?: string,
category?: LayerCategory,
source?: AttributeSource,
): string | undefined {
if (override != null) return override;
if (source === "layer") return category?.color ?? comp.background;
return comp.background;
}
/**
* Kollabiert die Schraffur-Referenz (Hatch Manager) eines Elements zu einer
* einzelnen `hatchId` für {@link resolveHatch} — By-Layer/By-Object, analog zu
* {@link resolveForeground}:
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.hatch`) ??
* `fallback` (Bauteil-Schraffur, z. B. `Component.hatchId`/`viewHatchId`, oder
* `undefined` bei Drawing2D ohne Bauteil-Bezug).
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" (DEFAULT bei Wand/Decke, s.
* {@link resolveForeground}).
* Ergebnis kann `undefined` sein — der Aufrufer fällt dann auf `NO_HATCH` zurück.
*/
export function resolveHatchId(
override: string | undefined,
source: AttributeSource | undefined,
category: LayerCategory | undefined,
fallback?: string,
): string | undefined {
if (override != null) return override;
if (source === "layer") return category?.hatch ?? fallback;
return fallback;
}
/**
* Kollabiert die Strichstärke-Kette (mm Papier) eines Elements, By-Layer/By-
* Object, analog zu {@link resolveHatchId}:
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.lw`) ??
* `fallback` (bisherige Bauteil-/LineStyle-/Kategorie-Default-Kette des
* Aufrufers — heutiges Verhalten).
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" (DEFAULT bei Wand/Decke, s.
* {@link resolveForeground}).
*/
export function resolveStrokeWeight(
override: number | undefined,
source: AttributeSource | undefined,
category: LayerCategory | undefined,
fallback: number,
): number {
if (override != null) return override;
if (source === "layer") return category?.lw ?? fallback;
return fallback;
}
// ── SIA-Poché-Füllung + monochrome Schraffur-Tinte ──────────────────────────
// Die 2D-Poché einer (auch mehrschichtigen) Wand trägt NICHT die rohe Bauteil-
// Albedo (3D-Materialfarbe), sondern einen neutralen Hintergrund, auf dem die
// Schraffur-Musterlinien liegen: Vollmuster ("solid") = dunkle Tinten-Poché
// (kein Linienmuster), jedes andere Muster (inkl. "none") = HELLER Hintergrund.
// Dieselben zwei Werte sind AKTUELL auch die feste Schraffur-Vordergrundfarbe
// (siehe {@link resolveHatch}) — Schnitt- wie Oberflächen-Schraffur zeigen also
// einheitlich Tinte auf hellem Papier, unabhängig von Material-/Bauteilfarbe.
export const HATCH_PAPER = "#f0f0f0";
export const HATCH_INK = "#0f0f0f";
/** Neutrale Poché-Füllfarbe für ein Muster: "solid" → Tinte, sonst Papier. */
export function pocheFill(pattern: HatchPattern): string {
return pattern === "solid" ? HATCH_INK : HATCH_PAPER;
}
/** Schraffur-Platzhalter „ohne" (für reine Umriss-/Sammelflächen). */
export const NO_HATCH: HatchRender = {
pattern: "none",
scale: 1,
angle: 0,
color: POCHE_STROKE,
lineWeight: LAYER_LINE_MM,
dash: null,
};