mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-10 02:50:00 +08:00
fixed # 修补缺失资源,修正已知BUG
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<#
|
||||
Batch-crop Godot AtlasTexture .tres files into PNGs from their referenced atlas images.
|
||||
Requires a FULL RECOVERY output where atlas sources are PNG/WebP. This script does not
|
||||
unpack PCK files or decode .ctex; it only reads .tres/.tpsheet metadata and crops images.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ProjectRoot,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$OutputDirectory,
|
||||
|
||||
[string]$SpriteDirectory = 'images/atlases/compressed.sprites/card_template',
|
||||
|
||||
[switch]$Overwrite
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$root = [System.IO.Path]::GetFullPath($ProjectRoot).TrimEnd('\')
|
||||
$outRoot = [System.IO.Path]::GetFullPath($OutputDirectory).TrimEnd('\')
|
||||
$relativeSpriteDir = $SpriteDirectory.TrimStart('\', '/').Replace('/', '\')
|
||||
$sourceDir = [System.IO.Path]::GetFullPath((Join-Path $root $relativeSpriteDir))
|
||||
|
||||
if (-not (Test-Path -LiteralPath $root -PathType Container)) {
|
||||
throw "ProjectRoot not found: $root"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $sourceDir -PathType Container)) {
|
||||
throw "SpriteDirectory not found: $sourceDir"
|
||||
}
|
||||
if ($outRoot.Equals($root, [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$outRoot.StartsWith($root + '\', [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw 'OutputDirectory must be outside ProjectRoot so recovered source files stay untouched.'
|
||||
}
|
||||
|
||||
$invariant = [System.Globalization.CultureInfo]::InvariantCulture
|
||||
$files = @(Get-ChildItem -LiteralPath $sourceDir -Filter '*.tres' -File -Recurse)
|
||||
$written = 0
|
||||
$skipped = 0
|
||||
$errors = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
foreach ($tres in $files) {
|
||||
$sourceImage = $null
|
||||
$crop = $null
|
||||
try {
|
||||
$text = [System.IO.File]::ReadAllText($tres.FullName)
|
||||
if ($text -notmatch 'type="AtlasTexture"') {
|
||||
$skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
$atlasIdMatch = [regex]::Match($text, '(?m)^\s*atlas\s*=\s*ExtResource\("([^"]+)"\)')
|
||||
$regionMatch = [regex]::Match($text, '(?m)^\s*region\s*=\s*Rect2i?\(\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*\)')
|
||||
if (-not $atlasIdMatch.Success -or -not $regionMatch.Success) {
|
||||
$skipped++
|
||||
$errors.Add("Missing atlas/region fields: $($tres.FullName)")
|
||||
continue
|
||||
}
|
||||
|
||||
$atlasPath = $null
|
||||
foreach ($blockMatch in [regex]::Matches($text, '(?m)^\s*\[ext_resource[^\]]*\]')) {
|
||||
$block = $blockMatch.Value
|
||||
$idMatch = [regex]::Match($block, '\bid="([^"]+)"')
|
||||
$pathMatch = [regex]::Match($block, '\bpath="([^"]+)"')
|
||||
if ($idMatch.Success -and $pathMatch.Success -and
|
||||
$idMatch.Groups[1].Value -eq $atlasIdMatch.Groups[1].Value) {
|
||||
$atlasPath = $pathMatch.Groups[1].Value
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $atlasPath) {
|
||||
$skipped++
|
||||
$errors.Add("Could not resolve atlas ExtResource: $($tres.FullName)")
|
||||
continue
|
||||
}
|
||||
|
||||
if ($atlasPath.StartsWith('res://', [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
$atlasRelative = $atlasPath.Substring(6).Replace('/', '\')
|
||||
$atlasFile = Join-Path $root $atlasRelative
|
||||
} else {
|
||||
$atlasFile = Join-Path $tres.DirectoryName $atlasPath.Replace('/', '\')
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $atlasFile -PathType Leaf)) {
|
||||
$skipped++
|
||||
$errors.Add("Atlas image missing (Full Recovery should restore it as PNG): $atlasFile ; referenced by $($tres.FullName)")
|
||||
continue
|
||||
}
|
||||
|
||||
$values = @(
|
||||
[double]::Parse($regionMatch.Groups[1].Value, $invariant),
|
||||
[double]::Parse($regionMatch.Groups[2].Value, $invariant),
|
||||
[double]::Parse($regionMatch.Groups[3].Value, $invariant),
|
||||
[double]::Parse($regionMatch.Groups[4].Value, $invariant)
|
||||
)
|
||||
$rounded = @($values | ForEach-Object { [int][Math]::Round($_) })
|
||||
for ($i = 0; $i -lt 4; $i++) {
|
||||
if ([Math]::Abs($values[$i] - $rounded[$i]) -gt 0.001) {
|
||||
throw "Non-integer pixel region in $($tres.FullName)"
|
||||
}
|
||||
}
|
||||
$x = $rounded[0]; $y = $rounded[1]; $width = $rounded[2]; $height = $rounded[3]
|
||||
if ($x -lt 0 -or $y -lt 0 -or $width -le 0 -or $height -le 0) {
|
||||
throw "Invalid crop region Rect2($x, $y, $width, $height) in $($tres.FullName)"
|
||||
}
|
||||
|
||||
$sourceImage = [System.Drawing.Bitmap]::FromFile($atlasFile)
|
||||
if (($x + $width) -gt $sourceImage.Width -or ($y + $height) -gt $sourceImage.Height) {
|
||||
throw "Crop exceeds atlas bounds ($($sourceImage.Width)x$($sourceImage.Height)): $($tres.FullName)"
|
||||
}
|
||||
$rect = [System.Drawing.Rectangle]::new($x, $y, $width, $height)
|
||||
$crop = $sourceImage.Clone($rect, $sourceImage.PixelFormat)
|
||||
|
||||
$relativeTres = $tres.FullName.Substring($root.Length).TrimStart('\')
|
||||
$relativeOutput = [System.IO.Path]::ChangeExtension($relativeTres, '.png')
|
||||
$target = Join-Path $outRoot $relativeOutput
|
||||
$targetDir = Split-Path -Parent $target
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
if ((Test-Path -LiteralPath $target) -and -not $Overwrite) {
|
||||
$skipped++
|
||||
continue
|
||||
}
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
Remove-Item -LiteralPath $target -Force
|
||||
}
|
||||
$crop.Save($target, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$written++
|
||||
}
|
||||
catch {
|
||||
$skipped++
|
||||
$errors.Add("$($tres.FullName): $($_.Exception.Message)")
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $crop) { $crop.Dispose() }
|
||||
if ($null -ne $sourceImage) { $sourceImage.Dispose() }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "TRES scanned: $($files.Count)"
|
||||
Write-Output "PNG written: $written"
|
||||
Write-Output "Skipped: $skipped"
|
||||
foreach ($message in $errors) { Write-Warning $message }
|
||||
Reference in New Issue
Block a user