Akkumulierten grünen Arbeitsstand landen (Basis für Weiterarbeit)

Bündelt den über mehrere Sessions gewachsenen, uncommitteten Stand in
einem Basis-Commit, damit Folge-Features isoliert darauf aufsetzen.
Verifikation: tsc --noEmit sauber, vitest 600/600 grün.

Enthalten (Details in PENDENZEN.md ✅-Liste / HANDOVER.md):
- truck-Integration: Profil-Extrusion + Verjüngung + Boolean-CSG (csgrs),
  Crate src-tauri/trucksolid, Werkzeug `extrude`, ExtrudedSolid-Modell.
- kernel2d-Port nach Rust/WASM (Phasen 1–5, Diff-Harness).
- render3d 3D-Live-Schnitt = 2D-Schnitt: geschichteter Bodenaufbau,
  Prioritäts-Verschneidung (section_boolean.rs), einstellbare
  Schichttrennlinien, per-Hatch-Strichstärke, relativeToWall-Orientierung.
- Interop-Export IFC4/STL/OBJ (Loch-Ausschnitt wallMeshCut), Schnellexport.
- Projektdatei .obp + OS-Lock (lock.rs, LockConflictDialog).
- Layout-Blätter (Modell/Editor/Panel/PDF), Ausschnitte, Override-Engine,
  Tragwerk-Stützen (Column), BIM-Tree-Panel.
- Bauteil-Typsystem (Tür/Fenster/Treppe-Typen), Betontreppe mit schräger
  Laufplatte, Text-/Textbox-Werkzeug, Mess-Werkzeug, 2D/3D-Griffe für
  Öffnungen/Treppen, Snap-Symbol-Restyle.
This commit is contained in:
2026-07-09 00:57:29 +02:00
parent 889cbb2c12
commit 35299307d6
131 changed files with 26501 additions and 852 deletions
+115
View File
@@ -0,0 +1,115 @@
// Unit-Tests für die Wand-/Decken-Resolver (wallVerticalExtent /
// ceilingVerticalExtent): reine Datenschicht, kein React/Store.
import { describe, it, expect } from "vitest";
import { ceilingVerticalExtent } from "./wall";
import type { Ceiling, Project } from "./types";
/** Minimalprojekt mit einem Geschoss (baseElevation 0, floorHeight 2.6) und
* einem einschichtigen Wandtyp (0.2 m), den die Decke als Aufbau nutzt. */
function makeProject(overrides?: Partial<Project>): Project {
return {
id: "t",
name: "T",
lineStyles: [],
hatches: [],
components: [{ id: "c", name: "C", color: "#ccc", hatchId: "none", joinPriority: 10 }],
wallTypes: [
{ id: "dt", name: "Decke", layers: [{ componentId: "c", thickness: 0.2 }] },
],
drawingLevels: [
{
id: "eg",
name: "EG",
kind: "floor",
visible: true,
locked: false,
floorHeight: 2.6,
cutHeight: 1.0,
baseElevation: 0,
},
{
id: "og",
name: "OG",
kind: "floor",
visible: true,
locked: false,
floorHeight: 2.6,
cutHeight: 1.0,
baseElevation: 2.8,
},
],
layers: [{ code: "30", name: "Decken", color: "#0a0a0a", lw: 0.5, visible: true, locked: false }],
walls: [],
doors: [],
openings: [],
ceilings: [],
stairs: [],
rooms: [],
drawings2d: [],
context: [],
...overrides,
};
}
function makeCeiling(overrides?: Partial<Ceiling>): Ceiling {
return {
id: "cl1",
type: "ceiling",
floorId: "eg",
categoryCode: "30",
outline: [
{ x: 0, y: 0 },
{ x: 4, y: 0 },
{ x: 4, y: 4 },
{ x: 0, y: 4 },
],
wallTypeId: "dt",
...overrides,
};
}
describe("ceilingVerticalExtent", () => {
it("ohne top/bottom: OK = Geschoss-Oberkante, UK = OK − Dicke (heutiges Verhalten)", () => {
const project = makeProject();
const ceiling = makeCeiling();
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
expect(zTop).toBeCloseTo(2.6); // baseElevation(0) + floorHeight(2.6)
expect(zBottom).toBeCloseTo(2.4); // zTop − thickness(0.2)
});
it("mit top: custom übersteuert die Oberkante; UK bleibt OK − Dicke", () => {
const project = makeProject();
const ceiling = makeCeiling({ top: { mode: "custom", z: 3.0 } });
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
expect(zTop).toBeCloseTo(3.0);
expect(zBottom).toBeCloseTo(2.8);
});
it("mit bottom: custom übersteuert die Unterkante — gewinnt über OK − Dicke", () => {
const project = makeProject();
const ceiling = makeCeiling({ bottom: { mode: "custom", z: 2.2 } });
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
expect(zTop).toBeCloseTo(2.6); // OK unverändert (Geschoss-Default)
expect(zBottom).toBeCloseTo(2.2); // UK-Override statt OK − Dicke (2.4)
});
it("mit top UND bottom: beide Overrides wirken unabhängig (Dicke wird ignoriert)", () => {
const project = makeProject();
const ceiling = makeCeiling({
top: { mode: "custom", z: 5.0 },
bottom: { mode: "custom", z: 4.5 },
});
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
expect(zTop).toBeCloseTo(5.0);
expect(zBottom).toBeCloseTo(4.5);
});
it("bottom: floor bindet die Unterkante an ein Geschoss (+ optionalem offset)", () => {
const project = makeProject();
const ceiling = makeCeiling({ bottom: { mode: "floor", floorId: "og", offset: 0.1 } });
const { zBottom, zTop } = ceilingVerticalExtent(project, ceiling);
expect(zTop).toBeCloseTo(2.6);
expect(zBottom).toBeCloseTo(2.9); // baseElevation(og)=2.8 + offset(0.1)
});
});