Attribut-Panel: Default "Nach Ebene" statt "Nach Bauteil", Wert immer
sichtbar, Bearbeiten schaltet automatisch auf "eigener Wert" Nutzer-Report: "aktuell zeigt jedes Element standard nach Bauteil. Das wäre eigentlich 'custom' also eigener Wert. Deshalb es soll nach Ebene Standard sein bei allen Dingen. Und man sollte immer sehen welche Stiftdicke oder welche Farbe... und wenn man auf das Farbfeld klickt und die Farbe ändert dann springt es auf eigener Wert automatisch." **Default-Umkehr** (plan/generatePlan/shared.ts): resolveForeground/ resolveBackground/resolveHatchId/resolveStrokeWeight prüften bisher `source === "layer"`, sonst (auch bei fehlendem Source-Feld -- der Normalfall bei jedem neu erzeugten Element, das nie explizit gesetzt wird) fiel die Kette auf "Nach Bauteil" zurück. Jetzt `source !== "object"`: fehlend/"layer" liefert die Kategorie, NUR ein explizites "object" fällt noch auf das Bauteil zurück. Zentraler Fix in den Resolver-Funktionen selbst wirkt automatisch überall (Grundriss, Schnitt, Attribut-Panel-Vorschau) konsistent, nicht nur im Panel. Rückfrage an den Nutzer zum riskantesten Teil (Hintergrund/Poché hat eine dokumentierte SIA-neutrale Sonderregel, falls kein Wert gesetzt ist) -- bestätigt: einheitlich umstellen, "Nach Bauteil" bleibt bei Wand/Decke als explizite Wahl verfügbar. **"Nach Bauteil" nur noch bei Wand/Decke** (AttributesPanel.tsx): ein Drawing2D hat kein eigenes Bauteil (Component) -- die Option wäre dort bedeutungslos (resolveForeground & Co. fielen auf gar keinen Wert zurück). Neuer `allowObjectSource`-Schalter blendet die Dropdown- Option für alle vier Felder bei 2D-Elementen aus. **Wert immer sichtbar + Auto-Switch auf "eigener Wert"**: das Eingabe- Element (Farb-Swatch/Zahlenfeld/Schraffur-Dropdown) war bisher nur bei Quelle "eigener Wert" sichtbar -- jetzt immer, mit dem EFFEKTIVEN Wert befüllt. Neue Selection-Felder effectiveForeground/effectiveBackground/ effectiveHatchId (selectionInfo.ts, über dieselbe Resolve-Kette wie der Renderer) liefern dafür den echten Wert statt des oft leeren rohen Overrides. Editiert man das Feld direkt, greift der bestehende Override- Setter (setzt z.B. wall.foreground) -- die Quelle springt automatisch auf "eigener Wert", weil uiSourceOf ausschliesslich davon abhängt, ob ein Wert gesetzt ist (kein zusätzlicher Umschalt-Schritt nötig). Beim expliziten Umschalten per Dropdown wird jetzt vom aktuell ANGEZEIGTEN effektiven Wert gesät statt von sel.color, damit die Farbe dabei nicht unerwartet springt. +9 Tests (shared.resolve.test.ts: Default-Umkehr aller vier Resolver, Override gewinnt immer, Fallback ohne Kategorie). tsc/vitest 934/934 grün.
This commit is contained in:
+6
-4
@@ -831,12 +831,14 @@ export interface LayerCategory {
|
|||||||
* Quelle eines vererbbaren Attributs (Vordergrund/Hintergrund/Strichstärke/
|
* Quelle eines vererbbaren Attributs (Vordergrund/Hintergrund/Strichstärke/
|
||||||
* Schraffur), wenn KEIN expliziter Wert am Element gesetzt ist:
|
* Schraffur), wenn KEIN expliziter Wert am Element gesetzt ist:
|
||||||
* • "layer" — „Nach Ebene": die LayerCategory des Elements erzwingt den Wert
|
* • "layer" — „Nach Ebene": die LayerCategory des Elements erzwingt den Wert
|
||||||
* (`color`/`lw`/`hatch`).
|
* (`color`/`lw`/`hatch`). Das ist auch der DEFAULT, wenn das Source-Feld
|
||||||
|
* fehlt (`undefined`) — ein frisch erzeugtes Element (Source-Feld nie
|
||||||
|
* gesetzt) folgt also der Ebene, nicht dem Bauteil.
|
||||||
* • "object" — „Nach Bauteil": erbt vom Bauteil (Component) bzw. dessen
|
* • "object" — „Nach Bauteil": erbt vom Bauteil (Component) bzw. dessen
|
||||||
* bisheriger Fallback-Kette. Das ist auch der Default, wenn das Source-Feld
|
* bisheriger Fallback-Kette. Muss aktiv im Attribut-Panel gewählt werden
|
||||||
* fehlt (`undefined`) — damit bleibt das heutige Verhalten unverändert.
|
* (nur bei Wand/Decke anwendbar — ein Drawing2D hat kein Bauteil).
|
||||||
* Ein gesetzter expliziter Wert (z. B. `foreground`) gewinnt IMMER, unabhängig
|
* Ein gesetzter expliziter Wert (z. B. `foreground`) gewinnt IMMER, unabhängig
|
||||||
* von der Source (s. Resolve-Reihenfolge in `plan/generatePlan.ts`).
|
* von der Source (s. Resolve-Reihenfolge in `plan/generatePlan/shared.ts`).
|
||||||
*/
|
*/
|
||||||
export type AttributeSource = "layer" | "object";
|
export type AttributeSource = "layer" | "object";
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ import {
|
|||||||
type UiSource = "layer" | "object" | "custom";
|
type UiSource = "layer" | "object" | "custom";
|
||||||
|
|
||||||
/** Leitet den UI-Quellen-Zustand ab: ein gesetzter Wert gewinnt immer („eigener
|
/** Leitet den UI-Quellen-Zustand ab: ein gesetzter Wert gewinnt immer („eigener
|
||||||
* Wert"), sonst das rohe Source-Feld (`undefined` ⇒ „Nach Bauteil", Default). */
|
* Wert"), sonst das rohe Source-Feld (`undefined` ⇒ „Nach Ebene", Default —
|
||||||
|
* s. AttributeSource in model/types.ts). */
|
||||||
function uiSourceOf(hasValue: boolean, raw: AttributeSource | undefined): UiSource {
|
function uiSourceOf(hasValue: boolean, raw: AttributeSource | undefined): UiSource {
|
||||||
return hasValue ? "custom" : raw ?? "object";
|
return hasValue ? "custom" : raw ?? "layer";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AttributesPanel() {
|
export function AttributesPanel() {
|
||||||
@@ -131,13 +132,21 @@ export function AttributesPanel() {
|
|||||||
sel.kind === "ceiling" ||
|
sel.kind === "ceiling" ||
|
||||||
(isDrawing && sel.closed === true);
|
(isDrawing && sel.closed === true);
|
||||||
|
|
||||||
// Der 3-Optionen-Quellen-Dropdown, gemeinsam für alle vier Felder. Deaktiviert,
|
// Ein Drawing2D hat KEIN eigenes Bauteil (Component) — „Nach Bauteil" wäre
|
||||||
// wenn das Feld für diese Elementart gar nicht gilt (z. B. Strichstärke bei
|
// dort bedeutungslos (resolveForeground & Co. fallen dann auf gar keinen
|
||||||
// einer Treppe).
|
// Wert zurück, s. selectionInfo.ts `noComponent`). Nur Wand/Decke bieten
|
||||||
|
// die Option daher an.
|
||||||
|
const allowObjectSource = !isDrawing;
|
||||||
|
|
||||||
|
// Der Quellen-Dropdown (Nach Ebene/Nach Bauteil/eigener Wert), gemeinsam für
|
||||||
|
// alle vier Felder. Deaktiviert, wenn das Feld für diese Elementart gar
|
||||||
|
// nicht gilt (z. B. Strichstärke bei einer Treppe); „Nach Bauteil" fehlt bei
|
||||||
|
// Drawing2D (s. o.).
|
||||||
const sourceSelect = (
|
const sourceSelect = (
|
||||||
editable: boolean,
|
editable: boolean,
|
||||||
ui: UiSource,
|
ui: UiSource,
|
||||||
onChange: (next: UiSource) => void,
|
onChange: (next: UiSource) => void,
|
||||||
|
allowObject: boolean,
|
||||||
) => (
|
) => (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
value={ui}
|
value={ui}
|
||||||
@@ -145,33 +154,39 @@ export function AttributesPanel() {
|
|||||||
onChange={(v) => onChange(v as UiSource)}
|
onChange={(v) => onChange(v as UiSource)}
|
||||||
options={[
|
options={[
|
||||||
{ value: "layer", label: t("attr.source.layer") },
|
{ value: "layer", label: t("attr.source.layer") },
|
||||||
{ value: "object", label: t("attr.source.object") },
|
...(allowObject ? [{ value: "object", label: t("attr.source.object") }] : []),
|
||||||
{ value: "custom", label: t("attr.source.custom") },
|
{ value: "custom", label: t("attr.source.custom") },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Farb-Swatch-Eingabe für „eigener Wert" (Vordergrund/Hintergrund). Erscheint
|
// Farb-Swatch — IMMER sichtbar (Nutzer-Wunsch: den aktuell EFFEKTIVEN Wert
|
||||||
// nur, wenn die Quelle „custom" ist — sonst zeigt allein der Dropdown den
|
// (welche Farbe auch immer Ebene/Bauteil/Override gerade liefert) jederzeit
|
||||||
// Zustand.
|
// sehen, nicht nur bei „eigener Wert"). Ändert man ihn direkt, wird
|
||||||
const colorValue = (value: string | undefined, onSet: (color: string) => void) => (
|
// automatisch „eigener Wert": der Setter schreibt den Override, wodurch
|
||||||
<ColorHexField value={value ?? "#808080"} onChange={onSet} />
|
// `uiSourceOf` beim nächsten Render „custom" ableitet (kein separater
|
||||||
);
|
// Umschalt-Schritt nötig).
|
||||||
|
const colorValue = (
|
||||||
|
value: string | undefined,
|
||||||
|
onSet: (color: string) => void,
|
||||||
|
editable: boolean,
|
||||||
|
) => <ColorHexField value={value ?? "#808080"} onChange={onSet} disabled={!editable} />;
|
||||||
|
|
||||||
// Eine Attribut-Zeile: Label + Quellen-Dropdown + (nur bei „eigener Wert")
|
// Eine Attribut-Zeile: Label + IMMER sichtbares Eingabe-Element (zeigt den
|
||||||
// das passende Eingabe-Element.
|
// effektiven Wert) + Quellen-Dropdown.
|
||||||
const sourceRow = (
|
const sourceRow = (
|
||||||
labelKey: string,
|
labelKey: string,
|
||||||
editable: boolean,
|
editable: boolean,
|
||||||
ui: UiSource,
|
ui: UiSource,
|
||||||
onSource: (next: UiSource) => void,
|
onSource: (next: UiSource) => void,
|
||||||
valueEditor: ReactNode,
|
valueEditor: ReactNode,
|
||||||
|
allowObject: boolean = allowObjectSource,
|
||||||
) => (
|
) => (
|
||||||
<>
|
<>
|
||||||
<span className="attr-key">{t(labelKey)}</span>
|
<span className="attr-key">{t(labelKey)}</span>
|
||||||
<span className="attr-val">
|
<span className="attr-val">
|
||||||
{ui === "custom" && valueEditor}
|
{valueEditor}
|
||||||
{sourceSelect(editable, ui, onSource)}
|
{sourceSelect(editable, ui, onSource, allowObject)}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -187,12 +202,17 @@ export function AttributesPanel() {
|
|||||||
sel.hatchSource,
|
sel.hatchSource,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Wechsel auf „eigener Wert" per Dropdown (nicht durchs Editieren des Felds
|
||||||
|
// selbst, s. colorValue-Kommentar oben) sät den Override mit dem gerade
|
||||||
|
// ANGEZEIGTEN effektiven Wert — sonst würde die Farbe beim Umschalten
|
||||||
|
// unerwartet springen (z. B. auf sel.color statt der tatsächlich sichtbaren
|
||||||
|
// Ebenen-/Bauteilfarbe).
|
||||||
const onForegroundSourceChange = (next: UiSource) => {
|
const onForegroundSourceChange = (next: UiSource) => {
|
||||||
if (next === "custom") host.onSetSelectionForeground(sel.foreground ?? sel.color);
|
if (next === "custom") host.onSetSelectionForeground(sel.effectiveForeground ?? sel.color);
|
||||||
else host.onSetSelectionForegroundSource(next);
|
else host.onSetSelectionForegroundSource(next);
|
||||||
};
|
};
|
||||||
const onBackgroundSourceChange = (next: UiSource) => {
|
const onBackgroundSourceChange = (next: UiSource) => {
|
||||||
if (next === "custom") host.onSetSelectionBackground(sel.background ?? sel.color);
|
if (next === "custom") host.onSetSelectionBackground(sel.effectiveBackground ?? sel.color);
|
||||||
else host.onSetSelectionBackgroundSource(next);
|
else host.onSetSelectionBackgroundSource(next);
|
||||||
};
|
};
|
||||||
const onWeightSourceChange = (next: UiSource) => {
|
const onWeightSourceChange = (next: UiSource) => {
|
||||||
@@ -200,8 +220,11 @@ export function AttributesPanel() {
|
|||||||
else host.onSetSelectionStrokeWeightSource(next);
|
else host.onSetSelectionStrokeWeightSource(next);
|
||||||
};
|
};
|
||||||
const onHatchSourceChange = (next: UiSource) => {
|
const onHatchSourceChange = (next: UiSource) => {
|
||||||
if (next === "custom") host.onSetSelectionFill(project.hatches[0]?.id ?? null);
|
if (next === "custom") {
|
||||||
else host.onSetSelectionHatchSource(next);
|
host.onSetSelectionFill(sel.effectiveHatchId ?? 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).
|
||||||
@@ -272,6 +295,7 @@ export function AttributesPanel() {
|
|||||||
type="number"
|
type="number"
|
||||||
step={0.01}
|
step={0.01}
|
||||||
min={0}
|
min={0}
|
||||||
|
disabled={!weightEditable}
|
||||||
list="attr-pen-weights"
|
list="attr-pen-weights"
|
||||||
value={sel.weightMm}
|
value={sel.weightMm}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -352,7 +376,7 @@ export function AttributesPanel() {
|
|||||||
pocheEditable,
|
pocheEditable,
|
||||||
fgUi,
|
fgUi,
|
||||||
onForegroundSourceChange,
|
onForegroundSourceChange,
|
||||||
colorValue(sel.foreground, host.onSetSelectionForeground),
|
colorValue(sel.effectiveForeground, host.onSetSelectionForeground, pocheEditable),
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sourceRow(
|
{sourceRow(
|
||||||
@@ -360,7 +384,7 @@ export function AttributesPanel() {
|
|||||||
pocheEditable,
|
pocheEditable,
|
||||||
bgUi,
|
bgUi,
|
||||||
onBackgroundSourceChange,
|
onBackgroundSourceChange,
|
||||||
colorValue(sel.background, host.onSetSelectionBackground),
|
colorValue(sel.effectiveBackground, host.onSetSelectionBackground, pocheEditable),
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sourceRow(
|
{sourceRow(
|
||||||
@@ -369,7 +393,8 @@ export function AttributesPanel() {
|
|||||||
hatchUi,
|
hatchUi,
|
||||||
onHatchSourceChange,
|
onHatchSourceChange,
|
||||||
<Dropdown
|
<Dropdown
|
||||||
value={sel.fillHatchId ?? ""}
|
value={sel.effectiveHatchId ?? ""}
|
||||||
|
disabled={!fillEditable}
|
||||||
onChange={(v) => host.onSetSelectionFill(v || null)}
|
onChange={(v) => host.onSetSelectionFill(v || null)}
|
||||||
options={[
|
options={[
|
||||||
{ value: "", label: t("attr.none") },
|
{ value: "", label: t("attr.none") },
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* `resolveForeground`/`resolveBackground`/`resolveHatchId`/`resolveStrokeWeight`
|
||||||
|
* — By-Layer/By-Object-Auflösung. Nutzer-Report: ein neu erzeugtes Element
|
||||||
|
* (Source-Feld nie gesetzt) zeigte im Attribut-Panel „Nach Bauteil" als
|
||||||
|
* Default, obwohl „Nach Ebene" der gewünschte Standard ist. Fix: `source`
|
||||||
|
* fehlend/`"layer"` liefert jetzt die Kategorie, NUR ein explizites
|
||||||
|
* `"object"` fällt auf das Bauteil zurück (vorher umgekehrt).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
resolveForeground,
|
||||||
|
resolveBackground,
|
||||||
|
resolveHatchId,
|
||||||
|
resolveStrokeWeight,
|
||||||
|
} from "./shared";
|
||||||
|
import type { Component, LayerCategory } from "../../model/types";
|
||||||
|
|
||||||
|
const comp: Component = {
|
||||||
|
id: "c1",
|
||||||
|
name: "Beton",
|
||||||
|
color: "#888888",
|
||||||
|
foreground: "#111111",
|
||||||
|
background: "#222222",
|
||||||
|
hatchId: "sia-concrete",
|
||||||
|
joinPriority: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const category: LayerCategory = {
|
||||||
|
code: "20",
|
||||||
|
name: "Wände",
|
||||||
|
color: "#0a0a0a",
|
||||||
|
lw: 0.5,
|
||||||
|
visible: true,
|
||||||
|
locked: false,
|
||||||
|
hatch: "sia-wall",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("resolveForeground/resolveBackground — Default fehlender Source ist jetzt 'Nach Ebene'", () => {
|
||||||
|
it("kein Override, Source fehlt: liefert die Kategorie-Farbe (nicht mehr die Bauteilfarbe)", () => {
|
||||||
|
expect(resolveForeground(comp, undefined, category, undefined)).toBe(category.color);
|
||||||
|
expect(resolveBackground(comp, undefined, category, undefined)).toBe(category.color);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("kein Override, Source explizit 'layer': unverändert die Kategorie-Farbe", () => {
|
||||||
|
expect(resolveForeground(comp, undefined, category, "layer")).toBe(category.color);
|
||||||
|
expect(resolveBackground(comp, undefined, category, "layer")).toBe(category.color);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("kein Override, Source explizit 'object': fällt weiterhin auf die Bauteilfarbe zurück", () => {
|
||||||
|
expect(resolveForeground(comp, undefined, category, "object")).toBe(comp.foreground);
|
||||||
|
expect(resolveBackground(comp, undefined, category, "object")).toBe(comp.background);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ein gesetzter Override gewinnt immer, unabhängig von der Source", () => {
|
||||||
|
expect(resolveForeground(comp, "#ff00ff", category, "object")).toBe("#ff00ff");
|
||||||
|
expect(resolveForeground(comp, "#ff00ff", category, undefined)).toBe("#ff00ff");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ohne Kategorie fällt 'Nach Ebene' auf die Bauteilfarbe zurück (kein Crash)", () => {
|
||||||
|
expect(resolveForeground(comp, undefined, undefined, undefined)).toBe(comp.foreground);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolveHatchId — Default fehlender Source ist jetzt 'Nach Ebene'", () => {
|
||||||
|
it("kein Override, Source fehlt: liefert die Kategorie-Schraffur", () => {
|
||||||
|
expect(resolveHatchId(undefined, undefined, category, comp.hatchId)).toBe(category.hatch);
|
||||||
|
});
|
||||||
|
it("kein Override, Source explizit 'object': liefert den Fallback (Bauteil-Schraffur)", () => {
|
||||||
|
expect(resolveHatchId(undefined, "object", category, comp.hatchId)).toBe(comp.hatchId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolveStrokeWeight — Default fehlender Source ist jetzt 'Nach Ebene'", () => {
|
||||||
|
it("kein Override, Source fehlt: liefert die Kategorie-Strichstärke", () => {
|
||||||
|
expect(resolveStrokeWeight(undefined, undefined, category, 0.13)).toBe(category.lw);
|
||||||
|
});
|
||||||
|
it("kein Override, Source explizit 'object': liefert den Fallback", () => {
|
||||||
|
expect(resolveStrokeWeight(undefined, "object", category, 0.13)).toBe(0.13);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -419,12 +419,12 @@ 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}. AUFLÖSUNGSREIHENFOLGE
|
* Bauteils zu einem einzelnen Wert für {@link resolveHatch}. AUFLÖSUNGSREIHENFOLGE
|
||||||
* (By-Layer/By-Object, siehe `docs`/Attribut-Panel):
|
* (By-Layer/By-Object, siehe `docs`/Attribut-Panel):
|
||||||
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.color`) ??
|
* (Attribut-Override `override`) ?? (`source!=="object"` ⇒ `category.color`) ??
|
||||||
* (`Component.foreground`) ?? `undefined`.
|
* (`Component.foreground`) ?? `undefined`.
|
||||||
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" (heutiges Verhalten, unverändert).
|
* `source` fehlt/`"layer"` ⇒ „Nach Ebene" (DEFAULT — ein neu erzeugtes Element
|
||||||
* `undefined` als Endergebnis heißt „Nach System" — die Kette läuft dann in
|
* ohne explizites Source-Feld folgt der Ebene, nicht dem Bauteil; „Nach
|
||||||
* resolveHatch auf den HatchStyle.color-Backward-Compat-Fallback weiter
|
* Bauteil" muss aktiv gewählt werden). Nur ein EXPLIZITES `"object"` fällt auf
|
||||||
* (Sample-Verhalten: kein Element setzt `override`/`source`, also identisch).
|
* die Bauteil-Kette zurück.
|
||||||
*/
|
*/
|
||||||
export function resolveForeground(
|
export function resolveForeground(
|
||||||
comp: Component,
|
comp: Component,
|
||||||
@@ -433,14 +433,14 @@ export function resolveForeground(
|
|||||||
source?: AttributeSource,
|
source?: AttributeSource,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (override != null) return override;
|
if (override != null) return override;
|
||||||
if (source === "layer") return category?.color ?? comp.foreground;
|
if (source !== "object") return category?.color ?? comp.foreground;
|
||||||
return comp.foreground;
|
return comp.foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kollabiert die Hintergrund-/Füllfarbkette (Poché-Override) eines Bauteils zu
|
* Kollabiert die Hintergrund-/Füllfarbkette (Poché-Override) eines Bauteils zu
|
||||||
* einem einzelnen Wert, exakt analog zu {@link resolveForeground}:
|
* einem einzelnen Wert, exakt analog zu {@link resolveForeground}:
|
||||||
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.color`) ??
|
* (Attribut-Override `override`) ?? (`source!=="object"` ⇒ `category.color`) ??
|
||||||
* (`Component.background`) ?? `undefined`.
|
* (`Component.background`) ?? `undefined`.
|
||||||
* `undefined` als Endergebnis heißt „kein Override gesetzt" — Aufrufer fallen
|
* `undefined` als Endergebnis heißt „kein Override gesetzt" — Aufrufer fallen
|
||||||
* dann auf ihren eigenen Default zurück (bei Wand/Decke die neutrale SIA-
|
* dann auf ihren eigenen Default zurück (bei Wand/Decke die neutrale SIA-
|
||||||
@@ -455,7 +455,7 @@ export function resolveBackground(
|
|||||||
source?: AttributeSource,
|
source?: AttributeSource,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (override != null) return override;
|
if (override != null) return override;
|
||||||
if (source === "layer") return category?.color ?? comp.background;
|
if (source !== "object") return category?.color ?? comp.background;
|
||||||
return comp.background;
|
return comp.background;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,10 +463,10 @@ export function resolveBackground(
|
|||||||
* Kollabiert die Schraffur-Referenz (Hatch Manager) eines Elements zu einer
|
* Kollabiert die Schraffur-Referenz (Hatch Manager) eines Elements zu einer
|
||||||
* einzelnen `hatchId` für {@link resolveHatch} — By-Layer/By-Object, analog zu
|
* einzelnen `hatchId` für {@link resolveHatch} — By-Layer/By-Object, analog zu
|
||||||
* {@link resolveForeground}:
|
* {@link resolveForeground}:
|
||||||
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.hatch`) ??
|
* (Attribut-Override `override`) ?? (`source!=="object"` ⇒ `category.hatch`) ??
|
||||||
* `fallback` (Bauteil-Schraffur, z. B. `Component.hatchId`/`viewHatchId`, oder
|
* `fallback` (Bauteil-Schraffur, z. B. `Component.hatchId`/`viewHatchId`, oder
|
||||||
* `undefined` bei Drawing2D ohne Bauteil-Bezug).
|
* `undefined` bei Drawing2D ohne Bauteil-Bezug).
|
||||||
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" (heutiges Verhalten, unverändert).
|
* `source` fehlt/`"layer"` ⇒ „Nach Ebene" (DEFAULT, s. {@link resolveForeground}).
|
||||||
* Ergebnis kann `undefined` sein — der Aufrufer fällt dann auf `NO_HATCH` zurück.
|
* Ergebnis kann `undefined` sein — der Aufrufer fällt dann auf `NO_HATCH` zurück.
|
||||||
*/
|
*/
|
||||||
export function resolveHatchId(
|
export function resolveHatchId(
|
||||||
@@ -476,17 +476,17 @@ export function resolveHatchId(
|
|||||||
fallback?: string,
|
fallback?: string,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (override != null) return override;
|
if (override != null) return override;
|
||||||
if (source === "layer") return category?.hatch ?? fallback;
|
if (source !== "object") return category?.hatch ?? fallback;
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kollabiert die Strichstärke-Kette (mm Papier) eines Elements, By-Layer/By-
|
* Kollabiert die Strichstärke-Kette (mm Papier) eines Elements, By-Layer/By-
|
||||||
* Object, analog zu {@link resolveHatchId}:
|
* Object, analog zu {@link resolveHatchId}:
|
||||||
* (Attribut-Override `override`) ?? (`source==="layer"` ⇒ `category.lw`) ??
|
* (Attribut-Override `override`) ?? (`source!=="object"` ⇒ `category.lw`) ??
|
||||||
* `fallback` (bisherige Bauteil-/LineStyle-/Kategorie-Default-Kette des
|
* `fallback` (bisherige Bauteil-/LineStyle-/Kategorie-Default-Kette des
|
||||||
* Aufrufers — heutiges Verhalten).
|
* Aufrufers — heutiges Verhalten).
|
||||||
* `source` fehlt/`"object"` ⇒ „Nach Bauteil" (heutiges Verhalten, unverändert).
|
* `source` fehlt/`"layer"` ⇒ „Nach Ebene" (DEFAULT, s. {@link resolveForeground}).
|
||||||
*/
|
*/
|
||||||
export function resolveStrokeWeight(
|
export function resolveStrokeWeight(
|
||||||
override: number | undefined,
|
override: number | undefined,
|
||||||
@@ -495,7 +495,7 @@ export function resolveStrokeWeight(
|
|||||||
fallback: number,
|
fallback: number,
|
||||||
): number {
|
): number {
|
||||||
if (override != null) return override;
|
if (override != null) return override;
|
||||||
if (source === "layer") return category?.lw ?? fallback;
|
if (source !== "object") return category?.lw ?? fallback;
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import type {
|
|||||||
WallReferenceLine,
|
WallReferenceLine,
|
||||||
} from "../model/types";
|
} from "../model/types";
|
||||||
import { localToLv95 } from "../io/lv95";
|
import { localToLv95 } from "../io/lv95";
|
||||||
|
import { resolveForeground, resolveBackground, resolveHatchId } from "../plan/generatePlan/shared";
|
||||||
import { columnVerticalExtent } from "../model/wall";
|
import { columnVerticalExtent } from "../model/wall";
|
||||||
import { columnFootprint } from "../geometry/column";
|
import { columnFootprint } from "../geometry/column";
|
||||||
import { evaluateRoom, polygonArea } from "../geometry/roomArea";
|
import { evaluateRoom, polygonArea } from "../geometry/roomArea";
|
||||||
@@ -413,6 +414,18 @@ export interface Selection {
|
|||||||
strokeWeightSource?: AttributeSource;
|
strokeWeightSource?: AttributeSource;
|
||||||
/** Quelle der Schraffur, wenn kein `fillHatchId` gesetzt ist. */
|
/** Quelle der Schraffur, wenn kein `fillHatchId` gesetzt ist. */
|
||||||
hatchSource?: AttributeSource;
|
hatchSource?: AttributeSource;
|
||||||
|
/**
|
||||||
|
* EFFEKTIV aufgelöster Vordergrund (Override ?? Ebene ?? Bauteil, s.
|
||||||
|
* `resolveForeground` — dieselbe Kette wie beim Rendern), für die IMMER
|
||||||
|
* sichtbare Vorschau im Attribut-Panel (Nutzer-Wunsch: „man sollte immer
|
||||||
|
* sehen welche Farbe [gilt]"), unabhängig von der aktuellen Quelle.
|
||||||
|
* `undefined` = kein Wert an keiner Stelle der Kette (echtes „Nach System").
|
||||||
|
*/
|
||||||
|
effectiveForeground?: string;
|
||||||
|
/** Effektiv aufgelöster Hintergrund, analog zu {@link Selection.effectiveForeground}. */
|
||||||
|
effectiveBackground?: string;
|
||||||
|
/** Effektiv aufgelöste Schraffur-ID, analog zu {@link Selection.effectiveForeground}. */
|
||||||
|
effectiveHatchId?: string | null;
|
||||||
/** 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;
|
||||||
/**
|
/**
|
||||||
@@ -533,6 +546,26 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
// Wand-Attribute auflösen (Referenzlinie, Aufbau, Referenzgeschoss, UK/OK).
|
// Wand-Attribute auflösen (Referenzlinie, Aufbau, Referenzgeschoss, UK/OK).
|
||||||
const wt = getWallType(project, wall);
|
const wt = getWallType(project, wall);
|
||||||
const thickness = wallTypeThickness(wt);
|
const thickness = wallTypeThickness(wt);
|
||||||
|
// Effektive Vordergrund-/Hintergrund-/Schraffur-Vorschau fürs Attribut-Panel
|
||||||
|
// (Nutzer-Wunsch: immer den TATSÄCHLICHEN Wert sehen, nicht nur ein leeres
|
||||||
|
// Override-Feld) — dieselbe Resolve-Kette wie beim Rendern (resolveForeground
|
||||||
|
// & Co.), als Bauteil-Repräsentant die ÄUSSERSTE Schicht (wie `abbrevOf`
|
||||||
|
// unten). Bei mehrschichtigem Aufbau nur eine Annäherung (jede Schicht kann
|
||||||
|
// im Schnitt ihre EIGENE Bauteilfarbe zeigen) — für die Panel-Vorschau
|
||||||
|
// ausreichend, der Override selbst wirkt ohnehin einheitlich auf alle Schichten.
|
||||||
|
const outerComponent = getComponent(project, wt.layers[0]?.componentId);
|
||||||
|
const effectiveForeground = outerComponent
|
||||||
|
? resolveForeground(outerComponent, wall.foreground, cat, wall.foregroundSource)
|
||||||
|
: wall.foreground;
|
||||||
|
const effectiveBackground = outerComponent
|
||||||
|
? resolveBackground(outerComponent, wall.background, cat, wall.backgroundSource)
|
||||||
|
: wall.background;
|
||||||
|
const effectiveHatchId = resolveHatchId(
|
||||||
|
wall.hatchId,
|
||||||
|
wall.hatchSource,
|
||||||
|
cat,
|
||||||
|
outerComponent?.hatchId,
|
||||||
|
);
|
||||||
const floor = project.drawingLevels.find((z) => z.id === wall.floorId);
|
const floor = project.drawingLevels.find((z) => z.id === wall.floorId);
|
||||||
const { zBottom, zTop } = wallVerticalExtent(project, wall);
|
const { zBottom, zTop } = wallVerticalExtent(project, wall);
|
||||||
const above = nextFloorAbove(project, wall);
|
const above = nextFloorAbove(project, wall);
|
||||||
@@ -606,6 +639,9 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
background: wall.background,
|
background: wall.background,
|
||||||
foregroundSource: wall.foregroundSource,
|
foregroundSource: wall.foregroundSource,
|
||||||
backgroundSource: wall.backgroundSource,
|
backgroundSource: wall.backgroundSource,
|
||||||
|
effectiveForeground,
|
||||||
|
effectiveBackground,
|
||||||
|
effectiveHatchId,
|
||||||
strokeWeightOverride: wall.strokeWeight,
|
strokeWeightOverride: wall.strokeWeight,
|
||||||
strokeWeightSource: wall.strokeWeightSource,
|
strokeWeightSource: wall.strokeWeightSource,
|
||||||
hatchSource: wall.hatchSource,
|
hatchSource: wall.hatchSource,
|
||||||
@@ -623,6 +659,21 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
|||||||
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);
|
||||||
|
// Effektive Vordergrund-/Hintergrund-/Schraffur-Vorschau — s. Kommentar in
|
||||||
|
// wallSelection (dieselbe Kette, äusserste Schicht als Bauteil-Repräsentant).
|
||||||
|
const outerComponent = getComponent(project, wt.layers[0]?.componentId);
|
||||||
|
const effectiveForeground = outerComponent
|
||||||
|
? resolveForeground(outerComponent, ceiling.foreground, cat, ceiling.foregroundSource)
|
||||||
|
: ceiling.foreground;
|
||||||
|
const effectiveBackground = outerComponent
|
||||||
|
? resolveBackground(outerComponent, ceiling.background, cat, ceiling.backgroundSource)
|
||||||
|
: ceiling.background;
|
||||||
|
const effectiveHatchId = resolveHatchId(
|
||||||
|
ceiling.hatchId,
|
||||||
|
ceiling.hatchSource,
|
||||||
|
cat,
|
||||||
|
outerComponent?.hatchId,
|
||||||
|
);
|
||||||
const floor = project.drawingLevels.find((z) => z.id === ceiling.floorId);
|
const floor = project.drawingLevels.find((z) => z.id === ceiling.floorId);
|
||||||
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
|
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
|
||||||
const ceilingInfo: CeilingInfo = {
|
const ceilingInfo: CeilingInfo = {
|
||||||
@@ -666,6 +717,9 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
|||||||
background: ceiling.background,
|
background: ceiling.background,
|
||||||
foregroundSource: ceiling.foregroundSource,
|
foregroundSource: ceiling.foregroundSource,
|
||||||
backgroundSource: ceiling.backgroundSource,
|
backgroundSource: ceiling.backgroundSource,
|
||||||
|
effectiveForeground,
|
||||||
|
effectiveBackground,
|
||||||
|
effectiveHatchId,
|
||||||
strokeWeightOverride: ceiling.strokeWeight,
|
strokeWeightOverride: ceiling.strokeWeight,
|
||||||
strokeWeightSource: ceiling.strokeWeightSource,
|
strokeWeightSource: ceiling.strokeWeightSource,
|
||||||
hatchSource: ceiling.hatchSource,
|
hatchSource: ceiling.hatchSource,
|
||||||
@@ -998,15 +1052,32 @@ function drawingSelection(project: Project, d: Drawing2D): Selection {
|
|||||||
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;
|
||||||
// Effektive Strichstärke, dieselbe Kette wie generatePlan/addDrawing2D
|
// Effektive Strichstärke, dieselbe Kette wie generatePlan/addDrawing2D
|
||||||
// (resolveStrokeWeight): Override zuerst, "layer" erzwingt die Kategorie
|
// (resolveStrokeWeight): Override zuerst, Source ≠ "object" (also "layer"
|
||||||
// (umgeht das LineStyle-Gewicht), sonst die heutige LineStyle-/Kategorie-Kette.
|
// ODER fehlend, s. AttributeSource-Default) erzwingt die Kategorie (umgeht
|
||||||
|
// das LineStyle-Gewicht), sonst die LineStyle-/Kategorie-Kette.
|
||||||
const weightMm =
|
const weightMm =
|
||||||
d.weightMm ??
|
d.weightMm ??
|
||||||
(d.strokeWeightSource === "layer" ? cat?.lw : undefined) ??
|
(d.strokeWeightSource !== "object" ? cat?.lw : undefined) ??
|
||||||
ls?.weight ??
|
ls?.weight ??
|
||||||
cat?.lw ??
|
cat?.lw ??
|
||||||
WALL_FALLBACK_MM;
|
WALL_FALLBACK_MM;
|
||||||
const closed = isClosedGeom(d.geom);
|
const closed = isClosedGeom(d.geom);
|
||||||
|
// Effektive Vordergrund-/Hintergrund-/Schraffur-Vorschau — s. Kommentar in
|
||||||
|
// wallSelection. Ein Drawing2D hat KEIN eigenes Bauteil (daher auch keine
|
||||||
|
// „Nach Bauteil"-Option im Panel, s. AttributesPanel.tsx) — als Platzhalter
|
||||||
|
// ein leeres Bauteil, dessen foreground/background/hatchId immer fehlen,
|
||||||
|
// sodass die Kette bei einem (aus Alt-Daten) expliziten `source:"object"`
|
||||||
|
// korrekt auf „kein Wert" statt einer erfundenen Bauteilfarbe fällt.
|
||||||
|
const noComponent = { id: "", name: "", color, hatchId: "", joinPriority: 0 };
|
||||||
|
const effectiveForeground = closed
|
||||||
|
? resolveForeground(noComponent, d.foreground, cat, d.foregroundSource)
|
||||||
|
: undefined;
|
||||||
|
const effectiveBackground = closed
|
||||||
|
? resolveBackground(noComponent, d.background, cat, d.backgroundSource)
|
||||||
|
: undefined;
|
||||||
|
const effectiveHatchId = closed
|
||||||
|
? resolveHatchId(d.hatchId, d.hatchSource, cat, undefined)
|
||||||
|
: null;
|
||||||
return {
|
return {
|
||||||
kind: "drawing2d",
|
kind: "drawing2d",
|
||||||
id: d.id,
|
id: d.id,
|
||||||
@@ -1020,6 +1091,9 @@ function drawingSelection(project: Project, d: Drawing2D): Selection {
|
|||||||
background: closed ? d.background : undefined,
|
background: closed ? d.background : undefined,
|
||||||
foregroundSource: closed ? d.foregroundSource : undefined,
|
foregroundSource: closed ? d.foregroundSource : undefined,
|
||||||
backgroundSource: closed ? d.backgroundSource : undefined,
|
backgroundSource: closed ? d.backgroundSource : undefined,
|
||||||
|
effectiveForeground,
|
||||||
|
effectiveBackground,
|
||||||
|
effectiveHatchId,
|
||||||
strokeWeightOverride: d.weightMm,
|
strokeWeightOverride: d.weightMm,
|
||||||
strokeWeightSource: d.strokeWeightSource,
|
strokeWeightSource: d.strokeWeightSource,
|
||||||
hatchSource: closed ? d.hatchSource : undefined,
|
hatchSource: closed ? d.hatchSource : undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user