Files
HartOMat/ROADMAP.md
T

29 KiB
Raw Blame History

HartOMat — Master Roadmap

Consolidated: 2026-03-11 Branch: refactor/v2 Sources merged: PLAN.md (Phases AF), PLAN_REFACTOR.md (Phases 18), plan.md (GMSH), docs/rfcs/0001, visual-audit-report.md


What Is Done

Area Detail
Phase A runtime Flamenco removed from the active pipeline, render-worker replaced the old Blender service path, MinIO added; legacy blender-renderer/ and threejs-renderer/ directories still remain in the repo
Phase B Domain-driven project structure, Tenant model + RLS migrations 035/036, Tenant management UI
Phase C WorkflowDefinition model, standard workflows seeded, React Flow Workflow Editor
Phase D OCC mesh attributes extraction, Blender integration
Phase E MediaAsset catalog (model + API + frontend)
Sharp Edges V02 GCPnts curve sampling → 17,129 segment pairs in GLB extras → Blender KD-tree marks sharp+seam
Tessellation presets Draft/Standard/Fine preset buttons in Admin UI, default deflections updated
Media cache-bust ?v={file_size_bytes} in download URLs + Cache-Control: no-cache headers
GPU activation order Fix: _activate_gpu() called before AND after open_mainfile to survive engine reset
Material system Aliases-first lookup, get_material_library_path() via AssetLibrary
Pipeline task split backend/app/tasks/step_tasks.py is now a 23-line compatibility shim; active task implementations live in backend/app/domains/pipeline/tasks/
Render job tracking RenderJobDocument, PipelineLogger, and cancel-via-real-celery_task_id are already wired into the render pipeline
Tenant isolation baseline TenantContextMiddleware, JWT tenant_id, and the global_admin / tenant_admin role hierarchy are in place for HTTP requests
Hash groundwork compute_step_hash() exists and CadFile.step_file_hash is already persisted during thumbnail processing
Performance profiling @timed_step decorator, Blender timing laps, MeshRegistry (17→8 traverse calls eliminated), _pipeline_session context manager, KD-tree spatial pre-filter for sharp edges, batch material library append, GMSH single-session batching
Part-materials validation PUT /cad/{id}/part-materials and PUT /cad/{id}/manual-material-overrides now reject unknown part names against parsed_objects
Dead code cleanup Verified: blender-renderer/, threejs-renderer/, flamenco/, renderproblems_tmp/ all deleted; STL endpoints/settings removed; no Pillow imports; docker-compose clean

🔎 Status Snapshot

Verified against the repository on 2026-03-13.

Priority Status Re-evaluated state
1. Pipeline Cleanup Foundation Done All P1 gates pass: dead code absent, blender_render.py split into 8 submodules (68 lines), step_tasks.py is 24 lines, legacy dirs deleted
2. USD Foundation Without Viewer Regression Done export_step_to_usd.py, import_usd.py, usd_master MediaAsset, scene-manifest, partKey, part_key_service.py all implemented; migrations 060-062 applied
3. Tessellation and Topology Quality Done GMSH 4.15.1 installed, _tessellate_with_gmsh() implemented, tessellation_engine wired admin → pipeline → CLI
4. Viewer Migration to Canonical Part Identity Done GLB node extras.partKey injected; userData.partKey stamped in viewer; hover tooltip shows slug; scene manifest verified
5. Canonical USD Export and Render Migration Done All milestones complete: M1M7; production GLB deprecated; digit-only prim name fix (p_ prefix); EXPORT_GLB_PRODUCTION enum removed
6. Admin and Product Surface Simplification Done Settings renamed scene_*/render_*, migration applied, Admin progressive disclosure, ProductDetail single canonical scene, MediaAssetType deprecated values commented
7. Render Job Tracking and Structured Logging Done RenderJobDocument, migration 048, PipelineLogger, and revoke-by-real-task-id are present
8. Tenant Isolation Completion Done HTTP: TenantContextMiddleware + JWT tenant_id; Celery: set_tenant_context_sync() in all pipeline tasks; all routers migrated to require_global_admin
9. Hash-Based Scene Conversion Caching Done Composite cache key (hash + deflection + engine) in both geometry and USD tasks; disk existence check; render_config stored; step_hash in API
10. UI/UX Polish Done Empty states, Admin help text, notification batching, per-line reject in OrderDetail with portal modal, kanban drag-to-reject with native HTML5 DnD

🗺 Open Work — Reconciled Priorities

This roadmap now treats the USD refactor as an implementation workstream, not as a blocked strategic idea.

The key architectural clarification from docs/rfcs/0001-step-to-usd-workflow.md is:

  • USD becomes the canonical persisted scene asset
  • the browser does not need to render USD directly
  • the current 3D viewer workflow is preserved through a derived preview asset plus canonical partKey

That removes the old assumption that USD work must wait for a Three.js USD loader.

Priority 1 — Pipeline Cleanup Foundation

Goal: Reduce refactor risk by simplifying the current pipeline before introducing canonical scene concepts.

This priority combines dead-code deletion and task decomposition because both are prerequisites for a controlled cut-over to USD.

Status: In progress. M2 is complete; M1 and M3 remain open.

Milestones:

  • M1: Dead code deleted — Pillow block, STL settings, orphaned directories
  • M2: step_tasks.py decomposed into backend/app/domains/pipeline/tasks/ submodules
  • M3: blender_render.py decomposed into render-worker/scripts/_blender_*.py submodules

File targets:

Action Path
DELETE lines 798851 render-worker/scripts/blender_render.py (Pillow overlay, else: branch never runs)
DELETE blender-renderer/ directory
DELETE threejs-renderer/ directory
DELETE flamenco/ directory
DELETE renderproblems_tmp/
DONE backend/app/tasks/step_tasks.py — reduced to compatibility shim only
REMOVE settings admin.py: VALID_STL_QUALITIES, stl_quality, generate-missing-stls endpoint
REMOVE endpoint cad.py: POST /cad/{id}/generate-stl/{quality}
DONE backend/app/domains/pipeline/tasks/extract_metadata.py — metadata extraction
DONE backend/app/domains/pipeline/tasks/render_thumbnail.py — thumbnail render task
DONE (combined) backend/app/domains/pipeline/tasks/render_order_line.py — still/dispatch pipeline entry
OPEN turntable-specific pipeline task split still needs to be carved out explicitly if kept as a separate concern
THIN (< 80 lines) backend/app/tasks/step_tasks.py — dispatch only
CREATE render-worker/scripts/_blender_gpu.py
CREATE render-worker/scripts/_blender_import.py
CREATE render-worker/scripts/_blender_materials.py
CREATE render-worker/scripts/_blender_camera.py
CREATE render-worker/scripts/_blender_scene.py
THIN (< 80 lines) render-worker/scripts/blender_render.py — entry point only

Acceptance gates:

  • grep -r "VALID_STL_QUALITIES\|stl_quality\|from PIL\|from Pillow" backend/ render-worker/ → 0 matches
  • wc -l backend/app/tasks/step_tasks.py → < 100 lines
  • wc -l render-worker/scripts/blender_render.py → < 80 lines
  • Upload 81113-l_cut.stp, trigger thumbnail → still renders correctly (no regression)
  • ls blender-renderer/ threejs-renderer/ flamenco/ → all return "No such file"

Priority 2 — USD Foundation Without Viewer Regression

Goal: Introduce canonical part identity and the three-layer material assignment model while keeping the current GLB-based browser UX working end-to-end.

Status: Not started in code. Architecture decisions are documented, but repo work has not begun.

Milestones:

  • M1: export_step_to_usd.py produces valid USD with part hierarchy and hartomat:partKey on every prim
  • M2: usd_master MediaAsset type exists in DB and is stored after each export
  • M3: GET /api/cad/{id}/scene-manifest returns partKey list with effective assignments
  • M4: PUT /api/cad/{id}/part-materials accepts {partKey → materialName} map and persists it
  • M5: Browser ThreeDViewer saves material overrides keyed by partKey, survives page reload

File targets:

Action Path
ADD pip install render-worker/Dockerfileusd-core>=24.11 (provides pxr module) decided
CREATE render-worker/scripts/export_step_to_usd.py — XCAF → USD, hierarchy + metadata + partKey
ADD enum value backend/app/domains/media/models.pyusd_master to MediaAssetType
CREATE migration backend/alembic/versions/060_usd_master_asset_type.py
ADD JSONB columns backend/app/domains/products/models.pyCadFile.source_material_assignments, resolved_material_assignments, manual_material_overrides
CREATE migration backend/alembic/versions/061_material_assignment_layers.py
CREATE backend/app/services/part_key_service.pygenerate_part_key(xcaf_label), build_scene_manifest()
CREATE backend/app/domains/products/schemas.pySceneManifest, PartEntry Pydantic models
ADD endpoint backend/app/api/routers/cad.pyGET /cad/{id}/scene-manifest
MODIFY endpoint backend/app/api/routers/cad.pyGET/PUT /cad/{id}/part-materials → partKey-keyed
ADD task backend/app/domains/pipeline/tasks/export_glb.pygenerate_usd_master_task (dual-writes beside GLB)
CREATE frontend/src/api/sceneManifest.tsSceneManifest interface, fetchSceneManifest()

Open questions to decide before M1:

  • None blocking at the architecture level. The roadmap decisions for usd-core and index-space seam/sharp primvars are already captured in docs/plans/0001-step-to-usd-implementation.md.

Acceptance gates:

  • python3 export_step_to_usd.py --step_path 81113-l_cut.stp → valid .usd file, 25 part prims, each has hartomat:partKey attribute
  • GET /api/cad/{id}/scene-manifest returns parts[] array with part_key, source_name, effective_material, is_unassigned
  • Click part in ThreeDViewer → assign material → reload page → material still assigned (persisted via partKey, not mesh name)
  • CAD file with mismatched Excel names: UI shows unmatched_source_rows count > 0 and unassigned parts highlighted
  • No regression: existing click/select/isolate/ghost/hide still works in browser

References:

  • RFC: docs/rfcs/0001-step-to-usd-workflow.md
  • Execution checklist: docs/plans/0001-step-to-usd-implementation.md

Priority 3 — Tessellation and Topology Quality

Goal: Eliminate fan triangles on cylindrical surfaces (rings, bearings) and produce clean seams for UV unwrap.

Status: Not started in code. This is still a pure planning workstream at the moment.

Milestones:

  • M1: GMSH 4.15+ installed in render-worker container
  • M2: export_step_to_gltf.py --tessellation_engine gmsh produces fan-free GLB
  • M3: tessellation_engine system setting wired through CLI → Admin UI dropdown

File targets:

Action Path
ADD pip install render-worker/Dockerfilegmsh>=4.15.0
ADD arg + function render-worker/scripts/export_step_to_gltf.py--tessellation_engine, _tessellate_with_gmsh()
ADD setting backend/app/api/routers/admin.pytessellation_engine in SETTINGS_DEFAULTS + SettingsOut
MODIFY task backend/app/domains/pipeline/tasks/export_glb.py — read setting, pass to CLI
ADD UI frontend/src/pages/Admin.tsx — dropdown: OCC vs. GMSH

(Full task breakdown in plan.md)

Acceptance gates:

  • docker compose exec render-worker python3 -c "import gmsh; print(gmsh.__version__)"4.15.x
  • python3 export_step_to_gltf.py --step_path 81113-l_cut.stp --tessellation_engine gmsh → no vertex with valence > 10 at cylinder seam edges (inspect via Blender Mesh Analysis overlay)
  • Standard preset output size with GMSH ≤ 2× size with OCC at same deflection
  • Sharp edge pairs still extracted and injected into GLB extras after GMSH tessellation

Note: Better tessellation directly benefits Priority 2 (USD seam/sharp payload) and Priority UV-unwrap work.

Priority 4 — Viewer Migration to Canonical Part Identity

Goal: Move browser interactions from raw GLB mesh-name matching to canonical partKey without any UX regression.

Milestones:

  • M1: Preview GLB derivation embeds partKey as mesh extras.partKey on every selectable object
  • M2: ThreeDViewer reads partKey from scene manifest + mesh extras on click, no longer uses raw mesh.name
  • M3: MaterialPanel shows partKey, source name, assignment provenance; saves overrides by partKey
  • M4: Unmatched source rows and unassigned parts surfaced in MaterialPanel reconciliation section

File targets:

Action Path
CREATE frontend/src/api/sceneManifest.tsSceneManifest + PartEntry interfaces
MODIFY frontend/src/components/cad/ThreeDViewer.tsx — use partKey from scene manifest for selection, isolation, ghost
MODIFY frontend/src/components/cad/MaterialPanel.tsx — show provenance, unmatched/unassigned sections
MODIFY frontend/src/api/cad.ts — update PartMaterialsMap interface to { [partKey: string]: string }
MODIFY backend/app/api/routers/cad.pyGET/PUT /cad/{id}/part-materials keyed by partKey with provenance
ADD util render-worker/scripts/export_step_to_gltf.py — embed partKey into mesh extras during GLB export

Acceptance gates:

  • mesh.userData.partKey exists on every mesh object in the Three.js scene after GLB load
  • Select a part → DevTools shows the assignment payload contains part_key: "ring_outer", not mesh_name: "RingOuter_AF0"
  • Upload file with 3 unmatched Excel rows → MaterialPanel shows "3 unmatched source rows"
  • After full page reload: all manual material assignments are restored correctly
  • Isolation, hide, and ghost still work as before (no regression)

Priority 5 — Canonical USD Export and Render Migration

Goal: Switch Blender still/turntable renders to consume the canonical USD stage, retiring the production GLB as an intermediate render artifact.

Milestones:

  • M1: render-worker/scripts/import_usd.py — Blender can import USD + restore seam/sharp from primvars
  • M2: Still render from USD matches current production GLB render quality (side-by-side comparison)
  • M3: Turntable render from USD works end-to-end
  • M4: generate_production_glb_task bypassed; renders consume usd_master directly
  • M5: Material metadata as USD primvars — canonical material names baked into the USD asset
  • M6: Geometry transform fix for assembly hierarchy — correct transforms for multi-level assemblies
  • M7: Material map resolution at USD export time — USD becomes self-contained with canonical material names

M5: Material metadata as USD primvars

The current USD render path matches 0/25 parts for material assignment because Blender has no way to resolve canonical material names from the imported USD prims. This milestone embeds that metadata directly into the USD file.

  • Pass resolved material_map to export_step_to_usd.py via --material_map CLI arg (JSON string)
  • Write hartomat:canonicalMaterialName as a STRING primvar on each Mesh prim during USD export
  • import_usd.py reads the primvar after import and performs direct material lookup (no name-matching heuristics)
  • Acceptance: Blender log shows 25/25 parts matched for material assignment from USD

M6: Geometry transform fix for assembly hierarchy

Multi-level assemblies (3+ nesting levels) produce wrong geometry in USD because _extract_mesh() does not account for shape Location before face exploration and does not accumulate parent transforms through _traverse_xcaf recursion.

  • _extract_mesh() must strip shape Location before face exploration, then apply both transforms sequentially
  • For deeply nested assemblies (3+ levels): accumulate parent transforms through _traverse_xcaf recursion
  • Acceptance: USD render matches STEP import geometry side-by-side (no displaced or rotated parts)

M7: Material map resolution at USD export time

Currently generate_usd_master_task does not resolve the material map before passing it to the export subprocess. This means the USD file has no material metadata, making it impossible for Blender to assign materials without external context.

  • generate_usd_master_task must resolve material_map via material_service.resolve_material_map() and pass to subprocess
  • This makes the USD file self-contained: canonical material names baked into the asset
  • Acceptance: USD file inspected via pxr shows hartomat:canonicalMaterialName on every mesh prim

File targets:

Action Path
CREATE render-worker/scripts/export_step_to_usd.py — STEP→USD exporter (seam/sharp payload on mesh prims)
CREATE render-worker/scripts/import_usd.py — Blender USD import helper: reads primvars:hartomat:seamEdgeVertexPairs, marks seam+sharp
MODIFY render-worker/scripts/blender_render.py — accept --usd_path flag alongside --glb_path
MODIFY backend/app/services/render_blender.py — pass usd_master asset path when available
MODIFY backend/app/domains/pipeline/tasks/export_glb.py — retire generate_gltf_production_task once USD path validated
KEEP (compat) render-worker/scripts/export_gltf.py — retained as fallback until USD path confirmed stable
MODIFY (M5) render-worker/scripts/export_step_to_usd.py — accept --material_map CLI arg, write hartomat:canonicalMaterialName primvar on each Mesh prim
MODIFY (M5) render-worker/scripts/import_usd.py — read hartomat:canonicalMaterialName primvar after import, use for direct material lookup
MODIFY (M6) render-worker/scripts/export_step_to_usd.py — fix _extract_mesh() to strip shape Location; accumulate parent transforms in _traverse_xcaf
MODIFY (M7) backend/app/domains/pipeline/tasks/export_glb.py (or USD task) — call resolve_material_map() and pass result to export subprocess

Acceptance gates:

  • render_still --usd_path usd_master.usd → PNG output visually identical to current production GLB render (diff tolerance < 5% SSIM)
  • Blender log shows [USD_IMPORT] 25 parts imported, 5044 seam/sharp edges restored (not reconstructed by angle)
  • GET /api/media?asset_type=gltf_production returns 0 new entries after switch (old records preserved)
  • Turntable MP4 plays without texture or material pop artifacts
  • (M5) Blender log shows [USD_IMPORT] 25/25 parts matched for material assignment (not 0/25)
  • (M6) USD render geometry matches STEP import geometry side-by-side for multi-level assemblies (no displaced/rotated parts)
  • (M7) python3 -c "from pxr import Usd; stage=Usd.Stage.Open('usd_master.usd'); [print(p.GetAttribute('primvars:hartomat:canonicalMaterialName').Get()) for p in stage.Traverse()]" → prints canonical material name for every mesh prim

Priority 6 — Admin and Product Surface Simplification

Goal: Remove the geometry-GLB vs production-GLB mental model from admin, product detail, and repair flows.

Milestones:

  • M1: Admin tessellation settings collapsed from 4 knobs to scene_* + preview_* (2 or 3 knobs max)
  • M2: Bulk actions renamed from generate-missing-geometry-glbsgenerate-missing-canonical-scenes
  • M3: ProductDetail shows single canonical scene status card, not dual-GLB

File targets:

Action Path
MODIFY backend/app/api/routers/admin.py — rename/collapse tessellation settings; new bulk action labels
CREATE migration backend/alembic/versions/06x_rename_tessellation_settings.py — UPDATE system_settings SET key = 'scene_linear_deflection' WHERE key = 'gltf_production_linear_deflection'
MODIFY frontend/src/pages/Admin.tsx — simplified tessellation panel: scene quality + optional preview override
MODIFY frontend/src/pages/ProductDetail.tsx — single canonical scene card (status, regenerate button)
MODIFY backend/app/domains/media/models.py — deprecate gltf_geometry / gltf_production (keep values, add deprecated=True metadata)

Acceptance gates:

  • Admin Settings page has max 3 tessellation quality fields (down from 4)
  • ProductDetail page: one "Canonical Scene" status section, no separate geometry/production rows
  • GET /api/admin/settings no longer exposes gltf_preview_linear_deflection key (replaced by scene_linear_deflection)

Priority 7 — Render Job Tracking and Structured Logging

Goal: Fix broken render job cancellation (synthetic render-{line_id} ID never matches real Celery task ID) and establish structured per-step logging.

Status: Done, aside from any follow-up polish.

Milestones:

  • M1: RenderJobDocument schema + migration; tasks write real self.request.id to DB
  • M2: Cancel endpoint reads celery_task_id from job doc and calls revoke() — actually stops task
  • M3: PipelineLogger integrated in all task files; every step emits [STEP] start/done/error with duration

File targets:

Action Path
CREATE backend/app/domains/rendering/job_document.pyRenderJobDocument Pydantic model, update_step(), set_state()
CREATE backend/app/core/pipeline_logger.pyPipelineLogger(step_start/done/error) writing to logging + Redis SSE
DONE backend/alembic/versions/048_render_job_document.py — adds render_job_doc JSONB to order_lines
MODIFY backend/app/domains/pipeline/tasks/render_order_line.py — write celery_task_id + step events to job doc
MODIFY backend/app/domains/pipeline/tasks/render_thumbnail.py — same
MODIFY backend/app/api/routers/orders.py — cancel reads render_job_doc.celery_task_id, calls celery.control.revoke()

Acceptance gates:

  • Start a 60s render task → click Cancel → celery inspect active shows task is gone within 15s
  • GET /api/orders/{id}/lines/{line_id} response includes render_job_doc.steps[] with per-step duration_s
  • Worker log shows [THUMBNAIL] done in 34.2s format (not bare f-strings)

Priority 8 — Tenant Isolation Completion

Goal: Finish tenant isolation hardening, especially for non-HTTP execution paths.

Status: In progress. HTTP-side RLS enforcement is now real; task-side propagation is the remaining gap.

Milestones:

  • M1: TenantContextMiddleware registered; all HTTP requests set RLS context from JWT
  • M2: All Celery tasks call set_tenant_context(db, tenant_id) at task start
  • M3: global_admin + tenant_admin roles in DB; require_admin()require_global_admin()

File targets:

Action Path
CREATE backend/app/core/middleware.pyTenantContextMiddleware(BaseHTTPMiddleware)
MODIFY backend/app/main.pyapp.add_middleware(TenantContextMiddleware)
MODIFY backend/app/utils/auth.pycreate_access_token() embeds tenant_id in JWT claims
MODIFY backend/app/tasks/pipeline/thumbnail.py, extract.py, stills.py, turntable.pyset_tenant_context() at start
DONE backend/alembic/versions/049_role_hierarchy.py — adds global_admin and tenant_admin
PARTIAL Routers are mixed between new require_global_admin() usage and backward-compatible require_admin() aliases

Acceptance gates:

  • Login as tenant A user → GET /api/products returns 0 results when tenant A has no products, even if tenant B has 50
  • Verify via: SELECT count(*) FROM products WHERE tenant_id != '<tenantA_id>' returns 0 from within tenant A session
  • Celery task logs show [TENANT] context set: tenant_id=<uuid> at start
  • GET /api/admin/users returns 403 for tenant_admin role (only global_admin can list all users)

Priority 9 — Hash-Based Scene Conversion Caching

Goal: Extend the existing STEP-hash plumbing so canonical scene + preview derivatives can skip unnecessary re-tessellation.

Status: Partial foundation already exists.

Milestones:

  • M1: Existing cad_files.step_file_hash is reused or renamed for canonical-scene caching
  • M2: Export task checks hash before processing — returns cached asset UUID on hit
  • M3: Hash invalidated correctly when admin forces reprocess or deflection settings change

File targets:

Action Path
DONE backend/app/domains/products/models.pyCadFile.step_file_hash: str | None already exists
DONE backend/app/domains/products/cache_service.pycompute_step_hash(file_path) already exists
OPEN backend/app/domains/pipeline/tasks/export_glb.py (or future USD task) — hash check before subprocess call
OPEN optional migration to rename step_file_hashstep_hash only if naming consistency is worth the churn

Acceptance gates:

  • Upload same STEP file twice → second task completes in < 2s (cache hit logged: [CACHE] hash match, skipping tessellation)
  • Change deflection setting → force reprocess → new export runs fresh (hash same but settings changed, cache bypassed)
  • GET /api/cad/{id} response includes step_hash field

Priority 10 — UI/UX Polish

Goal: Address independent UI items from visual-audit-report.md that require no backend changes.

Status: Partial. Some milestones are already shipped and should be removed from the active queue.

Milestones:

  • M1: Tooltip/help text on every Admin settings input — mostly done
  • M2: Empty state messages in MediaBrowser, ProductLibrary, Orders — partially done
  • M3: Notification batching — group per-render noise into job summaries
  • M4: Mobile navigation — hamburger menu at < 768px — done
  • M5: Kanban rejection flow — drag-to-reject with reason field

File targets:

Action Path
MODIFY frontend/src/pages/Admin.tsxtitle attributes on all inputs; help text below complex settings
MODIFY frontend/src/pages/MediaBrowser.tsx — empty state: "No assets yet — upload a STEP file to get started"
MODIFY frontend/src/pages/ProductLibrary.tsx — empty state
MODIFY frontend/src/pages/Orders.tsx — empty state
MODIFY frontend/src/components/layout/Layout.tsx — hamburger + slide-in nav for mobile
MODIFY frontend/src/pages/OrderDetail.tsx — reject button with reason modal
CREATE frontend/src/components/shared/Tooltip.tsx — reusable tooltip wrapper

Acceptance gates:

  • All Admin settings inputs have visible help text or tooltip (manual check: no input label without explanation)
  • Mobile viewport (375px): no horizontal scroll, nav accessible via hamburger
  • Submit a render → NotificationCenter shows one "Render complete (3 files)" summary, not 3 individual toasts

Dependency Graph

Priority 1 (Pipeline Cleanup Foundation)
  └── Priority 2 (USD Foundation Without Viewer Regression)
        └── Priority 4 (Viewer Migration to Canonical Part Identity)
              └── Priority 5 (Canonical USD Export and Render Migration)
                    └── Priority 6 (Admin and Product Surface Simplification)
                          └── Priority 9 (Hash-Based Scene Conversion Caching)

Priority 3 (Tessellation and Topology Quality)
  └── Priority 5 (Canonical USD Export and Render Migration)

Priority 8 remaining work (Celery tenant context)      — can run in parallel
Priority 10 remaining polish                           — independent

What To Do Next

All 10 original priorities are complete. Additionally completed as of 2026-03-13:

  • Performance profiling and optimization — 12-task plan fully implemented (see plan.md)
  • Part-materials input validation — prevents bogus keys from being persisted
  • Dead code cleanup — verified all legacy directories, endpoints, and imports are gone

Remaining future work (not yet planned):

  • Automated test suite (currently no tests)
  • Batch material assignment UI improvements
  • Additional USD features (instancing, LOD)
  • Production deployment hardening (health checks, monitoring)
  • Draw call batching for 100+ part assemblies (deferred from performance plan — high risk, needs real test data)
  • Merge dual STEP parse into single read (deferred — OCP/OCC.Core API compatibility concerns)

Archive

Old planning files are kept for reference but superseded by this document:

  • PLAN.md — original Phase AF plan (Phases AE complete, Phase F = Priority 9 here)
  • PLAN_REFACTOR.md — 1,173-line architectural plan (Phases 18 mapped to Priorities 28 above)
  • plan.md — GMSH tessellation implementation plan (Priority 3)
  • docs/rfcs/0001-step-to-usd-workflow.md — USD RFC (Priorities 2, 4, and 5)
  • docs/plans/0001-step-to-usd-implementation.md — actionable USD implementation plan
  • review-report.md — latest code review results
  • visual-audit-report.md — UX audit results