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:
@@ -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 060–062 + 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 060–062
|
||||
- **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 ~210–228 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 1–3 (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 ~44–110, ~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 ~131–214, ~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 ~216–258, ~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
|
||||
|
||||
Reference in New Issue
Block a user