35299307d6
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.
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
// Text — modellverankerter Einzeltext als 2D-Element. Schritte:
|
|
// 1) „Ankerpunkt:" → Punkt (Position im Modell)
|
|
// 2) „Text:" → getippte Zeile (das Label) → commit Drawing2D {shape:"text"}
|
|
//
|
|
// Der Text-Schritt nimmt Freitext an (accepts:["text"]) — die Engine reicht die
|
|
// komplette getippte Zeile 1:1 durch (auch Zahlen/Kommas). Gerendert wird der
|
|
// Text in generatePlan als `drawingText` (SVG), analog zum DXF-Import.
|
|
|
|
import type { Drawing2D } from "../../model/types";
|
|
import { uniqueId } from "../../tools/types";
|
|
import type {
|
|
Command,
|
|
CommandContext,
|
|
CommandResult,
|
|
CommandState,
|
|
Project,
|
|
Vec2,
|
|
} from "../types";
|
|
|
|
/** Default-Schrifthöhe eines frei platzierten Texts in Modell-Metern. */
|
|
const DEFAULT_TEXT_HEIGHT_M = 0.25;
|
|
|
|
interface TextIdle extends CommandState {
|
|
phase: "point";
|
|
}
|
|
interface TextLabel extends CommandState {
|
|
phase: "label";
|
|
at: Vec2;
|
|
}
|
|
type TextState = TextIdle | TextLabel;
|
|
|
|
function appendText(p: Project, at: Vec2, text: string, ctx: CommandContext): Project {
|
|
const label = text.trim();
|
|
if (label === "") return p;
|
|
const d: Drawing2D = {
|
|
id: uniqueId("dr2d"),
|
|
type: "drawing2d",
|
|
levelId: ctx.level.id,
|
|
categoryCode: ctx.defaultCategoryCode,
|
|
geom: { shape: "text", at, text: label, height: DEFAULT_TEXT_HEIGHT_M, angle: 0 },
|
|
};
|
|
return { ...p, drawings2d: [...p.drawings2d, d] };
|
|
}
|
|
|
|
const idle = (): [CommandState, CommandResult] => [
|
|
{ phase: "point", lastPoint: null },
|
|
{ draft: null, done: true },
|
|
];
|
|
|
|
export const textCommand: Command = {
|
|
name: "text",
|
|
labelKey: "cmd.text.label",
|
|
prompt: (s) => ((s as TextState).phase === "label" ? "cmd.text.enter" : "cmd.text.point"),
|
|
accepts: (s) => ((s as TextState).phase === "label" ? ["text"] : ["point"]),
|
|
options: () => [],
|
|
init: (): TextIdle => ({ phase: "point", lastPoint: null }),
|
|
|
|
onInput: (state, input, ctx): [CommandState, CommandResult] => {
|
|
const s = state as TextState;
|
|
if (s.phase !== "label") {
|
|
if (input.kind !== "point") return [s, { draft: null }];
|
|
const ns: TextLabel = { phase: "label", at: input.point, lastPoint: input.point };
|
|
return [ns, { draft: null }];
|
|
}
|
|
if (input.kind !== "text") return [s, { draft: null }];
|
|
const at = s.at;
|
|
return [
|
|
{ phase: "point", lastPoint: null },
|
|
{ draft: null, done: true, commit: (p) => appendText(p, at, input.text, ctx) },
|
|
];
|
|
},
|
|
|
|
onMove: (state): [CommandState, CommandResult] => [state, { draft: null }],
|
|
onConfirm: (): [CommandState, CommandResult] => idle(),
|
|
onCancel: (): [CommandState, CommandResult] => idle(),
|
|
};
|