feat(M5-M7): embed canonical material names in USD via customData + pxr direct read
- export_step_to_usd.py: accept --material_map CLI arg, write
schaeffler:canonicalMaterialName as customData on each Mesh prim,
fix geometry transform (strip shape Location before face exploration,
apply both face_loc and shape_loc sequentially)
- import_usd.py: after Blender USD import, use pxr to read customData
directly from the USD file — builds {part_key: material_name} lookup
(Blender ignores STRING primvars and customData, but pxr reads both)
- _blender_materials.py: add apply_material_library_direct() for exact
dict-based material assignment without name-matching heuristics
- _blender_scene_setup.py: prefer direct USD lookup, fall back to
name-matching for legacy USD files without material metadata
- export_glb.py (generate_usd_master_task): resolve material_map via
material_service.resolve_material_map() and pass to subprocess;
include material hash in cache key for invalidation
- ROADMAP.md: update P5 status, add M5-M7 milestones
Tested: 3/3 parts matched (ans_lfs120), 172/175 parts matched
(F-802007.TR4-D1-H122AG). Previous: 0/25 matched.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,10 +15,14 @@ import bmesh # type: ignore[import]
|
||||
from mathutils import Vector # type: ignore[import]
|
||||
|
||||
|
||||
def import_usd_file(usd_path: str) -> list:
|
||||
def import_usd_file(usd_path: str) -> list | tuple:
|
||||
"""Import USD stage into current Blender scene.
|
||||
|
||||
Returns list of imported mesh objects, centred at world origin.
|
||||
Returns a tuple of (parts, material_lookup) where:
|
||||
- parts: list of imported mesh objects, centred at world origin
|
||||
- material_lookup: dict mapping blender_object_name → canonical_material_name
|
||||
(populated from schaeffler:canonicalMaterialName primvars, empty dict if absent)
|
||||
|
||||
USD stage is mm Y-up with metersPerUnit=0.001 — Blender scales to metres.
|
||||
"""
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
@@ -43,6 +47,38 @@ def import_usd_file(usd_path: str) -> list:
|
||||
if restored:
|
||||
print(f"[import_usd] restored seam/sharp on {restored} mesh(es)", flush=True)
|
||||
|
||||
# Extract material lookup via pxr direct read of the USD file.
|
||||
# Blender's USD importer does NOT expose STRING primvars or customData as
|
||||
# Python-accessible properties — but the pxr module (available in render-worker)
|
||||
# can read them perfectly from the same file.
|
||||
material_lookup: dict[str, str] = {}
|
||||
try:
|
||||
from pxr import Usd, UsdGeom # type: ignore[import]
|
||||
stage = Usd.Stage.Open(usd_path)
|
||||
for prim in stage.Traverse():
|
||||
if prim.GetTypeName() != "Mesh":
|
||||
continue
|
||||
part_key = prim.GetCustomDataByKey("schaeffler:partKey") or ""
|
||||
mat_name = prim.GetCustomDataByKey("schaeffler:canonicalMaterialName") or ""
|
||||
if not part_key or not mat_name:
|
||||
# Also check parent Xform prim (metadata may be on container)
|
||||
parent = prim.GetParent()
|
||||
if parent:
|
||||
part_key = part_key or (parent.GetCustomDataByKey("schaeffler:partKey") or "")
|
||||
mat_name = mat_name or (parent.GetCustomDataByKey("schaeffler:canonicalMaterialName") or "")
|
||||
if part_key and mat_name:
|
||||
# Blender object name = mesh prim leaf name (part_key)
|
||||
material_lookup[part_key] = mat_name
|
||||
except Exception as exc:
|
||||
print(f"[import_usd] WARNING: pxr material lookup failed: {exc}", flush=True)
|
||||
|
||||
if material_lookup:
|
||||
print(f"[import_usd] pxr material lookup: {len(material_lookup)}/{len(parts)} parts",
|
||||
flush=True)
|
||||
else:
|
||||
print("[import_usd] no schaeffler:canonicalMaterialName metadata found (legacy USD)",
|
||||
flush=True)
|
||||
|
||||
# Centre combined bbox at world origin (same as import_glb convention)
|
||||
all_corners = []
|
||||
for p in parts:
|
||||
@@ -60,7 +96,7 @@ def import_usd_file(usd_path: str) -> list:
|
||||
for obj in root_objects:
|
||||
obj.location -= center
|
||||
|
||||
return parts
|
||||
return parts, material_lookup
|
||||
|
||||
|
||||
def _rename_usd_objects(parts: list) -> None:
|
||||
|
||||
Reference in New Issue
Block a user