# Projekt-Learnings — Schaeffler Automat ## Format **Datum | Kategorie | Problem → Lösung** --- ## Learnings ### 2026-03-14 | Blender | OBJ-Rotation Turntable: Reparenting von USD-Parts verschiebt Geometrie USD-importierte Parts haben existierende Eltern-Objekte (Xform-Nodes). `part.parent = pivot; part.matrix_parent_inverse = pivot.matrix_world.inverted()` verliert den Beitrag des alten Parents zur Welt-Position → Parts verschieben sich ~14m. Fix: World-Matrix vor Reparenting sichern, dann via `part.matrix_local = pivot.inverted() @ saved_world` wiederherstellen. Zusätzlich `bpy.context.view_layer.update()` vor dem Parenting aufrufen, damit `pivot.matrix_world` aktuell ist. ### 2026-03-13 | Blender/USD | USD-Import erzeugt leere Material-Stubs → schwarze Renders Blender's `bpy.ops.wm.usd_import()` erstellt leere Material-Stubs (use_nodes=True, nodes=0) aus USD MaterialBinding-Referenzen. Wenn `apply_material_library_direct` dann die echten Materialien aus der .blend-Bibliothek laden will, findet es die Stubs bereits in `bpy.data.materials` und nutzt sie direkt — ohne die echten Shader-Node-Trees zu laden. Leere Materialien rendern in Cycles als pures Schwarz (RGB max=1). Fix: `_find_material_with_nodes()` Hilfsfunktion, die `.NNN`-Suffix-Kollisionen auflöst und nur Materialien mit echten Nodes zurückgibt. ### 2026-03-12 | Caching | Composite Cache Keys für Tessellierung Hash-basiertes Caching in Celery Tasks muss alle relevanten Parameter einschließen, nicht nur den Datei-Hash. Bei `generate_gltf_geometry_task` und `generate_usd_master_task` wurde der Cache-Key auf `{hash}:{linear}:{angular}:{engine}` erweitert. Außerdem: immer Disk-Existenz des gecachten Assets prüfen (`storage_key.exists()`) bevor ein Cache-Hit zurückgegeben wird — der Asset-Record kann existieren, die Datei aber nicht. ### 2026-03-12 | React | Modal in braucht createPortal Ein Modal-Dialog, der aus einer ``-Tabellenzeile gerendert wird, erzeugt invalides HTML (`
` in `` nicht erlaubt). Fix: `createPortal(modal, document.body)` — rendert das Modal am Body-Root, außerhalb der Tabellen-Hierarchie. ### 2026-03-12 | Prozess | ROADMAP war dem Code weit hinterher Pre-flight code audit vor dem Schreiben eines Plans ist zwingend erforderlich. Beim Sprint vom 2026-03-12 zeigte sich: P1 (dead-code cleanup, blender_render.py split), P5 (USD render wiring), P8 (Celery tenant context) und P10 (UI polish) waren bereits größtenteils im Code implementiert, aber nicht im ROADMAP.md reflektiert. Lesson: Immer zuerst grep/ls-Gates und Line-Count-Checks laufen lassen, bevor Tasks geplant werden. ### 2026-03-12 | Material | SQLAlchemy Mapper-Fehler bei isoliertem Domain-Import Wenn eine Celery-Task nur `from app.domains.orders.models import Order` importiert (ohne `app.models`), kann SQLAlchemy die String-Referenzen `relationship("Template", ...)` und `relationship("User", ...)` nicht auflösen — alle Mapper-Initialisierungen schlagen fehl. Fix: `import app.models as _all_models` am Modul-Level von `beat_tasks.py` eintragen. So werden alle Models geladen bevor irgendeine Task-Funktion die Mapper initialisiert. Gilt für alle Tasks die Domain-Models direkt importieren. ### 2026-03-12 | Material | OCC-Part-Namen vs. USD-Part-Keys beim Material-Matching OCC XCAF-Namen enthalten Bindestriche und _AF\d+-Suffixe (z.B. `GE360-HF-0011-EIN_HAELFTE_AF0_1_AF0`). Das USD-Export-Tool slugifiziert den Namen: `[^a-z0-9]+` → `_` (d.h. `ge360_hf_0011_ein_haelfte_af0_1_af0`). Blender importiert das USD-Objekt als `ge360_hf_0011_ein_haelfte` (base-name aus XCAF). Das Mat-Map aus der DB ist lowercased mit Bindestrichen. Fix: In `build_mat_map_lower()` auch eine slug-normierte Variante jeden Keys hinzufügen (`re.sub(r'[^a-z0-9]+', '_', kl).strip('_')`). Der bestehende Prefix-Fallback (`key.startswith(part_key)`) fangt dann den Rest ab. ### 2026-03-12 | USD | Stale USD-Master-Assets invalidieren USD-Assets die vor dem `elif shape_has_loc`-Fix (Commit `de7f97b`, 2026-03-12 16:43) generiert wurden, haben fehlerhafte Geometrie (double-transform). Fix: `DELETE FROM media_assets WHERE asset_type='usd_master' AND created_at < '2026-03-12 16:43:33'` + `UPDATE cad_files SET step_file_hash = NULL WHERE id IN (...)`. Nächster Render generiert neue korrekte USD-Datei. ### 2026-03-12 | USD | Mesh-Prim-Benennung für Blender 5.0 Blender 5.0 importiert USD und kollabiert single-child Xform+Mesh-Hierarchien zu einem einzigen Objekt. Der Objektname entspricht dabei dem Leaf-Namen des Mesh-Prims (nicht dem Xform). Die Lösung: `mesh_path = f"{part_path}/{part_key}"` statt `f"{part_path}/Mesh"`. Damit importiert Blender jedes Objekt direkt mit dem korrekten part_key als Namen — kein Post-Import-Rename nötig. Blender setzt `obj["usd:path"]` nicht, daher ist der pfadbasierte Rename-Ansatz nicht funktionsfähig. ### 2026-01-15 | Architektur | Backend-Port-Konflikt Port 8000 war belegt → Port 8888 in `docker-compose.yml` + Vite-Proxy. Früh festlegen und in CLAUDE.md dokumentieren. ### 2026-01-20 | Datenbank | SQLAlchemy trackt key-value-Store-Mutations nicht Admin-Settings via ORM gespeichert → Änderungen nicht persistiert. SQLAlchemy erkennt keine Mutation an geladenen Value-Feldern. **Lösung:** Direktes `session.execute(update(...))` statt ORM-Mutation. Key-Value-Stores immer mit direktem SQL verwalten. ### 2026-01-25 | Render-Pipeline | Blender ignoriert STEP-Einheiten (mm vs. m) STEP in mm, Blender in m → 50mm-Lager erscheint 50m breit. `_scale_mm_to_m(parts)` in allen 3 Render-Scripts: `part.scale = (0.001, 0.001, 0.001)` + Transform anwenden, direkt nach STL-Import vor Kamera-Kalkulation. ### 2026-01-28 | Render-Pipeline | Blender 5.0 hat `scene.node_tree` entfernt `_setup_bg_compositor()` → Python-Exception → Blender exit code 0 → Task fälschlicherweise "completed". **Lösung:** Compositing entfernt; bg_color via FFmpeg (`-f lavfi -i color=...` + overlay). Pflicht: `try: main() except SystemExit: raise except Exception: traceback; sys.exit(1)` in allen Blender-Scripts. ### 2026-02-05 | Material-System | Material-Alias-Lookup-Reihenfolge falsch `Steel--Stahl` war sowohl `Material.name` als auch Alias für `SCHAEFFLER_010101_Steel-Bare`. Lookup fand zuerst den Namen → Blender konnte ihn nicht in der Library finden. **Lösung:** `material_service.py`: **Aliases zuerst**, dann exakter Name, dann Pass-through. ### 2026-02-10 | Render-Pipeline | Blender-Template überschreibt HDRI/World Im Template-Modus (Mode B) lief Auto-Licht/World-Setup bedingungslos → überschrieb HDRI aus .blend-Template. **Lösung:** In Template-Mode Lights, World und Color-Management-Override vollständig überspringen; nur Kamera ggf. neu berechnen. ### 2026-02-15 | Celery | Blender-Queue-Flooding durch falsche Concurrency Alle Tasks auf `step_processing` (concurrency=8) → 8 Workers gleichzeitig an blender-renderer (max 1) → 7× Timeout. **Lösung:** `process_step_file` (step_processing, concurrency=8) nur schnelle Metadata; `render_step_thumbnail` (asset_pipeline, concurrency=1) für Blender. HTTP-Services mit max 1 Request immer auf eigener Queue mit concurrency=1. ### 2026-02-18 | Frontend | Tailwind CSS-Variablen inkompatibel mit opacity-Syntax `bg-surface/50` erzeugt `rgb(var(--color-bg-surface) / 0.5)` — invalides CSS wenn Variable ein Hex-Wert ist. **Lösung:** `style={{ backgroundColor: 'var(--color-bg-surface)' }}`. Alternativ: CSS-Vars im RGB-Channel-Format definieren (`255 255 255`). ### 2026-02-20 | STL-Cache | Three.js-Renderer nutzte tempfile → kein Download Three.js schrieb STL in tempfile und löschte es → Download-Endpoint fand nichts. **Lösung:** Persistent cache: `step_path.parent / f"{step_path.stem}_low.stl"`, Cache-Hit-Check vor Konvertierung, kein `unlink()`. Konvention `{stem}_{quality}.stl` neben STEP-Datei von Anfang an in allen Renderern. ### 2026-02-20 | STL-Cache | blender-renderer fehlte /convert-stl Endpoint Blender renderte + konvertierte in einem Schritt, persistierte STL nicht. **Lösung:** Neuer `/convert-stl` Endpoint (STEP→STL ohne Render), Celery-Task `generate_stl_cache` auf `asset_pipeline`, Admin-Batch-Funktion "Generate Missing STLs". ### 2026-02-22 | Material-System | Fehlender Alias blockiert Material-Replacement `"Stahl v2"` in DB nicht in materials noch in material_aliases → keine Ersetzung, Silent-Fail. **Lösung:** Alias direkt in DB eintragen. Bei Render ohne Materialersetzung immer zuerst `resolve_material_map()` debuggen, Alias-Tabelle prüfen. ### 2026-02-25 | Frontend | canDispatch-Bedingung zu restriktiv `canDispatch` enthielt `&& hasRetryable` → Button fehlte wenn alle Zeilen initial "pending". **Lösung:** `hasRetryable` entfernt; Validierung im Backend. Aktions-Buttons nicht zu stark von abgeleiteten Zuständen abhängig machen. ### 2026-02-28 | Frontend | MaterialInput-Dropdown ohne Hintergrund `bg-surface` mit CSS-Hex-Variable → transparenter Dropdown (siehe 2026-02-18). **Lösung:** `style={{ backgroundColor: 'var(--color-bg-surface)' }}` für Dropdown-Container, Group-Header, Sticky-Button. Datei: `frontend/src/components/shared/MaterialInput.tsx`. ### 2026-03-06 | Refactor | .gitignore `core` trifft Verzeichnisse `core` in `.gitignore` ignorierte `backend/app/core/` Verzeichnis. **Lösung:** `/core` (root-relativ) — trifft nur Datei im Root, nicht verschachtelte Verzeichnisse. ### 2026-03-06 | Architektur | Blender-HTTP-Service vs. direkter Subprocess Flask-HTTP-Microservice = Single-Point-of-Failure, kein Scaling, HTTP-Overhead. **Lösung:** Celery render-worker Container — Blender direkt via `subprocess.run`. `is_blender_available()` prüft `BLENDER_BIN` env var; Backend-Container fallen auf Pillow zurück. Subprocess > HTTP für blocking compute tasks. ### 2026-03-06 | Refactor | Bash CWD-Problem durch Hook-Pfad-Auflösung Nach `cd frontend` im Bash-Tool blieb CWD in `frontend/` → Hook-Pfad nicht gefunden → alle Tool-Calls blockiert. **Lösung:** Symlink `frontend/.claude → .claude`. Hooks mit absoluten Pfaden konfigurieren; `cd` nur mit `&&` am Ende der Command-Chain. ### 2026-03-06 | Multi-Tenancy | PostgreSQL RLS mit current_setting und Null-Safety `current_setting('app.current_tenant_id')` wirft Exception wenn nicht gesetzt. **Lösung:** `current_setting('app.current_tenant_id', true)` — zweites Argument macht Funktion Null-safe. Admin-Bypass: separate Policy mit `SET LOCAL app.current_tenant_id = 'bypass'`. ### 2026-03-11 | Tessellation | GMSH CharacteristicLength ≠ OCC linear_deflection OCC `linear_deflection` ist ein **Oberflächenabweichungs-Toleranzwert** (max. Abstand Mesh→echte Fläche). GMSH `CharacteristicLengthMax` ist eine **Kantenlängenvorgabe**. Gleicher Wert (0.1) erzeugt bei GMSH 50× mehr Dreiecke → 231MB statt 3MB. **Lösung:** `CharacteristicLengthMax = linear_deflection * 15.0` (15× Faktor). `MinimumCirclePoints = min(20, ceil(2π/angular_deflection))` — ohne Cap liefert `angular_deflection=0.1rad` → 63 Punkte/Kreis (10× zu dicht). Mit Cap 20: ~20MB statt 231MB, OCC-ähnliche Dichte bei 0 Fan-Dreiecken. ### 2026-03-11 | Tessellation | BRep_Builder.UpdateFace — richtige Signatur OCP Python API: `BRep_Builder.UpdateFace(face, triangulation)` — 2-Argumente-Form, NICHT `(face, tri, loc, tolerance)` wie in C++-Doku. Falsche Signatur führt zu Silent-Exception, alle Faces fallen auf BRepMesh zurück. ### 2026-03-11 | Tessellation | GMSH OOM bei Assembly-Compound GMSH verarbeitet ganzen Compound auf einmal → 25-teilige Lager-Baugruppe: 2.3GB RAM → OOM-Kill (exit -9). **Lösung:** Per-Solid-Iteration via `TopExp_Explorer(root_shape, TopAbs_SOLID)`. `BRep_Builder.UpdateFace` aktualisiert Face-Objekte in-place; Parent-Compound sieht Updates automatisch. ### 2026-03-11 | Celery | Timeout in Worker-Code ≠ Running Worker liest neue Version `export_glb.py` mit 600s-Timeout in Container-Datei — aber Celery-Worker hatte Code beim Start geladen. Fehler zeigt `timeout=120` obwohl Datei 600 enthält. **Lösung:** `docker compose restart render-worker` nach Datei-Update. Celery lädt Module beim Start, nicht bei Task-Ausführung. ### 2026-03-06 | Refactor | Domain-Driven Migration: Compat-Shims statt Big-Bang Vollständige Migration in einem Schritt bricht alle Imports. **Lösung:** Alte Dateien werden Re-Export-Shims: `from app.domains.auth.models import User; __all__ = ["User"]`. Erst nach vollständiger Import-Migration Shims entfernen. ### 2026-03-06 | Workflow-System | Celery Canvas vs. Custom Workflow-Engine Custom Graph-Traversal-Engine = 2-3 Wochen Eigenentwicklung. **Lösung:** Celery Canvas (`chain`, `group`, `chord`) als Execution-Engine. `dispatch_workflow(type, order_line_id, params)` baut Canvas dynamisch aus Config. Kein Backward-Compat nötig: Celery Canvas reicht für parallele/sequentielle Workflows. ### 2026-03-06 | Circular Import | template_service ↔ domains/rendering/service Beide Module waren Shims die aufeinander zeigten → `resolve_template()` nie aufrufbar → alle Renders crashten mit ImportError. **Lösung:** `template_service.py` aus git-History wiederhergestellt (echte Implementierung). `domains/rendering/service.py` importiert nur davon. Nach Refactoring: `grep -rn "def resolve_template"` muss ≥1 Treffer liefern. ### 2026-03-06 | Multi-Tenancy | audit_log.tenant_id NOT NULL blockiert alle Notifications Migration 036 machte `audit_log.tenant_id NOT NULL`, aber `emit_notification` setzt kein `tenant_id` → INSERT schlägt fehl → 500 bei Order-Submit. **Lösung:** `ALTER TABLE audit_log ALTER COLUMN tenant_id DROP NOT NULL`. Broadcast-Notifications (system-weit) dürfen NULL tenant_id haben. ### 2026-03-06 | Frontend | GET /api/tenants gibt 307 Redirect zurück FastAPI registriert `/tenants/` (trailing slash) → `GET /tenants` → 307. Axios folgt, verliert Authorization-Header → 401. **Lösung:** `getTenants()` auf `/tenants/` (mit trailing slash). Generell: FastAPI 307-Redirects verlieren Auth-Header in Axios. Immer trailing slash im Frontend verwenden. ### 2026-03-06 | Celery Canvas | workflow_builder: order_line_id als step_path übergeben `render_still_task.si(order_line_id, **params)` — Task erwartet `step_path: str`. Blender versuchte UUID als Pfad → crash. **Lösung:** Separate `render_order_line_still_task` die intern OrderLine→Product→CadFile auflöst. Workflow-Builder-Tasks dürfen nie Domain-IDs als file-path-Argumente verwenden. ### 2026-03-06 | Docker | docker compose in Container braucht multi-stage CLI-Copy `python:3.11-slim` hat kein `docker` binary. **Lösung:** `COPY --from=docker:cli /usr/local/bin/docker /usr/local/bin/docker` + cli-plugins. Docker-Socket mounten (`/var/run/docker.sock`), Compose-File als Volume, `COMPOSE_PROJECT_DIR` env var. ### 2026-03-06 | Docker | `COPY --from=docker-cli cli-plugins` schlägt fehl `docker:cli` Image hat kein `/usr/local/lib/docker/cli-plugins` Verzeichnis. **Lösung:** Nur `/usr/local/bin/docker` kopieren — neuere docker:cli Images enthalten `compose` bereits im Binary. ### 2026-03-06 | Docker | apt-Paketname libgdk-pixbuf2.0-0 vs libgdk-pixbuf-2.0-0 Auf Debian bookworm heißt das Paket `libgdk-pixbuf-2.0-0` (mit Bindestrichen). `apt-get install` mit falschem Namen → exit 100. **Regel:** Immer `apt-cache search libgdk` im Container prüfen. ### 2026-03-06 | React Three Fiber | Wireframe-Toggle über Material-Clone GLTF-Materialien sind shared objects — direkte Mutation von `child.material.wireframe` beeinflusst alle Instanzen. **Lösung:** `child.material = child.material.clone()` vor Wireframe-Mutation. GLTF-Materialien bei Runtime-Modifikationen immer zuerst clonen. ### 2026-03-06 | pytest | Backend ohne dev-Dependencies `pip install -e .` ohne `[dev]` → kein pytest → `ModuleNotFoundError`. **Lösung:** `pip install -e ".[dev]"`. Dev-Extras immer im Dockerfile angeben wenn Tests im Container laufen sollen. ### 2026-03-06 | Celery | `@shared_task` verbindet sich mit localhost statt Redis-Container `@shared_task` aus `celery` ohne App-Kontext → Default-Broker `localhost:6379` → `kombu.exceptions.OperationalError`. **Lösung:** Immer `from app.tasks.celery_app import celery_app` + `@celery_app.task(...)`. `@shared_task` nur wenn Modul garantiert nach `celery_app.py` geladen wird. ### 2026-03-06 | SQLAlchemy | Relationship-Auflösung schlägt fehl wenn Models nicht importiert Celery-Task importierte nur `AssetLibrary` → `Material.creator` → String-Ref `"User"` nicht im Mapper → `InvalidRequestError`. **Lösung:** `import app.models # noqa: F401` vor erstem DB-Zugriff in Celery-Tasks — `__init__.py` registriert alle 14 Modelle. ### 2026-03-06 | MinIO / Storage | `storage.upload()` erwartet `Path`, nicht `str` `storage.upload(str_path, key)` → `AttributeError: 'str' has no attribute 'name'` → STLs lokal gespeichert aber nie in MinIO. **Lösung:** `storage.upload(Path(stl_path), key)` — alle `upload()`-Aufrufe mit `Path()`-Cast absichern. ### 2026-03-06 | Blender / Scripts | `catalog_assets.py` Pfad in Docker falsch `Path(__file__).parent` zeigt auf Backend-Container-Pfad, nicht auf render-worker `/render-scripts/`. **Lösung:** `RENDER_SCRIPTS_DIR` Env-Var: `Path(os.environ.get("RENDER_SCRIPTS_DIR", "/render-scripts")) / "catalog_assets.py"`. ### 2026-03-06 | Alembic | Migration exit code 100 bei enum-Konflikt `Enum(create_type=False)` unzuverlässig mit asyncpg. **Lösung:** Raw SQL: `DO $$ BEGIN CREATE TYPE ...; EXCEPTION WHEN duplicate_object THEN NULL; END $$;`. Für Tabellen: `CREATE TABLE IF NOT EXISTS`. ### 2026-03-06 | Render-Pipeline | Circular Shim blockiert alle Order-Renders `dispatch_order_line_render` → Shim A→B→A → Render startet nie. **Lösung:** `dispatch_order_line_render` direkt auf `render_order_line_task.delay()`. Den echten Aufruf-Pfad API→Task vor Refactoring dokumentieren. ### 2026-03-06 | Render-Pipeline | render_order_line_task auf falschem Worker (kein Blender) Task auf `step_processing` → `worker`-Container (kein Blender) → `is_blender_available()` = False → Pillow-Placeholder, kein Fehler. **Lösung:** Queue auf `asset_pipeline` → `render-worker`-Container (Blender 5.0.1 + cadquery). Blender-Tasks IMMER auf `asset_pipeline`. ### 2026-03-06 | Docker | worker-thumbnail vs render-worker — beide auf asset_pipeline Zwei Services unterschiedlicher Capabilities auf gleicher Queue → round-robin → 50% Silent-Fail. **Lösung:** `worker-thumbnail` aus docker-compose entfernt. `render-worker` ist alleiniger Consumer. Nie zwei Services mit unterschiedlichen Fähigkeiten auf dieselbe Queue. ### 2026-03-06 | Multi-Tenancy | tenant_id NOT NULL verletzt bei Order-Erstellung Migration 036 machte `tenant_id NOT NULL` auf orders/order_lines/order_items → alle Create-Endpoints übergaben das Feld nicht. **Lösung:** `tenant_id=getattr(user, 'tenant_id', None)` in allen Model-Konstruktoren. Nach jeder RLS-Migration alle Create-Endpoints auf neue Pflichtfelder prüfen. ### 2026-03-06 | Workflow-Dispatch | dispatch_render_with_workflow als Drop-in Legacy `dispatch_order_line_render.delay()` wurde nie durch Workflow-Engine ersetzt. **Lösung:** `dispatch_render_with_workflow(order_line_id)` lädt `OutputType.workflow_definition_id`, nutzt Canvas wenn gesetzt, fällt sonst auf Legacy zurück. ### 2026-03-06 | OCC | `RWMesh_CoordinateSystemConverter` nicht als Python-Binding verfügbar `writer.ChangeCoordinateSystemConverter()` → `TypeError: Unregistered type`. **Lösung:** Shapes vor Export mit `BRepBuilderAPI_Transform(shape, trsf, True)` um Faktor 0.001 skalieren. `RWGltf_CafWriter` direkt ohne Koordinatensystem-Konverter aufrufen. ### 2026-03-06 | OCC | `XCAFDoc_ShapeTool.GetComponents` → `GetComponents_s` In OCP alle XCAF static-Methoden mit `_s`-Suffix. Gilt für alle `XCAFDoc_*`-Klassen. ### 2026-03-06 | Pipeline | OCC-native STEP→GLB ersetzt STL-Intermediary STL verliert Materialien/Farben, doppelter Aufwand, Dateiflut. **Lösung:** `RWGltf_CafWriter` + `STEPCAFControl_Reader`: STEP→GLB direkt. `BRepMesh_IncrementalMesh` tesselliert vor Export. OCP (cadquery's Bindings, NICHT `OCC.Core`), statische Methoden mit `_s`-Suffix. ### 2026-03-06 | Celery | generate_gltf_geometry_task als Subprocess OCP + bpy können nicht im selben Python-Prozess koexistieren (native C++ Konflikt). **Lösung:** `export_step_to_gltf.py` via `subprocess.run([sys.executable, script, ...], timeout=120)` — OCC läuft isoliert. ### 2026-03-06 | OCC | Dihedralwinkel für sharp-edge Extraktion aus STEP `topexp.MapShapesAndAncestors(shape, TopAbs_EDGE, TopAbs_FACE, map)` → Face-Paare pro Edge. `BRepAdaptor_Surface.DN()` für Normalen. Medianwinkel → `suggested_smooth_angle`, Midpunkte → `sharp_edge_midpoints` für KD-Tree in Blender. Nur im render-worker verfügbar. ### 2026-03-06 | Blender | UV-Seams aus sharp edges ableiten Nach `edges_select_sharp(sharpness=radians(angle))` → `mark_sharp()` → `mark_seam(clear=False)`. Optional: bmesh KD-Tree für OCC-Midpoints (Toleranz 0.5mm vor scale(0.001)). ### 2026-03-06 | Multi-Tenancy | audit_log.tenant_id NOT NULL — Broadcast-Events `emit_notification` setzt kein `tenant_id` → INSERT fehlschlägt → 500 bei Order-Submit. **Lösung:** `tenant_id DROP NOT NULL`. Broadcast-Notifications (kein konkreter Tenant) brauchen nullable tenant_id. ### 2026-03-06 | Bbox | GLB statt STL für Bounding-Box Extraktion `_bbox_from_stl()` obsolet nach Pipeline-Umbau. **Lösung:** `_bbox_from_glb()` mit trimesh: `scene.bounds * 1000` für mm. Fallback auf `_bbox_from_step_cadquery()`. ### 2026-03-06 | Workflow | Turntable-Workflow brauchte step_path zur Laufzeit `WorkflowDefinition.config` ist statisch → kein `step_path` → `ValueError`. **Lösung:** `dispatch_render_with_workflow()` löst `step_path` + `output_dir` aus `OrderLine→Product→CadFile` auf und injiziert sie vor Dispatch. Statische (engine, samples) von laufzeit-abhängigen (Pfade, IDs) trennen. ### 2026-03-06 | Blender | Asset Library link=True — Assets müssen markiert sein `bpy.data.libraries.load(blend_path, link=True, assets_only=True)` liefert nur via "Mark as Asset" markierte Datenblöcke. **Lösung:** In .blend: jedes Material/Node-Group via "Mark as Asset" markieren. `catalog_assets.py` filtert via `m.asset_data is not None`. ### 2026-03-06 | Celery Inspect | active_queues() zum Worker-Capability-Check `celery_app.control.inspect().active_queues()` zeigt pro Worker welche Queues er konsumiert. Besser als Worker-Namen-Heuristiken für `render_worker_connected`/`blender_available`-Detection. ### 2026-03-06 | TypeScript | Test-Dateien aus Haupt-tsconfig ausschließen `vitest`/`msw`-Imports in `src/__tests__/` → TypeScript-Fehler in `tsc --noEmit` (Types nur im Test-Kontext). **Lösung:** `"exclude": ["src/__tests__"]` in `tsconfig.json`. Vitest prüft eigene Typen intern. ### 2026-03-06 | ffmpeg | Turntable hängt ohne `shortest=1` `lavfi color`-Stream hat unendliche Dauer → ffmpeg wartet nach PNG-Sequenz-Ende unbegrenzt. **Lösung:** `overlay=0:0:shortest=1`. Bei ffmpeg-Overlays mit lavfi/nullsrc als Input IMMER `shortest=1`. ### 2026-03-06 | Architektur | WebSocket Auth via Query-Parameter (JWT) Browser-WebSocket-API kann keine `Authorization`-Header senden. **Lösung:** JWT als Query-Parameter: `ws://host/api/ws?token=`. Token ist in Logs sichtbar — für v3: kurzlebigen WS-Token (TTL 30s) generieren. ### 2026-03-07 | Security | Media-Endpoints ohne Auth `list_assets`, `download_asset`, `zip_download` ohne `get_current_user`-Dep → unauthentifizierte Requests. RLS schützt nur DB, nicht HTTP. **Lösung:** `_user: User = Depends(get_current_user)` zu allen drei. Jeder neue Endpoint braucht expliziten Auth-Dep — RLS ist Defense-in-Depth, kein Ersatz. ### 2026-03-07 | MediaAsset | `is_animation` Flag entscheidet Asset-Type — falsches Design `is_animation == True` setzte `asset_type = turntable` auch für `.jpg` Poster-Frames → Broken-Video-Icons. **Lösung:** Extension entscheidet: `.mp4`/`.webm` → `turntable`, alles andere → `still`. MIME-Typ/Extension immer als primäre Typ-Quelle. ### 2026-03-07 | OCC | Bounding-Box aus STEP mit `Bnd_Box` + `brepbndlib.Add()` ```python bbox = Bnd_Box(); brepbndlib.Add(shape, bbox) xmin,ymin,zmin,xmax,ymax,zmax = bbox.Get() ``` Werte in mm (STEP-Einheit). In `mesh_attributes` JSONB speichern — kein neues DB-Feld nötig. ### 2026-03-07 | Storage | `storage_key` absolute Pfade brachen Volume-Moves Absolute Pfade in `storage_key` → nach Volume-Umzug 398 Assets nicht erreichbar. **Lösung:** `_normalize_key()` strippt `UPLOAD_DIR`-Prefix. Legacy-Remapping als Fallback. Neue Assets immer relativ: `renders/{uuid}/{filename}`. ### 2026-03-07 | SQLAlchemy Async | db.refresh() lädt keine Relationships `await db.refresh(invoice)` lädt nur skalare Spalten → `invoice.lines` → lazy-load außerhalb Greenlet → `MissingGreenlet`, HTTP 500. **Lösung:** Nach `db.commit()` separate select-Query mit `selectinload` statt `db.refresh()`. ### 2026-03-07 | Frontend Auth | Bearer-Token bei direktem Link-Download fehlt `` sendet keinen Authorization-Header → 401. **Lösung:** `api.get(..., { responseType: 'blob' })` → `URL.createObjectURL()` + programmatischer `.click()`. Gilt für alle geschützten Download-Endpoints. ### 2026-03-07 | PostgreSQL RLS | SET LOCAL muss in jeder Transaktion gesetzt werden `GRANT BYPASSRLS TO schaeffler` schlug still fehl → Admin-Endpoints bekamen 0 Zeilen. **Lösung:** `await db.execute(text("SET LOCAL app.current_tenant_id = 'bypass'"))` direkt vor jede RLS-geschützte Query in internen/Admin-Endpoints. ### 2026-03-07 | trimesh | GLB-Export-Scale: STL in mm → Three.js in Metern `mesh.apply_scale(0.001)` nach `trimesh.load()` vor Export. Bei `trimesh.Scene` über `scene.geometry.values()` iterieren. Optional: `trimesh.smoothing.filter_laplacian(mesh, lamb=0.5, iterations=5)` für smooth normals. ### 2026-03-07 | React Dashboard | Responsive CSS-Grid mit matchMedia Inline-Styles für Grid-Positioning haben keine Medienabfrage-Unterstützung. **Lösung:** `useLargeScreen()` Hook mit `window.matchMedia('(min-width: 1024px)')` + Change-Listener → `isLarge`-Boolean bedingt Inline-Styles. ### 2026-03-07 | Media Browser | ZIP-Download 22-Byte-Korruption `storage_key` absolute Pfade + `except Exception: pass` → Silent-Fail → leere Archive. **Lösung:** Pfad-Check vor MinIO-Fallback; `except` loggt jetzt `logger.warning()`. In Generator-Funktionen für Streaming IMMER loggen. ### 2026-03-07 | Frontend | Fehlende React-Imports crashen die gesamte App `useEffect` hinzugefügt ohne Import → `ReferenceError` → Blank Page. ErrorBoundary auf Root-Level fängt nicht ab. **Regel:** Nach jedem neuen Hook sofort Import-Zeile prüfen. `tsc --noEmit` als Quality Gate im Container. ### 2026-03-07 | Storage Keys | Absolute Pfade in DB brechen nach Infrastruktur-Änderung Flamenco-Outputs in `/shared/renders/...` → nach Flamenco-Entfernung 396 Assets nicht erreichbar (absoluter Pfad + MinIO-Fallback beide leer). **Lösung:** Bulk-UPDATE auf relative Keys + Safety-Net-Remapping im Code. `storage_key` IMMER relativ zu `UPLOAD_DIR`: `renders/{uuid}/{filename}`. ### 2026-03-07 | Config | Pydantic Settings: Attributname case-sensitive `settings.UPLOAD_DIR` → `AttributeError`. Korrekt: `settings.upload_dir` (lowercase wie in config.py). **Smoke-Test:** `docker compose exec backend python -c "from app.config import settings; print(settings.upload_dir)"`. ### 2026-03-07 | Media ZIP | MIME-Type-basierte Extension → ".bin" statt ".png" `(mime_type or "").split("/")[-1]` → `"bin"` für Assets ohne MIME-Type. **Lösung:** Extension primär aus `Path(storage_key).suffix` lesen. MIME-Type nur als Fallback. Duplikat-Filenames mit `_1`, `_2` deduplizieren. ### 2026-03-07 | Blender 5.0 | `export_colors` in bpy.ops.export_scene.gltf entfernt `export_colors=False` → `keyword unrecognized` → exit 1 → immer Trimesh-Fallback, nie Materialien. **Lösung:** Parameter entfernen. Gültige Blender-5.0-Parameter: `export_format`, `export_apply`, `use_selection`, `export_materials`, `export_image_format`. Nach Blender-Major-Updates alle bpy.ops.*-Parameter prüfen. ### 2026-03-07 | React | useRef mit if(!ref.current) Guard reagiert nicht auf Prop-Änderungen `GlbModel` klonte `scene` in `useRef` einmalig → bei neuem `url`-Prop blieb altes Mesh. **Lösung:** `key={glbBlobUrl}` auf `` → React remountet, frischer useRef. Alternativ: `useMemo` mit URL-Dependency. ### 2026-03-07 | React | staleTime zu hoch verzögert Erkennung neuer API-Daten `staleTime: 30_000` + `qc.invalidateQueries()` → trotzdem 30s Verzögerung bis neue GLB-URL ankam. **Lösung:** `staleTime: 0` für Queries die nach Mutations sofort aktuell sein müssen. ### 2026-03-07 | GLB Export | Trimesh kennt keine Materialien — Blender-Pipeline ist Pflicht Trimesh = reine Geometrie, keine Material-Libraries, kein Asset-Library-Support → graue, facettierte GLBs. **Lösung:** `generate_gltf_geometry_task` auf Blender headless (`export_gltf.py`) umgestellt. Trimesh nur als Fallback. Skript vorhanden ≠ Skript verdrahtet — Aufruf-Pfad immer prüfen. ### 2026-03-07 | Frontend | `` kann keine Auth-Header senden — useAuthBlob Hook nötig `` → kein Authorization-Header → 401 → graues Icon. **Lösung:** `useAuthBlob(url, enabled)`: `fetch(url, { headers: { Authorization } })` → `URL.createObjectURL(blob)` als src. Cleanup via `revokeObjectURL` + `cancelled`-Flag. Gilt für alle browser-nativen Media-Elemente (``, `