# ZimaOS CAD Pipeline Notes
device: ZimaCube (ZimaOS v1.6.1, headless)
gpu_support: none (libGL.so.1 missing, cannot install Mesa — no apt/dnf/apk)
python_interp: /DATA/AppData/hermes/venv/bin/python3.11
installed_libs:
  trimesh: "✅"
  manifold3d: "✅"
  matplotlib: "✅"
  cadquery: "❌ fails at import — libGL.so.1 missing"

## Lessons learned from sessions 2026-05-12

### 1. CadQuery impossible on ZimaOS
- `ImportError: libGL.so.1: cannot open shared object file`
- cadquery-ocp dringt auf libGL.so.1 für OpenCASCADE Wrapping
- Kein Paketmanager um Mesa zu installieren (kein apt/dnf/apk)
- → trimesh+manifold3d ist der einzige funktionierende Pfad

### 2. np.ptp() vs TrackedArray.ptp()
- `trimesh.vertices` liefern numpy `TrackedArray`
- `.ptp()` wurde in neueren numpy Versionen entfernt
- Fix: `np.ptp(arr)` statt `arr.ptp()`
- Gilt für alle trimesh vertex/face-Array Operationen

### 3. Output = STL + PNG only
- Kein 3MF mehr (verzichtet auf networkx Dependency)
- Skill Workflow: nur .stl und .png pro Modell
- Report enthält: STL path, size, volume, watertight, PNG preview
- Keine Druck-Empfehlungen — User will sie nicht

### 4. MPLCONFIGDIR Workaround
- matplotlib versucht `~/.config/matplotlib` — Permission denied
- Harmlose Warnung, aber Cache wird in `/tmp/` angelegt
- Für saubere Hintergrundläufe: `MPLCONFIGDIR=/tmp/mpl-conf` setzen

### 5. Over-engineering pitfall
- User beschrieb: "inner_length + 4mm × inner_width + 4mm × 2mm Box oben abziehen"
- Ich baute stattdessen: komplexes Fillet mit Zylindern + Sphären + Clipping-Boolean-Intersection
- Ergebnis: User sagte "Das ist nicht was ich wollte. Ich möchte LEDIGLICH die Kanten an der oberen Innenseite abgerundet."
- **Regel: Implementiere exakt was der User beschreibt.** Wenn er "Box subtrahieren" sagt, subtrahiere eine Box. Keine geometrisch "bessere" Lösung ohne Frage.

### 6. Simple Chamfer Helper
- `chamfer_inner_top(mesh, inner_l, inner_w, h=2.0)` — die richtige Implementation
- Auto-detect top z via `mesh.bounds[1][2]`
- Subtrahiert eine Box der Größe `(inner_l+2h) × (inner_w+2h) × h` an der Oberkante
- Erzeugt eine 45°-ähnliche Abschrägung, keine Kurve — genau was der User beschrieb

### 7. `mesh.fix_faces()` does not exist — use manifold3d instead
- trimesh `Trimesh` object has **no method `fix_faces()`** — calling it raises `AttributeError`
- The correct way to fix winding/orientation issues:
  - Convert to manifold3d with `Manifold(m.vert_properties[:, :3].tolist(), m.faces.tolist())` — or better, use `Mesh` from manifold3d
  - manifold3d will clean the mesh automatically
- The `merge_vertices()` method **does** exist on trimesh and should be called after manual vertex construction.

### 8. manifold3d Booleans require closed solid meshes
- `difference()` on trimesh objects **only works reliably on closed solids**
- Open shells (hollow boxes with no top) will silently fail or produce garbage when boolean-subtracting co-planar geometry
- **Never use booleans to add chamfers to open-top boxes.**
- For chamfers on open shells: build the chamfer into the mesh at construction time (shift vertices), or use a different approach

### 9. manifold3d to/from trimesh conversion pattern
```python
from manifold3d import Manifold, Mesh

def to_manifold(mesh: trimesh.Trimesh) -> Manifold:
    mm = Mesh(vert_properties=mesh.vertices, tri_verts=mesh.faces)
    return Manifold(mm)
    # Alternative: m = Manifold.cube([l, w, h], True)  # centered=True

def from_manifold(m: Manifold) -> trimesh.Trimesh:
    mm = m.to_mesh()
    return trimesh.Trimesh(vertices=mm.vert_properties[:, :3], faces=mm.tri_verts)
```

### 10. manifold3d API vs trimesh booleans
- manifold3d: `Manifold.cube([l, w, h], center=True).translate([x, y, z])`
- trimesh: `trimesh.creation.box(extents=[l, w, h]).apply_translation([x, y, z])`
- Boolean result from manifold3d: watertight=True, volume correct
- Boolean result from trimesh `difference()` on open shells: watertight=True but **geometry unchanged** (silent failure)
- **Rule**: for reliable booleans on ZimaOS, use manifold3d directly, NOT trimesh booleans on open shells

### 11. trimesh `difference()` returns silently wrong results on open shells
- When boolean-subtracting from an open-top box, trimesh's difference engine (backed by manifold3d) may:
  - Report watertight=True
  - Report unchanged volume
  - Produce a PNG preview with no visible geometry change
- **This is the most dangerous pitfall**: the mesh "looks" valid but is functionally wrong
- Detection: compare volume before/after the operation. If unchanged → boolean failed silently.
- Fix: Do not use booleans for chamfers on open shells. Build the feature into the mesh geometry.

### 12. Volume check as boolean validation
After any boolean operation, ALWAYS check if the volume changed meaningfully:
```python
vol_before = shell.volume
result = shell.difference(chamfer_box)
vol_after = result.volume
if abs(vol_after - vol_before) < 0.01:
    print("WARN: Boolean produced no geometry change — likely failed on open shell")
```