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:
@@ -557,6 +557,7 @@ def generate_usd_master_task(self, cad_file_id: str) -> dict:
|
||||
from app.models.cad_file import CadFile
|
||||
from app.models.system_setting import SystemSetting
|
||||
from app.domains.products.models import Product
|
||||
from app.services.material_service import resolve_material_map
|
||||
|
||||
pl = PipelineLogger(task_id=self.request.id)
|
||||
pl.step_start("usd_master", {"cad_file_id": cad_file_id})
|
||||
@@ -593,12 +594,26 @@ def generate_usd_master_task(self, cad_file_id: str) -> dict:
|
||||
).scalar_one_or_none()
|
||||
|
||||
color_map: dict[str, str] = {}
|
||||
raw_mat_map: dict[str, str] = {}
|
||||
if product and product.cad_part_materials:
|
||||
for entry in product.cad_part_materials:
|
||||
part_name = entry.get("part_name") or entry.get("name", "")
|
||||
hex_color = entry.get("hex_color") or entry.get("color", "")
|
||||
if part_name and hex_color:
|
||||
color_map[part_name] = hex_color
|
||||
# Build raw material map for resolve_material_map
|
||||
raw_material = entry.get("material", "")
|
||||
if part_name and raw_material:
|
||||
raw_mat_map[part_name] = raw_material
|
||||
|
||||
# Resolve raw material names to SCHAEFFLER library names via aliases
|
||||
material_map: dict[str, str] = {}
|
||||
if raw_mat_map:
|
||||
material_map = resolve_material_map(raw_mat_map)
|
||||
logger.info(
|
||||
"generate_usd_master_task: resolved %d material(s) for material_map",
|
||||
len(material_map),
|
||||
)
|
||||
|
||||
settings_rows = sess.execute(_sel(SystemSetting)).scalars().all()
|
||||
sys_settings = {s.key: s.value for s in settings_rows}
|
||||
@@ -611,9 +626,14 @@ def generate_usd_master_task(self, cad_file_id: str) -> dict:
|
||||
from app.domains.products.cache_service import compute_step_hash as _compute_step_hash_usd
|
||||
_current_hash_usd = _compute_step_hash_usd(str(step_path))
|
||||
|
||||
# Composite cache key includes deflection settings so changing them invalidates cache
|
||||
# Composite cache key includes deflection settings and material_map
|
||||
# so changing either invalidates cache (material primvars are baked into USD)
|
||||
import hashlib as _hashlib_cache
|
||||
_mat_hash = _hashlib_cache.md5(
|
||||
_json.dumps(material_map, sort_keys=True).encode()
|
||||
).hexdigest()[:12] if material_map else "none"
|
||||
effective_cache_key = (
|
||||
f"{_current_hash_usd}:{linear_deflection}:{angular_deflection}:{sharp_threshold}"
|
||||
f"{_current_hash_usd}:{linear_deflection}:{angular_deflection}:{sharp_threshold}:{_mat_hash}"
|
||||
if _current_hash_usd else None
|
||||
)
|
||||
|
||||
@@ -670,6 +690,8 @@ def generate_usd_master_task(self, cad_file_id: str) -> dict:
|
||||
"--sharp_threshold", str(sharp_threshold),
|
||||
"--cad_file_id", cad_file_id,
|
||||
]
|
||||
if material_map:
|
||||
cmd += ["--material_map", _json.dumps(material_map)]
|
||||
|
||||
log_task_event(
|
||||
self.request.id,
|
||||
|
||||
Reference in New Issue
Block a user