diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2dc2b32..e42fcfd 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,452 +1,390 @@ -# Architektur — Standalone Browser-BIM (cad) +# Architektur — Dossier (Desktop-CAAD) -> Stand: 2026-06-29 -> Vision, Phasen und Backlog: [ROADMAP.md](ROADMAP.md). Konventionen: [CONVENTIONS.md](CONVENTIONS.md). -> Detail-Designs: [docs/design/elements.md](docs/design/elements.md) · -> [docs/design/plans-output.md](docs/design/plans-output.md) · -> [docs/design/resources-graphics.md](docs/design/resources-graphics.md). +> Stand: 2026-07-21 (grundlegend überarbeitet — siehe [STATUS.md](STATUS.md) für +> die volle Bestandsaufnahme inkl. Mist-Liste, die diese Überarbeitung begründet). +> Vision/Phasen (historisch, Tag-1-Stand): [ROADMAP.md](ROADMAP.md). Konventionen: +> [CONVENTIONS.md](CONVENTIONS.md). Detail-Designs (teils ebenfalls veraltet, +> siehe Hinweis in [docs/README.md](docs/README.md)): [docs/design/](docs/design/). -Dieses Dokument beschreibt, **wie** die Standalone-Browser-App gebaut wird und -wie sie die Konzepte des DOSSIER-Rhino-Plugins in Browser-Äquivalente übersetzt. -DOSSIER ist ein Rhino-8-Plugin (Python + React-WebView); `cad` ist die *eigen­ -ständige* Browser-Variante: kein Rhino-Document, kein `doc.Strings`, kein -IronPython — stattdessen ein **eigenes typisiertes Datenmodell in TypeScript**, -gerendert über **Three.js** (3D) und **SVG** (Plan), gespeichert als **JSON-Datei**. +Dieses Dokument beschreibt, **wie** Dossier tatsächlich gebaut ist — nicht wie +es am ersten Tag geplant war. Dossier ist die eigenständige Neuimplementierung +des Rhino-Plugins [DOSSIER](https://git.openbureau.ch/karim/dossier): dieselbe +Denkweise (Geschosse, Kategorie-Ebenen, mehrschichtige Bauteile, +Prioritäts-Verschneidung), aber als **native Desktop-App** mit einem **eigenen +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 -UI-Texte sind deutsch. Einheiten intern in **Metern**. +**Desktop-Rahmen (plattformabhängig, wegen WebGPU):** auf **macOS Tauri** +(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 -Das semantische Gebäudemodell ist die **einzige Wahrheit**. Jede Sicht (3D, -Grundriss, Schnitt, Ansicht) ist eine **reine Ableitung** (`derive`) daraus. +Das semantische Gebäudemodell (`Project`) ist die **einzige Wahrheit**. Jede +Sicht (3D, Grundriss, Schnitt, Ansicht) ist eine **reine Ableitung** daraus. 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 - │ resources · types · designLevels · │ - │ layers · elements │ + │ resources · types · drawingLevels · │ + │ layers · walls/doors/openings/stairs/… │ └──────────────┬───────────────────────────┘ │ pure derive() - ┌───────────────────────┼────────────────────────────┐ - ▼ ▼ ▼ - Scene3D (Three.js) PlanModel → SVG SectionModel → SVG - buildScene() generatePlan() generateSection() (HLR) - │ │ │ - └────────── beide lesen dieselben joins/components/styles ──────────┘ + ┌───────────────────────┼────────────────────────────┬───────────────┐ + ▼ ▼ ▼ ▼ + 3D-Viewport 2D-Plan (generatePlan) 3D-Live-Schnitt Export + Viewport3D (three.js) PlanView (SVG) · glPlan (GL2) render3d/section IFC/DXF/ + ODER Wasm3DViewport ODER render2d (Rust/WGSL) (Rust, analytisch)PDF/STL + (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) - -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). +## 1. Repo-Struktur (IST-Zustand) ``` src/ - model/ - types.ts // Project, DesignLevel, LayerCategory, Element-Union (existiert) - geometry.ts // 2D/3D-Mathe ohne Kernel (existiert) - joins.ts // Wand-Verschneidung (Gehrung; später Prio-T/X) (existiert) - sampleProject.ts // Demo-Haus (existiert) - project.ts // Factory, Defaults, Migrationen - selectors.ts // abgeleitete Reads (visibleCodes, elementsOnLevel…) - ids.ts // newId(prefix) — UUID-Erzeugung - elements/ // pro Bauteil ein Modul (Daten + Generierung) - wall.ts opening.ts slab.ts stair.ts roof.ts structure.ts space.ts - resources/ - componentManager.ts hatchManager.ts lineManager.ts symbolLibrary.ts - overrides.ts // regelbasierte Engine (Condition → Action) - store/ - store.ts // Zustand-Store: { project, ui }, Aktionen, Undo/Redo - persistence.ts // Datei save/load (JSON), Autosave (IndexedDB) - history.ts // Undo/Redo-Ring - plan/ - generatePlan.ts // Grundriss aus Footprint + Symbolik (existiert) - generateSection.ts // Schnitt/Ansicht aus 3D-Projektion (HLR-Worker) - PlanView.tsx // SVG-Renderer + Pan/Zoom/Grips (existiert) - primitives.ts // Primitive-Union, SVG-Serializer, DXF/PDF-Export - viewport/ - Viewport3D.tsx // Three.js-Szene aus dem Modell (existiert) - scene.ts // buildScene(project) → THREE.Group (Layer-Spiegel) - clip.ts // Plan-/Schnitt-Clipping über THREE.Plane - camera.ts // Kamera-Presets, Norden-Rotation - sheets/ - layout.ts // Sheet/Viewport-Datenmodell - SheetEditor.tsx // Plansatz-Editor - exportPdf.ts // Vektor-PDF (svg → pdf-lib / jsPDF) - workers/ - geometry.worker.ts // Booleans (Öffnungen) + HLR via Comlink - ui/ - App.tsx Navigator panels… // React-Oberfläche (heute in App.tsx, wird gesplittet) + model/ Project-Schema (types.ts, ~2500 LOC), joins.ts (Wand-Gehrung/ + -Prio-Stösse), parametricWalls.ts, roomStamp.ts, terrain.ts, + geoRebase.ts, sampleProject.ts + geometry/ 2D-Kernel (kernel2d.ts: offset/trim/fillet/split — LIVE, siehe + §3), ceiling/opening/roomArea/roomBoundary/stair/roof/column.ts, + polygonHoles.ts + commands/ Rhino-artiges Kommandosystem: types/registry/engine/parseInput.ts + + cmds/ (ein Modul je Kommando: wall, opening, stair, roof, + column, room, line/rect/circle/arc, move/mirror/copy/offset/ + trim/join, extrude, import, terrain, measure, georef, …) + tools/ Interaktive Zeichenwerkzeuge, snapping.ts, transform.ts (Grips) + compute/ Compute-Boundary: leitet Ops (aktuell nur computeJoins) unter + Tauri via `invoke` an Rust weiter, sonst TS-Fallback + plan/ generatePlan.ts (2D-Ableitung), PlanView.tsx (SVG + Pan/Zoom/ + Grips), glPlan/ (eigener WebGL2-Renderer), toSection.ts/ + toElevation.ts (Schnitt/Ansicht-Ableitung), toWalls3d.ts, + toRenderScene.ts (Szene für Rust-render2d), wallMeshCut.ts + viewport/ Viewport3D.tsx (three.js, „Free") UND Wasm3DViewport.tsx + (Rust/wgpu „Nordstern", Default/editierbar), raycast3d.ts + section/ TOTER Code (OCCT-WASM-HLR-Spike, keine Aufrufer mehr) — Schnitt + läuft über render3d/section.rs, siehe §4.3 + export/ exportIfc.ts (IFC4), exportDxf.ts/dxfWriter.ts, exportPdf.ts, + layoutPdf.ts (Mehrseiten-PDF pro Ordner), exportMesh.ts (STL/OBJ), + exportSchedule.ts (CSV-Bauteilliste), sceneToPrintSvg.ts + materials/ ambientcg.ts (Live-Suche ambientCG-API), library.ts (13 + gebündelte Starter-Materialien), runtime.ts (PBR-Material aus + ComponentMaterial via three.js TextureLoader) + io/ DXF/DWG-Import, Swisstopo (swissBUILDINGS3D/-ALTI3D/SWISSIMAGE, + LV95↔WGS84), OSM-Overpass, .lin/.pat-Parser, projectFile.ts + (.obp Speichern/Laden, Tauri-Lock) + overrides/ Regelbasierte Override-Engine (Bedingung → Aktion) + state/ Eigener Store auf `useSyncExternalStore` (KEIN Zustand/Redux): + store.ts + Slices (project/history/selection/view/layout/site/ + notify), appStore.ts komponiert sie + 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.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 -JSON-Bäume (`dossier_zeichnungsebenen`, `dossier_ebenen`). Wir übernehmen das -1:1 in `types.ts` (existiert bereits als `drawingLevels` + `layers`): +1. **Zeichnungsebenen** (`DrawingLevel`) — Geschoss (`kind:"floor"`, + `floorHeight`/`cutHeight`/`baseElevation`), Schnitt/Ansicht + (`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 - 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 …` +### 2.2 Elemente — typisierte Arrays statt `Element[]`-Union -Ein **Element** kennt sein **Geschoss** (`floorId`) *und* seine **Ebene** -(`categoryCode`). Sichtbarkeit ergibt sich aus dem Schnitt (Geschoss `geschoss × code` -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. +Anders als ursprünglich geplant hält `Project` (`src/model/types.ts:2095`) +**pro Bauteiltyp ein eigenes (meist optionales) Array**: ```ts -interface Resources { - lineStyles: LineStyle[]; // Line Manager: { id, name, weight(mm), color, dash:number[] } - hatches: Hatch[]; // Hatch Manager: { id, name, pattern, scale, angle, lineStyleId } - components: Component[]; // Component Manager (= DOSSIER-Material erweitert): - // { id, name, hatchId, color3d, texture3d?, joinPriority } +interface Project { + walls: Wall[]; + ceilings?: Ceiling[]; + roofs?: Roof[]; + 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 -**Daten** statt Hardcode — steuert die Prioritäts-T-Verschneidung (Risiko #1). +Ein `Element`-Typalias existiert noch (`kind:"door"|"window"` etc.), wird aber +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 +interface Resources { lineStyles: LineStyle[]; hatches: Hatch[]; components: Component[]; } 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 -einmal pflegen). 3D und Plan lesen dieselben `layers[]`. +`joinPriority` sitzt am `Component` (Daten statt Hardcode) und steuert die +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 -`Window | Slab | Stair | Roof | Column | Beam | Space | Draw2d`. Jedes Element: -`{ id, type, floorId, categoryCode, ... }`. Gehostete Elemente (Tür/Fenster) -tragen `hostWallId` statt `floorId` (Geschoss ergibt sich aus der Wand). Volle -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 }; -} -``` +`ViewSnapshot` (Kamera/Massstab/Detailgrad/Sichtbarkeiten/Override-Preset) in +`ViewSnapshotFolder`-Bäumen; `Layout` (Papierformat, mehrere +`LayoutViewport`s, freie `LayoutAnnotation`s, optionale `MasterLayout`-Vererbung) +in `LayoutFolder`-Bäumen. Beide gehören ins Dokument (nicht LocalStorage). --- ## 3. State, Persistenz, Undo/Redo -### 3.1 Store — Zustand +### 3.1 Store — eigener `useSyncExternalStore`, nicht Zustand -Heute: `useState` in `App.tsx`, immutabel via `setProject`. Das skaliert -nicht für Werkzeuge + Undo. Migration auf **Zustand** (ROADMAP §4): +Entgegen der ursprünglichen Empfehlung (Zustand-Library) wurde ein +**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 -interface AppState { - project: Project; // das Dokument - ui: { // flüchtig, NICHT persistiert/undobar - 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; -} -``` +**Nicht umgesetzt** (geplant in `docs/design/state-architecture.md`): die +Extraktion von View-Routing nach `src/views/` und Kontextmenü-Aufbau nach +`src/menus/` — beide Ordner existieren nicht, diese Logik liegt weiterhin +inline in `App.tsx` (7.130 Zeilen). Siehe STATUS.md §4.3. -**Trennung Modell ↔ UI** ist hart: nur `project` ist undobar und wird gespeichert. -`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 -### 3.2 Persistenz — JSON-Datei statt `doc.Strings` - -DOSSIER speichert *alles* in `doc.Strings` (Key-Value am Rhino-Document) + -globale Presets als JSON-Dateien im User-Home. Browser-Äquivalent: - -| DOSSIER | Browser (cad) | -|---|---| -| `doc.Strings.SetString(key, json)` | Feld im `Project`-Objekt (ein JSON-Baum) | -| `.3dm`-Datei mit eingebetteten Strings | **`.cad.json`-Datei** via File System Access API | -| 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 // showSaveFilePicker → .cad.json -async function loadFromFile(): Promise // showOpenFilePicker; Fallback -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 + `` - 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. +- **Datei:** eigenes `.obp`-Format (`src/io/projectFile.ts`), unter Tauri über + native Speichern/Öffnen-Dialoge (`plugin-fs`/`plugin-dialog`), im Browser + Blob-Fallback. Ältere `.json`-Projekte bleiben ladbar. + OS-Level-Exklusiv-Lock (`fs4`-Crate, `src-tauri/src/lock.rs`) verhindert + Doppelöffnen desselben Projekts. +- **Compute-Boundary:** `src/compute/index.ts` leitet einzelne Operationen + (aktuell nur `computeJoins`) unter Tauri via `invoke` an eine native Rust- + Implementierung weiter; im Browser bzw. für nicht migrierte Ops (Room- + Detection, DWG-Parsing) läuft die TS-Implementierung. ### 3.3 Undo/Redo -DOSSIER überlässt Undo Rhino (und hat dadurch Cache-Stale-Bugs, Schwachstelle -#4.3). Wir besitzen das Dokument selbst → **eigener History-Ring**: - -```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. +Eigener History-Ring im `projectSlice` (kein DOSSIER-Sticky-Bus, kein +Cache-Stale-Problem, da alle Sichten pure Ableitungen sind). --- ## 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` -(`layer_builder.build_layers`) und hängt jedes Objekt an den passenden Sublayer. -Browser-Spiegel: ein **`THREE.Group`-Baum** mit identischer Struktur, gebaut aus -dem Modell (nicht persistiert — reine Ableitung): +- **`Viewport3D.tsx`** — three.js, die „Free"-Stufe. +- **`Wasm3DViewport.tsx`** — Rust/wgpu, „Nordstern", editierbar, **Default**. + Ein Settings-Schalter wählt die Engine; WebGL2/three.js nur noch als + 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 -└─ levelGroup[floorId] (y-Offset = baseElevation) ← Geschoss - └─ categoryGroup[categoryCode] (userData.code) ← Ebene - └─ element meshes (userData.elementId) +App.tsx (section3dCutId/section3dPlane) + → Wasm3DViewport.tsx (section3d-Prop → setSectionPlane) + → src-tauri/render3d/src/{section.rs, section_boolean.rs, section_fill.rs} ``` -```ts -// scene.ts -function buildScene(project: Project, vis: VisibilityState): THREE.Group -function syncScene(root: THREE.Group, project, vis) // diff statt full rebuild (Perf) -``` +`section_boolean.rs` ist ein 1:1-Port von `toSection.ts::subtractDominantBands` +— 2D-Plan-Schnitt und 3D-Live-Schnitt nutzen dieselbe Prioritäts-Logik und +stimmen dadurch exakt überein. Kein Worker, kein Comlink (beides war geplant, +existiert nirgends im Projekt) — läuft synchron GPU-seitig. -- **Sichtbarkeit:** `categoryGroup.visible = vis.codes.has(code)` und - `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.4 Öffnungen als Löcher (kein Mesh-Boolean) -### 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 -`okff + schnitthöhe` legt, Normale −Z (`layer_builder.update_clipping_plane`). -Zwei Browser-Pfade — **beide nötig** (ROADMAP §3): +### 4.5 Materialien -1. **3D-Viewport im Plan-Modus:** `THREE.Plane(normal=(0,0,-1), constant=cutZ)` über - `renderer.clippingPlanes` + `material.clippingPlanes`. `clip.ts` setzt die Ebene - pro aktivem Geschoss (`cutZ = baseElevation + cutHeight`), Kamera orthografisch - von oben. So sieht man den **echten geschnittenen Volumenkörper**. -2. **Symbolischer SVG-Grundriss** (der Hauptweg, schnell + sauber): `generatePlan.ts` - 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 1–2 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. +`materials/library.ts` (13 gebündelte PBR-Starter, ambientCG CC0) + +`materials/ambientcg.ts` (Live-Suche der kompletten ambientCG-Bibliothek, +1K/2K/4K-Auflösungswahl, On-Demand-Download via `jszip`, Proxy wegen CORS) → +`materials/runtime.ts` baut daraus gecachte `THREE.MeshStandardMaterial`s mit +physisch korrekter Kachelgrösse (UV in Weltmetern). --- ## 5. React-Panel-Struktur -DOSSIER ist eine Sammlung **getrennter WebView-Panels** (EBENEN, ELEMENTE, -GESTALTUNG, OBERLEISTE, MASSSTAB, AUSSCHNITTE, DIMENSIONEN, LAYOUTS, OVERRIDES), -die über die Python-Bridge + `sc.sticky` kommunizieren. Im Browser ist alles -**eine SPA** mit einem geteilten Store — die Panels werden zu Docking-Bereichen. +Dock-/Floating-Panel-System (`src/panels/`: Dock, FloatingPanel, TabStrip, +Registry) mit eingebauten Panels: Tools, Attributes, ObjectInfo, +DrawingLevels, Layers, Site (Kontext/Terrain-Import), RoomBalance +(SIA-416-CSV), Elements (Bauteilbaum), ViewSnapshots, Layouts. -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ TopBar Werkzeuge · Ansichtstyp · Massstab · Snaps · Save/Load │ ≙ OBERLEISTE+MASSSTAB -├──────────────┬──────────────────────────────────────┬───────────────┤ -│ Navigator │ Viewport (Three.js oder SVG-Plan) │ Inspector │ -│ ┌──────────┐ │ ┌────────────────────────────────┐ │ ┌───────────┐ │ -│ │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 -``` +Der **Resource Manager läuft bewusst NICHT als Dock-Panel**, sondern als +eigenständiges (unter Tauri natives) Fenster — ebenso Settings, +DrawingLevels-Detaileditor, LayerSettings und ContextImport +(`src/native/*Window.ts` + `*WindowApp.tsx`), jeweils mit +`isTauriRuntime()`-Gate und ohne separate Browser-Variante (im Browser bleibt +die entsprechende In-App-Overlay-Variante aktiv, wo vorhanden). -**Komponenten-Map** (heute alles in `App.tsx`; wird gesplittet): - -| Bereich | DOSSIER-Panel | cad-Komponente | -|---|---|---| -| Zeichnungsebenen-Liste | EBENEN (oben) | `Navigator/DrawingLevels.tsx` | -| 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. +**Werkzeug-System:** `Tool`-Interface (`src/tools/types.ts`) für Zeichenwerkzeuge +mit Snap-Engine; daneben das umfangreichere **Kommandosystem** (`src/commands/`) +für Rhino-artige getippte Eingabe (`5,3`/`r5,3`/`5<45`, Tab-Feld-Zyklus in +`CommandLine.tsx`) — beide koexistieren, decken unterschiedliche +Interaktionsstile ab. --- -## 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 | -| **`doc.Strings[key]=json`** | Feld im `Project`-JSON | persistiert in `.cad.json` | -| **`.3dm`-Datei** | **`.cad.json`** via File System Access API | + IndexedDB-Autosave | -| **Globale Presets (~/Library/*.json)** | **LocalStorage** + Export/Import | cross-Projekt | -| **`sc.sticky` (cross-modul Bus)** | **Zustand-Store** (reaktiv) | kein Polling, kein None-Bug | -| **`panel_base.BaseBridge` / WebView-IPC** | direkte React-Props/Store | keine `document.title`-Hacks | -| **`document.title="RHINOMSG::"` Polling** | entfällt | SPA, kein WebView | -| **Eto.Forms Satelliten-Fenster** | React-Modal/Drawer | z.B. Ausschnitt-Settings | -| **Rhino Layer-Tabelle (Sublayer-Baum)** | `LayerCategory[]`-Baum + `THREE.Group`-Spiegel | `scene.ts` | -| **`layer_builder.build_layers`** | `buildScene` (Ableitung) | Gruppen statt Layer | -| **`layer.PlotWeight`** | `LineStyle.weight` (mm) → SVG `stroke-width` | massstabsabh. (§plans) | -| **`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 `` | resources-graphics.md | -| **`Linetype`-Tabelle** | `LineStyle.dash[]` → SVG `stroke-dasharray` | resources-graphics.md | -| **`TextEntity` / Rich-Text** | SVG `` / 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) | — | +| Rhino `RhinoDoc` | `Project` (TS-Objekt im eigenen Store) | einzige Wahrheit | +| `doc.Strings[key]=json` | Feld im `Project`-JSON | persistiert in `.obp` | +| `.3dm`-Datei | **`.obp`**-Datei (Tauri `plugin-fs`) | + Blob-Fallback im Browser | +| `sc.sticky` (cross-modul Bus) | eigener `useSyncExternalStore`-Store | kein Polling | +| Rhino Layer-Tabelle | `LayerCategory[]`-Baum, Sichtbarkeit als `.visible`-Flag | pro Renderer umgesetzt | +| Clipping-Plane (`AddClippingPlane`) | `render3d::section.rs` (analytisch, Rechteck-Subtraktion) | KEIN HLR | +| `HLRBRep` | — (ungenutzt; OCCT-Spike ist toter Code) | ersetzt durch obiges | +| `Rhino.Geometry.Brep`-Booleans | `trucksolid::boolean_mesh` (csgrs) | existiert, nicht an Wände angeschlossen | +| Rhino-Grips + DisplayConduit | Pointer-Events + Grip-Overlay (2D vollständig, 3D nur Basis, kein Snap) | siehe STATUS.md §4.7 | +| Swisstopo via .NET HttpClient | `fetch()` (CORS-offen) | radiusgenau zugeschnitten (nicht ganze STAC-Kachel) | +| IronPython-Laufzeit-Risiken | entfällt | TS/Rust | --- -## 7. Was wir von DOSSIER übernehmen — und was nicht +## 7. Was übernommen wurde — und was bewusst anders lief -**Übernehmen (bewährte Konzepte):** -- Zwei-Achsen-Dokumentmodell (Zeichnungsebenen × Ebenen). -- Layer-Codes/Farben/lw 1:1 (`DEFAULT_LAYER_SCHEMA`). -- Component/Hatch/Line-Manager mit id-Verweisen + `joinPriority`. -- Ansichtstypen = Kamera + optionaler Schnitt (vereinheitlicht). -- 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. +**Übernommen (Prinzipien, bewährt):** +- Zwei-Achsen-Dokumentmodell, Layer-Codes 1:1, Component/Hatch/Line-Manager + mit `joinPriority`, LoD (grob/mittel/fein), regelbasierte Overrides, + Ausschnitte, SIA-416-Räume, Norden-Rotation. +- Pure-Ableitungs-Architektur (kein Cache-Stale, kein Sticky-Bus). -**Bewusst anders (Browser-nativ, Schwachstellen vermeiden):** -- **Kein Sticky-Bus** → ein reaktiver Store (vermeidet DOSSIER #4.4/#4.5/#4.6). -- **Kein Monolith** → Bauteil-Module statt 7244-LOC-`elemente.py` (#4.1). -- **Pure Ableitungen** statt mutierter Doc-Objekte → kein Cache-Stale (#4.3), - kein Undo/Redo-Loch. -- **Versioniertes Datei-Schema** statt UserString-Sticky-Migration. -- **Persistenz im Project-JSON** statt verteilt über `doc.Strings`. +**Anders gelaufen als geplant (siehe STATUS.md §5 für die volle Tabelle):** +- Eigene Rust/WASM-Rendering-Engines statt Three.js/OpenCascade.js/web-ifc. +- Typisierte Arrays pro Bauteiltyp statt `Element[]`-Union. +- Eigener Store statt Zustand-Library. +- Analytische Rust-Schnitt-Pipeline statt HLR/Worker/Comlink. +- App.tsx wurde **nicht** wie geplant auf eine dünne Shell reduziert + (`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 -ohne konkretes Problem; kein `try/catch: {}` als Bug-Versteck; erst grep/lesen, -dann editieren; Wand-Geometrie nie „nebenbei" refactoren. +**Anti-Over-Engineering (weiterhin gültig):** keine Abstraktion ohne konkretes +Problem; erst lesen, dann editieren; Geometrie nie „nebenbei" refactoren. --- ## 8. Reihenfolge bei Code-Arbeit -1. **Dieses Dokument + das relevante Detail-Design** (elements/plans-output/ - resources-graphics) lesen. -2. **Dann das betroffene Modul** lesen (nicht raten). -3. **Erst danach editieren**; `Project` immer immutabel via `store.apply()` ändern. -4. **Verifizieren:** `npx tsc -b`, `npm run build`, Screenshot via - `node scripts/probe.mjs` — Geometrie visuell prüfen. -5. **Dieses Dokument aktuell halten**, wenn sich Patterns/Mapping ändern. +1. **STATUS.md** (aktueller Ist-Zustand) + dieses Dokument lesen. +2. **Das betroffene Modul** lesen (nicht raten) — bei Zweifel: welcher der + mehreren Renderer/Pfade ist gerade aktiv (§4.1/§4.2)? +3. **Erst danach editieren**; `Project` immer immutabel via den Store ändern. +4. **Verifizieren:** `npx tsc -b`, `npm test` (Vitest), `cargo test` in den + betroffenen Crates, bei Rust-Änderungen `npm run build:engine{,3d}` (WASM + 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. diff --git a/CONVENTIONS.md b/CONVENTIONS.md index c69aa29..dad627d 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -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) @@ -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) 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, - Statusleiste, Floating-Panels, Ressourcen-Overlay) — keine Geschäftslogik darin. -- **Globaler Zustand in einem Store** (`src/state/`, Slices: project/selection/view/layout). - Komponenten lesen Zustand über Store-Hooks statt Prop-Drilling. -- **Features als eigene Module:** `src/views/` (View-Router-Teile), `src/editors/` - (Inline-Editoren), `src/menus/` (Kontextmenü-Builder), `src/panels/`, `src/ui/`. +- **Ziel:** `App.tsx` bleibt ein dünner Shell (Store-Provider, Oberleiste, + Docks+View-Router, Statusleiste, Floating-Panels, Ressourcen-Overlay) — keine + Geschäftslogik darin. **Realität (Stand 2026-07-21):** `App.tsx` ist mit + ~7.100 Zeilen die grösste Datei des Projekts und enthält weiterhin + View-Umschaltung und Kontextmenü-Aufbau inline — die geplante Auslagerung + 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). - Siehe `docs/design/state-architecture.md`. ## UI-Konventionen @@ -61,4 +73,6 @@ Rendern angewandt, nie in die Geometrie eingebacken. - Ä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 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). diff --git a/HANDOVER.md b/HANDOVER.md index e773a25..093c39d 100644 --- a/HANDOVER.md +++ b/HANDOVER.md @@ -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) Lange autonome Session (Nutzer-Auftrag: „arbeite die Pendenzen/Funktionen durch, diff --git a/README.md b/README.md index 5fc94b7..c9c548e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ -# DOSSIER Standalone +# Dossier Open-Source-CAAD (Computer Aided Architecture Design). Modelliert ein Gebäude 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 -vollständige Fassung. +ohne Revit. **Desktop-App** — auf macOS über **Tauri**, auf Linux über eine +**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 -[DOSSIER](https://git.kgva.ch/karim/DOSSIER): dieselbe Denkweise (Geschosse, -Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts-Verschneidung), aber als -React-App mit eigener Rendering-Engine (Rust/WASM/WebGPU) statt als Rhino-Aufsatz. -Als Desktop-App (Tauri) läuft sie im eigenen Fenster mit voller Engine-Leistung; -browserseitig ist derselbe Kern zugänglich, die App ist die vollständige Fassung. +Das ist die eigenständige Neuimplementierung des Rhino-Plugins +[DOSSIER](https://git.openbureau.ch/karim/dossier): dieselbe Denkweise +(Geschosse, Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts- +Verschneidung), aber mit eigenem Datenmodell + eigener Rendering-Engine +(Rust/WASM/WebGPU, intern „Nordstern" genannt) statt Rhino-Aufsatz. ## 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 3D-Mesh, sondern **analytisch aus den Bauteil-Parametern** (Wandachsen + Dicken → Linien, Öffnungen → Lücken + Symbol). PDF-Export ist derselbe Plan, nur mit -echten mm-Stiftstärken statt Bildschirm-Hairlines — kein zweites Rendering. -Schnitte und Ansichten brauchen den zweiten Weg — echte 3D-Projektion mit -verdeckten Kanten (HLR) —, dessen Machbarkeit per Spike bewiesen, aber noch -nicht ans UI angebunden ist. +echten mm-Stiftstärken statt Bildschirm-Hairlines. Schnitte und Ansichten laufen +über eine eigene, analytische Rust-Pipeline (kein Hidden-Line-Removal nötig, +siehe [ARCHITECTURE.md](ARCHITECTURE.md) §4.3) und sind seit Juli live im +3D-Viewport verdrahtet, nicht nur ein Spike. ## Stand heute -Ehrlich eingeordnet: aus dem Risiko-Spike ist ein **benutzbares 2D-CAD mit -abgeleiteter 3D-Sicht, Vektor-PDF/DXF-Export und einer parametrischen -Wand-Engine** geworden. Der einfachere Teil steht und ist per Screenshot/Probe -verifiziert; die schwierigen BIM-Kernrisiken sind bewusst noch offen. +Aus dem ursprünglichen Risiko-Spike ist binnen weniger Wochen ein +**funktionsreiches Desktop-BIM-Tool** geworden: eigenes semantisches +Gebäudemodell, zwei eigene Rust/WASM-Rendering-Engines, IFC/DXF/PDF/STL/OBJ- +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:** -- Semantisches Modell mit **mehrschichtigen Wänden** und automatischer - **L-Ecken-Gehrung** (`computeJoins`); dieselbe Logik speist Plan *und* 3D. -- **Parametrische Wände** (`ParametricWall`-Regelwerke: Grid/Modul/Sequenz/ - Referenzlinie/bedingte Dicke) lösen sich zu konkreten `Wall`-Objekten auf, - statt jede Wand einzeln von Hand zu ziehen. -- **Dokumentmodell wie in DOSSIER:** Zeichnungsebenen (Geschosse + Schnitte/ - Ansichten/Zeichnungen) × Kategorie-Ebenen (Code-Baum 1:1, `20 Wände`, `30 Decken` …). -- **Zeichnen & Editieren** im Grundriss: Wand, Linie, Polylinie, Rechteck, Kreis, - Öffnungen, Treppen, Decken, Raumstempel; Snapping (Endpunkt/Mittelpunkt/ - Schnittpunkt/Lot/Raster/Ortho), Grips, Move/Copy/Offset, Spiegeln/Drehen/Array, - Trim/Split/Join. -- **Rhino-artiges Befehlssystem** mit getippten Koordinaten (`5,3` · `r5,3` · - `5<45`) und Tab-Feld-Zyklus (Länge → Winkel …). -- **Vektor-Export:** PDF (A4/A3, Titelblock, echte mm-Stiftstärken nach ISO-Pen- - Steps, keine Rasterbilder) und DXF — beide aus derselben `Plan`-Struktur wie - der Bildschirm. -- **PBR-Material-Bibliothek** (ambientCG-Import, `manifest.json`) für die - 3D-Ansicht. -- **Resource Manager** (Component / Hatch / Line) — alles per id referenziert, - zentral änderbar. -- **Panel-System** (dockbar, stapelbar, floatend), Top-Bar + Statusleiste - (echter Maßstab 1:N, Cursor X/Y), eigenes Kontextmenü, **i18n** (de/en). -- **Import:** DXF/DWG (Konturen/Mesh) → Terrain-TIN, Swisstopo/LV95-Geokontext, - OSM-Kontextimport; `.lin`/`.pat` für Linien/Schraffuren. +**Funktioniert (Auszug — volle Liste in STATUS.md §3):** +- Semantisches Modell mit **mehrschichtigen Wänden**, L-Eck-Gehrung **und** + Prioritäts-T-/X-Stössen (`joinPriority` am Component) — konsistent in + Grundriss, 3D-Viewport und 3D-Live-Schnitt. +- **Parametrische Wände**, Decken, Treppen (gerade/L/Wendel), Dächer + (Flach/Pult/Sattel/Walm/Mansarde/Zelt), Stützen, SIA-416-Räume mit + automatischer Bilanz + CSV-Export. +- **Türen & Fenster** gehostet in Wänden, mit Rahmen/Zarge/Kämpfer/Oberlicht, + Detailgrad grob/mittel/fein, echte Rechteck-Löcher im 3D-Wandkörper. +- Rhino-artiges **Kommandosystem** mit getippten Koordinaten (`5,3` · `r5,3` · + `5<45`) und Tab-Feld-Zyklus; Snapping, Grips, Trim/Split/Join, 2D-Booleans. +- **Live 2D↔3D-Schnitt**: eine Schnittebene im 3D-Viewport folgt derselben + Prioritäts-Logik wie der 2D-Plan-Schnitt (Rust-Port, keine Diskrepanz). +- **Vektor-Export:** PDF (Einzel- **und Mehrseiten-Layouts**), DXF, **IFC4** + (mit echten Fenster-/Tür-Löchern), STL/OBJ, CSV-Bauteilliste. +- **Materialbibliothek**: 13 gebündelte PBR-Starter **plus** Live-Suche der + kompletten ambientCG-Bibliothek (1K/2K/4K, On-Demand-Download). +- **Layouts/Plansätze** mit mehreren Viewports pro Blatt, Ausschnitte + (View-Snapshots), Kamera-Presets, Norden-Rotation. +- **Import:** DXF/DWG, Swisstopo (swissBUILDINGS3D/-ALTI3D/SWISSIMAGE, + radiusgenau zugeschnitten), OSM-Kontextimport, `.lin`/`.pat`. +- **Native Desktop-Integration** (Tauri): eigene randlose Fenster, native + Speichern/Öffnen-Dialoge, eigenes `.obp`-Dateiformat mit OS-Level-Lock gegen + Doppelöffnen, mehrere native Zusatzfenster (Resources, Settings, …). +- **Resource Manager** (Component/Hatch/Line/Typ-Editoren), regelbasierte + Overrides, Panel-System (dockbar/floatend), i18n (de/en). -**Bewusst noch offen** (die eigentlich harten Teile): -- Echte **3D-Booleans** für Öffnungen — Türen/Fenster sind im Plan eine Lücke, - noch kein geschnittenes Volumen. -- **HLR** (verdeckte Kanten) für Schnitte und Ansichten: Machbarkeit per - OCCT-WASM-Spike bewiesen (`docs/welle-c-hlr-spike/`, `src/section/hlr.ts`), - aber noch nicht ans UI/Dokumentmodell verdrahtet — Views sind noch Stubs. -- **Prioritäts-T-/X-Stöße** mehrschichtiger Wände (Beton läuft durch, Putz - verbindet seitlich) — das berüchtigte Risiko #1. -- **Multi-Page-Layouts/Ausschnitte** (mehrere Viewports pro Blatt) — PDF-Export - ist noch single-sheet. - -Details und die Begründungen stehen im -[HANDOVER](HANDOVER.md) (laufendes Arbeitsprotokoll) und in der -[ROADMAP](ROADMAP.md) (Vision, Phasen, Backlog). +**Bewusst noch offen** (Details + volle Liste: STATUS.md §4.7): +- **Echtes Mesh-Boolean für Öffnungen** — funktioniert heute über achsparallele + Rechteck-Löcher; ein generisches CSG-Boolean existiert bereits + (`trucksolid`/`csgrs`), ist aber nicht an die Wand-Pipeline angeschlossen. +- **3D-Griffsystem**: Feld-Controller (Tab-Zyklus) und Snapping fehlen für + 3D-Grip-Drag (im 2D vollständig vorhanden). +- **DWG/DXF-Domänenimport** (Entitäten → echte Wände/Öffnungen) — Lesen + funktioniert, das Mapping auf das Modell ist unbegonnen; DWG-Schreiben fehlt. +- **`make2D`**-Kommando (3D-Ansicht → flacher 2D-Plan mit Füllungen). +- Bekannte, noch nicht bereinigte Doppelspur `Door[]`/`Opening[]` im Modell. ## 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 | -| 3D | Three.js, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render3d`) | -| 2D-Plan | eigener SVG-Renderer, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render2d`) | -| Geometrie | eigener 2D-Kernel (`src/geometry/kernel2d.ts`), `delaunator` fürs Terrain | -| Export | `jspdf`/`svg2pdf.js` (Vektor-PDF), eigener DXF-Writer | +| 3D | eigener Rust/WASM/WebGPU-Renderer „Nordstern" (`render3d`, Default), Three.js als leichtgewichtiger Zweitpfad | +| 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`); ein Rust-Port (`src-tauri/kernel2d`) existiert nur als Paritätstest, läuft nicht produktiv | +| 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 | -| 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), -Manifold (exakte 3D-Booleans). OCCT-WASM ist für HLR bereits als Spike da (s.o.), -aber noch nicht an Dokumentmodell/UI angebunden. Siehe ROADMAP §4. +`opencascade.js` steht noch als Dependency in `package.json`, wird aber nur +noch von totem Code (`src/section/hlr.ts`, superseded durch die Rust- +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 ```bash npm install -npm run dev # Vite, http://localhost:5187 -npm run electron # Electron-Fenster (Dev-Server + randlose App-Shell) -npx tsc -b # Typecheck -npm run build # tsc -b && vite build -npm test # vitest run +npm run dev # Vite, http://localhost:5187 +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 +npm run build # tsc -b && vite build +npm test # vitest run ``` -Verifiziert wird visuell: `node scripts/probe.mjs` rendert die App headless und -schreibt `scripts/probe.png` — Screenshot ansehen, Geometrie prüfen. Weitere -`scripts/probe-*.mjs` decken einzelne Features ab (PDF, Trim, Materialien, -Theme, Boolean-Ops …); für Firefox-Fälle gibt es `scripts/probe-ff*.mjs` -(Playwright). +WASM-Engines nach Rust-Änderungen neu bauen (nur `.rs` committen, +`src/engine/pkg*/` ist gitignored): + +```bash +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 ``` src/ - model/ semantisches Modell + Ableitungen (types, parametricWalls, roomStamp, joins, terrain) - geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling, opening, roomArea/-Boundary, stair) + model/ semantisches Modell (types, joins, parametricWalls, roomStamp, terrain) + geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling/opening/roomArea/stair/roof/column) commands/ Rhino-artiges Befehlssystem (engine, parser, registry, cmds/) tools/ interaktive Zeichenwerkzeuge + Snapping + Transformationen - plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2-Renderer) - viewport/ Viewport3D (Three.js) - section/ HLR-Spike (OCCT-WASM) — Schnitte/Ansichten, noch nicht verdrahtet - export/ PDF- und DXF-Export aus derselben Plan-Struktur wie der Bildschirm - materials/ PBR-Material-Bibliothek (ambientCG-Import, Runtime) - text/ Rich-Text (Beschriftungen, Textobjekte) + plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2) + Rust-Bindings + viewport/ Viewport3D (Three.js) + Wasm3DViewport (Rust/wgpu „Nordstern", Default) + section/ TOTER Code (OCCT-HLR-Spike) — Schnitt läuft über render3d, siehe ARCHITECTURE.md §4.3 + export/ IFC4-, PDF-, DXF-, STL/OBJ-, CSV-Export aus derselben Plan-/Modell-Struktur + materials/ ambientCG-Live-Suche + gebündelte Starter-Bibliothek + PBR-Runtime panels/ dockbares Panel-System + die einzelnen Paletten - state/ Store + Slices (project/selection/view/layout) - ui/ Top-Bar, Statusleiste, Kontextmenü, Resource Manager, Command-Line + state/ eigener Store (useSyncExternalStore) + Slices (project/selection/view/layout/…) + 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 i18n/ Wörterbücher de/en -src-tauri/ Rust-Crates (render2d/render3d) — headless per wasm-pack zu WASM - gebaut (npm run build:engine), Tauri-Host selbst ist ausrangiert +src-tauri/ 6 Rust-Crates (render2d/render3d/geometry/kernel2d/trucksolid/dwgimport, + je headless UND per wasm-pack baubar) + der Tauri-Host selbst ``` ## 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 über `t('key')` — keine hartcodierten Strings im JSX. - Intern alles in **Metern**; Anzeige via `formatM`. -- `App.tsx` bleibt dünner Shell, Zustand lebt im Store. Verbindliches in - [CONVENTIONS.md](CONVENTIONS.md). +- Verbindliches in [CONVENTIONS.md](CONVENTIONS.md). ## Weiterlesen -- [ROADMAP.md](ROADMAP.md) — Produktvision, Architektur-Entscheidungen, Phasen 0–7, DOSSIER-Backlog -- [ARCHITECTURE.md](ARCHITECTURE.md) — Technische Architektur im Detail -- [HANDOVER.md](HANDOVER.md) — aktueller Arbeitsstand, Befunde, nächste Schritte -- [docs/](docs/) — Design-Specs (Befehlssystem, Zeichenwerkzeuge, Wand-Joins, Backend …) +- [STATUS.md](STATUS.md) — **vollständige, ehrliche Bestandsaufnahme**: Zahlen, + Feature-Inventar, Mist-Liste, Doku-Widersprüche, Tag-1-Vision vs. heute +- [ARCHITECTURE.md](ARCHITECTURE.md) — technische Architektur im Detail (Ist-Zustand) +- [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 Copyright © 2026 Karim Gabriele Varano. Veröffentlicht unter der **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. 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) -sind als Differenzierer eingeplant. +sind ein bewusster Differenzierer. diff --git a/ROADMAP.md b/ROADMAP.md index 2e1d5d3..01f17dd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,16 @@ # 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 2–5"/„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** > Stand: 2026-06-28 diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 0000000..a00d6e6 --- /dev/null +++ b/STATUS.md @@ -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 2–5 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. diff --git a/docs/README.md b/docs/README.md index d2d1b7d..34dfc59 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,13 @@ # 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). Dieses Verzeichnis bündelt die Recherche- und Design-Dokumente für `cad`, die