8fd8987b70
Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung (konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text- Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback, falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform) fuer fluessige Interaktion ohne React-Re-Render je Frame. Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM (Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext- Import, Tauri-Compute-Boundary-PoC).
244 lines
7.2 KiB
Markdown
244 lines
7.2 KiB
Markdown
# Tauri-Migration + Compute-Boundary — Aufgabe für nächste Instanz
|
|
|
|
**Entscheidung (fix, 2026-07-01):** Browser-CAD → **Desktop Tauri-App mit Rust-Backend + wgpu-Rendering.**
|
|
|
|
**Grund:** komplexe Möbel mit vielen Polygonen (100k+) + mehrere parallele rechenintensive Ops → WebGL/three.js wird zum Blocker. **wgpu** (low-level GPU-API) + Rust-Compute skaliert native.
|
|
|
|
**Scope:** Desktop-only Release (Milestone 1). WASM-Fallback für Browser später wenn gebraucht.
|
|
|
|
**Rendering-Engine:** three.js → **wgpu** (Milestone 2)
|
|
|
|
---
|
|
|
|
## Aufgabe: Shell aufsetzen + Compute-Boundary + erste Op migrieren
|
|
|
|
### Schritt 0 — Compute-Boundary (TS-Kontrakt)
|
|
|
|
**Neu:** `src/compute/index.ts` — die einzige Stelle, durch die alle rechenintensiven Ops laufen.
|
|
|
|
```typescript
|
|
// src/compute/index.ts — einheitliche Schnittstelle
|
|
export async function computeKernel2D(op: 'offset'|'trim', …): Promise<Polyline[]> { … }
|
|
export async function detectRooms(…): Promise<Room[]> { … }
|
|
export async function computeJoins(…): Promise<JoinInfo[]> { … } // ← erste Op
|
|
export async function parseShapeFromDwg(…): Promise<DwgGeometry> { … }
|
|
```
|
|
|
|
**Hinter der Boundary:**
|
|
- Erst Tauri-invoke zu Rust `#[tauri::command]`
|
|
- Fallback auf lokale TS-Impl (bleibt unberührt, bis Rust stabil)
|
|
- `catch(err) → console.warn('Rust failed, using TS fallback'); return tsImpl(…)`
|
|
|
|
**Effekt:** Aufrufort im Code bleibt stabil; Caller sieht nicht, ob Op schon in Rust ist oder noch TS.
|
|
|
|
---
|
|
|
|
### Schritt 1 — Tauri-Shell aufsetzen
|
|
|
|
**Struktur:**
|
|
```
|
|
repo/
|
|
src-tauri/ ← neue Rust-Seite (Tauri-Konvention)
|
|
src/
|
|
main.rs ← Tauri window + invoke handlers
|
|
geometry.rs ← compute_joins() + weitere Ops später
|
|
...
|
|
Cargo.toml
|
|
src/ ← React/TS (unverändert)
|
|
compute/
|
|
index.ts ← Compute-Boundary
|
|
...
|
|
vite.config.ts ← Tauri plugin integrieren
|
|
package.json ← tauri scripts
|
|
```
|
|
|
|
**Setup:**
|
|
1. `cargo init --name cad-tauri src-tauri` (oder `src-tauri` manuell anlegen)
|
|
2. `Cargo.toml`: Tauri v2 einbinden mit Feature `webkit2gtk-6.0` (GTK4)
|
|
```toml
|
|
[dependencies]
|
|
tauri = { version = "2", features = ["webkit2gtk-6.0"] }
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
serde_json = "1.0"
|
|
```
|
|
3. `src-tauri/src/main.rs`: Minimal-Fenster, invoke-Handler registrieren
|
|
```rust
|
|
#[tauri::command]
|
|
async fn compute_joins(input: JoinInput) -> Result<JoinOutput, String> {
|
|
// Rust-Impl
|
|
geometry::compute_joins(input).map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
tauri::Builder::default()
|
|
.invoke_handler(tauri::generate_handler![compute_joins])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while running tauri application");
|
|
}
|
|
```
|
|
4. `vite.config.ts`: Tauri plugin + dev-server-Integration
|
|
```typescript
|
|
import { defineConfig } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
server: { port: 5173 } // Tauri dev zeigt hier drauf
|
|
})
|
|
```
|
|
5. `package.json`: Tauri scripts hinzufügen
|
|
```json
|
|
"scripts": {
|
|
"tauri": "tauri",
|
|
"tauri:dev": "tauri dev",
|
|
"tauri:build": "tauri build"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Schritt 2 — Erste Op migrieren: `computeJoins` (Wand-Eckverbindungen)
|
|
|
|
**Warum `computeJoins` zuerst?**
|
|
- Läuft häufig (bei jedem Wall-Edit)
|
|
- Klein und fokussiert (~50 Zeilen Kernlogik)
|
|
- Proof-of-Concept für Tauri-invoke-Flow
|
|
- Danach `kernel2d` parallel hochfahren
|
|
|
|
**Rust-Impl:** `src-tauri/src/geometry.rs`
|
|
|
|
```rust
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct Vec2 { pub x: f64, pub y: f64 }
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct WallJoinInput {
|
|
pub walls: Vec<Wall>,
|
|
pub joints: Vec<(usize, usize)>, // wall indices
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct JoinInfo { /* … */ }
|
|
|
|
pub fn compute_joins(input: WallJoinInput) -> Result<Vec<JoinInfo>, Box<dyn std::error::Error>> {
|
|
// Port der Logik aus src/model/joins.ts
|
|
// L-Ecken, T-Stösse, +-Kreuzungen
|
|
Ok(vec![]) // Placeholder
|
|
}
|
|
```
|
|
|
|
**TS-Fallback bleibt:** `src/model/joins.ts` (LS vor Rust-Port)
|
|
|
|
**Compute-Boundary:** `src/compute/index.ts`
|
|
```typescript
|
|
export async function computeJoins(walls: Wall[], joints: Array<[number, number]>): Promise<JoinInfo[]> {
|
|
try {
|
|
return await invoke<JoinInfo[]>('compute_joins', { walls, joints });
|
|
} catch (err) {
|
|
console.warn('Rust compute_joins failed, using TS fallback:', err);
|
|
return joinsTS(walls, joints); // Fallback
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Schritt 3 — Migrations-Parität testen
|
|
|
|
**Test-Fixtures:**
|
|
- Aus `src/model/sampleProject.ts` exportieren: Wand-Arrays mit bekannten L/T/±-Konfigurationen
|
|
- Rust-Unit-Tests: gleiche Fixtures → gleiche JoinInfo-Outputs
|
|
- Vergleich: `actual == expected`
|
|
|
|
**Beispiel (Rust):**
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_l_corner() {
|
|
let input = WallJoinInput { /* L-shaped walls */ };
|
|
let result = compute_joins(input).unwrap();
|
|
assert_eq!(result[0].kind, JoinKind::LCorner);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Schritt 4 — Vite/Tauri-Integration
|
|
|
|
**Dev-Workflow:**
|
|
```bash
|
|
npm run tauri:dev
|
|
# → Vite dev-server (localhost:5173) lädt React-App
|
|
# → Tauri-window zeigt auf :5173
|
|
# → invoke() ruft Rust-Commands auf
|
|
```
|
|
|
|
**Build-Workflow:**
|
|
```bash
|
|
npm run build # Vite → dist/
|
|
npm run tauri:build # Tauri packt dist/ + Rust-Binary
|
|
```
|
|
|
|
---
|
|
|
|
## Deliverable (Proof-of-Concept)
|
|
|
|
**Git-Stand nach dieser Aufgabe:**
|
|
|
|
- [ ] `src-tauri/` Verzeichnis mit `Cargo.toml` + `src/main.rs` + `src/geometry.rs`
|
|
- [ ] `Cargo.toml` buildet sauber (`cargo check` 0 Fehler)
|
|
- [ ] `src/compute/index.ts` mit `computeJoins()` Schnittstelle (invoke + TS-fallback)
|
|
- [ ] Rust `compute_joins()` implementiert, Tests pass (`cargo test`)
|
|
- [ ] `package.json` `tauri` scripts hinzugefügt
|
|
- [ ] **App läuft:** `npm run tauri:dev` → Tauri-Fenster öffnet, Wand-Edit triggert Rust-Op, Output identisch TS-Version
|
|
- [ ] **Trace-Scan sauber** (kein TS/Rust-Code übrig, beide Impl. aktiv)
|
|
- [ ] HANDOVER.md aktualisiert: `computeJoins` migriert, nächste Ops in Queue
|
|
|
|
**Verification:**
|
|
```bash
|
|
# Build-Check
|
|
cargo check # 0 Fehler
|
|
|
|
# Rust-Tests
|
|
cargo test
|
|
|
|
# App end-to-end
|
|
npm run tauri:dev
|
|
# → Wand zeichnen + editieren → computeJoins() über Rust aufgerufen
|
|
# → Plan + 3D aktualisiert wie vorher
|
|
```
|
|
|
|
---
|
|
|
|
## Nächste Ops (Priorisierung)
|
|
|
|
Nach `computeJoins` stabil:
|
|
|
|
1. **`kernel2d`** (Offset/Trim/Extend) — großer Hebel, läuft häufig
|
|
2. **DXF/DWG-Parser** (Geometrie) — heavy, aber niedrige Frequenz
|
|
3. **`detectRooms`** (SIA-Raumerkennung) — async, kann auf Hintergrund ziehen
|
|
4. **`booleanOps`** (Union/Differenz/Schnitt) — Kandidat für später
|
|
|
|
---
|
|
|
|
## Offene Punkte (NICHT jetzt)
|
|
|
|
- **GPU-Compute (wgpu):** Erst nach CPU-Ops stabil (kernel2d, joins, parsing)
|
|
- **WASM-Fallback:** Nur wenn Browser-Support nötig wird
|
|
- **Flatpak-Distribution:** Nach Tauri-Shell stable + erste Ops migriert
|
|
|
|
---
|
|
|
|
## Referenzen
|
|
|
|
- Tauri v2 Docs: https://tauri.app/v1/guides/getting-started/prerequisites
|
|
- Serde: https://serde.rs/
|
|
- CONVENTIONS.md: Identifiers englisch, UI-Text via `t()`, …
|
|
- Commit-Regel: kein AI-Attribution im Repo
|