4 root causes fixed:
1. export_colors=False was removed in Blender 4.x — caused every Blender
export to fail (exit 1) and always fall back to trimesh. Remove it.
Blender now runs the full pipeline: materials + sharp edges.
2. GlbModel cloned ref never reset on url change — key={glbBlobUrl} forces
React to remount GlbModel on each new blob URL, resetting the ref so
fresh geometry is always loaded.
3. glbBlobUrl not cleared before re-fetch — setGlbBlobUrl(null) added at
start of downloadUrl effect so spinner shows instead of stale mesh.
4. staleTime: 30_000 delayed picking up new MediaAsset after generation.
Changed to staleTime: 0 so invalidation always triggers immediate refetch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@@ -436,6 +436,21 @@ SQLAlchemy `Enum(create_type=False)` funktioniert nicht zuverlässig mit asyncpg
---
### 2026-03-07 | Blender 5.0 | `export_colors` in bpy.ops.export_scene.gltf entfernt
**Problem:** `bpy.ops.export_scene.gltf(export_colors=False)` → `keyword "export_colors" unrecognized` → exit code 1 → immer Trimesh-Fallback → nie Materialien, nie Sharp Edges, immer facettiert. Blender hat nie erfolgreich exportiert.
**Lösung:**`export_colors` aus dem Export-Call entfernen. Gültige Blender-5.0-Parameter: `export_format`, `export_apply`, `use_selection`, `export_materials`, `export_image_format`.
**Regel:** Beim Wechsel auf neue Blender-Versionen alle bpy.ops.*-Parameter gegen aktuelle Blender-Doku prüfen. Fehlende Parameter lassen Blender mit exit 1 fehlschlagen — OHNE aussagekräftige Fehlermeldung im Erfolgsfall.
### 2026-03-07 | React | useRef mit if(!ref.current) Guard reagiert nicht auf Prop-Änderungen
**Problem:** `GlbModel` klonte `scene` in `useRef` mit `if (!cloned.current)`. Bei neuem `url`-Prop (regeneriertes GLB) blieb `cloned.current` das alte geklonte Objekt → altes Mesh wurde weiter angezeigt. React mounted/unmounts die Komponente nicht bei Prop-Änderungen.
**Lösung:**`key={glbBlobUrl}` auf `<GlbModel>` → React remountet die Komponente bei jeder neuen URL → frischer `useRef` → neues Mesh. Alternativ: `useMemo` mit URL-Dependency statt `useRef`.
**Regel:** Wenn ein `useRef`-Initialisierungsmuster (`if (!ref.current)`) auf Prop-Änderungen reagieren muss → immer mit `key` oder `useEffect`/`useMemo` kombinieren.
**Problem:** `staleTime: 30_000` auf dem gltf_geometry-Assets-Query: Nach GLB-Generierung dauerte es bis zu 30 Sekunden bis der neue `download_url` im Browser ankam — obwohl `qc.invalidateQueries()` aufgerufen wurde. Invalidierung erzwingt Refetch, aber staleTime=30s verhindert ihn falls der Cache noch "frisch" gilt.
**Lösung:**`staleTime: 0` für Queries die bei Invalidierung sofort aktuell sein müssen.
**Regel:**`staleTime: 0` für Entitäten die nach Mutations sofort aktuell sein müssen. Höhere staleTime nur für read-heavy, selten ändernde Daten (z.B. Materialliste, Templates).
### 2026-03-07 | GLB Export | Trimesh kennt keine Materialien — Blender-Pipeline ist Pflicht
**Problem:** `generate_gltf_geometry_task` nutzte trimesh für STL→GLB. Trimesh ist eine reine Geometrie-Bibliothek: keine Material-Bibliotheken, kein OCC-Kantenmarking, kein Asset-Library-Support. Das erzeugte graue, facettierte GLB-Dateien ohne Materialien.
**Lösung:** Task auf Blender headless (`export_gltf.py`) umgestellt. Übergibt: `material_map` (via `resolve_material_map()` aus `cad_part_materials`), `sharp_edges_json` (aus `mesh_attributes.sharp_edge_midpoints`), `asset_library_blend` (via `get_material_library_path()`). Trimesh nur noch als Fallback wenn Blender nicht verfügbar.
For `thumbnail` type assets: fallback works via `get_thumbnail_url()` → `/api/cad/{cad_file_id}/thumbnail` (no-auth endpoint). For `still` type: no cad_file_id/product_id → no fallback → gray icon shown.
### Bug 1 — `export_colors` not valid in Blender 5.0 (CRITICAL)
→ Blender path always fails → always falls back to trimesh → no materials, no sharp edges, faceted mesh.
This is confirmed in every single log entry. Blender has never successfully exported a GLB.
### Bug B — No Dimensions in "Product Details" Card
The `cad_mesh_attributes.dimensions_mm` block exists in the CAD File section (right sidebar), NOT in the "Product Details" card. User wants it in Product Details.
### Bug 2 — `GlbModel` `cloned` ref never resets on URL change (CRITICAL)
`cloned = useRef<THREE.Group | null>(null)` with guard `if (!cloned.current)` only clones once.
When `glbBlobUrl` changes (new GLB generated), React does NOT remount `GlbModel` (same position in tree),
so `cloned.current` still holds the old geometry → old mesh shown forever.
Fix: add `key={glbBlobUrl}` to `<GlbModel>` → forces remount on each new URL.
### Bug C — No Embedded 3D Viewer
"View 3D" navigates to `/cad/:id` (full page). User wants an inline viewer in the product page CAD card that auto-loads when a `gltf_geometry` asset exists.
### Bug 3 — `glbBlobUrl` not cleared between fetches (UX)
- **Was**: In `publish_asset`, after loading the OrderLine, also load `line.product_id` and the product's `cad_file_id`. Set these on the new MediaAsset. This enables the `_resolve_thumbnails_bulk` fallback and `get_thumbnail_url()` for still assets.
- **Akzeptanzkriterium**: New still assets have product_id and cad_file_id set in DB.
### Task 1: Fix Blender GLTF export parameters
**File**: `render-worker/scripts/export_gltf.py`
Remove `export_colors=False` from `bpy.ops.export_scene.gltf()` call.
Keep `export_materials="EXPORT"` and `export_image_format="AUTO"` — these are valid in Blender 5.0.
**Acceptance**: Blender exits 0, GLB file is created with materials.
**Requires rebuild**: yes — scripts are COPY'd into container.
### Task A2: Frontend — useAuthBlob hook in MediaBrowser
- **Was**: Add `useAuthBlob(url)`hook that fetches the URL with Authorization header and returns a blob URL. Use it in `AssetCard` instead of `asset.download_url` for image rendering. Revoke blob URL on unmount.
- **Akzeptanzkriterium**: Still images visible in media library grid.
### Task 2: Fix GlbModel stale mesh on regeneration
- **Was**: In the "Product Details" card (around line 409-433), after the Notes field, add a read-only "Dimensions" row if `product.cad_mesh_attributes?.dimensions_mm` exists. Format: "X × Y × Z mm" with a Ruler icon and small "from CAD" label.
- **Akzeptanzkriterium**: Dimensions visible in Product Details card when mesh_attributes populated.
Change `staleTime: 30_000` → `staleTime: 0` on the `gltf_geometry` assets query.
The `qc.invalidateQueries()` call after generating already forces a refetch,
but staleTime=0 also ensures refetch on window focus/tab return.
**Acceptance**: New MediaAsset is picked up within seconds of task completion.
---
## Migrations-Check
Keine DB-Migrationen nötig. `product_id` und `cad_file_id` sind bereits Spalten in `media_assets`.
## Reihenfolge
Task 1 (rebuild) + Tasks 2/3/4 (frontend hot-reload) in parallel.
## Reihenfolge-Empfehlung
A1 + A2 + B + C parallel (alle unabhängig).
D parallel (nur Docker rebuild, kein Code).
## Risiken / Offene Fragen
-`useAuthBlob` creates blob URLs per asset — 50+ assets in grid could trigger many fetches. Add `limit` or lazy load (only fetch when card is visible).
- InlineCadViewer: GLB fetch for large files may take 5-30s. Show skeleton/spinner.
-`useGLTF` from drei expects a URL string. Blob URLs work fine.
- ThreeDViewer has `onClose` required prop — InlineCadViewer should be a new simpler component.
## Risiken
-`export_materials="EXPORT"` and `export_image_format="AUTO"` may also be invalid in Blender 5.0.
If so, remove them too and test with bare minimum params (format + apply only).
- If the Schaeffler .blend library materials use custom node groups instead of Principled BSDF,
the GLTF exporter will still export flat grey — that requires material baking, out of scope here.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.