Snap an Wand-Schichttrennlinien
Die Grenzlinien zwischen den Wandschichten (achsparallel, aus der addWallPoche- Offset-Logik gespiegelt: refOff-total/2, je Schicht +thickness, n+1 Linien inkl. Aussenflaechen) werden als Snap-Segmente in collectSegments eingereiht. Damit fangen die bestehenden Snap-Arten (Endpunkt/Mittelpunkt/Schnittpunkt/Lot) automatisch daran — Decken/Elemente koennen an einzelnen Wandschichten fangen. Kein neuer SnapKind/Toggle noetig (Default-aktiv). 4 neue Tests, 156 gruen.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Unit-Tests für den Wand-Schichttrennlinien-Snap:
|
||||
* • `wallLayerBoundarySegments` muss die kumulierten Schicht-Offsets EXAKT so
|
||||
* berechnen wie `addWallPoche` in `plan/generatePlan.ts` (Start bei
|
||||
* `refOff − total/2`, danach `+= layer.thickness` je Schicht, inkl. beider
|
||||
* Aussenflächen) — inklusive Referenzversatz (`center`/`left`/`right`).
|
||||
* • `computeSnap` muss auf diesen Grenzlinien fangen (Endpunkt-Snap ist per
|
||||
* Default aktiv), damit z. B. ein Decken-Umrisspunkt an einer einzelnen
|
||||
* Wandschicht statt nur an Achse/Aussenkante enden kann.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { computeSnap, wallLayerBoundarySegments } from "./snapping";
|
||||
import { DEFAULT_SNAP } from "./types";
|
||||
import type { Project, Vec2, Wall } from "../model/types";
|
||||
|
||||
/** Minimalprojekt mit einer dreischichtigen Wand (0.1 / 0.15 / 0.2 m). */
|
||||
function wallProject(wall: Wall): Project {
|
||||
return {
|
||||
id: "t",
|
||||
name: "T",
|
||||
lineStyles: [],
|
||||
hatches: [],
|
||||
components: [{ id: "c", name: "C", color: "#ccc", hatchId: "none", joinPriority: 10 }],
|
||||
wallTypes: [
|
||||
{
|
||||
id: "aw",
|
||||
name: "AW",
|
||||
layers: [
|
||||
{ componentId: "c", thickness: 0.1 },
|
||||
{ componentId: "c", thickness: 0.15 },
|
||||
{ componentId: "c", thickness: 0.2 },
|
||||
],
|
||||
},
|
||||
],
|
||||
drawingLevels: [
|
||||
{
|
||||
id: "eg",
|
||||
name: "EG",
|
||||
kind: "floor",
|
||||
visible: true,
|
||||
locked: false,
|
||||
floorHeight: 2.6,
|
||||
cutHeight: 1.0,
|
||||
baseElevation: 0,
|
||||
},
|
||||
],
|
||||
layers: [{ code: "20", name: "Wände", color: "#0a0a0a", lw: 0.5, visible: true, locked: false }],
|
||||
walls: [wall],
|
||||
doors: [],
|
||||
openings: [],
|
||||
ceilings: [],
|
||||
stairs: [],
|
||||
rooms: [],
|
||||
drawings2d: [],
|
||||
context: [],
|
||||
};
|
||||
}
|
||||
|
||||
function wall(id: string, start: Vec2, end: Vec2, referenceLine?: Wall["referenceLine"]): Wall {
|
||||
return {
|
||||
id,
|
||||
type: "wall",
|
||||
floorId: "eg",
|
||||
categoryCode: "20",
|
||||
start,
|
||||
end,
|
||||
wallTypeId: "aw",
|
||||
height: 2.6,
|
||||
referenceLine,
|
||||
};
|
||||
}
|
||||
|
||||
describe("wallLayerBoundarySegments", () => {
|
||||
it("liefert layers.length+1 Grenzlinien mit kumulierten Offsets (referenceLine=center)", () => {
|
||||
const w = wall("W1", { x: 0, y: 0 }, { x: 4, y: 0 });
|
||||
const segs = wallLayerBoundarySegments(wallProject(w), w);
|
||||
expect(segs).toHaveLength(4); // 3 Schichten → 4 Grenzlinien
|
||||
// Achse horizontal → leftNormal = (0,1); Offset erscheint als y-Koordinate.
|
||||
const offsets = segs.map(([a]) => a.y);
|
||||
expect(offsets[0]).toBeCloseTo(-0.225, 9); // −T/2 (Aussenfläche)
|
||||
expect(offsets[1]).toBeCloseTo(-0.125, 9); // −T/2 + 0.10
|
||||
expect(offsets[2]).toBeCloseTo(0.025, 9); // −T/2 + 0.10 + 0.15
|
||||
expect(offsets[3]).toBeCloseTo(0.225, 9); // +T/2 (Aussenfläche, innen)
|
||||
// Jede Grenzlinie läuft parallel zur Achse über die volle Wandlänge.
|
||||
for (const [a, b] of segs) {
|
||||
expect(a.y).toBeCloseTo(b.y, 9);
|
||||
expect(a.x).toBeCloseTo(0, 9);
|
||||
expect(b.x).toBeCloseTo(4, 9);
|
||||
}
|
||||
});
|
||||
|
||||
it("verschiebt die Offsets um wallReferenceOffset bei referenceLine=left/right", () => {
|
||||
const wLeft = wall("W1", { x: 0, y: 0 }, { x: 4, y: 0 }, "left");
|
||||
const offLeft = wallLayerBoundarySegments(wallProject(wLeft), wLeft).map(([a]) => a.y);
|
||||
// refOff = +T/2 = 0.225 → Offsets laufen von 0 bis T (0.45).
|
||||
expect(offLeft[0]).toBeCloseTo(0, 9);
|
||||
expect(offLeft[3]).toBeCloseTo(0.45, 9);
|
||||
|
||||
const wRight = wall("W1", { x: 0, y: 0 }, { x: 4, y: 0 }, "right");
|
||||
const offRight = wallLayerBoundarySegments(wallProject(wRight), wRight).map(([a]) => a.y);
|
||||
// refOff = −T/2 = −0.225 → Offsets laufen von −0.45 bis 0.
|
||||
expect(offRight[0]).toBeCloseTo(-0.45, 9);
|
||||
expect(offRight[3]).toBeCloseTo(0, 9);
|
||||
});
|
||||
|
||||
it("liefert eine leere Liste bei unbekanntem Wandtyp (kein Crash im Snap-Pfad)", () => {
|
||||
const w = wall("W1", { x: 0, y: 0 }, { x: 4, y: 0 });
|
||||
w.wallTypeId = "unknown";
|
||||
expect(wallLayerBoundarySegments(wallProject(w), w)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computeSnap — fängt auf Wand-Schichttrennlinien", () => {
|
||||
it("fängt den Endpunkt einer inneren Schichtgrenze (nicht nur Achse/Aussenkante)", () => {
|
||||
const w = wall("W1", { x: 0, y: 0 }, { x: 4, y: 0 });
|
||||
const project = wallProject(w);
|
||||
// Endpunkt der Schichtgrenze bei y=0.025 (Fuge zwischen Schicht 2 und 3).
|
||||
const raw: Vec2 = { x: 0.01, y: 0.02 };
|
||||
const result = computeSnap({
|
||||
raw,
|
||||
project,
|
||||
levelId: "eg",
|
||||
visibleCodes: new Set(["20"]),
|
||||
settings: DEFAULT_SNAP,
|
||||
draftPoints: [],
|
||||
lastPoint: null,
|
||||
pxPerMeter: 100,
|
||||
shift: false,
|
||||
ctrl: false,
|
||||
});
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.point.x).toBeCloseTo(0, 6);
|
||||
expect(result!.point.y).toBeCloseTo(0.025, 6);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user