Stütze (Column) bekommt dasselbe By-Layer/By-Object-Attributsystem wie
Wand/Decke Nutzer-Wunsch: "generell sollten alle BIM Teile also 3D die weiteren Attribute haben." Untersucht: Dach/Treppe/Raum/Öffnung haben HEUTE keine Poché-Füllung/Schraffur im Grundriss (reine Linien-/Symbol- Darstellung, bei Raum sogar bewusst so entschieden -- "keine Farbwäsche im Plan"); das Attribut-System dort anzuschliessen würde neue Poché-/Schraffur-Fähigkeiten erfordern, die es dort noch gar nicht gibt. Stütze dagegen hat bereits eine Poché UND nutzte bereits dieselben Resolver wie Wand/Decke (nur mit fest verdrahtetem override=source=undefined) -- Nutzer-Entscheidung nach Rückfrage: nur Stütze jetzt fertig verdrahten, der Rest bleibt vorerst unverändert. model/types.ts: Column bekommt dieselben acht Felder wie Wall (foreground/background/hatchId/strokeWeight × je einer *Source). plan/generatePlan/structures.ts (addColumnPoche): resolveHatchId/ resolveForeground lesen jetzt column.hatchId/column.hatchSource bzw. column.foreground/column.foregroundSource statt hartcodiertem undefined; neuer resolveBackground-Aufruf lässt einen expliziten Hintergrund-Override vor die neutrale SIA-Poché (Tinte/Papier je nach Schraffur-Muster) treten -- exakt das addWallPoche-Muster (bgOverride ?? neutrale Poché). plan/generatePlan.ts: die Strichstärke der Stütze lief bisher IMMER mit hartcodiertem override=source=undefined durch resolveStrokeWeight -- liest jetzt column.strokeWeight/column.strokeWeightSource. state/selectionInfo.ts (columnSelection): effectiveForeground/ effectiveBackground/effectiveHatchId analog wallSelection (Bauteil- Repräsentant ist hier `column.componentId` statt der äussersten Schicht); weightMm war bisher hart auf WALL_FALLBACK_MM gesetzt, läuft jetzt korrekt durch resolveStrokeWeight. panels/AttributesPanel.tsx: weightEditable/fillEditable/pocheEditable um `sel.kind === "column"` erweitert -- Stütze zeigt jetzt dieselben vier Attribut-Zeilen mit vollem 3-Quellen-Dropdown (Nach Ebene/Nach Bauteil/eigener Wert) wie Wand/Decke. +4 Tests (structures.column.test.ts: Default bleibt Bauteil-Schraffur, Hintergrund-Override greift, hatchSource "layer" nutzt die Kategorie- Schraffur, expliziter hatchId-Override gewinnt). tsc/vitest 938/938 grün.
This commit is contained in:
@@ -1721,6 +1721,36 @@ export interface Column {
|
|||||||
* Kategorie-Farbe.
|
* Kategorie-Farbe.
|
||||||
*/
|
*/
|
||||||
color?: string;
|
color?: string;
|
||||||
|
/**
|
||||||
|
* Attribut-Override der Muster-/Schraffurfarbe (Vordergrund) DIESER Stütze.
|
||||||
|
* `undefined` = „Nach System" (erben → Component.foreground), analog `Wall.
|
||||||
|
* foreground`.
|
||||||
|
*/
|
||||||
|
foreground?: string;
|
||||||
|
/**
|
||||||
|
* Attribut-Override der Füllfarbe (Hintergrund/Poché) DIESER Stütze.
|
||||||
|
* `undefined` = „Nach System" (neutrale SIA-Poché HATCH_INK/HATCH_PAPER je
|
||||||
|
* nach Schraffur-Muster), analog `Wall.background`.
|
||||||
|
*/
|
||||||
|
background?: string;
|
||||||
|
/**
|
||||||
|
* Attribut-Override der Strichstärke (mm Papier) DIESER Stütze. `undefined`
|
||||||
|
* = „Nach System" (erben → `strokeWeightSource`), analog `Wall.strokeWeight`.
|
||||||
|
*/
|
||||||
|
strokeWeight?: number;
|
||||||
|
/**
|
||||||
|
* Attribut-Override der Schnitt-Schraffur (Hatch Manager) DIESER Stütze.
|
||||||
|
* `undefined` = „Nach System" (erben → `hatchSource`), analog `Wall.hatchId`.
|
||||||
|
*/
|
||||||
|
hatchId?: string;
|
||||||
|
/** Quelle des Vordergrunds, wenn kein explizites `foreground` gesetzt ist — analog `Wall.foregroundSource`. */
|
||||||
|
foregroundSource?: AttributeSource;
|
||||||
|
/** Quelle des Hintergrunds, analog {@link Column.foregroundSource}. */
|
||||||
|
backgroundSource?: AttributeSource;
|
||||||
|
/** Quelle der Strichstärke, analog {@link Column.foregroundSource}. */
|
||||||
|
strokeWeightSource?: AttributeSource;
|
||||||
|
/** Quelle der Schraffur, analog {@link Column.foregroundSource}. */
|
||||||
|
hatchSource?: AttributeSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Kontext-Geometrie (importiert / abgeleitet, NICHT semantisch) ───────────
|
// ── Kontext-Geometrie (importiert / abgeleitet, NICHT semantisch) ───────────
|
||||||
|
|||||||
@@ -121,29 +121,36 @@ export function AttributesPanel() {
|
|||||||
// sind IMMER ein eigener Wert (kein 3-Quellen-Dropdown, keine Füllung/
|
// sind IMMER ein eigener Wert (kein 3-Quellen-Dropdown, keine Füllung/
|
||||||
// Schraffur/Masse — s. Ausblendungen weiter unten).
|
// Schraffur/Masse — s. Ausblendungen weiter unten).
|
||||||
const isAnnotation = sel.kind === "layoutAnnotation";
|
const isAnnotation = sel.kind === "layoutAnnotation";
|
||||||
// Strichstärke: Wand/Decke/Drawing2D tragen einen eigenen Override
|
// Strichstärke: Wand/Decke/Stütze/Drawing2D tragen einen eigenen Override
|
||||||
// (`strokeWeight`/`weightMm`) + Quellen-Dropdown; andere Elementarten erben
|
// (`strokeWeight`/`weightMm`) + Quellen-Dropdown; andere Elementarten erben
|
||||||
// stets aus der Kategorie (kein Setter im Kontrakt → nicht editierbar). Bei
|
// stets aus der Kategorie (kein Setter im Kontrakt → nicht editierbar). Bei
|
||||||
// Annotationen nur Linie/Rechteck (Text/Bild kennen keine Strichstärke).
|
// Annotationen nur Linie/Rechteck (Text/Bild kennen keine Strichstärke).
|
||||||
const weightEditable =
|
const weightEditable =
|
||||||
sel.kind === "wall" ||
|
sel.kind === "wall" ||
|
||||||
sel.kind === "ceiling" ||
|
sel.kind === "ceiling" ||
|
||||||
|
sel.kind === "column" ||
|
||||||
isDrawing ||
|
isDrawing ||
|
||||||
(isAnnotation && (sel.annotKind === "line" || sel.annotKind === "rect"));
|
(isAnnotation && (sel.annotKind === "line" || sel.annotKind === "rect"));
|
||||||
// Schraffur: Wand/Decke (Schnitt-/Ansichts-Schraffur) oder geschlossene 2D-Form.
|
// Schraffur: Wand/Decke/Stütze (Schnitt-/Ansichts-Schraffur) oder
|
||||||
|
// geschlossene 2D-Form.
|
||||||
const fillEditable =
|
const fillEditable =
|
||||||
sel.kind === "wall" || sel.kind === "ceiling" || (isDrawing && sel.closed === true);
|
sel.kind === "wall" ||
|
||||||
// Vordergrund/Hintergrund (Muster-/Füllfarbe) tragen Wand, Decke und
|
sel.kind === "ceiling" ||
|
||||||
|
sel.kind === "column" ||
|
||||||
|
(isDrawing && sel.closed === true);
|
||||||
|
// Vordergrund/Hintergrund (Muster-/Füllfarbe) tragen Wand, Decke, Stütze und
|
||||||
// geschlossene 2D-Formen als Override; `undefined` = „Nach System".
|
// geschlossene 2D-Formen als Override; `undefined` = „Nach System".
|
||||||
const pocheEditable =
|
const pocheEditable =
|
||||||
sel.kind === "wall" ||
|
sel.kind === "wall" ||
|
||||||
sel.kind === "ceiling" ||
|
sel.kind === "ceiling" ||
|
||||||
|
sel.kind === "column" ||
|
||||||
(isDrawing && sel.closed === true);
|
(isDrawing && sel.closed === true);
|
||||||
|
|
||||||
// Ein Drawing2D hat KEIN eigenes Bauteil (Component) — „Nach Bauteil" wäre
|
// Ein Drawing2D hat KEIN eigenes Bauteil (Component) — „Nach Bauteil" wäre
|
||||||
// dort bedeutungslos (resolveForeground & Co. fallen dann auf gar keinen
|
// dort bedeutungslos (resolveForeground & Co. fallen dann auf gar keinen
|
||||||
// Wert zurück, s. selectionInfo.ts `noComponent`). Nur Wand/Decke bieten
|
// Wert zurück, s. selectionInfo.ts `noComponent`). Wand/Decke/Stütze haben
|
||||||
// die Option daher an.
|
// alle ein echtes Bauteil (Schicht-Component bzw. `Column.componentId`) und
|
||||||
|
// bieten die Option daher an.
|
||||||
const allowObjectSource = !isDrawing;
|
const allowObjectSource = !isDrawing;
|
||||||
|
|
||||||
// Der Quellen-Dropdown (Nach Ebene/Nach Bauteil/eigener Wert), gemeinsam für
|
// Der Quellen-Dropdown (Nach Ebene/Nach Bauteil/eigener Wert), gemeinsam für
|
||||||
|
|||||||
@@ -437,7 +437,13 @@ export function generatePlan(
|
|||||||
const category = catByCode.get(column.categoryCode);
|
const category = catByCode.get(column.categoryCode);
|
||||||
const ov = overrideFor(column.categoryCode, columnLabel(column));
|
const ov = overrideFor(column.categoryCode, columnLabel(column));
|
||||||
const lwMm =
|
const lwMm =
|
||||||
ov.lineweight ?? resolveStrokeWeight(undefined, undefined, category, lwByCode.get(column.categoryCode) ?? WALL_FALLBACK_MM);
|
ov.lineweight ??
|
||||||
|
resolveStrokeWeight(
|
||||||
|
column.strokeWeight,
|
||||||
|
column.strokeWeightSource,
|
||||||
|
category,
|
||||||
|
lwByCode.get(column.categoryCode) ?? WALL_FALLBACK_MM,
|
||||||
|
);
|
||||||
const effCol = ov.color !== undefined ? { ...column, color: ov.color } : column;
|
const effCol = ov.color !== undefined ? { ...column, color: ov.color } : column;
|
||||||
addColumnPoche(primitives, project, effCol, greyed, lwMm, category);
|
addColumnPoche(primitives, project, effCol, greyed, lwMm, category);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* `addColumnPoche` — By-Layer/By-Object-Override für Stützen (Nutzer-Wunsch:
|
||||||
|
* "generell sollten alle BIM Teile ... die weiteren Attribute haben" — Stütze
|
||||||
|
* hat bereits eine Poché UND nutzt dieselben Resolver wie Wand/Decke, daher
|
||||||
|
* hier zuerst fertig verdrahtet). Regressionsschutz: die neuen
|
||||||
|
* foreground/background/hatchId/strokeWeight(+Source)-Felder auf `Column`
|
||||||
|
* müssen tatsächlich bis in die gerenderte Poché durchschlagen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { addColumnPoche } from "./structures";
|
||||||
|
import type { Column, Component, LayerCategory, Project } from "../../model/types";
|
||||||
|
import type { Primitive } from "./shared";
|
||||||
|
|
||||||
|
const comp: Component = {
|
||||||
|
id: "beton",
|
||||||
|
name: "Beton",
|
||||||
|
color: "#888888",
|
||||||
|
hatchId: "sia-concrete",
|
||||||
|
joinPriority: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const category: LayerCategory = {
|
||||||
|
code: "50",
|
||||||
|
name: "Tragwerk",
|
||||||
|
color: "#333333",
|
||||||
|
lw: 0.35,
|
||||||
|
visible: true,
|
||||||
|
locked: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function project(overrides: Partial<Project> = {}): Project {
|
||||||
|
return {
|
||||||
|
id: "t",
|
||||||
|
name: "T",
|
||||||
|
lineStyles: [],
|
||||||
|
hatches: [
|
||||||
|
{ id: "sia-concrete", name: "Beton", pattern: "solid" },
|
||||||
|
{ id: "sia-other", name: "Sonstiges", pattern: "none" },
|
||||||
|
] as unknown as Project["hatches"],
|
||||||
|
components: [comp],
|
||||||
|
wallTypes: [],
|
||||||
|
drawingLevels: [],
|
||||||
|
layers: [category],
|
||||||
|
walls: [],
|
||||||
|
doors: [],
|
||||||
|
openings: [],
|
||||||
|
ceilings: [],
|
||||||
|
stairs: [],
|
||||||
|
rooms: [],
|
||||||
|
drawings2d: [],
|
||||||
|
context: [],
|
||||||
|
columns: [],
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function column(overrides: Partial<Column> = {}): Column {
|
||||||
|
return {
|
||||||
|
id: "col1",
|
||||||
|
type: "column",
|
||||||
|
floorId: "eg",
|
||||||
|
categoryCode: "50",
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
profile: { kind: "rect", width: 0.3, depth: 0.3 },
|
||||||
|
rotation: 0,
|
||||||
|
height: 2.6,
|
||||||
|
componentId: "beton",
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function polygonOf(out: Primitive[]): Extract<Primitive, { kind: "polygon" }> {
|
||||||
|
const p = out.find((x) => x.kind === "polygon");
|
||||||
|
if (!p || p.kind !== "polygon") throw new Error("kein Poché-Polygon gefunden");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("addColumnPoche — Attribut-Override greift", () => {
|
||||||
|
it("ohne Override: Bauteil-Schraffur/-Farbe bestimmen die Poché (Default 'Nach Bauteil')", () => {
|
||||||
|
const out: Primitive[] = [];
|
||||||
|
addColumnPoche(out, project(), column(), false, 0.35, category);
|
||||||
|
const poly = polygonOf(out);
|
||||||
|
expect(poly.hatch.pattern).toBe("solid");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mit explizitem background-Override: die Poché-Füllung wird die Override-Farbe", () => {
|
||||||
|
const out: Primitive[] = [];
|
||||||
|
addColumnPoche(out, project(), column({ background: "#ff00ff" }), false, 0.35, category);
|
||||||
|
expect(polygonOf(out).fill).toBe("#ff00ff");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mit hatchSource:'layer': die Kategorie-Schraffur wird verwendet, nicht die Bauteil-Schraffur", () => {
|
||||||
|
const out: Primitive[] = [];
|
||||||
|
const catWithHatch: LayerCategory = { ...category, hatch: "sia-other" };
|
||||||
|
addColumnPoche(
|
||||||
|
out,
|
||||||
|
project({ layers: [catWithHatch] }),
|
||||||
|
column({ hatchSource: "layer" }),
|
||||||
|
false,
|
||||||
|
0.35,
|
||||||
|
catWithHatch,
|
||||||
|
);
|
||||||
|
expect(polygonOf(out).hatch.pattern).toBe("none");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("mit explizitem hatchId-Override: gewinnt gegenüber Bauteil UND Kategorie", () => {
|
||||||
|
const out: Primitive[] = [];
|
||||||
|
addColumnPoche(
|
||||||
|
out,
|
||||||
|
project(),
|
||||||
|
column({ hatchId: "sia-other" }),
|
||||||
|
false,
|
||||||
|
0.35,
|
||||||
|
category,
|
||||||
|
);
|
||||||
|
expect(polygonOf(out).hatch.pattern).toBe("none");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
MONO_INK,
|
MONO_INK,
|
||||||
NO_HATCH,
|
NO_HATCH,
|
||||||
SYMBOL_HAIRLINE_MM,
|
SYMBOL_HAIRLINE_MM,
|
||||||
|
resolveBackground,
|
||||||
resolveForeground,
|
resolveForeground,
|
||||||
resolveHatch,
|
resolveHatch,
|
||||||
resolveHatchId,
|
resolveHatchId,
|
||||||
@@ -67,15 +68,19 @@ export function addColumnPoche(
|
|||||||
try {
|
try {
|
||||||
const comp = getComponent(project, column.componentId);
|
const comp = getComponent(project, column.componentId);
|
||||||
const hatchId =
|
const hatchId =
|
||||||
resolveHatchId(undefined, undefined, category, comp.hatchId) ?? comp.hatchId;
|
resolveHatchId(column.hatchId, column.hatchSource, category, comp.hatchId) ?? comp.hatchId;
|
||||||
const resolved = resolveHatch(
|
const resolved = resolveHatch(
|
||||||
project,
|
project,
|
||||||
hatchId,
|
hatchId,
|
||||||
undefined,
|
undefined,
|
||||||
resolveForeground(comp, undefined, category, undefined),
|
resolveForeground(comp, column.foreground, category, column.foregroundSource),
|
||||||
);
|
);
|
||||||
hatch = resolved;
|
hatch = resolved;
|
||||||
fill = resolved.pattern === "solid" ? HATCH_INK : HATCH_PAPER;
|
// SIA-Poché: neutraler Hintergrund (Tinte/Papier je nach Muster) statt
|
||||||
|
// Bauteil-Albedo, es sei denn ein expliziter Hintergrund-Override greift
|
||||||
|
// (analog addWallPoche: bgOverride ?? neutrale Poché).
|
||||||
|
const bgOverride = resolveBackground(comp, column.background, category, column.backgroundSource);
|
||||||
|
fill = bgOverride ?? (resolved.pattern === "solid" ? HATCH_INK : HATCH_PAPER);
|
||||||
} catch {
|
} catch {
|
||||||
// Bauteil nicht auflösbar → neutrale Poché (Fallback bleibt bestehen).
|
// Bauteil nicht auflösbar → neutrale Poché (Fallback bleibt bestehen).
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,12 @@ 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 {
|
||||||
|
resolveForeground,
|
||||||
|
resolveBackground,
|
||||||
|
resolveHatchId,
|
||||||
|
resolveStrokeWeight,
|
||||||
|
} 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";
|
||||||
@@ -985,13 +990,42 @@ function columnSelection(project: Project, col: Column): Selection {
|
|||||||
zBottom,
|
zBottom,
|
||||||
zTop,
|
zTop,
|
||||||
};
|
};
|
||||||
|
// Effektive Vordergrund-/Hintergrund-/Schraffur-/Strichstärke-Vorschau — s.
|
||||||
|
// Kommentar in wallSelection (dieselbe Kette, das zugewiesene Bauteil als
|
||||||
|
// Repräsentant statt der äussersten Schicht, da Column nur EIN componentId
|
||||||
|
// trägt statt eines geschichteten Aufbaus).
|
||||||
|
// `getComponent` wirft bei unbekannter ID (z. B. gelöschtes Bauteil) — die
|
||||||
|
// Panel-Vorschau soll dabei nicht abstürzen (analog dem try/catch in
|
||||||
|
// structures.ts addColumnPoche), einfach auf "kein Bauteil" zurückfallen.
|
||||||
|
const comp = col.componentId
|
||||||
|
? project.components.find((c) => c.id === col.componentId)
|
||||||
|
: undefined;
|
||||||
|
const effectiveForeground = comp
|
||||||
|
? resolveForeground(comp, col.foreground, category, col.foregroundSource)
|
||||||
|
: col.foreground;
|
||||||
|
const effectiveBackground = comp
|
||||||
|
? resolveBackground(comp, col.background, category, col.backgroundSource)
|
||||||
|
: col.background;
|
||||||
|
const effectiveHatchId = resolveHatchId(col.hatchId, col.hatchSource, category, comp?.hatchId);
|
||||||
|
const weightMm = resolveStrokeWeight(col.strokeWeight, col.strokeWeightSource, category, WALL_FALLBACK_MM);
|
||||||
return {
|
return {
|
||||||
kind: "column",
|
kind: "column",
|
||||||
id: col.id,
|
id: col.id,
|
||||||
categoryCode: col.categoryCode,
|
categoryCode: col.categoryCode,
|
||||||
floorId: col.floorId,
|
floorId: col.floorId,
|
||||||
color: col.color ?? category?.color ?? COLUMN_COLOR,
|
color: col.color ?? category?.color ?? COLUMN_COLOR,
|
||||||
weightMm: WALL_FALLBACK_MM,
|
weightMm,
|
||||||
|
fillHatchId: col.hatchId,
|
||||||
|
foreground: col.foreground,
|
||||||
|
background: col.background,
|
||||||
|
foregroundSource: col.foregroundSource,
|
||||||
|
backgroundSource: col.backgroundSource,
|
||||||
|
effectiveForeground,
|
||||||
|
effectiveBackground,
|
||||||
|
effectiveHatchId,
|
||||||
|
strokeWeightOverride: col.strokeWeight,
|
||||||
|
strokeWeightSource: col.strokeWeightSource,
|
||||||
|
hatchSource: col.hatchSource,
|
||||||
bbox: { minX, minY, maxX, maxY },
|
bbox: { minX, minY, maxX, maxY },
|
||||||
column: info,
|
column: info,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user