334 lines
14 KiB
Python
334 lines
14 KiB
Python
|
|
import bpy
|
||
|
|
import re
|
||
|
|
import os
|
||
|
|
|
||
|
|
def link_bsdf_materials():
|
||
|
|
"""Link all materials from the BSDF library file"""
|
||
|
|
|
||
|
|
library_path = r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\MATERIALS_BSDF_pallette_v1.0.blend"
|
||
|
|
|
||
|
|
if not os.path.exists(library_path):
|
||
|
|
print(f"Warning: Library file not found at {library_path}")
|
||
|
|
return []
|
||
|
|
|
||
|
|
print(f"Linking materials from: {library_path}")
|
||
|
|
|
||
|
|
# Get list of materials before linking
|
||
|
|
materials_before = set(bpy.data.materials.keys())
|
||
|
|
|
||
|
|
# Link all materials from the library file
|
||
|
|
with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):
|
||
|
|
# Link all materials
|
||
|
|
data_to.materials = data_from.materials
|
||
|
|
|
||
|
|
# Get list of newly linked materials
|
||
|
|
materials_after = set(bpy.data.materials.keys())
|
||
|
|
newly_linked = materials_after - materials_before
|
||
|
|
|
||
|
|
print(f"Linked {len(newly_linked)} materials from library")
|
||
|
|
for mat_name in sorted(newly_linked):
|
||
|
|
print(f" - {mat_name}")
|
||
|
|
|
||
|
|
return list(newly_linked)
|
||
|
|
|
||
|
|
def remap_appended_to_linked():
|
||
|
|
"""Remap any appended BSDF materials to their linked counterparts"""
|
||
|
|
|
||
|
|
print("\nChecking for appended BSDF materials to remap to linked versions...")
|
||
|
|
|
||
|
|
materials = bpy.data.materials
|
||
|
|
remapping_count = 0
|
||
|
|
|
||
|
|
# Group materials by base name (without library suffix)
|
||
|
|
material_groups = {}
|
||
|
|
|
||
|
|
for mat in materials:
|
||
|
|
# Check if it's a BSDF material (from any source)
|
||
|
|
if mat.name.startswith("BSDF_") or "BSDF_" in mat.name:
|
||
|
|
# Extract base name (remove library reference if present)
|
||
|
|
base_name = mat.name.split(".blend")[0] if ".blend" in mat.name else mat.name
|
||
|
|
base_name = base_name.split(".")[0] if "." in base_name else base_name
|
||
|
|
|
||
|
|
if base_name not in material_groups:
|
||
|
|
material_groups[base_name] = []
|
||
|
|
material_groups[base_name].append(mat)
|
||
|
|
|
||
|
|
# For each group, prefer linked materials over appended ones
|
||
|
|
for base_name, mats in material_groups.items():
|
||
|
|
if len(mats) > 1:
|
||
|
|
# Sort to prefer linked materials (they have library references)
|
||
|
|
linked_mats = [m for m in mats if m.library is not None]
|
||
|
|
appended_mats = [m for m in mats if m.library is None]
|
||
|
|
|
||
|
|
if linked_mats and appended_mats:
|
||
|
|
# Use the first linked material as target
|
||
|
|
target_material = linked_mats[0]
|
||
|
|
|
||
|
|
# Remap all appended materials to the linked one
|
||
|
|
for appended_mat in appended_mats:
|
||
|
|
if appended_mat.users > 0:
|
||
|
|
print(f"Remapping appended {appended_mat.name} ({appended_mat.users} users) to linked {target_material.name}")
|
||
|
|
appended_mat.user_remap(target_material)
|
||
|
|
remapping_count += 1
|
||
|
|
|
||
|
|
# Remove the unused appended material
|
||
|
|
if appended_mat.users == 0:
|
||
|
|
print(f"Removing unused appended material: {appended_mat.name}")
|
||
|
|
bpy.data.materials.remove(appended_mat)
|
||
|
|
|
||
|
|
# Also check for any BSDF materials that might be from old paths or different files
|
||
|
|
# and try to find matching linked materials
|
||
|
|
for mat in materials:
|
||
|
|
if mat.library is None and (mat.name.startswith("BSDF_") or "BSDF_" in mat.name):
|
||
|
|
# This is an appended BSDF material - look for a linked version
|
||
|
|
base_name = mat.name.split(".blend")[0] if ".blend" in mat.name else mat.name
|
||
|
|
base_name = base_name.split(".")[0] if "." in base_name else base_name
|
||
|
|
|
||
|
|
# Look for any linked material with the same base name
|
||
|
|
for linked_mat in materials:
|
||
|
|
if (linked_mat.library is not None and
|
||
|
|
linked_mat.name.startswith("BSDF_") and
|
||
|
|
(linked_mat.name == base_name or
|
||
|
|
linked_mat.name.startswith(base_name + ".") or
|
||
|
|
linked_mat.name == mat.name)):
|
||
|
|
|
||
|
|
if mat.users > 0:
|
||
|
|
print(f"Remapping old BSDF {mat.name} ({mat.users} users) to linked {linked_mat.name}")
|
||
|
|
mat.user_remap(linked_mat)
|
||
|
|
remapping_count += 1
|
||
|
|
|
||
|
|
# Remove the unused material
|
||
|
|
if mat.users == 0:
|
||
|
|
print(f"Removing unused old BSDF material: {mat.name}")
|
||
|
|
bpy.data.materials.remove(mat)
|
||
|
|
break
|
||
|
|
|
||
|
|
print(f"Remapped {remapping_count} appended/old BSDF materials to linked versions")
|
||
|
|
return remapping_count
|
||
|
|
|
||
|
|
def remap_missing_datablocks():
|
||
|
|
"""Remap materials that have missing/broken library links"""
|
||
|
|
|
||
|
|
print("\nChecking for missing datablocks to remap...")
|
||
|
|
|
||
|
|
materials = bpy.data.materials
|
||
|
|
remapping_count = 0
|
||
|
|
|
||
|
|
# Find materials with missing library links
|
||
|
|
missing_materials = []
|
||
|
|
for mat in materials:
|
||
|
|
if mat.library is not None and mat.library.filepath and not os.path.exists(bpy.path.abspath(mat.library.filepath)):
|
||
|
|
missing_materials.append(mat)
|
||
|
|
print(f"Found missing datablock: {mat.name} (from {mat.library.filepath})")
|
||
|
|
|
||
|
|
if not missing_materials:
|
||
|
|
print("No missing datablocks found.")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
# For each missing material, try to find a replacement
|
||
|
|
for missing_mat in missing_materials:
|
||
|
|
base_name = missing_mat.name.split(".blend")[0] if ".blend" in missing_mat.name else missing_mat.name
|
||
|
|
base_name = base_name.split(".")[0] if "." in base_name else base_name
|
||
|
|
|
||
|
|
# Look for a replacement material
|
||
|
|
replacement_found = False
|
||
|
|
|
||
|
|
# First, try to find a linked material with the same name from the current library
|
||
|
|
for mat in materials:
|
||
|
|
if (mat.library is not None and
|
||
|
|
mat.library.filepath and
|
||
|
|
os.path.exists(bpy.path.abspath(mat.library.filepath)) and
|
||
|
|
mat.name == missing_mat.name):
|
||
|
|
|
||
|
|
if missing_mat.users > 0:
|
||
|
|
print(f"Remapping missing {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}")
|
||
|
|
missing_mat.user_remap(mat)
|
||
|
|
remapping_count += 1
|
||
|
|
replacement_found = True
|
||
|
|
break
|
||
|
|
|
||
|
|
# If no exact match, try to find a BSDF material with similar name
|
||
|
|
if not replacement_found and (missing_mat.name.startswith("BSDF_") or "BSDF_" in missing_mat.name):
|
||
|
|
for mat in materials:
|
||
|
|
if (mat.library is not None and
|
||
|
|
mat.library.filepath and
|
||
|
|
os.path.exists(bpy.path.abspath(mat.library.filepath)) and
|
||
|
|
mat.name.startswith("BSDF_") and
|
||
|
|
(mat.name == base_name or
|
||
|
|
mat.name.startswith(base_name + ".") or
|
||
|
|
base_name in mat.name)):
|
||
|
|
|
||
|
|
if missing_mat.users > 0:
|
||
|
|
print(f"Remapping missing BSDF {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}")
|
||
|
|
missing_mat.user_remap(mat)
|
||
|
|
remapping_count += 1
|
||
|
|
replacement_found = True
|
||
|
|
break
|
||
|
|
|
||
|
|
# If still no replacement, try to find any valid linked material with the same base name
|
||
|
|
if not replacement_found:
|
||
|
|
for mat in materials:
|
||
|
|
if (mat.library is not None and
|
||
|
|
mat.library.filepath and
|
||
|
|
os.path.exists(bpy.path.abspath(mat.library.filepath)) and
|
||
|
|
mat.name == base_name):
|
||
|
|
|
||
|
|
if missing_mat.users > 0:
|
||
|
|
print(f"Remapping missing {missing_mat.name} ({missing_mat.users} users) to valid linked {mat.name}")
|
||
|
|
missing_mat.user_remap(mat)
|
||
|
|
remapping_count += 1
|
||
|
|
replacement_found = True
|
||
|
|
break
|
||
|
|
|
||
|
|
if not replacement_found:
|
||
|
|
print(f"Warning: No replacement found for missing material {missing_mat.name}")
|
||
|
|
|
||
|
|
print(f"Remapped {remapping_count} missing datablocks to valid linked materials")
|
||
|
|
return remapping_count
|
||
|
|
|
||
|
|
def replace_cel_materials():
|
||
|
|
"""Replace all CEL materials with their BSDF counterparts using Blender's user remapping"""
|
||
|
|
|
||
|
|
# First, link BSDF materials from library
|
||
|
|
linked_materials = link_bsdf_materials()
|
||
|
|
|
||
|
|
# Then, remap any missing datablocks
|
||
|
|
missing_remaps = remap_missing_datablocks()
|
||
|
|
|
||
|
|
# Then, remap any appended BSDF materials to linked versions
|
||
|
|
appended_remaps = remap_appended_to_linked()
|
||
|
|
|
||
|
|
print(f"\n=== STARTING MATERIAL REPLACEMENT ===")
|
||
|
|
|
||
|
|
# Custom material mappings (source -> target)
|
||
|
|
custom_mappings = {
|
||
|
|
"bag BLACK (squid ink)": "BSDF_black_SQUID-INK",
|
||
|
|
"bag WHITE": "BSDF_WHITE",
|
||
|
|
"Wheel-White": "BSDF_WHITE",
|
||
|
|
"Bag Colors": "BSDF_Bag Colors",
|
||
|
|
"cardboard": "Package_Cardboard",
|
||
|
|
"blue (triton)": "BSDF_blue-2_TRITON",
|
||
|
|
"gray (snow)": "BSDF_gray-6_SNOW",
|
||
|
|
"gray (storm)": "BSDF_gray-2_STORM",
|
||
|
|
"gray (summit)": "BSDF_gray-5_SUMMIT",
|
||
|
|
"light blue (prime)": "BSDF_blue-4_PRIME",
|
||
|
|
"yellow (summer)": "BSDF_orange-5_SUMMER",
|
||
|
|
"Accessory_CEL_gray-6_SNOW": "BSDF_gray-6_SNOW",
|
||
|
|
"Accessory_CEL_SquidInk": "BSDF_black_SQUID-INK",
|
||
|
|
"FingerScanner": "BSDF_black_SQUID-INK",
|
||
|
|
"cel BLACK (squid ink)": "BSDF_black_SQUID-INK",
|
||
|
|
"cel WHITE": "BSDF_WHITE",
|
||
|
|
"gray (stone)": "BSDF_gray-3_STONE",
|
||
|
|
"green (oxygen)": "BSDF_green-3_OXYGEN",
|
||
|
|
"orange (smile)": "BSDF_orange-3_SMILE"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get all materials in the scene
|
||
|
|
materials = bpy.data.materials
|
||
|
|
|
||
|
|
# Dictionary to store source -> target material mapping
|
||
|
|
material_mapping = {}
|
||
|
|
|
||
|
|
# Replace all CEL materials with their BSDF counterparts, ignoring numeric suffixes
|
||
|
|
cel_pattern = re.compile(r"^(CEL_.+?)(\.\d{3})?$")
|
||
|
|
bsdf_pattern = re.compile(r"^(BSDF_.+?)(\.\d{3})?$")
|
||
|
|
|
||
|
|
# Build a mapping from base BSDF name to BSDF material (without suffix)
|
||
|
|
bsdf_base_map = {bsdf_pattern.match(mat.name).group(1): mat for mat in materials if bsdf_pattern.match(mat.name)}
|
||
|
|
|
||
|
|
# Build a mapping from exact material names to materials
|
||
|
|
exact_material_map = {mat.name: mat for mat in materials}
|
||
|
|
|
||
|
|
replacements_made = 0
|
||
|
|
missing_targets = []
|
||
|
|
|
||
|
|
# Process custom mappings first
|
||
|
|
for source_name, target_name in custom_mappings.items():
|
||
|
|
if source_name in exact_material_map:
|
||
|
|
if target_name in exact_material_map:
|
||
|
|
material_mapping[exact_material_map[source_name]] = exact_material_map[target_name]
|
||
|
|
print(f"Found custom mapping: {source_name} -> {target_name}")
|
||
|
|
else:
|
||
|
|
missing_targets.append(f"{source_name} -> {target_name}")
|
||
|
|
print(f"Warning: Target material '{target_name}' not found for custom mapping '{source_name}'")
|
||
|
|
|
||
|
|
# Find all CEL materials and their BSDF counterparts
|
||
|
|
for mat in materials:
|
||
|
|
cel_match = cel_pattern.match(mat.name)
|
||
|
|
if cel_match:
|
||
|
|
base_cel = cel_match.group(1)
|
||
|
|
base_bsdf = base_cel.replace("CEL_", "BSDF_", 1)
|
||
|
|
if base_bsdf in bsdf_base_map:
|
||
|
|
material_mapping[mat] = bsdf_base_map[base_bsdf]
|
||
|
|
print(f"Found CEL mapping: {mat.name} -> {bsdf_base_map[base_bsdf].name}")
|
||
|
|
else:
|
||
|
|
missing_targets.append(f"{mat.name} -> {base_bsdf}")
|
||
|
|
print(f"Warning: No BSDF counterpart found for {mat.name}")
|
||
|
|
|
||
|
|
# Use Blender's built-in user remapping to replace ALL users
|
||
|
|
for source_material, target_material in material_mapping.items():
|
||
|
|
print(f"Remapping all users of {source_material.name} to {target_material.name}")
|
||
|
|
|
||
|
|
# Get user count before remapping
|
||
|
|
users_before = source_material.users
|
||
|
|
|
||
|
|
# Remap all users of the source material to the target material
|
||
|
|
# This catches ALL users including geometry node instances, drivers, etc.
|
||
|
|
source_material.user_remap(target_material)
|
||
|
|
|
||
|
|
# Get user count after remapping
|
||
|
|
users_after = source_material.users
|
||
|
|
|
||
|
|
replacements_this_material = users_before - users_after
|
||
|
|
replacements_made += replacements_this_material
|
||
|
|
|
||
|
|
print(f" Users before: {users_before}, after: {users_after}")
|
||
|
|
print(f" Remapped {replacements_this_material} users")
|
||
|
|
|
||
|
|
# Optional: Remove unused source materials after remapping
|
||
|
|
print(f"\nCleaning up unused source materials...")
|
||
|
|
removed_materials = 0
|
||
|
|
|
||
|
|
for source_material in material_mapping.keys():
|
||
|
|
if source_material.users == 0:
|
||
|
|
print(f"Removing unused material: {source_material.name}")
|
||
|
|
bpy.data.materials.remove(source_material)
|
||
|
|
removed_materials += 1
|
||
|
|
else:
|
||
|
|
print(f"Warning: {source_material.name} still has {source_material.users} users after remapping")
|
||
|
|
|
||
|
|
# Summary
|
||
|
|
print(f"\n=== REPLACEMENT SUMMARY ===")
|
||
|
|
print(f"Materials linked from library: {len(linked_materials)}")
|
||
|
|
print(f"Missing datablocks remapped: {missing_remaps}")
|
||
|
|
print(f"Appended->Linked remappings: {appended_remaps}")
|
||
|
|
print(f"Total materials mapped: {len(material_mapping)}")
|
||
|
|
print(f"Successful mappings: {len(material_mapping)}")
|
||
|
|
print(f"Total user remappings: {replacements_made}")
|
||
|
|
print(f"Source materials removed: {removed_materials}")
|
||
|
|
|
||
|
|
if missing_targets:
|
||
|
|
print(f"\nMissing target materials for:")
|
||
|
|
for mapping in missing_targets:
|
||
|
|
print(f" - {mapping}")
|
||
|
|
|
||
|
|
return len(material_mapping), replacements_made
|
||
|
|
|
||
|
|
# Run the replacement
|
||
|
|
if __name__ == "__main__":
|
||
|
|
replace_cel_materials()
|
||
|
|
|
||
|
|
print("\nRemaining CEL materials in file:")
|
||
|
|
cel_count = 0
|
||
|
|
for mat in bpy.data.materials:
|
||
|
|
if mat.name.startswith("CEL_"):
|
||
|
|
print(f" {mat.name} ({mat.users} users)")
|
||
|
|
cel_count += 1
|
||
|
|
|
||
|
|
if cel_count == 0:
|
||
|
|
print(" None - all CEL materials have been replaced!")
|
||
|
|
|
||
|
|
print("\nBSDF materials in file:")
|
||
|
|
for mat in bpy.data.materials:
|
||
|
|
if mat.name.startswith("BSDF_"):
|
||
|
|
print(f" {mat.name} ({mat.users} users)")
|