Attribute: By-Layer/By-Object-Quelle fuer Farbe, Strichstaerke, Schraffur
Vordergrund, Hintergrund, Strichstaerke und Schraffur je Element (Wand/Decke/ Drawing2D) haben jetzt einen 3-Wege-Quellen-Dropdown: Nach Ebene / Nach Bauteil / eigener Wert. Aufloesungsreihenfolge: expliziter Wert > (Quelle 'layer' => LayerCategory-Wert color/lw/hatch) > Bauteil/LineStyle-Default. Neue *Source-Felder + strokeWeight/hatchId-Overrides am Modell (additiv, optional), getLayerCategory-Accessor, resolveHatchId/resolveStrokeWeight; resolveForeground/Background um category+source erweitert. Sample rendert identisch (kein Override/Source gesetzt => Default 'object' = altes Verhalten). Offen: LayerCategory-Schraffur noch nicht im Kategorie-Dialog editierbar.
This commit is contained in:
+59
-4
@@ -1769,6 +1769,7 @@ export default function App() {
|
|||||||
const setElementFillColor = useStore((s) => s.setElementFillColor);
|
const setElementFillColor = useStore((s) => s.setElementFillColor);
|
||||||
const setElementForeground = useStore((s) => s.setElementForeground);
|
const setElementForeground = useStore((s) => s.setElementForeground);
|
||||||
const setElementBackground = useStore((s) => s.setElementBackground);
|
const setElementBackground = useStore((s) => s.setElementBackground);
|
||||||
|
const updateDrawing2D = useStore((s) => s.updateDrawing2D);
|
||||||
const resizeElement = useStore((s) => s.resizeElement);
|
const resizeElement = useStore((s) => s.resizeElement);
|
||||||
const updateWall = useStore((s) => s.updateWall);
|
const updateWall = useStore((s) => s.updateWall);
|
||||||
const setWallThickness = useStore((s) => s.setWallThickness);
|
const setWallThickness = useStore((s) => s.setWallThickness);
|
||||||
@@ -2065,13 +2066,22 @@ export default function App() {
|
|||||||
updateRoom(selection.id, { color });
|
updateRoom(selection.id, { color });
|
||||||
},
|
},
|
||||||
onSetSelectionWeight: (weightMm) => {
|
onSetSelectionWeight: (weightMm) => {
|
||||||
// Strichstärke gibt es als direktes Feld nur bei Drawing2D.
|
// Strichstärke-Override: Wand/Decke (`strokeWeight`) oder Drawing2D
|
||||||
if (selection?.kind === "drawing2d")
|
// (`weightMm`, Alt-Feldname bleibt).
|
||||||
|
if (selection?.kind === "wall") updateWall(selection.id, { strokeWeight: weightMm });
|
||||||
|
else if (selection?.kind === "ceiling")
|
||||||
|
updateCeiling(selection.id, { strokeWeight: weightMm });
|
||||||
|
else if (selection?.kind === "drawing2d")
|
||||||
setElementWeight(selection.id, weightMm);
|
setElementWeight(selection.id, weightMm);
|
||||||
},
|
},
|
||||||
onSetSelectionFill: (hatchId) => {
|
onSetSelectionFill: (hatchId) => {
|
||||||
// Füllschraffur nur bei Drawing2D (geschlossene Formen).
|
// Schraffur-Override: Wand, Decke oder Drawing2D (geschlossene Formen).
|
||||||
if (selection?.kind === "drawing2d")
|
// `null` löscht das Feld (→ „Nach System"/Quelle).
|
||||||
|
if (selection?.kind === "wall")
|
||||||
|
updateWall(selection.id, { hatchId: hatchId ?? undefined });
|
||||||
|
else if (selection?.kind === "ceiling")
|
||||||
|
updateCeiling(selection.id, { hatchId: hatchId ?? undefined });
|
||||||
|
else if (selection?.kind === "drawing2d")
|
||||||
setElementFill(selection.id, hatchId);
|
setElementFill(selection.id, hatchId);
|
||||||
},
|
},
|
||||||
onSetSelectionFillColor: (color) => {
|
onSetSelectionFillColor: (color) => {
|
||||||
@@ -2097,6 +2107,51 @@ export default function App() {
|
|||||||
)
|
)
|
||||||
setElementBackground(selection.kind, selection.id, color);
|
setElementBackground(selection.kind, selection.id, color);
|
||||||
},
|
},
|
||||||
|
// ── By-Layer/By-Object-Quellen (Vordergrund/Hintergrund/Strichstärke/
|
||||||
|
// Schraffur): "layer" erzwingt die Ebene, "object" erbt vom Bauteil —
|
||||||
|
// beide löschen dabei den expliziten Wert (siehe Modell-Kommentar
|
||||||
|
// `AttributeSource`); „eigener Wert" läuft weiterhin über die
|
||||||
|
// onSetSelection{Foreground,Background,Weight,Fill}-Setter oben.
|
||||||
|
onSetSelectionForegroundSource: (source) => {
|
||||||
|
if (!selection) return;
|
||||||
|
const patch = {
|
||||||
|
foregroundSource: source === "layer" ? ("layer" as const) : undefined,
|
||||||
|
foreground: undefined,
|
||||||
|
};
|
||||||
|
if (selection.kind === "wall") updateWall(selection.id, patch);
|
||||||
|
else if (selection.kind === "ceiling") updateCeiling(selection.id, patch);
|
||||||
|
else if (selection.kind === "drawing2d") updateDrawing2D(selection.id, patch);
|
||||||
|
},
|
||||||
|
onSetSelectionBackgroundSource: (source) => {
|
||||||
|
if (!selection) return;
|
||||||
|
const patch = {
|
||||||
|
backgroundSource: source === "layer" ? ("layer" as const) : undefined,
|
||||||
|
background: undefined,
|
||||||
|
};
|
||||||
|
if (selection.kind === "wall") updateWall(selection.id, patch);
|
||||||
|
else if (selection.kind === "ceiling") updateCeiling(selection.id, patch);
|
||||||
|
else if (selection.kind === "drawing2d") updateDrawing2D(selection.id, patch);
|
||||||
|
},
|
||||||
|
onSetSelectionStrokeWeightSource: (source) => {
|
||||||
|
if (!selection) return;
|
||||||
|
const strokeWeightSource = source === "layer" ? ("layer" as const) : undefined;
|
||||||
|
if (selection.kind === "wall")
|
||||||
|
updateWall(selection.id, { strokeWeightSource, strokeWeight: undefined });
|
||||||
|
else if (selection.kind === "ceiling")
|
||||||
|
updateCeiling(selection.id, { strokeWeightSource, strokeWeight: undefined });
|
||||||
|
else if (selection.kind === "drawing2d")
|
||||||
|
updateDrawing2D(selection.id, { strokeWeightSource, weightMm: undefined });
|
||||||
|
},
|
||||||
|
onSetSelectionHatchSource: (source) => {
|
||||||
|
if (!selection) return;
|
||||||
|
const hatchSource = source === "layer" ? ("layer" as const) : undefined;
|
||||||
|
if (selection.kind === "wall")
|
||||||
|
updateWall(selection.id, { hatchSource, hatchId: undefined });
|
||||||
|
else if (selection.kind === "ceiling")
|
||||||
|
updateCeiling(selection.id, { hatchSource, hatchId: undefined });
|
||||||
|
else if (selection.kind === "drawing2d")
|
||||||
|
updateDrawing2D(selection.id, { hatchSource, hatchId: undefined });
|
||||||
|
},
|
||||||
onResizeSelection: (w, h, anchor) => {
|
onResizeSelection: (w, h, anchor) => {
|
||||||
if (selection?.kind === "wall" || selection?.kind === "drawing2d")
|
if (selection?.kind === "wall" || selection?.kind === "drawing2d")
|
||||||
resizeElement(selection.kind, selection.id, w, h, anchor);
|
resizeElement(selection.kind, selection.id, w, h, anchor);
|
||||||
|
|||||||
+3
-2
@@ -220,8 +220,9 @@ export const de = {
|
|||||||
"attr.bySystem": "Nach System",
|
"attr.bySystem": "Nach System",
|
||||||
"attr.hatch": "Schraffur",
|
"attr.hatch": "Schraffur",
|
||||||
"attr.none": "keine",
|
"attr.none": "keine",
|
||||||
"attr.inheritedFromLayer": "Wände erben die Strichstärke aus der Ebene",
|
"attr.source.layer": "Nach Ebene",
|
||||||
"attr.fillNeedsClosed": "Füllung nur für geschlossene Formen",
|
"attr.source.object": "Nach Bauteil",
|
||||||
|
"attr.source.custom": "eigener Wert",
|
||||||
"attr.size": "Maße",
|
"attr.size": "Maße",
|
||||||
"attr.width": "Breite",
|
"attr.width": "Breite",
|
||||||
"attr.height": "Höhe",
|
"attr.height": "Höhe",
|
||||||
|
|||||||
+3
-2
@@ -219,8 +219,9 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"attr.bySystem": "By system",
|
"attr.bySystem": "By system",
|
||||||
"attr.hatch": "Hatch",
|
"attr.hatch": "Hatch",
|
||||||
"attr.none": "none",
|
"attr.none": "none",
|
||||||
"attr.inheritedFromLayer": "Walls inherit line weight from the layer",
|
"attr.source.layer": "By layer",
|
||||||
"attr.fillNeedsClosed": "Fill only for closed shapes",
|
"attr.source.object": "By component",
|
||||||
|
"attr.source.custom": "Custom",
|
||||||
"attr.size": "Size",
|
"attr.size": "Size",
|
||||||
"attr.width": "Width",
|
"attr.width": "Width",
|
||||||
"attr.height": "Height",
|
"attr.height": "Height",
|
||||||
|
|||||||
@@ -500,6 +500,19 @@ export interface LayerCategory {
|
|||||||
children?: LayerCategory[];
|
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
|
* Lage der Wandachse (Referenzlinie) über die Dicke der Wand — analog
|
||||||
* Vectorworks. Gemessen relativ zur Laufrichtung (start→end) mit der
|
* Vectorworks. Gemessen relativ zur Laufrichtung (start→end) mit der
|
||||||
@@ -553,6 +566,37 @@ export interface Wall {
|
|||||||
* `undefined` = „Nach System" (erben → Component.background → Component.color).
|
* `undefined` = „Nach System" (erben → Component.background → Component.color).
|
||||||
*/
|
*/
|
||||||
background?: string;
|
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
|
* Lage der Wandachse über die Dicke. Fehlt sie, gilt "center" (= heutiges
|
||||||
* Verhalten: Schichten symmetrisch −T/2 … +T/2 um die Achse).
|
* Verhalten: Schichten symmetrisch −T/2 … +T/2 um die Achse).
|
||||||
@@ -625,6 +669,25 @@ export interface Ceiling {
|
|||||||
* `undefined` = „Nach System" (erben → Component.background → Component.color).
|
* `undefined` = „Nach System" (erben → Component.background → Component.color).
|
||||||
*/
|
*/
|
||||||
background?: string;
|
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
|
* Vertikale Bindung der OBERKANTE (OK). Fehlt sie, sitzt die OK an der
|
||||||
* Oberkante des Geschosses (baseElevation + floorHeight).
|
* Oberkante des Geschosses (baseElevation + floorHeight).
|
||||||
@@ -914,6 +977,25 @@ export interface Drawing2D {
|
|||||||
* vor dem LineStyle-Gewicht und der Kategorie-Strichstärke.
|
* vor dem LineStyle-Gewicht und der Kategorie-Strichstärke.
|
||||||
*/
|
*/
|
||||||
weightMm?: number;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1190,6 +1272,16 @@ export const flattenCategories = (cats: LayerCategory[]): LayerCategory[] => {
|
|||||||
return out;
|
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). */
|
/** Menge aller Codes sichtbarer Kategorien (Baum berücksichtigt). */
|
||||||
export const collectVisibleCodes = (layers: LayerCategory[]): Set<string> => {
|
export const collectVisibleCodes = (layers: LayerCategory[]): Set<string> => {
|
||||||
const codes = new Set<string>();
|
const codes = new Set<string>();
|
||||||
|
|||||||
+139
-60
@@ -3,6 +3,14 @@
|
|||||||
// ersten selektierten Elements und erlaubt das direkte Bearbeiten (Farbe,
|
// ersten selektierten Elements und erlaubt das direkte Bearbeiten (Farbe,
|
||||||
// Strichstärke, Füllschraffur).
|
// Strichstärke, Füllschraffur).
|
||||||
//
|
//
|
||||||
|
// Vordergrund/Hintergrund/Strichstärke/Schraffur tragen je einen 3-Optionen-
|
||||||
|
// Quellen-Dropdown: „Nach Ebene" (die LayerCategory erzwingt den Wert), „Nach
|
||||||
|
// Bauteil" (erbt vom Component-Manager, heutiges Default-Verhalten) oder
|
||||||
|
// „eigener Wert" (expliziter Override, gewinnt immer). Das Eingabe-Element
|
||||||
|
// (Farb-Swatch/mm-Zahlenfeld/Schraffur-Dropdown) erscheint NUR bei „eigener
|
||||||
|
// Wert"; ein Wechsel auf „Nach Ebene"/„Nach Bauteil" löscht den Wert wieder
|
||||||
|
// (siehe host.ts: onSetSelection{Foreground,Background,StrokeWeight,Hatch}Source).
|
||||||
|
//
|
||||||
// Daten/Handler kommen ausschließlich über usePanelHost (kein Prop-Drilling).
|
// Daten/Handler kommen ausschließlich über usePanelHost (kein Prop-Drilling).
|
||||||
// Es werden NUR Eigenschaften angeboten, die der Host-Kontrakt tatsächlich
|
// Es werden NUR Eigenschaften angeboten, die der Host-Kontrakt tatsächlich
|
||||||
// setzen kann — keine Stubs/Fake-Setter (Konvention wire-dont-stub). Felder ohne
|
// setzen kann — keine Stubs/Fake-Setter (Konvention wire-dont-stub). Felder ohne
|
||||||
@@ -14,11 +22,24 @@
|
|||||||
// Label→Wert-Grid. Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
|
// Label→Wert-Grid. Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
|
||||||
// Alle sichtbaren Texte über t(...).
|
// Alle sichtbaren Texte über t(...).
|
||||||
|
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { formatM } from "../model/types";
|
import { formatM } from "../model/types";
|
||||||
import { PEN_WEIGHTS } from "../model/types";
|
import { PEN_WEIGHTS } from "../model/types";
|
||||||
|
import type { AttributeSource } from "../model/types";
|
||||||
import { usePanelHost } from "./host";
|
import { usePanelHost } from "./host";
|
||||||
|
|
||||||
|
/** UI-Zustand des 3-Optionen-Quellen-Dropdowns (nicht 1:1 das Modell-Feld:
|
||||||
|
* „custom" ist abgeleitet aus „ist ein expliziter Wert gesetzt?", nicht
|
||||||
|
* selbst gespeichert). */
|
||||||
|
type UiSource = "layer" | "object" | "custom";
|
||||||
|
|
||||||
|
/** Leitet den UI-Quellen-Zustand ab: ein gesetzter Wert gewinnt immer („eigener
|
||||||
|
* Wert"), sonst das rohe Source-Feld (`undefined` ⇒ „Nach Bauteil", Default). */
|
||||||
|
function uiSourceOf(hasValue: boolean, raw: AttributeSource | undefined): UiSource {
|
||||||
|
return hasValue ? "custom" : raw ?? "object";
|
||||||
|
}
|
||||||
|
|
||||||
export function AttributesPanel() {
|
export function AttributesPanel() {
|
||||||
const host = usePanelHost();
|
const host = usePanelHost();
|
||||||
const sel = host.selection;
|
const sel = host.selection;
|
||||||
@@ -34,11 +55,13 @@ export function AttributesPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isDrawing = sel.kind === "drawing2d";
|
const isDrawing = sel.kind === "drawing2d";
|
||||||
// Strichstärke wirkt laut Kontrakt nur auf Drawing2D; Wände erben sie aus der
|
// Strichstärke: Wand/Decke/Drawing2D tragen einen eigenen Override
|
||||||
// Ebene und sind daher hier nicht editierbar.
|
// (`strokeWeight`/`weightMm`) + Quellen-Dropdown; andere Elementarten erben
|
||||||
const weightEditable = isDrawing;
|
// stets aus der Kategorie (kein Setter im Kontrakt → nicht editierbar).
|
||||||
// Füllschraffur nur bei geschlossener 2D-Form sinnvoll.
|
const weightEditable = sel.kind === "wall" || sel.kind === "ceiling" || isDrawing;
|
||||||
const fillEditable = isDrawing && sel.closed === true;
|
// Schraffur: Wand/Decke (Schnitt-/Ansichts-Schraffur) oder geschlossene 2D-Form.
|
||||||
|
const fillEditable =
|
||||||
|
sel.kind === "wall" || sel.kind === "ceiling" || (isDrawing && sel.closed === true);
|
||||||
// Vordergrund/Hintergrund (Muster-/Füllfarbe) tragen Wand, Decke und
|
// Vordergrund/Hintergrund (Muster-/Füllfarbe) tragen Wand, Decke und
|
||||||
// geschlossene 2D-Formen als Override; `undefined` = „Nach System".
|
// geschlossene 2D-Formen als Override; `undefined` = „Nach System".
|
||||||
const pocheEditable =
|
const pocheEditable =
|
||||||
@@ -46,43 +69,84 @@ export function AttributesPanel() {
|
|||||||
sel.kind === "ceiling" ||
|
sel.kind === "ceiling" ||
|
||||||
(isDrawing && sel.closed === true);
|
(isDrawing && sel.closed === true);
|
||||||
|
|
||||||
// Ein Override-Farbfeld mit „Nach System"-Zustand: ist der Wert `undefined`,
|
// Der 3-Optionen-Quellen-Dropdown, gemeinsam für alle vier Felder. Deaktiviert,
|
||||||
// zeigt es „Nach System" (leerer Swatch) statt einer Farbe und erbt; ein
|
// wenn das Feld für diese Elementart gar nicht gilt (z. B. Strichstärke bei
|
||||||
// Klick auf × stellt zurück auf „Nach System" (schreibt `null` → Feld weg).
|
// einer Treppe).
|
||||||
const overrideColor = (
|
const sourceSelect = (
|
||||||
value: string | undefined,
|
editable: boolean,
|
||||||
onSet: (color: string | null) => void,
|
ui: UiSource,
|
||||||
|
onChange: (next: UiSource) => void,
|
||||||
) => (
|
) => (
|
||||||
<span className="attr-val">
|
<select
|
||||||
<label className="attr-color">
|
className="attr-select"
|
||||||
<span
|
style={{ width: 104 }}
|
||||||
className="attr-color-swatch"
|
value={ui}
|
||||||
style={{ background: pocheEditable && value ? value : "transparent" }}
|
disabled={!editable}
|
||||||
/>
|
onChange={(e) => onChange(e.target.value as UiSource)}
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={value ?? "#808080"}
|
|
||||||
disabled={!pocheEditable}
|
|
||||||
title={pocheEditable ? undefined : t("attr.fillNeedsClosed")}
|
|
||||||
onChange={(e) => onSet(e.target.value)}
|
|
||||||
/>
|
|
||||||
<span className="attr-color-hex">
|
|
||||||
{pocheEditable && value ? value : t("attr.bySystem")}
|
|
||||||
</span>
|
|
||||||
{pocheEditable && value !== undefined && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="attr-color-clear"
|
|
||||||
title={t("attr.bySystem")}
|
|
||||||
onClick={() => onSet(null)}
|
|
||||||
>
|
>
|
||||||
×
|
<option value="layer">{t("attr.source.layer")}</option>
|
||||||
</button>
|
<option value="object">{t("attr.source.object")}</option>
|
||||||
)}
|
<option value="custom">{t("attr.source.custom")}</option>
|
||||||
</label>
|
</select>
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Farb-Swatch-Eingabe für „eigener Wert" (Vordergrund/Hintergrund). Erscheint
|
||||||
|
// nur, wenn die Quelle „custom" ist — sonst zeigt allein der Dropdown den
|
||||||
|
// Zustand.
|
||||||
|
const colorValue = (value: string | undefined, onSet: (color: string) => void) => (
|
||||||
|
<label className="attr-color">
|
||||||
|
<span className="attr-color-swatch" style={{ background: value ?? "#808080" }} />
|
||||||
|
<input type="color" value={value ?? "#808080"} onChange={(e) => onSet(e.target.value)} />
|
||||||
|
<span className="attr-color-hex">{value ?? "#808080"}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Eine Attribut-Zeile: Label + Quellen-Dropdown + (nur bei „eigener Wert")
|
||||||
|
// das passende Eingabe-Element.
|
||||||
|
const sourceRow = (
|
||||||
|
labelKey: string,
|
||||||
|
editable: boolean,
|
||||||
|
ui: UiSource,
|
||||||
|
onSource: (next: UiSource) => void,
|
||||||
|
valueEditor: ReactNode,
|
||||||
|
) => (
|
||||||
|
<>
|
||||||
|
<span className="attr-key">{t(labelKey)}</span>
|
||||||
|
<span className="attr-val" style={{ gap: 6 }}>
|
||||||
|
{ui === "custom" && valueEditor}
|
||||||
|
{sourceSelect(editable, ui, onSource)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Vordergrund/Hintergrund: „eigener Wert" ⇒ auf Quelle "object" (Nach
|
||||||
|
// Bauteil, Default) zurückfallen; „Nach Ebene"/„Nach Bauteil" schreiben die
|
||||||
|
// Quelle direkt (der Host löscht dabei den expliziten Wert).
|
||||||
|
const fgUi = uiSourceOf(sel.foreground !== undefined, sel.foregroundSource);
|
||||||
|
const bgUi = uiSourceOf(sel.background !== undefined, sel.backgroundSource);
|
||||||
|
const weightUi = uiSourceOf(sel.strokeWeightOverride !== undefined, sel.strokeWeightSource);
|
||||||
|
const hatchUi = uiSourceOf(
|
||||||
|
sel.fillHatchId !== undefined && sel.fillHatchId !== null,
|
||||||
|
sel.hatchSource,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onForegroundSourceChange = (next: UiSource) => {
|
||||||
|
if (next === "custom") host.onSetSelectionForeground(sel.foreground ?? sel.color);
|
||||||
|
else host.onSetSelectionForegroundSource(next);
|
||||||
|
};
|
||||||
|
const onBackgroundSourceChange = (next: UiSource) => {
|
||||||
|
if (next === "custom") host.onSetSelectionBackground(sel.background ?? sel.color);
|
||||||
|
else host.onSetSelectionBackgroundSource(next);
|
||||||
|
};
|
||||||
|
const onWeightSourceChange = (next: UiSource) => {
|
||||||
|
if (next === "custom") host.onSetSelectionWeight(sel.weightMm);
|
||||||
|
else host.onSetSelectionStrokeWeightSource(next);
|
||||||
|
};
|
||||||
|
const onHatchSourceChange = (next: UiSource) => {
|
||||||
|
if (next === "custom") host.onSetSelectionFill(project.hatches[0]?.id ?? null);
|
||||||
|
else host.onSetSelectionHatchSource(next);
|
||||||
|
};
|
||||||
|
|
||||||
// Effektiver Linienstil rein informativ (kein Setter im Kontrakt → kein Fake).
|
// Effektiver Linienstil rein informativ (kein Setter im Kontrakt → kein Fake).
|
||||||
// Wir leiten ihn aus dem Modell-Element ab, wenn explizit gesetzt; sonst „—".
|
// Wir leiten ihn aus dem Modell-Element ab, wenn explizit gesetzt; sonst „—".
|
||||||
const drawing = isDrawing
|
const drawing = isDrawing
|
||||||
@@ -119,18 +183,22 @@ export function AttributesPanel() {
|
|||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Strichstärke (mm) — nur Drawing2D editierbar; Wände erben aus Ebene. */}
|
{/* Strichstärke (mm) — Wand/Decke/Drawing2D, mit Nach-Ebene/Nach-Bauteil/
|
||||||
<span className="attr-key">{t("attr.weight")}</span>
|
eigener-Wert-Quelle; andere Elementarten erben stets aus der Kategorie. */}
|
||||||
<span className="attr-val">
|
{sourceRow(
|
||||||
|
"attr.weight",
|
||||||
|
weightEditable,
|
||||||
|
weightUi,
|
||||||
|
onWeightSourceChange,
|
||||||
|
<>
|
||||||
<input
|
<input
|
||||||
className="attr-num"
|
className="attr-num"
|
||||||
|
style={{ width: 72 }}
|
||||||
type="number"
|
type="number"
|
||||||
step={0.01}
|
step={0.01}
|
||||||
min={0}
|
min={0}
|
||||||
list="attr-pen-weights"
|
list="attr-pen-weights"
|
||||||
value={sel.weightMm}
|
value={sel.weightMm}
|
||||||
disabled={!weightEditable}
|
|
||||||
title={weightEditable ? undefined : t("attr.inheritedFromLayer")}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const v = Number(e.target.value);
|
const v = Number(e.target.value);
|
||||||
if (Number.isFinite(v)) host.onSetSelectionWeight(v);
|
if (Number.isFinite(v)) host.onSetSelectionWeight(v);
|
||||||
@@ -141,7 +209,8 @@ export function AttributesPanel() {
|
|||||||
<option key={w} value={w} />
|
<option key={w} value={w} />
|
||||||
))}
|
))}
|
||||||
</datalist>
|
</datalist>
|
||||||
</span>
|
</>,
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Linienstil — read-only/informativ (kein Setter im Kontrakt). */}
|
{/* Linienstil — read-only/informativ (kein Setter im Kontrakt). */}
|
||||||
<span className="attr-key">{t("attr.lineStyle")}</span>
|
<span className="attr-key">{t("attr.lineStyle")}</span>
|
||||||
@@ -152,26 +221,36 @@ export function AttributesPanel() {
|
|||||||
|
|
||||||
{/* ── Füllung (Vordergrund/Hintergrund + Schraffur) ─────────────── */}
|
{/* ── Füllung (Vordergrund/Hintergrund + Schraffur) ─────────────── */}
|
||||||
{/* Vordergrund = Muster-/Schraffurfarbe, Hintergrund = Füllfarbe/Poché.
|
{/* Vordergrund = Muster-/Schraffurfarbe, Hintergrund = Füllfarbe/Poché.
|
||||||
Beide sind Overrides mit Default „Nach System" (erben vom Bauteil);
|
Jedes Feld: Nach Ebene (LayerCategory erzwingt den Wert) / Nach Bauteil
|
||||||
`background` ist der Nachfolger des alten reinen `fillColor`-Feldes. */}
|
(erbt vom Component-Manager, Default) / eigener Wert (Override). */}
|
||||||
<div className="attr-section-label">{t("attr.fill")}</div>
|
<div className="attr-section-label">{t("attr.fill")}</div>
|
||||||
<div className="attr-grid">
|
<div className="attr-grid">
|
||||||
<span className="attr-key">{t("attr.foreground")}</span>
|
{sourceRow(
|
||||||
{overrideColor(sel.foreground, host.onSetSelectionForeground)}
|
"attr.foreground",
|
||||||
|
pocheEditable,
|
||||||
|
fgUi,
|
||||||
|
onForegroundSourceChange,
|
||||||
|
colorValue(sel.foreground, host.onSetSelectionForeground),
|
||||||
|
)}
|
||||||
|
|
||||||
<span className="attr-key">{t("attr.background")}</span>
|
{sourceRow(
|
||||||
{overrideColor(sel.background, host.onSetSelectionBackground)}
|
"attr.background",
|
||||||
|
pocheEditable,
|
||||||
|
bgUi,
|
||||||
|
onBackgroundSourceChange,
|
||||||
|
colorValue(sel.background, host.onSetSelectionBackground),
|
||||||
|
)}
|
||||||
|
|
||||||
<span className="attr-key">{t("attr.hatch")}</span>
|
{sourceRow(
|
||||||
<span className="attr-val">
|
"attr.hatch",
|
||||||
|
fillEditable,
|
||||||
|
hatchUi,
|
||||||
|
onHatchSourceChange,
|
||||||
<select
|
<select
|
||||||
className="attr-select"
|
className="attr-select"
|
||||||
value={fillEditable ? sel.fillHatchId ?? "" : ""}
|
style={{ width: 104 }}
|
||||||
disabled={!fillEditable}
|
value={sel.fillHatchId ?? ""}
|
||||||
title={fillEditable ? undefined : t("attr.fillNeedsClosed")}
|
onChange={(e) => host.onSetSelectionFill(e.target.value || null)}
|
||||||
onChange={(e) =>
|
|
||||||
host.onSetSelectionFill(e.target.value || null)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<option value="">{t("attr.none")}</option>
|
<option value="">{t("attr.none")}</option>
|
||||||
{project.hatches.map((h) => (
|
{project.hatches.map((h) => (
|
||||||
@@ -179,8 +258,8 @@ export function AttributesPanel() {
|
|||||||
{h.name}
|
{h.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>,
|
||||||
</span>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Maße (informativ, aus der bbox) ───────────────────────────── */}
|
{/* ── Maße (informativ, aus der bbox) ───────────────────────────── */}
|
||||||
|
|||||||
+30
-6
@@ -15,6 +15,7 @@ import { useContext } from "react";
|
|||||||
import { PanelHostContext } from "./types";
|
import { PanelHostContext } from "./types";
|
||||||
import type { DisplayMode } from "./types";
|
import type { DisplayMode } from "./types";
|
||||||
import type {
|
import type {
|
||||||
|
AttributeSource,
|
||||||
CeilingType,
|
CeilingType,
|
||||||
Component,
|
Component,
|
||||||
ContextObject,
|
ContextObject,
|
||||||
@@ -139,9 +140,17 @@ export interface PanelHostValue {
|
|||||||
selection: Selection | null;
|
selection: Selection | null;
|
||||||
/** Setzt die (Strich-)Farbe der aktuellen Selektion (Wand oder Drawing2D). */
|
/** Setzt die (Strich-)Farbe der aktuellen Selektion (Wand oder Drawing2D). */
|
||||||
onSetSelectionColor: (color: string) => void;
|
onSetSelectionColor: (color: string) => void;
|
||||||
/** Setzt die Strichstärke (mm) — wirkt NUR auf Drawing2D (sonst No-op). */
|
/**
|
||||||
|
* Setzt den Strichstärke-Override (mm, „eigener Wert") — Wand/Decke
|
||||||
|
* (`strokeWeight`) oder Drawing2D (`weightMm`). Die Quelle (Ebene/Bauteil)
|
||||||
|
* setzt {@link onSetSelectionStrokeWeightSource}.
|
||||||
|
*/
|
||||||
onSetSelectionWeight: (weightMm: number) => void;
|
onSetSelectionWeight: (weightMm: number) => void;
|
||||||
/** Setzt/entfernt die Füllschraffur — wirkt NUR auf Drawing2D (sonst No-op). */
|
/**
|
||||||
|
* Setzt/entfernt den Schraffur-Override („eigener Wert") — Wand, Decke oder
|
||||||
|
* Drawing2D (geschlossene Formen). `null` löscht den Wert (fällt zurück auf
|
||||||
|
* die Quelle, siehe {@link onSetSelectionHatchSource}).
|
||||||
|
*/
|
||||||
onSetSelectionFill: (hatchId: string | null) => void;
|
onSetSelectionFill: (hatchId: string | null) => void;
|
||||||
/**
|
/**
|
||||||
* Setzt/entfernt die Vollton-Füllfarbe — wirkt NUR auf Drawing2D (sonst No-op).
|
* Setzt/entfernt die Vollton-Füllfarbe — wirkt NUR auf Drawing2D (sonst No-op).
|
||||||
@@ -149,15 +158,30 @@ export interface PanelHostValue {
|
|||||||
*/
|
*/
|
||||||
onSetSelectionFillColor: (color: string | null) => void;
|
onSetSelectionFillColor: (color: string | null) => void;
|
||||||
/**
|
/**
|
||||||
* Setzt/entfernt den Vordergrund-Override (Muster-/Schraffurfarbe) der
|
* Setzt/entfernt den Vordergrund-Override (Muster-/Schraffurfarbe, „eigener
|
||||||
* Selektion — Wand, Decke oder geschlossene Drawing2D. `null` = „Nach System".
|
* Wert") der Selektion — Wand, Decke oder geschlossene Drawing2D. `null` =
|
||||||
|
* „Nach System" (fällt zurück auf die Quelle, siehe
|
||||||
|
* {@link onSetSelectionForegroundSource}).
|
||||||
*/
|
*/
|
||||||
onSetSelectionForeground: (color: string | null) => void;
|
onSetSelectionForeground: (color: string | null) => void;
|
||||||
/**
|
/**
|
||||||
* Setzt/entfernt den Hintergrund-Override (Füllfarbe/Poché) der Selektion —
|
* Setzt/entfernt den Hintergrund-Override (Füllfarbe/Poché, „eigener Wert")
|
||||||
* Wand, Decke oder geschlossene Drawing2D. `null` = „Nach System".
|
* der Selektion — Wand, Decke oder geschlossene Drawing2D. `null` = „Nach
|
||||||
|
* System" (siehe {@link onSetSelectionBackgroundSource}).
|
||||||
*/
|
*/
|
||||||
onSetSelectionBackground: (color: string | null) => void;
|
onSetSelectionBackground: (color: string | null) => void;
|
||||||
|
/**
|
||||||
|
* Setzt die Quelle des Vordergrunds, wenn KEIN „eigener Wert" gilt: "layer" =
|
||||||
|
* Nach Ebene, "object" = Nach Bauteil. Löscht dabei IMMER den expliziten
|
||||||
|
* Vordergrund-Override (die Quelle gewinnt nur, wenn kein Wert gesetzt ist).
|
||||||
|
*/
|
||||||
|
onSetSelectionForegroundSource: (source: AttributeSource) => void;
|
||||||
|
/** Setzt die Quelle des Hintergrunds, analog zu {@link onSetSelectionForegroundSource}. */
|
||||||
|
onSetSelectionBackgroundSource: (source: AttributeSource) => void;
|
||||||
|
/** Setzt die Quelle der Strichstärke, analog zu {@link onSetSelectionForegroundSource}. */
|
||||||
|
onSetSelectionStrokeWeightSource: (source: AttributeSource) => void;
|
||||||
|
/** Setzt die Quelle der Schraffur, analog zu {@link onSetSelectionForegroundSource}. */
|
||||||
|
onSetSelectionHatchSource: (source: AttributeSource) => void;
|
||||||
/**
|
/**
|
||||||
* Skaliert die Selektion auf Zielbreite×-höhe (Meter) um einen Anker
|
* Skaliert die Selektion auf Zielbreite×-höhe (Meter) um einen Anker
|
||||||
* (fx/fy ∈ [0,1] relativ zur bbox; 0,0 = oben-links … 1,1 = unten-rechts).
|
* (fx/fy ∈ [0,1] relativ zur bbox; 0,0 = oben-links … 1,1 = unten-rechts).
|
||||||
|
|||||||
+163
-31
@@ -6,6 +6,7 @@
|
|||||||
// Öffnungen) — exakt, schnell, sauber.
|
// Öffnungen) — exakt, schnell, sauber.
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
AttributeSource,
|
||||||
Ceiling,
|
Ceiling,
|
||||||
Component,
|
Component,
|
||||||
Drawing2D,
|
Drawing2D,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
getCeilingType,
|
getCeilingType,
|
||||||
getComponent,
|
getComponent,
|
||||||
getHatch,
|
getHatch,
|
||||||
|
getLayerCategory,
|
||||||
getLineStyle,
|
getLineStyle,
|
||||||
getWallType,
|
getWallType,
|
||||||
openingsOfWall,
|
openingsOfWall,
|
||||||
@@ -334,22 +336,82 @@ export function resolveHatch(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Kollabiert die Vordergrund-Farbkette (Muster-/Schraffurlinienfarbe) eines
|
* Kollabiert die Vordergrund-Farbkette (Muster-/Schraffurlinienfarbe) eines
|
||||||
* Bauteils zu einem einzelnen Wert für {@link resolveHatch}:
|
* Bauteils zu einem einzelnen Wert für {@link resolveHatch}. AUFLÖSUNGSREIHENFOLGE
|
||||||
* (Attribut-Override `override`) ?? (`Component.foreground`) ?? `undefined`.
|
* (By-Layer/By-Object, siehe `docs`/Attribut-Panel):
|
||||||
* `undefined` heißt „Nach System" — die Kette läuft dann in resolveHatch auf den
|
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.color`) ??
|
||||||
* HatchStyle.color-Backward-Compat-Fallback weiter (Sample-Verhalten).
|
* (`Component.foreground`) ?? `undefined`.
|
||||||
|
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" (heutiges Verhalten, unverändert).
|
||||||
|
* `undefined` als Endergebnis heißt „Nach System" — die Kette läuft dann in
|
||||||
|
* resolveHatch auf den HatchStyle.color-Backward-Compat-Fallback weiter
|
||||||
|
* (Sample-Verhalten: kein Element setzt `override`/`source`, also identisch).
|
||||||
*/
|
*/
|
||||||
export function resolveForeground(comp: Component, override?: string): string | undefined {
|
export function resolveForeground(
|
||||||
return override ?? comp.foreground;
|
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é) eines Bauteils:
|
* Kollabiert die Hintergrund-/Füllfarbkette (Poché) eines Bauteils, analog zu
|
||||||
* (Attribut-Override `override`) ?? (`Component.background`) ?? `Component.color`.
|
* {@link resolveForeground}:
|
||||||
|
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.color`) ??
|
||||||
|
* (`Component.background`) ?? `Component.color`.
|
||||||
* Immer definiert (endet spätestens bei `Component.color` = heutiges Verhalten).
|
* Immer definiert (endet spätestens bei `Component.color` = heutiges Verhalten).
|
||||||
*/
|
*/
|
||||||
export function resolveBackground(comp: Component, override?: string): string {
|
export function resolveBackground(
|
||||||
return override ?? comp.background ?? comp.color;
|
comp: Component,
|
||||||
|
override?: string,
|
||||||
|
category?: LayerCategory,
|
||||||
|
source?: AttributeSource,
|
||||||
|
): string {
|
||||||
|
if (override != null) return override;
|
||||||
|
if (source === "layer") return category?.color ?? comp.background ?? comp.color;
|
||||||
|
return comp.background ?? comp.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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" (heutiges Verhalten, unverändert).
|
||||||
|
* 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" (heutiges Verhalten, unverändert).
|
||||||
|
*/
|
||||||
|
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 ────────────────────────────────────────────────────
|
// ── SIA-Poché-Füllung ────────────────────────────────────────────────────
|
||||||
@@ -386,13 +448,15 @@ export function resolveWallSectionStyle(project: Project, wall: Wall): SectionCu
|
|||||||
if (!best || comp.joinPriority > best.joinPriority) best = comp;
|
if (!best || comp.joinPriority > best.joinPriority) best = comp;
|
||||||
}
|
}
|
||||||
if (!best) return null;
|
if (!best) return null;
|
||||||
|
const category = getLayerCategory(project, wall.categoryCode);
|
||||||
const wallAngleDeg =
|
const wallAngleDeg =
|
||||||
(Math.atan2(wall.end.y - wall.start.y, wall.end.x - wall.start.x) * 180) / Math.PI;
|
(Math.atan2(wall.end.y - wall.start.y, wall.end.x - wall.start.x) * 180) / Math.PI;
|
||||||
|
const hatchId = resolveHatchId(wall.hatchId, wall.hatchSource, category, best.hatchId) ?? best.hatchId;
|
||||||
const hatch = resolveHatch(
|
const hatch = resolveHatch(
|
||||||
project,
|
project,
|
||||||
best.hatchId,
|
hatchId,
|
||||||
wallAngleDeg,
|
wallAngleDeg,
|
||||||
resolveForeground(best, wall.foreground),
|
resolveForeground(best, wall.foreground, category, wall.foregroundSource),
|
||||||
);
|
);
|
||||||
// Poché-Hintergrund neutral (weiß) statt Bauteil-Albedo; Vollmuster → schwarz.
|
// Poché-Hintergrund neutral (weiß) statt Bauteil-Albedo; Vollmuster → schwarz.
|
||||||
const solid = hatch.pattern === "solid";
|
const solid = hatch.pattern === "solid";
|
||||||
@@ -419,9 +483,17 @@ export function resolveCeilingSectionStyle(
|
|||||||
const wt = getCeilingType(project, ceiling);
|
const wt = getCeilingType(project, ceiling);
|
||||||
if (wt.layers.length === 0) return null;
|
if (wt.layers.length === 0) return null;
|
||||||
const comp = getComponent(project, wt.layers[0].componentId);
|
const comp = getComponent(project, wt.layers[0].componentId);
|
||||||
|
const category = getLayerCategory(project, ceiling.categoryCode);
|
||||||
|
const hatchId =
|
||||||
|
resolveHatchId(ceiling.hatchId, ceiling.hatchSource, category, comp.hatchId) ?? comp.hatchId;
|
||||||
return {
|
return {
|
||||||
fill: resolveBackground(comp, ceiling.background),
|
fill: resolveBackground(comp, ceiling.background, category, ceiling.backgroundSource),
|
||||||
hatch: resolveHatch(project, comp.hatchId, undefined, resolveForeground(comp, ceiling.foreground)),
|
hatch: resolveHatch(
|
||||||
|
project,
|
||||||
|
hatchId,
|
||||||
|
undefined,
|
||||||
|
resolveForeground(comp, ceiling.foreground, category, ceiling.foregroundSource),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@@ -466,6 +538,9 @@ export function generatePlan(
|
|||||||
// Modell (Ebene) stammt statt aus einer Magie-Konstante.
|
// Modell (Ebene) stammt statt aus einer Magie-Konstante.
|
||||||
const lwByCode = categoryLwMap(project.layers);
|
const lwByCode = categoryLwMap(project.layers);
|
||||||
const colorByCode = categoryColorMap(project.layers);
|
const colorByCode = categoryColorMap(project.layers);
|
||||||
|
// Code → volle LayerCategory, für die By-Layer-Attribut-Auflösung (Quelle
|
||||||
|
// "layer" von Vordergrund/Hintergrund/Strichstärke/Schraffur).
|
||||||
|
const catByCode = categoryMap(project.layers);
|
||||||
|
|
||||||
// Freie 2D-Zeichengeometrie dieses Geschosses (wie Wände nach sichtbaren
|
// Freie 2D-Zeichengeometrie dieses Geschosses (wie Wände nach sichtbaren
|
||||||
// Kategorien + Darstellungsmodus gefiltert). Wird über der Wand-Poché
|
// Kategorien + Darstellungsmodus gefiltert). Wird über der Wand-Poché
|
||||||
@@ -489,8 +564,14 @@ export function generatePlan(
|
|||||||
);
|
);
|
||||||
for (const ceiling of ceilings) {
|
for (const ceiling of ceilings) {
|
||||||
const greyed = categoryDisplay(ceiling.categoryCode).greyed;
|
const greyed = categoryDisplay(ceiling.categoryCode).greyed;
|
||||||
const lwMm = lwByCode.get(ceiling.categoryCode) ?? WALL_FALLBACK_MM;
|
const category = catByCode.get(ceiling.categoryCode);
|
||||||
addCeilingPoche(primitives, project, ceiling, greyed, detail, lwMm);
|
const lwMm = resolveStrokeWeight(
|
||||||
|
ceiling.strokeWeight,
|
||||||
|
ceiling.strokeWeightSource,
|
||||||
|
category,
|
||||||
|
lwByCode.get(ceiling.categoryCode) ?? WALL_FALLBACK_MM,
|
||||||
|
);
|
||||||
|
addCeilingPoche(primitives, project, ceiling, greyed, detail, lwMm, category);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Räume (SIA-416-Flächen) dieses Geschosses: transluzente Farbfüllung + Stempel
|
// Räume (SIA-416-Flächen) dieses Geschosses: transluzente Farbfüllung + Stempel
|
||||||
@@ -510,14 +591,20 @@ export function generatePlan(
|
|||||||
|
|
||||||
for (const wall of walls) {
|
for (const wall of walls) {
|
||||||
const wallGreyed = categoryDisplay(wall.categoryCode).greyed;
|
const wallGreyed = categoryDisplay(wall.categoryCode).greyed;
|
||||||
const wallLwMm = lwByCode.get(wall.categoryCode) ?? WALL_FALLBACK_MM;
|
const wallCategory = catByCode.get(wall.categoryCode);
|
||||||
|
const wallLwMm = resolveStrokeWeight(
|
||||||
|
wall.strokeWeight,
|
||||||
|
wall.strokeWeightSource,
|
||||||
|
wallCategory,
|
||||||
|
lwByCode.get(wall.categoryCode) ?? WALL_FALLBACK_MM,
|
||||||
|
);
|
||||||
// Türen (Legacy) + Öffnungen (Fenster/Türen) am Wandhost: BEIDE sparen die
|
// Türen (Legacy) + Öffnungen (Fenster/Türen) am Wandhost: BEIDE sparen die
|
||||||
// Wand-Poché aus. Aus beiden wird eine gemeinsame Lücken-Intervall-Liste.
|
// Wand-Poché aus. Aus beiden wird eine gemeinsame Lücken-Intervall-Liste.
|
||||||
const doors = project.doors.filter((d) => d.hostWallId === wall.id);
|
const doors = project.doors.filter((d) => d.hostWallId === wall.id);
|
||||||
const openings = openingsOfWall(project, wall.id);
|
const openings = openingsOfWall(project, wall.id);
|
||||||
const gaps = wallGaps(wall, doors, openings);
|
const gaps = wallGaps(wall, doors, openings);
|
||||||
const cuts = joins.get(wall.id) ?? { startCut: null, endCut: null };
|
const cuts = joins.get(wall.id) ?? { startCut: null, endCut: null };
|
||||||
addWallPoche(primitives, project, wall, gaps, cuts, wallGreyed, detail, wallLwMm);
|
addWallPoche(primitives, project, wall, gaps, cuts, wallGreyed, detail, wallLwMm, wallCategory);
|
||||||
// Referenzlinie: dünne gestrichelte Wand-Achslinie (von der Oberleiste
|
// Referenzlinie: dünne gestrichelte Wand-Achslinie (von der Oberleiste
|
||||||
// umschaltbar). Wird über der Poché gezeichnet, damit sie sichtbar bleibt.
|
// umschaltbar). Wird über der Poché gezeichnet, damit sie sichtbar bleibt.
|
||||||
if (showReferenceLines) addReferenceLine(primitives, wall, wallGreyed);
|
if (showReferenceLines) addReferenceLine(primitives, wall, wallGreyed);
|
||||||
@@ -557,7 +644,7 @@ export function generatePlan(
|
|||||||
// 2D-Zeichengeometrie zuletzt (über der Poché).
|
// 2D-Zeichengeometrie zuletzt (über der Poché).
|
||||||
for (const d of drawings) {
|
for (const d of drawings) {
|
||||||
const greyed = categoryDisplay(d.categoryCode).greyed;
|
const greyed = categoryDisplay(d.categoryCode).greyed;
|
||||||
addDrawing2D(primitives, project, d, greyed, lwByCode, colorByCode);
|
addDrawing2D(primitives, project, d, greyed, lwByCode, colorByCode, catByCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kontext-Konturen (Höhenlinien) als dezente Linien zeichnen — UNTER der
|
// Kontext-Konturen (Höhenlinien) als dezente Linien zeichnen — UNTER der
|
||||||
@@ -780,6 +867,17 @@ function categoryColorMap(layers: LayerCategory[]): Map<string, string> {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut eine Map Kategorie-Code → volle LayerCategory über den ganzen Baum, für
|
||||||
|
* die By-Layer-Attribut-Auflösung (Quelle "layer" von Vordergrund/Hintergrund/
|
||||||
|
* Strichstärke/Schraffur, siehe {@link resolveForeground} und Geschwister).
|
||||||
|
*/
|
||||||
|
function categoryMap(layers: LayerCategory[]): Map<string, LayerCategory> {
|
||||||
|
const map = new Map<string, LayerCategory>();
|
||||||
|
for (const c of flattenCategories(layers)) map.set(c.code, c);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leitet ein 2D-Zeichenelement in Plan-Primitive ab (docs/design/drawing-tools.md
|
* Leitet ein 2D-Zeichenelement in Plan-Primitive ab (docs/design/drawing-tools.md
|
||||||
* §7.2). Farbe/Strichstärke kommen aus dem optionalen LineStyle, sonst aus der
|
* §7.2). Farbe/Strichstärke kommen aus dem optionalen LineStyle, sonst aus der
|
||||||
@@ -793,15 +891,30 @@ function addDrawing2D(
|
|||||||
greyed: boolean,
|
greyed: boolean,
|
||||||
lwByCode: Map<string, number>,
|
lwByCode: Map<string, number>,
|
||||||
colorByCode: Map<string, string>,
|
colorByCode: Map<string, string>,
|
||||||
|
catByCode: Map<string, LayerCategory>,
|
||||||
): void {
|
): void {
|
||||||
|
const category = catByCode.get(d.categoryCode);
|
||||||
const ls = d.lineStyleId
|
const ls = d.lineStyleId
|
||||||
? project.lineStyles.find((l) => l.id === d.lineStyleId)
|
? project.lineStyles.find((l) => l.id === d.lineStyleId)
|
||||||
: undefined;
|
: undefined;
|
||||||
// Plan-Tinte fix dunkel: Fallback auf MONO_INK (statt hell „#d0d0d0"), damit
|
// Plan-Tinte fix dunkel: Fallback auf MONO_INK (statt hell „#d0d0d0"), damit
|
||||||
// ein kategorieloses 2D-Element auf dem hellen Papier sichtbar bleibt.
|
// ein kategorieloses 2D-Element auf dem hellen Papier sichtbar bleibt.
|
||||||
const color = d.color ?? ls?.color ?? colorByCode.get(d.categoryCode) ?? MONO_INK;
|
const color = d.color ?? ls?.color ?? colorByCode.get(d.categoryCode) ?? MONO_INK;
|
||||||
const weightMm = d.weightMm ?? ls?.weight ?? lwByCode.get(d.categoryCode) ?? WALL_FALLBACK_MM;
|
// Strichstärke: "layer" erzwingt die Ebenen-Strichstärke (umgeht das
|
||||||
|
// LineStyle-Gewicht), sonst (Default "object") die heutige Kette.
|
||||||
|
const weightMm = resolveStrokeWeight(
|
||||||
|
d.weightMm,
|
||||||
|
d.strokeWeightSource,
|
||||||
|
category,
|
||||||
|
ls?.weight ?? lwByCode.get(d.categoryCode) ?? WALL_FALLBACK_MM,
|
||||||
|
);
|
||||||
const dash = ls?.dash ?? null;
|
const dash = ls?.dash ?? null;
|
||||||
|
// Drawing2D trägt kein Bauteil → die By-Layer-Quelle ist hier die einzige
|
||||||
|
// Alternative zum expliziten Wert (kein Component-Fallback); Default
|
||||||
|
// "object"/undefined bleibt exakt das heutige Verhalten.
|
||||||
|
const foreground = d.foreground ?? (d.foregroundSource === "layer" ? category?.color : undefined);
|
||||||
|
const background = d.background ?? (d.backgroundSource === "layer" ? category?.color : undefined);
|
||||||
|
const hatchId = resolveHatchId(d.hatchId, d.hatchSource, category, undefined);
|
||||||
// Zickzack-Parameter aus dem LineStyle durchreichen (nur kind==="zigzag").
|
// Zickzack-Parameter aus dem LineStyle durchreichen (nur kind==="zigzag").
|
||||||
// Additiv: fehlt es, bleibt die Linie gerade/gestrichelt wie bisher.
|
// Additiv: fehlt es, bleibt die Linie gerade/gestrichelt wie bisher.
|
||||||
const zigzag = ls?.kind === "zigzag" ? ls.zigzag : undefined;
|
const zigzag = ls?.kind === "zigzag" ? ls.zigzag : undefined;
|
||||||
@@ -821,16 +934,18 @@ function addDrawing2D(
|
|||||||
out.push({
|
out.push({
|
||||||
kind: "polygon",
|
kind: "polygon",
|
||||||
pts,
|
pts,
|
||||||
// Hintergrund-Override hat Vorrang vor dem Alt-Feld fillColor (beide leer =
|
// Hintergrund-Override (Wert ?? By-Layer) hat Vorrang vor dem Alt-Feld
|
||||||
// transparente Fläche, nur Schraffur) — Sample bleibt identisch.
|
// fillColor (alle leer = transparente Fläche, nur Schraffur) — Sample
|
||||||
fill: d.background ?? d.fillColor ?? "none",
|
// bleibt identisch (kein Element setzt background/backgroundSource).
|
||||||
|
fill: background ?? d.fillColor ?? "none",
|
||||||
// Umriss zeichnen die separaten Linien-Primitive (mit Strichmuster); die
|
// Umriss zeichnen die separaten Linien-Primitive (mit Strichmuster); die
|
||||||
// Füllfläche selbst bleibt randlos, um doppelte Striche zu vermeiden.
|
// Füllfläche selbst bleibt randlos, um doppelte Striche zu vermeiden.
|
||||||
stroke: "none",
|
stroke: "none",
|
||||||
strokeWidthMm: 0,
|
strokeWidthMm: 0,
|
||||||
// Drawing2D trägt kein Bauteil → Vordergrund-Override direkt aus d.foreground
|
// Drawing2D trägt kein Bauteil → Vordergrund/Schraffur-Override direkt aus
|
||||||
// (Attribut „Nach System" ⇒ undefined ⇒ HatchStyle.color-Fallback).
|
// d.foreground/d.hatchId bzw. der Ebene (Attribut „Nach System" ⇒
|
||||||
hatch: d.hatchId ? resolveHatch(project, d.hatchId, undefined, d.foreground) : NO_HATCH,
|
// undefined ⇒ HatchStyle.color-Fallback bzw. NO_HATCH).
|
||||||
|
hatch: hatchId ? resolveHatch(project, hatchId, undefined, foreground) : NO_HATCH,
|
||||||
greyed,
|
greyed,
|
||||||
drawingId: d.id,
|
drawingId: d.id,
|
||||||
});
|
});
|
||||||
@@ -929,6 +1044,7 @@ function addWallPoche(
|
|||||||
greyed: boolean,
|
greyed: boolean,
|
||||||
detail: DetailLevel,
|
detail: DetailLevel,
|
||||||
wallLwMm: number,
|
wallLwMm: number,
|
||||||
|
category: LayerCategory | undefined,
|
||||||
): void {
|
): void {
|
||||||
const wt = getWallType(project, wall);
|
const wt = getWallType(project, wall);
|
||||||
const total = wallTypeThickness(wt);
|
const total = wallTypeThickness(wt);
|
||||||
@@ -1015,11 +1131,13 @@ function addWallPoche(
|
|||||||
for (let li = 0; li < wt.layers.length; li++) {
|
for (let li = 0; li < wt.layers.length; li++) {
|
||||||
const layer = wt.layers[li];
|
const layer = wt.layers[li];
|
||||||
const comp = getComponent(project, layer.componentId);
|
const comp = getComponent(project, layer.componentId);
|
||||||
|
const layerHatchId =
|
||||||
|
resolveHatchId(wall.hatchId, wall.hatchSource, category, comp.hatchId) ?? comp.hatchId;
|
||||||
const layerHatch = resolveHatch(
|
const layerHatch = resolveHatch(
|
||||||
project,
|
project,
|
||||||
comp.hatchId,
|
layerHatchId,
|
||||||
wallAngleDeg,
|
wallAngleDeg,
|
||||||
resolveForeground(comp, wall.foreground),
|
resolveForeground(comp, wall.foreground, category, wall.foregroundSource),
|
||||||
);
|
);
|
||||||
// SIA-Poché: neutraler Hintergrund (weiß) statt Bauteil-Albedo, die
|
// SIA-Poché: neutraler Hintergrund (weiß) statt Bauteil-Albedo, die
|
||||||
// Schichtschraffur liegt darüber; Vollmuster ("solid") = schwarze Poché.
|
// Schichtschraffur liegt darüber; Vollmuster ("solid") = schwarze Poché.
|
||||||
@@ -1319,6 +1437,7 @@ function addCeilingPoche(
|
|||||||
greyed: boolean,
|
greyed: boolean,
|
||||||
detail: DetailLevel,
|
detail: DetailLevel,
|
||||||
lwMm: number,
|
lwMm: number,
|
||||||
|
category: LayerCategory | undefined,
|
||||||
): void {
|
): void {
|
||||||
const pts = ceiling.outline;
|
const pts = ceiling.outline;
|
||||||
if (pts.length < 3) return;
|
if (pts.length < 3) return;
|
||||||
@@ -1333,10 +1452,23 @@ function addCeilingPoche(
|
|||||||
// NICHT die Schnitt-Schraffur (die gehört auf echte Schnittflächen, siehe
|
// NICHT die Schnitt-Schraffur (die gehört auf echte Schnittflächen, siehe
|
||||||
// resolveCeilingSectionStyle/splitSlabLayers), sondern `viewHatchId`. Fehlt
|
// resolveCeilingSectionStyle/splitSlabLayers), sondern `viewHatchId`. Fehlt
|
||||||
// sie (Default), bleibt die Aufsicht weiss. Im Detailgrad „grob" entfällt
|
// sie (Default), bleibt die Aufsicht weiss. Im Detailgrad „grob" entfällt
|
||||||
// die Schraffur ganz (nur Fläche + Umriss).
|
// die Schraffur ganz (nur Fläche + Umriss). Das Attribut-Override
|
||||||
|
// `ceiling.hatchId` (+ `hatchSource`) überschreibt diese Ansichts-Schraffur
|
||||||
|
// genauso wie die Schnitt-Schraffur in {@link resolveCeilingSectionStyle}.
|
||||||
|
const effectiveViewHatchId = resolveHatchId(
|
||||||
|
ceiling.hatchId,
|
||||||
|
ceiling.hatchSource,
|
||||||
|
category,
|
||||||
|
comp?.viewHatchId,
|
||||||
|
);
|
||||||
const hatch =
|
const hatch =
|
||||||
detail !== "grob" && comp?.viewHatchId
|
detail !== "grob" && comp && effectiveViewHatchId
|
||||||
? resolveHatch(project, comp.viewHatchId, undefined, resolveForeground(comp, ceiling.foreground))
|
? resolveHatch(
|
||||||
|
project,
|
||||||
|
effectiveViewHatchId,
|
||||||
|
undefined,
|
||||||
|
resolveForeground(comp, ceiling.foreground, category, ceiling.foregroundSource),
|
||||||
|
)
|
||||||
: NO_HATCH;
|
: NO_HATCH;
|
||||||
const solid = hatch.pattern === "solid";
|
const solid = hatch.pattern === "solid";
|
||||||
out.push({
|
out.push({
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "../model/types";
|
} from "../model/types";
|
||||||
import type {
|
import type {
|
||||||
Component,
|
Component,
|
||||||
|
Drawing2D,
|
||||||
DrawingLevel,
|
DrawingLevel,
|
||||||
HatchStyle,
|
HatchStyle,
|
||||||
LayerCategory,
|
LayerCategory,
|
||||||
@@ -152,6 +153,13 @@ export interface ProjectSlice {
|
|||||||
id: string,
|
id: string,
|
||||||
value: string | null,
|
value: string | null,
|
||||||
) => void;
|
) => void;
|
||||||
|
/**
|
||||||
|
* Generischer immutabler Patch auf ein Drawing2D (per ID) — analog zu
|
||||||
|
* `updateWall`/`updateCeiling`. Trägt u. a. die By-Layer/By-Object-
|
||||||
|
* Quellenfelder (`foregroundSource`/`backgroundSource`/`strokeWeightSource`/
|
||||||
|
* `hatchSource`), die kein eigenes Setter-Paar haben.
|
||||||
|
*/
|
||||||
|
updateDrawing2D: (id: string, patch: Partial<Drawing2D>) => void;
|
||||||
/**
|
/**
|
||||||
* Skaliert die Geometrie eines Elements auf Zielbreite×-höhe (Meter) um einen
|
* Skaliert die Geometrie eines Elements auf Zielbreite×-höhe (Meter) um einen
|
||||||
* Ankerpunkt (fx/fy ∈ [0,1] relativ zur bbox; 0,0 = oben-links).
|
* Ankerpunkt (fx/fy ∈ [0,1] relativ zur bbox; 0,0 = oben-links).
|
||||||
@@ -748,6 +756,12 @@ export function createProjectSlice(
|
|||||||
setElementBackground: (kind, id, value) =>
|
setElementBackground: (kind, id, value) =>
|
||||||
setProject((p) => setOverrideField(p, kind, id, "background", value)),
|
setProject((p) => setOverrideField(p, kind, id, "background", value)),
|
||||||
|
|
||||||
|
updateDrawing2D: (id, patch) =>
|
||||||
|
setProject((p) => ({
|
||||||
|
...p,
|
||||||
|
drawings2d: p.drawings2d.map((d) => (d.id === id ? { ...d, ...patch } : d)),
|
||||||
|
})),
|
||||||
|
|
||||||
// ── Größe (Resize um Anker) ────────────────────────────────────────────
|
// ── Größe (Resize um Anker) ────────────────────────────────────────────
|
||||||
resizeElement: (kind, id, w, h, anchor) =>
|
resizeElement: (kind, id, w, h, anchor) =>
|
||||||
setProject((p) => resizeElement(p, kind, id, w, h, anchor)),
|
setProject((p) => resizeElement(p, kind, id, w, h, anchor)),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
wallTypeThickness,
|
wallTypeThickness,
|
||||||
} from "../model/types";
|
} from "../model/types";
|
||||||
import type {
|
import type {
|
||||||
|
AttributeSource,
|
||||||
Ceiling,
|
Ceiling,
|
||||||
Drawing2D,
|
Drawing2D,
|
||||||
Drawing2DGeom,
|
Drawing2DGeom,
|
||||||
@@ -229,6 +230,24 @@ export interface Selection {
|
|||||||
* System". Nachfolger von `fillColor`; hat im Plan Vorrang.
|
* System". Nachfolger von `fillColor`; hat im Plan Vorrang.
|
||||||
*/
|
*/
|
||||||
background?: string;
|
background?: string;
|
||||||
|
/**
|
||||||
|
* Quelle des Vordergrunds, wenn `foreground` NICHT gesetzt ist ("layer" =
|
||||||
|
* Nach Ebene, "object"/`undefined` = Nach Bauteil, Default). Gesetzt bei
|
||||||
|
* Wand, Decke und geschlossener Drawing2D (wie `foreground`/`pocheEditable`).
|
||||||
|
*/
|
||||||
|
foregroundSource?: AttributeSource;
|
||||||
|
/** Quelle des Hintergrunds, analog zu {@link Selection.foregroundSource}. */
|
||||||
|
backgroundSource?: AttributeSource;
|
||||||
|
/**
|
||||||
|
* RAW Strichstärke-Override (mm) DIESES Elements; `undefined` = kein Override
|
||||||
|
* (Herkunft dann `strokeWeightSource`). Gesetzt bei Wand, Decke, Drawing2D.
|
||||||
|
* `weightMm` bleibt der EFFEKTIVE (aufgelöste) Wert für die Anzeige.
|
||||||
|
*/
|
||||||
|
strokeWeightOverride?: number;
|
||||||
|
/** Quelle der Strichstärke, wenn kein `strokeWeightOverride` gesetzt ist. */
|
||||||
|
strokeWeightSource?: AttributeSource;
|
||||||
|
/** Quelle der Schraffur, wenn kein `fillHatchId` gesetzt ist. */
|
||||||
|
hatchSource?: AttributeSource;
|
||||||
/** Ob die Form geschlossen ist (rect/circle/closed polyline) — nur Drawing2D. */
|
/** Ob die Form geschlossen ist (rect/circle/closed polyline) — nur Drawing2D. */
|
||||||
closed?: boolean;
|
closed?: boolean;
|
||||||
/** Achsparallele Bounding-Box in Modell-Metern. */
|
/** Achsparallele Bounding-Box in Modell-Metern. */
|
||||||
@@ -310,7 +329,10 @@ function geomBBox(g: Drawing2DGeom): SelectionBBox {
|
|||||||
function wallSelection(project: Project, wall: Wall): Selection {
|
function wallSelection(project: Project, wall: Wall): Selection {
|
||||||
const cat = findCategory(project.layers, wall.categoryCode);
|
const cat = findCategory(project.layers, wall.categoryCode);
|
||||||
const color = wall.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
|
const color = wall.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
|
||||||
const weightMm = cat?.lw ?? WALL_FALLBACK_MM;
|
// Effektive Strichstärke: expliziter Override zuerst, sonst die Kategorie
|
||||||
|
// (die Quelle "layer"/"object" macht heute keinen Unterschied, da Component
|
||||||
|
// keine eigene Strichstärke trägt — siehe resolveStrokeWeight).
|
||||||
|
const weightMm = wall.strokeWeight ?? cat?.lw ?? WALL_FALLBACK_MM;
|
||||||
const minX = Math.min(wall.start.x, wall.end.x);
|
const minX = Math.min(wall.start.x, wall.end.x);
|
||||||
const minY = Math.min(wall.start.y, wall.end.y);
|
const minY = Math.min(wall.start.y, wall.end.y);
|
||||||
const maxX = Math.max(wall.start.x, wall.end.x);
|
const maxX = Math.max(wall.start.x, wall.end.x);
|
||||||
@@ -351,10 +373,15 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
categoryCode: wall.categoryCode,
|
categoryCode: wall.categoryCode,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: undefined,
|
fillHatchId: wall.hatchId,
|
||||||
fillColor: undefined,
|
fillColor: undefined,
|
||||||
foreground: wall.foreground,
|
foreground: wall.foreground,
|
||||||
background: wall.background,
|
background: wall.background,
|
||||||
|
foregroundSource: wall.foregroundSource,
|
||||||
|
backgroundSource: wall.backgroundSource,
|
||||||
|
strokeWeightOverride: wall.strokeWeight,
|
||||||
|
strokeWeightSource: wall.strokeWeightSource,
|
||||||
|
hatchSource: wall.hatchSource,
|
||||||
closed: undefined,
|
closed: undefined,
|
||||||
bbox: { minX, minY, maxX, maxY },
|
bbox: { minX, minY, maxX, maxY },
|
||||||
wall: wallInfo,
|
wall: wallInfo,
|
||||||
@@ -365,7 +392,7 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
||||||
const cat = findCategory(project.layers, ceiling.categoryCode);
|
const cat = findCategory(project.layers, ceiling.categoryCode);
|
||||||
const color = ceiling.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
|
const color = ceiling.color ?? cat?.color ?? WALL_FALLBACK_COLOR;
|
||||||
const weightMm = cat?.lw ?? WALL_FALLBACK_MM;
|
const weightMm = ceiling.strokeWeight ?? cat?.lw ?? WALL_FALLBACK_MM;
|
||||||
const box = outlineBBox(ceiling.outline);
|
const box = outlineBBox(ceiling.outline);
|
||||||
const wt = getCeilingType(project, ceiling);
|
const wt = getCeilingType(project, ceiling);
|
||||||
const thickness = ceilingThickness(project, ceiling);
|
const thickness = ceilingThickness(project, ceiling);
|
||||||
@@ -397,10 +424,15 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
|||||||
categoryCode: ceiling.categoryCode,
|
categoryCode: ceiling.categoryCode,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: undefined,
|
fillHatchId: ceiling.hatchId,
|
||||||
fillColor: undefined,
|
fillColor: undefined,
|
||||||
foreground: ceiling.foreground,
|
foreground: ceiling.foreground,
|
||||||
background: ceiling.background,
|
background: ceiling.background,
|
||||||
|
foregroundSource: ceiling.foregroundSource,
|
||||||
|
backgroundSource: ceiling.backgroundSource,
|
||||||
|
strokeWeightOverride: ceiling.strokeWeight,
|
||||||
|
strokeWeightSource: ceiling.strokeWeightSource,
|
||||||
|
hatchSource: ceiling.hatchSource,
|
||||||
closed: true,
|
closed: true,
|
||||||
bbox: { minX: box.minX, minY: box.minY, maxX: box.maxX, maxY: box.maxY },
|
bbox: { minX: box.minX, minY: box.minY, maxX: box.maxX, maxY: box.maxY },
|
||||||
ceiling: ceilingInfo,
|
ceiling: ceilingInfo,
|
||||||
@@ -547,7 +579,15 @@ function drawingSelection(project: Project, d: Drawing2D): Selection {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const cat = findCategory(project.layers, d.categoryCode);
|
const cat = findCategory(project.layers, d.categoryCode);
|
||||||
const color = d.color ?? ls?.color ?? cat?.color ?? DRAW_FALLBACK_COLOR;
|
const color = d.color ?? ls?.color ?? cat?.color ?? DRAW_FALLBACK_COLOR;
|
||||||
const weightMm = d.weightMm ?? ls?.weight ?? cat?.lw ?? WALL_FALLBACK_MM;
|
// Effektive Strichstärke, dieselbe Kette wie generatePlan/addDrawing2D
|
||||||
|
// (resolveStrokeWeight): Override zuerst, "layer" erzwingt die Kategorie
|
||||||
|
// (umgeht das LineStyle-Gewicht), sonst die heutige LineStyle-/Kategorie-Kette.
|
||||||
|
const weightMm =
|
||||||
|
d.weightMm ??
|
||||||
|
(d.strokeWeightSource === "layer" ? cat?.lw : undefined) ??
|
||||||
|
ls?.weight ??
|
||||||
|
cat?.lw ??
|
||||||
|
WALL_FALLBACK_MM;
|
||||||
const closed = isClosedGeom(d.geom);
|
const closed = isClosedGeom(d.geom);
|
||||||
return {
|
return {
|
||||||
kind: "drawing2d",
|
kind: "drawing2d",
|
||||||
@@ -559,6 +599,11 @@ function drawingSelection(project: Project, d: Drawing2D): Selection {
|
|||||||
fillColor: closed ? d.fillColor ?? null : null,
|
fillColor: closed ? d.fillColor ?? null : null,
|
||||||
foreground: closed ? d.foreground : undefined,
|
foreground: closed ? d.foreground : undefined,
|
||||||
background: closed ? d.background : undefined,
|
background: closed ? d.background : undefined,
|
||||||
|
foregroundSource: closed ? d.foregroundSource : undefined,
|
||||||
|
backgroundSource: closed ? d.backgroundSource : undefined,
|
||||||
|
strokeWeightOverride: d.weightMm,
|
||||||
|
strokeWeightSource: d.strokeWeightSource,
|
||||||
|
hatchSource: closed ? d.hatchSource : undefined,
|
||||||
closed,
|
closed,
|
||||||
bbox: geomBBox(d.geom),
|
bbox: geomBBox(d.geom),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user