Files
DOSSIER-STANDALONE/src/model/geoRebase.test.ts
T
karim b9e330ecb7 Schnittebenen 2D↔3D Phase 1+2, native Fenster, Hell/Dunkel-Theme, SWISSIMAGE-Import
- Schnittebenen: 3D-Live-Schnitt folgt der gewählten Grundriss-Schnittlinie
  (section3dCutId/sectionPlaneFromLevel), Schalter "Im 3D schneiden" im
  Objekt-Info, Doppelklick auf Schnittlinie springt in 2D-Schnittansicht,
  unsichtbare Ebenen blenden ihre Führungslinie aus
- Eigene native Tauri-Fenster für Kontext-Import/Zeichnungsebenen/
  Ebenen-Einstellungen/Ressourcen/Einstellungen + klassische Menüleiste
  (AppMenuBar) neben der Wortmarke
- Hell/Dunkel-Umschalter (Einstellungen → Darstellung), persistiert,
  flackerfrei vor erstem Render gesetzt
- SWISSIMAGE-Luftbild-Import (swisstopo WMS) als Kontext-Hintergrundebene
- UI-Politur: Werkzeug-Panel Symbole/Liste umschaltbar, Topbar-Quick-Access-
  Icons entfernt, Zahnrad→Einstellungen in Panel-Köpfen, Footerbar/
  Snap-Marker/Maß-HUD auf helle Pillen-Sprache umgestellt
- Neues Dachziegel-Material (RoofingTiles013A)
2026-07-20 10:51:37 +02:00

250 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from "vitest";
import type { Column, ExtrudedSolid, Project } from "./types";
import { sampleProject } from "./sampleProject";
import { translateProject, projectToWorldCoords, projectGeoOrigin } from "./geoRebase";
const DX = 100;
const DY = -50;
describe("translateProject", () => {
it("dx=dy=0 -> unverändertes Projekt (Referenzgleichheit)", () => {
expect(translateProject(sampleProject, 0, 0)).toBe(sampleProject);
});
it("verschiebt Wand-Achsen (start/end)", () => {
const out = translateProject(sampleProject, DX, DY);
const w = sampleProject.walls[0];
const ow = out.walls.find((x) => x.id === w.id)!;
expect(ow.start).toEqual({ x: w.start.x + DX, y: w.start.y + DY });
expect(ow.end).toEqual({ x: w.end.x + DX, y: w.end.y + DY });
});
it("verschiebt Decken-Umriss inkl. Aussparungen", () => {
const withHole: Project = {
...sampleProject,
ceilings: [
{
...sampleProject.ceilings![0],
openings: [
[{ x: 1, y: 1 }, { x: 2, y: 1 }, { x: 2, y: 2 }],
],
},
],
};
const out = translateProject(withHole, DX, DY);
const oc = out.ceilings![0];
const src = withHole.ceilings![0];
expect(oc.outline).toEqual(src.outline.map((p) => ({ x: p.x + DX, y: p.y + DY })));
expect(oc.openings![0]).toEqual(src.openings![0].map((p) => ({ x: p.x + DX, y: p.y + DY })));
});
it("verschiebt Dach-Umriss", () => {
const out = translateProject(sampleProject, DX, DY);
const r = sampleProject.roofs![0];
const or_ = out.roofs!.find((x) => x.id === r.id)!;
expect(or_.outline).toEqual(r.outline.map((p) => ({ x: p.x + DX, y: p.y + DY })));
});
it("Treppe: start/center verschieben sich, dir (Richtungsvektor) bleibt unverändert", () => {
const withSpiral: Project = {
...sampleProject,
stairs: [...sampleProject.stairs!, { ...sampleProject.stairs![0], id: "ST2", center: { x: 5, y: 5 } }],
};
const out = translateProject(withSpiral, DX, DY);
const st1 = out.stairs!.find((s) => s.id === "ST1")!;
const st2 = out.stairs!.find((s) => s.id === "ST2")!;
expect(st1.start).toEqual({ x: sampleProject.stairs![0].start.x + DX, y: sampleProject.stairs![0].start.y + DY });
expect(st1.dir).toEqual(sampleProject.stairs![0].dir); // Richtung bleibt
expect(st2.center).toEqual({ x: 5 + DX, y: 5 + DY });
});
it("Raum: boundary + stampAnchor verschieben sich", () => {
const out = translateProject(sampleProject, DX, DY);
const r = sampleProject.rooms![0];
const or_ = out.rooms!.find((x) => x.id === r.id)!;
expect(or_.boundary).toEqual(r.boundary.map((p) => ({ x: p.x + DX, y: p.y + DY })));
expect(or_.stampAnchor).toEqual({ x: r.stampAnchor!.x + DX, y: r.stampAnchor!.y + DY });
});
it("Stütze: position verschiebt sich, rotation bleibt unverändert", () => {
const col: Column = {
id: "C1",
type: "column",
floorId: "eg",
categoryCode: "50",
position: { x: 2, y: 3 },
profile: { kind: "round", radius: 0.15 },
rotation: Math.PI / 4,
height: 2.6,
};
const withColumn: Project = { ...sampleProject, columns: [col] };
const out = translateProject(withColumn, DX, DY);
const oc = out.columns![0];
expect(oc.position).toEqual({ x: 2 + DX, y: 3 + DY });
expect(oc.rotation).toBe(col.rotation);
});
it("Extrusion: points + Kreis-Mitte verschieben sich", () => {
const solid: ExtrudedSolid = {
id: "EX1",
type: "extrudedSolid",
levelId: "eg",
points: [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }],
height: 1,
circle: { center: { x: 5, y: 5 }, r: 1 },
};
const withSolid: Project = { ...sampleProject, extrudedSolids: [solid] };
const out = translateProject(withSolid, DX, DY);
const os = out.extrudedSolids![0];
expect(os.points).toEqual(solid.points.map((p) => ({ x: p.x + DX, y: p.y + DY })));
expect(os.circle!.center).toEqual({ x: 5 + DX, y: 5 + DY });
});
it("Drawing2D: alle Geometrie-Varianten verschieben ihre Punkte", () => {
const withDrawings: Project = {
...sampleProject,
drawings2d: [
{ id: "D1", type: "drawing2d", levelId: "eg", categoryCode: "10", geom: { shape: "line", a: { x: 0, y: 0 }, b: { x: 1, y: 1 } } },
{ id: "D2", type: "drawing2d", levelId: "eg", categoryCode: "10", geom: { shape: "polyline", pts: [{ x: 0, y: 0 }, { x: 1, y: 0 }], closed: false } },
{ id: "D3", type: "drawing2d", levelId: "eg", categoryCode: "10", geom: { shape: "rect", min: { x: 0, y: 0 }, max: { x: 1, y: 1 } } },
{ id: "D4", type: "drawing2d", levelId: "eg", categoryCode: "10", geom: { shape: "circle", center: { x: 1, y: 1 }, r: 0.5 } },
{ id: "D5", type: "drawing2d", levelId: "eg", categoryCode: "10", geom: { shape: "arc", center: { x: 2, y: 2 }, r: 0.5, a0: 0, a1: 1 } },
{ id: "D6", type: "drawing2d", levelId: "eg", categoryCode: "10", geom: { shape: "text", at: { x: 3, y: 3 }, text: "x", height: 0.2, angle: 0.7 } },
],
};
const out = translateProject(withDrawings, DX, DY);
const byId = (id: string) => out.drawings2d.find((d) => d.id === id)!.geom;
expect(byId("D1")).toMatchObject({ a: { x: DX, y: DY }, b: { x: 1 + DX, y: 1 + DY } });
expect(byId("D2")).toMatchObject({ pts: [{ x: DX, y: DY }, { x: 1 + DX, y: DY }] });
expect(byId("D3")).toMatchObject({ min: { x: DX, y: DY }, max: { x: 1 + DX, y: 1 + DY } });
expect(byId("D4")).toMatchObject({ center: { x: 1 + DX, y: 1 + DY } });
expect(byId("D5")).toMatchObject({ center: { x: 2 + DX, y: 2 + DY }, a0: 0, a1: 1 }); // Winkel unverändert
expect(byId("D6")).toMatchObject({ at: { x: 3 + DX, y: 3 + DY }, angle: 0.7 }); // Winkel unverändert
});
it("Kontext-Mesh: nur x/y verschieben sich, Höhe (z) bleibt", () => {
const withContext: Project = {
...sampleProject,
context: [
{
id: "CTX1",
type: "importedMesh",
name: "Test",
positions: [0, 0, 5, 2, 3, 7],
indices: [0, 1, 0],
},
],
};
const out = translateProject(withContext, DX, DY);
const obj = out.context![0];
if (obj.type !== "importedMesh") throw new Error("expected importedMesh");
expect(obj.positions).toEqual([0 + DX, 0 + DY, 5, 2 + DX, 3 + DY, 7]);
});
it("Konturen-Satz: Punkte + Kreis-/Bogen-Mitte verschieben sich, Winkel bleiben", () => {
const withContours: Project = {
...sampleProject,
context: [
{
id: "CS1",
type: "contourSet",
name: "Höhenlinien",
contours: [
{
z: 400,
pts: [{ x: 0, y: 0 }, { x: 1, y: 0 }],
closed: false,
curve: { kind: "arc", cx: 1, cy: 1, r: 0.5, a0: 0, a1: 1 },
},
],
},
],
};
const out = translateProject(withContours, DX, DY);
const obj = out.context![0];
if (obj.type !== "contourSet") throw new Error("expected contourSet");
expect(obj.contours[0].pts).toEqual([{ x: DX, y: DY }, { x: 1 + DX, y: DY }]);
expect(obj.contours[0].curve).toEqual({ kind: "arc", cx: 1 + DX, cy: 1 + DY, r: 0.5, a0: 0, a1: 1 });
});
it("Schnittlinie (DrawingLevel.linePoints) verschiebt sich", () => {
const withSection: Project = {
...sampleProject,
drawingLevels: [
...sampleProject.drawingLevels,
{
id: "SEC1",
name: "Schnitt A",
kind: "section",
visible: true,
locked: false,
linePoints: [{ x: 0, y: 0 }, { x: 5, y: 0 }],
},
],
};
const out = translateProject(withSection, DX, DY);
const lvl = out.drawingLevels.find((l) => l.id === "SEC1")!;
expect(lvl.linePoints).toEqual([{ x: DX, y: DY }, { x: 5 + DX, y: DY }]);
});
it("geoAnchor.model verschiebt sich mit, lv95 bleibt unverändert (Bezug bleibt exakt erhalten)", () => {
const withAnchor: Project = {
...sampleProject,
geoAnchor: { lv95: { e: 2600000, n: 1200000 }, model: { x: 0, y: 0 }, label: "Test" },
};
const out = translateProject(withAnchor, DX, DY);
expect(out.geoAnchor).toEqual({ lv95: { e: 2600000, n: 1200000 }, model: { x: DX, y: DY }, label: "Test" });
});
it("kein geoAnchor gesetzt -> bleibt undefined", () => {
const out = translateProject(sampleProject, DX, DY);
expect(out.geoAnchor).toBeUndefined();
});
});
describe("projectGeoOrigin / projectToWorldCoords", () => {
it("ohne geoAnchor -> Origin null, projectToWorldCoords ist Identität", () => {
expect(projectGeoOrigin(sampleProject)).toBeNull();
expect(projectToWorldCoords(sampleProject)).toBe(sampleProject);
});
it("Origin = lv95 − model", () => {
const p: Project = {
...sampleProject,
geoAnchor: { lv95: { e: 2600500, n: 1200300 }, model: { x: 500, y: 300 } },
};
expect(projectGeoOrigin(p)).toEqual({ e: 2600000, n: 1200000 });
});
it("Round-Trip: lokal bearbeitetes Modell exportiert an echten LV95-Koordinaten", () => {
// Rohimport (model == LV95): eine Wand steht an LV95 (2600010, 1200020).
// Danach per georef um (−2600000, −1200000) lokalisiert -> Wand bei (10,20),
// geoAnchor.model wandert mit. Der Welt-Export muss (2600010,1200020) liefern.
const raw: Project = {
...sampleProject,
walls: [
{
id: "W1",
type: "wall",
floorId: "eg",
categoryCode: "20",
start: { x: 2600010, y: 1200020 },
end: { x: 2600015, y: 1200020 },
wallTypeId: "aw",
height: 2.6,
},
],
geoAnchor: { lv95: { e: 2600000, n: 1200000 }, model: { x: 2600000, y: 1200000 } },
};
// georef: klick auf (2600000,1200000) -> translate um (−2600000,−1200000).
const local = translateProject(raw, -2600000, -1200000);
expect(local.walls[0].start).toEqual({ x: 10, y: 20 });
// geoAnchor.model ist mitgewandert, lv95 fix.
expect(local.geoAnchor).toEqual({ lv95: { e: 2600000, n: 1200000 }, model: { x: 0, y: 0 } });
// Welt-Export rechnet exakt auf die ursprünglichen LV95-Koordinaten zurück.
const world = projectToWorldCoords(local);
expect(world.walls[0].start).toEqual({ x: 2600010, y: 1200020 });
expect(world.walls[0].end).toEqual({ x: 2600015, y: 1200020 });
});
});