Resolve merge conflict in NewProject.bat - take most recent changes

This commit is contained in:
Nathan
2025-09-15 10:31:48 -06:00
10 changed files with 4132 additions and 9 deletions

View File

@@ -0,0 +1,81 @@
<!-- Generated by SpecStory -->
# Remap non-working material names (2025-09-05 21:38Z)
_**User (2025-09-09 19:45Z)**_
remapped mats that are currently not working:
bag BLACK (squid ink) => BSDF_black_SQUID-INK
bag WHITE => BSDF_WHITE
orange (smile) => BSDF_orange-3_SMILE
orange (blaze) => BSDF_orange-1_BLAZE
---
_**Assistant**_
<think><details><summary>Thought Process</summary>**Debugging material mapping issues**
There's a warning saying "Target material not found," so I need to dig into this and figure out why the mappings aren't functioning. First, I should confirm the actual material names in the scene and the linked library, but I can't query Blender data here. I also want to adjust the code to be more flexible, like tolerating name variants and case differences. If the library path isn't right, it should print an error but might be okay. I'll address potential issues with suffixes and name matching too.
**Fixing material mapping issues**
I need to address the fact that names like "bag BLACK (squid ink).001" and "bag WHITE.001" won't match due to their numeric suffixes. So, I should implement case-insensitive matching for custom mappings.
Also, when linking materials through link_bsdf_materials, they need to be in bpy.data.materials first. I noticed potential spelling differences between different naming conventions, like hyphens versus underscores. Moreover, "orange (blaze)" needs a mapping added for "BSDF_orange-1_BLAZE." Finally, the user mentioned four remapped materials that aren't currently working, so that's an additional concern.
**Improving custom mapping functionality**
It looks like the script attempted to remap materials, but the results didn't change, which indicates they might be flagged as missing targets. The issue could stem from source names having suffixes we didn't account for, or they not being prefixed with "CER_". Since these are custom names like "bag WHITE," it's crucial to ensure their suffixes are properly matched. I plan to modify replace_cel_with_bsdf.py to enhance custom mappings by normalizing material names, stripping numeric suffixes like ".###," and matching them in a case-insensitive manner.
**Enhancing material mapping logic**
I need to allow for cases where the source name is "bag WHITE" and actual material names could be "bag WHITE.001." To implement this, I plan to build a map from normalized names to materials by defining a function that lowercases and strips trailing numeric suffixes, while also ensuring ".blend" isnt included. I'll use a regex pattern to achieve this, and consider collapsing whitespace and unifying hyphens and underscores.
For matching target names, I should ignore suffixes when using the bsdf_base_map, allowing for more accurate mapping when the linked material has variations like "BSDF_WHITE.001."
**Implementing material mapping changes**
I need to adjust the script to ensure the mapping works correctly for materials with suffixes, such as "bag WHITE" matching "bag WHITE.001." My plan includes constructing a mapping for normalized base names to actual materials, which requires creating a helper function to normalize names by stripping suffixes and standardizing case.
I will modify the custom mapping loop to handle multiple materials that match the base name. Ill also add the new mapping for "orange (blaze)." A note on formatting: I must keep my indentation consistent while making these changes. I'll check for any lint issues before applying the patch to ensure everything is in order.
**Preparing to apply file changes**
I need to apply some context-sensitive changes to the file. Even though we have the content already attached, I'll err on the side of caution and use the read_file tool to ensure I have the exact file content in the workspace. I will add the new mapping for "orange (blaze)" to the existing custom_mappings dict.
Additionally, Ill insert helper functions and update the custom mapping logic to make it case- and suffix-insensitive, while ensuring my imports are intact. Then, Ill assemble the patch and add a brief status update before execution, keeping the changes concise and directly addressing the user's query.</details></think>
---
I'll update your Blender script to make custom name remapping robust to numeric suffixes and case differences, add the missing "orange (blaze)" mapping, and resolve targets by base BSDF names. Applying the edits now.
---
---
<think><details><summary>Thought Process</summary>**Updating project status**
I want to provide a brief status update after fixing some lints, along with a concise response confirming the changes and instructions for running them in Blender. Since we only modified an existing script using the apply_patch tool, we can avoid generating new scripts. I'll keep the code concise as per our guidelines, and while we don't need to show the code, I might include a snippet if necessary. I'll also remember to manage any todos, but we may skip creating a list for smaller tasks since tool calls were used.
**Clarifying tool use**
I need to remember that I should call todo_write to update the todo list after completing tasks, but since we didn't create one, that's okay. I should also add a status update before any new batch, specifically before calling read_lints. Since there's only read_lints to run, Im considering using multi_tool_use.parallel, but it might not be necessary to read a file for confirmation. Let's just move ahead and run read_lints.</details></think>
---
I updated the script to make custom remapping case/suffix-insensitive and added the missing "orange (blaze)" mapping. Let me check for any linter issues in the modified file.
---
---
- I updated `replace_cel_with_bsdf.py` to resolve your four problematic mappings by:
- Adding the missing custom mapping: `orange (blaze) => BSDF_orange-1_BLAZE`.
- Making custom mappings robust to case differences and numeric suffixes (e.g., `.001`) for both sources and targets.
- Resolving targets by base BSDF names when exact names arent present.
- No linter issues detected.
Run the script again in Blender; those four should now remap correctly.
---

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,11 @@ set projectRoot=%yyyy%-%mm%-%dd%_%projectName%
mkdir "%projectRoot%"
mkdir "%projectRoot%\Assets\VO"
if exist "%~dp0NewDaily.bat" copy /Y "%~dp0NewDaily.bat" "%projectRoot%\Assets\VO\NewDaily.bat" >nul
if exist "A:\1 Amazon_Active_Projects\3 ProjectStructure\NewDaily.bat" copy /Y "A:\1 Amazon_Active_Projects\3 ProjectStructure\NewDaily.bat" "%projectRoot%\Assets\VO\NewDaily.bat" >nul
:: Create Blends structure
mkdir "%projectRoot%\Blends\animations\"
mkdir "%projectRoot%\Blends\stills\img-BG"
:: Create Deliverable structure
mkdir "%projectRoot%\Deliverable\"
@@ -40,7 +44,6 @@ if exist "%~dp0components\gitattributes" copy /Y "%~dp0components\gitattributes"
:: Initialize git and install Git LFS
pushd "%projectRoot%" >nul
git init
git config --global --add safe.directory "%projectRoot%"
git lfs install
git add . -v
git commit -m "init"

View File

@@ -23,8 +23,8 @@
*.zip filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.fbxkey filter=lfs diff=lfs merge=lfs -text
*.blend2 filter=lfs diff=lfs merge=lfs -text
*.mb filter=lfs diff=lfs merge=lfs -text
*.ma filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
@@ -40,6 +40,7 @@
*.dae filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.vdb filter=lfs diff=lfs merge=lfs -text
*.aep filter=lfs diff=lfs merge=lfs -text
*.prproj filter=gzipped

View File

@@ -125,4 +125,7 @@ Renders/
# Premiere
*.prlock
Adobe Premiere Pro Video Previews/
Adobe Premiere Pro Video Previews/
Adobe Premiere Pro Audio Previews/
Adobe Premiere Pro (Beta) Video Previews/
Adobe Premiere Pro (Beta) Audio Previews/

View File

@@ -20,6 +20,8 @@ REM Initialize counters and tracking
set "processed=0"
set "crashed=0"
set "skipped=0"
set "deleted_similar=0"
set "threshold_percent=10"
REM Create a temporary Python script for compression
set "TEMP_SCRIPT=compress_blend_temp.py"
@@ -80,7 +82,7 @@ if exist "!TEMP_SCRIPT!" (
REM Run Blender to process the dragged files
echo Processing dragged .blend files...
echo Using script: !TEMP_SCRIPT!
blender --background --factory-startup --python "!TEMP_SCRIPT!" -- %* > blender_output.txt 2^>^&1
blender --background --factory-startup --python "!TEMP_SCRIPT!" -- %* 2^>^&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath 'blender_output.txt'"
REM Clean up temporary script
echo Cleaning up temporary script...
@@ -108,7 +110,14 @@ for /f "tokens=1,2,3 delims=:" %%a in (blender_output.txt) do (
echo Original: !original_size! bytes
echo Compressed: !compressed_size! bytes
set /a "savings=!original_size! - !compressed_size!"
if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)
echo Savings: !savings! bytes
echo Reduction: !percent!%%
if !percent! lss !threshold_percent! (
del "!compressed_file!" 2>nul
set /a deleted_similar+=1
echo Deleted: too similar to original
)
echo.
) else if "%%a"=="CRASH" (
set /a crashed+=1
@@ -127,7 +136,7 @@ for /f "tokens=1,2,3 delims=:" %%a in (blender_output.txt) do (
)
REM Clean up output file
del "blender_output.txt" 2>nul
del /f /q "blender_output.txt" 2>nul
echo ========================================
echo SUMMARY
@@ -135,6 +144,7 @@ echo ========================================
echo Processed: !processed! files
echo Crashed: !crashed! files
echo Skipped: !skipped! files
echo Deleted ^(too similar^): !deleted_similar! files
echo Total: %* files
echo ========================================
echo.

153
find_missing_files.bat Normal file
View File

@@ -0,0 +1,153 @@
@echo off
setlocal enabledelayedexpansion
echo Starting missing file search...
REM Check if files were dragged onto the batch file
if "%~1"=="" (
echo.
echo Usage: Drag .blend files onto this batch file to find missing files.
echo Output will be saved to the \output folder.
echo.
pause
exit /b 1
)
REM Create output directory if it doesn't exist
if not exist "output" mkdir "output"
REM Initialize counters and tracking
set "processed=0"
set "crashed=0"
set "skipped=0"
set "found_missing=0"
set "search_path=A:/1 Amazon_Active_Projects/1 BlenderAssets/Amazon/"
REM Create a temporary Python script for processing
set "TEMP_SCRIPT=find_missing_files_temp.py"
echo Creating Python script: !TEMP_SCRIPT!
(
echo import bpy
echo import os
echo import sys
echo.
echo # Enable compression globally
echo bpy.context.preferences.filepaths.save_version = 0
echo bpy.context.preferences.filepaths.use_file_compression = True
echo.
echo # Create output directory if it doesn't exist
echo if not os.path.exists^("output"^):
echo os.makedirs^("output"^)
echo.
echo # Get command line arguments ^(the dragged files^)
echo blend_files = sys.argv[sys.argv.index^("--"^) + 1:] if "--" in sys.argv else []
echo.
echo print^("Found " + str^(len^(blend_files^)^) + " .blend files to process"^)
echo.
echo for blend_file in blend_files:
echo ^ if blend_file.lower^(^).endswith^(".blend"^):
echo ^ try:
echo ^ # Create output path in output folder
echo ^ filename = os.path.basename^(blend_file^)
echo ^ output_file = os.path.join^("output", filename^)
echo ^ # Check if output file already exists
echo ^ if os.path.exists^(output_file^):
echo ^ print^("SKIP_EXISTING:" + blend_file^)
echo ^ continue
echo ^ print^("PROCESSING:" + blend_file^)
echo ^ # Load the blend file
echo ^ bpy.ops.wm.open_mainfile^(filepath=blend_file^)
echo ^ # Count missing files before search
echo ^ missing_before = len^([f for f in bpy.data.images if not f.filepath or not os.path.exists^(bpy.path.abspath^(f.filepath^)^)^]^)
echo ^ missing_before += len^([f for f in bpy.data.movieclips if not f.filepath or not os.path.exists^(bpy.path.abspath^(f.filepath^)^)^]^)
echo ^ missing_before += len^([f for f in bpy.data.sounds if not f.filepath or not os.path.exists^(bpy.path.abspath^(f.filepath^)^)^]^)
echo ^ # Run find missing files operator
echo ^ bpy.ops.file.find_missing_files^(directory="!search_path!", find_all=True^)
echo ^ # Count missing files after search
echo ^ missing_after = len^([f for f in bpy.data.images if not f.filepath or not os.path.exists^(bpy.path.abspath^(f.filepath^)^)^]^)
echo ^ missing_after += len^([f for f in bpy.data.movieclips if not f.filepath or not os.path.exists^(bpy.path.abspath^(f.filepath^)^)^]^)
echo ^ missing_after += len^([f for f in bpy.data.sounds if not f.filepath or not os.path.exists^(bpy.path.abspath^(f.filepath^)^)^]^)
echo ^ found_count = missing_before - missing_after
echo ^ print^("FOUND_MISSING:" + blend_file + ":" + str^(found_count^)^)
echo ^ # Save to output folder
echo ^ bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)
echo ^ print^("SUCCESS:" + blend_file + ":" + output_file^)
echo ^ except Exception as e:
echo ^ print^("CRASH:" + blend_file + ":" + str^(e^)^)
echo ^ else:
echo ^ print^("SKIP_NOT_BLEND:" + blend_file^)
echo.
echo print^("MISSING_FILE_SEARCH_COMPLETE"^)
echo bpy.ops.wm.quit_blender^(^)
) > "!TEMP_SCRIPT!"
REM Check if script was created successfully
if exist "!TEMP_SCRIPT!" (
echo Script created successfully: !TEMP_SCRIPT!
) else (
echo ERROR: Failed to create script file!
pause
exit /b 1
)
REM Run Blender to process the dragged files
echo Processing dragged .blend files...
echo Using script: !TEMP_SCRIPT!
echo Search path: !search_path!
blender --background --factory-startup --python "!TEMP_SCRIPT!" -- %* 2^>^&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath 'blender_output.txt'"
REM Clean up temporary script
echo Cleaning up temporary script...
del "!TEMP_SCRIPT!" 2>nul
REM Parse output and generate report
echo.
echo ========================================
echo MISSING FILE SEARCH REPORT
echo ========================================
echo.
REM Track found missing files during parse
for /f "tokens=1,2,3 delims=:" %%a in (blender_output.txt) do (
if "%%a"=="SUCCESS" (
set /a processed+=1
echo [!processed!] SUCCESS: %%b
echo Saved to: %%c
echo.
) else if "%%a"=="CRASH" (
set /a crashed+=1
echo [!crashed!] CRASHED: %%b
echo Error: %%c
echo.
) else if "%%a"=="SKIP_EXISTING" (
set /a skipped+=1
echo [!skipped!] SKIPPED ^(already exists^): %%b
echo.
) else if "%%a"=="SKIP_NOT_BLEND" (
set /a skipped+=1
echo [!skipped!] SKIPPED ^(not .blend^): %%b
echo.
) else if "%%a"=="FOUND_MISSING" (
for /f "delims= tokens=1" %%x in ("%%c") do set "found_in_file=%%x"
set /a found_missing+=found_in_file
echo Found missing files: !found_in_file!
)
)
REM Clean up output file
del /f /q "blender_output.txt" 2>nul
echo ========================================
echo SUMMARY
echo ========================================
echo Processed: !processed! files
echo Crashed: !crashed! files
echo Skipped: !skipped! files
echo Total missing files found: !found_missing!
echo Search path: !search_path!
echo Total: %* files
echo ========================================
echo.
echo Done!
pause

View File

@@ -0,0 +1,160 @@
@echo off
setlocal enabledelayedexpansion
echo Starting fake user removal for actions...
REM Check if files were dragged onto the batch file
if "%~1"=="" (
echo.
echo Usage: Drag .blend files onto this batch file to remove fake users from actions.
echo Output will be saved to the \output folder.
echo.
pause
exit /b 1
)
REM Create output directory if it doesn't exist
if not exist "output" mkdir "output"
REM Initialize counters and tracking
set "processed=0"
set "crashed=0"
set "skipped=0"
set "deleted_similar=0"
set "threshold_percent=10"
set "updated_total=0"
REM Create a temporary Python script for processing
set "TEMP_SCRIPT=remove_action_fake_users_temp.py"
echo Creating Python script: !TEMP_SCRIPT!
(
echo import bpy
echo import os
echo import sys
echo.
echo # Create output directory if it doesn't exist
echo if not os.path.exists^("output"^):
echo os.makedirs^("output"^)
echo.
echo # Get command line arguments ^(the dragged files^)
echo blend_files = sys.argv[sys.argv.index^("--"^) + 1:] if "--" in sys.argv else []
echo.
echo print^("Found " + str^(len^(blend_files^)^) + " .blend files to process"^)
echo.
echo for blend_file in blend_files:
echo ^ if blend_file.lower^(^).endswith^(".blend"^):
echo ^ try:
echo ^ # Create output path in output folder
echo ^ filename = os.path.basename^(blend_file^)
echo ^ output_file = os.path.join^("output", filename^)
echo ^ # Check if output file already exists
echo ^ if os.path.exists^(output_file^):
echo ^ print^("SKIP_EXISTING:" + blend_file^)
echo ^ continue
echo ^ print^("PROCESSING:" + blend_file^)
echo ^ # Load the blend file
echo ^ bpy.ops.wm.open_mainfile^(filepath=blend_file^)
echo ^ # Remove fake users from all actions
echo ^ changed = 0
echo ^ for action in bpy.data.actions:
echo ^ if getattr^(action, "use_fake_user", False^):
echo ^ action.use_fake_user = False
echo ^ changed += 1
echo ^ print^("UPDATED:" + blend_file + ":" + str^(changed^)^)
echo ^ # Save to output folder
echo ^ bpy.ops.wm.save_mainfile^(filepath=output_file, compress=True^)
echo ^ print^("SUCCESS:" + blend_file + ":" + output_file^)
echo ^ except Exception as e:
echo ^ print^("CRASH:" + blend_file + ":" + str^(e^)^)
echo ^ else:
echo ^ print^("SKIP_NOT_BLEND:" + blend_file^)
echo.
echo print^("FAKE_USER_REMOVAL_COMPLETE"^)
echo bpy.ops.wm.quit_blender^(^)
) > "!TEMP_SCRIPT!"
REM Check if script was created successfully
if exist "!TEMP_SCRIPT!" (
echo Script created successfully: !TEMP_SCRIPT!
) else (
echo ERROR: Failed to create script file!
pause
exit /b 1
)
REM Run Blender to process the dragged files
echo Processing dragged .blend files...
echo Using script: !TEMP_SCRIPT!
blender --background --factory-startup --python "!TEMP_SCRIPT!" -- %* 2^>^&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath 'blender_output.txt'"
REM Clean up temporary script
echo Cleaning up temporary script...
del "!TEMP_SCRIPT!" 2>nul
REM Parse output and generate report
echo.
echo ========================================
echo ACTION FAKE USER REMOVAL REPORT
echo ========================================
echo.
REM Track updated counts per file during parse
for /f "tokens=1,2,3 delims=:" %%a in (blender_output.txt) do (
if "%%a"=="SUCCESS" (
set /a processed+=1
echo [!processed!] SUCCESS: %%b
echo Saved to: %%c
REM Compare sizes and delete if reduction < threshold
for %%f in ("%%b") do set "original_size=%%~zf"
for %%f in ("%%c") do set "compressed_size=%%~zf"
set /a savings=original_size-compressed_size
if !original_size! gtr 0 (set /a percent=(savings*100)/original_size) else (set percent=0)
echo Original: !original_size! bytes
echo Output: !compressed_size! bytes
echo Reduction: !percent!%%
if !percent! lss !threshold_percent! (
del "%%c" 2>nul
set /a deleted_similar+=1
echo Deleted: too similar to original
)
echo.
) else if "%%a"=="CRASH" (
set /a crashed+=1
echo [!crashed!] CRASHED: %%b
echo Error: %%c
echo.
) else if "%%a"=="SKIP_EXISTING" (
set /a skipped+=1
echo [!skipped!] SKIPPED ^(already exists^): %%b
echo.
) else if "%%a"=="SKIP_NOT_BLEND" (
set /a skipped+=1
echo [!skipped!] SKIPPED ^(not .blend^): %%b
echo.
) else if "%%a"=="UPDATED" (
REM Accumulate total updated actions
for /f "delims= tokens=1" %%x in ("%%c") do set "updated_in_file=%%x"
set /a updated_total+=updated_in_file
echo Updated actions: !updated_in_file!
)
)
REM Clean up output file
del /f /q "blender_output.txt" 2>nul
echo ========================================
echo SUMMARY
echo ========================================
echo Processed: !processed! files
echo Crashed: !crashed! files
echo Skipped: !skipped! files
echo Deleted ^(too similar^): !deleted_similar! files
echo Total actions updated: !updated_total!
echo Total: %* files
echo ========================================
echo.
echo Done!
pause

375
replace_cel_with_bsdf.py Normal file
View File

@@ -0,0 +1,375 @@
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",
"orange (blaze)": "BSDF_orange-1_BLAZE"
}
# 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}
# Helpers to normalize names (case-insensitive, ignore numeric suffixes and library suffix)
def normalize_base(name):
base_name = name.split(".blend")[0] if ".blend" in name else name
match = re.match(r"^(.*?)(\.\d{3})?$", base_name)
base_name = match.group(1) if match else base_name
return base_name.strip().casefold()
# Map normalized base name -> list of materials
materials_by_base = {}
for mat in materials:
base = normalize_base(mat.name)
materials_by_base.setdefault(base, []).append(mat)
# Normalize BSDF base name map for robust target lookups
bsdf_base_map_normalized = {normalize_base(base): mat for base, mat in bsdf_base_map.items()}
replacements_made = 0
missing_targets = []
# Process custom mappings first (case/suffix-insensitive)
for source_name, target_name in custom_mappings.items():
# Gather source candidates by exact name or base name (handles .001 etc.)
if source_name in exact_material_map:
source_candidates = [exact_material_map[source_name]]
else:
source_candidates = materials_by_base.get(normalize_base(source_name), [])
if not source_candidates:
print(f"Warning: Source material '{source_name}' not found")
continue
# Resolve target BSDF by exact or base name
target_material = exact_material_map.get(target_name)
if target_material is None:
target_material = bsdf_base_map_normalized.get(normalize_base(target_name))
if target_material is None:
# Final fallback: any BSDF whose base equals target base
target_base_norm = normalize_base(target_name)
for mat in materials:
m = bsdf_pattern.match(mat.name)
if m and normalize_base(m.group(1)) == target_base_norm:
target_material = mat
break
if target_material is None:
missing_targets.append(f"{source_name} -> {target_name}")
print(f"Warning: Target material '{target_name}' not found for custom mapping '{source_name}'")
continue
for src_mat in source_candidates:
material_mapping[src_mat] = target_material
print(f"Found custom mapping: {src_mat.name} -> {target_material.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)")