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
+224
View File
@@ -22,9 +22,13 @@ import type {
DrawingLevel,
HatchStyle,
LayerCategory,
LayoutOrientation,
LayoutPaperFormat,
LineStyle,
MasterLayout,
Project,
SiaCategory,
SliceTermination,
StairShape,
VerticalAnchor,
WallReferenceLine,
@@ -32,6 +36,7 @@ import type {
} from "../model/types";
import type { SnapSettings, ToolId } from "../tools/types";
import type { Selection } from "../state/selectionInfo";
import type { ScheduleKind } from "../export/exportSchedule";
// ── Darstellungsmodus-Steuerung je Dock-Inhalt ────────────────────────────
@@ -196,10 +201,38 @@ export interface PanelHostValue {
h: number,
anchor: { fx: number; fy: number },
) => void;
/**
* Verschiebt die Selektion um (dx,dy) Meter — generischer Move über das
* Transform-System (analog dem Move-Werkzeug/-Befehl), deckt Wand/Drawing2D/
* Extrusionskörper ab. Bei Decke/Öffnung/Treppe/Raum (Transform-Kern kennt
* diese Elementarten nicht) ein No-op.
*/
onMoveSelectionBy: (dx: number, dy: number) => void;
/**
* Dreht die Selektion um `deg` Grad um den Weltpunkt (cx,cy) — generischer
* Rotate über das Transform-System; deckt dieselben Selektionsarten ab wie
* {@link onMoveSelectionBy}.
*/
onRotateSelectionAround: (cx: number, cy: number, deg: number) => void;
/**
* Setzt die Länge einer Linien-Drawing2D (Endpunkt entlang der Richtung neu
* skaliert, Startpunkt bleibt fix). No-op, wenn die Selektion keine Linie ist.
*/
onSetDrawingLineLength: (length: number) => void;
/**
* Setzt den Radius einer Kreis-Drawing2D (Mittelpunkt bleibt fix). No-op,
* wenn die Selektion kein Kreis ist.
*/
onSetDrawingCircleRadius: (radius: number) => void;
// ── Wand-Attribute (Object-Info-Panel; wirken NUR auf die selektierte Wand) ─
/** Setzt die Lage der Wandachse über die Dicke (außen/mitte/innen). */
onSetWallReferenceLine: (ref: WallReferenceLine) => void;
/**
* Setzt die Terminierungs-Regel am Deckenanschluss (Zuschnitt, NICHT Priorität):
* "both" = heutiges Verhalten, "below"/"above" = die Wand endet an der Decke.
*/
onSetWallSliceTermination: (termination: SliceTermination) => void;
/** Weist der Wand einen Wandtyp-Preset (mehrschichtiger Aufbau) zu. */
onSetWallType: (wallTypeId: string) => void;
/** Setzt die Gesamtdicke einer einschichtigen Wand (Meter). */
@@ -216,6 +249,12 @@ export interface PanelHostValue {
onSetCeilingThickness: (thickness: number) => void;
/** Setzt/entfernt die OK-Bindung der Decke (`null` = Geschoss-Oberkante). */
onSetCeilingTop: (anchor: VerticalAnchor | null) => void;
/**
* Setzt/entfernt die UK-Bindung der Decke (`null` = OK Dicke). Optional,
* da bestehende Hosts (App.tsx) diesen Setter ggf. noch nicht verdrahtet
* haben — additive Erweiterung analog `onSetWallBottom`.
*/
onSetCeilingBottom?: (anchor: VerticalAnchor | null) => void;
// ── Öffnungs-Attribute (Object-Info-Panel; nur die selektierte Öffnung) ─
/** Wechselt die Art (Fenster/Tür); setzt bei Tür die Tür-Defaults. */
@@ -242,6 +281,8 @@ export interface PanelHostValue {
onSetOpeningDoorType: (doorType: "normal" | "wandoeffnung") => void;
/** Setzt die Sturzlinien-Darstellung der Tür. */
onSetOpeningLintelLines: (lintelLines: "keine" | "innen" | "aussen" | "beide") => void;
/** Weist der Öffnung einen Tür-/Fenstertyp (Bibliothek) zu; "" löst die Zuweisung. */
onSetOpeningType: (typeId: string) => void;
// ── Treppen-Attribute (Object-Info-Panel; nur die selektierte Treppe) ───
/** Setzt die Grundform (gerade/L/Wendel). */
@@ -256,6 +297,8 @@ export interface PanelHostValue {
onSetStairUp: (up: boolean) => void;
/** Setzt den Referenzpunkt der Laufbreite (links/mitte/rechts). */
onSetStairReferenz: (referenz: "links" | "mitte" | "rechts") => void;
/** Weist der Treppe einen Treppentyp (Bibliothek) zu; "" löst die Zuweisung. */
onSetStairType: (typeId: string) => void;
// ── Raum-Attribute (Object-Info-Panel; nur der selektierte Raum) ────────
/** Setzt den Raum-Namen. */
@@ -265,6 +308,26 @@ export interface PanelHostValue {
/** Öffnet den Rich-Text-Editor des Raum-Stempels (per ID). */
onEditRoomStamp: (roomId: string) => void;
// ── Extrusions-Attribute (truck-Integration; nur der selektierte Körper) ──
/** Setzt die Extrusionshöhe (Meter, > 0) — löst eine Re-Extrusion aus. */
onSetExtrudedSolidHeight: (height: number) => void;
/** Setzt die Verjüngung (0..1) — löst eine Re-Extrusion aus. */
onSetExtrudedSolidTaper: (taper: number) => void;
// ── Stützen-Attribute (Tragwerk; nur die selektierte Stütze) ────────────
/** Wechselt das Profil (Rechteck/Kreis); leitet ein sinnvolles Mass ab. */
onSetColumnProfileKind: (kind: "rect" | "round") => void;
/** Setzt die Breite (X) eines Rechteckprofils (Meter). */
onSetColumnWidth: (width: number) => void;
/** Setzt die Tiefe (Y) eines Rechteckprofils (Meter). */
onSetColumnDepth: (depth: number) => void;
/** Setzt den Radius eines Kreisprofils (Meter). */
onSetColumnRadius: (radius: number) => void;
/** Setzt die Höhe der Stütze (Meter, > 0). */
onSetColumnHeight: (height: number) => void;
/** Setzt die Drehung der Stütze (Grad; intern in Radiant gespeichert). */
onSetColumnRotation: (deg: number) => void;
// ── Site-/Kontext-Schicht (SitePanel) ───────────────────────────────────
/** Aktuelle Kontext-Objekte (Meshes/Konturen/Gelände) aus `project.context`. */
contextObjects: ContextObject[];
@@ -279,6 +342,166 @@ export interface PanelHostValue {
onGenerateTerrain: (contourSetId: string) => string | null;
/** Öffnet den (in App montierten) versteckten DXF-Datei-Dialog. */
onImportDxf: () => void;
// ── Element-Baum (Elemente-Panel) ───────────────────────────────────────
/**
* Selektiert EIN Bauteil-Vorkommen aus der Elementliste (`scheduleRows`) im
* Baum: leert alle Auswahl-Kanäle und setzt genau dieses Element im
* passenden Kanal (Wand/Decke/Fenster+Öffnung/Treppe/Extrusion). Liegt das
* Element auf einem anderen Geschoss, wechselt zuerst die aktive
* Zeichnungsebene. Für Bauteilklassen ohne Auswahl-Kanal (aktuell nur die
* Legacy-„Tür"-Datensätze aus `project.doors`, die kein Werkzeug mehr
* anlegt) ein No-op. `opts.zoom` (Shift-Klick/Doppelklick in der Baumzeile)
* passt die Plan-Ansicht zusätzlich ein.
*/
onSelectScheduleRow: (
kind: ScheduleKind,
id: string,
floorId: string | undefined,
opts?: { zoom?: boolean },
) => void;
// ── Ausschnitte / View-Snapshots (Ausschnitte-Panel, DOSSIER A2) ─────────
// Die Liste selbst liest das Panel über `project.viewSnapshots`. Die
// Erfassungs-/Anwendungslogik (Setter/rAF) lebt in App.tsx; hier nur die
// immutablen Handler.
/**
* Erfasst den aktuellen Darstellungszustand als neuen, benannten Ausschnitt.
* `folderId` legt ihn direkt im angegebenen Ordner ab (sonst Wurzelebene) —
* additiv, Alt-Aufrufe ohne Ordner-Argument bleiben gültig.
*/
onCaptureViewSnapshot: (name: string, folderId?: string) => void;
/** Stellt alle Felder eines Ausschnitts wieder her (Geschoss/Sichtbarkeit/Overrides/Ansicht). */
onApplyViewSnapshot: (id: string) => void;
/** Benennt einen Ausschnitt um. */
onRenameViewSnapshot: (id: string, name: string) => void;
/** Löscht einen Ausschnitt. */
onDeleteViewSnapshot: (id: string) => void;
// ── Ausschnitte-Baum (Ordner, DOSSIER A2) ───────────────────────────────
// Die Listen liest das Panel über `project.viewSnapshots`/
// `project.viewSnapshotFolders`; pure CRUD in state/viewSnapshotFolders.ts.
/** Legt einen neuen Ordner an (optional in `parentId`); liefert die neue Id. */
onAddViewSnapshotFolder: (parentId?: string) => string;
/** Benennt einen Ausschnitte-Ordner um. */
onRenameViewSnapshotFolder: (id: string, name: string) => void;
/**
* Löscht einen Ausschnitte-Ordner (enthaltene Ausschnitte + Unterordner wandern
* auf die Elternebene, kein Datenverlust).
*/
onDeleteViewSnapshotFolder: (id: string) => void;
/**
* Verschiebt einen Ausschnitt per Drag&Drop in einen Ordner (`folderId`) bzw.
* auf die Wurzelebene (`null`).
*/
onMoveViewSnapshotToFolder: (snapshotId: string, folderId: string | null) => void;
/**
* Verschiebt einen Ordner per Drag&Drop unter einen Elternordner (`parentId`)
* bzw. auf die Wurzelebene (`null`). Zyklen (Ordner in sich/seinen Nachfahren)
* werden verworfen (No-op).
*/
onMoveViewSnapshotFolder: (folderId: string, parentId: string | null) => void;
/**
* Id des aktuell „angewählten" Ausschnitts (Footer-Bar sichtbar) oder `null`.
* Bleibt gesetzt, solange der Live-Zustand exakt dem Ausschnitt entspricht;
* App löscht ihn, sobald der Nutzer eine erfasste Grösse ändert.
*/
selectedViewSnapshotId: string | null;
/** Namen aller gespeicherten Ebenen-Kombinationen (localStorage). */
listLayerCombos: () => string[];
/** Lädt die `codes`-Map einer Ebenen-Kombination (`null`, wenn unbekannt). */
loadLayerCombo: (name: string) => Record<string, boolean> | null;
/** Namen aller gespeicherten Zeichnungs-Kombinationen (localStorage). */
listDrawingCombos: () => string[];
/** Lädt die `ids`-Map einer Zeichnungs-Kombination (`null`, wenn unbekannt). */
loadDrawingCombo: (name: string) => Record<string, boolean> | null;
// ── Layouts / Masterlayouts (Layouts-Panel, DOSSIER A3) ─────────────────
// Die Listen liest das Panel über `project.layouts`/`project.masterLayouts`.
// Die Mutationslogik lebt in App.tsx (setProject); pure CRUD in
// panels/layoutModel.ts.
/** Legt ein neues, leeres Layout an und öffnet den Editor. */
onAddLayout: (
name: string,
paper: LayoutPaperFormat,
orientation: LayoutOrientation,
) => void;
/** Benennt ein Layout um. */
onRenameLayout: (id: string, name: string) => void;
/** Löscht ein Layout (schliesst ggf. den offenen Editor). */
onDeleteLayout: (id: string) => void;
/** Öffnet ein Layout im schwebenden Layout-Editor. */
onOpenLayout: (id: string) => void;
/** Legt ein neues Masterlayout an. */
onAddMasterLayout: (name: string) => void;
/** Benennt ein Masterlayout um. */
onRenameMasterLayout: (id: string, name: string) => void;
/** Löscht ein Masterlayout (löst Bindungen der Layouts). */
onDeleteMasterLayout: (id: string) => void;
/** Ändert Felder eines Masterlayouts (Titelblock/Papier/Rahmen). */
onPatchMasterLayout: (id: string, patch: Partial<MasterLayout>) => void;
// ── Layouts-Baum (Ordner) + Erstell-Dialoge (DOSSIER A3) ────────────────
/** Legt einen neuen Ordner an (optional in `parentId`); liefert die neue Id. */
onAddLayoutFolder: (parentId?: string) => string;
/**
* Legt einen neuen MASTER-Ordner (`kind:"master"`) an (optional in `parentId`);
* liefert die neue Id. Analog {@link onAddLayoutFolder}, aber im Master-Baum.
*/
onAddMasterFolder: (parentId?: string) => string;
/** Benennt einen Ordner um (Layout- wie Master-Ordner, per Id). */
onRenameLayoutFolder: (id: string, name: string) => void;
/** Löscht einen Ordner (Inhalt wandert auf die Elternebene, kein Datenverlust). */
onDeleteLayoutFolder: (id: string) => void;
/**
* Löscht einen Master-Ordner (enthaltene Masterlayouts + Unterordner wandern
* auf die Elternebene, kein Datenverlust). Analog {@link onDeleteLayoutFolder}.
*/
onDeleteMasterFolder: (id: string) => void;
/**
* Legt ein Layout mit expliziten Startwerten (Master-Vorlage ODER freie
* Grösse) an und liefert dessen Id (das Panel versetzt es in Inline-Rename).
* Öffnet das Blatt NICHT automatisch (anders als {@link onAddLayout}).
*/
onCreateLayout: (opts: CreateLayoutOptions) => string;
/** Legt ein Masterlayout mit Grösse an und liefert dessen Id. */
onCreateMasterLayout: (opts: CreateMasterLayoutOptions) => string;
/** Exportiert alle Layouts eines Ordners als EIN Mehrseiten-PDF. */
onExportFolderPdf: (folderId: string) => void;
/**
* Verschiebt ein Layout per Drag&Drop in einen Ordner (`folderId`) bzw. auf
* die Wurzelebene (`null`).
*/
onMoveLayoutToFolder: (layoutId: string, folderId: string | null) => void;
/**
* Verschiebt einen Ordner per Drag&Drop unter einen Elternordner (`parentId`)
* bzw. auf die Wurzelebene (`null`). Zyklen (Ordner in sich/seinen Nachfahren)
* werden verworfen (No-op).
*/
onMoveFolderToFolder: (folderId: string, parentId: string | null) => void;
}
/** Startwerte eines neuen Layouts (aus dem „Neues Layout"-Dialog). */
export interface CreateLayoutOptions {
/** Zielordner; ohne → Wurzelebene. */
folderId?: string;
/** Gebundenes Masterlayout (erbt dessen Grösse); ohne → freie Grösse. */
masterId?: string;
paper: LayoutPaperFormat;
orientation: LayoutOrientation;
customWidthMm?: number;
customHeightMm?: number;
}
/** Startwerte eines neuen Masterlayouts (aus dem „Neues Masterlayout"-Dialog). */
export interface CreateMasterLayoutOptions {
/** Ziel-Master-Ordner; ohne → Wurzelebene des Master-Baums. */
folderId?: string;
paper: LayoutPaperFormat;
orientation: LayoutOrientation;
customWidthMm?: number;
customHeightMm?: number;
}
// Re-Export der Modelltypen, die die Panels im selben Atemzug brauchen — so
@@ -286,6 +509,7 @@ export interface PanelHostValue {
export type { DrawingLevel, LayerCategory };
export type { SnapSettings, ToolId };
export type { Selection } from "../state/selectionInfo";
export type { ScheduleKind, ScheduleRow } from "../export/exportSchedule";
// ── Hook ───────────────────────────────────────────────────────────────────