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
+74
View File
@@ -0,0 +1,74 @@
// Messen — schnelles, unverbindliches Distanz-/Winkel-Messwerkzeug (kein Element
// wird erzeugt). Schritte:
// 1) „Messen von:" → Punkt (Startpunkt)
// 2) „bis:" → Live-Vorschau (Messlinie + Distanz·Winkel im HUD); Klick
// setzt einen neuen Startpunkt (fortlaufend messen).
// Esc/Enter beendet. Rein visuell: `commit` bleibt immer leer, das Modell ändert
// sich nie — nur der Entwurf (Linie + HUD) zeigt das Mass.
import type {
Command,
CommandResult,
CommandState,
ToolDraft,
Vec2,
} from "../types";
const segLen = (a: Vec2, b: Vec2): number => Math.hypot(b.x - a.x, b.y - a.y);
const segAngleDeg = (a: Vec2, b: Vec2): number =>
(Math.atan2(b.y - a.y, b.x - a.x) * 180) / Math.PI;
interface MeasureStart extends CommandState {
phase: "start";
}
interface MeasureTo extends CommandState {
phase: "to";
from: Vec2;
cursor: Vec2 | null;
}
type MeasureState = MeasureStart | MeasureTo;
/** Vorschau: Messlinie from→cursor + Distanz·Winkel im HUD am Cursor. */
function measureDraft(from: Vec2, cursor: Vec2): ToolDraft {
const dist = segLen(from, cursor);
const ang = ((segAngleDeg(from, cursor) % 360) + 360) % 360;
return {
preview: [{ kind: "line", a: from, b: cursor }],
vertices: [from, cursor],
hud: { at: cursor, text: `${dist.toFixed(3)} m · ${ang.toFixed(1)}°` },
};
}
const idle = (): [CommandState, CommandResult] => [
{ phase: "start", lastPoint: null } as MeasureStart,
{ draft: null, done: true },
];
export const measureCommand: Command = {
name: "measure",
labelKey: "cmd.measure.label",
prompt: (s) => ((s as MeasureState).phase === "to" ? "cmd.measure.to" : "cmd.measure.start"),
accepts: () => ["point"],
options: () => [],
init: (): MeasureStart => ({ phase: "start", lastPoint: null }),
onInput: (state, input): [CommandState, CommandResult] => {
const s = state as MeasureState;
if (input.kind !== "point") {
return [s, { draft: s.phase === "to" && s.cursor ? measureDraft(s.from, s.cursor) : null }];
}
// Jeder Klick setzt den (neuen) Startpunkt — fortlaufendes Messen, bis Esc/Enter.
const ns: MeasureTo = { phase: "to", from: input.point, cursor: input.point, lastPoint: input.point };
return [ns, { draft: measureDraft(input.point, input.point) }];
},
onMove: (state, point): [CommandState, CommandResult] => {
const s = state as MeasureState;
if (s.phase !== "to") return [s, { draft: null }];
const ns: MeasureTo = { ...s, cursor: point };
return [ns, { draft: measureDraft(s.from, point) }];
},
onConfirm: (): [CommandState, CommandResult] => idle(),
onCancel: (): [CommandState, CommandResult] => idle(),
};