From 814e9a8656326245c889dc88a2dc107370724842 Mon Sep 17 00:00:00 2001 From: Karim Date: Sun, 12 Jul 2026 18:51:09 +0200 Subject: [PATCH] =?UTF-8?q?Fenster:=20Fl=C3=BCgelstoss-Bl=C3=B6cke=20separ?= =?UTF-8?q?at=20von=20Laibungs-Enden=20steuerbar=20(sashLine=20statt=20fra?= =?UTF-8?q?meLine)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit windowSymbol trug beide Blocktypen (Laibungs-Enden UND Flügelstoss-Marken) im selben meetingMarks-Array, dadurch übersteuerte frameLine auch die Marke zwischen den Flügeln — Nutzer wollte diese separat regelbar. Aufgeteilt in jambMarks (Laibungs-Enden, zählt zu "Rahmen") und meetingMarks (Flügelstoss, zählt zu "Fenster"/sashLine). --- src/geometry/opening.ts | 24 ++++++++++++++++------ src/plan/generatePlan.ts | 12 +++++++++-- src/plan/generatePlan.windowFields.test.ts | 18 +++++++++++++--- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/geometry/opening.ts b/src/geometry/opening.ts index 90a09d8..bc472d4 100644 --- a/src/geometry/opening.ts +++ b/src/geometry/opening.ts @@ -256,9 +256,18 @@ export interface WindowSymbol { * Flügel-Trennung ein kleines Rechteck-Polygon auf der Glasebene, das den * Anschlag der zwei zusammenstossenden Flügelprofile darstellt. Bei „mittel" * ein Quadrat je Stoss, bei „fein" zwei (die beiden Stulpprofile). „grob": - * leer. Der Aufrufer zeichnet sie als dünne Umriss-Rechtecke. + * leer. Zählt zur "Fenster"-Linien-Kategorie (Flügel/Sprossen), NICHT zum + * Blendrahmen — separat von {@link jambMarks} steuerbar. Der Aufrufer + * zeichnet sie als dünne Umriss-Rechtecke. */ meetingMarks: Vec2[][]; + /** + * Blendrahmen-Querschnittsblöcke an den beiden Laibungs-Enden (ein Block je + * Seite, unabhängig von Flügelanzahl/Detailgrad) — Teil des Blendrahmens, + * zählt zur "Rahmen"-Linien-Kategorie (im Unterschied zu {@link meetingMarks}, + * die an inneren Flügelstössen sitzen und zur "Fenster"-Kategorie zählen). + */ + jambMarks: Vec2[][]; } /** @@ -401,11 +410,12 @@ export function windowSymbol( const mullionLines: [Vec2, Vec2][] = []; const sashFrames: Vec2[][] = []; const meetingMarks: Vec2[][] = []; + const jambMarks: Vec2[][] = []; // grob (1:100): nur der Rahmenkasten + EINE Glaslinie über die ganze Öffnung. if (detail === "grob") { glassLines.push([add(jambStart, scale(n, gMidBase)), add(jambEnd, scale(n, gMidBase))]); - return { frame, glassLines, mullionLines, sashFrames, sashOpeningLines: [], meetingMarks }; + return { frame, glassLines, mullionLines, sashFrames, sashOpeningLines: [], meetingMarks, jambMarks }; } const fein = detail === "fein"; @@ -428,8 +438,10 @@ export function windowSymbol( ]; }; // Laibungs-Pfosten an beiden Enden (Blendrahmen-Querschnitt an der Wand- - // öffnung) — EIN Block je Seite, unabhängig vom Detailgrad. - meetingMarks.push(frameBlock(fw / 2, fw / 2), frameBlock(axisWidth - fw / 2, fw / 2)); + // öffnung) — EIN Block je Seite, unabhängig vom Detailgrad. Teil des + // Blendrahmens (jambMarks), NICHT der Flügelstoss-Marken (meetingMarks) — + // separat steuerbar (Nutzer-Feedback). + jambMarks.push(frameBlock(fw / 2, fw / 2), frameBlock(axisWidth - fw / 2, fw / 2)); // Flügel-Trennlinien (Mittelpfosten) an den inneren Segmentgrenzen + // Stulp-Block: mittel 1, fein 2 (die beiden zusammenstossenden Profile) — @@ -475,7 +487,7 @@ export function windowSymbol( ]); } } - return { frame, glassLines, mullionLines, sashFrames, sashOpeningLines: [], meetingMarks }; + return { frame, glassLines, mullionLines, sashFrames, sashOpeningLines: [], meetingMarks, jambMarks }; } // ── Alt-Pfad (kein Typ auflösbar) ──────────────────────────────────────── @@ -506,5 +518,5 @@ export function windowSymbol( mullionLines.push([add(onAxis, scale(n, pOuter)), add(onAxis, scale(n, pInner))]); } } - return { frame, glassLines, mullionLines, sashFrames: [], sashOpeningLines: [], meetingMarks: [] }; + return { frame, glassLines, mullionLines, sashFrames: [], sashOpeningLines: [], meetingMarks: [], jambMarks: [] }; } diff --git a/src/plan/generatePlan.ts b/src/plan/generatePlan.ts index c612875..fbe924e 100644 --- a/src/plan/generatePlan.ts +++ b/src/plan/generatePlan.ts @@ -2304,13 +2304,21 @@ function addOpeningSymbol( } } } + // Laibungs-Blöcke an den Öffnungsenden (Blendrahmen-Querschnitt) — Teil des + // Blendrahmens, zählt zu "Rahmen". + for (const mark of sym.jambMarks) { + for (let i = 0; i < mark.length; i++) { + out.push({ kind: "line", a: mark[i], b: mark[(i + 1) % mark.length], cls: "window-stulp", weightMm: frameHairline, color: frameColor, greyed, openingId: o.id }); + } + } // Stulp-Mittelquadrat je Flügel-Junction (SIA-Signatur): mittel = EIN Quadrat, // fein = ZWEI anliegende Rechtecke (die beiden Stulp-Stäbe der Flügel) — // bereits tiefenkorrekt (inset-/insetFace-bewusst) in windowSymbol berechnet. - // Zählt als "Rahmen" (Laibungs-/Stossblock des Blendrahmens). + // Sitzt zwischen den Flügeln, zählt zu "Fenster" (separat vom Blendrahmen + // steuerbar — Nutzer-Feedback). for (const mark of sym.meetingMarks) { for (let i = 0; i < mark.length; i++) { - out.push({ kind: "line", a: mark[i], b: mark[(i + 1) % mark.length], cls: "window-stulp", weightMm: frameHairline, color: frameColor, greyed, openingId: o.id }); + out.push({ kind: "line", a: mark[i], b: mark[(i + 1) % mark.length], cls: "window-stulp", weightMm: sashWeight, color: sashColor, greyed, openingId: o.id }); } } // Glaslinien (Anzahl je Detailgrad/Verglasung, s. glassCount). diff --git a/src/plan/generatePlan.windowFields.test.ts b/src/plan/generatePlan.windowFields.test.ts index 28ecc27..70e689f 100644 --- a/src/plan/generatePlan.windowFields.test.ts +++ b/src/plan/generatePlan.windowFields.test.ts @@ -341,10 +341,12 @@ describe("generatePlan — Linien-Übersteuerung je Kategorie (Opening.frameLine for (const l of stulp) expect(l.kind === "line" && l.weightMm).toBeCloseTo(0.02, 6); }); - it("frameLine übersteuert Farbe+Stärke der Stulp-/Laibungsblöcke (window-stulp)", () => { + it("frameLine übersteuert Farbe+Stärke der Laibungs-Blöcke (window-stulp, einflügliges Fenster ohne Flügelstoss)", () => { + // Einflüglig -> nur jambMarks (Laibungs-Enden, "Rahmen"), keine + // Flügelstoss-Blöcke -> eindeutig zuordenbar. const p = project( { ...baseWindow, typeId: "wt1", frameLine: { color: "#ff0000", weight: 0.5 } }, - [twoSashType], + [baseWindowType], ); const stulp = linesOfClass(p, "window-stulp", "mittel"); expect(stulp.length).toBeGreaterThan(0); @@ -354,7 +356,7 @@ describe("generatePlan — Linien-Übersteuerung je Kategorie (Opening.frameLine } }); - it("sashLine übersteuert Farbe+Stärke des Flügelrahmens (window-sash, fein)", () => { + it("sashLine übersteuert Farbe+Stärke des Flügelrahmens (window-sash, fein) UND der Flügelstoss-Blöcke (window-stulp an der Junction) — NICHT die Laibungs-Enden", () => { const p = project( { ...baseWindow, typeId: "wt1", sashLine: { color: "#00ff00", weight: 0.3 } }, [twoSashType], @@ -365,6 +367,16 @@ describe("generatePlan — Linien-Übersteuerung je Kategorie (Opening.frameLine expect(l.kind === "line" && l.color).toBe("#00ff00"); expect(l.kind === "line" && l.weightMm).toBeCloseTo(0.3, 6); } + // Öffnung 1.2 m breit ab x=1.0 -> Junction bei x=1.6 (Mitte), Laibungs- + // Enden bei x≈1.03/2.17 (fw/2 vom Rand). Nur die Junction-Blöcke (Mitte) + // reagieren auf sashLine; die Laibungs-Enden bleiben beim Default. + const stulp = linesOfClass(p, "window-stulp", "mittel"); + const junction = stulp.filter((l) => l.kind === "line" && l.a.x > 1.3 && l.a.x < 1.9); + const jamb = stulp.filter((l) => l.kind === "line" && (l.a.x < 1.3 || l.a.x > 1.9)); + expect(junction.length).toBeGreaterThan(0); + expect(jamb.length).toBeGreaterThan(0); + for (const l of junction) expect(l.kind === "line" && l.color).toBe("#00ff00"); + for (const l of jamb) expect(l.kind === "line" && l.color).toBe("#1a1a1a"); }); it("sillLineStyle übersteuert Farbe+Stärke der Auf-/Untersicht-Linie (window-sill)", () => {