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>