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).
7.2 KiB
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.
// 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:
cargo init --name cad-tauri src-tauri(odersrc-taurimanuell anlegen)Cargo.toml: Tauri v2 einbinden mit Featurewebkit2gtk-6.0(GTK4)[dependencies] tauri = { version = "2", features = ["webkit2gtk-6.0"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"src-tauri/src/main.rs: Minimal-Fenster, invoke-Handler registrieren#[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"); }vite.config.ts: Tauri plugin + dev-server-Integrationimport { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 5173 } // Tauri dev zeigt hier drauf })package.json: Tauri scripts hinzufügen"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
kernel2dparallel hochfahren
Rust-Impl: src-tauri/src/geometry.rs
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
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.tsexportieren: Wand-Arrays mit bekannten L/T/±-Konfigurationen - Rust-Unit-Tests: gleiche Fixtures → gleiche JoinInfo-Outputs
- Vergleich:
actual == expected
Beispiel (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:
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:
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 mitCargo.toml+src/main.rs+src/geometry.rsCargo.tomlbuildet sauber (cargo check0 Fehler)src/compute/index.tsmitcomputeJoins()Schnittstelle (invoke + TS-fallback)- Rust
compute_joins()implementiert, Tests pass (cargo test) package.jsontauriscripts 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:
computeJoinsmigriert, nächste Ops in Queue
Verification:
# 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:
kernel2d(Offset/Trim/Extend) — großer Hebel, läuft häufig- DXF/DWG-Parser (Geometrie) — heavy, aber niedrige Frequenz
detectRooms(SIA-Raumerkennung) — async, kann auf Hintergrund ziehenbooleanOps(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