Files
DOSSIER-STANDALONE/src/plan/toSection.termination.test.ts
T
karim 0e02c2b20f 2D-Schnitt = 3D: Wand-Terminierung (sliceTermination) auch im Vertikalschnitt
Nutzer-Report: 'Wand unten geschnitten' wirkte im 3D, aber nicht im 2D-Schnitt
— die Terminierung lief nur im layered-3D-Pfad (terminateOrSubtractSpans),
der Schnitt stanzte die Decke nur per Prioritäts-Subtraktion heraus (die Wand
tauchte darüber wieder auf).

Neu: applyWallTermination (Phase 1b in attachCutStyles) spiegelt die 3D-Regel
im (u,v)-Schnittraum: 'below' kappt jedes Wand-Band an der UNTERKANTE der
dominanten Decken-Bänder (strikt höhere joinPriority + u-Überlapp), 'above'
spiegelbildlich an der Oberkante; 'both'/undefined unverändert (heutige
Subtraktion). +6 Tests. 696/696 grün.
2026-07-10 23:59:31 +02:00

107 lines
4.0 KiB
TypeScript

// Decken-Terminierung im 2D-Schnitt (applyWallTermination) — der Spiegel von
// terminateOrSubtractSpans (3D): Wände mit sliceTermination "below" enden an
// der Decken-Unterkante, "above" beginnen an der Oberkante; "both"/undefined
// bleibt bei der reinen Prioritäts-Subtraktion (unverändert hier).
import { describe, it, expect } from "vitest";
import { applyWallTermination } from "./toSection";
import type { SectionCutPolygon } from "./toSection";
import type { Wall } from "../model/types";
const rect = (
uMin: number,
uMax: number,
vMin: number,
vMax: number,
): Array<[number, number]> => [
[uMin, vMax],
[uMax, vMax],
[uMax, vMin],
[uMin, vMin],
];
const wallBand = (joinPriority: number | undefined, pts = rect(0, 0.3, 0, 2.6)): SectionCutPolygon => ({
component: { kind: "wall", index: 0 },
color: [0.8, 0.8, 0.8],
pts,
joinPriority,
});
const slabBand = (joinPriority: number, vMin: number, vMax: number, uMin = -2, uMax = 2): SectionCutPolygon => ({
component: { kind: "slab", index: 0 },
color: [0.9, 0.9, 0.9],
pts: rect(uMin, uMax, vMin, vMax),
joinPriority,
});
const wall = (sliceTermination?: Wall["sliceTermination"]): Wall =>
({
id: "W1",
start: { x: 0, y: 0 },
end: { x: 5, y: 0 },
height: 2.6,
wallTypeId: "wt",
floorId: "eg",
categoryCode: "21",
sliceTermination,
}) as Wall;
const bbox = (cp: SectionCutPolygon) => {
const vs = cp.pts.map((p) => p[1]);
return { vMin: Math.min(...vs), vMax: Math.max(...vs) };
};
describe("applyWallTermination — 2D-Schnitt = 3D-Terminierung", () => {
it("'below': Wand-Band endet an der Unterkante der dominanten Decke", () => {
const bands = [wallBand(50), slabBand(100, 2.2, 2.5)];
const out = applyWallTermination(bands, [wall("below")]);
const wb = out.find((b) => b.component.kind === "wall")!;
expect(bbox(wb).vMax).toBeCloseTo(2.2, 9);
expect(bbox(wb).vMin).toBeCloseTo(0, 9);
});
it("'above': Wand-Band beginnt an der Oberkante der dominanten Decke", () => {
const bands = [wallBand(50), slabBand(100, 2.2, 2.5)];
const out = applyWallTermination(bands, [wall("above")]);
const wb = out.find((b) => b.component.kind === "wall")!;
expect(bbox(wb).vMin).toBeCloseTo(2.5, 9);
expect(bbox(wb).vMax).toBeCloseTo(2.6, 9);
});
it("'both'/undefined: Band bleibt unverändert (Prioritäts-Subtraktion regelt)", () => {
const bands = [wallBand(50), slabBand(100, 2.2, 2.5)];
for (const term of ["both", undefined] as const) {
const out = applyWallTermination(bands, [wall(term)]);
const wb = out.find((b) => b.component.kind === "wall")!;
expect(wb.pts).toEqual(rect(0, 0.3, 0, 2.6));
}
});
it("nur STRIKT dominante Decken terminieren (schwächere/gleiche nicht)", () => {
for (const prio of [50, 10]) {
const bands = [wallBand(50), slabBand(prio, 2.2, 2.5)];
const out = applyWallTermination(bands, [wall("below")]);
const wb = out.find((b) => b.component.kind === "wall")!;
expect(bbox(wb).vMax).toBeCloseTo(2.6, 9);
}
});
it("ohne u-Überlapp keine Terminierung; Band unter der Decken-UK verschwindet nicht", () => {
// Decke liegt in u komplett rechts neben der Wand.
const far = [wallBand(50), slabBand(100, 2.2, 2.5, 5, 8)];
const outFar = applyWallTermination(far, [wall("below")]);
expect(bbox(outFar.find((b) => b.component.kind === "wall")!).vMax).toBeCloseTo(2.6, 9);
// Decke UNTER dem Wandfuss ⇒ 'below'-Clip entfernt das ganze Band.
const under = [wallBand(50), slabBand(100, -0.5, -0.2)];
const outUnder = applyWallTermination(under, [wall("below")]);
expect(outUnder.some((b) => b.component.kind === "wall")).toBe(false);
});
it("Bänder ohne joinPriority (unauflösbares Bauteil) bleiben unangetastet", () => {
const bands = [wallBand(undefined), slabBand(100, 2.2, 2.5)];
const out = applyWallTermination(bands, [wall("below")]);
expect(out.find((b) => b.component.kind === "wall")!.pts).toEqual(rect(0, 0.3, 0, 2.6));
});
});