Files
DOSSIER-STANDALONE/docs/design/tauri-migration-plan.md
T
karim 8fd8987b70 2D-Plan-Renderer auf WebGL2 (GPU) + akkumulierter Funktionsstand
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).
2026-07-02 00:12:39 +02:00

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:

  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)
    [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
    #[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
    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
    "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

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.ts exportieren: 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 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:

# 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