From 39ddd9b501c3706db19a22999d15662bd1e67f50 Mon Sep 17 00:00:00 2001 From: Karim Date: Sun, 5 Jul 2026 21:31:48 +0200 Subject: [PATCH] =?UTF-8?q?Schnitt/Ansicht=20Phase=202:=20Decken-=C3=9Cber?= =?UTF-8?q?kopf-Umriss=20als=20gestrichelte=20Haarlinie=20(=C3=BCber=20Sch?= =?UTF-8?q?nittebene),=20mit=20Test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plan/generatePlan.ts | 21 +++++----- src/plan/generatePlan.viewwall.test.ts | 53 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/plan/generatePlan.ts b/src/plan/generatePlan.ts index 7470bdb..05c9730 100644 --- a/src/plan/generatePlan.ts +++ b/src/plan/generatePlan.ts @@ -98,6 +98,10 @@ const MONO_INK = "#111111"; const LAYER_LINE_MM = 0.02; /** Haarlinie für Tür-/Fenstersymbole (Flügel, Bogen, Rahmen, Glas) in mm. */ const SYMBOL_HAIRLINE_MM = 0.02; +/** Strichmuster (mm Papier) für ÜBERKOPF-Ansichtslinien: Bauteile über der + * Grundriss-Schnittebene (Decken-/Slab-Überstände, Unterzüge) werden nach + * BIM-Konvention gestrichelt gezeichnet (Aufsicht auf ein Bauteil über einem). */ +const OVERHEAD_DASH: number[] = [0.4, 0.25]; /** Stärke der Wand-Umrisslinie je Detailgrad, als Faktor auf die Ebenen-lw. */ const OUTLINE_DETAIL_FACTOR: Record = { grob: 1.6, // dickere Sammellinie @@ -638,13 +642,9 @@ export function generatePlan( for (const ceiling of ceilings) { const greyed = categoryDisplay(ceiling.categoryCode).greyed; const category = catByCode.get(ceiling.categoryCode); - const lwMm = resolveStrokeWeight( - ceiling.strokeWeight, - ceiling.strokeWeightSource, - category, - lwByCode.get(ceiling.categoryCode) ?? WALL_FALLBACK_MM, - ); - addCeilingPoche(primitives, project, ceiling, greyed, detail, lwMm, category, wallFootprints); + // Kein lwMm mehr: der Decken-Umriss ist eine feste Überkopf-Haarlinie + // (gestrichelt), unabhängig von der Kategorie-Strichstärke. + addCeilingPoche(primitives, project, ceiling, greyed, detail, category, wallFootprints); } // Räume (SIA-416-Flächen) dieses Geschosses: transluzente Farbfüllung + Stempel @@ -1904,7 +1904,6 @@ function addCeilingPoche( ceiling: Ceiling, greyed: boolean, detail: DetailLevel, - lwMm: number, category: LayerCategory | undefined, wallFootprints: WallFootprint[], ): void { @@ -1913,7 +1912,6 @@ function addCeilingPoche( const wt = getCeilingType(project, ceiling); const comp = wt.layers.length > 0 ? getComponent(project, wt.layers[0].componentId) : null; const stroke = ceiling.color ?? POCHE_STROKE; - const outlineMm = lwMm * OUTLINE_DETAIL_FACTOR[detail]; const ceilingId = ceiling.id; // Gefüllte Fläche mit der Ansichts-Schraffur: die Decke wird im Grundriss @@ -1974,7 +1972,10 @@ function addCeilingPoche( a: add(a, scale(d, t0)), b: add(a, scale(d, t1)), cls: "ceiling-outline", - weightMm: outlineMm, + // Überkopf-Ansichtslinie (Decke liegt als Slab über der Schnittebene) → + // gestrichelte Haarlinie statt kräftiger Volllinie (BIM-Konvention). + weightMm: LAYER_LINE_MM * LAYER_DETAIL_FACTOR[detail], + dash: OVERHEAD_DASH, color: stroke, greyed, }); diff --git a/src/plan/generatePlan.viewwall.test.ts b/src/plan/generatePlan.viewwall.test.ts index 2e00174..977ada8 100644 --- a/src/plan/generatePlan.viewwall.test.ts +++ b/src/plan/generatePlan.viewwall.test.ts @@ -80,3 +80,56 @@ describe("generatePlan — Schnitt vs. Ansicht nach Schnitthöhe", () => { expect(low.some((pr) => pr.fill !== "none")).toBe(true); }); }); + +// ── Decke über der Schnittebene → gestrichelte Überkopf-Umrisslinie ────────── + +/** Minimalprojekt mit EINER Decke (Slab) ohne Wände → voller Umriss sichtbar. */ +function ceilingProject(): Project { + return { + id: "t", + name: "T", + lineStyles: [{ id: "thin", name: "d", weight: 0.13, color: "#111", dash: null }], + hatches: [{ id: "none", name: "Ohne", pattern: "none", scale: 1, angle: 0, color: "#111" }], + components: [{ id: "a", name: "A", color: "#d8d2c7", hatchId: "none", joinPriority: 10 }], + wallTypes: [{ id: "aw", name: "AW", layers: [{ componentId: "a", thickness: 0.2 }] }], + drawingLevels: [ + { id: "eg", name: "EG", kind: "floor", visible: true, locked: false, floorHeight: 2.6, cutHeight: 1.0, baseElevation: 0 }, + ], + layers: [{ code: "30", name: "Decken", color: "#0a0a0a", lw: 0.5, visible: true, locked: false }], + walls: [], + doors: [], + openings: [], + ceilings: [ + { + id: "C1", + type: "ceiling", + floorId: "eg", + categoryCode: "30", + outline: [ + { x: 0, y: 0 }, + { x: 2, y: 0 }, + { x: 2, y: 2 }, + { x: 0, y: 2 }, + ], + wallTypeId: "aw", + }, + ], + stairs: [], + rooms: [], + drawings2d: [], + context: [], + }; +} + +describe("generatePlan — Decke = gestrichelte Überkopf-Ansicht", () => { + it("zeichnet den freien Decken-Umriss gestrichelt (Überkopf-Konvention)", () => { + const plan = generatePlan(ceilingProject(), "eg", new Set(["30"]), undefined, "mittel"); + const outlines = plan.primitives.filter( + (p): p is Extract => + p.kind === "line" && p.cls === "ceiling-outline", + ); + expect(outlines.length).toBeGreaterThan(0); + // JEDE Decken-Umrisslinie ist gestrichelt (dash gesetzt, nicht null). + expect(outlines.every((p) => Array.isArray(p.dash) && p.dash.length >= 2)).toBe(true); + }); +});