ProjectExporterController.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using AssetBank.Editor.Tools;
  7. using AssetBank.Settings;
  8. using UnityEditor;
  9. using UnityEditor.Animations;
  10. using UnityEngine;
  11. using UnityEngine.Audio;
  12. using UnityEngine.Video;
  13. namespace AssetBank.DockableWindow
  14. {
  15. public class ProjectExporterController
  16. {
  17. private static readonly string ProjectRoot = Path.GetDirectoryName(Application.dataPath);
  18. private readonly ProjectExporterSettings _settings;
  19. public ProjectExporterController()
  20. {
  21. _settings = ProjectExporterSettings.GetOrCreateSettings();
  22. }
  23. // Finds assets based on a search filter (e.g., "t:Scene") and builds a hierarchical model.
  24. public static AssetModel GetAssets(string filter, string category)
  25. {
  26. var root = new AssetModel("Assets", "Assets", category);
  27. var assetGuids = AssetDatabase.FindAssets(filter, new[] { "Assets" });
  28. foreach (var guid in assetGuids)
  29. {
  30. var path = AssetDatabase.GUIDToAssetPath(guid);
  31. if (filter.Contains("t:ScriptableObject") && !path.EndsWith(".asset"))
  32. {
  33. continue;
  34. }
  35. var parts = path.Split('/');
  36. var currentNode = root;
  37. for (int i = 1; i < parts.Length; i++) // Start at 1 to skip "Assets"
  38. {
  39. var part = parts[i];
  40. var child = currentNode.Children.FirstOrDefault(c => c.Name == part);
  41. if (child == null)
  42. {
  43. var currentPath = string.Join("/", parts.Take(i + 1));
  44. child = new AssetModel(currentPath, part, category);
  45. currentNode.Children.Add(child);
  46. }
  47. currentNode = child;
  48. }
  49. }
  50. return root;
  51. }
  52. // Lists all files in the ProjectSettings directory.
  53. public AssetModel GetProjectSettingsFiles()
  54. {
  55. const string category = "Settings";
  56. var settingsRootPath = Path.Combine(ProjectRoot, "ProjectSettings");
  57. var root = new AssetModel(settingsRootPath, "ProjectSettings", category);
  58. if (Directory.Exists(settingsRootPath))
  59. {
  60. // Filter to only include *.asset files.
  61. var files = Directory.GetFiles(settingsRootPath, "*.asset", SearchOption.AllDirectories);
  62. foreach (var file in files)
  63. {
  64. var relativePath = Path.GetRelativePath(ProjectRoot, file);
  65. root.Children.Add(new AssetModel(relativePath, Path.GetFileName(file), category));
  66. }
  67. }
  68. return root;
  69. }
  70. // Scans the entire Assets folder for .meta files and builds a hierarchical model.
  71. public AssetModel GetAllMetaFiles()
  72. {
  73. const string category = "Meta Files";
  74. var assetsRootPath = Application.dataPath; // This is the "Assets" folder
  75. var root = new AssetModel("Assets", "Assets", category);
  76. var files = Directory.GetFiles(assetsRootPath, "*.meta", SearchOption.AllDirectories);
  77. foreach (var file in files)
  78. {
  79. var relativePath = "Assets/" + Path.GetRelativePath(assetsRootPath, file).Replace('\\', '/');
  80. var parts = relativePath.Split('/');
  81. var currentNode = root;
  82. for (int i = 1; i < parts.Length; i++) // Start at 1 to skip "Assets"
  83. {
  84. var part = parts[i];
  85. var child = currentNode.Children.FirstOrDefault(c => c.Name == part);
  86. if (child == null)
  87. {
  88. var currentPath = string.Join("/", parts.Take(i + 1));
  89. child = new AssetModel(currentPath, part, category);
  90. currentNode.Children.Add(child);
  91. }
  92. currentNode = child;
  93. }
  94. }
  95. return root;
  96. }
  97. private string FindPythonExecutable()
  98. {
  99. var venvPath = Path.Combine(ProjectRoot, "venv", "bin", "python3");
  100. if (File.Exists(venvPath))
  101. {
  102. return venvPath;
  103. }
  104. venvPath = Path.Combine(ProjectRoot, "venv", "bin", "python");
  105. if (File.Exists(venvPath))
  106. {
  107. return venvPath;
  108. }
  109. return "python3";
  110. }
  111. private string GetConversionScriptPath()
  112. {
  113. var guids = AssetDatabase.FindAssets("convert_scene");
  114. if (guids.Length == 0)
  115. {
  116. UnityEngine.Debug.LogError("Conversion script 'convert_scene.py' not found.");
  117. return null;
  118. }
  119. var path = AssetDatabase.GUIDToAssetPath(guids[0]);
  120. return Path.Combine(ProjectRoot, path);
  121. }
  122. // Executes the Python conversion script for the selected assets.
  123. public void ExportAssets(AssetModel rootNode, string customExportPath = null)
  124. {
  125. var assetsToExport = new List<AssetModel>();
  126. CollectSelectedAssets(rootNode, assetsToExport);
  127. var filteredAssets = FilterAssets(assetsToExport.Select(a => a.Path).ToList())
  128. .ToHashSet();
  129. var finalAssetsToExport = assetsToExport.Where(a => filteredAssets.Contains(a.Path)).ToList();
  130. var pythonExecutable = FindPythonExecutable();
  131. var conversionScript = GetConversionScriptPath();
  132. if (string.IsNullOrEmpty(conversionScript)) return;
  133. var outputDirectory = customExportPath ?? Path.Combine(ProjectRoot, "Library", "ProjectDataExport");
  134. Directory.CreateDirectory(outputDirectory);
  135. EditorUtility.DisplayProgressBar("Exporting...", "Starting export process...", 0f);
  136. try
  137. {
  138. for (int i = 0; i < finalAssetsToExport.Count; i++)
  139. {
  140. var asset = finalAssetsToExport[i];
  141. var relativeAssetPath = asset.Path;
  142. var progress = (float)i / finalAssetsToExport.Count;
  143. EditorUtility.DisplayProgressBar("Exporting...", $"Processing {Path.GetFileName(relativeAssetPath)}", progress);
  144. var absoluteAssetPath = Path.Combine(ProjectRoot, relativeAssetPath);
  145. var outputJsonPath = Path.Combine(outputDirectory, relativeAssetPath.Replace('/', Path.DirectorySeparatorChar) + ".json");
  146. var fileOutputDir = Path.GetDirectoryName(outputJsonPath);
  147. if (!Directory.Exists(fileOutputDir))
  148. {
  149. Directory.CreateDirectory(fileOutputDir);
  150. }
  151. var arguments =
  152. $"\"{conversionScript}\" \"{absoluteAssetPath}\" \"{outputJsonPath}\"";
  153. var process = new Process
  154. {
  155. StartInfo = new ProcessStartInfo
  156. {
  157. FileName = pythonExecutable,
  158. Arguments = arguments,
  159. RedirectStandardOutput = true,
  160. RedirectStandardError = true,
  161. UseShellExecute = false,
  162. CreateNoWindow = true
  163. }
  164. };
  165. process.Start();
  166. string output = process.StandardOutput.ReadToEnd();
  167. string error = process.StandardError.ReadToEnd();
  168. process.WaitForExit();
  169. if (process.ExitCode != 0)
  170. {
  171. UnityEngine.Debug.LogError($"Error exporting {relativeAssetPath}: {error}");
  172. }
  173. else
  174. {
  175. if (_settings.OptimiseExport && _settings.CategoriesToOptimise.Contains(asset.Category))
  176. {
  177. var rawJson = File.ReadAllText(outputJsonPath);
  178. var processedJson = JsonDataTrimmer.Process(rawJson, _settings);
  179. File.WriteAllText(outputJsonPath, processedJson);
  180. }
  181. UnityEngine.Debug.Log($"Successfully exported {relativeAssetPath}: {output}");
  182. }
  183. }
  184. }
  185. finally
  186. {
  187. EditorUtility.ClearProgressBar();
  188. }
  189. }
  190. private List<string> FilterAssets(List<string> assetPaths)
  191. {
  192. if (_settings == null)
  193. {
  194. UnityEngine.Debug.LogWarning("ProjectExporterSettings not found. Skipping filtering.");
  195. return assetPaths;
  196. }
  197. var ignoredFolders = _settings.FoldersToIgnore;
  198. var ignoredExtensions = _settings.FileExtensionsToIgnore.Select(ext => ext.StartsWith(".") ? ext : "." + ext).ToList();
  199. var ignoredUnityTypes = _settings.UnityTypesToIgnore;
  200. var filteredList = new List<string>();
  201. foreach (var path in assetPaths)
  202. {
  203. if (ignoredFolders.Any(folder => path.StartsWith(folder, StringComparison.OrdinalIgnoreCase)))
  204. {
  205. continue;
  206. }
  207. var extension = Path.GetExtension(path);
  208. if (ignoredExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
  209. {
  210. continue;
  211. }
  212. var assetPathForTypeCheck = path.EndsWith(".meta") ? path[..^5] : path;
  213. var assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPathForTypeCheck);
  214. if (assetType != null && IsTypeIgnored(assetType, ignoredUnityTypes))
  215. {
  216. continue;
  217. }
  218. filteredList.Add(path);
  219. }
  220. return filteredList;
  221. }
  222. private bool IsTypeIgnored(Type assetType, List<IgnoredUnityType> ignoredTypes)
  223. {
  224. foreach (var ignoredType in ignoredTypes)
  225. {
  226. if (ignoredType.assetType == UnityAssetType.Custom)
  227. {
  228. if (!string.IsNullOrEmpty(ignoredType.customType) && assetType.Name.Equals(ignoredType.customType, StringComparison.OrdinalIgnoreCase))
  229. {
  230. return true;
  231. }
  232. }
  233. else if (GetSystemTypeForEnum(ignoredType.assetType) == assetType)
  234. {
  235. return true;
  236. }
  237. }
  238. return false;
  239. }
  240. private Type GetSystemTypeForEnum(UnityAssetType unityAssetType)
  241. {
  242. switch (unityAssetType)
  243. {
  244. case UnityAssetType.Animation: return typeof(AnimationClip);
  245. case UnityAssetType.AnimatorController: return typeof(AnimatorController);
  246. case UnityAssetType.AnimatorOverrideController: return typeof(AnimatorOverrideController);
  247. case UnityAssetType.AudioClip: return typeof(AudioClip);
  248. case UnityAssetType.AudioMixer: return typeof(AudioMixer);
  249. case UnityAssetType.ComputeShader: return typeof(ComputeShader);
  250. case UnityAssetType.Font: return typeof(Font);
  251. case UnityAssetType.GUISkin: return typeof(GUISkin);
  252. case UnityAssetType.Material: return typeof(Material);
  253. case UnityAssetType.Mesh: return typeof(Mesh);
  254. case UnityAssetType.Model: return typeof(GameObject);
  255. case UnityAssetType.PhysicMaterial: return typeof(PhysicMaterial);
  256. case UnityAssetType.Prefab: return typeof(GameObject);
  257. case UnityAssetType.Scene: return typeof(SceneAsset);
  258. case UnityAssetType.Script: return typeof(MonoScript);
  259. case UnityAssetType.Shader: return typeof(Shader);
  260. case UnityAssetType.Sprite: return typeof(Sprite);
  261. case UnityAssetType.Texture: return typeof(Texture2D);
  262. case UnityAssetType.VideoClip: return typeof(VideoClip);
  263. case UnityAssetType.RenderTexture: return typeof(RenderTexture);
  264. case UnityAssetType.LightmapParameters: return typeof(LightmapParameters);
  265. default: return null;
  266. }
  267. }
  268. // Recursively collects the paths of all selected assets.
  269. private void CollectSelectedAssets(AssetModel node, List<AssetModel> selectedAssets)
  270. {
  271. // If the node itself is a file and is selected, add it.
  272. if (node.Children.Count == 0 && node.IsSelected)
  273. {
  274. selectedAssets.Add(node);
  275. }
  276. // Recurse into children
  277. foreach (var child in node.Children)
  278. {
  279. CollectSelectedAssets(child, selectedAssets);
  280. }
  281. }
  282. }
  283. }