step 3 actually working, can't believe it first try
This commit is contained in:
16968
.specstory/history/2025-12-19_18-52Z-blend-file-path-prefix-logic.md
Normal file
16968
.specstory/history/2025-12-19_18-52Z-blend-file-path-prefix-logic.md
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -132,7 +132,8 @@ function Process-DuplicateGroup {
|
|||||||
[string]$CommonPath,
|
[string]$CommonPath,
|
||||||
[hashtable]$FilesInCommon,
|
[hashtable]$FilesInCommon,
|
||||||
[switch]$StripPrefix,
|
[switch]$StripPrefix,
|
||||||
[string[]]$ValidPrefixes = @()
|
[string[]]$ValidPrefixes = @(),
|
||||||
|
[System.Collections.ArrayList]$MoveLog = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
$movedCount = 0
|
$movedCount = 0
|
||||||
@@ -221,6 +222,17 @@ function Process-DuplicateGroup {
|
|||||||
Move-Item -Path $firstFile.FullName -Destination $destinationPath -Force
|
Move-Item -Path $firstFile.FullName -Destination $destinationPath -Force
|
||||||
$FilesInCommon[$fileName] = $true
|
$FilesInCommon[$fileName] = $true
|
||||||
$movedCount++
|
$movedCount++
|
||||||
|
|
||||||
|
# Log the move
|
||||||
|
if ($null -ne $MoveLog) {
|
||||||
|
$normalizedOriginal = [System.IO.Path]::GetFullPath($firstFile.FullName)
|
||||||
|
$normalizedNew = [System.IO.Path]::GetFullPath($destinationPath)
|
||||||
|
$null = $MoveLog.Add([PSCustomObject]@{
|
||||||
|
OriginalPath = $normalizedOriginal
|
||||||
|
NewPath = $normalizedNew
|
||||||
|
Type = "moved"
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Write-Warning "Failed to move first duplicate file: $($firstFile.FullName) - $($_.Exception.Message)"
|
Write-Warning "Failed to move first duplicate file: $($firstFile.FullName) - $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
@@ -229,8 +241,20 @@ function Process-DuplicateGroup {
|
|||||||
for ($i = 1; $i -lt $Files.Count; $i++) {
|
for ($i = 1; $i -lt $Files.Count; $i++) {
|
||||||
$fileObj = $Files[$i].File
|
$fileObj = $Files[$i].File
|
||||||
try {
|
try {
|
||||||
Remove-Item -Path $fileObj.FullName -Force
|
$originalPath = $fileObj.FullName
|
||||||
|
Remove-Item -Path $originalPath -Force
|
||||||
$duplicateCount++
|
$duplicateCount++
|
||||||
|
|
||||||
|
# Log the deletion (replaced by the moved file)
|
||||||
|
if ($null -ne $MoveLog) {
|
||||||
|
$normalizedOriginal = [System.IO.Path]::GetFullPath($originalPath)
|
||||||
|
$normalizedReplacement = [System.IO.Path]::GetFullPath($destinationPath)
|
||||||
|
$null = $MoveLog.Add([PSCustomObject]@{
|
||||||
|
OriginalPath = $normalizedOriginal
|
||||||
|
ReplacedBy = $normalizedReplacement
|
||||||
|
Type = "deleted"
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Write-Warning "Failed to delete duplicate file: $($fileObj.FullName) - $($_.Exception.Message)"
|
Write-Warning "Failed to delete duplicate file: $($fileObj.FullName) - $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
@@ -258,6 +282,7 @@ if ($null -eq $blendfileFolders -or $blendfileFolders.Count -eq 0) {
|
|||||||
|
|
||||||
$totalPass1Moved = 0
|
$totalPass1Moved = 0
|
||||||
$totalPass1Duplicates = 0
|
$totalPass1Duplicates = 0
|
||||||
|
$pass1MoveLog = [System.Collections.ArrayList]::new()
|
||||||
|
|
||||||
foreach ($blendfileFolder in $blendfileFolders) {
|
foreach ($blendfileFolder in $blendfileFolders) {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
@@ -297,7 +322,7 @@ if ($null -eq $blendfileFolders -or $blendfileFolders.Count -eq 0) {
|
|||||||
$blendfileDuplicates = 0
|
$blendfileDuplicates = 0
|
||||||
|
|
||||||
foreach ($group in $groupedByChecksum) {
|
foreach ($group in $groupedByChecksum) {
|
||||||
$result = Process-DuplicateGroup -Files $group.Group -CommonPath $blendfileCommonPath -FilesInCommon $filesInBlendfileCommon
|
$result = Process-DuplicateGroup -Files $group.Group -CommonPath $blendfileCommonPath -FilesInCommon $filesInBlendfileCommon -MoveLog $pass1MoveLog
|
||||||
$blendfileMoved += $result.MovedCount
|
$blendfileMoved += $result.MovedCount
|
||||||
$blendfileDuplicates += $result.DuplicateCount
|
$blendfileDuplicates += $result.DuplicateCount
|
||||||
}
|
}
|
||||||
@@ -377,6 +402,7 @@ if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
|
|||||||
Write-Host "Moving files to \common and deleting duplicates..." -ForegroundColor Yellow
|
Write-Host "Moving files to \common and deleting duplicates..." -ForegroundColor Yellow
|
||||||
$pass2Moved = 0
|
$pass2Moved = 0
|
||||||
$pass2Duplicates = 0
|
$pass2Duplicates = 0
|
||||||
|
$pass2MoveLog = [System.Collections.ArrayList]::new()
|
||||||
|
|
||||||
foreach ($group in $groupedByChecksum) {
|
foreach ($group in $groupedByChecksum) {
|
||||||
$files = $group.Group
|
$files = $group.Group
|
||||||
@@ -388,7 +414,7 @@ if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
|
|||||||
|
|
||||||
# Multiple files with same checksum (duplicates across blendfiles)
|
# Multiple files with same checksum (duplicates across blendfiles)
|
||||||
# Use StripPrefix to remove blendfile prefixes (e.g., "Demarco_", "Chan_") when all files share the same suffix
|
# Use StripPrefix to remove blendfile prefixes (e.g., "Demarco_", "Chan_") when all files share the same suffix
|
||||||
$result = Process-DuplicateGroup -Files $files -CommonPath $rootCommonPath -FilesInCommon $filesInRootCommon -StripPrefix -ValidPrefixes $validBlendfilePrefixes
|
$result = Process-DuplicateGroup -Files $files -CommonPath $rootCommonPath -FilesInCommon $filesInRootCommon -StripPrefix -ValidPrefixes $validBlendfilePrefixes -MoveLog $pass2MoveLog
|
||||||
$pass2Moved += $result.MovedCount
|
$pass2Moved += $result.MovedCount
|
||||||
$pass2Duplicates += $result.DuplicateCount
|
$pass2Duplicates += $result.DuplicateCount
|
||||||
}
|
}
|
||||||
@@ -419,6 +445,7 @@ if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
|
|||||||
$filesInFlatColors = @{}
|
$filesInFlatColors = @{}
|
||||||
$flatColorsMoved = 0
|
$flatColorsMoved = 0
|
||||||
$flatColorsDuplicates = 0
|
$flatColorsDuplicates = 0
|
||||||
|
$flatColorsMoveLog = [System.Collections.ArrayList]::new()
|
||||||
|
|
||||||
foreach ($group in $flatColorsGroupedByChecksum) {
|
foreach ($group in $flatColorsGroupedByChecksum) {
|
||||||
$files = $group.Group
|
$files = $group.Group
|
||||||
@@ -450,16 +477,26 @@ if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Move-Item -Path $fileObj.FullName -Destination $destinationPath -Force
|
$originalPath = $fileObj.FullName
|
||||||
|
Move-Item -Path $originalPath -Destination $destinationPath -Force
|
||||||
$filesInFlatColors[$fileName] = $true
|
$filesInFlatColors[$fileName] = $true
|
||||||
$flatColorsMoved++
|
$flatColorsMoved++
|
||||||
|
|
||||||
|
# Log the move
|
||||||
|
$normalizedOriginal = [System.IO.Path]::GetFullPath($originalPath)
|
||||||
|
$normalizedNew = [System.IO.Path]::GetFullPath($destinationPath)
|
||||||
|
$null = $flatColorsMoveLog.Add([PSCustomObject]@{
|
||||||
|
OriginalPath = $normalizedOriginal
|
||||||
|
NewPath = $normalizedNew
|
||||||
|
Type = "moved"
|
||||||
|
})
|
||||||
} catch {
|
} catch {
|
||||||
Write-Warning "Failed to move FlatColors file: $($fileObj.FullName) - $($_.Exception.Message)"
|
Write-Warning "Failed to move FlatColors file: $($fileObj.FullName) - $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
# Multiple files with same checksum (duplicates) - merge them
|
# Multiple files with same checksum (duplicates) - merge them
|
||||||
# Use Process-DuplicateGroup to handle merging
|
# Use Process-DuplicateGroup to handle merging
|
||||||
$result = Process-DuplicateGroup -Files $files -CommonPath $flatColorsPath -FilesInCommon $filesInFlatColors -StripPrefix -ValidPrefixes $validBlendfilePrefixes
|
$result = Process-DuplicateGroup -Files $files -CommonPath $flatColorsPath -FilesInCommon $filesInFlatColors -StripPrefix -ValidPrefixes $validBlendfilePrefixes -MoveLog $flatColorsMoveLog
|
||||||
$flatColorsMoved += $result.MovedCount
|
$flatColorsMoved += $result.MovedCount
|
||||||
$flatColorsDuplicates += $result.DuplicateCount
|
$flatColorsDuplicates += $result.DuplicateCount
|
||||||
}
|
}
|
||||||
@@ -474,6 +511,179 @@ if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
|
|||||||
Write-Host "Pass 2 complete: $pass2Moved file(s) moved to \common, $pass2Duplicates duplicate(s) deleted" -ForegroundColor Green
|
Write-Host "Pass 2 complete: $pass2Moved file(s) moved to \common, $pass2Duplicates duplicate(s) deleted" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Save Move Log and Find Blend Files
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Saving move log and finding blend files..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Combine all move logs
|
||||||
|
$allMoves = @()
|
||||||
|
if ($null -ne $pass1MoveLog -and $pass1MoveLog.Count -gt 0) {
|
||||||
|
$allMoves += $pass1MoveLog
|
||||||
|
}
|
||||||
|
if ($null -ne $pass2MoveLog -and $pass2MoveLog.Count -gt 0) {
|
||||||
|
$allMoves += $pass2MoveLog
|
||||||
|
}
|
||||||
|
if ($null -ne $flatColorsMoveLog -and $flatColorsMoveLog.Count -gt 0) {
|
||||||
|
$allMoves += $flatColorsMoveLog
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get parent directory of texture folder
|
||||||
|
$blendFileParentDir = Split-Path -Path $textureFolderPath -Parent
|
||||||
|
|
||||||
|
# Find matching blend files
|
||||||
|
$blendFileMappings = @()
|
||||||
|
if ($null -ne $blendfileFolders -and $blendfileFolders.Count -gt 0) {
|
||||||
|
# Get blendfile folder names
|
||||||
|
$blendfileFolderNames = $blendfileFolders | ForEach-Object { $_.Name }
|
||||||
|
|
||||||
|
# Find all .blend files in parent directory
|
||||||
|
$blendFiles = Get-ChildItem -Path $blendFileParentDir -Filter "*.blend" -File -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($null -ne $blendFiles -and $blendFiles.Count -gt 0) {
|
||||||
|
foreach ($blendFile in $blendFiles) {
|
||||||
|
# Extract prefix from blend file name (e.g., "Chan_v4.3.blend" -> "Chan")
|
||||||
|
$blendFileName = $blendFile.BaseName
|
||||||
|
$parts = $blendFileName.Split('_', 2)
|
||||||
|
|
||||||
|
if ($parts.Length -ge 1) {
|
||||||
|
$prefix = $parts[0]
|
||||||
|
|
||||||
|
# Check if prefix matches any blendfile folder name
|
||||||
|
if ($blendfileFolderNames -contains $prefix) {
|
||||||
|
$blendFileMappings += [PSCustomObject]@{
|
||||||
|
BlendFile = [System.IO.Path]::GetFullPath($blendFile.FullName)
|
||||||
|
BlendfileFolder = $prefix
|
||||||
|
}
|
||||||
|
Write-Host "Found matching blend file: $($blendFile.Name) -> $prefix" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create move log object
|
||||||
|
$moveLogData = [PSCustomObject]@{
|
||||||
|
TextureFolderPath = $textureFolderPath
|
||||||
|
Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
|
||||||
|
TotalMoves = ($allMoves | Where-Object { $_.Type -eq "moved" }).Count
|
||||||
|
TotalDeletes = ($allMoves | Where-Object { $_.Type -eq "deleted" }).Count
|
||||||
|
Moves = $allMoves
|
||||||
|
BlendFileMappings = $blendFileMappings
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save to JSON file
|
||||||
|
$moveLogPath = Join-Path -Path $blendFileParentDir -ChildPath "texture_moves.json"
|
||||||
|
try {
|
||||||
|
$moveLogData | ConvertTo-Json -Depth 10 | Set-Content -Path $moveLogPath -Encoding UTF8
|
||||||
|
Write-Host "Move log saved to: $moveLogPath" -ForegroundColor Green
|
||||||
|
Write-Host " Total moves: $($moveLogData.TotalMoves), Total deletes: $($moveLogData.TotalDeletes)" -ForegroundColor Gray
|
||||||
|
Write-Host " Blend files found: $($blendFileMappings.Count)" -ForegroundColor Gray
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to save move log: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Remap Texture Paths in Blend Files
|
||||||
|
# ============================================================================
|
||||||
|
if ($blendFileMappings.Count -gt 0 -and (Test-Path -Path $moveLogPath)) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Remapping texture paths in blend files..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Find Blender executable from PATH
|
||||||
|
$blenderExe = $null
|
||||||
|
$blenderInPath = Get-Command blender -ErrorAction SilentlyContinue
|
||||||
|
if ($null -ne $blenderInPath) {
|
||||||
|
$blenderExe = $blenderInPath.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq $blenderExe) {
|
||||||
|
Write-Warning "Blender executable not found. Skipping texture path remapping."
|
||||||
|
Write-Host " Please install Blender or add it to your PATH." -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
|
Write-Host "Found Blender: $blenderExe" -ForegroundColor Gray
|
||||||
|
|
||||||
|
# Get the remap script path (should be in the same directory as this script)
|
||||||
|
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
|
||||||
|
$remapScriptPath = Join-Path -Path $scriptDir -ChildPath "remap_texture_paths.py"
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $remapScriptPath)) {
|
||||||
|
Write-Warning "Remap script not found: $remapScriptPath"
|
||||||
|
Write-Warning "Skipping texture path remapping."
|
||||||
|
} else {
|
||||||
|
$processedCount = 0
|
||||||
|
$failedCount = 0
|
||||||
|
|
||||||
|
Write-Host "Processing $($blendFileMappings.Count) blend file(s)..." -ForegroundColor Gray
|
||||||
|
|
||||||
|
foreach ($mapping in $blendFileMappings) {
|
||||||
|
$blendFilePath = $mapping.BlendFile
|
||||||
|
$blendFileName = Split-Path -Path $blendFilePath -Leaf
|
||||||
|
|
||||||
|
Write-Host " Processing: $blendFileName" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Check if blend file exists
|
||||||
|
if (-not (Test-Path -Path $blendFilePath)) {
|
||||||
|
Write-Warning " Blend file not found: $blendFilePath"
|
||||||
|
$failedCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run Blender with the remapping script (one file at a time)
|
||||||
|
# Delete old output file
|
||||||
|
Remove-Item -Path "blender_output.txt" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Run Blender and capture output while displaying it (like compress_blend_files.bat)
|
||||||
|
# Build command with proper argument array
|
||||||
|
$blenderArgsArray = @(
|
||||||
|
"--background",
|
||||||
|
"--factory-startup",
|
||||||
|
"--python", $remapScriptPath,
|
||||||
|
"--",
|
||||||
|
$blendFilePath,
|
||||||
|
$moveLogPath
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute and pipe output through Tee-Object to display and save
|
||||||
|
& $blenderExe $blenderArgsArray 2>&1 | Tee-Object -FilePath "blender_output.txt" | ForEach-Object {
|
||||||
|
Write-Host " $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check exit code
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host " Successfully remapped texture paths" -ForegroundColor Green
|
||||||
|
$processedCount++
|
||||||
|
} else {
|
||||||
|
Write-Warning " Failed to remap texture paths (exit code: $exitCode)"
|
||||||
|
$failedCount++
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Warning " Exception while processing blend file: $($_.Exception.Message)"
|
||||||
|
$failedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up temporary output files
|
||||||
|
Remove-Item -Path "blender_output.txt" -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item -Path "blender_error.txt" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
if ($processedCount -gt 0 -or $failedCount -gt 0) {
|
||||||
|
Write-Host "Blend file remapping complete: $processedCount succeeded, $failedCount failed" -ForegroundColor $(if ($failedCount -eq 0) { "Green" } else { "Yellow" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($blendFileMappings.Count -eq 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "No matching blend files found. Skipping texture path remapping." -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Function to remove empty folders recursively
|
# Function to remove empty folders recursively
|
||||||
function Remove-EmptyFolders {
|
function Remove-EmptyFolders {
|
||||||
param(
|
param(
|
||||||
|
|||||||
239
remap_texture_paths.py
Normal file
239
remap_texture_paths.py
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
"""
|
||||||
|
Blender script to remap texture paths after texture organization.
|
||||||
|
Usage: blender --background --factory-startup --python remap_texture_paths.py -- [blend_file_path] [texture_moves_json_path]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def normalize_path(path):
|
||||||
|
"""Normalize a path for comparison (handle case, separators, etc.)"""
|
||||||
|
if not path:
|
||||||
|
return ""
|
||||||
|
# Convert to absolute path and normalize
|
||||||
|
abs_path = os.path.abspath(path)
|
||||||
|
# Normalize separators (Windows uses backslash, but we'll compare case-insensitively)
|
||||||
|
normalized = os.path.normpath(abs_path).replace('\\', '/')
|
||||||
|
return normalized.lower()
|
||||||
|
|
||||||
|
def load_move_log(json_path):
|
||||||
|
"""Load the move log JSON file and build lookup dictionaries"""
|
||||||
|
try:
|
||||||
|
with open(json_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# Debug: Check what keys are in the data
|
||||||
|
print(f" JSON keys: {list(data.keys())}")
|
||||||
|
|
||||||
|
# PowerShell ConvertTo-Json uses PascalCase by default, so check both cases
|
||||||
|
moves = data.get('moves', []) or data.get('Moves', [])
|
||||||
|
|
||||||
|
print(f" Found {len(moves)} move entries in JSON")
|
||||||
|
|
||||||
|
if len(moves) > 0:
|
||||||
|
# Debug: Show first move entry structure
|
||||||
|
print(f" First move entry keys: {list(moves[0].keys())}")
|
||||||
|
print(f" First move entry: {moves[0]}")
|
||||||
|
|
||||||
|
# Build lookup dictionaries: normalized original path -> actual new path
|
||||||
|
original_to_new = {}
|
||||||
|
original_to_replacement = {}
|
||||||
|
|
||||||
|
for move in moves:
|
||||||
|
# Try both camelCase and PascalCase property names
|
||||||
|
move_type = move.get('type', '') or move.get('Type', '')
|
||||||
|
original = move.get('originalPath', '') or move.get('OriginalPath', '')
|
||||||
|
|
||||||
|
if not original:
|
||||||
|
continue
|
||||||
|
|
||||||
|
orig_norm = normalize_path(original)
|
||||||
|
|
||||||
|
if move_type == 'moved' or move_type == 'Moved':
|
||||||
|
new_path = move.get('newPath', '') or move.get('NewPath', '')
|
||||||
|
if new_path:
|
||||||
|
original_to_new[orig_norm] = new_path
|
||||||
|
|
||||||
|
elif move_type == 'deleted' or move_type == 'Deleted':
|
||||||
|
replacement = move.get('replacedBy', '') or move.get('ReplacedBy', '')
|
||||||
|
if replacement:
|
||||||
|
original_to_replacement[orig_norm] = replacement
|
||||||
|
|
||||||
|
print(f" Built {len(original_to_new)} move mappings and {len(original_to_replacement)} replacement mappings")
|
||||||
|
|
||||||
|
return original_to_new, original_to_replacement, data.get('textureFolderPath', '') or data.get('TextureFolderPath', '')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to load move log: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return {}, {}, ""
|
||||||
|
|
||||||
|
def remap_texture_paths(blend_file_path, move_log_path):
|
||||||
|
"""Remap all texture paths in the blend file"""
|
||||||
|
|
||||||
|
print(f"\n=== REMAPPING TEXTURE PATHS ===")
|
||||||
|
print(f"Blend file: {blend_file_path}")
|
||||||
|
print(f"Move log: {move_log_path}")
|
||||||
|
|
||||||
|
# Load move log
|
||||||
|
original_to_new, original_to_replacement, texture_folder_path = load_move_log(move_log_path)
|
||||||
|
|
||||||
|
if not original_to_new and not original_to_replacement:
|
||||||
|
print("WARNING: No moves found in move log. Nothing to remap.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f"Loaded {len(original_to_new)} move mappings and {len(original_to_replacement)} replacement mappings")
|
||||||
|
|
||||||
|
# Get blend file directory for relative path conversion
|
||||||
|
blend_file_dir = os.path.dirname(os.path.abspath(blend_file_path))
|
||||||
|
|
||||||
|
remapped_count = 0
|
||||||
|
not_found_count = 0
|
||||||
|
|
||||||
|
# Remap paths in image datablocks
|
||||||
|
print("\nRemapping image datablock paths...")
|
||||||
|
for image in bpy.data.images:
|
||||||
|
if not image.filepath:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Convert to absolute path for comparison
|
||||||
|
abs_path = bpy.path.abspath(image.filepath)
|
||||||
|
if not abs_path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
abs_norm = normalize_path(abs_path)
|
||||||
|
was_relative = not os.path.isabs(image.filepath)
|
||||||
|
|
||||||
|
# Check if this path needs remapping
|
||||||
|
new_path = None
|
||||||
|
|
||||||
|
# First check moved files
|
||||||
|
if abs_norm in original_to_new:
|
||||||
|
new_path = original_to_new[abs_norm]
|
||||||
|
|
||||||
|
# Then check deleted files (replaced by)
|
||||||
|
elif abs_norm in original_to_replacement:
|
||||||
|
new_path = original_to_replacement[abs_norm]
|
||||||
|
|
||||||
|
if new_path:
|
||||||
|
# Convert to relative path if original was relative
|
||||||
|
if was_relative:
|
||||||
|
try:
|
||||||
|
rel_path = bpy.path.relpath(new_path, blend_file_dir)
|
||||||
|
image.filepath = rel_path
|
||||||
|
except:
|
||||||
|
# Fallback to absolute if relative conversion fails
|
||||||
|
image.filepath = new_path
|
||||||
|
else:
|
||||||
|
image.filepath = new_path
|
||||||
|
|
||||||
|
print(f" Remapped: {os.path.basename(abs_path)} -> {os.path.basename(new_path)}")
|
||||||
|
remapped_count += 1
|
||||||
|
else:
|
||||||
|
# Check if file exists - if not, it might be a broken reference
|
||||||
|
if not os.path.exists(abs_path):
|
||||||
|
not_found_count += 1
|
||||||
|
|
||||||
|
# Also check material node trees for image texture nodes
|
||||||
|
# Note: Most image texture nodes reference the image datablock (which we already remapped above),
|
||||||
|
# but we check node.filepath for completeness in case any nodes have direct file paths
|
||||||
|
print("\nRemapping material node texture paths...")
|
||||||
|
node_remapped_count = 0
|
||||||
|
for material in bpy.data.materials:
|
||||||
|
if not material.use_nodes or not material.node_tree:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for node in material.node_tree.nodes:
|
||||||
|
if node.type == 'TEX_IMAGE' and hasattr(node, 'filepath') and node.filepath:
|
||||||
|
abs_path = bpy.path.abspath(node.filepath)
|
||||||
|
if abs_path:
|
||||||
|
abs_norm = normalize_path(abs_path)
|
||||||
|
|
||||||
|
new_path = None
|
||||||
|
if abs_norm in original_to_new:
|
||||||
|
new_path = original_to_new[abs_norm]
|
||||||
|
elif abs_norm in original_to_replacement:
|
||||||
|
new_path = original_to_replacement[abs_norm]
|
||||||
|
|
||||||
|
if new_path:
|
||||||
|
node.filepath = new_path
|
||||||
|
print(f" Remapped node texture: {os.path.basename(abs_path)} -> {os.path.basename(new_path)}")
|
||||||
|
node_remapped_count += 1
|
||||||
|
|
||||||
|
if node_remapped_count > 0:
|
||||||
|
remapped_count += node_remapped_count
|
||||||
|
|
||||||
|
print(f"\n=== REMAPPING SUMMARY ===")
|
||||||
|
print(f"Paths remapped: {remapped_count}")
|
||||||
|
print(f"Broken references (not found): {not_found_count}")
|
||||||
|
|
||||||
|
# Save the blend file
|
||||||
|
try:
|
||||||
|
print(f"\nSaving blend file...")
|
||||||
|
# Ensure we use an absolute path
|
||||||
|
abs_blend_path = os.path.abspath(blend_file_path)
|
||||||
|
print(f" Saving to: {abs_blend_path}")
|
||||||
|
bpy.ops.wm.save_mainfile(filepath=abs_blend_path)
|
||||||
|
|
||||||
|
# Verify the file was actually saved
|
||||||
|
if os.path.exists(abs_blend_path):
|
||||||
|
file_time = os.path.getmtime(abs_blend_path)
|
||||||
|
print(f"Successfully saved: {abs_blend_path}")
|
||||||
|
print(f" File modified time: {file_time}")
|
||||||
|
return remapped_count
|
||||||
|
else:
|
||||||
|
print(f"ERROR: File was not created at {abs_blend_path}")
|
||||||
|
return -1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to save blend file: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Get command line arguments (after --)
|
||||||
|
if '--' in sys.argv:
|
||||||
|
args = sys.argv[sys.argv.index('--') + 1:]
|
||||||
|
else:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
if len(args) < 2:
|
||||||
|
print("ERROR: Usage: blender --background --factory-startup --python remap_texture_paths.py -- [blend_file_path] [texture_moves_json_path]")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
blend_file_path = args[0]
|
||||||
|
move_log_path = args[1]
|
||||||
|
|
||||||
|
# Validate paths
|
||||||
|
if not os.path.exists(blend_file_path):
|
||||||
|
print(f"ERROR: Blend file not found: {blend_file_path}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not os.path.exists(move_log_path):
|
||||||
|
print(f"ERROR: Move log file not found: {move_log_path}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Load the blend file
|
||||||
|
try:
|
||||||
|
print(f"Loading blend file: {blend_file_path}")
|
||||||
|
bpy.ops.wm.open_mainfile(filepath=blend_file_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to load blend file: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Remap texture paths
|
||||||
|
result = remap_texture_paths(blend_file_path, move_log_path)
|
||||||
|
|
||||||
|
if result >= 0:
|
||||||
|
print("\nTexture path remapping completed successfully!")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print("\nTexture path remapping failed!")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit_code = main()
|
||||||
|
sys.exit(exit_code)
|
||||||
Reference in New Issue
Block a user