Commit Graph

56 Commits

Author SHA1 Message Date
Hartmut c82f2a894d fix: invalidate orders list after deleting an order
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>
2026-03-15 20:49:46 +01:00
Hartmut cfccdd5397 feat: rich product metadata extraction from STEP files
Extract volume, surface area, part count, assembly hierarchy, and
complexity from STEP files via OCC B-rep analysis.

Backend:
- extract_rich_metadata() in step_processor.py: computes per-part volume
  (BRepGProp), surface area, triangle/vertex count, assembly depth,
  instance count, complexity score, largest part identification
- cad_metadata JSONB column on Product model (DB migration)
- Auto-populated during STEP processing (non-fatal, 10s timeout)
- Also stored in cad_files.mesh_attributes["rich_metadata"]
- Batch re-extract endpoint: POST /admin/settings/reextract-rich-metadata

AI Agent:
- search_products returns part_count, volume_cm3, complexity, largest_part
- query_database tool description documents cad_metadata schema

Frontend:
- ProductDetail page: CAD Metadata section with stat cards
  (parts, volume, surface area, complexity, triangles, assembly depth)
- Admin System Tools: "Re-extract Rich Metadata" button for backfill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:49:50 +01:00
Hartmut b892f72f7e feat: per-line render overrides — override any output type setting at order time
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>
2026-03-15 12:26:38 +01:00
Hartmut db9f6f45ed fix: auto-poll order detail while renders are active (3s interval)
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>
2026-03-15 09:40:49 +01:00
Hartmut 2c7eb81aab refactor: clean up Render Settings — remove 11 unused settings, fix Blender status
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>
2026-03-15 09:37:54 +01:00
Hartmut 9a794ff2da refactor: full UI/UX cleanup — expandable edit rows, better controls, cleaner UX
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>
2026-03-15 09:20:45 +01:00
Hartmut 966876dced refactor: reorganize Admin page — 8 focused tabs, grouped system tools
Restructured the overloaded 6-tab Admin page into 8 focused tabs:

1. Overview — pricing summary (unchanged)
2. Users — user CRUD (unchanged)
3. Render Settings — engine, samples, tessellation, viewer config only
4. Output Types — OutputTypeTable (extracted from old Pricing tab)
5. Templates & Positions — render templates + camera positions + material lib
6. Pricing — pricing tiers only (cleaned up)
7. Libraries — asset libraries + template editor (unchanged)
8. System Tools — ALL maintenance buttons organized into cards:
   - Reprocessing (stuck recovery, thumbnails, metadata, workflows)
   - USD/Canonical Scenes (missing masters, regenerate all)
   - Cleanup (orphaned media/STEP, purge renders)
   - GPU Status, SMTP, Dashboard Config

Section header pattern (icon + title + description) for each section.
Card pattern for grouped maintenance actions.
Scrollable tab bar for smaller screens.

No sub-components changed — only Admin.tsx reorganization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 01:58:45 +01:00
Hartmut ed7efd923b fix: add custom accent color picker to Preferences page
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>
2026-03-15 01:45:27 +01:00
Hartmut dbadfdf489 feat: per-line material override in product order wizard Step 3
- 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>
2026-03-14 14:52:21 +01:00
Hartmut f7aeeec5d8 feat: material override in new product order wizard (Step 3)
- 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>
2026-03-14 14:41:42 +01:00
Hartmut d84ce8252e feat: batch material override — apply to all lines in an order at once
- 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>
2026-03-14 14:36:18 +01:00
Hartmut 9d6def84c1 feat: per-order-line material override — override materials for individual renders
- 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>
2026-03-14 14:33:00 +01:00
Hartmut b6bac080bb feat: duplicate product detection — STEP conflict warnings on Excel import and CAD upload
- 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>
2026-03-14 13:05:40 +01:00
Hartmut f0dd952f63 feat: material alias seeds expansion, bulk product delete, dashboard stats widgets
- Material alias seeds: 95 → 855 aliases covering German variants, DIN standards,
  Werkstoffnummern, industry terms, English equivalents, polymer abbreviations
- Batch product delete/deactivate endpoint (POST /products/batch-delete)
- Multi-select UI on Products page with floating action bar
- Dashboard: RenderThroughput + MaterialCoverage widgets
- Dashboard stats endpoint (GET /admin/dashboard-stats)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:45:41 +01:00
Hartmut b583b0d7a2 feat: per-position camera settings, material alias dialog, product delete, media browser links
- 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>
2026-03-14 12:16:37 +01:00
Hartmut 5afe502bc0 feat(admin): add "Regenerate All GLB + USD" button
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>
2026-03-13 15:21:04 +01:00
Hartmut ec667dd56a refactor: remove dead export_gltf.py, cleanup rendering tasks, improve tessellation UI
- Remove export_gltf.py (Blender-based GLB export replaced by OCC direct)
- Remove unused export_gltf_for_order_line_task
- Add Ultra tessellation preset to Admin settings
- Improve tessellation preset descriptions and styling
- Minor cleanup across media, rendering, and workflow modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:37:35 +01:00
Hartmut d843162e5f feat(PBR): extract Blender PBR properties and apply in 3D viewer
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>
2026-03-13 10:37:23 +01:00
Hartmut 577dd1ca7e refactor(P11+P12): codebase hygiene — CLAUDE.md rewrite, type safety, dead code removal
- Rewrite CLAUDE.md to match current 8-service architecture (was 11, 5 deleted)
- Remove all as-any casts in OrderDetail.tsx (9 casts → 0)
- Add cad_parsed_objects/cad_part_materials to OrderItem interface
- Rename require_admin → require_global_admin across 6 router files (22 calls)
- Remove EXPORT_GLB_PRODUCTION enum + generate_gltf_production_task (dead code)
- Remove worker-thumbnail from ALLOWED_SERVICES, replace Flamenco link
- Delete obsolete PLAN.md (1455 lines) and PLAN_REFACTOR.md (1174 lines)
- Fix digit-only USD prim names with p_ prefix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 07:22:04 +01:00
Hartmut 1321ef2bd4 refactor: rename thumbnail_rendering queue to asset_pipeline
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>
2026-03-12 22:28:38 +01:00
Hartmut 8e1cd41868 fix(critical): SQLAlchemy mapper crash + material matching for USD renders + kanban drag-to-reject
- 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>
2026-03-12 22:21:46 +01:00
Hartmut 409fb92899 feat(P2): USD Foundation — canonical part identity + material overrides
M1 — USD exporter:
- render-worker/scripts/export_step_to_usd.py (631 lines)
  Full XCAF traversal, one UsdGeom.Mesh per leaf part,
  schaeffler:partKey on every prim, index-space sharpEdgeVertexPairs
- render-worker/Dockerfile: usd-core>=24.11 installed (USD 0.26.3)

M2 — usd_master MediaAsset + pipeline auto-chain:
- migrations 060 (usd_master enum), 061 (3 JSONB columns),
  062 (rename tessellation settings keys)
- generate_usd_master_task: runs export_step_to_usd.py, upserts
  usd_master MediaAsset, writes resolved_material_assignments to CadFile
- Auto-chained from generate_gltf_geometry_task after every GLB export
- step_tasks.py shim re-exports generate_usd_master_task

M3 — scene-manifest API:
- part_key_service.py: build_scene_manifest(), generate_part_key(),
  four-layer material priority resolution with provenance
- SceneManifest / PartEntry Pydantic models in products/schemas.py
- GET /api/cad/{id}/scene-manifest endpoint (graceful fallback to
  parsed_objects when USD not yet generated)
- POST /api/cad/{id}/generate-usd-master endpoint
- frontend/src/api/sceneManifest.ts: fetchSceneManifest(),
  triggerUsdMasterGeneration()

M4 — manual-material-overrides API:
- GET/PUT /api/cad/{id}/manual-material-overrides endpoints
- CadFile.manual_material_overrides JSONB column (migration 061)
- getManualOverrides() / saveManualOverrides() in cad.ts

M5 — ThreeDViewer partKey integration:
- export_step_to_gltf.py injects partKeyMap into GLB extras
- ThreeDViewer: partKeyMap extraction, resolvePartKey(), effectiveMaterials
  merges legacy partMaterials + new manualOverrides (server-side persistence)
- MaterialPanel: dual-path save (partKey vs legacy), provenance badge,
  reconciliation panel for unmatched/unassigned parts

Also:
- Admin.tsx: generate-missing-usd-masters + canonical scenes bulk actions
- ProductDetail.tsx: usd_master row in asset table
- vite-env.d.ts: fix ImportMeta.env TypeScript error
- GPUProbeResult: add timestamp/devices/render_time_s fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 13:11:09 +01:00
Hartmut 393e4b92a7 refactor(P1): complete pipeline cleanup — M1 dead code + M3 blender split
M1 dead code removal:
- admin.py: remove VALID_STL_QUALITIES + stl_quality (7 locations)
- frontend: remove stl_quality from 6 files (api/orders.ts, api/worker.ts,
  WorkerActivity.tsx, RenderInfoModal.tsx, helpTexts.ts, mocks/handlers.ts)
- blender_render.py: delete _mark_sharp_and_seams() — dead, never called (62 lines)
- step_processor.py: delete _render_via_service() + 2 elif renderer=="threejs" branches
- renderproblems_tmp/: remove 3 orphaned debug images

M3 blender_render.py decomposition (858 → 248 lines):
- _blender_gpu.py: activate_gpu(), configure_engine()
- _blender_import.py: import_glb(), apply_rotation()
- _blender_materials.py: FAILED_MATERIAL_NAME, assign_failed_material(),
  build_mat_map_lower(), apply_material_library()
- _blender_camera.py: setup_auto_camera(), setup_auto_lights()
- _blender_scene.py: ensure_collection(), apply_smooth_batch(),
  apply_sharp_edges_from_occ(), setup_shadow_catcher()
- Entry-point: sys.path.insert for submodule discovery; arg-parse + Mode A/B orchestration only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:19:59 +01:00
Hartmut af320bcdc8 feat(P3): add GMSH Frontal-Delaunay tessellation engine
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>
2026-03-11 19:17:26 +01:00
Hartmut ca62319688 feat: sharp edge pipeline V02, tessellation presets, media cache-bust, GMSH plan
Sharp Edge Pipeline V02:
- export_step_to_gltf.py: replace BRep_Tool.Polygon3D_s (returns None in XCAF) with
  GCPnts_UniformAbscissa curve sampling at 0.3mm step — extracts 17,129 segment pairs
- Inject sharp_edge_pairs + sharp_threshold_deg into GLB extras (scenes[0].extras)
  via binary GLB JSON-chunk patching (no extra dependency)
- export_gltf.py: read schaeffler_sharp_edge_pairs from Blender scene custom props,
  apply via KD-tree to mark edges sharp=True + seam=True (OCC mm Z-up → Blender transform)
- tools/restore_sharp_marks.py: dual-pass (dihedral angle + OCC pairs), updated coordinate
  transform (X, -Z, Y) * 0.001

Tessellation:
- Admin UI: Draft / Standard / Fine preset buttons with active-state highlighting
- Default angular deflection: preview 0.5→0.1 rad, production 0.2→0.05 rad
- export_glb.py: read updated defaults from system_settings

Media / Cache:
- media/service.py: get_download_url appends ?v={file_size_bytes} cache-buster
- media/router.py: Cache-Control: no-cache for all download/thumbnail endpoints

Render pipeline:
- still_render.py / turntable_render.py: shared GPU activation + camera improvements
- render_order_line.py: global render position support
- render_thumbnail.py: updated defaults

Frontend:
- InlineCadViewer: file_size_bytes-aware URL update triggers re-fetch on regeneration
- ThreeDViewer: material panel, part selection, PBR mode improvements
- Admin.tsx: tessellation preset cards, GMSH setting dropdown
- MediaBrowser, ProductDetail, OrderDetail, Orders: various UI improvements
- New: MaterialPanel, GlobalRenderPositionsPanel, StepIndicator components
- New: renderPositions.ts API client

Plans / Docs:
- plan.md: GMSH Frontal-Delaunay tessellation plan (6 tasks)
- LEARNINGS.md: OCC Polygon3D_s None issue + GCPnts fix
- .gitignore: add backend/core (core dump from root process)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 14:40:36 +01:00
Hartmut 8933d0be17 fix(render+roles): batch smooth shading + step timings + global_admin role support
Render pipeline:
- Replace per-object _apply_smooth() loop with _apply_smooth_batch(): selects
  all 175 parts, calls shade_smooth_by_angle() ONCE in C → reduces 16s to ~0.2s
- Remove 175 per-part "assigned material to part" log lines (replace with summary)
- Add TIMING_SUMMARY log line at end of every render showing all step durations
- _lap() helper records split times for: template_load, glb_import, rotation,
  smooth_shading, material_assign, pre_render_setup, gpu_render

Frontend role checks:
- Add global_admin + tenant_admin to User role type in auth store
- Add isAdmin() and isPrivileged() helper functions
- Fix Admin.tsx, Layout.tsx, Notifications.tsx, OrderDetail.tsx, ProductDetail.tsx,
  CostOverviewWidget.tsx — all were checking role === 'admin' but JWT now has
  role === 'global_admin' after migration 049 (admin → global_admin backfill)
- This caused Admin page to render completely empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 21:29:22 +01:00
Hartmut 22c29d5655 feat(azure-ai+gpu-ui): per-tenant Azure AI config + GPU health panel
- 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>
2026-03-08 21:04:09 +01:00
Hartmut 07e3d1e026 feat(phase8.1-8.2): dynamic worker concurrency via worker_configs
- Migration 054: worker_configs table (queue_name PK, max/min_concurrency,
  enabled, updated_at); seeds step_processing(8/2), thumbnail_rendering(1/1),
  ai_validation(4/1)
- WorkerConfig SQLAlchemy model
- apply_worker_concurrency beat task: reads enabled configs, broadcasts
  pool_grow to all Celery workers every 5min
- GET/PUT /api/worker/configs (admin): list + update per-queue concurrency
- docker-compose.yml: worker uses --autoscale=${MAX_CONCURRENCY:-8},${MIN_CONCURRENCY:-2};
  render-worker uses --autoscale=1,1 --concurrency=1
- WorkerManagement.tsx: "Concurrency Settings" section with +/- steppers
  and Save button per queue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:41:57 +01:00
Hartmut 1cc10d4bbb feat(phase7.4): order rejection + resubmit flow
- Migration 053: rejection_reason TEXT NULL on orders table
- POST /api/orders/{id}/reject (PM+): sets rejected status, cancels
  active renders, stores reason, notifies creator, broadcasts WS event
- POST /api/orders/{id}/resubmit (creator or PM+): resets to draft,
  clears rejection fields, notifies PMs
- OrderDetail: Reject button (PM+) + inline modal with reason textarea
  and notify-client checkbox; Resubmit button; rejection reason amber
  alert box shown below header when order is rejected
- Orders Kanban: rejection_reason shown as red italic note on card
- Order interface: rejected_at, rejection_reason fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:37:05 +01:00
Hartmut 1409be171c feat(phase7.3): workflow editor pipeline step nodes
- GET /api/workflows/pipeline-steps: returns all StepName enum values
  with category (input|processing|rendering|output) + descriptions;
  registered before /{workflow_id} to avoid path collision
- frontend/src/api/workflows.ts: getPipelineSteps(), PipelineStep
  and PipelineStepsResponse interfaces
- WorkflowEditor: PipelineStepsPanel showing steps grouped by category
  with collapsible accordion sections
- ConfigSidepanel: "Pipeline Step" select dropdown binds any node to a
  StepName; selected step description shown below dropdown
- Active workflow indicator: green dot next to is_active=true entries
- Improved empty state with descriptive copy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:24:17 +01:00
Hartmut c99976cc85 feat(phase7.2): media browser with server-side filters + pagination
- Migration 052: indexes on media_assets(asset_type, created_at) and
  products(category_key, lagertyp) for efficient filter queries
- GET /api/media/assets: JOINs media_assets→products→order_lines,
  filters by asset_type / category_key / render_status / q (ILIKE),
  paginated (page/page_size), returns total+pages count
- New schemas: MediaAssetBrowseItem, MediaAssetBrowseResponse
- frontend/src/api/media.ts: getMediaAssets(filters), typed interfaces
- MediaBrowser.tsx: rewritten with sticky filter bar (debounced search,
  type/category/status dropdowns), responsive grid, image previews,
  download buttons, pagination footer with page size selector
- Renamed legacy function to listMediaAssets for backward compat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:24:03 +01:00
Hartmut 89c44b846f feat(phase5.1+6): fallback material cleanup + notification batch refactor
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>
2026-03-08 20:20:07 +01:00
Hartmut 10d05bd2e7 feat(phase7.1): add HelpTooltip system with contextual help icons
- New HelpTooltip component: hover-triggered floating panel, themed via
  CSS variables, supports top/right/bottom/left positioning, no deps
- New helpTexts.ts registry: 14 entries covering render settings,
  admin actions, template fields, and wizard fields
- Admin.tsx: tooltips on Cycles/EEVEE samples, smooth angle, regenerate
  thumbnails, process unprocessed
- RenderTemplateTable.tsx: tooltips on material replace, lighting only,
  shadow catcher column headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:16:42 +01:00
Hartmut 206672a858 i18n(frontend): translate all German UI strings to English
Replace German labels, button text, toast messages, table headers,
tooltips, and placeholder strings across 7 files:
- WorkflowEditor: buttons, toasts, node labels
- Tenants: buttons, toasts, dialog text, table headers
- Admin: widget layout description
- OrderDetail: column headers (Baureihe→Series, Ebene→Level, Lagertyp→Bearing Type)
- ExcelSpreadsheet: column label definitions
- Upload: series/duplicate warning strings
- TemplateEditor: ALL_FIELD_DEFS default labels

API field names (baureihe, ebene1, produkt_baureihe etc.) unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:07:01 +01:00
Hartmut 915abe9d74 fix(ux): replace confirm() with ConfirmModal, fix dark-mode colors, add currency format
- Add reusable ConfirmModal component (themed, Escape key, focus trap)
- Replace all native confirm() calls in Orders, ProductLibrary, Materials, Admin, Billing
- Fix ValidationDialog (Upload.tsx) hardcoded bg-white/text-gray-* → semantic tokens
- Fix NewInvoiceModal (Billing.tsx) hardcoded colors → semantic tokens
- Add formatCurrency (Intl.NumberFormat de-DE/EUR) to NewProductOrder wizard
- Resolves audit issues C1, C3, M3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 19:59:13 +01:00
Hartmut ee6eb34b4c feat: GPU rendering + material matching + perf improvements
- 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>
2026-03-08 19:05:03 +01:00
Hartmut 95cfe0aa93 refactor: replace STL intermediary with OCC-native STEP→GLB pipeline
- export_step_to_gltf.py: STEP→GLB via RWGltf_CafWriter + BRepBuilderAPI_Transform
  (mm→m pre-scaling, XCAFDoc_ShapeTool.GetComponents_s static method)
- Blender scripts (blender_render.py, still_render.py, turntable_render.py,
  export_gltf.py, export_blend.py): import GLB instead of STL, remove _scale_mm_to_m
- step_tasks.py: add generate_gltf_production_task, remove generate_stl_cache,
  replace _bbox_from_stl with _bbox_from_glb (trimesh), auto-queue geometry GLB
  after thumbnail render
- render_blender.py: replace _stl_from_cache_or_convert with _glb_from_step,
  remove convert_step_to_stl and export_per_part_stls
- domains/rendering/tasks.py: update render_turntable_task, export_gltf/blend tasks
  to use GLB instead of STL
- cad.py: remove STL download/generate endpoints, add generate-gltf-production
- admin.py: generate-missing-stls → generate-missing-geometry-glbs
- Frontend: replace STL cache UI with GLB generate buttons, remove stl_cached field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 16:49:18 +01:00
Hartmut bfd58e3419 fix: media thumbnails, product dimensions, inline 3D viewer, GLB export
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>
2026-03-07 13:27:46 +01:00
Hartmut 5029a94608 fix: full-width content area + auto-create MediaAssets on render complete
- 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>
2026-03-07 00:17:17 +01:00
Hartmut f5ca91ee02 feat: layout hamburger, media browser filters+previews, billing fixes
- Layout: mobile hamburger menu + overlay backdrop + close button; content area always full-width
- Media browser: filter chips (default still+turntable); advanced toggle for GLB/STL; thumbnail_url previews for non-image types; video hover-play for turntable
- Backend: asset_types multi-filter, thumbnail_url in MediaAssetOut, download proxy endpoint for MinIO/local files
- Admin: "Import Existing Media" button → POST /api/admin/import-media-assets
- Billing: fix invoice create 500 (MissingGreenlet — use selectinload after commit); PDF download uses axios blob instead of bare <a href> (auth header missing); fix storage.upload() accepting str|Path
- SSE task logs: task_logs.py core + router, LiveRenderLog component
- CadPreview: fix infinite loop when no gltf_geometry assets; loading screen before ThreeDViewer render
- render-worker: add trimesh layer to Dockerfile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:09:27 +01:00
Hartmut 382a18fd02 feat(O): UI-Vollständigkeit + v3-Workflows + OCC-Kantenanalyse
Backend:
- Phase I: notification_configs router (GET/PUT/{event}/{channel}/POST reset)
  war bereits in notifications.py — add-alias endpoint in uploads.py ergänzt
- OutputType schema: workflow_definition_id + workflow_name fields;
  PATCH unterstützt Workflow-Zuweisung; _enrich_workflow_names() batch query
- Dispatch-Integration: orders.py dispatch_renders() → dispatch_render_with_workflow()
  mit Legacy-Fallback; neues Logging
- uploads.py: POST /validations/{id}/add-alias für Material-Lücken

Pipeline:
- step_processor.py: extract_mesh_edge_data() via OCC — berechnet Dihedralwinkel
  aller Kanten, liefert suggested_smooth_angle + sharp_edge_midpoints
  Integriert in extract_cad_metadata() und process_cad_file()
- domains/rendering/tasks.py: apply_asset_library_materials_task (K3),
  export_gltf_for_order_line_task → Blender export_gltf.py (K4),
  export_blend_for_order_line_task → export_blend.py fix (K5)
- render-worker/scripts/still_render.py: _mark_sharp_and_seams() mit
  OCC midpoint KD-tree matching + UV-Seam-Markierung
- render-worker/scripts/blender_render.py: identische Funktion + mesh_attributes parsing

Frontend:
- Layout.tsx: Upload-Link in Sidebar (alle User); Asset Libraries Link (admin/PM)
- App.tsx: /asset-libraries Route
- AssetLibrary.tsx: neue Seite (Upload, Catalog-Anzeige, Refresh, Toggle, Delete)
- OutputTypeTable.tsx: Workflow-Dropdown + Legacy/Workflow Badge
- ProductDetail.tsx: Geometry-Karte (Volumen, Surface, BBox, Sharp-Winkel)
- api/outputTypes.ts + api/products.ts: neue Felder
- api/imports.ts: ImportValidation API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:20:55 +01:00
Hartmut f15b035b88 feat(L1): modular widget dashboard — 15 configurable widgets
Replaces monolithic AdminDashboard/ClientDashboard with a per-user
configurable widget grid. 15 widget types: ProductionStats, QueueStatus,
RecentRenders, CostOverview, WorkerStatus, KPISummary, OrderThroughput,
Revenue, ItemStatus, ProcessingTimes, RenderTimeByOutputType,
OutputTypeUsage, TopProducts, OrdersByUser, RenderBackendStats.

DashboardTimeframeContext provides shared timeframe state. Dashboard
config persisted in DB via GET/PUT /api/dashboard/config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:11:13 +01:00
Hartmut a70cb55d01 feat(N): workflow pipeline, 3D viewer, worker management, QC tests
- workflow_builder.py: fix broken stubs, add render_order_line_still_task
  (resolves step_path from DB instead of passing order_line_id as step_path)
- domains/rendering/tasks.py: add render_order_line_still_task,
  export_gltf_for_order_line_task, export_blend_for_order_line_task,
  generate_gltf_geometry_task (trimesh STL→GLB, no Blender needed)
- tasks/step_tasks.py: add generate_gltf_geometry_task for CadFile GLB export
- cad router: POST /{id}/generate-gltf-geometry endpoint (admin/PM)
- worker router: GET /celery-workers + POST /scale (docker compose subprocess)
- Dockerfile: pip install -e "[dev]" to enable pytest
- docker-compose.yml: docker socket + compose file mount on backend
- ThreeDViewer.tsx: mode toggle (geometry/production), wireframe, env presets,
  download buttons (GLB + .blend)
- CadPreview.tsx: load gltf_geometry/gltf_production/blend_production assets
  from MediaAsset table and pass URLs to ThreeDViewer
- ProductDetail.tsx: "View 3D" button → /cad/:id, "Generate GLB" button
- media router/service: cad_file_id filter on GET /api/media
- WorkerManagement.tsx: new page with worker status, queue depth, scale controls
- App.tsx + Layout.tsx: /workers route + sidebar link (admin/PM)
- tests: test_rendering_service.py, test_orders_service.py (backend)
- tests: WorkerActivity.test.tsx, WorkerManagement.test.tsx (frontend)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 22:56:53 +01:00
Hartmut bfc0050580 feat(L+M): configurable dashboard widget system + test framework
Phase L: Dashboard widget system
- Migration 046: dashboard_configs table (user/tenant/role fallback cascade)
- DashboardConfig model + dashboard_service with get/upsert per-user and tenant-default
- API router: GET/PUT /api/dashboard/config, GET/PUT /api/dashboard/tenant-default
- Frontend: 5 widget components (ProductionStats, QueueStatus, RecentRenders, CostOverview, WorkerStatus)
- DashboardGrid with API-backed config, DashboardCustomizeModal (checkbox selection, save/cancel)
- Dashboard.tsx: widget grid + analytics section (privileged users)
- Admin.tsx: restructured with new section order and Maintenance 2-col grid

Phase M: Test framework
- Backend: pytest-asyncio + pytest-cov + factory-boy in pyproject.toml
- conftest.py: excel_dir fixtures + async DB fixtures + mock storage/celery stubs
- Domain tests: billing_service, cache_service, notifications_service, imports_sanity
- Integration: test_api_health.py smoke test (requires running backend)
- Frontend: vitest + @testing-library/react + msw added to package.json
- vite.config.ts: test block (jsdom + globals + setupFiles)
- tsconfig.json: exclude src/__tests__ from main tsc (test runner handles its own types)
- MSW handlers for /api/auth/me, Billing.test.tsx, formatters.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 21:50:07 +01:00
Hartmut ced64055f2 refactor(admin): remove Pillow as selectable renderer, restructure admin page
- Backend: VALID_RENDERERS = {"blender"} only; remove pillow from renderer-status response
- Frontend: remove renderer picker (pillow/blender buttons) — Blender is the only renderer
- Blender options always visible, grouped into Render Quality / Performance / Output sections
- Maintenance buttons in 2-column grid with descriptions
- Page reorder: Pricing Summary → Users → Blender Settings → Render Templates →
  Asset Libraries → Output Types → Pricing Tiers → SMTP → Templates
- Pillow code kept internally as fallback (not exposed in UI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 21:20:28 +01:00
Hartmut a18d4c23ec feat(K): Blender Asset Library + production exports (GLB + .blend)
- feat(migration): 045_asset_libraries — new asset_libraries table (blend_file_path, catalog JSONB)
- feat(model): AssetLibrary SQLAlchemy model in domains/materials/models.py
- feat(api): POST/GET/PATCH/DELETE /api/asset-libraries + /upload-blend + /refresh-catalog endpoints
- feat(celery): refresh_asset_library_catalog task on thumbnail_rendering queue — runs Blender headless
- feat(blender): catalog_assets.py — extracts asset-marked materials + node_groups from .blend
- feat(blender): asset_library.py — apply_asset_library_materials + apply_asset_library_node_groups helpers
- feat(blender): export_gltf.py — STEP→STL→GLB production export with optional asset library
- feat(blender): export_blend.py — STEP→STL→.blend production export with pack_all()
- feat(frontend): api/assetLibraries.ts — full CRUD API client
- feat(frontend): AssetLibraryPanel in Admin.tsx — upload, refresh, expand catalog view
- docs: Blender asset_data marking requirement learning in LEARNINGS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:56:26 +01:00
Hartmut 7a1329958d feat(J): WebSocket live-events + replace polling + fix ffmpeg turntable timeout
- fix(render): ffmpeg overlay=0:0 -> overlay=0:0:shortest=1 to prevent hang on finite PNG sequences
- feat(ws): add core/websocket.py ConnectionManager + Redis Pub/Sub subscriber loop
- feat(ws): add /api/ws WebSocket endpoint with JWT query-param auth in main.py
- feat(ws): emit render_complete/failed + cad_processing_complete events from step_tasks.py
- feat(ws): emit order_status_change events from orders router
- feat(ws): add beat_tasks.py broadcast_queue_status task (every 10s via Redis __broadcast__)
- feat(frontend): add useWebSocket hook with auto-reconnect (exponential backoff, 25s ping)
- feat(frontend): add WebSocketContext + WebSocketProvider wrapping App
- refactor(frontend): remove polling from WorkerActivity (was 5s/3s) + OrderDetail (was 5s)
- refactor(frontend): reduce polling in Layout (8s->60s) + NotificationCenter (15s->60s)
- docs: add ffmpeg shortest=1 + WebSocket JWT auth learnings to LEARNINGS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:49:34 +01:00
Hartmut d138bc4bc4 feat(activity): merge CAD + render jobs into unified timeline
- 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>
2026-03-06 20:01:03 +01:00
Hartmut ab3f9c734a fix: render pipeline + multi-tenancy bugs (B-Fix-1 through B-Fix-9)
- Remove worker-thumbnail (no Blender, was competing on thumbnail_rendering)
- Move render_order_line_task to thumbnail_rendering queue (render-worker)
- Restore template_service.py real implementation (fix circular import shim)
- Thread tenant_id through STEP upload, Excel import, product create
- Make system tables (output_types, materials, etc.) tenant_id nullable
- Fix tenants frontend 307-redirect: use trailing slash /tenants/
- Remove Flamenco + Three.js from Admin UI (unsupported)
- Set all output_types render_backend to celery (was flamenco)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 19:34:20 +01:00
Hartmut df02067687 fix(ui): theme WorkflowEditor for dark mode
Replace all hardcoded gray-*/white Tailwind classes with semantic CSS
variable tokens (bg-surface, text-content, border-border-default, etc.)
so the page respects dark/light mode correctly.

- Sidebar, header bar, toolbar: bg-surface + border-border-default
- Node cards: bg-surface + border-accent (selected) / border-border-default
- ConfigSidepanel: bg-surface, text-content-secondary labels
- NewWorkflowModal: bg-surface, border-border-default inputs, accent buttons
- Workflow list items: bg-accent-light highlight for selected, hover:bg-surface-hover
- Empty state icon: text-content-muted
- ReactFlow: colorMode prop tied to useThemeStore (dark/light)
- Background: removed hardcoded #e5e7eb dot color (uses ReactFlow colorMode)
- All blue-600 buttons → bg-accent / hover:bg-accent-hover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 18:21:35 +01:00