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:
+297
-359
@@ -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<Project>` 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<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.
|
||||
- **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 `<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) | — |
|
||||
| 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.
|
||||
|
||||
Reference in New Issue
Block a user