From ee9aeda01e37347c3a3ffada867e09d7f817ebc9 Mon Sep 17 00:00:00 2001 From: Karim Date: Sat, 11 Jul 2026 02:44:56 +0200 Subject: [PATCH] =?UTF-8?q?Ansicht-Fenster=20im=20VW-Detail:=20Fl=C3=BCgel?= =?UTF-8?q?rahmen,=20Sims=20mit=20Tropfkante,=20exakte=20Symbole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nutzer-Referenz: drei ablesbare Konturen (Blendrahmen → Flügelprofil → Glas) je Flügel; Fensterbank als zwei seitlich überstehende Bänder (Bank + Tropfkante, Default an, aus bei sillBoard keine/innen); die DIN-Öffnungs- symbole starten/enden jetzt EXAKT auf den Glasfeld-Ecken (Dreh: Spitze am Bandseiten-Mittel des Glasfelds, Kipp: Spitze oben Mitte — wie VW; vorher sassen die Anker auf der Flügelspanne statt dem Glasfeld). 737/737 grün. --- src/plan/toElevation.ts | 82 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/src/plan/toElevation.ts b/src/plan/toElevation.ts index 3ae4c7d..be1517d 100644 --- a/src/plan/toElevation.ts +++ b/src/plan/toElevation.ts @@ -423,7 +423,11 @@ interface ProjectedOpening { glass: Array>; /** Teilungslinien (Pfosten/Kämpfer) über den Rahmen. */ divisions: Array<[[number, number], [number, number]]>; - /** Öffnungssymbol (DIN-Ansicht): gestrichelte Linien zum Bandseiten-Mittel. */ + /** Flügelrahmen-Rechtecke (Kontur je öffenbarem/festem Flügel). */ + sashRects: Array>; + /** Fensterbank (Sims): Bank + Tropfkante als zwei Bänder unter dem Rahmen. */ + sill: Array>; + /** Öffnungssymbol (DIN-Ansicht): Linien exakt auf den Glasfeld-Ecken. */ symbol: Array<[[number, number], [number, number]]>; /** Tür (Blatt-Fläche statt Glas, ausser Glas-Blatt). */ isDoor: boolean; @@ -476,7 +480,7 @@ function projectOpeningRect( if (depth <= EPS) continue; // pts-Reihenfolge: [unten-von, unten-bis, oben-bis, oben-von] — Basis für // die (fa, fz)-Interpolation der Flügel-/Glasfelder in collectOpenings. - return { pts, depth, glass: [], divisions: [], symbol: [], isDoor: false }; + return { pts, depth, glass: [], divisions: [], sashRects: [], sill: [], symbol: [], isDoor: false }; } return null; } @@ -575,29 +579,57 @@ function collectOpenings(project: Project, frame: ElevationFrame): ProjectedOpen pr.divisions.push([quadAt(pr.pts, fm, fz), quadAt(pr.pts, fm, fieldTop)]); continue; } - pr.glass.push(rect(pr.pts, fa0 + fa * 0.3, fa1 - fa * 0.3, fz, fieldTop)); + // Flügelrahmen (Kontur) + Glas darin eingerückt (VW: Blendrahmen → + // Flügelprofil → Glas als drei ablesbare Konturen). + pr.sashRects.push(rect(pr.pts, fa0, fa1, fz, fieldTop)); + const spw = Math.min(0.06, Math.max(0.03, fw)); // Flügelprofil (m) + const gU = spw / Math.max(w, 1e-6); + const gV = spw / Math.max(h, 1e-6); + const gA = fa0 + gU; + const gB = fa1 - gU; + const gz0 = fz + gV; + const gz1 = fieldTop - gV; + if (gB - gA > 1e-6 && gz1 - gz0 > 1e-6) { + pr.glass.push(rect(pr.pts, gA, gB, gz0, gz1)); + } // Trennlinie zum nächsten Feld (ausser am Rand). if (fa1 < 1 - fa - 1e-6) { pr.divisions.push([quadAt(pr.pts, fa1, fz), quadAt(pr.pts, fa1, fieldTop)]); } - // Öffnungssymbol (DIN-Ansicht, gestrichelt): Spitze am Bandseiten-Mittel. + // Öffnungssymbol (DIN): Linien starten/enden EXAKT auf den Glasfeld- + // Ecken (Nutzer-Report: Anker waren daneben). Dreh: Spitze am + // Bandseiten-Mittel des Glasfelds; Kipp: Spitze oben Mitte (VW). const kind = sd.opening ?? wt?.kind ?? "fest"; if (kind === "dreh" || kind === "drehkipp") { const hingeLeft = (sd.hingeSide ?? "left") === "left"; - const apex = quadAt(pr.pts, hingeLeft ? fa0 : fa1, (fz + fieldTop) / 2); + const apex = quadAt(pr.pts, hingeLeft ? gA : gB, (gz0 + gz1) / 2); + const farU = hingeLeft ? gB : gA; pr.symbol.push( - [quadAt(pr.pts, hingeLeft ? fa1 : fa0, fz), apex], - [quadAt(pr.pts, hingeLeft ? fa1 : fa0, fieldTop), apex], + [quadAt(pr.pts, farU, gz0), apex], + [quadAt(pr.pts, farU, gz1), apex], ); } if (kind === "kipp" || kind === "drehkipp") { - const apex = quadAt(pr.pts, (fa0 + fa1) / 2, fz); + const apex = quadAt(pr.pts, (gA + gB) / 2, gz1); pr.symbol.push( - [quadAt(pr.pts, fa0, fieldTop), apex], - [quadAt(pr.pts, fa1, fieldTop), apex], + [quadAt(pr.pts, gA, gz0), apex], + [quadAt(pr.pts, gB, gz0), apex], ); } } + // Fensterbank (Sims, VW): Bank + Tropfkante als zwei Bänder unter dem + // Rahmen, seitlich leicht überstehend. Default AN (sillBoard fehlt), + // aus bei "keine"/"innen". + const sb = wt?.sillBoard; + if (sb !== "keine" && sb !== "innen") { + const ov1 = 0.02 / Math.max(w, 1e-6); + const ov2 = 0.05 / Math.max(w, 1e-6); + const h1 = 0.035 / Math.max(h, 1e-6); + const h2 = 0.025 / Math.max(h, 1e-6); + pr.sill.push(rect(pr.pts, -ov1, 1 + ov1, -h1, 0)); + pr.sill.push(rect(pr.pts, -ov2, 1 + ov2, -(h1 + h2), -h1)); + } + // Oberlicht: eigenes Glasfeld + Kämpferlinie. if (fTr > 0) { pr.glass.push(rect(pr.pts, fa, 1 - fa, fTr + fz / 2, 1 - fz)); @@ -969,6 +1001,36 @@ export function generateElevationPlan( hatch: NO_HATCH, }, }); + // Sims (Bank + Tropfkante) — weiss gefüllt (verdeckt die Fassade), Kontur. + for (const sRect of op.sill) { + items.push({ + depth: d, + seq: seq++, + prim: { + kind: "polygon", + pts: toVec(sRect), + fill: mono ? "#ffffff" : FRAME_FILL, + stroke: EDGE_INK, + strokeWidthMm: EDGE_MM, + hatch: NO_HATCH, + }, + }); + } + // Flügelrahmen-Konturen (zwischen Blendrahmen und Glas). + for (const sRect of op.sashRects) { + items.push({ + depth: d, + seq: seq++, + prim: { + kind: "polygon", + pts: toVec(sRect), + fill: "none", + stroke: EDGE_INK, + strokeWidthMm: EDGE_MM, + hatch: NO_HATCH, + }, + }); + } for (const g of op.glass) { items.push({ depth: d,