---
title: 3D-Druck Design für Kiwi
name: 3d-druck-kiwi
description: Vollständiger Workflow zum Erstellen druckbarer 3D-Modelle aus Textbeschreibungen. Nutzt Python (trimesh + manifold3d) als primären CAD-Engine. Generiert validierte STL/3MF-Dateien für Bambu Lab Drucker.
skill_version: 1.1.0
summary: Vollständiger Workflow zum Erstellen druckbarer 3D-Modelle aus Textbeschreibungen. Nutzt Python (trimesh + manifold3d) als primären CAD-Engine. Generiert validierte STL/3MF-Dateien für Bambu Lab Drucker.
---

# 3D-Druck Design für Kiwi

## Übersicht

Dieser Skill beschreibt den vollständigen Workflow, um aus einer natürlichen Sprachbeschreibung ein druckbares 3D-Modell zu erzeugen.

**Ziel**: Der User (Ahmed Zeyd Aytac) hat einen Bambu Lab 3D-Drucker und möchte 1:1 druckbare Modelle bekommen, ohne manuelles CAD bedienen zu müssen.

**Support-Dateien**:
- `templates/generate_model.py` — Kopierbares Boilerplate für jedes neue Modell
- `references/zimaos-environment.md` — Betriebsumgebung, Berechtigungen, headless-GL-Pitfalls
- `scripts/verify_pipeline.py` — Schneller Integritätstest des Stacks

## Technischer Stack

| Komponente | Status | Nutzung |
|---|---|---|
| **trimesh** | ✅ v4.12.2 | Primärer CAD-Engine: Primitives, Extrusion, Boolean |
| **manifold3d** | ✅ v3.4.1 | Boolesche Operationen, Mesh-Reparatur, Schnelle STL-Generierung |
| **numpy** | ✅ Installiert | Mathematische Operationen, Punktwolken |
| **shapely** | ✅ Installiert | 2D-Polygone für Extrusion |
| **matplotlib** | ✅ Installiert | 2D-Profil-Visualisierung |
| **OpenSCAD** | ⚠️ AppImage bereit | Fallback für komplexe CSG-Szenen (headless-GL-Limitation auf ZimaOS) |
| **CadQuery** | ⚠️ Installiert aber GL-broken | Nur wenn GL-Libs später installiert werden |

> **ZimaOS-Spezifika**: Keine Display-GL-Libs installiert. CadQuery und OpenSCAD brauchen `libGL.so.1`. Workaround: `trimesh + manifold3d` arbeiten vollständig headless. Für OpenSCAD: `MESA_GL_VERSION_OVERRIDE=3.0 LIBGL_ALWAYS_SOFTWARE=1` testen.

## Arbeitsablauf

### 0. Venv aktivieren (einmalig pro Session)
```bash
source /DATA/AppData/hermes/venv/bin/activate
```

### 1. Anforderung verstehen
- User beschreibt Objekt in natürlicher Sprache.
- Identifiziere: Funktion, Maße, Material (PLA/PETG/TPU), Toleranzen.
- Fehlende Maße: Realistische Standardwerte vorschlagen oder nachfragen.

### 2. Design-Strategie wählen

| Objekt-Typ | Methode |
|---|---|
| Primitive Kombinationen | `trimesh.primitives` + `manifold3d` Boolean |
| Extrudierte 2D-Profile | `trimesh.creation.extrude_polygon()` |
| Gewinde/Schraubenlöcher | `trimesh.creation.cylinder()` + Boolean-Differenz |
| Organische/Gehäuse-Formen | Parametrischer trimesh-Code oder OpenSCAD-Fallback |
| Mesh-Reparatur | `manifold3d` (repariert nicht-watertight Meshes) |

### 3. Python-Code generieren

**Schnellstart**: Kopiere `templates/generate_model.py` und passe die Modell-Logik an.
Das Template enthält bereits alle Imports, Konvertierungsfunktionen und das Output-Verzeichnis.

```python
#!/usr/bin/env python3
import numpy as np
import trimesh
from trimesh.creation import box, cylinder, extrude_polygon
from trimesh.primitives import Sphere
import manifold3d as m3d
import os

OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "/DATA/AppData/hermes/Projekte/3D-Druck")
os.makedirs(OUTPUT_DIR, exist_ok=True)

# Boolean-Hilfsfunktionen (aus dem Template kopiert)
def trimesh_to_manifold(mesh):
    verts = np.asarray(mesh.vertices, dtype=np.float32)
    faces = np.asarray(mesh.faces, dtype=np.uint32)
    return m3d.Manifold(m3d.Mesh(vert_properties=verts, tri_verts=faces))

def manifold_to_trimesh(manifold):
    m = manifold.to_mesh()
    return trimesh.Trimesh(vertices=m.vert_properties[:,:3], faces=m.tri_verts)

def union(m1, m2):
    return manifold_to_trimesh(trimesh_to_manifold(m1) + trimesh_to_manifold(m2))

def difference(m1, m2):
    return manifold_to_trimesh(trimesh_to_manifold(m1) - trimesh_to_manifold(m2))

def save_stl(mesh, filename):
    if not mesh.is_watertight:
        print("WARN: Nicht watertight — repariere...")
        mesh = manifold_to_trimesh(trimesh_to_manifold(mesh))
    path = os.path.join(OUTPUT_DIR, filename)
    mesh.export(path)
    print(f"✅ {path}")
    print(f"   Volumen: {mesh.volume/1000:.2f} cm³")
    print(f"   Bounding Box: {mesh.bounding_box.extents}")
    return path

# === MODELL-LOGIK HIER ===
```

> **Referenz**: Ausführliche Umgebungsdoku, Typ-Cast-Pitfalls und GL-Probleme unter `references/zimaos-environment.md`.

### 4. Design-Regeln für 3D-Druck

**Wandstärken** (Minimal für Stabilität):
- PLA: 1.2mm Wand (3 Perimeter bei 0.4mm Nozzle)
- PETG: 1.5mm Wand
- Funktionsteile: 3-4mm Material drumherum

**Toleranzen** (für Passgenauigkeit):
- Presspassung: -0.1mm bis -0.15mm
- Gleitpassung: +0.15mm bis +0.25mm
- Lose Passung: +0.3mm bis +0.5mm

**Overhangs**:
- Max 45° ohne Support
- Brücken max 5cm (besser 2cm)

**Fasen**:
- ALLE scharfen Kanten mit 0.5-1mm Fase
- Schraubenlöcher: 0.2mm Fase am Eingang

### 5. Export & Validierung

Jedes Modell muss diese Checks bestehen:

```python
def validate(mesh):
    ok = True
    if not mesh.is_watertight:
        print("❌ Nicht watertight")
        ok = False
    if mesh.volume <= 0:
        print("❌ Negatives Volumen")
        ok = False
    if ok:
        print("✅ Druckbarkeit OK")
    return ok
```

**Ausgabe-Dateien** (immer bereitstellen):
1. `.stl` — Universal für Slicer
2. `.3mf` — Bambu Studio bevorzugt (Metadaten)
3. `.png` — Render-Vorschau

### 6. Druck-Empfehlungen ausgeben

> ⚠️ **User-Präferenz**: Ahmed möchte **keine standardmäßigen Druck-Empfehlungen** (Material, Layer-Höhe, Supports, Orientierung, Bett-Temperatur) automatisch ausgegeben haben. Diese Sektion nur anwenden, wenn er explizit danach fragt.

```
Material: PLA (Bambu Lab Basic PLA)
Nozzle: 0.4mm
Layer-Höhe: 0.2mm
Wände: 3 Perimeter
Top/Bottom: 4 Schichten
Infill: 15% Gyroid (funktional: 30-40%)
Support: Ja/Nein
Brim: Empfohlen bei kleinem Footprint
Orientierung: <Empfohlene Ausrichtung>
Geschätzte Zeit: <Xh Ym>
Filament: <Xg>
```

## Wichtige Code-Patterns

### Boolean-Operationen (manifold3d)
```python
def trimesh_to_manifold(mesh):
    verts = np.asarray(mesh.vertices, dtype=np.float32)
    faces = np.asarray(mesh.faces, dtype=np.uint32)
    return m3d.Manifold(m3d.Mesh(vert_properties=verts, tri_verts=faces))

def manifold_to_trimesh(manifold):
    m = manifold.to_mesh()
    return trimesh.Trimesh(vertices=m.vert_properties[:,:3], faces=m.tri_verts)

a = trimesh_to_manifold(mesh1)
b = trimesh_to_manifold(mesh2)
result = manifold_to_trimesh(a + b)  # Union
result = manifold_to_trimesh(a - b)  # Differenz
result = manifold_to_trimesh(a ^ b)  # Intersection
```

### trimesh.creation.extrude_polygon — z-Translation & Caching

```python
from shapely.geometry import Polygon
from shapely.geometry.polygon import orient

poly = Polygon([(0,0), (10,0), (10,5), (0,5)])
poly = orient(poly, sign=1.0)  # Normale nach +Z (wichtig!)

mesh = trimesh.creation.extrude_polygon(poly, height=H)
# Ursprung: Extrusion sitzt bei z = -H/2 ... +H/2
# Um auf z=0 zu setzen:
mesh.apply_translation((0, 0, H/2))
```

> ⚠️ **Bounding-Box-Caching-Pitfall**: `apply_translation()` auf eine `Extrusion` aktualisiert die Bounding Box **nicht** korrekt. `mesh.bounding_box.extents` gibt dann veraltete Werte. **Fix**: `mesh = trimesh.Trimesh(vertices=mesh.vertices, faces=mesh.faces)` nach der Translation aufrufen, um die gecachte Geometrie zu invalidieren, ODER direkt auf `mesh.vertices` und `mesh.faces` arbeiten ohne auf `bounding_box` zu vertrauen.

> ⚠️ **shapely Polygon-Orientierung**: `trimesh.creation.extrude_polygon` erwartet eine bestimmte Winding-Order. Wenn die Normale nach unten zeigt (inside-out Extrusion), nutze `orient(poly, sign=1.0)` vor der Extrusion.

### Partielle Kanten-Abrundung (z. B. nur obere Innenkanten)

**Falsch / Fragil:** Zylinder entlang der Kante subtrahieren — Boolean-Differenz mit langen Zylindern verliert Geometrie oder repariert unerwünscht.

**Auch falsch — einfach gestufte Öffnung:** Zwei Hüllen mit unterschiedlichen Wandstärken (z.B. unterer Teil `wall=2.0` + oberer Teil `wall=1.0`) zu `union`ieren erzeugt nur eine **gestufte Öffnung**, keinen Chamfer. Der Volumenverlust ist ~0 cm³. Ein echter Chamfer braucht schräge Übergangsflächen.

**Richtig:** Zwei-Teile-Bauweise mit echter Abrundung im oberen Ring — unterer Korpus bleibt scharf, oberer Ring enthält die Abrundung via `rounded_rect_polygon` oder direkter Mesh-Konstruktion.

```python
# 1. Unterer Korpus (scharfkantig, z=0 bis z=H-R)
LOW_H = OUT_H - R_FILLET
low_outer = trimesh.primitives.Box(extents=(OUT_W, OUT_D, LOW_H))
low_outer.apply_translation((0, 0, LOW_H/2))

low_inner = trimesh.primitives.Box(extents=(IN_W, IN_D, LOW_H - BOTTOM))
low_inner.apply_translation((0, 0, BOTTOM + (LOW_H - BOTTOM)/2))
m_low = trimesh_to_manifold(low_outer) - trimesh_to_manifold(low_inner)

# 2. Oberer Ring (abgerundete innere Kontur, z=H-R bis z=H)
ring_outer = trimesh.primitives.Box(extents=(OUT_W, OUT_D, R_FILLET))
ring_outer.apply_translation((0, 0, LOW_H + R_FILLET/2))

# Innenprofil mit shapely abgerundet
inner_poly = rounded_rect_polygon(IN_W, IN_D, R_FILLET)
ring_inner = trimesh.creation.extrude_polygon(inner_poly, height=R_FILLET + 0.02)
# Leichte Überlappung zum unteren Teil
ring_inner.apply_translation((0, 0, LOW_H + R_FILLET/2 - 0.01))

m_ring = trimesh_to_manifold(ring_outer) - trimesh_to_manifold(ring_inner)

# 3. Union
m_box = m_low + m_ring
box = manifold_to_trimesh(m_box)
```

> **Regel:** Union zweier Teile braucht eine leichte Überlappung oder eine exakt gemeinsame Grenzfläche. ±0.01 mm Versatz verhindert Mikro-Lücken.

### Extrusion von 2D-Profilen
```python
poly = Polygon([(0,0), (10,0), (10,5), (5,5), (5,10), (0,10)])
mesh = trimesh.creation.extrude_polygon(poly, height=3.0)
```

### Gewinde-Loch
```python
def hole(pos, dia, depth):
    h = cylinder(radius=dia/2, height=depth, sections=32)
    h.apply_translation(pos)
    return h
```

## OpenSCAD Fallback

Wenn trimesh/manifold3d für komplexe CSG-Szenen ungeeignet:

```python
scad_code = """
$fn = 64;
difference() {
    cube([50, 30, 10], center=true);
    translate([0,0,5]) cylinder(h=12, d=8, center=true);
}
"""
with open("/tmp/model.scad", "w") as f:
    f.write(scad_code)

import subprocess
subprocess.run([
    "env", "MESA_GL_VERSION_OVERRIDE=3.0", "LIBGL_ALWAYS_SOFTWARE=1",
    "/tmp/bin/openscad", "-o", "/tmp/model.stl", "/tmp/model.scad"
], check=True)
```

## Troubleshooting

| Problem | Lösung |
|---|---|
| Mesh hat Löcher | `manifold3d.Manifold.mesh()` — repariert automatisch |
| Boolean fehlschlägt | manifold3d statt trimesh.boolean nutzen |
| Dünne Wände brechen | Wandstärke auf min 1.2mm erhöhen |
| Support-Marken | Überhänge auf <45° reduzieren |
| Passgenauigkeit schlecht | Toleranzen anpassen |
| `trimesh.section()` crash | `networkx` fehlt → installieren oder Vertices/Normals direkt analysieren |
| CadQuery libGL.so.1 Error | GL-Libs fehlen — trimesh/manifold3d nutzen |

## Checkliste vor STL-Abgabe

- [ ] Maße mit User abgeglichen
- [ ] Watertight-Check bestanden
- [ ] Mindestwandstärke ≥ 1.2mm
- [ ] Toleranzen berücksichtigt
- [ ] Support-Strukturen minimiert
- [ ] Fasen an scharfen Kanten
- [ ] Druckeinstellungen dokumentiert (nur auf explizite Anfrage)
- [ ] STL + 3MF + PNG Preview erstellt
