From 1086225f7b85e87c8b3d6b53d8d1bce9265692da Mon Sep 17 00:00:00 2001 From: Karim Date: Fri, 3 Jul 2026 23:36:06 +0200 Subject: [PATCH] =?UTF-8?q?Mehrschichtige=20W=C3=A4nde=20im=20Schnitt=20al?= =?UTF-8?q?s=20Schicht-B=C3=A4nder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Analog zur Decke (splitSlabLayers) wird ein mehrschichtiger Wandtyp im Schnitt nicht mehr mit einer repräsentativen Schraffur über die ganze Dicke gefüllt, sondern in Schicht-Bänder zerlegt — quer zur Dicke entlang u (volle Höhe je Band), jedes Band mit der Schnittschraffur seines Bauteils. Einschicht-Wände laufen unverändert über resolveWallSectionStyle. Seitenorientierung (aussen/innen) vorerst deterministisch layers[0]→uMin; korrekte Orientierung via Wandnormale bleibt Folge-Iteration. --- src/plan/toSection.ts | 77 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/plan/toSection.ts b/src/plan/toSection.ts index 2513933..64b8689 100644 --- a/src/plan/toSection.ts +++ b/src/plan/toSection.ts @@ -16,7 +16,7 @@ // v = absolute Höhe (world.y). Siehe src-tauri/render3d/src/section.rs. import type { Ceiling, DrawingLevel, Project, Wall } from "../model/types"; -import { getCeilingType, getComponent, openingsOfWall } from "../model/types"; +import { getCeilingType, getComponent, getWallType, openingsOfWall } from "../model/types"; import { ceilingVerticalExtent, wallVerticalExtent } from "../model/wall"; import { openingInterval, openingVerticalExtent } from "../geometry/opening"; import { projectToModel3d } from "./toWalls3d"; @@ -213,6 +213,18 @@ function attachCutStyles(output: SectionOutput, project: Project): void { const ref = cp.component; if (ref.kind === "wall") { const owner = walls[ref.index]; + // Wand: bei mehrschichtigem Wandtyp wird das EINE geschnittene Rechteck in + // stehende Teil-Rechtecke je Schicht quer zur Dicke (u) aufgeteilt — das + // vertikale Gegenstück zur Decken-Zerlegung (dort quer zur Höhe v), siehe + // `splitWallLayers`. Einschichtige Wände laufen unverändert über + // `resolveWallSectionStyle` (ein repräsentatives Bauteil). + if (owner) { + const wt = getWallType(project, owner); + if (wt.layers.length > 1) { + next.push(...splitWallLayers(cp, project, wt)); + continue; + } + } const style = owner ? resolveWallSectionStyle(project, owner) : null; if (style) { cp.fill = style.fill; @@ -296,6 +308,69 @@ function splitSlabLayers( return out; } +/** + * Zerlegt das EINE geschnittene Wandrechteck (`cp`, vier Ecken in u/v-Metern) in + * stehende Teil-Rechtecke je Schicht des Wandtyps `wt` — das Wand-Gegenstück zu + * `splitSlabLayers`. Achsen-Spiegelung: eine Wand ist vertikal geschnitten, das + * Schnittrechteck hat die volle HÖHE in v (vMin..vMax) und spannt über die DICKE + * in u (uMin..uMax); die Schichten stapeln quer zur Dicke, also entlang u — im + * Gegensatz zur Decke, die quer zur Höhe (v) geschichtet ist. v bleibt darum je + * Band voll, u wird proportional zu den Schichtdicken zerlegt. Die Modell- + * Schichtdicken werden auf die tatsächlich geschnittene Dicke (uSpan) skaliert. + * Jedes Teil-Rechteck trägt Füllung + Schnitt-Schraffur seines Bauteils; die + * Fugen ergeben sich implizit aus den aneinanderstoßenden Rechtecken. + * + * Reihenfolge/Orientierung: `WallType.layers` ist von einer Wandseite zur anderen + * geordnet. Aus dem hier vorliegenden (u, v)-Rechteck allein lässt sich nicht + * eindeutig ableiten, welche u-Seite der „Aussenseite" (layers[0]) entspricht — + * das erforderte die Schnittebene (u→world) plus Wand-Normale. Daher die + * deterministische Default-Zuordnung layers[0] → uMin, layers[n] → uMax. Keine + * Zufalls-/Reihenfolge-Abhängigkeit. Eine Folge-Iteration kann die Seiten aus + * Wandgeometrie + Schnittebene korrekt orientieren. + * + * Annahme: rechteckiges Schnittpolygon (min/max-Bounding-Box auf `cp.pts`, + * identisch zu `splitSlabLayers`). + */ +function splitWallLayers( + cp: SectionCutPolygon, + project: Project, + wt: ReturnType, +): SectionCutPolygon[] { + const totalT = wt.layers.reduce((s, l) => s + l.thickness, 0); + const us = cp.pts.map((p) => p[0]); + const vs = cp.pts.map((p) => p[1]); + const uMin = Math.min(...us); + const uMax = Math.max(...us); + const vMin = Math.min(...vs); + const vMax = Math.max(...vs); + const uSpan = uMax - uMin; + if (totalT <= 1e-9 || uSpan <= 1e-9) return [cp]; + const scale = uSpan / totalT; + + const out: SectionCutPolygon[] = []; + let acc = 0; // kumulierte Dicke von uMin (layers[0]) nach uMax + for (const layer of wt.layers) { + const comp = getComponent(project, layer.componentId); + const hatch = resolveHatch(project, comp.hatchId); + const uLeft = uMin + acc * scale; + acc += layer.thickness; + const uRight = uMin + acc * scale; + out.push({ + component: cp.component, + color: cp.color, + pts: [ + [uLeft, vMax], + [uRight, vMax], + [uRight, vMin], + [uLeft, vMin], + ], + fill: hatch.pattern === "solid" ? "#000000" : "#ffffff", + hatch, + }); + } + return out; +} + /** * Berechnet den Schnitt/die Ansicht der Ebene `level` gegen das Modell `project` * über den WASM-Extraktor. Asynchron (das render3d-WASM-Modul wird lazy geladen).