2025-12-19 11:57:12 -07:00
|
|
|
# Script to organize texture files by checksum with two-level duplicate detection
|
|
|
|
|
# Pass 1: Intra-blendfile duplicates → [blendfile]\common
|
|
|
|
|
# Pass 2: Inter-blendfile duplicates → \textures\common
|
2025-12-19 11:26:50 -07:00
|
|
|
# Usage: .\organize_textures.ps1
|
2025-12-19 10:59:09 -07:00
|
|
|
|
2025-12-19 11:26:50 -07:00
|
|
|
# Prompt user for texture folder path
|
2025-12-19 11:31:47 -07:00
|
|
|
$textureFolderPath = Read-Host "Enter texture folder path"
|
2025-12-19 10:59:09 -07:00
|
|
|
|
2025-12-19 11:26:50 -07:00
|
|
|
# Validate the input path
|
|
|
|
|
if ([string]::IsNullOrWhiteSpace($textureFolderPath)) {
|
|
|
|
|
Write-Host "Error: No path provided." -ForegroundColor Red
|
|
|
|
|
exit
|
2025-12-19 10:59:09 -07:00
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:26:50 -07:00
|
|
|
if (-not (Test-Path -Path $textureFolderPath -PathType Container)) {
|
|
|
|
|
Write-Host "Error: Path does not exist or is not a directory: $textureFolderPath" -ForegroundColor Red
|
|
|
|
|
exit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Resolve the full path
|
|
|
|
|
$textureFolderPath = (Resolve-Path $textureFolderPath).ProviderPath
|
|
|
|
|
Write-Host "Processing texture folder: $textureFolderPath" -ForegroundColor Cyan
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
# Function to calculate checksums for files
|
|
|
|
|
function Get-FilesWithChecksums {
|
|
|
|
|
param(
|
|
|
|
|
[array]$Files
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$throttleLimit = [Math]::Max(1, [Environment]::ProcessorCount)
|
|
|
|
|
$parallelScriptBlock = {
|
|
|
|
|
try {
|
|
|
|
|
$hash = Get-FileHash -Path $_.FullName -Algorithm SHA256
|
|
|
|
|
[PSCustomObject]@{
|
|
|
|
|
File = $_
|
|
|
|
|
Hash = $hash.Hash
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Warning "Failed to calculate checksum for: $($_.FullName) - $($_.Exception.Message)"
|
|
|
|
|
$null
|
2025-12-19 10:59:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-19 11:57:12 -07:00
|
|
|
|
|
|
|
|
return $Files | ForEach-Object -Parallel $parallelScriptBlock -ThrottleLimit $throttleLimit | Where-Object { $null -ne $_ }
|
2025-12-19 11:26:50 -07:00
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
# Function to move files to common folder
|
|
|
|
|
function Move-FilesToCommon {
|
|
|
|
|
param(
|
|
|
|
|
[array]$Files,
|
|
|
|
|
[string]$CommonPath,
|
|
|
|
|
[string]$DuplicatesPath,
|
|
|
|
|
[hashtable]$FilesInCommon
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$movedCount = 0
|
|
|
|
|
$duplicateCount = 0
|
2025-12-19 11:26:50 -07:00
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
foreach ($fileObj in $Files) {
|
2025-12-19 11:26:50 -07:00
|
|
|
$fileName = $fileObj.Name
|
2025-12-19 11:57:12 -07:00
|
|
|
$destinationPath = Join-Path -Path $CommonPath -ChildPath $fileName
|
2025-12-19 11:26:50 -07:00
|
|
|
|
|
|
|
|
# Handle name conflicts
|
2025-12-19 11:57:12 -07:00
|
|
|
if ($FilesInCommon.ContainsKey($fileName)) {
|
2025-12-19 11:26:50 -07:00
|
|
|
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($fileName)
|
|
|
|
|
$extension = [System.IO.Path]::GetExtension($fileName)
|
|
|
|
|
$counter = 1
|
|
|
|
|
do {
|
|
|
|
|
$newFileName = "${baseName}_${counter}${extension}"
|
2025-12-19 11:57:12 -07:00
|
|
|
$destinationPath = Join-Path -Path $CommonPath -ChildPath $newFileName
|
2025-12-19 11:26:50 -07:00
|
|
|
$counter++
|
2025-12-19 11:57:12 -07:00
|
|
|
} while ($FilesInCommon.ContainsKey($newFileName) -or (Test-Path -Path $destinationPath))
|
2025-12-19 11:26:50 -07:00
|
|
|
|
|
|
|
|
$fileName = $newFileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Move-Item -Path $fileObj.FullName -Destination $destinationPath -Force
|
2025-12-19 11:57:12 -07:00
|
|
|
$FilesInCommon[$fileName] = $true
|
2025-12-19 11:26:50 -07:00
|
|
|
$movedCount++
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Warning "Failed to move file: $($fileObj.FullName) - $($_.Exception.Message)"
|
|
|
|
|
}
|
2025-12-19 11:57:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return @{
|
|
|
|
|
MovedCount = $movedCount
|
|
|
|
|
DuplicateCount = $duplicateCount
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 13:17:03 -07:00
|
|
|
# Function to extract suffix after blendfile prefix (e.g., "Demarco_Std_Teeth_ao.jpg" -> "Std_Teeth_ao.jpg")
|
|
|
|
|
# Only strips prefixes that are in the $ValidPrefixes list
|
|
|
|
|
function Get-FileNameWithoutPrefix {
|
|
|
|
|
param(
|
|
|
|
|
[string]$FileName,
|
|
|
|
|
[string[]]$ValidPrefixes
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Validate input
|
|
|
|
|
if ([string]::IsNullOrWhiteSpace($FileName)) {
|
|
|
|
|
return $FileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# If no valid prefixes provided, return original filename
|
|
|
|
|
if ($null -eq $ValidPrefixes -or $ValidPrefixes.Count -eq 0) {
|
|
|
|
|
return $FileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Use Split instead of regex for robustness (avoids $matches variable issues in loops)
|
|
|
|
|
# Split by the first underscore only (limit to 2 parts)
|
|
|
|
|
$parts = $FileName.Split('_', 2)
|
|
|
|
|
|
|
|
|
|
if ($parts.Length -eq 2) {
|
|
|
|
|
$prefix = $parts[0]
|
|
|
|
|
$suffix = $parts[1]
|
|
|
|
|
|
|
|
|
|
# Only strip if the prefix is in the valid prefixes list
|
|
|
|
|
if ($ValidPrefixes -contains $prefix -and -not [string]::IsNullOrWhiteSpace($suffix)) {
|
|
|
|
|
return $suffix
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# No valid prefix found or suffix is empty, return original filename
|
|
|
|
|
return $FileName
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
# Function to process duplicate group
|
|
|
|
|
function Process-DuplicateGroup {
|
|
|
|
|
param(
|
|
|
|
|
[array]$Files,
|
|
|
|
|
[string]$CommonPath,
|
2025-12-19 13:17:03 -07:00
|
|
|
[hashtable]$FilesInCommon,
|
|
|
|
|
[switch]$StripPrefix,
|
|
|
|
|
[string[]]$ValidPrefixes = @()
|
2025-12-19 11:57:12 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$movedCount = 0
|
|
|
|
|
$duplicateCount = 0
|
|
|
|
|
|
|
|
|
|
if ($Files.Count -eq 1) {
|
|
|
|
|
# Single file - leave in place (will be processed in Pass 2)
|
|
|
|
|
return @{
|
|
|
|
|
MovedCount = 0
|
|
|
|
|
DuplicateCount = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 13:17:03 -07:00
|
|
|
# Validate files array
|
|
|
|
|
if ($null -eq $Files -or $Files.Count -eq 0) {
|
|
|
|
|
return @{
|
|
|
|
|
MovedCount = 0
|
|
|
|
|
DuplicateCount = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
# Multiple files with same checksum (duplicates)
|
2025-12-19 13:17:03 -07:00
|
|
|
# Determine the filename to use
|
2025-12-19 11:57:12 -07:00
|
|
|
$firstFile = $Files[0].File
|
2025-12-19 13:17:03 -07:00
|
|
|
if ($null -eq $firstFile -or [string]::IsNullOrWhiteSpace($firstFile.Name)) {
|
|
|
|
|
Write-Warning "Invalid file object in duplicate group"
|
|
|
|
|
return @{
|
|
|
|
|
MovedCount = 0
|
|
|
|
|
DuplicateCount = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
$fileName = $firstFile.Name
|
2025-12-19 13:17:03 -07:00
|
|
|
|
|
|
|
|
# If StripPrefix is enabled, always strip prefix from the filename
|
|
|
|
|
if ($StripPrefix) {
|
|
|
|
|
Write-Host " [DEBUG] StripPrefix enabled for group with $($Files.Count) file(s)" -ForegroundColor Magenta
|
|
|
|
|
# Always try to strip prefix - if file has a prefix, it will be removed
|
|
|
|
|
$strippedName = Get-FileNameWithoutPrefix -FileName $firstFile.Name -ValidPrefixes $ValidPrefixes
|
|
|
|
|
|
|
|
|
|
Write-Host " [DEBUG] Original: '$($firstFile.Name)' -> Stripped: '$strippedName'" -ForegroundColor Gray
|
|
|
|
|
|
|
|
|
|
# Use stripped name if it's different from original (prefix was removed)
|
|
|
|
|
# Otherwise keep original (file had no prefix to strip)
|
|
|
|
|
if ($strippedName -ne $firstFile.Name) {
|
|
|
|
|
$fileName = $strippedName
|
|
|
|
|
Write-Host " [DEBUG] Using stripped name: '$fileName'" -ForegroundColor Green
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host " [DEBUG] No prefix found, keeping original: '$fileName'" -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# If we have multiple files with same checksum, try to find common suffix
|
|
|
|
|
# This handles cases where files from different blendfiles share the same texture
|
|
|
|
|
if ($Files.Count -gt 1) {
|
|
|
|
|
$allSuffixes = @()
|
|
|
|
|
foreach ($fileObj in $Files) {
|
|
|
|
|
# $fileObj is a PSCustomObject with .File property, so access .File.Name
|
|
|
|
|
$suffix = Get-FileNameWithoutPrefix -FileName $fileObj.File.Name -ValidPrefixes $ValidPrefixes
|
|
|
|
|
$allSuffixes += $suffix
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host " [DEBUG] All suffixes: $($allSuffixes -join ', ')" -ForegroundColor Gray
|
|
|
|
|
|
|
|
|
|
# If all files strip to the same suffix, use that
|
|
|
|
|
$uniqueSuffixes = @($allSuffixes | Select-Object -Unique)
|
|
|
|
|
if ($uniqueSuffixes.Count -eq 1 -and $uniqueSuffixes[0] -ne $firstFile.Name) {
|
|
|
|
|
$fileName = [string]$uniqueSuffixes[0]
|
|
|
|
|
Write-Host " [DEBUG] All files share common suffix, using: '$fileName'" -ForegroundColor Green
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host " [DEBUG] Final filename: '$fileName'" -ForegroundColor Cyan
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Ensure fileName is never null or empty
|
|
|
|
|
if ([string]::IsNullOrWhiteSpace($fileName)) {
|
|
|
|
|
$fileName = $firstFile.Name
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
$destinationPath = Join-Path -Path $CommonPath -ChildPath $fileName
|
|
|
|
|
|
|
|
|
|
# Handle name conflicts for the first file
|
|
|
|
|
if ($FilesInCommon.ContainsKey($fileName)) {
|
|
|
|
|
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($fileName)
|
|
|
|
|
$extension = [System.IO.Path]::GetExtension($fileName)
|
|
|
|
|
$counter = 1
|
|
|
|
|
do {
|
|
|
|
|
$newFileName = "${baseName}_${counter}${extension}"
|
|
|
|
|
$destinationPath = Join-Path -Path $CommonPath -ChildPath $newFileName
|
|
|
|
|
$counter++
|
|
|
|
|
} while ($FilesInCommon.ContainsKey($newFileName) -or (Test-Path -Path $destinationPath))
|
|
|
|
|
|
|
|
|
|
$fileName = $newFileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Move-Item -Path $firstFile.FullName -Destination $destinationPath -Force
|
|
|
|
|
$FilesInCommon[$fileName] = $true
|
|
|
|
|
$movedCount++
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Warning "Failed to move first duplicate file: $($firstFile.FullName) - $($_.Exception.Message)"
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 13:20:58 -07:00
|
|
|
# Delete remaining duplicate files (they're replaced by the common file)
|
2025-12-19 11:57:12 -07:00
|
|
|
for ($i = 1; $i -lt $Files.Count; $i++) {
|
|
|
|
|
$fileObj = $Files[$i].File
|
2025-12-19 11:26:50 -07:00
|
|
|
try {
|
2025-12-19 13:20:58 -07:00
|
|
|
Remove-Item -Path $fileObj.FullName -Force
|
2025-12-19 11:57:12 -07:00
|
|
|
$duplicateCount++
|
2025-12-19 11:26:50 -07:00
|
|
|
} catch {
|
2025-12-19 13:20:58 -07:00
|
|
|
Write-Warning "Failed to delete duplicate file: $($fileObj.FullName) - $($_.Exception.Message)"
|
2025-12-19 11:26:50 -07:00
|
|
|
}
|
2025-12-19 11:57:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return @{
|
|
|
|
|
MovedCount = $movedCount
|
|
|
|
|
DuplicateCount = $duplicateCount
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
# PASS 1: Intra-Blendfile Processing
|
|
|
|
|
# ============================================================================
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "=== PASS 1: Intra-Blendfile Processing ===" -ForegroundColor Cyan
|
|
|
|
|
|
|
|
|
|
# Get all direct subdirectories of texture folder (blendfile folders)
|
|
|
|
|
$blendfileFolders = Get-ChildItem -Path $textureFolderPath -Directory | Where-Object { $_.Name -ne "common" }
|
|
|
|
|
|
|
|
|
|
if ($null -eq $blendfileFolders -or $blendfileFolders.Count -eq 0) {
|
|
|
|
|
Write-Host "No blendfile folders found. Skipping Pass 1." -ForegroundColor Yellow
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host "Found $($blendfileFolders.Count) blendfile folder(s) to process." -ForegroundColor Green
|
|
|
|
|
|
|
|
|
|
$totalPass1Moved = 0
|
|
|
|
|
$totalPass1Duplicates = 0
|
|
|
|
|
|
|
|
|
|
foreach ($blendfileFolder in $blendfileFolders) {
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Processing blendfile: $($blendfileFolder.Name)" -ForegroundColor Yellow
|
2025-12-19 11:26:50 -07:00
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
# Get all files in this blendfile folder, excluding \common folders
|
|
|
|
|
$blendfileFiles = Get-ChildItem -Path $blendfileFolder.FullName -Recurse -File | Where-Object { $_.FullName -notlike "*\common\*" }
|
|
|
|
|
|
|
|
|
|
if ($null -eq $blendfileFiles -or $blendfileFiles.Count -eq 0) {
|
|
|
|
|
Write-Host " No files found in this blendfile folder." -ForegroundColor Gray
|
|
|
|
|
continue
|
2025-12-19 10:59:09 -07:00
|
|
|
}
|
2025-12-19 11:57:12 -07:00
|
|
|
|
|
|
|
|
Write-Host " Found $($blendfileFiles.Count) files." -ForegroundColor Gray
|
|
|
|
|
|
|
|
|
|
# Calculate checksums
|
|
|
|
|
Write-Host " Calculating checksums..." -ForegroundColor Gray
|
|
|
|
|
$filesWithChecksums = Get-FilesWithChecksums -Files $blendfileFiles
|
|
|
|
|
|
|
|
|
|
# Group by checksum
|
|
|
|
|
$groupedByChecksum = $filesWithChecksums | Group-Object -Property Hash
|
|
|
|
|
|
|
|
|
|
Write-Host " Found $($groupedByChecksum.Count) unique checksums." -ForegroundColor Gray
|
|
|
|
|
|
2025-12-19 13:20:58 -07:00
|
|
|
# Create [blendfile]\common directory
|
2025-12-19 11:57:12 -07:00
|
|
|
$blendfileCommonPath = Join-Path -Path $blendfileFolder.FullName -ChildPath "common"
|
|
|
|
|
|
|
|
|
|
if (-not (Test-Path -Path $blendfileCommonPath -PathType Container)) {
|
|
|
|
|
New-Item -ItemType Directory -Path $blendfileCommonPath | Out-Null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Track filenames already in [blendfile]\common
|
|
|
|
|
$filesInBlendfileCommon = @{}
|
|
|
|
|
|
|
|
|
|
# Process each checksum group
|
|
|
|
|
$blendfileMoved = 0
|
|
|
|
|
$blendfileDuplicates = 0
|
|
|
|
|
|
|
|
|
|
foreach ($group in $groupedByChecksum) {
|
2025-12-19 13:20:58 -07:00
|
|
|
$result = Process-DuplicateGroup -Files $group.Group -CommonPath $blendfileCommonPath -FilesInCommon $filesInBlendfileCommon
|
2025-12-19 11:57:12 -07:00
|
|
|
$blendfileMoved += $result.MovedCount
|
|
|
|
|
$blendfileDuplicates += $result.DuplicateCount
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 13:20:58 -07:00
|
|
|
Write-Host " Moved $blendfileMoved file(s) to \common, deleted $blendfileDuplicates duplicate(s)" -ForegroundColor Green
|
2025-12-19 11:57:12 -07:00
|
|
|
$totalPass1Moved += $blendfileMoved
|
|
|
|
|
$totalPass1Duplicates += $blendfileDuplicates
|
2025-12-19 10:59:09 -07:00
|
|
|
}
|
2025-12-19 11:57:12 -07:00
|
|
|
|
|
|
|
|
Write-Host ""
|
2025-12-19 13:20:58 -07:00
|
|
|
Write-Host "Pass 1 complete: $totalPass1Moved file(s) moved, $totalPass1Duplicates duplicate(s) deleted" -ForegroundColor Green
|
2025-12-19 11:57:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
# PASS 2: Inter-Blendfile Processing
|
|
|
|
|
# ============================================================================
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "=== PASS 2: Inter-Blendfile Processing ===" -ForegroundColor Cyan
|
|
|
|
|
|
2025-12-19 13:17:03 -07:00
|
|
|
# Build list of valid blendfile prefixes from folder names
|
|
|
|
|
$validBlendfilePrefixes = @()
|
|
|
|
|
$blendfileFoldersForPrefixes = Get-ChildItem -Path $textureFolderPath -Directory | Where-Object { $_.Name -ne "common" }
|
|
|
|
|
if ($null -ne $blendfileFoldersForPrefixes) {
|
|
|
|
|
$validBlendfilePrefixes = $blendfileFoldersForPrefixes | ForEach-Object { $_.Name }
|
|
|
|
|
Write-Host "Valid blendfile prefixes: $($validBlendfilePrefixes -join ', ')" -ForegroundColor Gray
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 15:34:07 -07:00
|
|
|
# Collect FlatColors files separately (including those in \common\FlatColors folders, but not from root \common\FlatColors)
|
|
|
|
|
Write-Host "Collecting FlatColors files..." -ForegroundColor Yellow
|
|
|
|
|
$rootCommonFlatColorsPath = Join-Path -Path $textureFolderPath -ChildPath "common\FlatColors"
|
|
|
|
|
$allFlatColorsFiles = Get-ChildItem -Path $textureFolderPath -Recurse -File | Where-Object {
|
|
|
|
|
($_.FullName -like "*\FlatColors\*" -or $_.Name -like "*FlatColors*") -and
|
|
|
|
|
$_.FullName -notlike "$([regex]::Escape($rootCommonFlatColorsPath))\*"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Get all remaining files (excluding all \common folders, but we'll handle FlatColors separately)
|
2025-12-19 11:57:12 -07:00
|
|
|
Write-Host "Collecting remaining files..." -ForegroundColor Yellow
|
|
|
|
|
$remainingFiles = Get-ChildItem -Path $textureFolderPath -Recurse -File | Where-Object { $_.FullName -notlike "*\common\*" }
|
|
|
|
|
|
|
|
|
|
if ($null -eq $remainingFiles -or $remainingFiles.Count -eq 0) {
|
|
|
|
|
Write-Host "No remaining files found to process." -ForegroundColor Yellow
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host "Found $($remainingFiles.Count) remaining files to process." -ForegroundColor Green
|
|
|
|
|
|
|
|
|
|
# Calculate checksums
|
|
|
|
|
Write-Host "Calculating checksums using parallel processing (this may take a while)..." -ForegroundColor Yellow
|
|
|
|
|
$filesWithChecksums = Get-FilesWithChecksums -Files $remainingFiles
|
|
|
|
|
|
|
|
|
|
Write-Host "Checksum calculation complete." -ForegroundColor Green
|
|
|
|
|
|
2025-12-19 15:34:07 -07:00
|
|
|
# Separate FlatColors files from other files before processing
|
|
|
|
|
Write-Host "Identifying FlatColors files..." -ForegroundColor Yellow
|
|
|
|
|
$flatColorsFiles = $allFlatColorsFiles | Where-Object { (Test-Path -Path $_.FullName) }
|
|
|
|
|
$nonFlatColorsFiles = $remainingFiles | Where-Object { $_.Name -notlike "*FlatColors*" }
|
|
|
|
|
|
|
|
|
|
Write-Host "Found $($flatColorsFiles.Count) FlatColors file(s) (including from \common\FlatColors folders), $($nonFlatColorsFiles.Count) other file(s)" -ForegroundColor Gray
|
|
|
|
|
|
|
|
|
|
# Group non-FlatColors files by checksum
|
2025-12-19 11:57:12 -07:00
|
|
|
Write-Host "Grouping files by checksum..." -ForegroundColor Yellow
|
2025-12-19 15:34:07 -07:00
|
|
|
$nonFlatColorsWithChecksums = $filesWithChecksums | Where-Object { $_.File.Name -notlike "*FlatColors*" }
|
|
|
|
|
$groupedByChecksum = $nonFlatColorsWithChecksums | Group-Object -Property Hash
|
2025-12-19 11:57:12 -07:00
|
|
|
|
|
|
|
|
Write-Host "Found $($groupedByChecksum.Count) unique checksums." -ForegroundColor Green
|
|
|
|
|
|
2025-12-19 13:20:58 -07:00
|
|
|
# Create \textures\common directory
|
2025-12-19 11:57:12 -07:00
|
|
|
$rootCommonPath = Join-Path -Path $textureFolderPath -ChildPath "common"
|
|
|
|
|
|
|
|
|
|
if (-not (Test-Path -Path $rootCommonPath -PathType Container)) {
|
|
|
|
|
New-Item -ItemType Directory -Path $rootCommonPath | Out-Null
|
|
|
|
|
Write-Host "Created directory: $rootCommonPath" -ForegroundColor Green
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Track filenames already in \textures\common
|
|
|
|
|
$filesInRootCommon = @{}
|
|
|
|
|
|
2025-12-19 15:34:07 -07:00
|
|
|
# Process each checksum group (excluding FlatColors)
|
2025-12-19 13:20:58 -07:00
|
|
|
Write-Host "Moving files to \common and deleting duplicates..." -ForegroundColor Yellow
|
2025-12-19 11:57:12 -07:00
|
|
|
$pass2Moved = 0
|
|
|
|
|
$pass2Duplicates = 0
|
|
|
|
|
|
|
|
|
|
foreach ($group in $groupedByChecksum) {
|
|
|
|
|
$files = $group.Group
|
|
|
|
|
|
|
|
|
|
if ($files.Count -eq 1) {
|
|
|
|
|
# Single file - leave in place (unique file)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Multiple files with same checksum (duplicates across blendfiles)
|
2025-12-19 13:17:03 -07:00
|
|
|
# Use StripPrefix to remove blendfile prefixes (e.g., "Demarco_", "Chan_") when all files share the same suffix
|
2025-12-19 13:20:58 -07:00
|
|
|
$result = Process-DuplicateGroup -Files $files -CommonPath $rootCommonPath -FilesInCommon $filesInRootCommon -StripPrefix -ValidPrefixes $validBlendfilePrefixes
|
2025-12-19 11:57:12 -07:00
|
|
|
$pass2Moved += $result.MovedCount
|
|
|
|
|
$pass2Duplicates += $result.DuplicateCount
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 15:34:07 -07:00
|
|
|
# Process FlatColors files: merge duplicates and move to \common\FlatColors
|
|
|
|
|
Write-Host "Processing FlatColors files (merging duplicates)..." -ForegroundColor Yellow
|
|
|
|
|
|
|
|
|
|
if ($null -ne $flatColorsFiles -and $flatColorsFiles.Count -gt 0) {
|
|
|
|
|
Write-Host "Found $($flatColorsFiles.Count) FlatColors file(s) to process" -ForegroundColor Gray
|
|
|
|
|
|
|
|
|
|
# Calculate checksums for FlatColors files
|
|
|
|
|
Write-Host "Calculating checksums for FlatColors files..." -ForegroundColor Yellow
|
|
|
|
|
$flatColorsWithChecksums = Get-FilesWithChecksums -Files $flatColorsFiles
|
|
|
|
|
|
|
|
|
|
# Group by checksum to find duplicates
|
|
|
|
|
$flatColorsGroupedByChecksum = $flatColorsWithChecksums | Group-Object -Property Hash
|
|
|
|
|
|
|
|
|
|
Write-Host "Found $($flatColorsGroupedByChecksum.Count) unique FlatColors file(s)" -ForegroundColor Gray
|
|
|
|
|
|
|
|
|
|
# Create \textures\common\FlatColors directory
|
|
|
|
|
$flatColorsPath = Join-Path -Path $rootCommonPath -ChildPath "FlatColors"
|
|
|
|
|
if (-not (Test-Path -Path $flatColorsPath -PathType Container)) {
|
|
|
|
|
New-Item -ItemType Directory -Path $flatColorsPath | Out-Null
|
|
|
|
|
Write-Host "Created directory: $flatColorsPath" -ForegroundColor Green
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Track filenames already in \common\FlatColors
|
|
|
|
|
$filesInFlatColors = @{}
|
|
|
|
|
$flatColorsMoved = 0
|
|
|
|
|
$flatColorsDuplicates = 0
|
|
|
|
|
|
|
|
|
|
foreach ($group in $flatColorsGroupedByChecksum) {
|
|
|
|
|
$files = $group.Group
|
|
|
|
|
|
|
|
|
|
if ($files.Count -eq 1) {
|
|
|
|
|
# Single file - move to FlatColors folder
|
|
|
|
|
$fileObj = $files[0].File
|
|
|
|
|
$fileName = $fileObj.Name
|
|
|
|
|
|
|
|
|
|
# Strip blendfile prefix if present
|
|
|
|
|
$strippedName = Get-FileNameWithoutPrefix -FileName $fileName -ValidPrefixes $validBlendfilePrefixes
|
|
|
|
|
if ($strippedName -ne $fileName) {
|
|
|
|
|
$fileName = $strippedName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$destinationPath = Join-Path -Path $flatColorsPath -ChildPath $fileName
|
|
|
|
|
|
|
|
|
|
# Handle name conflicts (shouldn't happen for unique files, but just in case)
|
|
|
|
|
if ($filesInFlatColors.ContainsKey($fileName) -or (Test-Path -Path $destinationPath)) {
|
|
|
|
|
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($fileName)
|
|
|
|
|
$extension = [System.IO.Path]::GetExtension($fileName)
|
|
|
|
|
$counter = 1
|
|
|
|
|
do {
|
|
|
|
|
$newFileName = "${baseName}_${counter}${extension}"
|
|
|
|
|
$destinationPath = Join-Path -Path $flatColorsPath -ChildPath $newFileName
|
|
|
|
|
$counter++
|
|
|
|
|
} while ($filesInFlatColors.ContainsKey($newFileName) -or (Test-Path -Path $destinationPath))
|
|
|
|
|
$fileName = $newFileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Move-Item -Path $fileObj.FullName -Destination $destinationPath -Force
|
|
|
|
|
$filesInFlatColors[$fileName] = $true
|
|
|
|
|
$flatColorsMoved++
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Warning "Failed to move FlatColors file: $($fileObj.FullName) - $($_.Exception.Message)"
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
# Multiple files with same checksum (duplicates) - merge them
|
|
|
|
|
# Use Process-DuplicateGroup to handle merging
|
|
|
|
|
$result = Process-DuplicateGroup -Files $files -CommonPath $flatColorsPath -FilesInCommon $filesInFlatColors -StripPrefix -ValidPrefixes $validBlendfilePrefixes
|
|
|
|
|
$flatColorsMoved += $result.MovedCount
|
|
|
|
|
$flatColorsDuplicates += $result.DuplicateCount
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "Moved $flatColorsMoved unique FlatColors file(s) to \common\FlatColors, deleted $flatColorsDuplicates duplicate(s)" -ForegroundColor Green
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host "No FlatColors files found to process." -ForegroundColor Gray
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:57:12 -07:00
|
|
|
Write-Host ""
|
2025-12-19 13:20:58 -07:00
|
|
|
Write-Host "Pass 2 complete: $pass2Moved file(s) moved to \common, $pass2Duplicates duplicate(s) deleted" -ForegroundColor Green
|
2025-12-19 10:59:09 -07:00
|
|
|
}
|
|
|
|
|
|
2025-12-19 15:34:07 -07:00
|
|
|
# Function to remove empty folders recursively
|
|
|
|
|
function Remove-EmptyFolders {
|
|
|
|
|
param(
|
|
|
|
|
[string]$Path,
|
|
|
|
|
[string]$RootPath
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$removedCount = 0
|
|
|
|
|
|
|
|
|
|
# Get all subdirectories
|
|
|
|
|
$subdirs = Get-ChildItem -Path $Path -Directory -ErrorAction SilentlyContinue
|
|
|
|
|
|
|
|
|
|
if ($null -ne $subdirs) {
|
|
|
|
|
foreach ($subdir in $subdirs) {
|
|
|
|
|
# Recursively process subdirectories first
|
|
|
|
|
$removedCount += Remove-EmptyFolders -Path $subdir.FullName -RootPath $RootPath
|
|
|
|
|
|
|
|
|
|
# Check if this directory is now empty (after processing subdirectories)
|
|
|
|
|
$items = Get-ChildItem -Path $subdir.FullName -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -eq $items -or $items.Count -eq 0) {
|
|
|
|
|
# Don't remove the root path
|
|
|
|
|
if ($subdir.FullName -ne $RootPath) {
|
|
|
|
|
try {
|
|
|
|
|
Remove-Item -Path $subdir.FullName -Force -ErrorAction Stop
|
|
|
|
|
$removedCount++
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Warning "Failed to remove empty folder: $($subdir.FullName) - $($_.Exception.Message)"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $removedCount
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Remove empty folders
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Removing empty folders..." -ForegroundColor Yellow
|
|
|
|
|
$emptyFoldersRemoved = Remove-EmptyFolders -Path $textureFolderPath -RootPath $textureFolderPath
|
|
|
|
|
if ($emptyFoldersRemoved -gt 0) {
|
|
|
|
|
Write-Host "Removed $emptyFoldersRemoved empty folder(s)" -ForegroundColor Green
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host "No empty folders found to remove." -ForegroundColor Gray
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 11:26:50 -07:00
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "File organization complete!" -ForegroundColor Green
|