Wand-Poché bei "grob" immer vollschwarz (SIA 400 Fig. 36)

Bisher war die Füllung bei Detailgrad "grob" nur schwarz, wenn das
dominante Wandbauteil selbst ein "solid"-Hatch-Pattern hatte — bei
mehrschichtigen Wandtypen (Backstein/Dämmung/Verputz) blieb sie weiss.
SIA 400 kennt bei 1:100 aber keine Materialunterscheidung, die Poché ist
immer vollschwarz. Fix + Regressionstest von Hermes/Qwen3 übernommen und
um den fehlenden Test sowie das Aufräumen der jetzt toten
backbonePocheFill-Hilfsfunktion ergänzt.
This commit is contained in:
2026-07-12 16:20:36 +02:00
parent e309e54af7
commit 0240a23a02
3 changed files with 73 additions and 25 deletions
+2
View File
@@ -193,6 +193,8 @@
_Nur jüngste Session; ältere Historie siehe `git log` und HANDOVER-Narrative._
- [x] 2026-07-12 **Fenster-Grundriss grob/mittel/fein nach SIA 400 klar unterschieden** (`13cc6a0`) — Nutzer-Report „mittel und fein sind genau gleich, grob ist viel zu grob" behoben: `windowSymbol` (`geometry/opening.ts`) staffelt jetzt nach SIA 400 Anhang B.9.1 Fig. 3638 statt DIN — grob (1:100) nur eine Glaslinie, mittel (1:50) Blendrahmen + Flügel-Trennlinien + 1 Stulp-Quadrat je Flügelstoss, fein (1:20) zusätzlich verschachtelte Flügelrahmen + Glas-Doppellinie (Isolierverglasung) + 2 Stulp-Quadrate. `generatePlan.ts` reicht `detail` durch, rendert die neuen Stulp-Marken (`window-stulp`) nur im typisierten Pfad (kein Fake-Stoss bei reinem `wingCount`-Altfall). Referenzdoku `docs/research/sia400-fenster-tueren.md` (Fig.-Transkription aus der SIA-400-PDF, lokal excluded). +9 Tests, Suite 753 grün. Visuell per Puppeteer verifiziert. ✅ **Folge-Fix `e309e54`:** Öffnungssymbol-Kommentare in `toElevation.ts`/`toWalls3d.ts` waren fälschlich „DIN" benannt (Grundlage ist SIA 400 B.9.1.3) — reine Doku-Korrektur, Symbol-Geometrie selbst war bereits korrekt (SIA-PDF zeigt nur Sinnbild-Namen, keine Pfeilgrafiken, daher keine Geometrieänderung). **Offen:** Wand-Poché bei „grob" ist nur bei Bauteil-Pattern exakt `"solid"` schwarz (`backbonePocheFill`/`pocheFill`, `generatePlan.ts:1849`+`546`) — sollte bei „grob" IMMER Vollschwarz sein (SIA Fig. 36 kennt bei 1:100 keine Materialunterscheidung); an Hermes-Agent zur Bearbeitung übergeben (Stand 2026-07-12: erster Versuch verworfen, Fehleinschätzung — Task erneut zurückgegeben).
- [x] 2026-07-11 **Schnitt/Ansicht auf VW-Niveau (grosser Block)** — Schnitt ENTSPIEGELT (`983d061`, u-Achse = Betrachter-Rechts, Rust+TS koordiniert, Engine neu gebaut); Kanten geschnittener Bauteile gefiltert + verdeckte Kanten opt-in (`4c4c990`); Wand-Terminierung wirkt im Schnitt auch bei Decken-ANSCHLUSS ±5 cm (`a612ae5`); Dämmschraffur-Orientierung Wand/Decke korrekt (Sprossen quer zur Schicht, empirisch verifiziert, `a612ae5`+`38a4d8c`). Ansicht als LINIENZEICHNUNG (`c02a02c`): weisse Flächen + XOR-Silhouette (xorOutlineEdges — Teilbox-Innenkanten heben sich auf), Fenster mit Blendrahmen→Flügelprofil→Glas, Sims mit Tropfkante, DIN-Symbole exakt auf den Glasfeld-Ecken (`ee9aeda`). **3D-Öffnungen VW-fein** (Merge `31d7aef`): vorstehende Flügelrahmen, DIN-Symbole auf dem Glas (openingPlaneBox-Prismen), 3D-Fensterbank + Tropfkante, Zargen-Umgriff; Staffelung grob/mittel/fein. Display/Print-Umschalter in allen 2D-Darstellungen. **Offen:** Tauri-Visualabnahme 3D; Schnitt-Auswahl-UX (Treffer nur auf der Poché).
- [x] 2026-07-11 **Lineale + VW-Zeichen-Feedback komplett** (`1945fec`, `8f85e13`, `7898a0d`, `242b850`) — Lineale oben/links in allen 2D-Ansichten (Meter-Ticks, Cursor-Marker); Cursor-HUD im Programm-Look mit Live-Echo getippter Werte (oben+unten synchron), Tab-Feldwechsel, Direkt-Tippen; Δx/Δy beim Rechteck; lange Führungslinien + Winkelbogen mit 0°-Referenz.
+10 -25
View File
@@ -553,11 +553,13 @@ export interface SectionCutStyle {
}
/**
* Löst das tragende Bauteil (höchste `joinPriority`) einer Wand auf dieselbe
* Regel wie {@link backbonePocheFill} für die grobe Grundriss-Poché und liefert
* dessen Poché-Füllung + echte Schraffur für die Schnitt-Poché. `null`, wenn Wandtyp
* oder Bauteil nicht auflösbar sind (z. B. verwaistes `wallTypeId`); der
* Aufrufer fällt dann auf die generische Schnitt-Schraffur zurück.
* Löst das tragende Bauteil (höchste `joinPriority`) einer Wand auf und liefert
* dessen Poché-Füllung + echte Schraffur für die Schnitt-Poché (mittel/fein).
* Die grobe Grundriss-Poché (1:100) folgt dieser Bauteil-Regel NICHT mehr
* sie ist immer vollschwarz, unabhängig vom Bauteil-Pattern (SIA 400 Fig. 36,
* s. `addWallPoche`). `null`, wenn Wandtyp oder Bauteil nicht auflösbar sind
* (z. B. verwaistes `wallTypeId`); der Aufrufer fällt dann auf die generische
* Schnitt-Schraffur zurück.
*/
export function resolveWallSectionStyle(project: Project, wall: Wall): SectionCutStyle | null {
try {
@@ -1565,9 +1567,7 @@ function addWallPoche(
const outlineMm = wallLwMm * OUTLINE_DETAIL_FACTOR[detail];
const layerLineMm = LAYER_LINE_MM * LAYER_DETAIL_FACTOR[detail];
// Vereinfachte Füllung (grob): neutrale SIA-Poché des tragenden Bauteils
// (höchste Priorität) — weiß, bzw. schwarz falls dessen Muster "solid" ist.
const simpleFill = backbonePocheFill(project, wt);
// Vereinfachte Füllung (grob): immer schwarze Vollfüllung, keine Schraffur
// Durchgangswand-Aussparungen (Phase 1c): über diese Achsen-Intervalle × Quer-
// Offsetbereiche wird die Nah-Putz-Zone der Durchgangswand echt ausgeschnitten
@@ -1617,10 +1617,11 @@ function addWallPoche(
if (detail === "grob") {
// Eine Sammelfläche über die volle Dicke, ohne Schraffur, dicker Umriss.
// Bei grob: immer Vollschwarz-Füllung gemäß SIA 400 Anhang B.9, Figur 36
out.push({
kind: "polygon",
pts: clippedBand(p1, p2, refOff - total / 2, refOff + total / 2, startCut, endCut),
fill: simpleFill,
fill: HATCH_INK,
stroke,
strokeWidthMm: outlineMm,
hatch: NO_HATCH,
@@ -1840,22 +1841,6 @@ function addWallPoche(
}
}
/**
* Neutrale SIA-Poché-Füllung des tragenden Bauteils (höchste joinPriority) für
* die grobe Sammelfläche: Papier, bzw. Tinte falls dessen Schraffur "solid" ist.
* (Ersetzt die frühere rohe Bauteil-Albedo im Plan zählt die Poché, nicht die
* 3D-Materialfarbe.)
*/
function backbonePocheFill(project: Project, wt: Project["wallTypes"][number]): string {
let best: { pattern: HatchPattern; prio: number } | null = null;
for (const layer of wt.layers) {
const comp = getComponent(project, layer.componentId);
if (!best || comp.joinPriority > best.prio)
best = { pattern: getHatch(project, comp.hatchId).pattern, prio: comp.joinPriority };
}
return best ? pocheFill(best.pattern) : HATCH_PAPER;
}
/**
* Tür-Symbol je Detailgrad:
* grob nur die gerade Türblatt-Linie (die Öffnungs-Lücke kommt aus der
+61
View File
@@ -83,6 +83,67 @@ describe("generatePlan — Schnitt vs. Ansicht nach Schnitthöhe", () => {
// ── Decke über der Schnittebene → gestrichelte Überkopf-Umrisslinie ──────────
// ── grob (1:100) → Wand-Poché IMMER vollschwarz (SIA 400 Fig. 36) ──────────
/** Minimalprojekt mit einer Wand, deren Bauteil ein NICHT-"solid" Hatch-Pattern
* hat (Backstein/diagonal der Normalfall bei mehrschichtigen Wandtypen). */
function nonSolidWallProject(): Project {
const wall: Wall = {
id: "W1",
type: "wall",
floorId: "eg",
categoryCode: "20",
start: { x: 0, y: 0 },
end: { x: 4, y: 0 },
wallTypeId: "aw",
height: 2.6,
};
return {
id: "t",
name: "T",
lineStyles: [{ id: "thin", name: "d", weight: 0.13, color: "#111", dash: null }],
hatches: [{ id: "diag", name: "Diagonal", pattern: "diagonal", scale: 1, angle: 45, color: "#111" }],
components: [{ id: "brick", name: "Backstein", color: "#c9a876", hatchId: "diag", joinPriority: 10 }],
wallTypes: [{ id: "aw", name: "AW", layers: [{ componentId: "brick", thickness: 0.3 }] }],
drawingLevels: [
{ id: "eg", name: "EG", kind: "floor", visible: true, locked: false, floorHeight: 2.6, cutHeight: 1.0, baseElevation: 0 },
],
layers: [{ code: "20", name: "Wände", color: "#0a0a0a", lw: 0.5, visible: true, locked: false }],
walls: [wall],
doors: [],
openings: [],
ceilings: [],
stairs: [],
rooms: [],
drawings2d: [],
context: [],
};
}
describe("generatePlan — grob: Wand-Poché immer vollschwarz (SIA 400 Fig. 36)", () => {
const wallPolys = (plan: ReturnType<typeof generatePlan>, id: string) =>
plan.primitives.filter(
(p): p is Extract<typeof p, { kind: "polygon" }> => p.kind === "polygon" && p.wallId === id,
);
it("grob: Vollschwarz-Füllung auch wenn das Bauteil-Pattern NICHT 'solid' ist (Regression)", () => {
const plan = generatePlan(nonSolidWallProject(), "eg", new Set(["20"]), undefined, "grob");
const polys = wallPolys(plan, "W1");
expect(polys.length).toBeGreaterThan(0);
expect(polys.every((p) => p.fill === "#0f0f0f")).toBe(true);
expect(polys.every((p) => p.hatch.pattern === "none")).toBe(true);
});
it("mittel/fein: Füllung bleibt bauteilfarben (KEIN Vollschwarz-Zwang) — unverändertes Verhalten", () => {
for (const detail of ["mittel", "fein"] as const) {
const plan = generatePlan(nonSolidWallProject(), "eg", new Set(["20"]), undefined, detail);
const polys = wallPolys(plan, "W1");
expect(polys.length).toBeGreaterThan(0);
expect(polys.some((p) => p.fill !== "#0f0f0f")).toBe(true);
}
});
});
/** Minimalprojekt mit EINER Decke (Slab) ohne Wände → voller Umriss sichtbar. */
function ceilingProject(): Project {
return {