TMP_PackageUtilities.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. using System.Threading;
  9. using TMPro.EditorUtilities;
  10. namespace TMPro
  11. {
  12. // Suppressing warnings related to the use of private structures which are confusing the compiler as these data structures are used by .json files.
  13. #pragma warning disable 0649
  14. /// <summary>
  15. /// Data structure containing the target and replacement fileIDs and GUIDs which will require remapping from previous version of TextMesh Pro to the new TextMesh Pro UPM package.
  16. /// </summary>
  17. [System.Serializable]
  18. struct AssetConversionRecord
  19. {
  20. public string referencedResource;
  21. public string target;
  22. public string replacement;
  23. }
  24. /// <summary>
  25. /// Data structure containing a list of target and replacement fileID and GUID requiring remapping from previous versions of TextMesh Pro to the new TextMesh Pro UPM package.
  26. /// This data structure is populated with the data contained in the PackageConversionData.json file included in the package.
  27. /// </summary>
  28. [System.Serializable]
  29. class AssetConversionData
  30. {
  31. public List<AssetConversionRecord> assetRecords;
  32. }
  33. public class TMP_ProjectConversionUtility : EditorWindow
  34. {
  35. // Create Project Files GUID Remapping Tool window
  36. [MenuItem("Window/TextMeshPro/Project Files GUID Remapping Tool", false, 2100)]
  37. static void ShowConverterWindow()
  38. {
  39. var window = GetWindow<TMP_ProjectConversionUtility>();
  40. window.titleContent = new GUIContent("Conversion Tool");
  41. window.Focus();
  42. }
  43. private static HashSet<Type> m_IgnoreAssetTypes = new HashSet<Type>()
  44. {
  45. typeof(AnimatorOverrideController),
  46. typeof(AudioClip),
  47. typeof(AvatarMask),
  48. typeof(ComputeShader),
  49. typeof(Cubemap),
  50. typeof(DefaultAsset),
  51. typeof(Flare),
  52. typeof(Font),
  53. typeof(GUISkin),
  54. typeof(HumanTemplate),
  55. typeof(LightingDataAsset),
  56. typeof(Mesh),
  57. typeof(MonoScript),
  58. typeof(PhysicMaterial),
  59. typeof(PhysicsMaterial2D),
  60. typeof(RenderTexture),
  61. typeof(Shader),
  62. typeof(TerrainData),
  63. typeof(TextAsset),
  64. typeof(Texture2D),
  65. typeof(Texture2DArray),
  66. typeof(Texture3D),
  67. typeof(UnityEditorInternal.AssemblyDefinitionAsset),
  68. typeof(UnityEngine.AI.NavMeshData),
  69. typeof(UnityEngine.Tilemaps.Tile),
  70. typeof(UnityEngine.U2D.SpriteAtlas),
  71. typeof(UnityEngine.Video.VideoClip),
  72. };
  73. /// <summary>
  74. ///
  75. /// </summary>
  76. struct AssetModificationRecord
  77. {
  78. public string assetFilePath;
  79. public string assetDataFile;
  80. }
  81. struct AssetFileRecord
  82. {
  83. public string assetFilePath;
  84. public string assetMetaFilePath;
  85. public AssetFileRecord(string filePath, string metaFilePath)
  86. {
  87. this.assetFilePath = filePath;
  88. this.assetMetaFilePath = metaFilePath;
  89. }
  90. }
  91. private static string m_ProjectPath;
  92. private static string m_ProjectFolderToScan;
  93. private static bool m_IsAlreadyScanningProject;
  94. private static bool m_CancelScanProcess;
  95. private static string k_ProjectScanReportDefaultText = "<color=#FFFF80><b>Project Scan Results</b></color>\n";
  96. private static string k_ProjectScanLabelPrefix = "Scanning: ";
  97. private static string m_ProjectScanResults = string.Empty;
  98. private static Vector2 m_ProjectScanResultScrollPosition;
  99. private static float m_ProgressPercentage = 0;
  100. private static int m_ScanningTotalFiles;
  101. private static int m_RemainingFilesToScan;
  102. private static int m_ScanningCurrentFileIndex;
  103. private static string m_ScanningCurrentFileName;
  104. private static AssetConversionData m_ConversionData;
  105. private static List<AssetModificationRecord> m_ModifiedAssetList = new List<AssetModificationRecord>();
  106. void OnEnable()
  107. {
  108. // Set Editor Window Size
  109. SetEditorWindowSize();
  110. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  111. }
  112. void OnGUI()
  113. {
  114. GUILayout.BeginVertical();
  115. {
  116. // Scan project files and resources
  117. GUILayout.BeginVertical(EditorStyles.helpBox);
  118. {
  119. GUILayout.Label("Scan Project Files", EditorStyles.boldLabel);
  120. GUILayout.Label("Press the <i>Scan Project Files</i> button to begin scanning your project for files & resources that were created with a previous version of TextMesh Pro.", TMP_UIStyleManager.label);
  121. GUILayout.Space(10f);
  122. GUILayout.Label("Project folder to be scanned. Example \"Assets/TextMesh Pro\"");
  123. m_ProjectFolderToScan = EditorGUILayout.TextField("Folder Path: Assets/", m_ProjectFolderToScan);
  124. GUILayout.Space(5f);
  125. GUI.enabled = m_IsAlreadyScanningProject == false ? true : false;
  126. if (GUILayout.Button("Scan Project Files"))
  127. {
  128. m_CancelScanProcess = false;
  129. // Make sure Asset Serialization mode is set to ForceText and Version Control mode to Visible Meta Files.
  130. if (CheckProjectSerializationAndSourceControlModes() == true)
  131. {
  132. m_ProjectPath = Path.GetFullPath("Assets/..");
  133. TMP_EditorCoroutine.StartCoroutine(ScanProjectFiles());
  134. }
  135. else
  136. {
  137. EditorUtility.DisplayDialog("Project Settings Change Required", "In menu options \"Edit - Project Settings - Editor\", please change Asset Serialization Mode to ForceText and Source Control Mode to Visible Meta Files.", "OK", string.Empty);
  138. }
  139. }
  140. GUI.enabled = true;
  141. // Display progress bar
  142. Rect rect = GUILayoutUtility.GetRect(0f, 20f, GUILayout.ExpandWidth(true));
  143. EditorGUI.ProgressBar(rect, m_ProgressPercentage, "Scan Progress (" + m_ScanningCurrentFileIndex + "/" + m_ScanningTotalFiles + ")");
  144. // Display cancel button and name of file currently being scanned.
  145. if (m_IsAlreadyScanningProject)
  146. {
  147. Rect cancelRect = new Rect(rect.width - 20, rect.y + 2, 20, 16);
  148. if (GUI.Button(cancelRect, "X"))
  149. {
  150. m_CancelScanProcess = true;
  151. }
  152. GUILayout.Label(k_ProjectScanLabelPrefix + m_ScanningCurrentFileName, TMP_UIStyleManager.label);
  153. }
  154. else
  155. GUILayout.Label(string.Empty);
  156. GUILayout.Space(5);
  157. // Creation Feedback
  158. GUILayout.BeginVertical(TMP_UIStyleManager.textAreaBoxWindow, GUILayout.ExpandHeight(true));
  159. {
  160. m_ProjectScanResultScrollPosition = EditorGUILayout.BeginScrollView(m_ProjectScanResultScrollPosition, GUILayout.ExpandHeight(true));
  161. EditorGUILayout.LabelField(m_ProjectScanResults, TMP_UIStyleManager.label);
  162. EditorGUILayout.EndScrollView();
  163. }
  164. GUILayout.EndVertical();
  165. GUILayout.Space(5f);
  166. }
  167. GUILayout.EndVertical();
  168. // Scan project files and resources
  169. GUILayout.BeginVertical(EditorStyles.helpBox);
  170. {
  171. GUILayout.Label("Save Modified Project Files", EditorStyles.boldLabel);
  172. GUILayout.Label("Pressing the <i>Save Modified Project Files</i> button will update the files in the <i>Project Scan Results</i> listed above. <color=#FFFF80>Please make sure that you have created a backup of your project first</color> as these file modifications are permanent and cannot be undone.", TMP_UIStyleManager.label);
  173. GUILayout.Space(5f);
  174. GUI.enabled = m_IsAlreadyScanningProject == false && m_ModifiedAssetList.Count > 0 ? true : false;
  175. if (GUILayout.Button("Save Modified Project Files"))
  176. {
  177. UpdateProjectFiles();
  178. }
  179. GUILayout.Space(10f);
  180. }
  181. GUILayout.EndVertical();
  182. }
  183. GUILayout.EndVertical();
  184. GUILayout.Space(5f);
  185. }
  186. void OnInspectorUpdate()
  187. {
  188. Repaint();
  189. }
  190. /// <summary>
  191. /// Limits the minimum size of the editor window.
  192. /// </summary>
  193. void SetEditorWindowSize()
  194. {
  195. EditorWindow editorWindow = this;
  196. Vector2 currentWindowSize = editorWindow.minSize;
  197. editorWindow.minSize = new Vector2(Mathf.Max(640, currentWindowSize.x), Mathf.Max(420, currentWindowSize.y));
  198. }
  199. /// <summary>
  200. ///
  201. /// </summary>
  202. /// <param name="filePath"></param>
  203. /// <returns></returns>
  204. private static bool ShouldIgnoreFile(string filePath)
  205. {
  206. string fileExtension = Path.GetExtension(filePath);
  207. Type fileType = AssetDatabase.GetMainAssetTypeAtPath(filePath);
  208. if (m_IgnoreAssetTypes.Contains(fileType))
  209. return true;
  210. // Exclude FBX
  211. if (fileType == typeof(GameObject) && (fileExtension.ToLower() == ".fbx" || fileExtension.ToLower() == ".blend"))
  212. return true;
  213. return false;
  214. }
  215. private IEnumerator ScanProjectFiles()
  216. {
  217. m_IsAlreadyScanningProject = true;
  218. string packageFullPath = EditorUtilities.TMP_EditorUtility.packageFullPath;
  219. // List containing assets that have been modified.
  220. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  221. m_ModifiedAssetList.Clear();
  222. m_ProgressPercentage = 0;
  223. // Read Conversion Data from Json file.
  224. if (m_ConversionData == null)
  225. m_ConversionData = JsonUtility.FromJson<AssetConversionData>(File.ReadAllText(packageFullPath + "/PackageConversionData.json"));
  226. // Get list of GUIDs for assets that might contain references to previous GUIDs that require updating.
  227. string searchFolder = string.IsNullOrEmpty(m_ProjectFolderToScan) ? "Assets" : ("Assets/" + m_ProjectFolderToScan);
  228. string[] guids = AssetDatabase.FindAssets("t:Object", new string[] { searchFolder }).Distinct().ToArray();
  229. k_ProjectScanLabelPrefix = "<b>Phase 1 - Filtering:</b> ";
  230. m_ScanningTotalFiles = guids.Length;
  231. m_ScanningCurrentFileIndex = 0;
  232. List<AssetFileRecord> projectFilesToScan = new List<AssetFileRecord>();
  233. foreach (var guid in guids)
  234. {
  235. if (m_CancelScanProcess)
  236. break;
  237. string assetFilePath = AssetDatabase.GUIDToAssetPath(guid);
  238. m_ScanningCurrentFileIndex += 1;
  239. m_ScanningCurrentFileName = assetFilePath;
  240. m_ProgressPercentage = (float)m_ScanningCurrentFileIndex / m_ScanningTotalFiles;
  241. // Filter out file types we have no interest in searching
  242. if (ShouldIgnoreFile(assetFilePath))
  243. continue;
  244. string assetMetaFilePath = AssetDatabase.GetTextMetaFilePathFromAssetPath(assetFilePath);
  245. projectFilesToScan.Add(new AssetFileRecord(assetFilePath, assetMetaFilePath));
  246. yield return null;
  247. }
  248. m_RemainingFilesToScan = m_ScanningTotalFiles = projectFilesToScan.Count;
  249. k_ProjectScanLabelPrefix = "<b>Phase 2 - Scanning:</b> ";
  250. for (int i = 0; i < m_ScanningTotalFiles; i++)
  251. {
  252. if (m_CancelScanProcess)
  253. break;
  254. AssetFileRecord fileRecord = projectFilesToScan[i];
  255. ThreadPool.QueueUserWorkItem(Task =>
  256. {
  257. ScanProjectFileAsync(fileRecord);
  258. m_ScanningCurrentFileName = fileRecord.assetFilePath;
  259. int completedScans = m_ScanningTotalFiles - Interlocked.Decrement(ref m_RemainingFilesToScan);
  260. m_ScanningCurrentFileIndex = completedScans;
  261. m_ProgressPercentage = (float)completedScans / m_ScanningTotalFiles;
  262. });
  263. if (i % 64 == 0)
  264. yield return new WaitForSeconds(2.0f);
  265. }
  266. while (m_RemainingFilesToScan > 0 && !m_CancelScanProcess)
  267. yield return null;
  268. m_IsAlreadyScanningProject = false;
  269. m_ScanningCurrentFileName = string.Empty;
  270. }
  271. static void ScanProjectFileAsync(AssetFileRecord fileRecord)
  272. {
  273. if (m_CancelScanProcess)
  274. return;
  275. // Read the asset data file
  276. string assetDataFile = string.Empty;
  277. bool hasFileChanged = false;
  278. try
  279. {
  280. assetDataFile = File.ReadAllText(m_ProjectPath + "/" + fileRecord.assetFilePath);
  281. }
  282. catch
  283. {
  284. // Continue to the next asset if we can't read the current one.
  285. return;
  286. }
  287. // Read the asset meta data file
  288. string assetMetaFile = File.ReadAllText(m_ProjectPath + "/" + fileRecord.assetMetaFilePath);
  289. bool hasMetaFileChanges = false;
  290. foreach (AssetConversionRecord record in m_ConversionData.assetRecords)
  291. {
  292. if (assetDataFile.Contains(record.target))
  293. {
  294. hasFileChanged = true;
  295. assetDataFile = assetDataFile.Replace(record.target, record.replacement);
  296. }
  297. //// Check meta file
  298. if (assetMetaFile.Contains(record.target))
  299. {
  300. hasMetaFileChanges = true;
  301. assetMetaFile = assetMetaFile.Replace(record.target, record.replacement);
  302. }
  303. }
  304. if (hasFileChanged)
  305. {
  306. AssetModificationRecord modifiedAsset;
  307. modifiedAsset.assetFilePath = fileRecord.assetFilePath;
  308. modifiedAsset.assetDataFile = assetDataFile;
  309. m_ModifiedAssetList.Add(modifiedAsset);
  310. m_ProjectScanResults += fileRecord.assetFilePath + "\n";
  311. }
  312. if (hasMetaFileChanges)
  313. {
  314. AssetModificationRecord modifiedAsset;
  315. modifiedAsset.assetFilePath = fileRecord.assetMetaFilePath;
  316. modifiedAsset.assetDataFile = assetMetaFile;
  317. m_ModifiedAssetList.Add(modifiedAsset);
  318. m_ProjectScanResults += fileRecord.assetMetaFilePath + "\n";
  319. }
  320. }
  321. /// <summary>
  322. ///
  323. /// </summary>
  324. private static void ResetScanProcess()
  325. {
  326. m_IsAlreadyScanningProject = false;
  327. m_ScanningCurrentFileName = string.Empty;
  328. m_ProgressPercentage = 0;
  329. m_ScanningCurrentFileIndex = 0;
  330. m_ScanningTotalFiles = 0;
  331. }
  332. /// <summary>
  333. ///
  334. /// </summary>
  335. private static void UpdateProjectFiles()
  336. {
  337. // Make sure Asset Serialization mode is set to ForceText with Visible Meta Files.
  338. CheckProjectSerializationAndSourceControlModes();
  339. string projectPath = Path.GetFullPath("Assets/..");
  340. // Display dialogue to show user a list of project files that will be modified upon their consent.
  341. if (EditorUtility.DisplayDialog("Save Modified Asset(s)?", "Are you sure you want to save all modified assets?", "YES", "NO"))
  342. {
  343. for (int i = 0; i < m_ModifiedAssetList.Count; i++)
  344. {
  345. // Make sure all file streams that might have been opened by Unity are closed.
  346. //AssetDatabase.ReleaseCachedFileHandles();
  347. //Debug.Log("Writing asset file [" + m_ModifiedAssetList[i].assetFilePath + "].");
  348. File.WriteAllText(projectPath + "/" + m_ModifiedAssetList[i].assetFilePath, m_ModifiedAssetList[i].assetDataFile);
  349. }
  350. }
  351. AssetDatabase.Refresh();
  352. m_ProgressPercentage = 0;
  353. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  354. }
  355. /// <summary>
  356. /// Check project Asset Serialization and Source Control modes
  357. /// </summary>
  358. private static bool CheckProjectSerializationAndSourceControlModes()
  359. {
  360. // Check Project Asset Serialization and Visible Meta Files mode.
  361. if (EditorSettings.serializationMode != SerializationMode.ForceText || EditorSettings.externalVersionControl != "Visible Meta Files")
  362. {
  363. return false;
  364. }
  365. return true;
  366. }
  367. }
  368. public class TMP_PackageUtilities : Editor
  369. {
  370. enum SaveAssetDialogueOptions { Unset = 0, Save = 1, SaveAll = 2, DoNotSave = 3 };
  371. private static SerializationMode m_ProjectAssetSerializationMode;
  372. private static string m_ProjectExternalVersionControl;
  373. struct AssetRemappingRecord
  374. {
  375. public string oldGuid;
  376. public string newGuid;
  377. public string assetPath;
  378. }
  379. struct AssetModificationRecord
  380. {
  381. public string assetFilePath;
  382. public string assetDataFile;
  383. }
  384. /// <summary>
  385. ///
  386. /// </summary>
  387. [MenuItem("Window/TextMeshPro/Import TMP Essential Resources", false, 2050)]
  388. public static void ImportProjectResourcesMenu()
  389. {
  390. ImportEssentialResources();
  391. }
  392. /// <summary>
  393. ///
  394. /// </summary>
  395. [MenuItem("Window/TextMeshPro/Import TMP Examples and Extras", false, 2051)]
  396. public static void ImportExamplesContentMenu()
  397. {
  398. ImportExamplesAndExtras();
  399. }
  400. private static void GetVersionInfo()
  401. {
  402. string version = TMP_Settings.version;
  403. Debug.Log("The version of this TextMesh Pro UPM package is (" + version + ").");
  404. }
  405. /// <summary>
  406. ///
  407. /// </summary>
  408. private static void ImportExamplesAndExtras()
  409. {
  410. string packageFullPath = TMP_EditorUtility.packageFullPath;
  411. AssetDatabase.ImportPackage(packageFullPath + "/Package Resources/TMP Examples & Extras.unitypackage", true);
  412. }
  413. private static string k_SettingsFilePath;
  414. private static byte[] k_SettingsBackup;
  415. /// <summary>
  416. ///
  417. /// </summary>
  418. private static void ImportEssentialResources()
  419. {
  420. // Check if the TMP Settings asset is already present in the project.
  421. string[] settings = AssetDatabase.FindAssets("t:TMP_Settings");
  422. if (settings.Length > 0)
  423. {
  424. // Save assets just in case the TMP Setting were modified before import.
  425. AssetDatabase.SaveAssets();
  426. // Copy existing TMP Settings asset to a byte[]
  427. k_SettingsFilePath = AssetDatabase.GUIDToAssetPath(settings[0]);
  428. k_SettingsBackup = File.ReadAllBytes(k_SettingsFilePath);
  429. RegisterResourceImportCallback();
  430. }
  431. string packageFullPath = TMP_EditorUtility.packageFullPath;
  432. AssetDatabase.ImportPackage(packageFullPath + "/Package Resources/TMP Essential Resources.unitypackage", true);
  433. }
  434. private static void RegisterResourceImportCallback()
  435. {
  436. AssetDatabase.importPackageCompleted += ImportCallback;
  437. }
  438. private static void ImportCallback(string packageName)
  439. {
  440. // Restore backup of TMP Settings from byte[]
  441. File.WriteAllBytes(k_SettingsFilePath, k_SettingsBackup);
  442. AssetDatabase.Refresh();
  443. AssetDatabase.importPackageCompleted -= ImportCallback;
  444. }
  445. }
  446. }