H1: model_validator in Settings raises ValueError when jwt_secret_key is
"changeme" and the process is running inside a container — fast-fail
prevents insecure deployments; local dev outside Docker is unaffected.
H7: generate_order_number now acquires pg_advisory_xact_lock before SELECT MAX,
matching the same pattern used in generate_invoice_number (billing/service.py).
Concurrent order creation can no longer race to produce duplicate numbers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
H4: add internal_api_base_url setting to config.py (default http://localhost:8888,
env-overridable via INTERNAL_API_BASE_URL); replace all 5 hardcoded base_url
strings in chat_service.py.
H6: add post-read size check in both Excel and STEP upload handlers;
raises HTTP 413 when content exceeds settings.max_upload_size_mb.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
H2: list_invoices now passes tenant_id (from current_user) to get_invoices;
get_invoices applies WHERE tenant_id filter for non-global-admin users;
get_invoice_endpoint returns 404 when tenant mismatch for non-admins.
H3: InvoiceStatusUpdate.status changed to Literal["draft","sent","paid","cancelled"]
for schema-level validation; guard also added in update_invoice_status service.
H5: _settings_to_out masks smtp_password as "***" when set, "" when empty;
update_settings skips writing when value is the "***" sentinel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_render_turntable: Option B — resolved objects as params (render_invocation, step_path,
output_path, template, emit, pl). Session and PipelineLogger stay in the caller so
no second DB connection is opened and log steps roll up to the main task.
_handle_render_task_exhausted: extracted 68-line mark-as-failed block; retry/raise
logic with Celery self.retry stays in render_order_line_task since it needs the
bound-task context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
canonicalize_workflow_config was silently rebuilding any workflow with
ui.blueprint or ui.preset == still_graph from the canonical template on
every save, preflight, and read — making blueprint-based workflows
effectively read-only.
Add preserve_user_graph=True flag that skips the rebuild blocks. Only
create_workflow still uses preserve_user_graph=False so blueprints are
correctly expanded at creation time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 070: seeds 'Cinematic Highlight' output type (legacy_only, no
workflow link) for fresh installs
- Migration 071: patches existing cinematic output types — clears
workflow_definition_id and forces legacy_only rollout mode. The row
existed since 2026-03 with shadow mode + a linked workflow def;
no BLENDER_CINEMATIC graph node exists so shadow execution would fail.
- API guard in output_types POST + PATCH: cinematic output types cannot
receive a workflow_definition_id (HTTP 400)
- Defense-in-depth in dispatch_service: early legacy exit if
render_settings.cinematic is true, regardless of rollout mode
- docs: learning erfasst — cinematic rollout mode footgun
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drei Bugs aus Cinematic-Render-Session dokumentiert:
- is_cinematic immer False wegen Invocation-Override-Filter
- usd_path.exists() auf str (fehlender str→Path-Cast in turntable/cinematic)
- NameError stdout/stderr in cinematic error path
Co-Authored-By: Claude Sonnet 4.6 <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>
- 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>
- 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>
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>
- 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>
Sharp edges:
- OCC→Blender coordinate transform was wrong: Blender(X,Y,Z) = OCC(X×0.001, -Z×0.001, Y×0.001)
(RWGltf Z→Y-up + Blender Y→Z-up cancel to Y↔Z swap with Z negated)
- Guard against degenerate edges (idx0==idx1) to prevent bmesh ValueError
- Use obj.matrix_world @ v.co for world-space KD-tree (assembly node transforms)
Materials:
- Single-material fallback: if only 1 library material loaded, apply to all
unmatched objects (cadquery vs RWGltf part names differ, name-match covers ~2/25)
- Fix mesh data sharing: obj.data.copy() before materials.clear() to avoid
clearing shared data blocks
- Use obj.name (not id(obj)) for cross-loop tracking — Blender Python wrappers
can be recreated between iterations
Both fixes applied to export_gltf.py (production GLB) and blender_render.py (thumbnails).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 root causes fixed:
1. export_colors=False was removed in Blender 4.x — caused every Blender
export to fail (exit 1) and always fall back to trimesh. Remove it.
Blender now runs the full pipeline: materials + sharp edges.
2. GlbModel cloned ref never reset on url change — key={glbBlobUrl} forces
React to remount GlbModel on each new blob URL, resetting the ref so
fresh geometry is always loaded.
3. glbBlobUrl not cleared before re-fetch — setGlbBlobUrl(null) added at
start of downloadUrl effect so spinner shows instead of stale mesh.
4. staleTime: 30_000 delayed picking up new MediaAsset after generation.
Changed to staleTime: 0 so invalidation always triggers immediate refetch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
- 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>