// Wand-Resolver: leitet aus den (optionalen) Wand-Attributen die konkreten // geometrischen Größen ab — Versatz der Achse über die Dicke (Referenzlinie) // und die vertikale Ausdehnung (UK..OK). Alle Defaults entsprechen exakt dem // bisherigen Verhalten, sodass bestehende Wände unverändert rendern. // // Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md). import type { Ceiling, Column, Project, Stair, VerticalAnchor, Wall } from "./types"; import { ceilingThickness, getFloor } from "./types"; /** * Versatz der Schichtstapelung entlang der linken Normalen `+n` (leftNormal), * sodass die Achse auf der gewünschten Wandfläche liegt. Die Schichten werden * normalerweise symmetrisch von `−T/2` (außen, +n-Seite) bis `+T/2` (innen) * gestapelt; dieser Versatz wird ZUSÄTZLICH auf jeden Schicht-Offset addiert. * * • "center" → 0 (Schichten symmetrisch um die Achse — Default). * • "left" → +T/2 (Achse auf der +n-/„außen"-Fläche; Wand liegt auf −n). * • "right" → −T/2 (Achse auf der −n-/„innen"-Fläche; Wand liegt auf +n). * * Die Schicht-Offsets laufen von −T/2 (= +n außen) nach +T/2 (= −n innen). * Für "left" soll die −T/2-Kante (außen) auf der Achse liegen → +T/2 addieren, * sodass die Schichten von 0…T (alle auf der −n-Seite) liegen. */ export function wallReferenceOffset( wall: Wall, totalThickness: number, ): number { const ref = wall.referenceLine ?? "center"; const half = totalThickness / 2; if (ref === "left") return +half; if (ref === "right") return -half; return 0; } /** Löst einen VerticalAnchor zu einem absoluten Z (Meter) auf oder `null`. */ function resolveAnchor( project: Project, anchor: VerticalAnchor | undefined, ): number | null { if (!anchor) return null; if (anchor.mode === "custom") return anchor.z; // "floor": baseElevation des gebundenen Geschosses (+ optionalem Offset). const floor = project.drawingLevels.find((z) => z.id === anchor.floorId); if (!floor) return null; return (floor.baseElevation ?? 0) + (anchor.offset ?? 0); } /** * Vertikale Ausdehnung der Wand (absolute Z-Werte in Metern). * • zBottom: aus `wall.bottom`, sonst baseElevation des Wand-Geschosses * (= heutiges Verhalten). * • zTop: aus `wall.top`, sonst `zBottom + wall.height` (= heute). * Sind beide UK/OK ungesetzt, liefert dies exakt `[baseElevation, base+height]`. */ export function wallVerticalExtent( project: Project, wall: Wall, ): { zBottom: number; zTop: number } { // Geschoss-baseElevation als Default-UK (heutiges Verhalten). Fehlt das // Geschoss, 0 als sicherer Rückfall. let floorBase = 0; try { floorBase = getFloor(project, wall.floorId).baseElevation ?? 0; } catch { floorBase = 0; } const zBottom = resolveAnchor(project, wall.bottom) ?? floorBase; const zTop = resolveAnchor(project, wall.top) ?? zBottom + wall.height; return { zBottom, zTop }; } /** * Vertikale Ausdehnung einer Decke (absolute Z-Werte in Metern). * • zTop: aus `ceiling.top`, sonst Oberkante des Geschosses * (baseElevation + floorHeight) — also bündig mit dem Wandkopf. * • zBottom: aus `ceiling.bottom`, sonst zTop − Deckendicke (die Decke * wächst nach UNTEN — heutiges Verhalten). Die `thickness` bleibt bei * gesetztem `bottom` rein informativ (dient nicht mehr der UK-Ableitung). * Fehlt das Geschoss, dient 0 als sicherer Rückfall für die Oberkante. */ export function ceilingVerticalExtent( project: Project, ceiling: Ceiling, ): { zBottom: number; zTop: number } { let floorTop = 0; try { const floor = getFloor(project, ceiling.floorId); floorTop = (floor.baseElevation ?? 0) + (floor.floorHeight ?? 0); } catch { floorTop = 0; } const zTop = resolveAnchor(project, ceiling.top) ?? floorTop; const zBottom = resolveAnchor(project, ceiling.bottom) ?? zTop - ceilingThickness(project, ceiling); return { zBottom, zTop }; } /** * Vertikale Ausdehnung einer Treppe (absolute Z-Werte in Metern). * • zBottom: OKFF (baseElevation) des zugehörigen Geschosses. * • zTop: zBottom + Gesamt-Steighöhe. Fehlt `stair.totalRise`, gilt die * Geschosshöhe (floorHeight) des Geschosses — so steigt die Treppe genau ins * nächste Geschoss (OKFF → OKFF). * Fehlt das Geschoss, dient 0 als sicherer Rückfall für die Unterkante. */ export function stairVerticalExtent( project: Project, stair: Stair, ): { zBottom: number; zTop: number } { let base = 0; let floorHeight = 0; try { const floor = getFloor(project, stair.floorId); base = floor.baseElevation ?? 0; floorHeight = floor.floorHeight ?? 0; } catch { base = 0; floorHeight = 0; } const rise = stair.totalRise != null && stair.totalRise > 0 ? stair.totalRise : floorHeight > 0 ? floorHeight : 2.6; return { zBottom: base, zTop: base + rise }; } /** * Vertikale Ausdehnung einer Stütze (absolute Z-Werte in Metern) — dieselbe * Regel wie {@link wallVerticalExtent}: * • zBottom: aus `column.bottom`, sonst baseElevation des Geschosses. * • zTop: aus `column.top`, sonst `zBottom + column.height`. * Fehlt das Geschoss, dient 0 als sicherer Rückfall für die Unterkante. */ export function columnVerticalExtent( project: Project, column: Column, ): { zBottom: number; zTop: number } { let floorBase = 0; try { floorBase = getFloor(project, column.floorId).baseElevation ?? 0; } catch { floorBase = 0; } const zBottom = resolveAnchor(project, column.bottom) ?? floorBase; const zTop = resolveAnchor(project, column.top) ?? zBottom + column.height; return { zBottom, zTop }; } /** * Das Geschoss DIREKT ÜBER dem Wand-Geschoss (in Dokumentreihenfolge der * "floor"-Ebenen) oder `undefined`, wenn die Wand im obersten Geschoss liegt. * Dient der „Verknüpfung zum oberen Geschoss" (OK an nächstes Geschoss binden). */ export function nextFloorAbove( project: Project, wall: Wall, ): { id: string; name: string } | undefined { const floors = project.drawingLevels.filter((z) => z.kind === "floor"); const idx = floors.findIndex((z) => z.id === wall.floorId); if (idx < 0) return undefined; const above = floors[idx + 1]; return above ? { id: above.id, name: above.name } : undefined; }