Added product_material filter to find_product_renders:
- Searches cad_part_materials JSONB for materials assigned to CAD parts
- find_product_renders(transparent_only=true, product_material="Durotect")
→ finds 9 products that naturally have Durotect parts with transparent renders
Two material levels explained in system prompt rule 13:
- product_material: materials from STEP/Excel (Durotect_M, Stahl, Bronze)
- material_override: single material forced on ALL parts at render time
AI now searches product_material FIRST when user asks "with Durotect material"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rule 13: when searching with multiple criteria (transparent + Durotect),
decompose the search. Show what exists (1220 transparent renders) and
explain what's missing (no Durotect material applied). Never say
"no renders found" when transparent renders DO exist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
find_product_renders now reads actual render metadata from the
render_log JSONB (stored at render time) via COALESCE fallback:
- engine: render_log.engine_used > render_log.engine > output_type setting
- width/height: render_log > output_type.render_settings
- samples: render_log > output_type.render_settings
- render_type: still/turntable/cinematic from render_log.type
- has_template: whether a .blend template was used
All past renderings now have correct metadata without re-processing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
find_product_renders now returns full render job metadata:
- material_override (effective: line override > output type override)
- output_format (with render_overrides applied)
- resolution (width x height)
- engine, samples
- order_number + order_id (for linking)
- is_animation flag
New material_override filter: search renders by material name
(e.g. transparent_only=true, material_override="Durotect" finds
renders with Durotect material on transparent background)
AI can now answer: "Show me a transparent Durotect render" by filtering
both transparency AND material in one query.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explicitly listed all write tools that require confirmation:
create_order, dispatch_renders, set_material_override, set_render_overrides
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split rules into read-only vs write actions:
- READ (search, list, show images, status): execute immediately
- WRITE (create orders, dispatch, set overrides): ALWAYS ask for
confirmation before executing
Prevents accidental render job creation from casual questions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System prompt rule 12: always format product mentions as
[ProductName](/products/UUID) and orders as [OrderNumber](/orders/UUID).
ReactMarkdown in ChatPanel already renders these as clickable links
with accent color styling, so users can navigate directly from chat.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added list_materials tool to the chat agent:
- Searches SCHAEFFLER library materials by name, description, or alias
- Returns material name + schaeffler_code + aliases
- Enables: "zeig mir ein Bild mit Durotect-Material" → agent searches
for "durotect" → finds SCHAEFFLER_020101_Durotect-Blue → uses as
material_override
System prompt updated with rules 10-11:
- Explains alias → library material mapping
- Always use full SCHAEFFLER name for material_override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For 16:9 (1280x720), vertical FOV is narrower than horizontal —
products could be clipped top/bottom. Now applies an aspect ratio
correction factor to camera distance: wider formats push camera
further back proportionally.
Still render (_blender_camera.py) already handled this via
min(fov_h, fov_v); cinematic now has equivalent correction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Blender 5.0 renamed Action.fcurves. Now tries fcurves first, falls
back to channels, and wraps in try/except so the render proceeds
even if LINEAR interpolation can't be set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
The kanban board kept showing deleted orders until manual reload.
Added qc.invalidateQueries for 'orders' and qc.removeQueries for
the deleted order's detail cache on delete success.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Session ID saved to localStorage (schaeffler-chat-session).
On mount, restores the last session and loads messages from DB.
"New Chat" clears the stored session.
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>
Added react-markdown to render assistant messages with proper formatting:
- **bold** text renders as bold
- Bullet/numbered lists render correctly
- Inline code renders with monospace background
- User messages stay as plain text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Frontend interface expected 'message' and 'response' but API returns
'user_message' and 'assistant_message'. Field name mismatch caused
undefined access and page crash.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Catch all exceptions (not just ValueError) and return meaningful
error messages from OpenAI API errors instead of generic 500.
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>
Root cause: render-worker and render-worker-light shared the same GPU,
causing contention. Complex TRB renders went from 17s → 36s (2x slower).
Changes:
- Thumbnails back to asset_pipeline queue (not asset_pipeline_light)
- Dispatch routing always uses asset_pipeline (no queue splitting)
- render-worker-light gated behind "multi-gpu" profile — only starts with:
docker compose --profile multi-gpu up -d
- For single-GPU setups: all rendering is sequential on one worker
The dual queue approach is correct for multi-GPU machines where each
worker gets its own GPU. On single-GPU, serial execution is faster
than concurrent GPU contention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of duplicating output types for every variation (WebP vs PNG,
different resolution), keep one canonical output type and override
specific fields per order line via render_overrides JSONB.
Backend:
- render_overrides JSONB column on OrderLine (DB migration)
- Render task merges overrides with output type settings (format, width,
height, samples, engine, denoiser, transparent_bg, cycles_device)
- POST /orders/{id}/batch-render-overrides endpoint for bulk override
- PatchLineBody accepts render_overrides for per-line patching
Frontend:
- Batch render overrides section on OrderDetail: output format dropdown
(PNG/JPG/WebP) + resolution dropdown (512-4096)
- Clear button to remove overrides
MCP:
- create_order tool: accepts product_ids, output_type, render_overrides,
material_override — enables "render all products as WebP" via Claude
- set_render_overrides tool: batch override on existing orders
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 OptiX cache was mounted at /root/.nv but NVIDIA writes to
/var/tmp/OptixCache_root/optix7cache.db (28MB). Fixed volume mount.
Before: first render after container restart = 181s (OptiX recompilation)
After: first render after container restart = 20s (cached kernels)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added refetchInterval to the order query that polls every 3 seconds
while render_progress has pending or processing lines. Stops polling
automatically when all renders reach a terminal state (completed/failed/cancelled).
Fixes: render log and backend status not appearing until manual page reload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed from UI (saved to DB but never read by any service):
- Max Concurrent Renders, Stall Timeout, Thumbnail Format, Product Thumbnail Priority
- Render Linear/Angular Deflection (only Scene deflections are used)
- GLB Scale Factor, Smooth Normals, GLB Material Mode, PBR Roughness, PBR Metallic
Fixed Blender status check:
- Old: called is_blender_available() in backend container (Blender not installed there)
- New: dispatches Celery task on asset_pipeline queue → runs in render-worker container
- Returns: available=true, version="Blender 5.0.1", binary path
- Status card moved to System Tools tab with refresh button
Kept active: engine, device, samples, smooth angle, tessellation, scene deflections,
3D viewer zoom limits
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin tables (same pattern as OutputTypeTable):
- RenderTemplateTable: 11 cramped columns → expandable form row with grouped fields,
boolean flags consolidated into compact badges, .blend upload in proper section
- PricingTierTable: inline cell editing → expandable form with labeled fields,
shared renderEditFormGrid() for add/edit modes
- GlobalRenderPositionsPanel: tiny rotation inputs → expandable form with w-24 inputs,
proper labels, sensor_width_mm added to edit form
Page polish:
- WorkerManagement: larger scale controls (p-2 rounded-lg), wider number displays (w-12),
proper labels, more prominent Save button
- Billing: status select gets visible dropdown indicator (ChevronDown icon),
hover border to signal interactivity, larger action buttons with borders
- OrderDetail: batch override in proper card with title/description,
per-line override shows compact "+ override" link (expands on click),
active overrides show as amber badge with X to clear
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced the 18-column inline cell editing with an expandable detail row:
- Display row always visible (no more disappearing content on edit)
- Edit form opens BELOW in a full-width colSpan row with accent border
- Fields organized in a 4-column grid with proper labels:
Row 1: Name, Renderer, Format, Animation
Row 2: Turntable, Background, Device, Engine
Row 3: Samples, Resolution, Pricing, Workflow
Row 4: Categories, Material Override, Sort Order, Active
Row 5: Denoising settings (6 cols, Blender only)
- Shared renderEditFormGrid() helper for both edit and add modes
- Save/Cancel buttons at bottom of form
- React.Fragment wrappers for dual-row rendering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Preferences page had its own inline accent color section that wasn't
updated with the custom picker. Now shows:
- 5 preset swatches + separator + custom swatch with pipette icon
- Color picker + hex input appear when custom is selected
- Larger swatches (w-8 h-8) matching the page's spacious layout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added 'custom' accent option with color picker + hex input
- Pipette icon on the custom swatch (switches to solid when active)
- Color picker appears inline when custom is selected
- Generates hover/light variants automatically from hex (darken/lighten)
- Dark mode accent-light uses rgba for translucency
- Persisted in localStorage, applied before React hydration (no flash)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Widgets scale in with staggered delays (60ms per widget, up to 12)
- Widget hover: lift 3px + accent border glow (dark mode: accent shadow)
- Inner numbers animate up with count-up effect
- Progress bars grow from left with spring curve (800ms delay for content-first feel)
- All wrapped in prefers-reduced-motion guard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSS-layer-only changes for zero-regression visual modernization:
- Typography: Inter font with OpenType features (cv02-cv04, cv11), tighter letter-spacing
- Dark theme: neutral zinc tones (#09090b/#18181b) replacing blue-slate (#0f172a/#1e293b)
- Borders: rgba-based for subtlety instead of solid hex colors
- Cards: rounded-xl, refined shadows with dark mode variant
- Buttons: rounded-lg, subtle lift on hover, smoother transitions (150ms ease-out)
- Badges: rounded-md (softer than full-round), tracking-wide
- Inputs: rounded-lg, ring-offset-0 for tighter focus rings
- Scrollbars: thin 6px custom scrollbars matching theme
- Selection: accent-colored text selection
- Table headers: uppercase tracking-wider for modern data table look
- Utility: fadeIn animation, subtle-pulse for loading states
No component files changed — all styling propagates via CSS variables and Tailwind config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added 5 new tools:
- get_product_detail: full product with parts, materials, render history, media assets
- get_render_detail: complete render metadata for an order line (timing, engine, log)
- get_completed_renders: filterable list of completed renders with timing/paths
- get_failed_renders: recent failures with error messages
- get_media_assets: browse media assets by product/type
Total: 17 tools + 2 resources
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added "Mat Override" column to the review table
- Each line has its own dropdown (per-line takes priority over global)
- Default shows global override if set, otherwise "No override"
- "Clear" option to explicitly remove override on a line when global is set
- Amber background when override is active
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
create_order and add_order_line endpoints were not passing
material_override from the request body to the OrderLine constructor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dropdown in Step 3 review to set a single material override for all lines
- Override is passed to each OrderLine.material_override at creation time
- Amber background when override is active
- Library materials loaded on Step 3 entry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- POST /orders/{id}/batch-material-override endpoint
- Dropdown above the lines table: "Apply to all lines…"
- Options: clear all overrides, or select a library material
- Updates all order lines in one request
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add `material_override` nullable column on OrderLine (DB migration)
- Line override takes priority over OutputType override
- PATCH /orders/{id}/lines/{id} endpoint to update material_override
- Inline dropdown on each order line in the OrderDetail page
- Amber background when override is active
- Same output type, different material per line — no need to create a new output type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both camera-orbit and object-rotation modes now parse and apply
the --material-override CLI arg, overriding USD primvar lookups
and material_map before assignment — same pattern as still renders.
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>
- Add `material_override` nullable column on OutputType (DB migration)
- When set, ALL product parts get rendered with this single material
- Override applies after alias resolution in render_order_line task
- Admin UI: dropdown in OutputType table to select a library material
- Display: amber badge showing active override material name
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>
Root cause 1: BRepBuilderAPI_Transform(shape, trsf, copy=True) destroys
all Poly_Triangulation data. The mm→m scaling was applied before export,
wiping the tessellation from BRepMesh_IncrementalMesh.
Fix: Remove BRepBuilderAPI_Transform entirely — RWGltf_CafWriter already
handles mm→m conversion and Z-up→Y-up rotation internally.
Root cause 2: RWGltf_CafWriter with MergeFaces=False (the default) fails
to find per-face tessellation from the XCAF component hierarchy, producing
degenerate meshes (~2 vertices per face instead of thousands).
Fix: SetMergeFaces(True) to compose face triangulations into proper
per-shape mesh buffers. Vertex count goes from 1,212 to 46,573.
Also bumps cache key version to v2 to invalidate broken cached GLBs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds early-exit checks in dispatch_order_line_render and
render_order_line_task to prevent rendering when order lines are
cancelled or the parent order is rejected/completed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New endpoint POST /admin/settings/regenerate-all-canonical-scenes
queues GLB + USD master export for ALL completed CAD files, replacing
existing assets. Used after pipeline changes that affect tessellation
or normals.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- @timed_step decorator with wall-clock + RSS tracking (pipeline_logger)
- Blender timing laps for sharp edges and material assignment
- MeshRegistry pattern: eliminate 13 scene.traverse() calls across viewers
- Lazy material cloning (clone-on-first-write in both viewers)
- _pipeline_session context manager: 7 create_engine() → 2 in render_thumbnail
- KD-tree spatial pre-filter for sharp edge marking (bbox-based pruning)
- Batch material library append: N bpy.ops.wm.append → single bpy.data.libraries.load
- GMSH single-session batching: compound all solids into one tessellation call
- Validate part-materials save endpoints against parsed_objects (prevents bogus keys)
- ROADMAP updated with completion status
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract Base Color, Metallic, Roughness, Transmission, IOR from Blender
asset library materials via catalog_assets.py. Store in catalog JSON and
serve via /api/asset-libraries/pbr-map endpoint. Frontend viewers apply
PBR properties to Three.js MeshStandardMaterial using hex color strings
(avoiding Three.js ColorManagement sRGB/linear issues).
Key fixes:
- RLS bypass for material alias lookup in pbr-map endpoint
- pbrMap empty guard prevents premature grey fallback in viewers
- Cache-Control: no-cache on pbr-map requests to avoid stale data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite _traverse_xcaf → _author_xcaf_to_usd that recursively authors
USD prims mirroring the XCAF assembly tree:
- Assembly nodes become UsdGeom.Xform prims with local transforms from
each component label's Location (not composed with parents)
- Leaf shapes get definition-space vertices (face_loc only, no instance
placement) — the USD scene graph composes transforms hierarchically
- Coordinate swap (X,-Z,Y) now authored once as a root Xform on
/Root/Assembly instead of per-vertex transformation
- Sharp/seam edges extracted per-part from definition shape (not global)
This fixes misplaced geometry for sub-assembly parts (e.g. KOMP-EIN
roller cages with -45° Z rotation) that were previously lost by the
flat traversal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Material prims now use resolved canonical names (e.g.
SCHAEFFLER_010101_Steel_Bare) instead of source object names. When
importing USD in Blender, materials show the correct SCHAEFFLER names
directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- export_step_to_usd.py: change stage from Y-up to Z-up, keep (X,-Z,Y)
transform — matches GLB orientation exactly (verified: bounding box match)
- export_glb.py: include canonical_material in resolved_material_assignments
DB field (was being dropped during manifest parsing)
- import_usd.py: use pxr customData read (not string primvars — Blender
ignores those, confirmed by testing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- export_step_to_usd.py: accept --material_map CLI arg, write
schaeffler:canonicalMaterialName as customData on each Mesh prim,
fix geometry transform (strip shape Location before face exploration,
apply both face_loc and shape_loc sequentially)
- import_usd.py: after Blender USD import, use pxr to read customData
directly from the USD file — builds {part_key: material_name} lookup
(Blender ignores STRING primvars and customData, but pxr reads both)
- _blender_materials.py: add apply_material_library_direct() for exact
dict-based material assignment without name-matching heuristics
- _blender_scene_setup.py: prefer direct USD lookup, fall back to
name-matching for legacy USD files without material metadata
- export_glb.py (generate_usd_master_task): resolve material_map via
material_service.resolve_material_map() and pass to subprocess;
include material hash in cache key for invalidation
- ROADMAP.md: update P5 status, add M5-M7 milestones
Tested: 3/3 parts matched (ans_lfs120), 172/175 parts matched
(F-802007.TR4-D1-H122AG). Previous: 0/25 matched.
Co-Authored-By: Claude Opus 4.6 <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>
Previously the endpoint queued USD generation for ALL 295 completed CadFiles,
including 250 orphan CadFiles not linked to any product. Now filters to only
CadFiles referenced by at least one Product.cad_file_id, reducing the backfill
from ~285 to ~41 tasks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- beat_tasks.py: import app.models at module level so SQLAlchemy can
resolve relationship("Template") and relationship("User") when domain
models are imported in isolation inside task functions. Fixes all
beat tasks (batch_render_notifications, recover_stuck_cad_files) that
crashed every 60s with mapper initialization error.
- _blender_materials.py: build_mat_map_lower() now adds a slug-normalized
key variant (re.sub([^a-z0-9]+, _, kl)) for each mat_map entry. OCC
part names like 'F-802007_TR4-D1-H122AG' → slug 'f_802007_tr4_d1_h122ag'
now matches USD-imported Blender objects. Existing prefix fallback
(key.startswith(part_key)) catches AF-suffix variants.
- Orders.tsx: kanban drag-to-reject implemented. submitted/processing
cards are draggable (cursor-grab). Rejected column highlights with
red ring on drag-over. Drop opens reject reason modal via createPortal.
Confirm calls rejectOrder() mutation + invalidates orders cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dokumentiert drei neue Learnings aus der GE360-HF Wälzkörper-Positions-Untersuchung:
1. BRepMesh auf Compound: Triangulation in Definition-Space, Face-loc = Instance-Placement
2. IsSame() vs IsPartner() für Assembly-Instanz-Deduplizierung
3. Stale GLB-Cache maskiert Code-Fixes — nach Tessellierungs-Änderungen Cache invalidieren
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In _extract_mesh(), BRep_Tool.Triangulation_s(face, face_loc) returns a face_loc
that already encodes the instance's full placement transform when a compound shape
is tessellated with BRepMesh_IncrementalMesh. Applying shape_trsf on top doubled
every rotation/translation, causing multiple roller elements to collapse to the same
wrong world position (e.g. Z(-75°)×2 ≡ Z(+105°)×2 mod 360° → identical positions).
Fix: use elif so shape_loc is only applied as a fallback when face_loc is identity.
Adds seam edge extraction (UV seam primvar) and improves _traverse_xcaf doc.
docs: learning erfasst - OCC face_loc double-transform in compound tessellation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add per-file Redis SET NX EX 1800 locks to generate_gltf_geometry_task
and generate_usd_master_task — concurrent duplicates (e.g. double-click
of bulk action buttons) now log a warning and return immediately instead
of running two expensive OCC tessellation subprocesses on the same file
- Fix eng.dispose() called inside with Session() block in cache-hit path
of both tasks — moved to after the with block exits (Tasks 3+4 from plan)
- Add cad.updated_at = datetime.utcnow() in save_manual_material_overrides
(was missing vs parallel save_part_materials endpoint)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- export_gltf.py: replace single-material fallback (only fired when
len(appended)==1) with a universal sentinel that appends
SCHAEFFLER_059999_FailedMaterial unconditionally and assigns it to
every mesh object not matched by name-based lookup.
Also adds in-memory magenta fallback if library append fails.
Removes 2 temporary [DEBUG] print lines from investigation.
- blender_render.py: add FailedMaterial assignment inside
_apply_material_library() for unmatched parts (was log-only before).
Includes copy-on-write guard (users > 1) matching existing pattern.
Also added alias 'Stahl; Durotect CMT' (semicolon) → Durotect-Blue
to cover STEP files using semicolon separator instead of comma.
Verified: 23/25 objects matched correctly, 2 ISO8734 dowel pins
(empty material) receive SCHAEFFLER_059999_FailedMaterial as sentinel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1 — Missing parts (mirror/repeated instances):
- id(solid.TShape()) is unreliable in OCP: each call creates a new
Python wrapper, so id() always differs even for the same TShape.
Replaced with IsSame() for correct TShape-pointer deduplication.
- TopExp_Explorer(SOLID) misses free shells/faces in assemblies.
Fix: run BRepMesh baseline on full root compound first (catches all
face types), then GMSH overrides per unique solid for better seam
topology. REVERSED solids keep BRepMesh to avoid inverted Jacobians.
Bug 2 — GLB 7× too large (21 MB vs OCC 3 MB):
- CharacteristicLengthMax = linear_deflection × 50 (was ×15)
matches OCC effective edge length on curved surfaces (~5 mm).
- MinimumCirclePoints = min(12, ...) (was min(20, ...))
- Result: GMSH 91% of OCC file size (target ≤120% ✓)
Verified on rolling bearing STEP: same 4 skipped nodes as OCC,
25 unique GMSH tessellations (IsSame deduplication), no OOM.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fine production settings (0.03mm/0.05rad) with GMSH → CharacteristicLengthMax
0.45mm → OOM kill on large assemblies even with per-solid iteration.
GMSH quality is algorithmic (conforming seams, no fan triangles) — a denser
mesh provides no extra UV-unwrap benefit. Cap GMSH tessellation at preview
settings (0.1mm/0.1rad) in the production fallback path.
Normal path (GMSH + valid geometry GLB) is unaffected — continues to reuse
_geometry.glb directly without re-tessellating.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
GMSH defaults to single-threaded meshing. Setting General.NumThreads,
Mesh.MaxNumThreads1D and Mesh.MaxNumThreads2D to min(cpu_count, 16) enables
parallel Frontal-Delaunay surface meshing across all available cores.
Benchmark on 121-face assembly (32-core host, capped at 16 threads):
Before: 12.7s total (9.8s in gmsh.model.mesh.generate)
After: 2.8s total (1.1s in gmsh.model.mesh.generate)
Cap at 16 threads — benchmark showed 16 threads (1.1s) matches or beats auto
(1.6s), likely due to NUMA/coordination overhead above that threshold.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces GMSH as an alternative to OCC BRepMesh for STEP→GLB tessellation.
GMSH produces conforming meshes that eliminate fan triangles at cylinder seam
edges — a structural limitation of OCC BRepMesh that cannot be fixed via
deflection parameters.
Changes:
- render-worker/Dockerfile: install gmsh>=4.15.0 + libglu1-mesa + libxft2
- export_step_to_gltf.py: --tessellation_engine occ|gmsh CLI arg +
_tessellate_with_gmsh() using BRep→GMSH→Poly_Triangulation write-back
- admin.py: tessellation_engine setting (SETTINGS_DEFAULTS, SettingsOut,
SettingsUpdate, validation)
- export_glb.py: pass tessellation_engine to export_step_to_gltf.py CLI in
both geometry and production GLB tasks
- Admin.tsx: radio button UI for OCC vs GMSH selection
Tested: 121 faces meshed, 0 BRepMesh fallback, 649K triangles on sample part.
Clean seam edges for UV unwrap — GMSH respects B-rep periodic face boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merges PLAN.md (Phases A-F), PLAN_REFACTOR.md (Phases 1-8), plan.md (GMSH),
docs/rfcs/0001, and visual-audit-report findings into a single prioritized roadmap.
10 priorities with dependency graph and 'what to do next' decision options.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add export_extras=True to bpy.ops.export_scene.gltf() call
- Store schaeffler_sharp_angle_deg in scene custom props before export
→ value is embedded in scenes[0].extras in the GLB JSON chunk
→ survives import/export round-trip intact (verified: 30.0 restored)
- Add tools/restore_sharp_marks.py: companion Blender script that reads
the angle from scene.get("schaeffler_sharp_angle_deg") and re-applies
mark_sharp() + mark_seam() on all mesh objects after GLB import
GLB format cannot store per-edge sharp/seam flags natively; the visual
shading is correct via vertex splits. The extras + restore script give
users the ability to reconstruct Edit Mode markers without a second format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>