85be7173e1
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.
533 lines
23 KiB
TypeScript
533 lines
23 KiB
TypeScript
// 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,
|
||
};
|