fix(materials): universal FailedMaterial sentinel for unmatched mesh objects

- export_gltf.py: replace single-material fallback (only fired when
  len(appended)==1) with a universal sentinel that appends
  SCHAEFFLER_059999_FailedMaterial unconditionally and assigns it to
  every mesh object not matched by name-based lookup.
  Also adds in-memory magenta fallback if library append fails.
  Removes 2 temporary [DEBUG] print lines from investigation.

- blender_render.py: add FailedMaterial assignment inside
  _apply_material_library() for unmatched parts (was log-only before).
  Includes copy-on-write guard (users > 1) matching existing pattern.

Also added alias 'Stahl; Durotect CMT' (semicolon) → Durotect-Blue
to cover STEP files using semicolon separator instead of comma.

Verified: 23/25 objects matched correctly, 2 ISO8734 dowel pins
(empty material) receive SCHAEFFLER_059999_FailedMaterial as sentinel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 21:49:37 +01:00
parent 638b93bb1e
commit d938c4db1b
3 changed files with 166 additions and 291 deletions
+7 -1
View File
@@ -482,7 +482,13 @@ def _apply_material_library(parts, mat_lib_path, mat_map):
print(f"[blender_render] material assignment: {assigned_count}/{len(parts)} parts matched", flush=True)
if unmatched_names:
print(f"[blender_render] unmatched parts (palette fallback): {unmatched_names[:10]}", flush=True)
print(f"[blender_render] unmatched parts → assigning {FAILED_MATERIAL_NAME}: {unmatched_names[:10]}", flush=True)
unmatched_set = set(unmatched_names)
for part in parts:
if part.name in unmatched_set:
if part.data.users > 1:
part.data = part.data.copy()
_assign_failed_material(part)
# ── Early GPU activation (must happen BEFORE open_mainfile / Cycles init) ────