From 48d82b7b8a7eb61312d721c7fc90bd3bdbc811f0 Mon Sep 17 00:00:00 2001 From: David Bixler Date: Mon, 28 Oct 2024 04:07:16 -0400 Subject: [PATCH] Upload files to '' Initial Push --- MipmapDetector.cs | 583 ++++++++++++++++++++++++++++++++++++++++++++++ plugin.cfg | 7 + 2 files changed, 590 insertions(+) create mode 100644 MipmapDetector.cs create mode 100644 plugin.cfg diff --git a/MipmapDetector.cs b/MipmapDetector.cs new file mode 100644 index 0000000..acf715e --- /dev/null +++ b/MipmapDetector.cs @@ -0,0 +1,583 @@ +using Godot; +using System; +using System.Collections.Generic; +/// +/// A tool for finding and fixing images missing mipmaps. +/// Made with help from Claud (Sonnet) +/// +[Tool] +public partial class MipmapDetector : EditorPlugin +{ + private Button _toolButton; + private Control _mipmapPanel; + private VBoxContainer _resultContainer; + private ScrollContainer _scrollContainer; + private TextureProgressBar _progressBar; + private Label _statusLabel; + + + public override void _EnterTree() + { + _toolButton = new Button + { + Text = "Check Mipmaps", + TooltipText = "Scan project for textures with missing mipmaps" + }; + _toolButton.Pressed += OnCheckMipmapsPressed; + + AddControlToContainer(CustomControlContainer.Toolbar, _toolButton); + + CreatePanel(); + } + + public override void _ExitTree() + { + if (_toolButton != null) + { + RemoveControlFromContainer(CustomControlContainer.Toolbar, _toolButton); + _toolButton.QueueFree(); + } + if (_mipmapPanel != null) + { + RemoveControlFromDocks(_mipmapPanel); + _mipmapPanel.QueueFree(); + } + // if (_missingPanel != null) + // { + // RemoveControlFromDocks(_missingPanel); + // _missingPanel.QueueFree(); + // } + } + private void CheckImageValidity(string path) + { + try + { + var image = Image.LoadFromFile(path); + if (image == null) + { + // Add to missing/invalid files panel + GD.PrintErr(path, "Failed to load image file"); + return; + } + + // Additional image checks could go here + GD.Print($"Valid image found: {path}"); + GD.Print($"Size: {image.GetWidth()}x{image.GetHeight()}, Format: {image.GetFormat()}"); + } + catch (Exception e) + { + GD.PrintErr(path, $"Error loading image: {e.Message}"); + } + } + + // private void AddToMissingPanel(string path, string reason) + // { + // var hbox = new HBoxContainer(); + // + // var pathLabel = new Label + // { + // Text = $"{path}\nReason: {reason}", + // SizeFlagsHorizontal = Control.SizeFlags.ExpandFill + // }; + // hbox.AddChild(pathLabel); + // + // _missingContainer.AddChild(hbox); + // } + private void CreatePanel() + { + _mipmapPanel = new Control + { + Name = "Mipmap Inspector" + }; + + var vbox = new VBoxContainer + { + AnchorRight = 1, + AnchorBottom = 1 + }; + + // Add status section at the top + var statusBox = new HBoxContainer(); + _statusLabel = new Label + { + Text = "Ready", + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill + }; + statusBox.AddChild(_statusLabel); + + _progressBar = new TextureProgressBar + { + Visible = false, + CustomMinimumSize = new Vector2(200, 8) + }; + statusBox.AddChild(_progressBar); + + vbox.AddChild(statusBox); + + _scrollContainer = new ScrollContainer + { + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, + SizeFlagsVertical = Control.SizeFlags.ExpandFill + }; + + _resultContainer = new VBoxContainer + { + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill + }; + + _scrollContainer.AddChild(_resultContainer); + vbox.AddChild(_scrollContainer); + _mipmapPanel.AddChild(vbox); + + AddControlToDock(DockSlot.RightBl, _mipmapPanel); + } + + private void ShowLoading(string status) + { + _statusLabel.Text = status; + _progressBar.Visible = true; + // Start progress animation + _progressBar.Value = 0; + GetTree().CreateTimer(0.1).Timeout += AnimateProgress; + } + private void HideLoading() + { + _progressBar.Visible = false; + _statusLabel.Text = "Ready"; + } + private void AnimateProgress() + { + if (_progressBar.Visible) + { + _progressBar.Value = (_progressBar.Value + 1) % 100; + GetTree().CreateTimer(0.05).Timeout += AnimateProgress; + } + } + + + private void OnCheckMipmapsPressed() + { + ClearResults(); + ShowLoading("Scanning for textures..."); + + // Defer the actual scan to let the UI update + GetTree().CreateTimer(0.1).Timeout += () => + { + var texturePaths = FindAllTextures("res://"); + _statusLabel.Text = "Checking mipmaps..."; + var missingMipmaps = CheckMipmaps(texturePaths); + DisplayResults(missingMipmaps); + HideLoading(); + }; + } + + + private List FindAllTextures(string path) + { + var textures = new List(); + var dir = DirAccess.Open(path); + + if (dir == null) + return textures; + + // Skip addons folder + if (path.Contains("addons")) + return textures; + + // Check for .gdignore + if (FileAccess.FileExists(path.PathJoin(".gdignore"))) + return textures; + + dir.ListDirBegin(); + string fileName = dir.GetNext(); + + while (!string.IsNullOrEmpty(fileName)) + { + if (fileName != "." && fileName != "..") + { + string fullPath = path.PathJoin(fileName); + + if (dir.CurrentIsDir()) + { + if (path != "res://") + { + textures.AddRange(FindAllTextures(fullPath)); + } + else if (!fileName.StartsWith(".")) + { + textures.AddRange(FindAllTextures(fullPath)); + } + } + else if (path != "res://" && + (fileName.EndsWith(".png") || fileName.EndsWith(".jpg") || + fileName.EndsWith(".jpeg") || fileName.EndsWith(".webp"))) + { + if (FileAccess.FileExists(fullPath)) + { + textures.Add(fullPath); + } + } + } + fileName = dir.GetNext(); + } + + dir.ListDirEnd(); + return textures; + } + + private List CheckMipmaps(List texturePaths) + { + var missingMipmaps = new List(); + + foreach (string path in texturePaths) + { + if (!ResourceLoader.Exists(path)) + continue; + + var texture = ResourceLoader.Load(path); + if (texture != null) + { + var image = texture.GetImage(); + if (image != null && !image.HasMipmaps()) + { + missingMipmaps.Add(path); + } + } + } + + return missingMipmaps; + } + + private void DisplayResults(List missingMipmaps) + { + if (missingMipmaps.Count == 0) + { + var label = new Label { Text = "No textures with missing mipmaps found!" }; + _resultContainer.AddChild(label); + } + else + { + var headerBox = new HBoxContainer(); + + var headerLabel = new Label + { + Text = $"Found {missingMipmaps.Count} textures with missing mipmaps:", + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill + }; + headerBox.AddChild(headerLabel); + + var fixAllButton = new Button { Text = "Fix All" }; + fixAllButton.Pressed += () => FixAllMipmaps(missingMipmaps); + headerBox.AddChild(fixAllButton); + + _resultContainer.AddChild(headerBox); + + foreach (string path in missingMipmaps) + { var hbox = new HBoxContainer(); + + var pathLabel = new Label + { + Text = path, + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill + }; + + var pathButton = new Button + { + Flat = true, + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, + CustomMinimumSize = new Vector2(0, 0) + }; + pathButton.AddChild(pathLabel); + + // Add hover effect + pathButton.MouseEntered += () => pathButton.Modulate = new Color(1, 1, 1, 0.7f); + pathButton.MouseExited += () => pathButton.Modulate = Colors.White; + + // Handle click to select in FileSystem dock + pathButton.Pressed += () => SelectInFileSystem(path); + + hbox.AddChild(pathButton); + + var fixButton = new Button { Text = "Fix" }; + fixButton.Pressed += () => FixMipmaps(path); + hbox.AddChild(fixButton); + + _resultContainer.AddChild(hbox); + } + } + } + private void FixAllMipmaps(List paths) + { + ShowLoading($"Fixing mipmaps (0/{paths.Count})..."); + // Immediately clear the list to show we're processing + ClearResults(); + ProcessNextBatch(new Queue(paths)); + } + + private void ProcessNextBatch(Queue remainingPaths, int batchSize = 5) + { + if (remainingPaths.Count == 0) + { + HideLoading(); + // Force a filesystem scan + EditorInterface.Singleton.GetResourceFilesystem().Scan(); + + // Refresh the display + GetTree().CreateTimer(0.5).Timeout += () => + { + var texturePaths = FindAllTextures("res://"); + var missingMipmaps = CheckMipmaps(texturePaths); + DisplayResults(missingMipmaps); + }; + return; + } + + int totalCount = remainingPaths.Count; + int processedCount = 0; + + // Process a small batch + int count = Math.Min(batchSize, remainingPaths.Count); + var currentBatch = new List(); + + for (int i = 0; i < count; i++) + { + if (remainingPaths.TryDequeue(out string path)) + { + currentBatch.Add(path); + processedCount++; + } + } + + // Update status + _statusLabel.Text = $"Fixing mipmaps ({processedCount}/{totalCount})..."; + + // Process the batch + foreach (var path in currentBatch) + { + try + { + string importPath = path + ".import"; + if (!FileAccess.FileExists(importPath)) + continue; + + var file = FileAccess.Open(importPath, FileAccess.ModeFlags.ReadWrite); + if (file != null) + { + string content = file.GetAsText(); + file.Close(); + + content = content.Replace("mipmaps/generate=false", "mipmaps/generate=true"); + + file = FileAccess.Open(importPath, FileAccess.ModeFlags.Write); + if (file != null) + { + file.StoreString(content); + file.Close(); + } + } + } + catch (Exception e) + { + GD.PrintErr($"Error processing {path}: {e.Message}"); + } + } + + // Do a single reimport for the batch + if (currentBatch.Count > 0) + { + EditorInterface.Singleton.GetResourceFilesystem().ReimportFiles(currentBatch.ToArray()); + } + + // Schedule next batch with a delay + if (remainingPaths.Count > 0) + { + GetTree().CreateTimer(0.2).Timeout += () => ProcessNextBatch(remainingPaths); + } + } + + private bool UpdateImportSettings(string path) + { + try + { + // First verify the texture actually exists + if (!FileAccess.FileExists(path)) + { + GD.PushError($"Texture file doesn't exist: {path}"); + return false; + } + + string importPath = path + ".import"; + if (!FileAccess.FileExists(importPath)) + { + GD.PushError($"No import file found for: {path}"); + return false; + } + + // Read and modify the import file + var file = FileAccess.Open(importPath, FileAccess.ModeFlags.ReadWrite); + if (file == null) + { + GD.PushError($"Failed to open import file: {importPath}"); + return false; + } + + string content = file.GetAsText(); + file.Close(); + + // Parse and modify the import content + string[] lines = content.Split('\n'); + var newLines = new List(); + bool inMetadataSection = false; + bool addedMipmapSettings = false; + + foreach (string line in lines) + { + string trimmedLine = line.TrimStart(); + + if (trimmedLine.StartsWith("[")) + { + if (trimmedLine == "[metadata]") + { + inMetadataSection = true; + newLines.Add(line); + newLines.Add("mipmaps/generate=true"); + newLines.Add("mipmaps/limit=-1"); + addedMipmapSettings = true; + continue; + } + else + { + inMetadataSection = false; + } + } + + if (inMetadataSection && (trimmedLine.StartsWith("mipmaps/generate") || trimmedLine.StartsWith("mipmaps/limit"))) + { + continue; + } + + newLines.Add(line); + } + + if (!addedMipmapSettings) + { + newLines.Add("[metadata]"); + newLines.Add("mipmaps/generate=true"); + newLines.Add("mipmaps/limit=-1"); + } + + // Write back the modified content + file = FileAccess.Open(importPath, FileAccess.ModeFlags.Write); + if (file == null) + { + GD.PushError($"Failed to write to import file: {importPath}"); + return false; + } + + file.StoreString(string.Join("\n", newLines)); + file.Close(); + + // Delete the existing imported file to force a clean reimport + string importedPath = "res://.godot/imported/" + path.GetFile() + "-" + importPath.GetFile().Hash(); + if (FileAccess.FileExists(importedPath)) + { + DirAccess.RemoveAbsolute(importedPath); + } + + GD.Print($"Updated import settings for: {path}"); + return true; + } + catch (Exception e) + { + GD.PushError($"Exception while processing {path}: {e.Message}"); + return false; + } + } + private void SelectInFileSystem(string path) + { + // Get the EditorInterface singleton + var editorInterface = EditorInterface.Singleton; + + // Select the file in the FileSystem dock + editorInterface.GetFileSystemDock().NavigateToPath(path); + + // Optional: Also select it in the editor + editorInterface.EditResource(GD.Load(path)); + } + + private void FixMipmaps(string path) + { + try + { + string importPath = path + ".import"; + if (!FileAccess.FileExists(importPath)) + { + GD.PushError($"No import file found for: {path}"); + return; + } + + // Read current import settings + var file = FileAccess.Open(importPath, FileAccess.ModeFlags.ReadWrite); + if (file == null) + { + GD.PushError($"Failed to open import file: {path}"); + return; + } + + string content = file.GetAsText(); + file.Close(); + + // Directly replace the mipmap setting + content = content.Replace("mipmaps/generate=false", "mipmaps/generate=true"); + + // Write the modified content + file = FileAccess.Open(importPath, FileAccess.ModeFlags.Write); + if (file == null) + { + GD.PushError($"Failed to write to import file: {path}"); + return; + } + + file.StoreString(content); + file.Close(); + + // Delete the imported resource to force a clean reimport + string importedDir = "res://.godot/imported/"; + var importedFile = path.GetFile() + "-" + importPath.GetFile().Hash(); + string importedPath = importedDir.PathJoin(importedFile); + + if (FileAccess.FileExists(importedPath)) + { + DirAccess.RemoveAbsolute(importedPath); + } + + // Trigger reimport + EditorInterface.Singleton.GetResourceFilesystem().ReimportFiles(new[] { path }); + EditorInterface.Singleton.GetResourceFilesystem().Scan(); + + // Refresh the display after a short delay to allow for reimport + GetTree().CreateTimer(0.5).Timeout += () => + { + ClearResults(); + var texturePaths = FindAllTextures("res://"); + var missingMipmaps = CheckMipmaps(texturePaths); + DisplayResults(missingMipmaps); + }; + + GD.Print($"Successfully enabled mipmaps for: {path}"); + } + catch (Exception e) + { + GD.PushError($"Failed to process {path}: {e.Message}"); + } + } + + + private void ClearResults() + { + foreach (Node child in _resultContainer.GetChildren()) + { + child.QueueFree(); + } + } +} \ No newline at end of file diff --git a/plugin.cfg b/plugin.cfg new file mode 100644 index 0000000..42d0830 --- /dev/null +++ b/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="MipMap Fixer" +description="A plugin to find and list images with missing mipmaps " +author="David of Everlasting.Media" +version="0.1" +script="MipmapDetector.cs"