Old frame PNGs from previous render attempts persisted in the frames
directory, causing FFmpeg to stitch the wrong number of frames.
Now rmtree's the directory before creating it fresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changes per user feedback:
- Keyframe interpolation: BEZIER → LINEAR (all fcurves set to LINEAR)
- Removed segment 4 (closeup) — now 3 segments only
- Frame count: 480 → 250 (10 seconds at 25fps)
- FPS: 24 → 25
- Easing removed — pure linear interpolation between segment params
- White background by default (World node Color = white)
- Transparent bg still available as override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes for frame progress not appearing in frontend:
1. Added flush=True to all print() calls in cinematic_render.py
2. Set PYTHONUNBUFFERED=1 in subprocess environment
Without these, Python buffers stdout inside Blender, so all frame
progress lines arrive in a batch after the process exits instead
of streaming line-by-line during rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The filter only matched [cinematic_render] tag but Blender outputs its
own "Saved: frame_NNNN.png" lines per frame. Now also matches "Saved:"
lines, extracts the frame number, and formats as:
[cinematic_render] Frame 84/480 rendered
This shows live frame progress in the LiveRenderLog on the frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced communicate() (blocking) with selectors-based line-by-line
stdout streaming — same pattern as still render. Each frame now
streams live to the frontend:
[cinematic_render] Frame 42/480 -- 55.3s elapsed (0.76 fps)
Pipeline: Blender stdout → log_callback → emit() → Redis →
LiveRenderLog poll (2s) → frontend display
Also added log_callback parameter to cinematic render task call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The service was sending (glb, frames_dir, width, height, engine, samples...)
but the script expected turntable order (glb, frames_dir, frame_count, degrees,
width, height, engine, samples...). Fixed by adding frame_count and degrees
placeholders to match the expected positional layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System prompt rewritten with explicit RULES:
- Never ask user for info you can query yourself
- "Any product" / "beliebig" → just pick one, don't ask back
- Execute immediately, no confirmation needed
- Be concise, short answers preferred
find_product_renders tool:
- No longer requires product_name or product_id
- Call with empty params → returns any recent renders
- Enables "show me any render with transparent bg"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added find_product_renders tool to the AI agent:
- Searches completed renders by product name/ID
- Returns viewable image URLs (relative paths)
- Supports transparent_only filter
- AI formats results as Markdown images/links in chat
Frontend:
- ChatPanel ReactMarkdown renders <img> and <a> tags
- Images shown inline (max 200px height, rounded, bordered)
- Links open in new tab with accent color
System prompt updated to instruct AI to use Markdown image syntax
when showing renders: 
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fixed: from OCP.BRepGProp import BRepGProp as brepgprop (was lowercase)
- Routed reextract_rich_metadata_task to asset_pipeline queue (render-worker
has OCC/OCP installed, worker container does not)
- Backfill verified: 45/45 products updated with volume, surface area,
part count, complexity metrics
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- All httpx tool calls use real user_id instead of uuid(int=0) for
service token — fixes 401 Unauthorized on dispatch/override
- create_order auto-submits and auto-dispatches in one step
- System prompt updated: execute immediately without asking for
confirmation, respond in user's language
- Product search returns CAD dimensions (dim_x/y/z_mm)
- query_database tool description includes cad_files schema
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- search_products now returns dim_x_mm, dim_y_mm, dim_z_mm from
cad_files.mesh_attributes->'dimensions_mm'
- query_database tool description updated with cad_files schema
including mesh_attributes->'dimensions_mm' path
- AI can now answer "what's the biggest product?" using real dimensions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Frontend: Layout extracts order/product UUID from URL path and passes
to ChatPanel as contextType/contextId. When on /orders/{uuid}, the
chat knows which order you're viewing.
Backend: System prompt now loads actual entity data for the context:
- Order: order_number, status, line counts (completed/processing/failed)
- Product: name, PIM-ID, category, STEP file status
The AI understands "this order", "current product" etc. Example:
"What's the status of this order?" → agent knows you mean SA-2026-00164
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a tool like query_database fails (e.g., bad column name), the
SQLAlchemy session enters a failed transaction state. Subsequent
operations (like saving the assistant response) then also fail with
InFailedSQLTransactionError.
Fix: rollback the session in the except block of _execute_tool().
Also improved query_database tool description with correct column
names (category_key not category) to help the AI write valid SQL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Actionable AI assistant that uses per-tenant Azure OpenAI credentials
to execute natural language commands against the render pipeline.
Backend:
- ChatMessage model + migration (session-based conversations)
- Chat service with 10 OpenAI function-calling tools:
list_orders, search_products, create_order, dispatch_renders,
get_order_status, set_material_override, set_render_overrides,
get_render_stats, check_materials, query_database
- All tools tenant-scoped (queries filtered by tenant_id)
- Write operations use httpx to call backend API internally
- Chat API: POST /chat/messages, GET /chat/sessions, DELETE session
- Conversation history preserved in DB (last 50 messages per session)
Frontend:
- Slide-out ChatPanel (right side, w-96, animated)
- User/assistant message styling with avatars and timestamps
- Session management (new chat, session history, delete)
- Typing indicator while waiting for AI response
- Floating chat button in bottom-right corner
- Error state for unconfigured AI tenants
Example: "Render all Kugellager products as WebP at 1024x1024"
→ Agent calls search_products + create_order + dispatch_renders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Task 4: Dual render queue
- render-worker: heavy (asset_pipeline, concurrency=1) — HQ 2048x2048, animations
- render-worker-light: light (asset_pipeline_light, concurrency=2) — thumbnails, <=1024
- Thumbnails routed to light queue automatically
- Order line renders routed by resolution at dispatch time
Task 5: GLB caching (skip re-tessellation)
- Before tessellating, check if gltf_geometry MediaAsset exists for the cad_file_id
- If found, copy to expected path — render_blender.py finds it and skips tessellation
- Saves 7-11s per re-render of the same product
Task 6: WebP output format
- New 'webp' option in output_format (OutputType admin)
- Blender renders PNG intermediate, Pillow converts to WebP (quality=90, method=4)
- 50-70% smaller files with no visible quality loss
- Correct MIME type (image/webp) in MediaAsset
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The initial implementation only overrode the material_map dict in the task,
but the Blender USD primvar path bypassed it. Now:
- Added --material-override named CLI arg parsed in _blender_args.py
- Both Mode A (factory) and Mode B (template) in _blender_scene_setup.py
override usd_material_lookup and material_map when set
- Passed through full chain: task → step_processor → render_blender → CLI → Blender
- Tested: 175-part bearing rendered with single Steel-Bare material (1/1 materials)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Excel preview detects when a product already has a different STEP file linked
- Excel preview detects intra-Excel conflicts (same product, different CAD model names)
- Product STEP upload warns when replacing an existing file and shows render count
- All warnings are non-blocking (amber badges, toast warnings)
- LEARNINGS.md: all open items resolved
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Per-render-position focal_length_mm/sensor_width_mm (DB → pipeline → Blender)
- FOV-based camera distance with min clamp fix for wide-angle lenses
- Unmapped materials blocking dialog on "Dispatch Renders" with batch alias creation
- Material check endpoint (GET /orders/{id}/check-materials)
- Batch alias endpoint (POST /materials/batch-aliases)
- Quick-map "No alias" badges on Materials page
- Full product hard-delete with storage cleanup (MinIO + disk files + orphaned CadFile)
- Delete button on ProductDetail page with confirmation
- Clickable product names in Media Browser (links to product page)
- Single-line render dispatch/retry (POST /orders/{id}/lines/{id}/dispatch-render)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The queue handles far more than thumbnails: OCC tessellation, USD master
generation, GLB production, order line renders, and workflow renders.
asset_pipeline better reflects its role as the render-worker's primary queue.
Updated all references in: task decorators, celery_app.py, beat_tasks.py,
docker-compose.yml worker command, worker.py MONITORED_QUEUES, admin.py,
CLAUDE.md, LEARNINGS.md, Dockerfile, helpTexts.ts, test files,
and all .claude/commands/*.md skill files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Per-tenant Azure AI config stored in tenants.tenant_config JSONB
- GET/PUT /api/tenants/{id}/ai-config + POST .../test connection
- api_key never returned to frontend (has_api_key: bool pattern)
- azure_ai.py resolves creds from tenant config when ai_enabled=True
- ai_tasks.py loads tenant config and passes it to validate_thumbnail
- Admin GPU Status section: probe button + status badge + last-checked time
- Notifications: _BELL_CHANNELS filter (notification+alert only in bell)
- Tenants.tsx: per-row Azure AI Config modal with URL auto-parse helper
- Remove duplicate in-memory /gpu-probe endpoints (kept DB-backed /probe/gpu)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- gpu_probe.py: Blender script that probes OPTIX/CUDA/HIP/ONEAPI and
exits 1 on no GPU — used at startup + on-demand from Admin UI
- blender_render.py, still_render.py, turntable_render.py: emit
RENDER_DEVICE_USED: engine=CYCLES device=GPU|CPU compute_type=...
after GPU activation; exit 2 when CYCLES_DEVICE=gpu and CPU fallback
- render_blender.py: parse RENDER_DEVICE_USED token into render_log
(device_used, compute_type, gpu_fallback); handle exit code 2 as
explicit GPU strict-mode failure
- check_version.py: check_gpu() runs gpu_probe.py at container startup;
CYCLES_DEVICE=gpu aborts startup if no GPU found
- docker-compose.yml: CYCLES_DEVICE=${CYCLES_DEVICE:-auto} env var
- gpu_tasks.py: probe_gpu Celery task on thumbnail_rendering queue;
saves result to system_settings.gpu_probe_last_result; beat every 30min
- worker.py: POST /probe/gpu (trigger) + GET /probe/gpu/result (last result)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 5.1 — MATERIAL_PALETTE removal:
- Remove MATERIAL_PALETTE + _material_to_color() from step_processor.py
- build_part_colors() now returns {part→material_name} for Blender resolver
Phase 6 — Notification Center Refactor:
- Migration 051: add channel (activity|notification|alert) to audit_log,
add frequency (immediate|daily|never) to notification_configs
- Three notification channels: activity (per-render), notification (batch
order summaries), alert (admin infrastructure)
- Per-render emit_notification_sync calls demoted to channel=activity
- New emit_batch_render_notification_sync(): single summary notification
when all order lines reach terminal state ("47/50 succeeded, 3 failed")
- Beat task batch_render_notifications every 60s: safety-net for missed
batch notifications after order completion
- GET /notifications: defaults to channel IN (notification, alert);
accepts ?channel=activity for activity feed
- Unread count badge counts only notification+alert channels
- Notifications.tsx: three tabs (Notifications | Activity | Alerts)
- NotificationSettings.tsx: frequency dropdown per event type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GPU: fix Cycles device activation order — set compute_device_type
BEFORE engine init, re-set AFTER open_mainfile wipes preferences
- GPU: remove _mark_sharp_and_seams edit-mode loop (redundant with
Blender 5.0 shade_smooth_by_angle), saves ~200s/render on 175 parts
- Material: fix _AFN suffix mismatch — build AF-stripped mat_map keys
and add prefix fallback in _apply_material_library (blender_render.py)
- Material: production GLB now uses get_material_library_path() which
checks active AssetLibrary instead of empty legacy system setting
- Admin: RenderTemplateTable multi-select output types (M2M frontend)
- Admin: MaterialLibraryPanel replaced with link to Asset Libraries
- UX: move Toaster to top-left to avoid dispatch button overlap
- SQLAlchemy: add .unique() to all RenderTemplate M2M collection queries
- Logging: flush=True on all Blender progress prints, stdout reconfigure
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>
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>
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>
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>