Replace trimesh-only GLB export with Blender headless pipeline using
export_gltf.py. The viewer GLB and downloadable GLB now receive:
- Material substitution via the Schaeffler asset library (.blend)
- OCC-derived sharp edge data (sharp_edge_midpoints from mesh_attributes)
marked as Blender sharp edges before export
Material map is resolved via resolve_material_map() to convert aliases
(e.g. "Steel--Stahl" → "SCHAEFFLER_010101_Steel-Bare") before passing
to Blender. Old gltf_geometry MediaAsset is replaced on each run to
avoid stale records accumulating.
Trimesh kept as fallback if Blender binary unavailable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug A: Media Library thumbnails were gray because <img src> cannot send
JWT auth headers. Added useAuthBlob() hook (fetch + createObjectURL) in
MediaBrowser.tsx. Also fixed publish_asset Celery task to populate
product_id + cad_file_id on MediaAsset for thumbnail fallback resolution.
Bug B: Product dimensions now shown in Product Details card with Ruler
icon and "from CAD" label when cad_mesh_attributes.dimensions_mm exists.
Bug C: Replaced 128×128 CAD thumbnail with InlineCadViewer component.
Queries gltf_geometry MediaAssets, fetches GLB via auth fetch → blob URL
→ Three.js Canvas with OrbitControls. Falls back to thumbnail + "Load 3D
Model" button. Polling when GLB generation is in progress.
Bug D: trimesh was in [cad] optional extra but Dockerfile only installed
[dev]. Changed to pip install -e ".[dev,cad]" — trimesh now available in
backend container, GLB + Colors export works.
Also added bbox extraction (STL-first numpy parsing) in render_step_thumbnail
and admin "Re-extract CAD Metadata" bulk endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove max-w-* constraints from all data/table pages so content fills available width after sidebar (Billing, MediaBrowser, OrderDetail, WorkerManagement, WorkerActivity, Materials, Tenants, AssetLibrary, NewProductOrder, ProductDetail)
- Narrow form/settings pages keep their max-width (NotificationSettings, Preferences, NewOrder, Notifications)
- render_order_line_task: create MediaAsset record on render success so results immediately appear in Media Browser without requiring the retroactive import button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- QueueStatusWidget + WorkerStatusWidget expected res.data to be ActivityEntry[]
but /api/worker/activity returns {cad_processing: [...], render_jobs: [...]}
→ TypeError: entries.filter is not a function → blank screen (no error boundary)
- Both widgets now use ActivityResponse interface and read data?.cad_processing
- Field names updated: id→cad_file_id, filename→original_name, status→processing_status
- AdminDashboard: fix duplicate React key in top_products table (pim_id can repeat)
→ use index suffix to guarantee unique keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the cache_service was only used in the generate_stl_cache Celery task.
All render paths (render_still, render_turntable_to_file, render_turntable_task)
only checked for a local file and converted from scratch if missing.
Changes:
- render_blender.py: add _stl_from_cache_or_convert() helper that checks MinIO
cache before falling back to local STEP→STL conversion. Wire into render_still()
and render_turntable_to_file() (both STL conversion blocks).
- domains/rendering/tasks.py: wire MinIO cache check into render_turntable_task()
inline before convert_step_to_stl(). All errors are non-fatal (falls back to
fresh conversion).
Now a STEP file converted on one worker is available to all workers via MinIO,
avoiding redundant cadquery conversions on re-renders.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
storage.upload() expects Path, not str — str has no .name attribute,
causing 'str object has no attribute name' warnings on every STL cache write.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(tasks): @shared_task → @celery_app.task so task uses configured redis://redis broker
(@shared_task without explicit app bound to default app → localhost:6379 → Connection refused)
- fix(tasks): import app.models before DB access so SQLAlchemy can resolve Material.creator → User
relationship (was: InvalidRequestError: 'User' failed to locate a name)
Verified: catalog refresh now finds and stores 35 materials from uploaded .blend file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(tasks): use RENDER_SCRIPTS_DIR env var for catalog_assets.py path
(was computing wrong path via __file__ parents → /render-worker/scripts/ which doesn't exist in container)
- fix(models): add AssetLibrary to app/models/__init__.py so alembic autogenerate discovers it
- fix(api): remove unused FileResponse import from asset_libraries.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Single chronological list sorted by updated_at (newest first)
- Type badges distinguish CAD processing (FileCode2) from Render jobs (Image)
- Render job rows now link directly to the order (/orders/:id)
- Remove separate "Render Jobs" and "CAD File Processing" sections
- Stat cards simplified to 4: CAD active/failed + Render active/failed
- Backend: add order_id to RenderJobEntry response
- Frontend: add order_id to RenderJobEntry interface, remove flamenco_job_id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change list/create route paths from '/' to '' on workflow_router so
GET /api/workflows and POST /api/workflows respond directly without
307 redirect. The redirect was causing axios to drop the Authorization
header, making the WorkflowEditor always show an empty list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docker-compose.yml: change render-worker build context from ./render-worker
to . (project root) so pyproject.toml is accessible; update dockerfile path
- render-worker/Dockerfile: update COPY paths for new build context;
install Python 3.11 via deadsnakes PPA (Ubuntu 22.04 ships 3.10 which
fails the >=3.11 requirement in pyproject.toml)
- 040_media_assets.py: rewrite upgrade() with raw idempotent SQL (CREATE TYPE
inside DO $$ EXCEPTION WHEN duplicate_object $$; CREATE TABLE IF NOT EXISTS;
CREATE INDEX IF NOT EXISTS) to handle pre-existing enum from partial runs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PLAN.md: Phasen A-E als ✅ ABGESCHLOSSEN markiert, Status auf Phase F.
LEARNINGS.md: 4 neue Learnings:
- Bash CWD-Problem durch Hook-Pfad-Auflösung (Symlink-Fix)
- PostgreSQL RLS current_setting Null-Safety + Admin-Bypass-Pattern
- Domain-Migration mit Compat-Shims (Big-Bang vermeiden)
- Celery Canvas vs. Custom Workflow-Engine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration 039: cad_files.mesh_attributes JSONB column.
domains/products/tasks.py: extract_mesh_attributes Celery task using pythonOCC.
still_render.py + turntable_render.py: _apply_mesh_attributes() sets auto-smooth
based on curved_ratio and topology threshold from OCC analysis.
render_blender.py: passes --mesh-attributes JSON arg to Blender subprocess.
render_still_task: loads mesh_attributes from DB and passes to renderer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
frontend/src/pages/WorkflowEditor.tsx: full React Flow editor with
custom nodes (Input/Convert/Render/FFmpeg/Output), config sidepanel,
node palette with drag-drop, new workflow dialog.
frontend/src/api/workflows.ts: workflow CRUD API client.
@xyflow/react added to package.json dependencies.
Route /workflows + sidebar link for admin+pm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads schaeffler_tenant_id from localStorage and injects it as
X-Tenant-ID header for all API requests (admin cross-tenant view).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration 035: tenants table with 'Schaeffler' default seed.
Migration 036: tenant_id FK on all tables, RLS policies, backfill.
New domains/tenants/ with CRUD router (admin only).
All domain models extended with tenant_id FK.
core/database.py: get_db_for_tenant with RLS context setter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move all models/schemas/services/routers into app/domains/.
Keep backward-compat shims in old locations for imports.
Preserves domains/rendering/tasks.py from Phase A.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PLAN.md: Phase A alle Tasks als ✅ markiert, Status IN UMSETZUNG
- LEARNINGS.md: 2 neue Learnings
- .gitignore 'core' Regel trifft Verzeichnisse (Root-relative Fix)
- Blender HTTP-Service vs. direkter Subprocess (render-worker Pattern)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>