dbc032ec74
- Per-solid iteration prevents OOM on multi-part assemblies (25-part bearing: 2.3GB RAM when processing compound → ~100MB per solid with per-solid approach) - Fix CharacteristicLengthMax multiplier 5× → 15× and cap MinimumCirclePoints at 20 (prevents 63-pts/circle on angular_deflection=0.1rad → 231MB → 21MB) - Geometry task timeout 120s → 600s for large assemblies - Production task: reuse _geometry.glb when GMSH enabled (no re-tessellation) and cache _production_geom.glb for OCC (mtime vs STEP check) - Viewer now prefers production GLB when available (shows correct GMSH mesh) - GMSH OpenMP multithreading (min(cpu_count,16)) for 4.4× speedup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
448 lines
38 KiB
Markdown
448 lines
38 KiB
Markdown
# Projekt-Learnings — Schaeffler Automat
|
||
|
||
## Format
|
||
**Datum | Kategorie | Problem → Lösung**
|
||
|
||
---
|
||
|
||
## Learnings
|
||
|
||
### 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` (thumbnail_rendering, 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 `thumbnail_rendering`, 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 `thumbnail_rendering` → `render-worker`-Container (Blender 5.0.1 + cadquery). Blender-Tasks IMMER auf `thumbnail_rendering`.
|
||
|
||
### 2026-03-06 | Docker | worker-thumbnail vs render-worker — beide auf thumbnail_rendering
|
||
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=<jwt>`. 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
|
||
`<a href="/api/billing/invoices/{id}/pdf">` sendet keinen Authorization-Header → 401.
|
||
**Lösung:** `api.get(..., { responseType: 'blob' })` → `URL.createObjectURL()` + programmatischer `<a>.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 `<GlbModel>` → 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 | `<img src>` kann keine Auth-Header senden — useAuthBlob Hook nötig
|
||
`<img src="/api/media/{id}/download">` → 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 (`<img>`, `<video>`, `<audio>`).
|
||
|
||
### 2026-03-07 | Backend | publish_asset fehlte product_id + cad_file_id
|
||
`MediaAsset` ohne `product_id`/`cad_file_id` → `get_thumbnail_url()` konnte keinen Thumbnail-Fallback berechnen → graue Icons.
|
||
**Lösung:** In `publish_asset`: `Product` laden, `product_id=line.product_id` + `cad_file_id=product.cad_file_id` setzen. MediaAssets immer mit allen verfügbaren Referenz-FKs erstellen.
|
||
|
||
### 2026-03-07 | Frontend | Inline 3D Viewer — GLB mit Auth via Blob URL laden
|
||
`useGLTF(url)` aus `@react-three/drei` kann keine Auth-Header senden.
|
||
**Lösung:** `fetch(url, { headers: { Authorization } })` → `.blob()` → `URL.createObjectURL(blob)` → String-URL an `useGLTF(blobUrl)`. Revoke in useEffect-Cleanup. Three.js / drei kennen kein Auth-Konzept.
|
||
|
||
### 2026-03-07 | Backend | trimesh in optionalem [cad]-Extra — nicht im Docker-Build installiert
|
||
`pip install -e ".[dev]"` installiert kein `trimesh` → `ModuleNotFoundError` beim ersten Aufruf.
|
||
**Lösung:** `pip install -e ".[dev,cad]"`. Beim Hinzufügen optionaler Extras immer prüfen ob alle relevanten Container-Images das Extra installieren.
|
||
|
||
### 2026-03-07 | Frontend | URL.revokeObjectURL sofort nach click() → Race Condition
|
||
`revokeObjectURL(url)` synchron nach `a.click()` → Download manchmal leer (click() ist in manchen Browsern asynchron).
|
||
**Lösung:** `setTimeout(() => URL.revokeObjectURL(url), 100)` für alle programmatischen Blob-Downloads.
|
||
|
||
### 2026-03-07 | Media Import | Falsche asset_type-Klassifizierung durch Dateinamen-Matching
|
||
Dateiname enthielt "Turntable" → `asset_type=turntable` auch für `.jpg` Poster-Frames → Browser versuchte JPGs als `<video>` zu rendern.
|
||
**Lösung:** Daten-Fix: `UPDATE media_assets SET asset_type='still' WHERE asset_type='turntable' AND storage_key LIKE '%.jpg'`. Code-Fix: `isVideoAsset()` nutzt `mime_type` zusätzlich. Asset-Typ IMMER aus mime_type + Dateiendung, nie nur aus Dateiname.
|
||
|
||
### 2026-03-08 | Render | OptiX BVH cache ephemeral → first render slow after rebuild
|
||
Nach `docker compose up --build render-worker`: erstes Render 130–150s statt 22s. `/root/.nv/ComputeCache/` liegt im ephemeren Container-Dateisystem → wird bei jedem Rebuild geleert.
|
||
**Lösung:** Named volume: `optix-cache:/root/.nv` in docker-compose.yml. Detection: Render 1 >> Render 2+3 nach Rebuild → OptiX cold-start.
|
||
|
||
### 2026-03-10 | R3F | Invisible meshes: check `e.object.visible` in handlers causes regression
|
||
`mesh.visible = false` + Guard `if (!e.object.visible) return` in Event-Handlern blockiert alle Folge-Events — R3F's Event-Tracking feuert events mit `e.object` auf Meshes die nach dem Tracking unsichtbar wurden.
|
||
**Lösung:** `mesh.raycast = () => {}` wenn `mesh.visible = false`; restore mit `THREE.Mesh.prototype.raycast` beim Einblenden. Meshes vom Raycasting ausschließen an der Quelle, nicht im Handler.
|
||
**Regel:** Nie `e.object.visible` in R3F pointer/click-Handlern prüfen.
|
||
|
||
### 2026-03-10 | Three.js/GLTF | `mesh.name` wird durch sanitizeNodeName verändert — `mesh.userData.name` nutzen
|
||
Three.js `GLTFLoader.createUniqueName()` ruft `PropertyBinding.sanitizeNodeName()` auf, welches reservierte Zeichen `[]:./` **entfernt**. Blender-Dedup-Suffixe `.001`, `.012` etc. werden zu `001`, `012` — ohne Punkt.
|
||
Unser `normalizeMeshName` strippte `\.\d{3}$` (mit Punkt), aber der Punkt ist bereits weg → `GE360-HF-0051-EIN_1.012` → (Three.js sanitize) → `GE360-HF-0051-EIN_1012` → kein Match in partMaterials → fälschlich "unassigned".
|
||
**Fix:** `mesh.userData.name` verwenden statt `mesh.name`. Three.js speichert den Original-GLTF-Node-Namen (unsanitized, mit Punkten) in `node.userData.name = nodeDef.name` **bevor** `node.name = sanitize(...)` gesetzt wird. Pattern: `(mesh.userData?.name as string) || mesh.name`.
|
||
**Gilt für:** handleClick, handlePointerOver/Out, visibility/glow/color effects, onReady callback, glbMeshNames-Traversal in InlineCadViewer + ThreeDViewer.
|
||
|
||
### 2026-03-10 | OCC/GLTF | Assembly containers have no geometry → no mesh in GLB
|
||
`cad_part_materials` enthält Keys für Assembly-Nodes (`_ASM_1`, `_BASIS_ASM_1`), die in cadquery geparst werden, aber keine Geometrie haben → nicht im GLB. Debug-Log "matched: 4 of 9 stored keys" = 5 Keys sind Assembly-Container, kein Bug.
|
||
**Regel:** Für assigned/total-Zählung immer `glbMeshNames` (Set aus scene traversal) nutzen, nie `Object.keys(partMaterials).length`.
|
||
|
||
### 2026-03-10 | Debug-Workflow | GLB-Geometrie-Analyse direkt per Python — ohne Browser-DevTools
|
||
Bei Mesh-Name-Mismatch-Bugs: GLB-Datei direkt parsen statt im Browser debuggen. Spart viele Iterationen.
|
||
|
||
**Schritt 1 — GLB-Download-URL holen:**
|
||
```python
|
||
import urllib.request, json, struct
|
||
# Login
|
||
data = json.dumps({'email':'admin@schaeffler.com','password':'Admin1234!'}).encode()
|
||
req = urllib.request.Request('http://localhost:8888/api/auth/login', data=data, headers={'Content-Type':'application/json'})
|
||
token = json.load(urllib.request.urlopen(req))['access_token']
|
||
# Media-Assets für CAD-File
|
||
assets = json.load(urllib.request.urlopen(urllib.request.Request(
|
||
f'http://localhost:8888/api/media/?cad_file_id={CAD_FILE_ID}&asset_types=gltf_production&asset_types=gltf_geometry',
|
||
headers={'Authorization': f'Bearer {token}'})))
|
||
```
|
||
|
||
**Schritt 2 — GLB parsen und Mesh-/Node-Namen ausgeben:**
|
||
```python
|
||
req = urllib.request.Request(DOWNLOAD_URL, headers={'Authorization': f'Bearer {token}'})
|
||
glb = urllib.request.urlopen(req).read()
|
||
chunk_len, _ = struct.unpack('<II', glb[12:20])
|
||
gltf = json.loads(glb[20:20+chunk_len])
|
||
nodes, meshes = gltf['nodes'], gltf['meshes']
|
||
for n in nodes:
|
||
if n.get('mesh') is not None:
|
||
print(f"node={repr(n['name'])} mesh_def={repr(meshes[n['mesh']]['name'])}")
|
||
```
|
||
|
||
**Schritt 3 — normalizeMeshName + resolvePartMaterial in Python nachbauen und alle Namen durchlaufen:**
|
||
So lässt sich der vollständige Matching-Status (ASSIGNED/UNASSIGNED) offline prüfen, ohne den Browser zu öffnen. Ggf. gespeicherte `cad_part_materials` via `GET /api/cad/{id}/part-materials` dazuladen.
|
||
|
||
**Produkt-ID → CAD-File-ID:** `GET /api/products/{product_id}` → Feld `cad_file_id`.
|
||
**CAD-File-ID für Media-Assets:** `GET /api/media/?cad_file_id={id}&asset_types=gltf_production` (nutzt `listMediaAssets`, nicht `getMediaAssets`).
|
||
|
||
### 2026-03-11 | Render-Pipeline | Production GLB: Koordinaten OCC→Blender falsch
|
||
OCC STEP-Koordinaten (Z-up, mm) werden von `export_step_to_gltf.py` auf Y-up (glTF) rotiert, dann vom Blender-Importer wieder auf Z-up. Netto: `Blender(X, Y, Z) = OCC(X×0.001, -Z×0.001, Y×0.001)` — Y und Z tauschen plus Z-Negierung. Einfach mm→m skalieren (X=X, Y=Y, Z=Z) ist FALSCH.
|
||
**Lösung:** In allen `_apply_sharp_edges_from_occ()`-Funktionen: `v = Vector((p[0]*0.001, -p[2]*0.001, p[1]*0.001))`.
|
||
**Diagnose:** `DEBUG first_vert_local=[0.0, -0.1175, 0.26]` vs `occ_pair=[0.0, 0.26, 0.1175]` zeigte eindeutig Y↔Z-Swap + Z-Negierung.
|
||
|
||
### 2026-03-11 | Render-Pipeline | Production GLB: Mesh-Data-Sharing bei Material-Zuweisung
|
||
Nach GLB-Import teilen sich mehrere Blender-Objekte denselben Mesh-Datablock (z.B. Instanzen). `obj.data.materials.clear()` auf geteilten Daten löscht Materialien für ALLE Objekte, die diesen Mesh teilen.
|
||
**Lösung:** Vor `obj.data.materials.clear()` immer: `if obj.data.users > 1: obj.data = obj.data.copy()` (single-user machen).
|
||
|
||
### 2026-03-11 | Render-Pipeline | Production GLB: Name-Mismatch cadquery vs. RWGltf
|
||
cadquery-Parser und RWGltf_CafWriter lesen dieselbe STEP-Datei, erzeugen aber unterschiedliche Part-Namen (z.B. cadquery: `GE360-HF-0011-EIN_HAELFTE_AF0_1_AF0` vs. RWGltf: `GE360-HF-0021-EIN`). Material-Mapping via Namen deckt nur 2/25 Objekte ab.
|
||
**Lösung 1:** Prefix-Match: `key.startswith(lower_base)` oder `lower_base.startswith(key)`.
|
||
**Lösung 2:** Single-Material-Fallback: wenn `len(appended) == 1` (nur 1 Bibliotheks-Material geladen), alle ungematchten Objekte ebenfalls mit diesem Material belegen.
|
||
**Generell:** Part-Namen aus zwei verschiedenen OCC-Lese-Pfaden NIE direkt vergleichen.
|
||
|
||
### 2026-03-11 | Python/Blender | `id(bpy_object)` über Iterationen hinweg unzuverlässig
|
||
Blender-Objekte sind Python-Wrapper über C++-Objekte. Nach einer Schleife können gleiche C++-Objekte neue Python-Wrapper bekommen → `id()` ändert sich. Tracking mit `id(obj)` in einem Set und Prüfung in einer zweiten Schleife funktioniert NICHT.
|
||
**Lösung:** `obj.name` als stabiler Identifier (Blender-Namen sind innerhalb der Szene eindeutig und bleiben stabil).
|
||
|
||
### 2026-03-11 | Python/Blender | `bmesh.edges.get((v0, v1))` schlägt fehl bei idx0==idx1
|
||
Wenn KD-Tree für beide Endpunkte denselben Vertex findet (z.B. degenerate/zero-length edge), wirft `edges.get((v, v))` `ValueError: found the same (BMVert) used multiple times`.
|
||
**Lösung:** Guard: `if idx0 == idx1: continue` direkt nach dem `kd.find()` für Endpunkt 2.
|
||
|
||
### 2026-03-11 | Workflow | System-Daten direkt lesen — kein docker exec nötig
|
||
Für den `/plan`-Agent: alle relevanten System-Daten sind OHNE docker-Befehle zugänglich:
|
||
- **Datenbankmodelle:** `backend/app/models/` und `backend/app/domains/*/models.py` direkt lesen
|
||
- **API-Endpoints:** `backend/app/api/routers/` und `backend/app/domains/*/routers.py` lesen — kein curl/docker exec nötig
|
||
- **Celery-Tasks:** `backend/app/tasks/step_tasks.py` und `backend/app/domains/pipeline/tasks/` lesen
|
||
- **Render-Scripts:** `render-worker/scripts/` lesen — `blender_render.py`, `export_gltf.py` etc.
|
||
- **Frontend-API:** `frontend/src/api/*.ts` zeigt alle API-Calls und Typen
|
||
- **Gespeicherte Daten:** `CadFile.mesh_attributes`, `Product.cad_part_materials`, `SystemSetting` in Modellen beschrieben
|
||
- **Docker-Logs nur für Debugging:** `docker compose logs --since 5m render-worker 2>&1 | grep -E "pattern"` — sparsam einsetzen, nicht für Codeanalyse
|
||
- **Kein `docker exec backend python3 -c`** für Code-Exploration — dauert lang und braucht laufende Container
|
||
- **MEMORY.md** enthält Container-Capabilities: OCP nur in `render-worker`, nicht in `worker` oder `backend`
|
||
|
||
### 2026-03-11 | Render-Pipeline | Production GLB: edit-mode mark_sharp+mark_seam für korrekte GLB Sharp Edges
|
||
In Blender 5.0: `shade_smooth_by_angle()` allein reicht nicht für scharfe Kanten im GLB-Export.
|
||
**Lösung:** Edit-mode-Operators verwenden: `edges_select_sharp(sharpness=angle)` → `mark_sharp()` → `mark_seam()`.
|
||
Der glTF-Exporter erstellt an den als sharp markierten Edges Vertex-Splits (duplizierte Vertices mit verschiedenen Normalen).
|
||
**Verifizierung:** 543 sharp+seam Edges markiert → production GLB hat 812 extra Vertices + 6027 Positionen mit mehreren Normalen = scharfe Kanten korrekt enkodiert.
|
||
**Wichtig:** `calc_normals_split()` wurde in Blender 5.0 entfernt (→ AttributeError). Nicht mehr nötig: `export_apply=True` triggert Vertex-Splitting automatisch.
|
||
|
||
### 2026-03-11 | Render-Pipeline | MediaAsset DELETE+INSERT erzeugt neue UUID → 404 auf gecachte URLs
|
||
`generate_gltf_production_task` und `generate_gltf_geometry_task` löschten alten MediaAsset-Record und erstellten neuen mit neuer UUID.
|
||
Gecachte Download-URLs (z.B. im Frontend-State) zeigten auf die alte, gelöschte UUID → 404.
|
||
**Lösung:** UPSERT: existierenden Record aktualisieren (`existing.storage_key = _key`) statt DELETE+INSERT. UUID bleibt stabil.
|
||
|
||
### 2026-03-11 | Render-Pipeline | Production GLB: OCC custom_normal überschreibt Blender-Normalen
|
||
`export_step_to_gltf.py` (RWGltf_CafWriter) embedded per-corner normals from OCC tessellation as a `custom_normal` attribute (CORNER, INT16_2D) in the geometry GLB. Blender's glTF importer preserves this as a custom attribute. The glTF exporter then re-exports these pre-baked normals **unchanged**, ignoring `shade_smooth_by_angle` processing and explicit `edge.smooth=False` sharp marks — sharp edges are invisible in the production GLB.
|
||
**Lösung:** In `export_gltf.py` und `blender_render.py`: nach GLB-Import das `custom_normal`-Attribut von allen Mesh-Objekten entfernen, BEVOR `shade_smooth_by_angle()` aufgerufen wird. Dann berechnet Blender die Normalen neu aus den sharp-edge-Marks.
|
||
**Diagnose:** File-Size-Test: WITH custom_normal=1218960 Bytes, WITHOUT=1137944 Bytes. Re-import zeigte `has_custom_normals=True`. GLB-JSON-Inspektion mit `struct.unpack` + `json.loads` direkt auf `.glb`-Datei.
|
||
```python
|
||
for obj in mesh_objects:
|
||
if "custom_normal" in obj.data.attributes:
|
||
obj.data.attributes.remove(obj.data.attributes["custom_normal"])
|
||
```
|
||
|
||
### 2026-03-11 | Render-Pipeline | OCC B-rep sharp edges: BRep_Tool.Polygon3D_s() gibt None zurück
|
||
`BRep_Tool.Polygon3D_s(edge, loc)` und `PolygonOnTriangulation_s()` geben in XCAF-Compound-Kontext immer `None` zurück — Tessellation-Polygon-Daten liegen auf Component-Instanzen, nicht auf den Compound-Edges. Ergebnis: 612 scharfe Kanten erkannt, 0 Segment-Paare extrahiert.
|
||
**Lösung:** `GCPnts_UniformAbscissa(curve3d, step_mm=0.3, tol=1e-6)` auf der analytischen B-rep-Kurve (`BRepAdaptor_Curve`) samplen. 0.3mm-Schritt garantiert dass konsekutive Sample-Paare die Tessellations-Kanten (~0.78-1.55mm) straddeln — die KD-Tree-Suche (TOL=0.5mm) findet dann die richtigen Blender-Mesh-Edges. Ergebnis: 17.129 Segment-Paare, 1.364 Kanten in Blender markiert.
|
||
**Imports:** `from OCP.GCPnts import GCPnts_UniformAbscissa; from OCP.BRepAdaptor import BRepAdaptor_Curve`
|
||
|
||
---
|
||
|
||
## Offene Fragen
|
||
- [ ] Azure AI Credentials für Phase 4 (Bildvalidierung) noch nicht konfiguriert
|
||
- [ ] pythonOCC verfügbar im render-worker (via cadquery dependency)? Deployment-Test ausstehend
|
||
- [ ] @xyflow/react noch nicht installiert — npm install nötig nach nächstem `docker compose up --build frontend`
|
||
- [ ] Material-Alias-Seeding deckt noch nicht alle deutschen Materialbezeichnungs-Varianten ab
|
||
- [ ] Turntable-Animation: bg_color via FFmpeg-Overlay — Qualität bei Transparenz-Edges prüfen
|