From 0240a23a020cec5d9b56b7a78d06885929ae32b8 Mon Sep 17 00:00:00 2001 From: Karim Date: Sun, 12 Jul 2026 16:20:36 +0200 Subject: [PATCH] =?UTF-8?q?Wand-Poch=C3=A9=20bei=20"grob"=20immer=20vollsc?= =?UTF-8?q?hwarz=20(SIA=20400=20Fig.=2036)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- PENDENZEN.md | 2 + src/plan/generatePlan.ts | 35 +++++---------- src/plan/generatePlan.viewwall.test.ts | 61 ++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/PENDENZEN.md b/PENDENZEN.md index 7727803..db8efd4 100644 --- a/PENDENZEN.md +++ b/PENDENZEN.md @@ -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. 36–38 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. diff --git a/src/plan/generatePlan.ts b/src/plan/generatePlan.ts index 2365923..fc881bb 100644 --- a/src/plan/generatePlan.ts +++ b/src/plan/generatePlan.ts @@ -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 diff --git a/src/plan/generatePlan.viewwall.test.ts b/src/plan/generatePlan.viewwall.test.ts index 977ada8..cbd7e51 100644 --- a/src/plan/generatePlan.viewwall.test.ts +++ b/src/plan/generatePlan.viewwall.test.ts @@ -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, id: string) => + plan.primitives.filter( + (p): p is Extract => 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 {