Files
plantplan/doc/tldraw_technologie.md
2026-04-13 14:45:27 +02:00

305 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (
<SVGContainer>
<path d={shape.props.svg_path} stroke="black" fill="none" />
</SVGContainer>
)
},
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 (
<SVGContainer>
{/* Oberer Kreis */}
<circle cx={0} cy={0} r={r} stroke="black" fill="none" />
{/* Unterer Kreis position hängt von abstand ab */}
<circle cx={0} cy={abstand} r={r} stroke="black" fill="none" />
{/* Zwei vertikale Verbindungslinien */}
<line x1={-r} y1={0} x2={-r} y2={abstand} stroke="black" />
<line x1={ r} y1={0} x2={ r} y2={abstand} stroke="black" />
</SVGContainer>
)
}
```
### 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 | 12 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*