From 6c473558ef81c17c3ffb97f7b83e6c81bb264395 Mon Sep 17 00:00:00 2001 From: Karim Date: Sat, 4 Jul 2026 14:44:36 +0200 Subject: [PATCH] Schnitt: materialspezifische SIA-Schraffuren wiederhergestellt de7a26b lieferte projectToModel3d pro SCHICHT eine eigene Voll-Box; der Schnitt bekam damit segments*layers Cut-Polygone, waehrend wallSegmentOwners weiter nur oeffnungsbasiert segmentiert -> Index- Versatz, falscher Owner, splitWallLayers zerteilte bereits einschichtige Baender erneut -> einheitliches Diagonal-Muster statt SIA-Muster. Fix: Model3dOptions { layeredWalls } an projectToModel3d (Default true, 3D-Viewer unveraendert); computeSection ruft mit layeredWalls:false -> eine Voll-Box pro Wand, Owner-Mapping wieder 1:1, splitWallLayers erzeugt die per-Material-Baender (Daemmung-Haarlinien, Backstein- Diagonale, Beton-Kreuz) wie im Grundriss. 216 Tests gruen. --- src/plan/toSection.ts | 14 ++++++++++---- src/plan/toWalls3d.ts | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/plan/toSection.ts b/src/plan/toSection.ts index d86edc2..33f693c 100644 --- a/src/plan/toSection.ts +++ b/src/plan/toSection.ts @@ -5,9 +5,11 @@ // 1. Aus einer Zeichnungsebene (DrawingLevel, kind "section"/"elevation") wird // eine Schnittebene abgeleitet — die Grundriss-Schnittlinie `linePoints` // plus `directionSign` (Blickrichtung). -// 2. Das Projekt wird über `projectToModel3d` (dieselbe Wand-/Decken-Zerlegung -// wie der 3D-Viewport) zu Wänden + Decken geflacht und als JSON an -// `cut_section_json` gereicht. +// 2. Das Projekt wird über `projectToModel3d({ layeredWalls: false })` zu +// Wänden + Decken geflacht und als JSON an `cut_section_json` gereicht — +// pro Wand EINE Voll-Box (der 3D-Viewport bekommt dagegen je Materiallage +// eine Box). So bleiben die Cut-Polygone 1:1 zu `wallSegmentOwners`, und +// die per-Material-Schnitt-Schraffur macht `splitWallLayers` TS-seitig. // 3. Das zurückgegebene `SectionOutput` (Cut-Polygone + sichtbare/verdeckte // Kanten, alles in (u, v)-Metern) wird in `generateSectionPlan` // (generatePlan.ts) zu Plan-Primitiven übersetzt. @@ -624,7 +626,11 @@ export async function computeSection( "render3d-WASM ohne cut_section_json — pkg3d neu bauen (npm run build:engine3d)", ); } - const modelJson = JSON.stringify(projectToModel3d(project)); + // Schnitt-Eingabe: EINE Voll-Box pro Wand (layeredWalls:false), damit die + // Cut-Polygone 1:1 zu `wallSegmentOwners` (Öffnungs-Segmentierung) liegen und + // `splitWallLayers` die materialspezifischen Schnitt-Bänder korrekt aus dem + // EINEN Wandrechteck erzeugt. Der 3D-Viewer bekommt weiter per-Schicht-Boxen. + const modelJson = JSON.stringify(projectToModel3d(project, { layeredWalls: false })); const json = mod.cut_section_json( modelJson, plane.point[0], diff --git a/src/plan/toWalls3d.ts b/src/plan/toWalls3d.ts index 1614ea5..5549379 100644 --- a/src/plan/toWalls3d.ts +++ b/src/plan/toWalls3d.ts @@ -185,13 +185,21 @@ interface WallBand { * WallType / keine Lagen ⇒ EIN Vollkörper-Band über die volle Dicke im * neutralen Grundton (heutiges Fallback-Verhalten). */ -function resolveWallBands(project: Project, wall: Wall): WallBand[] { +function resolveWallBands(project: Project, wall: Wall, layered = true): WallBand[] { try { const wt = getWallType(project, wall); if (wt.layers.length > 0) { // Gesamtdicke = Summe der Lagen (konsistent mit den Offsets); wenn diese // von wallTypeThickness abwiche, gewinnt die Lagen-Summe (keine Lücken). const total = wt.layers.reduce((s, l) => s + l.thickness, 0); + // Schnitt-Eingabe (layered=false): EINE Voll-Box über die ganze Dicke statt + // je Lage eine Box. So bleibt die Emission 1:1 Wand→Rechteck — passend zu + // `wallSegmentOwners`/`splitWallLayers` in toSection.ts, die die Schicht- + // Zerlegung TS-seitig aus diesem einen Rechteck machen. (Der 3D-Viewer ruft + // mit Default layered=true auf und bekommt unverändert die Schicht-Bänder.) + if (!layered) { + return [{ thickness: total, color: dominantLayerColor(wt.layers, project, WALL_RGB), offset: 0 }]; + } const bands: WallBand[] = []; let prefix = 0; for (const l of wt.layers) { @@ -415,8 +423,8 @@ function trimWallTopForCeilings( } /** Emittiert die Teilquader EINER Wand (mit oder ohne Öffnungen). */ -function emitWall(out: RWall[], project: Project, wall: Wall): void { - const bands = resolveWallBands(project, wall); +function emitWall(out: RWall[], project: Project, wall: Wall, layered = true): void { + const bands = resolveWallBands(project, wall, layered); const { zBottom, zTop: rawZTop } = wallVerticalExtent(project, wall); const zTop = trimWallTopForCeilings(project, wall, zBottom, rawZTop); const axisLen = Math.hypot(wall.end.x - wall.start.x, wall.end.y - wall.start.y); @@ -608,16 +616,31 @@ function emitOpeningGlass(project: Project): RMesh[] { * Nur die Wände (Rückwärts-Kompatibilität / bestehende Aufrufer). Zerlegt Wände * mit Öffnungen in Teilquader. */ -export function projectToWalls3d(project: Project): RWall[] { +export function projectToWalls3d(project: Project, layered = true): RWall[] { const out: RWall[] = []; - for (const w of project.walls) emitWall(out, project, w); + for (const w of project.walls) emitWall(out, project, w, layered); return out; } +/** + * Optionen für {@link projectToModel3d}. `layeredWalls` steuert, ob eine + * mehrschichtige Wand als eigene Box je Materiallage (Default, 3D-Viewer) oder + * als EINE Voll-Box über die ganze Dicke emittiert wird. Der Schnitt-Pfad + * (`computeSection`) setzt `false`, damit die Cut-Polygone 1:1 zur Wand liegen + * (die Schicht-Zerlegung macht dort `splitWallLayers` TS-seitig) — sonst + * verschöben die per-Lage vervielfachten Boxen die `ComponentRef.index` + * gegenüber `wallSegmentOwners` und die materialspezifische Schnitt-Schraffur + * ginge verloren. + */ +export interface Model3dOptions { + layeredWalls?: boolean; +} + /** Das volle 3D-Modell: Wände (mit Öffnungen) + Deckenplatten + Kontext-Meshes. */ -export function projectToModel3d(project: Project): RModel3d { +export function projectToModel3d(project: Project, opts: Model3dOptions = {}): RModel3d { + const layered = opts.layeredWalls ?? true; return { - walls: projectToWalls3d(project), + walls: projectToWalls3d(project, layered), slabs: emitSlabs(project), // Kontext-Meshes (Terrain/Import) + Fenster-Glasscheiben (eigenes Mesh je // Fensteröffnung) — pickGeometry/Highlight nutzen `meshes` bewusst NICHT,