Neuer Bezugspunkt: Projekt nach Kataster-/3D-Import auf praktischen Ursprung verschieben
Nach einem Standort-Import landet die Kontext-Geometrie dort, wo der gesuchte Ort war — das kann weit vom bisherigen Modell-Ursprung liegen. Neuer Befehl "georef" (Button in SitePanel) verschiebt das GESAMTE Projekt per Klick so, dass der gewählte Punkt zum neuen Ursprung wird; geoAnchor wandert automatisch mit, der reale LV95-Bezug bleibt exakt erhalten (rekonstruierbar, auch beim späteren Export). Bewusst nur Translation, keine Rotation — mehrere Felder (Roof.ridgeAxis, Column.rotation, Text-/Bild-Rotation) sind achsen-/winkel- gebunden und bräuchten bei einer Drehung eigene Sorgfalt.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { georefCommand } from "./georef";
|
||||
import type { CommandContext, Project, Vec2 } from "../types";
|
||||
|
||||
const ctx = {} as CommandContext;
|
||||
const P = (x: number, y: number): Vec2 => ({ x, y });
|
||||
|
||||
function projectWith(wallStart: Vec2): Project {
|
||||
return {
|
||||
id: "p",
|
||||
name: "T",
|
||||
layers: [],
|
||||
drawingLevels: [],
|
||||
walls: [
|
||||
{
|
||||
id: "W1",
|
||||
type: "wall",
|
||||
floorId: "eg",
|
||||
categoryCode: "20",
|
||||
start: wallStart,
|
||||
end: { x: wallStart.x + 5, y: wallStart.y },
|
||||
wallTypeId: "aw",
|
||||
height: 2.6,
|
||||
},
|
||||
],
|
||||
drawings2d: [],
|
||||
} as unknown as Project;
|
||||
}
|
||||
|
||||
describe("georefCommand", () => {
|
||||
it("Hover zeigt die resultierende Verschiebung im HUD (negativer Klickpunkt)", () => {
|
||||
const s0 = georefCommand.init();
|
||||
const [, r] = georefCommand.onMove(s0, P(120, 45), null, ctx);
|
||||
expect(r.draft?.hud).toEqual({ at: P(120, 45), dx: -120, dy: -45 });
|
||||
});
|
||||
|
||||
it("Klick committet sofort: der geklickte Punkt wird zum neuen Ursprung", () => {
|
||||
const s0 = georefCommand.init();
|
||||
const [ns, r] = georefCommand.onInput(s0, { kind: "point", point: P(120, 45) }, ctx);
|
||||
expect(r.done).toBe(true);
|
||||
expect((ns as { phase: string }).phase).toBe("start");
|
||||
const p = projectWith({ x: 120, y: 45 });
|
||||
const out = r.commit!(p);
|
||||
// Die Wand begann exakt am geklickten Punkt -> liegt danach auf (0,0).
|
||||
expect(out.walls[0].start).toEqual({ x: 0, y: 0 });
|
||||
expect(out.walls[0].end).toEqual({ x: 5, y: 0 });
|
||||
});
|
||||
|
||||
it("Esc/Rechtsklick auf leerem Zustand beendet ohne Commit", () => {
|
||||
const s0 = georefCommand.init();
|
||||
const [, r1] = georefCommand.onCancel(s0);
|
||||
expect(r1.done).toBe(true);
|
||||
expect(r1.commit).toBeUndefined();
|
||||
const [, r2] = georefCommand.onConfirm(s0, ctx);
|
||||
expect(r2.done).toBe(true);
|
||||
expect(r2.commit).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
// Neuer Bezugspunkt — verschiebt das GESAMTE Projekt (Wände, Decken, Dächer,
|
||||
// Treppen, Räume, Stützen, Extrusionen, freie 2D-Geometrie, Kontext-Schicht,
|
||||
// Schnittlinien) so, dass der geklickte Punkt zum neuen Modell-Ursprung (0,0)
|
||||
// wird. Praktisch nach einem Kataster-/3D-Import (Kontext-Geometrie landet
|
||||
// dort, wo der gesuchte Standort war — kann weit vom bisherigen Ursprung
|
||||
// liegen, s. `Project.geoAnchor`): man klickt den gewünschten Bezugspunkt
|
||||
// (z. B. eine Parzellenecke aus dem Import) und zeichnet danach mit kleinen,
|
||||
// bequemen Koordinaten weiter. `geoAnchor` wandert automatisch mit (reine
|
||||
// Translation, s. `model/geoRebase.ts`), der reale LV95-Bezug bleibt exakt
|
||||
// erhalten — beim Export ist die Verschiebung jederzeit rekonstruierbar.
|
||||
//
|
||||
// Ein-Klick-Befehl: der erste (einzige) Punkt committet sofort.
|
||||
|
||||
import { translateProject } from "../../model/geoRebase";
|
||||
import type {
|
||||
Command,
|
||||
CommandResult,
|
||||
CommandState,
|
||||
Project,
|
||||
ToolDraft,
|
||||
Vec2,
|
||||
} from "../types";
|
||||
|
||||
interface GeorefStart extends CommandState {
|
||||
phase: "start";
|
||||
}
|
||||
|
||||
const IDLE: GeorefStart = { phase: "start", lastPoint: null };
|
||||
const idle = (): [CommandState, CommandResult] => [{ ...IDLE }, { draft: null, done: true }];
|
||||
|
||||
/** HUD am Cursor: zeigt die resultierende Verschiebung, bevor geklickt wird. */
|
||||
function draft(point: Vec2): ToolDraft {
|
||||
return {
|
||||
preview: [],
|
||||
vertices: [],
|
||||
hud: { at: point, dx: -point.x, dy: -point.y },
|
||||
};
|
||||
}
|
||||
|
||||
export const georefCommand: Command = {
|
||||
name: "georef",
|
||||
labelKey: "cmd.georef.label",
|
||||
prompt: () => "cmd.georef.prompt",
|
||||
accepts: () => ["point"],
|
||||
options: () => [],
|
||||
init: (): GeorefStart => ({ ...IDLE }),
|
||||
|
||||
onInput: (state, input): [CommandState, CommandResult] => {
|
||||
if (input.kind !== "point") return [state, { draft: null }];
|
||||
const { x, y } = input.point;
|
||||
return [
|
||||
{ ...IDLE },
|
||||
{
|
||||
draft: null,
|
||||
done: true,
|
||||
commit: (p: Project) => translateProject(p, -x, -y),
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
onMove: (state, point): [CommandState, CommandResult] => [state, { draft: draft(point) }],
|
||||
onConfirm: (): [CommandState, CommandResult] => idle(),
|
||||
onCancel: (): [CommandState, CommandResult] => idle(),
|
||||
};
|
||||
@@ -30,6 +30,7 @@ import { importCommand } from "./cmds/import";
|
||||
import { terrainCommand } from "./cmds/terrain";
|
||||
import { measureCommand } from "./cmds/measure";
|
||||
import { sectionLineCommand, viewLineCommand } from "./cmds/sectionline";
|
||||
import { georefCommand } from "./cmds/georef";
|
||||
|
||||
/** Alle bekannten Befehle, Schlüssel = Befehlsname (lowercase). */
|
||||
export const COMMANDS: Record<string, Command> = {
|
||||
@@ -62,6 +63,7 @@ export const COMMANDS: Record<string, Command> = {
|
||||
measure: measureCommand,
|
||||
sectionline: sectionLineCommand,
|
||||
viewline: viewLineCommand,
|
||||
georef: georefCommand,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,6 +112,8 @@ export const ALIASES: Record<string, string> = {
|
||||
ter: "terrain",
|
||||
schnittlinie: "sectionline",
|
||||
ansichtslinie: "viewline",
|
||||
bezugspunkt: "georef",
|
||||
nullpunkt: "georef",
|
||||
};
|
||||
|
||||
/** Liefert den Befehl zu einem exakten Namen (oder undefined). */
|
||||
|
||||
Reference in New Issue
Block a user