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>
This commit is contained in:
2026-03-12 13:11:09 +01:00
parent 47b5d42bb5
commit 409fb92899
33 changed files with 2070 additions and 303 deletions
+90 -70
View File
@@ -1,88 +1,108 @@
# Plan: P1 Remaining Cleanup — M1 Dead Code + M3 blender_render.py Split
# Plan: P2 USD Foundation — Commit & Verify
## Context
Three categories of cleanup:
1. **M1a**: Two legacy HTTP renderer directories (`blender-renderer/`, `threejs-renderer/`) still exist in repo root despite the services being removed in Phase A.
2. **M1b**: Dead code in backend services — PIL fallback in `step_processor.py`, `stl_quality` param (always "low") in `render_blender.py` and `domains/rendering/tasks.py`.
3. **M3**: `render-worker/scripts/blender_render.py` is 263 lines (target < 80) — argparse, scene setup, and render config should move to submodules.
`domains/rendering/tasks.py` is **NOT dead code** — contains 6 active Celery tasks (`render_still_task`, `render_turntable_task`, `render_order_line_still_task`, `export_gltf_for_order_line_task`, `export_blend_for_order_line_task`, `apply_asset_library_materials_task`). Only the `stl_quality` param needs removal.
All five P2 milestones are already implemented in the working tree as uncommitted changes.
The task now is to apply the DB migrations, commit the work, and verify the stack runs.
## Affected Files
- `blender-renderer/` — delete entire directory
- `threejs-renderer/` — delete entire directory
- `backend/app/services/step_processor.py` — remove PIL fallback block (~line 565)
- `backend/app/services/render_blender.py` — remove `stl_quality` param from `_glb_from_step()`, `render_still()`, `render_turntable_to_file()`
- `backend/app/domains/rendering/tasks.py` — remove `stl_quality` param from `render_still_task`, `render_turntable_task`
- `render-worker/scripts/blender_render.py` — thin to < 80 lines
- `render-worker/scripts/_blender_args.py` — new file (argument parsing)
- `render-worker/scripts/_blender_scene_setup.py` — new file (MODE A/B scene setup)
- `render-worker/scripts/_blender_render_config.py` — new file (engine + output config)
### Milestone status (assessed 2026-03-12)
| Milestone | Status | Key files |
|---|---|---|
| M1: `export_step_to_usd.py` with `schaeffler:partKey` | ✅ DONE | `render-worker/scripts/export_step_to_usd.py` (631 lines) |
| M2: `usd_master` MediaAsset + migrations 060062 + Celery task | ✅ DONE | migrations 060/061/062, `generate_usd_master_task` in `export_glb.py` |
| M3: `GET /api/cad/{id}/scene-manifest` | ✅ DONE | `part_key_service.py`, `SceneManifest` schema, endpoint in `cad.py` |
| M4: `PUT /api/cad/{id}/manual-material-overrides` | ✅ DONE | New endpoint pair in `cad.py`, `saveManualOverrides` in `cad.ts` |
| M5: ThreeDViewer uses partKey, survives reload | ✅ DONE | `partKeyMap` in GLB extras, `effectiveMaterials` merge, server-side persistence |
## Affected Files (all uncommitted — working tree only)
**Backend**
- `backend/alembic/versions/060_usd_master_asset_type.py` — new migration
- `backend/alembic/versions/061_material_assignment_layers.py` — new migration
- `backend/alembic/versions/062_rename_tessellation_settings.py` — new migration
- `backend/app/domains/media/models.py``MediaAssetType.usd_master` added
- `backend/app/domains/products/models.py` — 3 new JSONB columns on `CadFile`
- `backend/app/domains/products/schemas.py``SceneManifest`, `PartEntry` Pydantic models
- `backend/app/domains/pipeline/tasks/export_glb.py``generate_usd_master_task` + auto-chain
- `backend/app/domains/pipeline/tasks/extract_metadata.py` — minor update
- `backend/app/domains/pipeline/tasks/render_thumbnail.py` — minor update
- `backend/app/domains/pipeline/tasks/render_order_line.py` — minor update
- `backend/app/api/routers/cad.py` — scene-manifest + manual-material-overrides endpoints
- `backend/app/api/routers/admin.py` — generate-missing-usd-masters + generate-missing-canonical-scenes buttons
- `backend/app/services/part_key_service.py` — new file: `build_scene_manifest()`, `generate_part_key()`
- `backend/app/core/config_service.py` — minor update
- `backend/app/core/tenant_context.py` — new file
- `backend/app/tasks/step_tasks.py` — re-exports `generate_usd_master_task`
**Render worker**
- `render-worker/scripts/export_step_to_usd.py` — new file: full USD exporter
- `render-worker/scripts/export_step_to_gltf.py` — injects `partKeyMap` into GLB extras
- `render-worker/scripts/still_render.py` — USD path support
- `render-worker/scripts/turntable_render.py` — USD path support
- `render-worker/Dockerfile``usd-core>=24.11` added
**Frontend**
- `frontend/src/api/cad.ts``getManualOverrides()`, `saveManualOverrides()`
- `frontend/src/api/media.ts``usd_master` type added
- `frontend/src/api/sceneManifest.ts` — new file: `SceneManifest`, `fetchSceneManifest()`
- `frontend/src/components/cad/ThreeDViewer.tsx``partKeyMap`, `effectiveMaterials`, reconciliation panel
- `frontend/src/components/cad/MaterialPanel.tsx` — dual-path save, provenance badge
- `frontend/src/pages/Admin.tsx` — USD master bulk action buttons
- `frontend/src/pages/ProductDetail.tsx``usd_master` row in asset table
- `frontend/src/pages/Orders.tsx` — minor update
## Tasks (in order)
### [x] Task 1: Delete legacy renderer directories
- **File**: `blender-renderer/`, `threejs-renderer/` (repo root)
- **What**: `git rm -rf blender-renderer/ threejs-renderer/` — removes both legacy HTTP service directories superseded by the Celery render-worker in Phase A
- **Acceptance gate**: `ls blender-renderer/ threejs-renderer/` both return "no such file or directory"
### [ ] Task 1: Apply migrations 060062
- **What**: Run `docker compose exec backend alembic upgrade head` to apply the three pending migrations
- **Acceptance gate**: `docker compose exec backend alembic current` shows `062` (or higher) as current
- **Dependencies**: none
- **Risk**: Low — not imported by any active pipeline code
- **Risk**: Low — each migration is additive (ADD VALUE, ADD COLUMN, UPDATE). Check for phantom drops before running.
### [x] Task 2: Remove PIL fallback from step_processor.py
- **File**: `backend/app/services/step_processor.py`
- **What**: Find `from PIL import Image` (~line 565, inside `_generate_thumbnail()`) and the PIL thumbnail generation conditional branch. Remove the import and the branch — leave only the render-worker path.
- **Acceptance gate**: `grep -n "PIL\|Pillow" backend/app/services/step_processor.py` returns nothing
- **Dependencies**: none
- **Risk**: Low — PIL path unreachable; render-worker handles all thumbnails
### [ ] Task 2: TypeScript check
- **What**: Run `docker compose exec frontend npx tsc --noEmit` to verify no type errors in the frontend changes
- **Acceptance gate**: Zero TypeScript errors
- **Dependencies**: none (frontend hot-reload, no rebuild needed)
- **Risk**: Low
### [x] Task 3: Remove stl_quality param from render_blender.py
- **File**: `backend/app/services/render_blender.py`
- **What**:
- `_glb_from_step(step_path, output_dir, quality="low")``_glb_from_step(step_path, output_dir)` — hardcode the low-quality deflection values inline (no conditional on quality)
- Remove `stl_quality: str = "low"` from `render_still(...)` and `render_turntable_to_file(...)`
- Remove all internal `quality=stl_quality` pass-throughs
- **Acceptance gate**: `grep -n "stl_quality" backend/app/services/render_blender.py` returns nothing
- **Dependencies**: none (Task 4 updates callers)
- **Risk**: Medium — callers in tasks.py pass `stl_quality`; update in Task 4 immediately after
### [ ] Task 3: Rebuild and restart backend + render-worker
- **What**: `docker compose up -d --build backend worker render-worker beat` — picks up new Dockerfile (usd-core), new tasks, and new migrations
- **Acceptance gate**: `docker compose logs backend | grep "Application startup complete"` and `docker compose exec render-worker python3 -c "from pxr import Usd; print(Usd.GetVersion())"` both succeed
- **Dependencies**: Task 1
- **Risk**: Medium — `usd-core` pip install adds build time; if it fails the render-worker won't start
### [x] Task 4: Remove stl_quality param from domains/rendering/tasks.py
- **File**: `backend/app/domains/rendering/tasks.py`
- **What**:
- `render_still_task` (~line 48): remove `stl_quality: str = "low"` from signature and from the `render_still(...)` call
- `render_turntable_task` (~line 152): remove `stl_quality: str = "low"` from signature. Lines ~210228 inline OCC GLB generation reads `stl_quality` to choose deflection values — replace hardcoded quality-based values with DB settings reads (`scene_linear_deflection`, `scene_angular_deflection`). Pattern to follow: `export_glb.py` reads these settings via `sys_settings.get("scene_linear_deflection", 0.03)`.
- **Acceptance gate**: `grep -n "stl_quality" backend/app/domains/rendering/tasks.py` returns nothing
### [ ] Task 4: Commit all P2 work
- **What**: Stage and commit all uncommitted P2 files in a single `feat(P2)` commit
- **Acceptance gate**: `git status` shows clean working tree (except LEARNINGS.md and review-report.md which can be included)
- **Dependencies**: Tasks 13 (verify before committing)
- **Risk**: Low
### [ ] Task 5: Smoke-test end-to-end via Admin panel
- **What**: Via Admin → "Generate Missing Canonical Scenes" to regenerate GLBs with `partKeyMap` + auto-chain USD masters for existing CAD files
- **Acceptance gate**:
- `GET /api/cad/{id}/scene-manifest` returns `{"parts": [...], ...}` for a processed CadFile
- ThreeDViewer loads, click a part → MaterialPanel shows assignment provenance
- Assign a material → reload page → assignment still present
- **Dependencies**: Task 3
- **Risk**: Medium — inline tessellation block must correctly read DB settings; verify key names match migration 062 output
### [x] Task 5: Extract _blender_args.py
- **File**: `render-worker/scripts/blender_render.py`, new `render-worker/scripts/_blender_args.py`
- **What**: Move the `argparse` block (lines ~44110, ~67 lines) into `_blender_args.py` as a `parse_args()` function. `blender_render.py` calls `from _blender_args import parse_args` and uses `args = parse_args()`.
- **Acceptance gate**: `_blender_args.py` exists with the parser; `blender_render.py` line count drops by ~60
- **Dependencies**: none
- **Risk**: Low — pure refactor, no logic change
### [x] Task 6: Extract _blender_scene_setup.py
- **File**: `render-worker/scripts/blender_render.py`, new `render-worker/scripts/_blender_scene_setup.py`
- **What**: Move the MODE A / MODE B scene setup branches (lines ~131214, ~84 lines) into `_blender_scene_setup.py` as `setup_scene(args, scene)` (dispatches internally to mode A or B based on `args.blend_template`). Import and call in `blender_render.py`.
- **Acceptance gate**: `_blender_scene_setup.py` exists; `blender_render.py` line count drops by ~80
- **Dependencies**: Task 5
- **Risk**: Low — pure refactor; `bpy` available in Blender Python context
### [x] Task 7: Extract _blender_render_config.py and verify ≤ 80 lines
- **File**: `render-worker/scripts/blender_render.py`, new `render-worker/scripts/_blender_render_config.py`
- **What**: Move engine/render settings + output path logic (lines ~216258, ~43 lines) into `_blender_render_config.py` as `configure_render(scene, args, output_path, gpu_type)`. After extraction, `blender_render.py` must be ≤ 80 lines.
- **Acceptance gate**: `wc -l render-worker/scripts/blender_render.py` shows ≤ 80
- **Dependencies**: Task 6
- **Risk**: Low — pure refactor
- **Risk**: Medium — existing CAD files need backfill; may take minutes for bulk jobs to complete
## Migration Check
No new Alembic migration required. Task 4 reads existing keys (`scene_linear_deflection`, `scene_angular_deflection`) from the `system_settings` table, already present after migration 062.
Three migrations are pending in the working tree:
- `060_usd_master_asset_type.py` — additive enum value
- `061_material_assignment_layers.py` — additive JSONB columns
- `062_rename_tessellation_settings.py` — UPDATE on `system_settings` rows (already checked: migration 062 was applied per review-report)
**Before running**: read each migration file to confirm no unexpected DROP statements.
## Order Recommendation
Tasks 1 and 2 are independent — can run in parallel.
Tasks 3 and 4 are coupled — run 3 immediately before 4.
Tasks 5, 6, 7 are sequential — each further reduces blender_render.py line count.
Migrations → TypeScript check → Rebuild → Commit → Smoke test
## Risks / Open Questions
- `render_turntable_task` inline tessellation: confirm exact key names are `scene_linear_deflection` / `scene_angular_deflection` (not the old `gltf_preview_*` names) by reading `export_glb.py` before Task 4.
- After Task 7, do a smoke-test render to confirm submodule imports work inside Blender's Python interpreter.
- `usd-core` build in Docker may be slow (first build) — expected, not a problem
- Migration 062 may already be applied (review noted "verified by 0-row SELECT") — `alembic upgrade head` is idempotent if so
- Existing CAD files need backfill for `partKeyMap` in GLB extras — handled by "Generate Missing Canonical Scenes" bulk action
- `resolvePartKey()` falls back to identity (raw mesh name) for GLBs generated before this change — graceful degradation, not a blocking issue