From f44949f0afdf65ed5271db25cd89277adaaae3b1 Mon Sep 17 00:00:00 2001 From: mistangl Date: Mon, 13 Apr 2026 14:45:27 +0200 Subject: [PATCH] erste Studie zur Verwendung von tldraw --- doc/tldraw_technologie.md | 304 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 doc/tldraw_technologie.md diff --git a/doc/tldraw_technologie.md b/doc/tldraw_technologie.md new file mode 100644 index 0000000..6226aba --- /dev/null +++ b/doc/tldraw_technologie.md @@ -0,0 +1,304 @@ +# Technologie-Zusammenfassung: iPad Skizzen-Tool mit Symbolbibliothek + +**Datum:** 09. April 2026 +**Thema:** Hybride iPad-Anwendung für technische Skizzen (Schienen/Weichen/Kreisel) mit Snap-to-Grid, Symbolbibliothek und JSON/SVG-Export + +--- + +## 1. Anforderungen + +- iPad als Eingabegerät (Bleistift-Look) +- ~10 Grundsymbole (Weichen, Kreisel, etc.) aus Katalog +- Snap-to-Grid +- Output: **JSON + SVG** +- Symbole parametrisch veränderbar (z.B. Kreiselab stand) + +--- + +## 2. Gewählte Architektur: Option C (Hybrid) + +``` +iPad (PWA/Safari) Hetzner Server +───────────────── ────────────── +tldraw (Canvas) FastAPI (Python) +rough.js (Bleistift) │ +Custom Shapes ├── GET /symbols → Bibliothek ausliefern + │ ├── POST /normalize → Skizze → SVG + │ JSON (Shapes + Meta) └── POST /export → finales JSON/SVG + └──────────────────────────────────────► +``` + +**Warum Hybrid:** +- iPad nur für Eingabe + lokales Snap/Rendering (kein App Store, kein Swift) +- Server für Bibliotheksverwaltung, Normalisierung und Export (Python/FastAPI) +- PWA läuft direkt im Safari, gehostet auf Hetzner via nginx + +--- + +## 3. Kerntechnologien + +| Schicht | Technologie | Zweck | +|---|---|---| +| iPad-Frontend | **tldraw** (PWA) | Canvas, Snap-to-Grid, SVG-Export | +| Bleistift-Look | **rough.js** | Skizzenhafter Renderstil | +| Symbolbibliothek | **JSON + SVG-Pfade** | Katalog mit Metadaten | +| Backend | **FastAPI (Python)** | Bibliothek, Normalisierung, Export | +| Output | **SVG + JSON** | Weiterverarbeitung / CAD | + +--- + +## 4. Symbolbibliothek-Format (JSON) + +```json +{ + "symbols": [ + { + "id": "weiche_links_r300", + "label": "Weiche links R300", + "type": "weiche", + "radius": 300, + "direction": "left", + "svg_path": "M 0,0 L 100,0 Q 150,0 200,50", + "snap_points": [[0,0], [100,0], [200,50]], + "ports": ["in", "out_gerade", "out_abzweig"] + }, + { + "id": "kreisel_standard", + "label": "Kreisel", + "type": "kreisel", + "geometry": { + "circle_top_r": 40, + "circle_bottom_r": 40, + "straight_length": 80 + }, + "svg_path": "...", + "snap_points": [[0,0], [0,160]] + } + ] +} +``` + +--- + +## 5. Metadaten an tldraw-Shapes anhängen + +### Option A: `meta`-Feld (schnell, für Prototypen) + +```typescript +editor.createShape({ + type: 'geo', + x: 100, + y: 200, + props: { + geo: 'rectangle', + w: 80, + h: 40, + }, + meta: { + symbol_id: 'weiche_links_r300', + symbol_type: 'weiche', + radius: 300, + direction: 'left', + ports: ['in', 'out_gerade', 'out_abzweig'], + catalog_version: '1.2' + } +}) +``` + +### Option B: Custom Shape Type (produktiv, empfohlen) + +```typescript +const WeicheShapeUtil = defineShape({ + type: 'weiche', + props: { + radius: T.number, + direction: T.string, + ports: T.arrayOf(T.string), + svg_path: T.string, + }, + component(shape) { + return ( + + + + ) + }, + getBounds(shape) { /* ... */ } +}) +``` + +**JSON-Export einer Custom Shape:** + +```json +{ + "id": "shape:abc123", + "type": "weiche", + "x": 100, + "y": 200, + "props": { + "radius": 300, + "direction": "left", + "ports": ["in", "out_gerade", "out_abzweig"], + "svg_path": "M 0,0 L 100,0 Q 150,0 200,50" + } +} +``` + +--- + +## 6. Parametrische Shapes: Kreisel-Beispiel + +Ziel: Nutzer kann nur den **Abstand der zwei Kreise** verändern, alles andere ist gesperrt. + +### 6.1 Shape-Definition mit eingeschränkten Props + +```typescript +const KreiselShapeUtil = defineShape({ + type: 'kreisel', + props: { + abstand: T.number, // ← einziger veränderbarer Parameter + radius: T.number, // fest (aus Katalog) + }, +``` + +### 6.2 Custom Handle (nur vertikale Bewegung) + +```typescript + getHandles(shape) { + return { + mitte: { + id: 'mitte', + type: 'vertex', + x: 0, + y: shape.props.abstand / 2, // Handle mittig zwischen den Kreisen + canBind: false, + } + } + }, + + onHandleDrag(shape, { handle, delta }) { + return { + props: { + // Nur abstand ändern, x-Bewegung ignorieren, Minimum erzwingen + abstand: Math.max(20, shape.props.abstand + delta.y * 2) + } + } + }, +``` + +### 6.3 Resize einschränken (nur Höhe) + +```typescript + onResize(shape, info) { + return { + props: { + abstand: Math.max(20, info.newPoint.y * 2) + } + } + }, + + getResizeSnapGeometry(shape) { + return { points: [[0, 0], [0, shape.props.abstand]] } + }, +``` + +### 6.4 SVG-Rendering (reagiert auf `abstand`) + +```typescript + component(shape) { + const { abstand, radius } = shape.props + const r = radius + + return ( + + {/* Oberer Kreis */} + + + {/* Unterer Kreis – position hängt von abstand ab */} + + + {/* Zwei vertikale Verbindungslinien */} + + + + ) + } +``` + +### 6.5 Freiheitsgrade im Überblick + +| Aktion | Erlaubt | +|---|---| +| Kreisel verschieben | ✓ | +| Abstand der Kreise ändern | ✓ (Handle mittig) | +| Breite / Radius ändern | ✗ gesperrt | +| Drehen | konfigurierbar | +| Im JSON-Export | `"abstand": 120` | + +--- + +## 7. FastAPI-Backend (3 Endpunkte) + +```python +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + +# 1. Symbolbibliothek ausliefern +@app.get("/symbols") +async def get_symbols(): + with open("catalog/symbols.json") as f: + return json.load(f) + +# 2. Koordinaten normalisieren / bereinigen +class SketchPayload(BaseModel): + shapes: list[dict] + grid_size: int = 10 + +@app.post("/normalize") +async def normalize(payload: SketchPayload): + # Snap-Koordinaten auf Grid runden, SVG-Pfade normalisieren + ... + return {"svg": normalized_svg, "shapes": cleaned_shapes} + +# 3. Finales Projektfile exportieren +@app.post("/export") +async def export(payload: SketchPayload): + return { + "json": build_project_json(payload.shapes), + "svg": build_final_svg(payload.shapes) + } +``` + +--- + +## 8. Umsetzungsplan (Phasen) + +| Phase | Inhalt | Aufwand | +|---|---|---| +| 1 | tldraw als PWA auf Hetzner (nginx + Docker) | 1 Tag | +| 2 | Symbolbibliothek JSON + SVG-Pfade für 10 Symbole | 1–2 Tage | +| 3 | Custom Shape Types in tldraw registrieren | 1 Tag | +| 4 | FastAPI-Backend mit 3 Endpunkten | 1 Tag | +| 5 | Parametrische Handles (Kreisel etc.) | 1 Tag | +| 6 | Export → JSON + SVG finalisieren | 1 Tag | + +**Geschätzter Gesamtaufwand Prototyp: ~1 Woche** + +--- + +## 9. Entscheidungsmatrix: `meta` vs. Custom Shape + +| Kriterium | `meta`-Feld | Custom Shape Type | +|---|---|---| +| Implementierungsaufwand | minimal | mittel | +| Eigenes SVG-Rendering | ✗ | ✓ | +| Typvalidierung der Props | ✗ | ✓ | +| Parametrische Handles | ✗ | ✓ | +| Im JSON-Export enthalten | ✓ | ✓ | +| Empfehlung | Prototyp Phase 1 | Produktiv ab Phase 3 | + +--- + +*Erstellt mit Claude (Anthropic) – Technologiegespräch vom 09.04.2026*