Doku: STATUS.md (Codebase-Analyse) + Kern-Docs an den Ist-Zustand angeglichen

Vollständige Bestandsaufnahme der Codebasis als neue STATUS.md (Kennzahlen,
Feature-Inventar, Mist-Liste: toter Code, verwaiste WASM-Crates,
Doku-Widersprüche). ARCHITECTURE.md/README.md/CONVENTIONS.md waren noch auf
dem Tag-1-Planungsstand (Electron/Three.js/OpenCascade/Zustand/HLR-Worker) und
beschrieben nicht mehr, was tatsächlich gebaut wurde (eigene Rust/WASM-Engines,
eigener Store, analytische Rust-Schnitt-Pipeline, Tauri auf macOS + Electron
auf Linux). ROADMAP.md und HANDOVER.md als historisch markiert (Hinweis-Box),
Inhalt unverändert.
This commit is contained in:
2026-07-21 13:35:58 +02:00
parent 956a85d93f
commit a6c2c04736
7 changed files with 798 additions and 470 deletions
+297 -359
View File
@@ -1,452 +1,390 @@
# Architektur — Standalone Browser-BIM (cad) # Architektur — Dossier (Desktop-CAAD)
> Stand: 2026-06-29 > Stand: 2026-07-21 (grundlegend überarbeitet — siehe [STATUS.md](STATUS.md) für
> Vision, Phasen und Backlog: [ROADMAP.md](ROADMAP.md). Konventionen: [CONVENTIONS.md](CONVENTIONS.md). > die volle Bestandsaufnahme inkl. Mist-Liste, die diese Überarbeitung begründet).
> Detail-Designs: [docs/design/elements.md](docs/design/elements.md) · > Vision/Phasen (historisch, Tag-1-Stand): [ROADMAP.md](ROADMAP.md). Konventionen:
> [docs/design/plans-output.md](docs/design/plans-output.md) · > [CONVENTIONS.md](CONVENTIONS.md). Detail-Designs (teils ebenfalls veraltet,
> [docs/design/resources-graphics.md](docs/design/resources-graphics.md). > siehe Hinweis in [docs/README.md](docs/README.md)): [docs/design/](docs/design/).
Dieses Dokument beschreibt, **wie** die Standalone-Browser-App gebaut wird und Dieses Dokument beschreibt, **wie** Dossier tatsächlich gebaut ist — nicht wie
wie sie die Konzepte des DOSSIER-Rhino-Plugins in Browser-Äquivalente übersetzt. es am ersten Tag geplant war. Dossier ist die eigenständige Neuimplementierung
DOSSIER ist ein Rhino-8-Plugin (Python + React-WebView); `cad` ist die *eigen­ des Rhino-Plugins [DOSSIER](https://git.openbureau.ch/karim/dossier): dieselbe
ständige* Browser-Variante: kein Rhino-Document, kein `doc.Strings`, kein Denkweise (Geschosse, Kategorie-Ebenen, mehrschichtige Bauteile,
IronPython — stattdessen ein **eigenes typisiertes Datenmodell in TypeScript**, Prioritäts-Verschneidung), aber als **native Desktop-App** mit einem **eigenen
gerendert über **Three.js** (3D) und **SVG** (Plan), gespeichert als **JSON-Datei**. typisierten Datenmodell in TypeScript** und **zwei eigenen Rust/WASM-Rendering-
Engines** („Nordstern") statt Rhino-Dokument/IronPython.
Alle Bezeichner im Code sind **englisch** (Vectorworks-Terminologie); Prosa und **Desktop-Rahmen (plattformabhängig, wegen WebGPU):** auf **macOS Tauri**
UI-Texte sind deutsch. Einheiten intern in **Metern**. (WKWebView unterstützt WebGPU), auf **Linux Electron/Chromium** (Tauris
Linux-Webview WebKitGTK unterstützt WebGPU nicht zuverlässig — die
render2d/render3d-Engines brauchen es). Beide teilen dieselbe React-App und
randlose Titelleiste; Laufzeit-Erkennung über `window.__TAURI__` bzw.
`window.dossierWindow` (Electron-`contextBridge`). Details: STATUS.md §2.7.
Alle Bezeichner im Code sind **englisch**; Prosa und UI-Texte sind deutsch.
Einheiten intern in **Metern**.
--- ---
## 0. Leitprinzip — ein Modell, viele Darstellungen ## 0. Leitprinzip — ein Modell, viele Darstellungen
Das semantische Gebäudemodell ist die **einzige Wahrheit**. Jede Sicht (3D, Das semantische Gebäudemodell (`Project`) ist die **einzige Wahrheit**. Jede
Grundriss, Schnitt, Ansicht) ist eine **reine Ableitung** (`derive`) daraus. Sicht (3D, Grundriss, Schnitt, Ansicht) ist eine **reine Ableitung** daraus.
Darstellung (Detailgrad, Stile, Schraffuren, Overrides) wird **beim Rendern** Darstellung (Detailgrad, Stile, Schraffuren, Overrides) wird **beim Rendern**
angewandt, nie in die Geometrie eingebacken. angewandt, nie in die Geometrie eingebacken. Dieses Prinzip hat sich über drei
Wochen und ~125.000 Zeilen Code bewährt und wird strikt gehalten — es ist der
einzige Teil der ursprünglichen Architektur-Vision, der **unverändert** Bestand
hat. Alle konkreten Technologie-Entscheidungen darunter (Rendering-Engine,
State-Store, Schnitt-Mechanismus) sind anders gelaufen als am Tag 1 geplant;
Details dazu in [STATUS.md](STATUS.md) §5.
``` ```
┌──────────────────────────────────────────┐ ┌──────────────────────────────────────────┐
│ Project (semantisches Modell, JSON) │ ← einzige Wahrheit │ Project (semantisches Modell, JSON) │ ← einzige Wahrheit
│ resources · types · designLevels · │ resources · types · drawingLevels · │
│ layers · elements │ layers · walls/doors/openings/stairs/…
└──────────────┬───────────────────────────┘ └──────────────┬───────────────────────────┘
│ pure derive() │ pure derive()
┌───────────────────────┼────────────────────────────┐ ┌───────────────────────┼────────────────────────────┬───────────────
▼ ▼ ▼ ▼ ▼ ▼
Scene3D (Three.js) PlanModel → SVG SectionModel → SVG 3D-Viewport 2D-Plan (generatePlan) 3D-Live-Schnitt Export
buildScene() generatePlan() generateSection() (HLR) Viewport3D (three.js) PlanView (SVG) · glPlan (GL2) render3d/section IFC/DXF/
│ │ │ ODER Wasm3DViewport ODER render2d (Rust/WGSL) (Rust, analytisch)PDF/STL
└────────── beide lesen dieselben joins/components/styles ──────────┘ (Rust/wgpu, Default)
│ │ │ │
└────────── alle lesen dieselben joins/components/styles ─────────────┘
``` ```
Das steht im Spike bereits: `generatePlan.ts` und `Viewport3D.tsx` extrudieren
**dasselbe** gehrte Band-Polygon (`clippedBand`). Diese Symmetrie ist der Kern
und wird beim Ausbau strikt gehalten.
--- ---
## 1. Repo-Struktur (Ziel) ## 1. Repo-Struktur (IST-Zustand)
Wächst aus dem heutigen `src/` (model/plan/viewport/ui). Module sind **klein und
fachlich geschnitten** — wir vermeiden bewusst den `elemente.py`-Monolithen
(7244 LOC) aus DOSSIER (dort als Schwachstelle #4.1 dokumentiert).
``` ```
src/ src/
model/ model/ Project-Schema (types.ts, ~2500 LOC), joins.ts (Wand-Gehrung/
types.ts // Project, DesignLevel, LayerCategory, Element-Union (existiert) -Prio-Stösse), parametricWalls.ts, roomStamp.ts, terrain.ts,
geometry.ts // 2D/3D-Mathe ohne Kernel (existiert) geoRebase.ts, sampleProject.ts
joins.ts // Wand-Verschneidung (Gehrung; später Prio-T/X) (existiert) geometry/ 2D-Kernel (kernel2d.ts: offset/trim/fillet/split — LIVE, siehe
sampleProject.ts // Demo-Haus (existiert) §3), ceiling/opening/roomArea/roomBoundary/stair/roof/column.ts,
project.ts // Factory, Defaults, Migrationen polygonHoles.ts
selectors.ts // abgeleitete Reads (visibleCodes, elementsOnLevel…) commands/ Rhino-artiges Kommandosystem: types/registry/engine/parseInput.ts
ids.ts // newId(prefix) — UUID-Erzeugung + cmds/ (ein Modul je Kommando: wall, opening, stair, roof,
elements/ // pro Bauteil ein Modul (Daten + Generierung) column, room, line/rect/circle/arc, move/mirror/copy/offset/
wall.ts opening.ts slab.ts stair.ts roof.ts structure.ts space.ts trim/join, extrude, import, terrain, measure, georef, …)
resources/ tools/ Interaktive Zeichenwerkzeuge, snapping.ts, transform.ts (Grips)
componentManager.ts hatchManager.ts lineManager.ts symbolLibrary.ts compute/ Compute-Boundary: leitet Ops (aktuell nur computeJoins) unter
overrides.ts // regelbasierte Engine (Condition → Action) Tauri via `invoke` an Rust weiter, sonst TS-Fallback
store/ plan/ generatePlan.ts (2D-Ableitung), PlanView.tsx (SVG + Pan/Zoom/
store.ts // Zustand-Store: { project, ui }, Aktionen, Undo/Redo Grips), glPlan/ (eigener WebGL2-Renderer), toSection.ts/
persistence.ts // Datei save/load (JSON), Autosave (IndexedDB) toElevation.ts (Schnitt/Ansicht-Ableitung), toWalls3d.ts,
history.ts // Undo/Redo-Ring toRenderScene.ts (Szene für Rust-render2d), wallMeshCut.ts
plan/ viewport/ Viewport3D.tsx (three.js, „Free") UND Wasm3DViewport.tsx
generatePlan.ts // Grundriss aus Footprint + Symbolik (existiert) (Rust/wgpu „Nordstern", Default/editierbar), raycast3d.ts
generateSection.ts // Schnitt/Ansicht aus 3D-Projektion (HLR-Worker) section/ TOTER Code (OCCT-WASM-HLR-Spike, keine Aufrufer mehr) — Schnitt
PlanView.tsx // SVG-Renderer + Pan/Zoom/Grips (existiert) läuft über render3d/section.rs, siehe §4.3
primitives.ts // Primitive-Union, SVG-Serializer, DXF/PDF-Export export/ exportIfc.ts (IFC4), exportDxf.ts/dxfWriter.ts, exportPdf.ts,
viewport/ layoutPdf.ts (Mehrseiten-PDF pro Ordner), exportMesh.ts (STL/OBJ),
Viewport3D.tsx // Three.js-Szene aus dem Modell (existiert) exportSchedule.ts (CSV-Bauteilliste), sceneToPrintSvg.ts
scene.ts // buildScene(project) → THREE.Group (Layer-Spiegel) materials/ ambientcg.ts (Live-Suche ambientCG-API), library.ts (13
clip.ts // Plan-/Schnitt-Clipping über THREE.Plane gebündelte Starter-Materialien), runtime.ts (PBR-Material aus
camera.ts // Kamera-Presets, Norden-Rotation ComponentMaterial via three.js TextureLoader)
sheets/ io/ DXF/DWG-Import, Swisstopo (swissBUILDINGS3D/-ALTI3D/SWISSIMAGE,
layout.ts // Sheet/Viewport-Datenmodell LV95↔WGS84), OSM-Overpass, .lin/.pat-Parser, projectFile.ts
SheetEditor.tsx // Plansatz-Editor (.obp Speichern/Laden, Tauri-Lock)
exportPdf.ts // Vektor-PDF (svg → pdf-lib / jsPDF) overrides/ Regelbasierte Override-Engine (Bedingung → Aktion)
workers/ state/ Eigener Store auf `useSyncExternalStore` (KEIN Zustand/Redux):
geometry.worker.ts // Booleans (Öffnungen) + HLR via Comlink store.ts + Slices (project/history/selection/view/layout/site/
ui/ notify), appStore.ts komponiert sie
App.tsx Navigator panels // React-Oberfläche (heute in App.tsx, wird gesplittet) panels/ Dock-/Floating-Panel-System (Dock/FloatingPanel/TabStrip/
registry/layout) + Panels (Tools, Attributes, ObjectInfo, Layers,
DrawingLevels, Site, RoomBalance, Elements, ViewSnapshots, Layouts)
ui/ App.tsx (Shell, **7.130 LOC — noch nicht auf dünne Shell
reduziert**, siehe STATUS.md §4.3), TopBar, ResourceManager.tsx
(Material-/Hatch-/Line-/Typ-Editoren, natives Fenster),
ContextMenu, CommandLine.tsx, LayoutSheet/-Menu, ribbon/
native/ NUR Tauri: native Fenster (Resources/Settings/DrawingLevels/
LayerSettings/ContextImport), macOS-Menüleiste, Fenster-Chrome —
jede Funktion no-opt via `isTauriRuntime()` im Browser
editors/ booleanOps.ts (2D-Boolean via polygon-clipping), splitJoin.ts
text/ Rich-Text (richText.ts, RichTextEditor.tsx, renderHtml.ts)
theme/ Hell/Dunkel, Akzentfarben
i18n/ de.ts/en.ts Wörterbücher, eigener t()-Mechanismus
engine/ Reine WASM-Lade-Glue: engine3d.ts (pkg3d), truckSolid.ts
(pkgTruck), plan/useWasmPlanRenderer.ts (pkg) — pkgGeometry und
pkgDwgImport sind gebaut, aber unbenutzt (siehe STATUS.md §4.1)
src-tauri/
src/ Tauri-Host (Fenster, native Dialoge, fs4-Exklusiv-Lock)
render2d/ 2D-Plan-GPU-Renderer (wgpu/WGSL, + glyphon-Text), auch → WASM
render3d/ „Nordstern" 3D-Engine (wgpu/WGSL): Wandextrusion + Schicht-
bänder + Gehrung, LIVE 2D=3D-Schnitt (section*.rs, analytisch,
kein HLR), Kanten-Extraktion, Materialtextur-Arrays,
Aerial-Drape, Render-Styles (Shaded/White/Textured/Wireframe/
Hidden/ShadedEdges) — auch → WASM
kernel2d/ Rust-Port von geometry/kernel2d.ts — NUR Paritätstest, nicht
produktiv (WASM war < 100 Wänden langsamer als TS)
geometry/ Wand-Join-Mathe — Rust-Port, UNBENUTZT (kein Aufrufer)
trucksolid/ CSG/Extrusion (`truck`-Crate + `csgrs`-Booleans) → WASM,
genutzt vom Extrude-Kommando; Boolean NICHT an Wände/
Öffnungen angeschlossen
dwgimport/ DXF-Parser-Spike (`acadrust`) → WASM, UNBENUTZT (DWG-Import
läuft über npm `@mlightcad/libredwg-web`)
``` ```
Jedes Crate unter `src-tauri/` ausser dem Host ist ein **eigenständiges
Cargo-Package** (kein gemeinsamer Workspace), das sowohl headless
(`cargo test`) als auch per `wasm-pack --features web` baut und dann via
`src/engine/pkg*/` von der TS-Seite geladen wird (`npm run build:engine{,3d,
Geometry,Kernel2d,DwgImport}`/`build:truck`).
--- ---
## 2. Datenmodell ## 2. Datenmodell
### 2.1 Zwei unabhängige Achsen (DOSSIER-Modell, im Spike umgesetzt) ### 2.1 Zwei unabhängige Achsen (unverändert gegenüber der Vision)
DOSSIER trennt zwei orthogonale Konzepte, persistiert als zwei getrennte 1. **Zeichnungsebenen** (`DrawingLevel`) — Geschoss (`kind:"floor"`,
JSON-Bäume (`dossier_zeichnungsebenen`, `dossier_ebenen`). Wir übernehmen das `floorHeight`/`cutHeight`/`baseElevation`), Schnitt/Ansicht
1:1 in `types.ts` (existiert bereits als `drawingLevels` + `layers`): (`kind:"section"|"elevation"`), oder freie Zeichnung (`kind:"drawing"`).
2. **Ebenen** (`LayerCategory`) — das Grafik-Kategorie-Schema (Baum,
`{code, name, color, lw, visible, locked, hatch?, children}`), in jedem
Geschoss gültig. Codes 1:1 aus DOSSIER (`00 Raster · 01 Vermessung ·
20 Wände (└21 Türen/Fenster, 25 Stützen) · 30 Decken · 31 Dächer ·
40 Treppen · 50 Text · 60 Räume · 80 Plangrafik …`).
1. **Zeichnungsebenen** (`DesignLevel` / `DrawingLevel`) — die obersten ### 2.2 Elemente — typisierte Arrays statt `Element[]`-Union
Dokument-Abschnitte. Zwei produktive Arten:
- **Geschoss** (`kind:"floor"`): `floorHeight`, `cutHeight`,
`baseElevation` (akkumuliert via `recomputeFloorElevations`), `visible`/`locked`.
- **Schnitt/Ansicht** (`kind:"section"|"elevation"`): `linePoints`,
`directionSign`, Höhenbereich, Tiefe.
- **Zeichnung** (`kind:"drawing"`): freie 2D-Ebene ohne Geschossbezug.
2. **Ebenen** (`LayerCategory`) — das **Grafik-Kategorie-Schema**, das in *jedem*
Geschoss gilt. Baum mit `{code, name, color, lw, visible, locked, hatch?, children}`.
Codes 1:1 wie DOSSIER (`DEFAULT_LAYER_SCHEMA` aus `launcher/src/App.jsx`):
`00 Raster · 01 Vermessung · 20 Wände (└21 Türen/Fenster, 22 Möbel, 25 Stützen) ·
30 Decken · 31 Dächer · 35 Träger · 50 Text · 60 Plangrafik …`
Ein **Element** kennt sein **Geschoss** (`floorId`) *und* seine **Ebene** Anders als ursprünglich geplant hält `Project` (`src/model/types.ts:2095`)
(`categoryCode`). Sichtbarkeit ergibt sich aus dem Schnitt (Geschoss `geschoss × code` **pro Bauteiltyp ein eigenes (meist optionales) Array**:
Matrix), genau wie DOSSIERs `apply_visibility(z_mode, e_mode)` in `layer_builder.py`.
> **Begriffsnotiz:** Heute heißt der Typ im Code `DrawingLevel`. ROADMAP §5 nennt
> die Modell-Eingabe (Geschosse) Vectorworks-konform **Design Layer** und die
> abgeleitete Ausgabe **Drawing Layer / Sheet**. Wir behalten `DrawingLevel` als
> Union (kind=floor ≙ Design Layer, kind=section/elevation/drawing ≙ Drawing
> Layer) und führen `Sheet` erst separat ein (§2.5, plans-output.md). Kein Rename
> ohne expliziten Auftrag.
### 2.2 Ressourcen-Bibliotheken (Vectorworks-Stil)
Neuer Block `Project.resources` (heute provisorisch als flache `materials[]`).
Alles verweist **per id** — zentral änderbar. Details: resources-graphics.md.
```ts ```ts
interface Resources { interface Project {
lineStyles: LineStyle[]; // Line Manager: { id, name, weight(mm), color, dash:number[] } walls: Wall[];
hatches: Hatch[]; // Hatch Manager: { id, name, pattern, scale, angle, lineStyleId } ceilings?: Ceiling[];
components: Component[]; // Component Manager (= DOSSIER-Material erweitert): roofs?: Roof[];
// { id, name, hatchId, color3d, texture3d?, joinPriority } doors: Door[]; // ÄLTER — siehe openings; bewusste Doppelspur
openings?: Opening[]; // NEUER, allgemeiner: kind:"window"|"door"
stairs?: Stair[];
rooms?: Room[];
columns?: Column[];
extrudedSolids?: ExtrudedSolid[];
drawings2d: Drawing2D[];
context?: ContextObject[]; // Terrain/Importe — NICHT semantisch
parametricWalls?: ParametricWall[];
overrideRules?: OverrideRule[];
viewSnapshots?: ViewSnapshot[];
viewSnapshotFolders?: ViewSnapshotFolder[];
layouts?: Layout[];
masterLayouts?: MasterLayout[];
layoutFolders?: LayoutFolder[];
// + Bibliotheken: lineStyles, hatches, components, wallTypes, roofTypes?,
// doorTypes?, windowTypes?, stairTypes?, ceilingTypes?
// + drawingLevels, layers, geoAnchor?, referenceElevationMasl?
} }
``` ```
`joinPriority` ist DOSSIERs `_MATERIAL_PRIO` (Beton 800 … Putz 100) als Ein `Element`-Typalias existiert noch (`kind:"door"|"window"` etc.), wird aber
**Daten** statt Hardcode — steuert die Prioritäts-T-Verschneidung (Risiko #1). nirgends im Code referenziert — Selektion/Element-Baum arbeiten direkt auf den
typisierten Arrays. `doors`/`openings` sind eine **bekannte, noch nicht
konsolidierte Doppelspur** (PENDENZEN.md).
### 2.3 Aufbauten (mehrschichtige Typen) ### 2.3 Ressourcen & Aufbauten (unverändert gegenüber der Vision)
```ts ```ts
interface Resources { lineStyles: LineStyle[]; hatches: Hatch[]; components: Component[]; }
interface WallType { id; name; layers: { componentId; thickness }[]; } interface WallType { id; name; layers: { componentId; thickness }[]; }
interface SlabType { id; name; layers: { componentId; thickness }[]; }
``` ```
Schichtdicke liegt am Layer, **Priorität am Component** (so muss man Prio nur `joinPriority` sitzt am `Component` (Daten statt Hardcode) und steuert die
einmal pflegen). 3D und Plan lesen dieselben `layers[]`. Prioritäts-T-/X-Verschneidung — **fertig implementiert**, inklusive Rust-Port
für den 3D-Live-Schnitt (`section_boolean.rs` = Port von
`toSection.ts::subtractDominantBands`).
### 2.4 Elemente ### 2.4 Layouts / Ausschnitte
Diskriminierte Union `Element` (heute `Wall | Door`), erweitert um `ViewSnapshot` (Kamera/Massstab/Detailgrad/Sichtbarkeiten/Override-Preset) in
`Window | Slab | Stair | Roof | Column | Beam | Space | Draw2d`. Jedes Element: `ViewSnapshotFolder`-Bäumen; `Layout` (Papierformat, mehrere
`{ id, type, floorId, categoryCode, ... }`. Gehostete Elemente (Tür/Fenster) `LayoutViewport`s, freie `LayoutAnnotation`s, optionale `MasterLayout`-Vererbung)
tragen `hostWallId` statt `floorId` (Geschoss ergibt sich aus der Wand). Volle in `LayoutFolder`-Bäumen. Beide gehören ins Dokument (nicht LocalStorage).
Felder: elements.md.
### 2.5 Pläne / Output (eigene Achse)
```ts
interface Sheet { // Plansatz-Blatt (≙ DOSSIER Layout/PageView)
id; name; paper: "A0".."A4"|"Letter"; landscape: boolean;
viewports: SheetViewport[]; // platzierte Ableitungen + Titelblock
folder?: string;
}
interface SheetViewport { // ≙ DOSSIER Detail + gebundener Ausschnitt
id; rect: Rect; sourceViewId: string; // referenziert DrawingLevel oder ViewSnapshot
scale: number; // 1:N
}
interface ViewSnapshot { // ≙ DOSSIER Ausschnitt (View-Snapshot)
id; name; folder?;
camera: CameraState; scale: number; detailLevel: DetailLevel;
visibility: VisibilityState; // pro Geschoss/Ebene
overrides?: { presetId?; enabled };
}
```
--- ---
## 3. State, Persistenz, Undo/Redo ## 3. State, Persistenz, Undo/Redo
### 3.1 Store — Zustand ### 3.1 Store — eigener `useSyncExternalStore`, nicht Zustand
Heute: `useState<Project>` in `App.tsx`, immutabel via `setProject`. Das skaliert Entgegen der ursprünglichen Empfehlung (Zustand-Library) wurde ein
nicht für Werkzeuge + Undo. Migration auf **Zustand** (ROADMAP §4): **abhängigkeitsfreier Store** gebaut (`src/state/store.ts`, gleiches Muster wie
`src/i18n`): `createStore()` komponiert Slice-Fabriken über eine gemeinsame
`RootState`. `appStore.ts` fügt zusammen: `projectSlice` (Projekt + Undo/Redo,
`setProject` als einziger Mutations-Einstiegspunkt), `historySlice`,
`selectionSlice`, `viewSlice`, `layoutSlice`, `siteSlice`, `notifySlice`
(Toast/Confirm-Ersatz für `window.alert`, in Tauris WKWebView deaktiviert).
Komponenten lesen über `useStore(selector)`.
```ts **Nicht umgesetzt** (geplant in `docs/design/state-architecture.md`): die
interface AppState { Extraktion von View-Routing nach `src/views/` und Kontextmenü-Aufbau nach
project: Project; // das Dokument `src/menus/` — beide Ordner existieren nicht, diese Logik liegt weiterhin
ui: { // flüchtig, NICHT persistiert/undobar inline in `App.tsx` (7.130 Zeilen). Siehe STATUS.md §4.3.
activeLevelId; activeViewType; selection: string[];
activeTool: ToolId; hover; cameraByView; planTransform;
};
// Aktionen mutieren via Immer-Producer; jede Modell-Mutation pusht History.
apply(mutator: (p: Project) => void): void;
undo(): void; redo(): void;
}
```
**Trennung Modell ↔ UI** ist hart: nur `project` ist undobar und wird gespeichert. ### 3.2 Persistenz
`ui` (Auswahl, Kamera, aktives Werkzeug) lebt separat. Das ersetzt DOSSIERs
`sc.sticky`-Bus — wo DOSSIER cross-modul über Sticky-Keys kommuniziert, lesen bei
uns einfach alle Komponenten denselben Store (reaktiv). **Kein Sticky, kein
Bridge-Polling, keine `is not None`-Lücken** (DOSSIER-Schwachstelle #4.4).
### 3.2 Persistenz — JSON-Datei statt `doc.Strings` - **Datei:** eigenes `.obp`-Format (`src/io/projectFile.ts`), unter Tauri über
native Speichern/Öffnen-Dialoge (`plugin-fs`/`plugin-dialog`), im Browser
DOSSIER speichert *alles* in `doc.Strings` (Key-Value am Rhino-Document) + Blob-Fallback. Ältere `.json`-Projekte bleiben ladbar.
globale Presets als JSON-Dateien im User-Home. Browser-Äquivalent: OS-Level-Exklusiv-Lock (`fs4`-Crate, `src-tauri/src/lock.rs`) verhindert
Doppelöffnen desselben Projekts.
| DOSSIER | Browser (cad) | - **Compute-Boundary:** `src/compute/index.ts` leitet einzelne Operationen
|---|---| (aktuell nur `computeJoins`) unter Tauri via `invoke` an eine native Rust-
| `doc.Strings.SetString(key, json)` | Feld im `Project`-Objekt (ein JSON-Baum) | Implementierung weiter; im Browser bzw. für nicht migrierte Ops (Room-
| `.3dm`-Datei mit eingebetteten Strings | **`.cad.json`-Datei** via File System Access API | Detection, DWG-Parsing) läuft die TS-Implementierung.
| Globale Presets (`~/Library/.../*.json`) | **LocalStorage** (`cad.presets.*`) + Export/Import |
| Launcher schreibt Settings-Datei | **IndexedDB** Autosave (Phase 5) |
```ts
// persistence.ts
const FORMAT_VERSION = 1;
function serialize(p: Project): string // JSON.stringify({ version, project })
function deserialize(text: string): Project // + migrate(version) bis aktuell
async function saveToFile(p: Project): Promise<void> // showSaveFilePicker → .cad.json
async function loadFromFile(): Promise<Project> // showOpenFilePicker; Fallback <input type=file>
async function autosave(p: Project) // IndexedDB, debounced 2 s
```
- **Save/Load:** [File System Access API](https://developer.mozilla.org/docs/Web/API/File_System_Access_API)
(`showSaveFilePicker`/`showOpenFilePicker`); Fallback Download-Blob + `<input type=file>`
für Firefox/Safari.
- **Autosave/Recovery:** IndexedDB (via `idb`), entkoppelt vom expliziten Speichern.
- **Cross-Projekt-Presets:** LocalStorage mit Namespacing (`cad.presets.overrides`,
`cad.presets.wallTypes`, …) + JSON-Export/Import — ersetzt DOSSIERs
`~/Library/.../override_presets.json` (overrides.py).
- **Migrationen:** `migrate(data, fromVersion)`-Kette, additiv. DOSSIER macht das
per Sticky-Prefix (`traite_``pause_``dossier_`); wir machen es versioniert im
Datei-Schema.
### 3.3 Undo/Redo ### 3.3 Undo/Redo
DOSSIER überlässt Undo Rhino (und hat dadurch Cache-Stale-Bugs, Schwachstelle Eigener History-Ring im `projectSlice` (kein DOSSIER-Sticky-Bus, kein
#4.3). Wir besitzen das Dokument selbst → **eigener History-Ring**: Cache-Stale-Problem, da alle Sichten pure Ableitungen sind).
```ts
// history.ts — Snapshots des project-Teilbaums (strukturelles Sharing via Immer-Patches)
interface History { undo: Patch[][]; redo: Patch[][]; }
```
Jede `apply()`-Mutation erzeugt Immer-Patches → Push auf `undo`. Da abgeleitete
Sichten **pure** sind, ist nach `undo()` kein Cache zu invalidieren — der
ganze Cache-Stale-Komplex aus DOSSIER (`_JOINTS_CACHE_KEY`, Material-Cache)
entfällt strukturell.
--- ---
## 4. Rendering ## 4. Rendering
### 4.1 Three.js-Szene spiegelt den Ebenen-Baum ### 4.1 Zwei 3D-Viewports
DOSSIER baut in Rhino eine **Layer-Hierarchie** `Zeichnungsebene → CODE_Name-Sublayer` - **`Viewport3D.tsx`** — three.js, die „Free"-Stufe.
(`layer_builder.build_layers`) und hängt jedes Objekt an den passenden Sublayer. - **`Wasm3DViewport.tsx`** — Rust/wgpu, „Nordstern", editierbar, **Default**.
Browser-Spiegel: ein **`THREE.Group`-Baum** mit identischer Struktur, gebaut aus Ein Settings-Schalter wählt die Engine; WebGL2/three.js nur noch als
dem Modell (nicht persistiert — reine Ableitung): explizite Wahl oder Fallback.
`render3d` (10.246 LOC) baut Wandextrusion inkl. Schichtbändern und
Prioritäts-Gehrung (`compute_wall_miters`), Materialtextur-Arrays (pro
Wandschicht) + separate Aerial-Drape-Textur, Render-Styles (Shaded/White/
Textured/Wireframe/Hidden/ShadedEdges via Kanten-Extraktion mit
Crease-Erkennung). **Dächer/Treppen/Stützen haben KEINE eigene Rust-Geometrie**
— sie werden vollständig in TypeScript erzeugt (`geometry/roof.ts`,
`emitRoofs`/`emitColumns`) und Rust nur als vorberechnetes Dreiecks-Mesh
(`MeshInput`/`append_context_mesh`, derselbe generische Importpfad wie für
swissBUILDINGS3D/DXF) übergeben.
### 4.2 Grundriss = drei koexistierende Renderer
1. **`PlanView.tsx`** (SVG) — bleibt immer im DOM (Hit-Testing/Grips),
unabhängig vom aktiven Zeichenpfad.
2. **`plan/glPlan/`** — eigener TypeScript-WebGL2-Renderer.
3. **`useWasmPlanRenderer.ts`** → Rust-`render2d` (WGSL, wgpu nativ,
WebGPU/WebGL2-Fallback im Web, echtes Text-Rendering via `glyphon`).
Beide GPU-Pfade fallen bei Init-Fehler still auf SVG zurück.
### 4.3 Schnitt/Ansicht — analytische Rust-Pipeline, KEIN HLR
Der ursprünglich geplante OCCT-WASM-HLR-Pfad (`src/section/hlr.ts`/`occt.ts`)
ist **toter Code** (keine Aufrufer mehr). Der tatsächlich funktionierende
Live-Schnitt nutzt aus, dass jedes Bauteil ein Prisma mit konstantem
Querschnitt ist — eine Schnittebene liefert dadurch **immer** ein
achsparalleles Rechteck, nie ein Trapez, wodurch HLR unnötig wird:
``` ```
scene App.tsx (section3dCutId/section3dPlane)
└─ levelGroup[floorId] (y-Offset = baseElevation) ← Geschoss → Wasm3DViewport.tsx (section3d-Prop → setSectionPlane)
└─ categoryGroup[categoryCode] (userData.code) ← Ebene → src-tauri/render3d/src/{section.rs, section_boolean.rs, section_fill.rs}
└─ element meshes (userData.elementId)
``` ```
```ts `section_boolean.rs` ist ein 1:1-Port von `toSection.ts::subtractDominantBands`
// scene.ts — 2D-Plan-Schnitt und 3D-Live-Schnitt nutzen dieselbe Prioritäts-Logik und
function buildScene(project: Project, vis: VisibilityState): THREE.Group stimmen dadurch exakt überein. Kein Worker, kein Comlink (beides war geplant,
function syncScene(root: THREE.Group, project, vis) // diff statt full rebuild (Perf) existiert nirgends im Projekt) — läuft synchron GPU-seitig.
```
- **Sichtbarkeit:** `categoryGroup.visible = vis.codes.has(code)` und ### 4.4 Öffnungen als Löcher (kein Mesh-Boolean)
`levelGroup.visible = vis.floors.has(floorId)` — entspricht DOSSIERs
`apply_visibility`, aber als simples `.visible`-Toggle statt Layer-Table-Modify.
- **Farbe/Material:** Component → `MeshStandardMaterial` (PBR später), per
`componentId` gecacht (wie heute `layerMaterial`-Cache, aber projektweit).
- **„Grau/gesperrt"-Modi** (DOSSIER `grey`/`grey_locked`): graues Override-Material
auf der Gruppe statt Layer-Color-Tausch.
### 4.2 Grundriss = Clipping-Ebene + symbolische Generierung Fenster/Türen schneiden echte achsparallele Rechteck-Löcher aus dem Wandkörper
(`plan/toWalls3d.ts` `subtractSpans`, gespiegelt in `render3d::mesh.rs`).
Ein generisches Mesh-CSG-Boolean existiert bereits (`trucksolid::boolean_mesh`,
`csgrs`, für das Extrude-Kommando genutzt), ist aber nicht an die
Wand/Öffnungs-Pipeline angeschlossen.
DOSSIER zeigt den Grundriss eines Geschosses, indem es eine **Clipping-Plane** auf ### 4.5 Materialien
`okff + schnitthöhe` legt, Normale Z (`layer_builder.update_clipping_plane`).
Zwei Browser-Pfade — **beide nötig** (ROADMAP §3):
1. **3D-Viewport im Plan-Modus:** `THREE.Plane(normal=(0,0,-1), constant=cutZ)` über `materials/library.ts` (13 gebündelte PBR-Starter, ambientCG CC0) +
`renderer.clippingPlanes` + `material.clippingPlanes`. `clip.ts` setzt die Ebene `materials/ambientcg.ts` (Live-Suche der kompletten ambientCG-Bibliothek,
pro aktivem Geschoss (`cutZ = baseElevation + cutHeight`), Kamera orthografisch 1K/2K/4K-Auflösungswahl, On-Demand-Download via `jszip`, Proxy wegen CORS) →
von oben. So sieht man den **echten geschnittenen Volumenkörper**. `materials/runtime.ts` baut daraus gecachte `THREE.MeshStandardMaterial`s mit
2. **Symbolischer SVG-Grundriss** (der Hauptweg, schnell + sauber): `generatePlan.ts` physisch korrekter Kachelgrösse (UV in Weltmetern).
erzeugt Vektor-Primitive **direkt aus Parametern** — Wand-Schnittbänder,
Öffnungs-Aussparungen, Tür-Schwenkbögen — *ohne* Mesh zu schneiden. Steht im
Spike. Ausbau: Schraffuren, Lauflinien, Bemaßung, LoD (plans-output.md).
### 4.3 Schnitt/Ansicht = Kamera + Schnittebenen + HLR
DOSSIER setzt 12 Clipping-Planes (Cut + Back), Parallel-Projektion senkrecht zur
Linie, zoomt auf die BBox (`schnitte.activate_schnitt`). Browser:
- **3D-Vorschau:** zwei `THREE.Plane` (Cut auf der Linie, Back in `directionSign`-
Richtung um `depthBack` versetzt) + orthografische Kamera senkrecht zur Linie.
- **Vektor-Ergebnis (Risiko #4):** Hidden-Line-Removal durch das zusammengebaute
Gebäude → saubere Linien als SVG. Via **OpenCascade.js** (`HLRBRep`) im
**Web Worker** (Comlink), Ergebnis gecacht pro (Schnitt, sichtbare Ebenen,
Modell-Hash). Geschnittene Bauteile bekommen Component-Schraffur (Section-Style,
resources-graphics.md). Details: plans-output.md.
### 4.4 SVG-Renderer + Geometrie-Booleans
- **Plan-Output:** SVG (`PlanView.tsx`). `Primitive`-Union (polygon/line/arc/text/
hatch) → SVG-Elemente; derselbe Serializer speist DXF- und PDF-Export.
- **Öffnungs-Booleans (Risiko #2):** Tür/Fenster schneidet Loch in Wand. Für 3D
reicht heute das Aussparen per Segment-Extrusion (im Spike). Für exakte B-Rep-
Verschneidung (und IFC) **OpenCascade.js** *oder* **Manifold** im Worker —
Entscheidung in Phase 0 (ROADMAP §8): OCC = exakt/B-Rep, Manifold = schnell/Mesh.
--- ---
## 5. React-Panel-Struktur ## 5. React-Panel-Struktur
DOSSIER ist eine Sammlung **getrennter WebView-Panels** (EBENEN, ELEMENTE, Dock-/Floating-Panel-System (`src/panels/`: Dock, FloatingPanel, TabStrip,
GESTALTUNG, OBERLEISTE, MASSSTAB, AUSSCHNITTE, DIMENSIONEN, LAYOUTS, OVERRIDES), Registry) mit eingebauten Panels: Tools, Attributes, ObjectInfo,
die über die Python-Bridge + `sc.sticky` kommunizieren. Im Browser ist alles DrawingLevels, Layers, Site (Kontext/Terrain-Import), RoomBalance
**eine SPA** mit einem geteilten Store — die Panels werden zu Docking-Bereichen. (SIA-416-CSV), Elements (Bauteilbaum), ViewSnapshots, Layouts.
``` Der **Resource Manager läuft bewusst NICHT als Dock-Panel**, sondern als
┌─────────────────────────────────────────────────────────────────────┐ eigenständiges (unter Tauri natives) Fenster — ebenso Settings,
│ TopBar Werkzeuge · Ansichtstyp · Massstab · Snaps · Save/Load │ ≙ OBERLEISTE+MASSSTAB DrawingLevels-Detaileditor, LayerSettings und ContextImport
├──────────────┬──────────────────────────────────────┬───────────────┤ (`src/native/*Window.ts` + `*WindowApp.tsx`), jeweils mit
│ Navigator │ Viewport (Three.js oder SVG-Plan) │ Inspector │ `isTauriRuntime()`-Gate und ohne separate Browser-Variante (im Browser bleibt
│ ┌──────────┐ │ ┌────────────────────────────────┐ │ ┌───────────┐ │ die entsprechende In-App-Overlay-Variante aktiv, wo vorhanden).
│ │Zeichnungs│ │ │ 3D / Grundriss / Schnitt / │ │ │ aktives │ │ ≙ ELEMENTE/
│ │-ebenen │ │ │ Ansicht — abgeleitet aus dem │ │ │ Element + │ │ GESTALTUNG-
│ ├──────────┤ │ │ Modell │ │ │ Stil │ │ Properties
│ │ Ebenen │ │ └────────────────────────────────┘ │ └───────────┘ │
│ │ (Baum) │ │ │ Manager-Tabs: │
│ │ │ │ │ Components · │ ≙ Resource-
│ └──────────┘ │ │ Hatch · Line │ Manager
├──────────────┴──────────────────────────────────────┴───────────────┤
│ BottomBar Koordinaten · Snaps · Statusmeldungen │
└─────────────────────────────────────────────────────────────────────┘
Modal/Drawer: Ausschnitte · Sheets/Layouts · Overrides · Kamera-Presets · SIA-Bilanz
```
**Komponenten-Map** (heute alles in `App.tsx`; wird gesplittet): **Werkzeug-System:** `Tool`-Interface (`src/tools/types.ts`) für Zeichenwerkzeuge
mit Snap-Engine; daneben das umfangreichere **Kommandosystem** (`src/commands/`)
| Bereich | DOSSIER-Panel | cad-Komponente | für Rhino-artige getippte Eingabe (`5,3`/`r5,3`/`5<45`, Tab-Feld-Zyklus in
|---|---|---| `CommandLine.tsx`) — beide koexistieren, decken unterschiedliche
| Zeichnungsebenen-Liste | EBENEN (oben) | `Navigator/DrawingLevels.tsx` | Interaktionsstile ab.
| Ebenen-Baum | EBENEN (Baum) | `Navigator/LayerTree.tsx` (Spike: `CategoryRow`) |
| Top-Bar (View/Snaps/Massstab) | OBERLEISTE, MASSSTAB | `TopBar.tsx` |
| Element-Eigenschaften | ELEMENTE, ELEMENT-PROPERTIES | `Inspector/ElementProps.tsx` |
| Stil/Attribute der Auswahl | GESTALTUNG | `Inspector/StylePanel.tsx` |
| Component/Hatch/Line | (Project-Settings, mass_style) | `managers/*Manager.tsx` |
| View-Snapshots | AUSSCHNITTE | `panels/Snapshots.tsx` |
| Plansätze | LAYOUTS | `sheets/SheetEditor.tsx` |
| Regelbasierte Overrides | OVERRIDES | `panels/Overrides.tsx` |
| Bemaßung | DIMENSIONEN | `panels/Dimensions.tsx` |
| Element-Übersicht (BIM-Tree) | ELEMENTE-ÜBERSICHT | `panels/ElementTree.tsx` |
| Kamera-Presets | KAMERA | `panels/CameraPresets.tsx` |
**Werkzeug-System** (ersetzt DOSSIERs Rhino-Command-Aliases `cmd/wand.py` etc.):
ein `Tool`-Interface mit `onPointerDown/Move/Up`, das Vorschau-Primitive
zurückgibt und beim Bestätigen `store.apply()` ruft. Pointer-Events auf Canvas/SVG
mit Snap-Engine (Endpunkt/Mitte/Schnitt/Ortho/Raster). Tabelle der Tools:
elements.md §8.
--- ---
## 6. Rhino → Browser — Mapping-Tabelle ## 6. Rhino → Dossier — Mapping-Tabelle (Kernkonzepte)
| DOSSIER (Rhino-Plugin) | cad (Standalone-Browser) | Anmerkung | | DOSSIER (Rhino-Plugin) | Dossier (Tauri) | Anmerkung |
|---|---|---| |---|---|---|
| **Rhino `RhinoDoc`** | `Project` (TS-Objekt im Store) | einzige Wahrheit | | Rhino `RhinoDoc` | `Project` (TS-Objekt im eigenen Store) | einzige Wahrheit |
| **`doc.Strings[key]=json`** | Feld im `Project`-JSON | persistiert in `.cad.json` | | `doc.Strings[key]=json` | Feld im `Project`-JSON | persistiert in `.obp` |
| **`.3dm`-Datei** | **`.cad.json`** via File System Access API | + IndexedDB-Autosave | | `.3dm`-Datei | **`.obp`**-Datei (Tauri `plugin-fs`) | + Blob-Fallback im Browser |
| **Globale Presets (~/Library/*.json)** | **LocalStorage** + Export/Import | cross-Projekt | | `sc.sticky` (cross-modul Bus) | eigener `useSyncExternalStore`-Store | kein Polling |
| **`sc.sticky` (cross-modul Bus)** | **Zustand-Store** (reaktiv) | kein Polling, kein None-Bug | | Rhino Layer-Tabelle | `LayerCategory[]`-Baum, Sichtbarkeit als `.visible`-Flag | pro Renderer umgesetzt |
| **`panel_base.BaseBridge` / WebView-IPC** | direkte React-Props/Store | keine `document.title`-Hacks | | Clipping-Plane (`AddClippingPlane`) | `render3d::section.rs` (analytisch, Rechteck-Subtraktion) | KEIN HLR |
| **`document.title="RHINOMSG::"` Polling** | entfällt | SPA, kein WebView | | `HLRBRep` | — (ungenutzt; OCCT-Spike ist toter Code) | ersetzt durch obiges |
| **Eto.Forms Satelliten-Fenster** | React-Modal/Drawer | z.B. Ausschnitt-Settings | | `Rhino.Geometry.Brep`-Booleans | `trucksolid::boolean_mesh` (csgrs) | existiert, nicht an Wände angeschlossen |
| **Rhino Layer-Tabelle (Sublayer-Baum)** | `LayerCategory[]`-Baum + `THREE.Group`-Spiegel | `scene.ts` | | Rhino-Grips + DisplayConduit | Pointer-Events + Grip-Overlay (2D vollständig, 3D nur Basis, kein Snap) | siehe STATUS.md §4.7 |
| **`layer_builder.build_layers`** | `buildScene` (Ableitung) | Gruppen statt Layer | | Swisstopo via .NET HttpClient | `fetch()` (CORS-offen) | radiusgenau zugeschnitten (nicht ganze STAC-Kachel) |
| **`layer.PlotWeight`** | `LineStyle.weight` (mm) → SVG `stroke-width` | massstabsabh. (§plans) | | IronPython-Laufzeit-Risiken | entfällt | TS/Rust |
| **`SectionStyle` (Layer)** | Component-Schraffur beim Schnitt-Rendern | resources-graphics.md |
| **Clipping-Plane (`AddClippingPlane`)** | `THREE.Plane` + `renderer.clippingPlanes` | `clip.ts` |
| **`vp.ChangeToParallelProjection`** | `THREE.OrthographicCamera` | `camera.ts` |
| **`vp.GetFrustum()` → Massstab** | Frustum-Breite (ortho) → 1:N | gleiche Mathe (§plans) |
| **CoreGraphics DPI-Auto-Detect** | `window.devicePixelRatio` + CSS-px | Browser kennt DPI nativ |
| **`Rhino.Geometry.Brep`-Booleans** | OpenCascade.js / Manifold (Worker) | Öffnungen, HLR |
| **`HLRBRep` (über Rhino-Display)** | OpenCascade.js `HLRBRep` (Worker) | Schnitt/Ansicht-Linien |
| **Rhino-Grips + DisplayConduit** | Pointer-Events auf SVG/Canvas + Overlay | grip-editing (elements.md) |
| **`Rhino.Input.Custom.GetPoint`** | `Tool`-Pointer-Handler + Snap | Werkzeug-System |
| **`MouseCallback` (Doppelklick Schnitt)** | `onDoubleClick` auf SVG-Symbol | plans-output.md |
| **Rhino Undo/Redo** | eigener History-Ring (Immer-Patches) | kein Cache-Stale |
| **`HatchPattern`-Tabelle** | `Hatch`-Ressourcen + SVG `<pattern>` | resources-graphics.md |
| **`Linetype`-Tabelle** | `LineStyle.dash[]` → SVG `stroke-dasharray` | resources-graphics.md |
| **`TextEntity` / Rich-Text** | SVG `<text>` / HTML-Annotation | plans-output.md |
| **`RhinoPageView` (Layout)** | `Sheet` + `SheetEditor` | plans-output.md |
| **`FilePdf` Multi-Page-Export** | svg → `pdf-lib`/`jsPDF` (Vektor) | plans-output.md |
| **Swisstopo/OSM via .NET HttpClient** | `fetch()` (CORS-fähige STAC/Overpass-APIs) | Phase 4 |
| **LV95↔WGS84 (Python-Formeln)** | dieselben Formeln in TS portiert | Phase 4 |
| **IronPython 2.7/3 Runtime-Risiken** | entfällt (TS/Browser) | — |
--- ---
## 7. Was wir von DOSSIER übernehmen — und was nicht ## 7. Was übernommen wurde — und was bewusst anders lief
**Übernehmen (bewährte Konzepte):** **Übernommen (Prinzipien, bewährt):**
- Zwei-Achsen-Dokumentmodell (Zeichnungsebenen × Ebenen). - Zwei-Achsen-Dokumentmodell, Layer-Codes 1:1, Component/Hatch/Line-Manager
- Layer-Codes/Farben/lw 1:1 (`DEFAULT_LAYER_SCHEMA`). mit `joinPriority`, LoD (grob/mittel/fein), regelbasierte Overrides,
- Component/Hatch/Line-Manager mit id-Verweisen + `joinPriority`. Ausschnitte, SIA-416-Räume, Norden-Rotation.
- Ansichtstypen = Kamera + optionaler Schnitt (vereinheitlicht). - Pure-Ableitungs-Architektur (kein Cache-Stale, kein Sticky-Bus).
- LoD (`darstellung`: einfach/standard/detail) mit Dokument-Override.
- Regelbasierte Overrides (Condition → Action, additive Priorität, reversibel).
- Ausschnitte (View-Snapshots), Layer-Kombinationen, Massstab-pro-Viewport.
- SIA-416-Räume, Norden-Rotation, Stil-Kataloge.
**Bewusst anders (Browser-nativ, Schwachstellen vermeiden):** **Anders gelaufen als geplant (siehe STATUS.md §5 für die volle Tabelle):**
- **Kein Sticky-Bus** → ein reaktiver Store (vermeidet DOSSIER #4.4/#4.5/#4.6). - Eigene Rust/WASM-Rendering-Engines statt Three.js/OpenCascade.js/web-ifc.
- **Kein Monolith** → Bauteil-Module statt 7244-LOC-`elemente.py` (#4.1). - Typisierte Arrays pro Bauteiltyp statt `Element[]`-Union.
- **Pure Ableitungen** statt mutierter Doc-Objekte → kein Cache-Stale (#4.3), - Eigener Store statt Zustand-Library.
kein Undo/Redo-Loch. - Analytische Rust-Schnitt-Pipeline statt HLR/Worker/Comlink.
- **Versioniertes Datei-Schema** statt UserString-Sticky-Migration. - App.tsx wurde **nicht** wie geplant auf eine dünne Shell reduziert
- **Persistenz im Project-JSON** statt verteilt über `doc.Strings`. (`src/views/`/`src/menus/` wurden nie angelegt) — bekannter, unbereinigter
Punkt, siehe STATUS.md §4.3.
**Anti-Over-Engineering (aus DOSSIER-CONVENTIONS.md übernommen):** keine Abstraktion **Anti-Over-Engineering (weiterhin gültig):** keine Abstraktion ohne konkretes
ohne konkretes Problem; kein `try/catch: {}` als Bug-Versteck; erst grep/lesen, Problem; erst lesen, dann editieren; Geometrie nie „nebenbei" refactoren.
dann editieren; Wand-Geometrie nie „nebenbei" refactoren.
--- ---
## 8. Reihenfolge bei Code-Arbeit ## 8. Reihenfolge bei Code-Arbeit
1. **Dieses Dokument + das relevante Detail-Design** (elements/plans-output/ 1. **STATUS.md** (aktueller Ist-Zustand) + dieses Dokument lesen.
resources-graphics) lesen. 2. **Das betroffene Modul** lesen (nicht raten) — bei Zweifel: welcher der
2. **Dann das betroffene Modul** lesen (nicht raten). mehreren Renderer/Pfade ist gerade aktiv (§4.1/§4.2)?
3. **Erst danach editieren**; `Project` immer immutabel via `store.apply()` ändern. 3. **Erst danach editieren**; `Project` immer immutabel via den Store ändern.
4. **Verifizieren:** `npx tsc -b`, `npm run build`, Screenshot via 4. **Verifizieren:** `npx tsc -b`, `npm test` (Vitest), `cargo test` in den
`node scripts/probe.mjs` — Geometrie visuell prüfen. betroffenen Crates, bei Rust-Änderungen `npm run build:engine{,3d}` (WASM
5. **Dieses Dokument aktuell halten**, wenn sich Patterns/Mapping ändern. neu bauen, nur `.rs` committen — `src/engine/pkg*/` ist gitignored). Bei
render3d/Wasm3DViewport-Änderungen testet der Nutzer selbst in der
Tauri-Dev-App (nicht per Puppeteer/Browser verifizierbar).
5. **Dieses Dokument aktuell halten**, wenn sich Patterns ändern — insbesondere
nicht wieder in „Ziel-Struktur"-Beschreibungen abdriften, die Monate lang
niemand nachführt.
+25 -11
View File
@@ -1,6 +1,8 @@
# Projekt-Konventionen — Browser-BIM (cad) # Projekt-Konventionen — Dossier
Siehe [ROADMAP.md](ROADMAP.md) für Vision, Architektur und Phasen. Siehe [ARCHITECTURE.md](ARCHITECTURE.md) für die aktuelle Architektur,
[STATUS.md](STATUS.md) für den vollständigen Ist-Zustand und
[ROADMAP.md](ROADMAP.md) für die ursprüngliche (historische) Produktvision.
## Code-Konventionen (verbindlich) ## Code-Konventionen (verbindlich)
@@ -15,16 +17,26 @@ Siehe [ROADMAP.md](ROADMAP.md) für Vision, Architektur und Phasen.
CCW-Wicklung zeigt `+n` nach innen. Schichten werden außen (T/2) → innen (+T/2) CCW-Wicklung zeigt `+n` nach innen. Schichten werden außen (T/2) → innen (+T/2)
gestapelt. gestapelt.
## Code-Struktur (kein God-Component) ## Code-Struktur (kein God-Component — bisher nur teilweise erreicht)
- **`App.tsx` bleibt ein dünner Shell** (Store-Provider, Oberleiste, Docks+View-Router, - **Ziel:** `App.tsx` bleibt ein dünner Shell (Store-Provider, Oberleiste,
Statusleiste, Floating-Panels, Ressourcen-Overlay) — keine Geschäftslogik darin. Docks+View-Router, Statusleiste, Floating-Panels, Ressourcen-Overlay) — keine
- **Globaler Zustand in einem Store** (`src/state/`, Slices: project/selection/view/layout). Geschäftslogik darin. **Realität (Stand 2026-07-21):** `App.tsx` ist mit
Komponenten lesen Zustand über Store-Hooks statt Prop-Drilling. ~7.100 Zeilen die grösste Datei des Projekts und enthält weiterhin
- **Features als eigene Module:** `src/views/` (View-Router-Teile), `src/editors/` View-Umschaltung und Kontextmenü-Aufbau inline — die geplante Auslagerung
(Inline-Editoren), `src/menus/` (Kontextmenü-Builder), `src/panels/`, `src/ui/`. nach `src/views/`/`src/menus/` (unten) ist **nie passiert**, siehe
[STATUS.md](STATUS.md) §4.3. Neue, grössere Features sollten trotzdem nicht
weiter in `App.tsx` wachsen; wo möglich in `src/panels/`, `src/editors/`
oder ein neues Modul auslagern statt die Datei weiter zu vergrössern.
- **Globaler Zustand in einem Store** (`src/state/`, eigener Store auf
`useSyncExternalStore`**kein** Zustand/Redux/Immer — mit Slices:
project/history/selection/view/layout/site/notify). Komponenten lesen
Zustand über `useStore(selector)` statt Prop-Drilling.
- **Features als eigene Module:** `src/editors/` (Inline-Editoren),
`src/panels/`, `src/ui/`. `src/views/` und `src/menus/` waren geplant, wurden
aber nie angelegt — bei Bedarf gilt das als offener Aufräum-Punkt, nicht als
bestehende Struktur.
- Ziel: modular + parallel bearbeitbar (verschiedene Features ≠ dieselbe Datei). - Ziel: modular + parallel bearbeitbar (verschiedene Features ≠ dieselbe Datei).
Siehe `docs/design/state-architecture.md`.
## UI-Konventionen ## UI-Konventionen
@@ -61,4 +73,6 @@ Rendern angewandt, nie in die Geometrie eingebacken.
- Änderungen verifizieren: `npx tsc -b`, `npm run build`, und Screenshot via - Änderungen verifizieren: `npx tsc -b`, `npm run build`, und Screenshot via
`node scripts/probe.mjs` (schreibt `scripts/probe.png`) bzw. `probe-ff*.mjs` für `node scripts/probe.mjs` (schreibt `scripts/probe.png`) bzw. `probe-ff*.mjs` für
Firefox-Fälle. Screenshot ansehen und Geometrie visuell prüfen. Firefox-Fälle. Screenshot ansehen und Geometrie visuell prüfen.
- Dev-Server läuft via `npm run dev` (Vite, Port 5173). - Dev-Server läuft via `npm run dev` (Vite, Port 5187). Nativer Rahmen
plattformabhängig: `npm run tauri:dev` auf macOS, `npm run electron` auf Linux
(WebKitGTK kann kein zuverlässiges WebGPU → dort Chromium/Electron).
+6
View File
@@ -1,3 +1,9 @@
> **Hinweis (2026-07-21):** Dieser Block (Stand 07-09) ist nicht mehr der
> neueste Stand — `git log`/[PENDENZEN.md](PENDENZEN.md) reichen bis 07-20/17.
> Für den aktuellen Ist-Zustand siehe [STATUS.md](STATUS.md). HANDOVER.md bleibt
> ein Append-Log (ältere Sessions unten); es wird nicht rückwirkend aktualisiert
> — bei neuen Übergaben oben einen neuen Block ergänzen, nicht diesen editieren.
# HANDOVER — Stand 2026-07-09 (autonome Session, ALLES committet) # HANDOVER — Stand 2026-07-09 (autonome Session, ALLES committet)
Lange autonome Session (Nutzer-Auftrag: „arbeite die Pendenzen/Funktionen durch, Lange autonome Session (Nutzer-Auftrag: „arbeite die Pendenzen/Funktionen durch,
+105 -94
View File
@@ -1,16 +1,17 @@
# DOSSIER Standalone # Dossier
Open-Source-CAAD (Computer Aided Architecture Design). Modelliert ein Gebäude Open-Source-CAAD (Computer Aided Architecture Design). Modelliert ein Gebäude
aus semantischen Bauteilen und zieht daraus saubere, normgerechte 2D-Pläne — aus semantischen Bauteilen und zieht daraus saubere, normgerechte 2D-Pläne —
ohne Revit. Als Desktop-App und im Browser zugänglich; die Desktop-App ist die ohne Revit. **Desktop-App** — auf macOS über **Tauri**, auf Linux über eine
vollständige Fassung. **Electron**-Shell (weil Tauris Linux-Webview WebKitGTK kein zuverlässiges
WebGPU bietet, das die Rendering-Engines brauchen); dieselbe Codebasis läuft
auch im Browser (eingeschränkt, ohne native Fenster/Dateisystem-Integration).
Das ist die eigenständige Standalone-Variante des Rhino-Plugins Das ist die eigenständige Neuimplementierung des Rhino-Plugins
[DOSSIER](https://git.kgva.ch/karim/DOSSIER): dieselbe Denkweise (Geschosse, [DOSSIER](https://git.openbureau.ch/karim/dossier): dieselbe Denkweise
Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts-Verschneidung), aber als (Geschosse, Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts-
React-App mit eigener Rendering-Engine (Rust/WASM/WebGPU) statt als Rhino-Aufsatz. Verschneidung), aber mit eigenem Datenmodell + eigener Rendering-Engine
Als Desktop-App (Tauri) läuft sie im eigenen Fenster mit voller Engine-Leistung; (Rust/WASM/WebGPU, intern „Nordstern" genannt) statt Rhino-Aufsatz.
browserseitig ist derselbe Kern zugänglich, die App ist die vollständige Fassung.
## Grundgedanke ## Grundgedanke
@@ -22,117 +23,124 @@ in die Geometrie eingebacken. Wer eine Wand verschiebt, verschiebt sie überall.
Konkret heißt das auch: Der Grundriss entsteht nicht aus einem zerschnittenen Konkret heißt das auch: Der Grundriss entsteht nicht aus einem zerschnittenen
3D-Mesh, sondern **analytisch aus den Bauteil-Parametern** (Wandachsen + Dicken → 3D-Mesh, sondern **analytisch aus den Bauteil-Parametern** (Wandachsen + Dicken →
Linien, Öffnungen → Lücken + Symbol). PDF-Export ist derselbe Plan, nur mit Linien, Öffnungen → Lücken + Symbol). PDF-Export ist derselbe Plan, nur mit
echten mm-Stiftstärken statt Bildschirm-Hairlines — kein zweites Rendering. echten mm-Stiftstärken statt Bildschirm-Hairlines. Schnitte und Ansichten laufen
Schnitte und Ansichten brauchen den zweiten Weg — echte 3D-Projektion mit über eine eigene, analytische Rust-Pipeline (kein Hidden-Line-Removal nötig,
verdeckten Kanten (HLR) —, dessen Machbarkeit per Spike bewiesen, aber noch siehe [ARCHITECTURE.md](ARCHITECTURE.md) §4.3) und sind seit Juli live im
nicht ans UI angebunden ist. 3D-Viewport verdrahtet, nicht nur ein Spike.
## Stand heute ## Stand heute
Ehrlich eingeordnet: aus dem Risiko-Spike ist ein **benutzbares 2D-CAD mit Aus dem ursprünglichen Risiko-Spike ist binnen weniger Wochen ein
abgeleiteter 3D-Sicht, Vektor-PDF/DXF-Export und einer parametrischen **funktionsreiches Desktop-BIM-Tool** geworden: eigenes semantisches
Wand-Engine** geworden. Der einfachere Teil steht und ist per Screenshot/Probe Gebäudemodell, zwei eigene Rust/WASM-Rendering-Engines, IFC/DXF/PDF/STL/OBJ-
verifiziert; die schwierigen BIM-Kernrisiken sind bewusst noch offen. Export, Swisstopo-Import, SIA-416-Flächen, Materialbibliothek, Layouts/
Plansätze, native Tauri-Fenster. Eine vollständige, ehrliche Bestandsaufnahme
(inklusive offener Punkte und bekannter technischer Schulden) steht in
**[STATUS.md](STATUS.md)**.
**Funktioniert:** **Funktioniert (Auszug — volle Liste in STATUS.md §3):**
- Semantisches Modell mit **mehrschichtigen Wänden** und automatischer - Semantisches Modell mit **mehrschichtigen Wänden**, L-Eck-Gehrung **und**
**L-Ecken-Gehrung** (`computeJoins`); dieselbe Logik speist Plan *und* 3D. Prioritäts-T-/X-Stössen (`joinPriority` am Component) — konsistent in
- **Parametrische Wände** (`ParametricWall`-Regelwerke: Grid/Modul/Sequenz/ Grundriss, 3D-Viewport und 3D-Live-Schnitt.
Referenzlinie/bedingte Dicke) lösen sich zu konkreten `Wall`-Objekten auf, - **Parametrische Wände**, Decken, Treppen (gerade/L/Wendel), Dächer
statt jede Wand einzeln von Hand zu ziehen. (Flach/Pult/Sattel/Walm/Mansarde/Zelt), Stützen, SIA-416-Räume mit
- **Dokumentmodell wie in DOSSIER:** Zeichnungsebenen (Geschosse + Schnitte/ automatischer Bilanz + CSV-Export.
Ansichten/Zeichnungen) × Kategorie-Ebenen (Code-Baum 1:1, `20 Wände`, `30 Decken` …). - **Türen & Fenster** gehostet in Wänden, mit Rahmen/Zarge/Kämpfer/Oberlicht,
- **Zeichnen & Editieren** im Grundriss: Wand, Linie, Polylinie, Rechteck, Kreis, Detailgrad grob/mittel/fein, echte Rechteck-Löcher im 3D-Wandkörper.
Öffnungen, Treppen, Decken, Raumstempel; Snapping (Endpunkt/Mittelpunkt/ - Rhino-artiges **Kommandosystem** mit getippten Koordinaten (`5,3` · `r5,3` ·
Schnittpunkt/Lot/Raster/Ortho), Grips, Move/Copy/Offset, Spiegeln/Drehen/Array, `5<45`) und Tab-Feld-Zyklus; Snapping, Grips, Trim/Split/Join, 2D-Booleans.
Trim/Split/Join. - **Live 2D↔3D-Schnitt**: eine Schnittebene im 3D-Viewport folgt derselben
- **Rhino-artiges Befehlssystem** mit getippten Koordinaten (`5,3` · `r5,3` · Prioritäts-Logik wie der 2D-Plan-Schnitt (Rust-Port, keine Diskrepanz).
`5<45`) und Tab-Feld-Zyklus (Länge → Winkel …). - **Vektor-Export:** PDF (Einzel- **und Mehrseiten-Layouts**), DXF, **IFC4**
- **Vektor-Export:** PDF (A4/A3, Titelblock, echte mm-Stiftstärken nach ISO-Pen- (mit echten Fenster-/Tür-Löchern), STL/OBJ, CSV-Bauteilliste.
Steps, keine Rasterbilder) und DXF — beide aus derselben `Plan`-Struktur wie - **Materialbibliothek**: 13 gebündelte PBR-Starter **plus** Live-Suche der
der Bildschirm. kompletten ambientCG-Bibliothek (1K/2K/4K, On-Demand-Download).
- **PBR-Material-Bibliothek** (ambientCG-Import, `manifest.json`) für die - **Layouts/Plansätze** mit mehreren Viewports pro Blatt, Ausschnitte
3D-Ansicht. (View-Snapshots), Kamera-Presets, Norden-Rotation.
- **Resource Manager** (Component / Hatch / Line) — alles per id referenziert, - **Import:** DXF/DWG, Swisstopo (swissBUILDINGS3D/-ALTI3D/SWISSIMAGE,
zentral änderbar. radiusgenau zugeschnitten), OSM-Kontextimport, `.lin`/`.pat`.
- **Panel-System** (dockbar, stapelbar, floatend), Top-Bar + Statusleiste - **Native Desktop-Integration** (Tauri): eigene randlose Fenster, native
(echter Maßstab 1:N, Cursor X/Y), eigenes Kontextmenü, **i18n** (de/en). Speichern/Öffnen-Dialoge, eigenes `.obp`-Dateiformat mit OS-Level-Lock gegen
- **Import:** DXF/DWG (Konturen/Mesh) → Terrain-TIN, Swisstopo/LV95-Geokontext, Doppelöffnen, mehrere native Zusatzfenster (Resources, Settings, …).
OSM-Kontextimport; `.lin`/`.pat` für Linien/Schraffuren. - **Resource Manager** (Component/Hatch/Line/Typ-Editoren), regelbasierte
Overrides, Panel-System (dockbar/floatend), i18n (de/en).
**Bewusst noch offen** (die eigentlich harten Teile): **Bewusst noch offen** (Details + volle Liste: STATUS.md §4.7):
- Echte **3D-Booleans** für Öffnungen — Türen/Fenster sind im Plan eine Lücke, - **Echtes Mesh-Boolean für Öffnungen**funktioniert heute über achsparallele
noch kein geschnittenes Volumen. Rechteck-Löcher; ein generisches CSG-Boolean existiert bereits
- **HLR** (verdeckte Kanten) für Schnitte und Ansichten: Machbarkeit per (`trucksolid`/`csgrs`), ist aber nicht an die Wand-Pipeline angeschlossen.
OCCT-WASM-Spike bewiesen (`docs/welle-c-hlr-spike/`, `src/section/hlr.ts`), - **3D-Griffsystem**: Feld-Controller (Tab-Zyklus) und Snapping fehlen für
aber noch nicht ans UI/Dokumentmodell verdrahtet — Views sind noch Stubs. 3D-Grip-Drag (im 2D vollständig vorhanden).
- **Prioritäts-T-/X-Stöße** mehrschichtiger Wände (Beton läuft durch, Putz - **DWG/DXF-Domänenimport** (Entitäten → echte Wände/Öffnungen) — Lesen
verbindet seitlich) — das berüchtigte Risiko #1. funktioniert, das Mapping auf das Modell ist unbegonnen; DWG-Schreiben fehlt.
- **Multi-Page-Layouts/Ausschnitte** (mehrere Viewports pro Blatt) — PDF-Export - **`make2D`**-Kommando (3D-Ansicht → flacher 2D-Plan mit Füllungen).
ist noch single-sheet. - Bekannte, noch nicht bereinigte Doppelspur `Door[]`/`Opening[]` im Modell.
Details und die Begründungen stehen im
[HANDOVER](HANDOVER.md) (laufendes Arbeitsprotokoll) und in der
[ROADMAP](ROADMAP.md) (Vision, Phasen, Backlog).
## Stack ## Stack
Bewusst leichtgewichtig — Three.js ist reiner Display-Layer, kein schwerer
Geometrie-Kernel verfrüht eingezogen.
| | | | | |
|---|---| |---|---|
| Shell | Electron (eigenes randloses App-Fenster, kein Browser-Tab) | | Shell | **Tauri** auf macOS (WKWebView+WebGPU) · **Electron/Chromium** auf Linux (WebKitGTK kann kein WebGPU) — beide randlos, eigene Titelleiste, gemeinsame React-App |
| Frontend | React + TypeScript + Vite | | Frontend | React + TypeScript + Vite |
| 3D | Three.js, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render3d`) | | 3D | eigener Rust/WASM/WebGPU-Renderer „Nordstern" (`render3d`, Default), Three.js als leichtgewichtiger Zweitpfad |
| 2D-Plan | eigener SVG-Renderer, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render2d`) | | 2D-Plan | eigener Rust/WASM/WebGPU-Renderer (`render2d`), plus ein TS/WebGL2-Renderer (`plan/glPlan`), plus SVG (`PlanView.tsx`, immer als Interaktions-/Fallback-Ebene aktiv) |
| Geometrie | eigener 2D-Kernel (`src/geometry/kernel2d.ts`), `delaunator` fürs Terrain | | Geometrie | eigener 2D-Kernel (`src/geometry/kernel2d.ts`); ein Rust-Port (`src-tauri/kernel2d`) existiert nur als Paritätstest, läuft nicht produktiv |
| Export | `jspdf`/`svg2pdf.js` (Vektor-PDF), eigener DXF-Writer | | CSG/Extrusion | `trucksolid`-Crate (`truck` + `csgrs`), fürs Extrude-Kommando |
| Export | eigener IFC4-Writer, `jspdf`/`svg2pdf.js` (Vektor-PDF), eigener DXF-Writer, STL/OBJ |
| Import | `dxf-parser`, `@mlightcad/libredwg-web` (DWG), eigene `.lin`/`.pat`-Parser | | Import | `dxf-parser`, `@mlightcad/libredwg-web` (DWG), eigene `.lin`/`.pat`-Parser |
| Schnitt/HLR-Spike | `opencascade.js` (OCCT-WASM) — isoliert, noch nicht verdrahtet | | Materialien | `jszip` (ambientCG-Zip-Entpacken), `three.js` `TextureLoader` |
Geplant, aber noch nicht eingezogen: `rhino3dm` (NURBS / `.3dm`), web-ifc (IFC), `opencascade.js` steht noch als Dependency in `package.json`, wird aber nur
Manifold (exakte 3D-Booleans). OCCT-WASM ist für HLR bereits als Spike da (s.o.), noch von totem Code (`src/section/hlr.ts`, superseded durch die Rust-
aber noch nicht an Dokumentmodell/UI angebunden. Siehe ROADMAP §4. Schnitt-Pipeline) importiert. Details zu allen Rust-Crates (inkl. zwei
aktuell unbenutzten WASM-Builds) in [ARCHITECTURE.md](ARCHITECTURE.md) §1
und [STATUS.md](STATUS.md) §4.1.
## Entwicklung ## Entwicklung
```bash ```bash
npm install npm install
npm run dev # Vite, http://localhost:5187 npm run dev # Vite, http://localhost:5187
npm run electron # Electron-Fenster (Dev-Server + randlose App-Shell) npm run tauri:dev # Tauri-Fenster (macOS — nativer Zielrahmen dort)
npm run electron # Electron-Fenster (Linux — WebGPU über Chromium statt WebKitGTK)
npx tsc -b # Typecheck npx tsc -b # Typecheck
npm run build # tsc -b && vite build npm run build # tsc -b && vite build
npm test # vitest run npm test # vitest run
``` ```
Verifiziert wird visuell: `node scripts/probe.mjs` rendert die App headless und WASM-Engines nach Rust-Änderungen neu bauen (nur `.rs` committen,
schreibt `scripts/probe.png` — Screenshot ansehen, Geometrie prüfen. Weitere `src/engine/pkg*/` ist gitignored):
`scripts/probe-*.mjs` decken einzelne Features ab (PDF, Trim, Materialien,
Theme, Boolean-Ops …); für Firefox-Fälle gibt es `scripts/probe-ff*.mjs` ```bash
(Playwright). npm run build:engine # render2d
npm run build:engine3d # render3d
npm run build:truck # trucksolid
```
render3d/Wasm3DViewport-Änderungen sind nicht per Puppeteer/Browser
verifizierbar — im laufenden `tauri:dev`-Fenster selbst testen.
## Aufbau ## Aufbau
``` ```
src/ src/
model/ semantisches Modell + Ableitungen (types, parametricWalls, roomStamp, joins, terrain) model/ semantisches Modell (types, joins, parametricWalls, roomStamp, terrain)
geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling, opening, roomArea/-Boundary, stair) geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling/opening/roomArea/stair/roof/column)
commands/ Rhino-artiges Befehlssystem (engine, parser, registry, cmds/) commands/ Rhino-artiges Befehlssystem (engine, parser, registry, cmds/)
tools/ interaktive Zeichenwerkzeuge + Snapping + Transformationen tools/ interaktive Zeichenwerkzeuge + Snapping + Transformationen
plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2-Renderer) plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2) + Rust-Bindings
viewport/ Viewport3D (Three.js) viewport/ Viewport3D (Three.js) + Wasm3DViewport (Rust/wgpu „Nordstern", Default)
section/ HLR-Spike (OCCT-WASM) — Schnitte/Ansichten, noch nicht verdrahtet section/ TOTER Code (OCCT-HLR-Spike) — Schnitt läuft über render3d, siehe ARCHITECTURE.md §4.3
export/ PDF- und DXF-Export aus derselben Plan-Struktur wie der Bildschirm export/ IFC4-, PDF-, DXF-, STL/OBJ-, CSV-Export aus derselben Plan-/Modell-Struktur
materials/ PBR-Material-Bibliothek (ambientCG-Import, Runtime) materials/ ambientCG-Live-Suche + gebündelte Starter-Bibliothek + PBR-Runtime
text/ Rich-Text (Beschriftungen, Textobjekte)
panels/ dockbares Panel-System + die einzelnen Paletten panels/ dockbares Panel-System + die einzelnen Paletten
state/ Store + Slices (project/selection/view/layout) state/ eigener Store (useSyncExternalStore) + Slices (project/selection/view/layout/…)
ui/ Top-Bar, Statusleiste, Kontextmenü, Resource Manager, Command-Line native/ Tauri-only: native Fenster, macOS-Menüleiste, Fenster-Chrome
ui/ App.tsx-Shell, Top-Bar, Resource Manager, Kontextmenü, Kommandozeile, Ribbon
io/ Import/Export (DXF, DWG, .lin, .pat), Swisstopo/LV95, OSM-Kontext io/ Import/Export (DXF, DWG, .lin, .pat), Swisstopo/LV95, OSM-Kontext
i18n/ Wörterbücher de/en i18n/ Wörterbücher de/en
src-tauri/ Rust-Crates (render2d/render3d) — headless per wasm-pack zu WASM src-tauri/ 6 Rust-Crates (render2d/render3d/geometry/kernel2d/trucksolid/dwgimport,
gebaut (npm run build:engine), Tauri-Host selbst ist ausrangiert je headless UND per wasm-pack baubar) + der Tauri-Host selbst
``` ```
## Konventionen ## Konventionen
@@ -141,21 +149,24 @@ src-tauri/ Rust-Crates (render2d/render3d) — headless per wasm-pack zu WAS
(Design Layer, Component, Hatch, Wall Style). **UI-Texte sind deutsch**, immer (Design Layer, Component, Hatch, Wall Style). **UI-Texte sind deutsch**, immer
über `t('key')` — keine hartcodierten Strings im JSX. über `t('key')` — keine hartcodierten Strings im JSX.
- Intern alles in **Metern**; Anzeige via `formatM`. - Intern alles in **Metern**; Anzeige via `formatM`.
- `App.tsx` bleibt dünner Shell, Zustand lebt im Store. Verbindliches in - Verbindliches in [CONVENTIONS.md](CONVENTIONS.md).
[CONVENTIONS.md](CONVENTIONS.md).
## Weiterlesen ## Weiterlesen
- [ROADMAP.md](ROADMAP.md) — Produktvision, Architektur-Entscheidungen, Phasen 07, DOSSIER-Backlog - [STATUS.md](STATUS.md) — **vollständige, ehrliche Bestandsaufnahme**: Zahlen,
- [ARCHITECTURE.md](ARCHITECTURE.md) — Technische Architektur im Detail Feature-Inventar, Mist-Liste, Doku-Widersprüche, Tag-1-Vision vs. heute
- [HANDOVER.md](HANDOVER.md) — aktueller Arbeitsstand, Befunde, nächste Schritte - [ARCHITECTURE.md](ARCHITECTURE.md) — technische Architektur im Detail (Ist-Zustand)
- [docs/](docs/) — Design-Specs (Befehlssystem, Zeichenwerkzeuge, Wand-Joins, Backend …) - [ROADMAP.md](ROADMAP.md) — ursprüngliche Produktvision (Tag-1-Stand, historisch)
- [HANDOVER.md](HANDOVER.md) / [PENDENZEN.md](PENDENZEN.md) — laufendes
Arbeitsprotokoll bzw. Backlog (Single Source of Truth für offene Punkte)
- [docs/](docs/) — Design-Specs (teils ebenfalls Tag-1-Vision, siehe Hinweis
in `docs/README.md`)
## Lizenz ## Lizenz
Copyright © 2026 Karim Gabriele Varano. Veröffentlicht unter der Copyright © 2026 Karim Gabriele Varano. Veröffentlicht unter der
**GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)** **GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)**
siehe [LICENSE](LICENSE). DOSSIER ist Teil der **openbureau**-Suite. siehe [LICENSE](LICENSE). Dossier ist Teil der **openbureau**-Suite.
Die AGPL verlangt, dass auch bei Betrieb als Netzwerk-/Webdienst der (ggf. Die AGPL verlangt, dass auch bei Betrieb als Netzwerk-/Webdienst der (ggf.
geänderte) Quellcode für die Nutzer verfügbar gemacht wird. Drittkomponenten geänderte) Quellcode für die Nutzer verfügbar gemacht wird. Drittkomponenten
@@ -164,4 +175,4 @@ behalten ihre jeweiligen Lizenzen (siehe „Über"-Dialog in der App).
--- ---
Die UI ist deutsch; Schweizer Spezifika (SIA-416-Flächen, Swisstopo-Geodaten) Die UI ist deutsch; Schweizer Spezifika (SIA-416-Flächen, Swisstopo-Geodaten)
sind als Differenzierer eingeplant. sind ein bewusster Differenzierer.
+11 -1
View File
@@ -1,6 +1,16 @@
# Browser-BIM für Wohnbau — Architektur & Produkt-Roadmap # Browser-BIM für Wohnbau — Architektur & Produkt-Roadmap
> Arbeitstitel: **cad** (Name später) > **Historisches Dokument (Tag-1-Vision, Stand 2026-06-28).** Vieles hier als
> „Phase 25"/„Backlog" gelistete ist inzwischen längst gebaut (Treppen, Dächer,
> Stützen, SIA-416, Swisstopo, OSM, Layouts, Ausschnitte, Kamera-Presets …), und
> mehrere Technologie-Entscheidungen liefen anders (eigene Rust/WASM-Engines
> statt Three.js/OpenCascade.js/web-ifc, siehe unten §4/§8). Für den aktuellen
> Ist-Zustand: **[STATUS.md](STATUS.md)** (Bestandsaufnahme + Mist-Liste) und
> **[ARCHITECTURE.md](ARCHITECTURE.md)** (aktuelle Architektur). Dieses Dokument
> bleibt als ursprüngliche Produktvision/Phasenplan stehen, wird aber nicht mehr
> laufend nachgeführt.
>
> Arbeitstitel: **cad** (Name später: **Dossier**)
> Ausrichtung: **BIM-first** · Nische: **Wohnbau / Einfamilienhäuser** > Ausrichtung: **BIM-first** · Nische: **Wohnbau / Einfamilienhäuser**
> Stand: 2026-06-28 > Stand: 2026-06-28
+342
View File
@@ -0,0 +1,342 @@
# STATUS — Codebase-Analyse
> Stand: 2026-07-21 · Vollständige Bestandsaufnahme von Code + Dokumentation.
> Ersetzt NICHT [ROADMAP.md](ROADMAP.md)/[ARCHITECTURE.md](ARCHITECTURE.md) (die wurden
> im gleichen Zug überarbeitet), sondern begründet die Überarbeitung mit Zahlen und
> Befunden. [HANDOVER.md](HANDOVER.md) und [PENDENZEN.md](PENDENZEN.md) bleiben die
> laufenden Arbeitsprotokolle (nicht rückwirkend umgeschrieben).
## 0. TL;DR
„Dossier" (Arbeitstitel `cad`, Rhino-Vorbild `DOSSIER`) ist in **3 Wochen**
(erster Commit 2026-06-30, 362 Commits bis 2026-07-20) von einem Risiko-Spike zu
einem **funktionsreichen Desktop-CAD/BIM-Tool** gewachsen: eigenes semantisches
Gebäudemodell, zwei eigene Rust/WASM-Rendering-Engines („Nordstern"), ein
Rhino-artiges Kommandosystem, IFC/DXF/PDF/STL/OBJ-Export, Swisstopo-Import,
SIA-416-Flächen, Materialbibliothek (statisch + live von ambientCG), Layouts/
Plansätze, Ausschnitte, native Tauri-Fenster. **~125.000 Zeilen Code** (TS+Rust,
inkl. Tests), gebaut über viele autonome Agent-Sessions.
Die drei zentralen Vision-Dokumente (ARCHITECTURE.md, README.md, ROADMAP.md)
stammen aus der **allerersten Woche** (Stand 28./29.6.) und beschreiben einen
Plan, der in der Zwischenzeit an vielen Stellen überholt, anders gelöst oder
längst umgesetzt wurde (z. B. HLR/OCCT→eigene Rust-Schnitt-Pipeline,
„Booleans noch offen"→teilweise längst gelöst). Diese Doku-Drift war der Auslöser
für diese Analyse; die Docs sind im gleichen Zug revidiert worden.
## 1. Kennzahlen
### Code-Umfang
| Bereich | Dateien | LOC (ohne Tests) | Tests |
|---|---:|---:|---:|
| `src/` (TypeScript, gesamt) | ~230 | 87.736 | 869 (Vitest, 71 Dateien) / 16.855 LOC |
| `src-tauri/render3d` („Nordstern" 3D) | 14 | 10.246 | 97 `#[test]` |
| `src-tauri/render2d` (2D-WGSL-Renderer) | 11 | 3.530 | 18 `#[test]` |
| `src-tauri/kernel2d` (Rust-Geometriekern, Paritätstest) | 1 | 3.383 | 18 `#[test]` |
| `src-tauri/geometry` (Wand-Join-Mathe, **unbenutzt**) | 1 | 1.057 | 8 `#[test]` |
| `src-tauri/trucksolid` (CSG/Extrusion, `truck`+`csgrs`) | 2 | 758 | 15 `#[test]` |
| `src-tauri/dwgimport` (DXF-Parser-Spike, **unbenutzt**) | 1 | 284 | 1 `#[test]` |
| `src-tauri/src` (Tauri-Host: Fenster, Dialoge, Lock) | 4 | 1.044 | — |
| **Gesamt** | | **~108.000** (ohne Tests) / **~125.000** (mit Tests) | 869 Vitest + 157 Rust-Tests |
Größte TS-Bereiche: `plan/` (23.626 LOC — Plan-Ableitung + 3 Renderer),
`ui/` (12.897 LOC — App-Shell, ResourceManager, Ribbon), `panels/` (9.538 LOC),
`model/` (7.043 LOC inkl. Tests), `commands/` (6.973 LOC), `io/` (5.299 LOC),
`state/` (5.297 LOC), `geometry/` (5.559 LOC), `viewport/` (5.165 LOC),
`export/` (4.280 LOC). `src/App.tsx` allein ist **7.130 Zeilen**.
### Tempo
362 Commits in 3 Wochen; Woche 27 (30.6.6.7.): 233 Commits, Woche 28: 126,
danach starker Rückgang (Woche 30 bislang 3) — die Session-Dichte hat spürbar
abgenommen, nicht das Projekt gestoppt (siehe PENDENZEN.md, weiterhin aktiv).
## 2. Architektur, wie sie WIRKLICH ist (nicht wie geplant)
### 2.1 Datenmodell — kein `Element[]`-Union, sondern typisierte Arrays
`ARCHITECTURE.md` (alt) plante eine diskriminierte Union `Element = Wall | Door |
Window | …`. Tatsächlich hält `Project` (`src/model/types.ts:2095`) **pro
Bauteiltyp ein eigenes optionales Array**: `walls`, `ceilings?`, `roofs?`,
`doors`, `openings?` (Fenster/Türen gehostet in Wänden, `kind:"window"|"door"`),
`stairs?`, `rooms?`, `columns?`, `extrudedSolids?`, `drawings2d`, `context?`
(Importe/Terrain), plus die Bibliotheks-/Typtabellen (`lineStyles`, `hatches`,
`components`, `wallTypes`, `roofTypes?`, `doorTypes?`, …) und die
Dokument-Ebene (`viewSnapshots?`, `layouts?`, `masterLayouts?`, …). Der Typ-Alias
`Element` (types.ts:1601) existiert zwar noch, wird aber **nirgends** verwendet
(Element-Baum/Selektion arbeiten direkt auf den typisierten Arrays). Praktisch
funktioniert das gut (jeder Bauteiltyp hat sein eigenes, spezifisches Interface),
ist aber eine bewusste Abweichung vom ursprünglichen Uniform-Union-Plan.
### 2.2 State — kein Zustand/Redux/Immer, sondern ein Eigenbau
`docs/design/state-architecture.md` empfahl **Zustand**. Gebaut wurde stattdessen
ein **abhängigkeitsfreier Store auf `useSyncExternalStore`** (`src/state/store.ts`,
gleiches Muster wie `src/i18n`). `createStore()` komponiert Slice-Fabriken
(`projectSlice` inkl. Undo/Redo, `historySlice`, `selectionSlice`, `viewSlice`,
`layoutSlice`, `siteSlice`, `notifySlice`) über eine gemeinsame `RootState`.
Funktioniert, aber: der geplante Folgeschritt „App.tsx wird dünne Shell,
View-Routing nach `src/views/`, Kontextmenüs nach `src/menus/`" ist **nicht**
passiert — `src/views/` und `src/menus/` existieren nicht, View-Umschaltung und
Kontextmenü-Aufbau liegen weiterhin inline in `App.tsx` (7.130 Zeilen). Das ist
der deutlichste Doku-vs-Code-Widerspruch im ganzen Repo (CONVENTIONS.md
verlangt explizit das Gegenteil).
### 2.3 Rendering — drei 2D-Pfade, zwei 3D-Viewports
**2D-Plan:** es gibt tatsächlich **drei** koexistierende Renderer, nicht einen:
1. `PlanView.tsx` (SVG) — Referenz-/Fallback-Pfad, bleibt IMMER im DOM für
Hit-Testing/Grips, unabhängig davon was zeichnet.
2. `plan/glPlan/` — eigener TypeScript-WebGL2-Renderer (`glPlanCompile/-Render/
-Shaders/-Hatch.ts`).
3. `useWasmPlanRenderer.ts` → Rust-`render2d`-Crate (WGSL, nativ via wgpu,
Web via WebGPU/WebGL2-Fallback, inkl. echtem Text-Rendering via `glyphon`).
Beide GPU-Pfade fallen bei Initialisierungsfehler still auf SVG zurück.
**3D:** ebenfalls zwei Viewports: `Viewport3D.tsx` (three.js, „Free"-Stufe) und
`Wasm3DViewport.tsx` (Rust/wgpu „Nordstern", editierbar, **Default**). Ein
Settings-Schalter wählt die Engine.
### 2.4 Schnitt/Section — NICHT über HLR, sondern eigene Rust-Pipeline
`src/section/hlr.ts` + `occt.ts` (der ursprüngliche OpenCascade.js-HLR-Spike aus
Phase 0) hat **keinen einzigen Aufrufer mehr** im gesamten `src/` — toter Code.
Der tatsächliche, funktionierende Live-Schnitt läuft über einen völlig anderen,
analytischen Mechanismus: `App.tsx` (`section3dCutId`/`section3dPlane`) →
`Wasm3DViewport.tsx` (`section3d`-Prop → `setSectionPlane`) →
`src-tauri/render3d/src/{section.rs, section_boolean.rs, section_fill.rs}`.
Die Rust-Seite nutzt aus, dass jedes Bauteil ein Prisma mit konstantem
Querschnitt ist — eine Schnittebene liefert dadurch immer ein
achsparalleles Rechteck, nie ein Trapez; `section_boolean.rs` ist ein 1:1-Port
von `toSection.ts::subtractDominantBands`, damit 2D-Plan-Schnitt und
3D-Live-Schnitt exakt übereinstimmen. Kein Worker, kein Comlink (beides war
geplant, keines existiert) — läuft synchron/GPU-seitig.
### 2.5 Öffnungen als Löcher — echt, aber kein Mesh-Boolean
Fenster/Türen schneiden echte achsparallele Rechteck-Löcher aus dem Wandkörper
(`plan/toWalls3d.ts` `RHole`/`subtractSpans`, gespiegelt in
`render3d/{mesh.rs,section.rs}`) — funktioniert, ist aber KEIN generisches
Mesh-Boolean. Ein echtes CSG-Boolean existiert bereits (`trucksolid::boolean_mesh`,
`csgrs`-basiert, 15 Rust-Tests) und wird von `src/engine/truckSolid.ts` für das
Extrusions-Kommando genutzt — ist aber **nicht** an die Wand/Öffnungs-Pipeline
angeschlossen (bestätigt: `booleanMesh` hat ausserhalb von `truckSolid.ts`
keinen Aufrufer).
### 2.6 Rust-Workspace: sechs unabhängige Crates, nicht ein Workspace
`src-tauri/Cargo.toml` bindet nur den Tauri-Host (`cad-tauri`) als Workspace-
Mitglied; `render2d/render3d/geometry/kernel2d/trucksolid/dwgimport` sind
**eigenständige Cargo-Packages**, die dem Host nur optional (Features
`native2d`/`native3d`, standardmässig AUS) als Path-Dependency zugespielt
werden. Jedes Crate muss headless (`cargo test`) UND per `wasm-pack --features
web` bauen, ohne den Tauri-Toolchain-Zwang zu erben — bewusst so geschnitten.
Geteilte Abhängigkeiten: `wgpu 29`/`naga 29` (render2d+render3d, versionsgekoppelt
wegen `glyphon 0.11`), `truck-modeling`+`csgrs`(gepinnter Git-Rev)+`nalgebra`
nur in `trucksolid`.
### 2.7 Zwei Desktop-Rahmen: Tauri (macOS) + Electron (Linux)
Die App läuft plattformabhängig in **zwei verschiedenen nativen Rahmen** —
das ist Absicht, kein Wildwuchs, und hängt an **WebGPU**:
- **macOS → Tauri.** WKWebView unterstützt WebGPU, das die render2d/render3d-
WASM-Engines brauchen. `src-tauri/tauri.conf.json` (Identifier
`ch.dossier.cad`, eigene Titelleiste, `trafficLightPosition`) +
`isTauriRuntime()`-Gates an 6+ Stellen in `App.tsx` + vier eigene native
Zusatzfenster (`src/native/`: Resources, Settings, DrawingLevels,
LayerSettings, ContextImport).
- **Linux → Electron.** Tauris Linux-Webview **WebKitGTK unterstützt WebGPU
nicht zuverlässig** → dort läuft die App über eine Electron/Chromium-Shell
(`scripts/electron-main.cjs` + `electron-preload.cjs`, gestartet via
`npm run electron`). Der Kommentar in `electron-main.cjs` sagt es explizit:
„Ersetzt WebKitGTK durch Chromium, damit WebGPU zuverlässig läuft."
Beide teilen sich **dieselbe** React-App und dieselbe randlose eigene
Titelleiste; die Laufzeit erkennt den Host über `window.__TAURI__` (Tauri)
bzw. `window.dossierWindow` (Electron, per `contextBridge` injiziert). Die
Fenstersteuerung (`src/ui/WindowControls.tsx`) ist an beide Wege angebunden.
Kein Rust-Backend nötig auf dem Electron-Pfad — `computeJoins` hat einen
TS-Fallback (`src/compute/index.ts`). Electron ist also **kein** totes Gleis,
sondern der aktive Linux-Zielrahmen.
## 3. Feature-Inventar (was tatsächlich funktioniert)
### Modell & Bauteile
- Mehrschichtige Wände (`WallType.layers[]`) mit L-Eck-Gehrung UND
Prioritäts-T-/X-Stössen (`joinPriority` am Component) — **fertig**, in 2D-Plan,
3D-Viewport UND 3D-Live-Schnitt konsistent (Rust-Port `section_boolean.rs`).
- Parametrische Wände (`ParametricWall`: Grid/Modul/Sequenz/Referenzlinie/
bedingte Dicke) lösen sich zu konkreten `Wall[]` auf.
- Decken (Slabs, `ceilings?`) mit Aussparungen, eigenem Typkatalog.
- Türen/Fenster gehostet in Wänden, mit Rahmen/Zarge/Blockrahmen, Kämpfer,
Oberlicht, Detailgrad grob/mittel/fein (2D UND 3D), Schwenkbogen; daneben
existiert weiterhin ein **älteres, separates `Door[]`** neben `Opening[]` —
laut PENDENZEN.md explizit als offene Doppelspur/Aufräum-Punkt vermerkt.
- Treppen (gerade/L/Wendel), geschossübergreifend, 2D-Symbol mit Lauflinie/Pfeil.
- Dächer (Flach/Pult/Sattel/Walm/Mansarde/Zelt) über Rechteck-Umriss, First/
Traufe/Grat im 2D-Plan.
- Stützen (Column) mit Profilbibliothek (Quadrat/Rechteck/Rund/I/Rohr).
- Räume (SIA-416: HNF/NNF/VF/FF/GF/AGF) mit automatischer Bilanz + CSV-Export,
Raumstempel-Editor (Drag&Drop-Felder).
- Extrudierte Volumenkörper (truck-Integration: konkave Profile, Verjüngung).
- Kontext-Layer (Terrain-TIN, importierte Meshes, Konturen) — semantisch getrennt.
### Zeichnen & Bedienung
- Rhino-artiges Kommandosystem (`commands/`): getippte Koordinaten
(`5,3`/`r5,3`/`5<45`), Tab-Feld-Zyklus für Präzisionseingabe
(`src/ui/CommandLine.tsx`), Alias/Autocomplete, ~25 Kommandos (wall, ceiling,
opening, stair, column, roof, room, line/polyline/rect/circle/arc, text,
move/mirror/copy/offset/trim/join, extrude, import, terrain, measure,
Schnittlinie, Georef).
- Snapping (Endpunkt/Mitte/Schnittpunkt/Lot/Raster/Ortho), Grips, Array,
Trim/Split/Join, 2D-Booleans (Union/Subtract/Intersect via `polygon-clipping`).
- Messwerkzeug (Polygonzug, Länge + Fläche).
- Rich-Text-Annotationen (Bold/Kursiv/Hoch-/Tiefstellung).
### Darstellung / Ressourcen
- Resource Manager: Line/Hatch/Component-Manager, Wand-/Decken-/Tür-/Fenster-/
Treppen-/Dach-Typeditoren — als eigenständiges natives Fenster (nicht als
Dock-Panel).
- Materialbibliothek: 13 fest gebündelte PBR-Starter (ambientCG, lokale
Texturen) **plus** Live-Suche der kompletten ambientCG-Bibliothek (Auflösung
1K/2K/4K, on-demand Download+Entpacken via `jszip`, Proxy wegen CORS) — heute
bereinigt (siehe Commit-Historie dieser Session).
- Regelbasierte Overrides (Bedingung → Farbe/Strichstärke/Schraffur/Sichtbarkeit).
- Detailgrad grob/mittel/fein je Bauteil + Dokument-Override.
- Hell-/Dunkel-Theme, Akzentfarben.
### Pläne / Output
- Ausschnitte (View-Snapshots: Kamera, Massstab, Detailgrad, Sichtbarkeiten,
Override-Preset) in Ordnerstruktur.
- Layout-Blätter (Plansätze): Papierformat/-grösse, mehrere Viewports pro
Blatt, Masterlayout-Vererbung (Titelblock), Ordnerstruktur, freie 2D-Annotationen.
- Vektor-Export: PDF (Einzelblatt UND **Mehrseiten pro Ordner**,
`layoutPdf.ts::buildFolderPdf`), DXF, IFC4 (mit echten Fenster-/Tür-Löchern,
deterministischen GUIDs), STL, OBJ, CSV-Bauteil-Schedule (volles Element-Set).
- Kamera-Presets (Kardinal + Iso), Norden-Rotation.
### Import / Kontext
- DXF (Konturen), DWG (`@mlightcad/libredwg-web`, WASM), `.lin`/`.pat`.
- Swisstopo: swissBUILDINGS3D (radiusgenau zugeschnitten, nicht die ganze
STAC-Kachel), swissALTI3D, SWISSIMAGE-Orthofoto-Draping, LV95↔WGS84,
Georeferenzierung über EINEN Vermessungspunkt (E/N/H, `geoAnchor`).
- OSM/Overpass-Kontextimport (7 Kategorien).
- Terrain-Mesh-Generator.
### Desktop-Integration (Tauri)
- Eigene randlose Fenster mit nativer Titelleiste (macOS-Ampel-Position).
- Native Speichern/Öffnen-Dialoge (`plugin-fs`/`plugin-dialog`), eigenes
`.obp`-Projektdateiformat.
- OS-Level-Exklusiv-Lock gegen Doppelöffnen desselben Projekts (`fs4`-Crate).
- Vier eigenständige native Zusatzfenster (Resources, Settings, DrawingLevels,
LayerSettings, ContextImport) statt Overlay/Modal.
- Native macOS-Menüleiste.
- i18n de/en durchgängig, eigener `t()`-Mechanismus (kein i18next).
## 4. Mist-Liste — Befunde, Doku-Widersprüche, offene Fäden
### 4.1 Verwaiste WASM-Crates (gebaut, aber nirgends importiert)
- **`src-tauri/geometry`** (1.057 LOC, 8 Tests) → `pkgGeometry` — **null**
Importstellen in `src/`. Wand-Join-Mathe existiert redundant als TS
(`src/model/joins.ts`) UND als Rust-Port, aber nur die TS-Version läuft.
- **`src-tauri/dwgimport`** (284 LOC) → `pkgDwgImport` — **null** Importstellen;
DWG-Import läuft stattdessen über `@mlightcad/libredwg-web` (npm-Paket).
- **`src-tauri/kernel2d`** (3.383 LOC, 18 Tests) → `pkgKernel2d` — wird nur von
einem Paritätstest (`kernel2d.parity.test.ts`) konsumiert, nicht produktiv.
Die TS-Version `src/geometry/kernel2d.ts` (886 Zeilen) ist die tatsächlich
laufende Implementierung. Laut PENDENZEN.md bewusst so belassen: ein
Join-Benchmark zeigte WASM unter ~100 Wänden **langsamer** als die naive
TS-Routine. Kein Bug, aber die drei Crates zusammen sind ~4.700 Zeilen Rust
(+44 Tests), die aktuell nichts zur Laufzeit beitragen ausser einem
Korrektheits-Cross-Check für kernel2d.
**Empfehlung:** entweder (a) `geometry`- und `dwgimport`-Crate + ihre
`build:*`-Scripts entfernen (kein Nutzen, nur Wartungslast), oder (b)
explizit als „Referenzimplementierung/Zukunftsoption" in ARCHITECTURE.md
dokumentieren, damit niemand sie für aktiv hält.
### 4.2 Toter Code
- `src/section/hlr.ts` + `occt.ts` + `occt-wasm.d.ts` (473 LOC) — OCCT-WASM-
HLR-Spike aus Phase 0, **keine Aufrufer mehr**. Ersetzt durch die analytische
Rust-Schnitt-Pipeline (§2.4). `opencascade.js` bleibt als npm-Dependency
bestehen, obwohl nur noch dieser tote Code sie importiert.
- `src/export/planToPrintSvg.ts` — Kommentar im Code selbst sagt „ERSETZT durch
`sceneToPrintSvg.ts`", ist aber noch im Baum.
- `Element`-Typalias (`src/model/types.ts:1601`) — definiert, nirgends benutzt.
### 4.3 Das grösste Doku-vs-Code-Problem: App.tsx
CONVENTIONS.md verlangt seit Tag 1 „App.tsx bleibt dünner Shell, keine
Geschäftslogik". `docs/design/state-architecture.md` plante explizit die
Extraktion nach `src/views/` (View-Routing) und `src/menus/`
(Kontextmenü-Aufbau). Beide Ordner **existieren nicht**. `App.tsx` ist mit
**7.130 Zeilen** die grösste Einzeldatei des Projekts und enthält weiterhin
View-Umschaltung und Kontextmenü-Konstruktion inline. Das ist der genaue
„God-Component"-Zustand, den die Doku von Anfang an vermeiden wollte.
### 4.4 Bekannte Doppelspur: `Door[]` vs. `Opening[]`
`Project` führt sowohl ein älteres `doors: Door[]` als auch das neuere,
allgemeinere `openings?: Opening[]` (`kind:"door"|"window"`). Laut
PENDENZEN.md ist das erkannt und als Aufräum-Punkt vorgemerkt, aber nicht
konsolidiert.
### 4.5 Doku-Widersprüche (README/ARCHITECTURE/CONVENTIONS vs. Realität)
| Dokument | Behauptung | Realität |
|---|---|---|
| README.md | Shell = Electron, Tauri „ausrangiert" | Falsch andersrum gedacht: **beide** sind aktiv — Tauri auf macOS (WKWebView+WebGPU), Electron auf Linux (WebKitGTK kann kein WebGPU); siehe §2.7 |
| README.md | „HLR noch nicht ans UI verdrahtet, Views sind Stubs" | Der OCCT-HLR-Pfad stimmt (tot), aber Schnitte/Ansichten funktionieren real über eine andere, eigene Rust-Pipeline |
| README.md | „Prioritäts-T-/X-Stösse … Risiko #1" unter „bewusst offen" | Seit 7.7. erledigt, inkl. Rust-Port |
| README.md | „PDF-Export ist noch single-sheet" | `layoutPdf.ts::buildFolderPdf` erzeugt echte Mehrseiten-PDFs pro Ordner |
| README.md | Engine-Liste nennt nur render2d/render3d | Es gibt 6 Rust-Crates (+kernel2d/geometry/trucksolid/dwgimport) |
| CONVENTIONS.md | Struktur-Ziel `src/views/`, `src/menus/` | Existieren nicht; Logik liegt in `App.tsx` |
| CONVENTIONS.md | Dev-Port 5173 | Tatsächlich 5187 (`vite.config.ts`, `tauri.conf.json`) |
| ARCHITECTURE.md | Ziel-Struktur `store/`, `sheets/`, `workers/geometry.worker.ts` (Comlink) | Tatsächlich `state/`, `panels/layoutModel.ts`; kein Worker/Comlink irgendwo im Projekt |
| ARCHITECTURE.md | HLR „im Web Worker (Comlink)" | Kein Comlink im Projekt; Schnitt läuft synchron GPU-seitig in Rust |
| HANDOVER.md | Neuester Block: „Stand 2026-07-09" | Jüngster Commit + PENDENZEN.md sind vom 17.20.7. — 8+ Tage/mehrere Sessions veraltet |
### 4.6 Codequalität — besser als der Tempo vermuten lässt
Trotz 3 Wochen / 362 Commits über viele autonome Sessions: **keine** FIXME/HACK/
XXX-Marker im ganzen Projekt; nur 2 TODOs (beide bekannt/harmlos: Geländer in
`Viewport3D.tsx:2649`, Kanten-Tiefe in `toSection.ts:1093`); `eslint-disable`
fast ausschliesslich `react-hooks/exhaustive-deps` (bewusst); keine
`_v2`/`_old`/`_backup`-Dateileichen. Die eigentliche Aufgabenliste lebt
diszipliniert in PENDENZEN.md statt in Code-Kommentaren verstreut — gesünder
als der Durchschnitt für dieses Bau-Tempo.
### 4.7 Genuine offene Punkte (Auszug aus PENDENZEN.md, Details dort)
- Geo-Block: Layer-Zuordnung für importierte Gebäude/Terrain, reale Höhen +
Projekt-müM-Draping, SWISSIMAGE-Draping.
- 3D-Feinschliff: Fensterrahmen-Ecken bei „fein" überlappen (kein Gehrungs-
Union), Dach-First/Grat hat unverschmolzene Dreiecke, unerklärte
Vertikalstreifen auf oberen Wandflächen (undiagnostiziert).
- truck-Boolean nicht an die Wand/Öffnungs-Pipeline angeschlossen (Scope-
Entscheid mit Nutzer ausstehend).
- Feld-Controller (Tab-Zyklus) fehlt für Body-Move und für 3D-Griffe generell
(nur 2D-Einzelpunkt-Drag hat ihn); 3D-Griff-Drag hat gar kein Snapping.
- `make2D`-Kommando (3D→flacher 2D-Plan mit Füllungen) ungebaut.
- Ribbon-3D-Tab leer; einige Punkte visuell noch nicht in Tauri abgenommen
(u. a. Materialfarben-Textur-Array, Schraffur-Schnittfüllung — laut
PENDENZEN als „[~] implementiert, aber unverifiziert" markiert).
- DWG/DXF-Domänen-Mapping (Entitäten → Wände/Öffnungen) unbegonnen; DWG-Schreiben
fehlt (Lesen über `libredwg-web` vorhanden).
- Teamwork/Kollaboration (Supabase) bewusst nicht begonnen, gilt als späte Phase.
## 5. Vergleich: Tag-1-Vision vs. heute
| Vision (28./29.6.) | Heute |
|---|---|
| Three.js als einziger 3D-Renderer, OpenCascade.js für Booleans/HLR | Eigene Rust/WASM-Engines („Nordstern") für 2D+3D; three.js nur noch „Free"-Fallback; OCCT-Pfad tot |
| Ein `Element[]`-Union | Typisierte Arrays pro Bauteiltyp auf `Project` |
| Zustand-Store | Eigener `useSyncExternalStore`-Store |
| Web Worker + Comlink für HLR | Synchrone, analytische Rust-GPU-Schnitt-Pipeline |
| „Booleans: Entscheidung in Phase 0" | Trucksolid/csgrs-CSG existiert, ist getestet, aber nicht an Wände angeschlossen |
| web-ifc für IFC | Eigener IFC4-Writer (`exportIfc.ts`) |
| Phase 25 grösstenteils „Backlog" | Treppen, Dächer, Stützen, SIA-416, Swisstopo, OSM, Kamera-Presets, Layouts, Ausschnitte, Terrain — alles bereits gebaut |
Kurz: die **Prinzipien** (ein Modell, viele Ableitungen; Darstellung erst beim
Rendern; keine Cache-Stale-Bugs) haben gehalten und wurden korrekt umgesetzt.
Die **konkreten Technologie-Entscheidungen** sind fast durchgängig anders
gelaufen als geplant — meist zugunsten einer eigenen, schnelleren Rust/WASM-
Lösung statt einer Drittbibliothek.
+8 -1
View File
@@ -1,6 +1,13 @@
# Dokumentation — Standalone Browser-BIM (cad) # Dokumentation — Standalone Browser-BIM (cad)
> Stand: 2026-06-29 · Übergeordnet: [ROADMAP.md](../ROADMAP.md) (Vision & Phasen) · > Stand: 2026-06-29 · **Historische Recherche-/Design-Dokumente aus der ersten
> Woche.** Mehrere Kern-Empfehlungen hier (replicad/OCCT als B-Rep-Kernel,
> Manifold, web-ifc, `three/webgpu`) wurden im tatsächlichen Bau **nicht**
> umgesetzt — stattdessen entstanden eigene Rust/WASM-Rendering-Engines
> („Nordstern"). Für den aktuellen Ist-Zustand: **[../STATUS.md](../STATUS.md)**
> und **[../ARCHITECTURE.md](../ARCHITECTURE.md)**. Die Dokumente unten sind als
> Recherche-Hintergrund weiterhin lesenswert, aber nicht mehr aktueller Plan.
> Übergeordnet: [ROADMAP.md](../ROADMAP.md) (Vision & Phasen, ebenfalls historisch) ·
> [CONVENTIONS.md](../CONVENTIONS.md) (Konventionen) · [ARCHITECTURE.md](../ARCHITECTURE.md). > [CONVENTIONS.md](../CONVENTIONS.md) (Konventionen) · [ARCHITECTURE.md](../ARCHITECTURE.md).
Dieses Verzeichnis bündelt die Recherche- und Design-Dokumente für `cad`, die Dieses Verzeichnis bündelt die Recherche- und Design-Dokumente für `cad`, die