Attribut-Panel: Vordergrund/Hintergrund mit 'Nach System'
Die Attribut-Sektion 'Fuellung' zeigt statt der einen Fuellfarbe jetzt zwei Felder: Vordergrund (Muster-/Schraffurfarbe) und Hintergrund (Fuellung), jeweils per Element ueberschreibbar. Leerer Override = 'Nach System' (erbt); Farbe waehlen setzt den Override, x loescht ihn wieder. Schreibt Wall/Ceiling/Drawing2D.foreground/background ueber neue Store-Mutationen (setElementForeground/Background), gespiegelt zum bestehenden Fuellfarbe-Pfad. Die Resolve-Schicht (generatePlan) liest diese Overrides bereits, wirkt also sofort im Plan.
This commit is contained in:
+20
@@ -1720,6 +1720,8 @@ export default function App() {
|
|||||||
const setElementWeight = useStore((s) => s.setElementWeight);
|
const setElementWeight = useStore((s) => s.setElementWeight);
|
||||||
const setElementFill = useStore((s) => s.setElementFill);
|
const setElementFill = useStore((s) => s.setElementFill);
|
||||||
const setElementFillColor = useStore((s) => s.setElementFillColor);
|
const setElementFillColor = useStore((s) => s.setElementFillColor);
|
||||||
|
const setElementForeground = useStore((s) => s.setElementForeground);
|
||||||
|
const setElementBackground = useStore((s) => s.setElementBackground);
|
||||||
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);
|
||||||
@@ -2026,6 +2028,24 @@ export default function App() {
|
|||||||
if (selection?.kind === "drawing2d")
|
if (selection?.kind === "drawing2d")
|
||||||
setElementFillColor(selection.id, color);
|
setElementFillColor(selection.id, color);
|
||||||
},
|
},
|
||||||
|
onSetSelectionForeground: (color) => {
|
||||||
|
// Vordergrund-Override: Wand, Decke oder Drawing2D.
|
||||||
|
if (
|
||||||
|
selection?.kind === "wall" ||
|
||||||
|
selection?.kind === "ceiling" ||
|
||||||
|
selection?.kind === "drawing2d"
|
||||||
|
)
|
||||||
|
setElementForeground(selection.kind, selection.id, color);
|
||||||
|
},
|
||||||
|
onSetSelectionBackground: (color) => {
|
||||||
|
// Hintergrund-Override: Wand, Decke oder Drawing2D.
|
||||||
|
if (
|
||||||
|
selection?.kind === "wall" ||
|
||||||
|
selection?.kind === "ceiling" ||
|
||||||
|
selection?.kind === "drawing2d"
|
||||||
|
)
|
||||||
|
setElementBackground(selection.kind, selection.id, color);
|
||||||
|
},
|
||||||
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);
|
||||||
|
|||||||
@@ -215,6 +215,9 @@ export const de = {
|
|||||||
"attr.fill": "Füllung",
|
"attr.fill": "Füllung",
|
||||||
"attr.fillColor": "Füllfarbe",
|
"attr.fillColor": "Füllfarbe",
|
||||||
"attr.clearFill": "Füllfarbe entfernen",
|
"attr.clearFill": "Füllfarbe entfernen",
|
||||||
|
"attr.foreground": "Vordergrund",
|
||||||
|
"attr.background": "Hintergrund",
|
||||||
|
"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.inheritedFromLayer": "Wände erben die Strichstärke aus der Ebene",
|
||||||
|
|||||||
@@ -214,6 +214,9 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"attr.fill": "Fill",
|
"attr.fill": "Fill",
|
||||||
"attr.fillColor": "Fill color",
|
"attr.fillColor": "Fill color",
|
||||||
"attr.clearFill": "Remove fill color",
|
"attr.clearFill": "Remove fill color",
|
||||||
|
"attr.foreground": "Foreground",
|
||||||
|
"attr.background": "Background",
|
||||||
|
"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.inheritedFromLayer": "Walls inherit line weight from the layer",
|
||||||
|
|||||||
@@ -37,8 +37,51 @@ export function AttributesPanel() {
|
|||||||
// Strichstärke wirkt laut Kontrakt nur auf Drawing2D; Wände erben sie aus der
|
// Strichstärke wirkt laut Kontrakt nur auf Drawing2D; Wände erben sie aus der
|
||||||
// Ebene und sind daher hier nicht editierbar.
|
// Ebene und sind daher hier nicht editierbar.
|
||||||
const weightEditable = isDrawing;
|
const weightEditable = isDrawing;
|
||||||
// Füllung nur bei geschlossener 2D-Form sinnvoll.
|
// Füllschraffur nur bei geschlossener 2D-Form sinnvoll.
|
||||||
const fillEditable = isDrawing && sel.closed === true;
|
const fillEditable = isDrawing && sel.closed === true;
|
||||||
|
// Vordergrund/Hintergrund (Muster-/Füllfarbe) tragen Wand, Decke und
|
||||||
|
// geschlossene 2D-Formen als Override; `undefined` = „Nach System".
|
||||||
|
const pocheEditable =
|
||||||
|
sel.kind === "wall" ||
|
||||||
|
sel.kind === "ceiling" ||
|
||||||
|
(isDrawing && sel.closed === true);
|
||||||
|
|
||||||
|
// Ein Override-Farbfeld mit „Nach System"-Zustand: ist der Wert `undefined`,
|
||||||
|
// zeigt es „Nach System" (leerer Swatch) statt einer Farbe und erbt; ein
|
||||||
|
// Klick auf × stellt zurück auf „Nach System" (schreibt `null` → Feld weg).
|
||||||
|
const overrideColor = (
|
||||||
|
value: string | undefined,
|
||||||
|
onSet: (color: string | null) => void,
|
||||||
|
) => (
|
||||||
|
<span className="attr-val">
|
||||||
|
<label className="attr-color">
|
||||||
|
<span
|
||||||
|
className="attr-color-swatch"
|
||||||
|
style={{ background: pocheEditable && value ? value : "transparent" }}
|
||||||
|
/>
|
||||||
|
<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)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
// 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 „—".
|
||||||
@@ -107,41 +150,17 @@ export function AttributesPanel() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Füllung (nur geschlossene 2D-Form) ────────────────────────── */}
|
{/* ── Füllung (Vordergrund/Hintergrund + Schraffur) ─────────────── */}
|
||||||
|
{/* Vordergrund = Muster-/Schraffurfarbe, Hintergrund = Füllfarbe/Poché.
|
||||||
|
Beide sind Overrides mit Default „Nach System" (erben vom Bauteil);
|
||||||
|
`background` ist der Nachfolger des alten reinen `fillColor`-Feldes. */}
|
||||||
<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">
|
||||||
{/* Vollton-Füllfarbe (getrennt von der Strichfarbe). „keine" = transparent. */}
|
<span className="attr-key">{t("attr.foreground")}</span>
|
||||||
<span className="attr-key">{t("attr.fillColor")}</span>
|
{overrideColor(sel.foreground, host.onSetSelectionForeground)}
|
||||||
<span className="attr-val">
|
|
||||||
<label className="attr-color">
|
<span className="attr-key">{t("attr.background")}</span>
|
||||||
<span
|
{overrideColor(sel.background, host.onSetSelectionBackground)}
|
||||||
className="attr-color-swatch"
|
|
||||||
style={{
|
|
||||||
background: fillEditable && sel.fillColor ? sel.fillColor : "transparent",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={sel.fillColor ?? "#808080"}
|
|
||||||
disabled={!fillEditable}
|
|
||||||
title={fillEditable ? undefined : t("attr.fillNeedsClosed")}
|
|
||||||
onChange={(e) => host.onSetSelectionFillColor(e.target.value)}
|
|
||||||
/>
|
|
||||||
<span className="attr-color-hex">
|
|
||||||
{fillEditable && sel.fillColor ? sel.fillColor : t("attr.none")}
|
|
||||||
</span>
|
|
||||||
{fillEditable && sel.fillColor && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="attr-color-clear"
|
|
||||||
title={t("attr.clearFill")}
|
|
||||||
onClick={() => host.onSetSelectionFillColor(null)}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="attr-key">{t("attr.hatch")}</span>
|
<span className="attr-key">{t("attr.hatch")}</span>
|
||||||
<span className="attr-val">
|
<span className="attr-val">
|
||||||
|
|||||||
@@ -144,6 +144,16 @@ export interface PanelHostValue {
|
|||||||
* `null` entfernt die Füllfarbe (Fläche wieder transparent).
|
* `null` entfernt die Füllfarbe (Fläche wieder transparent).
|
||||||
*/
|
*/
|
||||||
onSetSelectionFillColor: (color: string | null) => void;
|
onSetSelectionFillColor: (color: string | null) => void;
|
||||||
|
/**
|
||||||
|
* Setzt/entfernt den Vordergrund-Override (Muster-/Schraffurfarbe) der
|
||||||
|
* Selektion — Wand, Decke oder geschlossene Drawing2D. `null` = „Nach System".
|
||||||
|
*/
|
||||||
|
onSetSelectionForeground: (color: string | null) => void;
|
||||||
|
/**
|
||||||
|
* Setzt/entfernt den Hintergrund-Override (Füllfarbe/Poché) der Selektion —
|
||||||
|
* Wand, Decke oder geschlossene Drawing2D. `null` = „Nach System".
|
||||||
|
*/
|
||||||
|
onSetSelectionBackground: (color: string | null) => 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).
|
||||||
|
|||||||
@@ -134,6 +134,24 @@ export interface ProjectSlice {
|
|||||||
* `null` entfernt das Feld (Fläche wieder transparent).
|
* `null` entfernt das Feld (Fläche wieder transparent).
|
||||||
*/
|
*/
|
||||||
setElementFillColor: (id: string, fillColor: string | null) => void;
|
setElementFillColor: (id: string, fillColor: string | null) => void;
|
||||||
|
/**
|
||||||
|
* Setzt/entfernt den Vordergrund-Override (Muster-/Schraffurfarbe) — Wand,
|
||||||
|
* Decke oder Drawing2D. `null` = „Nach System" (Feld entfernen → erben).
|
||||||
|
*/
|
||||||
|
setElementForeground: (
|
||||||
|
kind: "wall" | "ceiling" | "drawing2d",
|
||||||
|
id: string,
|
||||||
|
value: string | null,
|
||||||
|
) => void;
|
||||||
|
/**
|
||||||
|
* Setzt/entfernt den Hintergrund-Override (Füllfarbe/Poché) — Wand, Decke
|
||||||
|
* oder Drawing2D. `null` = „Nach System" (Feld entfernen → erben).
|
||||||
|
*/
|
||||||
|
setElementBackground: (
|
||||||
|
kind: "wall" | "ceiling" | "drawing2d",
|
||||||
|
id: string,
|
||||||
|
value: string | null,
|
||||||
|
) => 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).
|
||||||
@@ -723,6 +741,13 @@ export function createProjectSlice(
|
|||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
// Vordergrund-/Hintergrund-Override (Wand/Decke/Drawing2D); `null` löscht
|
||||||
|
// das Feld (→ „Nach System", erbt wieder vom Bauteil/System).
|
||||||
|
setElementForeground: (kind, id, value) =>
|
||||||
|
setProject((p) => setOverrideField(p, kind, id, "foreground", value)),
|
||||||
|
setElementBackground: (kind, id, value) =>
|
||||||
|
setProject((p) => setOverrideField(p, kind, id, "background", value)),
|
||||||
|
|
||||||
// ── 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)),
|
||||||
@@ -879,6 +904,32 @@ function mapStair(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Immutabler Map über eine Decke (per ID); No-op ohne Treffer. */
|
/** Immutabler Map über eine Decke (per ID); No-op ohne Treffer. */
|
||||||
|
/**
|
||||||
|
* Setzt/entfernt einen optionalen Farb-Override (`foreground`/`background`) an
|
||||||
|
* einem Element (Wand, Decke oder Drawing2D). `value === null` entfernt das Feld
|
||||||
|
* (→ „Nach System", erbt wieder). Immutable; unbekannte IDs sind ein No-op.
|
||||||
|
*/
|
||||||
|
function setOverrideField(
|
||||||
|
project: Project,
|
||||||
|
kind: "wall" | "ceiling" | "drawing2d",
|
||||||
|
id: string,
|
||||||
|
field: "foreground" | "background",
|
||||||
|
value: string | null,
|
||||||
|
): Project {
|
||||||
|
const apply = <T extends { id: string }>(item: T): T => {
|
||||||
|
if (item.id !== id) return item;
|
||||||
|
if (value === null) {
|
||||||
|
const { [field]: _omit, ...rest } = item as Record<string, unknown>;
|
||||||
|
return rest as unknown as T;
|
||||||
|
}
|
||||||
|
return { ...item, [field]: value };
|
||||||
|
};
|
||||||
|
if (kind === "wall") return { ...project, walls: project.walls.map(apply) };
|
||||||
|
if (kind === "ceiling")
|
||||||
|
return { ...project, ceilings: (project.ceilings ?? []).map(apply) };
|
||||||
|
return { ...project, drawings2d: project.drawings2d.map(apply) };
|
||||||
|
}
|
||||||
|
|
||||||
function mapCeiling(
|
function mapCeiling(
|
||||||
project: Project,
|
project: Project,
|
||||||
ceilingId: string,
|
ceilingId: string,
|
||||||
|
|||||||
@@ -218,6 +218,17 @@ export interface Selection {
|
|||||||
* keine (transparent). Bei Wänden/Türen `undefined` (nicht anwendbar).
|
* keine (transparent). Bei Wänden/Türen `undefined` (nicht anwendbar).
|
||||||
*/
|
*/
|
||||||
fillColor?: string | null;
|
fillColor?: string | null;
|
||||||
|
/**
|
||||||
|
* Vordergrund-Override (Muster-/Schraffurfarbe) des Elements; `undefined` =
|
||||||
|
* „Nach System" (erbt vom Bauteil/System). Gesetzt bei Wand, Decke und
|
||||||
|
* geschlossener Drawing2D.
|
||||||
|
*/
|
||||||
|
foreground?: string;
|
||||||
|
/**
|
||||||
|
* Hintergrund-Override (Füllfarbe/Poché) des Elements; `undefined` = „Nach
|
||||||
|
* System". Nachfolger von `fillColor`; hat im Plan Vorrang.
|
||||||
|
*/
|
||||||
|
background?: string;
|
||||||
/** 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. */
|
||||||
@@ -342,6 +353,8 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: undefined,
|
fillHatchId: undefined,
|
||||||
fillColor: undefined,
|
fillColor: undefined,
|
||||||
|
foreground: wall.foreground,
|
||||||
|
background: wall.background,
|
||||||
closed: undefined,
|
closed: undefined,
|
||||||
bbox: { minX, minY, maxX, maxY },
|
bbox: { minX, minY, maxX, maxY },
|
||||||
wall: wallInfo,
|
wall: wallInfo,
|
||||||
@@ -386,6 +399,8 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
|||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: undefined,
|
fillHatchId: undefined,
|
||||||
fillColor: undefined,
|
fillColor: undefined,
|
||||||
|
foreground: ceiling.foreground,
|
||||||
|
background: ceiling.background,
|
||||||
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,
|
||||||
@@ -542,6 +557,8 @@ function drawingSelection(project: Project, d: Drawing2D): Selection {
|
|||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: closed ? d.hatchId ?? null : null,
|
fillHatchId: closed ? d.hatchId ?? null : null,
|
||||||
fillColor: closed ? d.fillColor ?? null : null,
|
fillColor: closed ? d.fillColor ?? null : null,
|
||||||
|
foreground: closed ? d.foreground : undefined,
|
||||||
|
background: closed ? d.background : undefined,
|
||||||
closed,
|
closed,
|
||||||
bbox: geomBBox(d.geom),
|
bbox: geomBBox(d.geom),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user