Aucune description
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

TMP_PackageUtilities.cs 46KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  1. using UnityEngine;
  2. using UnityEngine.SceneManagement;
  3. using UnityEditor;
  4. using System;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.Globalization;
  10. using System.Threading;
  11. using TMPro.EditorUtilities;
  12. namespace TMPro
  13. {
  14. // Suppressing warnings related to the use of private structures which are confusing the compiler as these data structures are used by .json files.
  15. #pragma warning disable 0649
  16. /// <summary>
  17. /// 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.
  18. /// </summary>
  19. [System.Serializable]
  20. struct AssetConversionRecord
  21. {
  22. public string referencedResource;
  23. public string target;
  24. public string replacement;
  25. }
  26. /// <summary>
  27. /// 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.
  28. /// This data structure is populated with the data contained in the PackageConversionData.json file included in the package.
  29. /// </summary>
  30. [System.Serializable]
  31. class AssetConversionData
  32. {
  33. public List<AssetConversionRecord> assetRecords;
  34. }
  35. internal class TMP_ProjectTextSpacingConversionTool : EditorWindow
  36. {
  37. // Create Text Spacing Conversion Tool window
  38. [MenuItem("Window/TextMeshPro/Project Text Spacing Conversion Tool", false, 2110)]
  39. static void ShowConverterWindow()
  40. {
  41. var window = GetWindow<TMP_ProjectTextSpacingConversionTool>();
  42. window.titleContent = new GUIContent("Conversion Tool");
  43. window.Focus();
  44. }
  45. /// <summary>
  46. ///
  47. /// </summary>
  48. struct AssetModificationRecord
  49. {
  50. public string assetFilePath;
  51. public string assetDataFile;
  52. }
  53. struct AssetFileRecord
  54. {
  55. public string assetFilePath;
  56. public AssetFileRecord(string filePath, string metaFilePath)
  57. {
  58. this.assetFilePath = filePath;
  59. }
  60. }
  61. private static string m_ProjectPath;
  62. [SerializeField] private string m_ProjectFolderToScan;
  63. private static bool m_IsAlreadyScanningProject;
  64. private static bool m_CancelScanProcess;
  65. private static string k_ProjectScanReportDefaultText = "<color=#FFFF80><b>" +
  66. " Character Word Line Paragraph\n" +
  67. "Project Scan Results Spacing Spacing Spacing Spacing</b></color>\n" +
  68. "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n";
  69. [SerializeField] private GUIStyle m_OutputWindowStyle;
  70. [SerializeField] private Font m_OutputWindowMonospacedFont;
  71. private static string k_ProjectScanLabelPrefix = "Scanning: ";
  72. private static string m_ProjectScanResults = string.Empty;
  73. private static Vector2 m_ProjectScanResultScrollPosition;
  74. private static float m_ProgressPercentage = 0;
  75. private static int m_ScanningTotalFiles;
  76. private static int m_ScanningCurrentFileIndex;
  77. private static string m_ScanningCurrentFileName;
  78. private static string k_TextMeshProScriptID = "m_Script: {fileID: 11500000, guid: 9541d86e2fd84c1d9990edf0852d74ab, type: 3}";
  79. private static string k_TextMeshProUGUIScriptID = "m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}";
  80. //private static string k_FontAssetScriptID = "m_Script: {fileID: 11500000, guid: 71c1514a6bd24e1e882cebbe1904ce04, type: 3}";
  81. private static string k_FontAssetProperty = "m_fontAsset: ";
  82. private static string k_FontSizeProperty = "m_fontSize: ";
  83. private static string k_LineSpacingProperty = "m_lineSpacing: ";
  84. private static string k_CharacterSpacingProperty = "m_characterSpacing: ";
  85. private static string k_WordSpacingProperty = "m_wordSpacing: ";
  86. private static string k_ParagraphSpacingProperty = "m_paragraphSpacing: ";
  87. private static AssetConversionData m_ConversionData;
  88. private static readonly List<AssetModificationRecord> m_ModifiedAssetList = new List<AssetModificationRecord>();
  89. void OnEnable()
  90. {
  91. // Set Editor Window Size
  92. SetEditorWindowSize();
  93. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  94. // Define new style with monospaced font (if we have not already done so).
  95. if (m_OutputWindowStyle == null || m_OutputWindowMonospacedFont == null)
  96. {
  97. if (m_OutputWindowMonospacedFont == null)
  98. m_OutputWindowMonospacedFont = Font.CreateDynamicFontFromOSFont("Courier New", 13);
  99. if (m_OutputWindowStyle == null)
  100. {
  101. m_OutputWindowStyle = new GUIStyle() {font = m_OutputWindowMonospacedFont, richText = true};
  102. m_OutputWindowStyle.normal.textColor = new Color(0.95f, 0.95f, 0.95f, 1f);
  103. }
  104. else
  105. {
  106. m_OutputWindowStyle.font = m_OutputWindowMonospacedFont;
  107. }
  108. }
  109. }
  110. void OnGUI()
  111. {
  112. // Define new style with monospaced font (if we have not already done so).
  113. if (m_OutputWindowStyle == null || m_OutputWindowMonospacedFont == null)
  114. {
  115. if (m_OutputWindowMonospacedFont == null)
  116. m_OutputWindowMonospacedFont = Font.CreateDynamicFontFromOSFont("Courier New", 13);
  117. if (m_OutputWindowStyle == null)
  118. {
  119. m_OutputWindowStyle = new GUIStyle() {font = m_OutputWindowMonospacedFont, richText = true};
  120. m_OutputWindowStyle.normal.textColor = new Color(0.95f, 0.95f, 0.95f, 1f);
  121. }
  122. else
  123. {
  124. m_OutputWindowStyle.font = m_OutputWindowMonospacedFont;
  125. }
  126. }
  127. GUILayout.BeginVertical();
  128. {
  129. // Scan project files and resources
  130. GUILayout.BeginVertical(EditorStyles.helpBox);
  131. {
  132. GUILayout.Label("Scan Project Files", EditorStyles.boldLabel);
  133. GUILayout.Label("Press the <i>Scan Project Files</i> button to begin scanning your project for Scenes and Prefabs containing text objects whose line spacing values might need to be converted to the new (em) line spacing values.", TMP_UIStyleManager.label);
  134. GUILayout.Space(10f);
  135. GUILayout.Label("Project folder to be scanned. Example \"Assets/TextMesh Pro\"");
  136. m_ProjectFolderToScan = EditorGUILayout.TextField("Folder Path: Assets/", m_ProjectFolderToScan);
  137. GUILayout.Space(5f);
  138. GUI.enabled = m_IsAlreadyScanningProject == false ? true : false;
  139. if (GUILayout.Button("Scan Project Files"))
  140. {
  141. m_CancelScanProcess = false;
  142. // Make sure Asset Serialization mode is set to ForceText and Version Control mode to Visible Meta Files.
  143. if (CheckProjectSerializationAndSourceControlModes() == true)
  144. {
  145. m_ProjectPath = Path.GetFullPath("Assets/..");
  146. TMP_EditorCoroutine.StartCoroutine(ScanProjectFiles());
  147. }
  148. else
  149. {
  150. 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);
  151. }
  152. }
  153. GUI.enabled = true;
  154. // Display progress bar
  155. Rect rect = GUILayoutUtility.GetRect(0f, 20f, GUILayout.ExpandWidth(true));
  156. EditorGUI.ProgressBar(rect, m_ProgressPercentage, "Scan Progress (" + m_ScanningCurrentFileIndex + "/" + m_ScanningTotalFiles + ")");
  157. // Display cancel button and name of file currently being scanned.
  158. if (m_IsAlreadyScanningProject)
  159. {
  160. Rect cancelRect = new Rect(rect.width - 20, rect.y + 2, 20, 16);
  161. if (GUI.Button(cancelRect, "X"))
  162. {
  163. m_CancelScanProcess = true;
  164. }
  165. GUILayout.Label(k_ProjectScanLabelPrefix + m_ScanningCurrentFileName, TMP_UIStyleManager.label);
  166. }
  167. else
  168. GUILayout.Label(string.Empty);
  169. GUILayout.Space(5);
  170. // Creation Feedback
  171. GUILayout.BeginVertical(TMP_UIStyleManager.textAreaBoxWindow, GUILayout.ExpandHeight(true));
  172. {
  173. m_ProjectScanResultScrollPosition = EditorGUILayout.BeginScrollView(m_ProjectScanResultScrollPosition, GUILayout.ExpandHeight(true));
  174. GUILayout.Label(m_ProjectScanResults, m_OutputWindowStyle);
  175. EditorGUILayout.EndScrollView();
  176. }
  177. GUILayout.EndVertical();
  178. GUILayout.Space(5f);
  179. }
  180. GUILayout.EndVertical();
  181. // Scan project files and resources
  182. GUILayout.BeginVertical(EditorStyles.helpBox);
  183. {
  184. GUILayout.Label("Save Modified Project Files", EditorStyles.boldLabel);
  185. 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);
  186. GUILayout.Space(5f);
  187. GUI.enabled = m_IsAlreadyScanningProject == false && m_ModifiedAssetList.Count > 0 ? true : false;
  188. if (GUILayout.Button("Save Modified Project Files"))
  189. {
  190. UpdateProjectFiles();
  191. }
  192. GUILayout.Space(10f);
  193. }
  194. GUILayout.EndVertical();
  195. }
  196. GUILayout.EndVertical();
  197. GUILayout.Space(5f);
  198. }
  199. void OnInspectorUpdate()
  200. {
  201. Repaint();
  202. }
  203. /// <summary>
  204. /// Limits the minimum size of the editor window.
  205. /// </summary>
  206. void SetEditorWindowSize()
  207. {
  208. EditorWindow editorWindow = this;
  209. Vector2 currentWindowSize = editorWindow.minSize;
  210. editorWindow.minSize = new Vector2(Mathf.Max(1024, currentWindowSize.x), Mathf.Max(420, currentWindowSize.y));
  211. }
  212. private IEnumerator ScanProjectFiles()
  213. {
  214. m_IsAlreadyScanningProject = true;
  215. string packageFullPath = EditorUtilities.TMP_EditorUtility.packageFullPath;
  216. // List containing assets that have been modified.
  217. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  218. m_ModifiedAssetList.Clear();
  219. m_ProgressPercentage = 0;
  220. // Get list of GUIDs for assets that might contain references to previous GUIDs that require updating.
  221. string searchFolder = string.IsNullOrEmpty(m_ProjectFolderToScan) ? "Assets" : ("Assets/" + m_ProjectFolderToScan);
  222. string[] guids = AssetDatabase.FindAssets("t:Object", new string[] { searchFolder }).Distinct().ToArray();
  223. k_ProjectScanLabelPrefix = "<b>Phase 1 - Filtering:</b> ";
  224. m_ScanningTotalFiles = guids.Length;
  225. m_ScanningCurrentFileIndex = 0;
  226. List<AssetFileRecord> projectFilesToScan = new List<AssetFileRecord>();
  227. foreach (var guid in guids)
  228. {
  229. if (m_CancelScanProcess)
  230. break;
  231. string assetFilePath = AssetDatabase.GUIDToAssetPath(guid);
  232. m_ScanningCurrentFileIndex += 1;
  233. m_ScanningCurrentFileName = assetFilePath;
  234. m_ProgressPercentage = (float)m_ScanningCurrentFileIndex / m_ScanningTotalFiles;
  235. string fileExtension = Path.GetExtension(assetFilePath);
  236. Type fileType = AssetDatabase.GetMainAssetTypeAtPath(assetFilePath);
  237. // Ignore all files other than Scenes and Prefabs.
  238. if ((fileType == typeof(SceneAsset) || (fileType == typeof(GameObject) && fileExtension.ToLower() == ".prefab")) == false)
  239. continue;
  240. string assetMetaFilePath = AssetDatabase.GetTextMetaFilePathFromAssetPath(assetFilePath);
  241. projectFilesToScan.Add(new AssetFileRecord(assetFilePath, assetMetaFilePath));
  242. yield return null;
  243. }
  244. m_ScanningTotalFiles = projectFilesToScan.Count;
  245. k_ProjectScanLabelPrefix = "<b>Phase 2 - Scanning:</b> ";
  246. m_ScanningCurrentFileIndex = 0;
  247. for (int i = 0; i < m_ScanningTotalFiles; i++)
  248. {
  249. if (m_CancelScanProcess)
  250. break;
  251. AssetFileRecord fileRecord = projectFilesToScan[i];
  252. ScanProjectFile(fileRecord);
  253. m_ScanningCurrentFileName = fileRecord.assetFilePath;
  254. m_ScanningCurrentFileIndex += 1;
  255. m_ProgressPercentage = (float)m_ScanningCurrentFileIndex / m_ScanningTotalFiles;
  256. yield return null;
  257. }
  258. m_IsAlreadyScanningProject = false;
  259. m_ScanningCurrentFileName = string.Empty;
  260. }
  261. static void ScanProjectFile(AssetFileRecord fileRecord)
  262. {
  263. if (m_CancelScanProcess)
  264. return;
  265. // Read the asset data file
  266. string assetDataFile;
  267. bool hasDataFileChanged = false;
  268. try
  269. {
  270. assetDataFile = File.ReadAllText(m_ProjectPath + "/" + fileRecord.assetFilePath);
  271. }
  272. catch
  273. {
  274. // Continue to the next asset if we can't read the current one.
  275. return;
  276. }
  277. // Check if asset file references any text components.
  278. if (assetDataFile.Contains(k_TextMeshProScriptID) || assetDataFile.Contains(k_TextMeshProUGUIScriptID))
  279. {
  280. float characterSpacingValue = 0;
  281. float newCharacterSpacingValue = 0;
  282. float wordSpacingValue = 0;
  283. float newWordSpacingValue = 0;
  284. float lineSpacingValue = 0;
  285. float newLineSpacingValue = 0;
  286. float paragraphSpacingValue = 0;
  287. float newParagraphSpacingValue = 0;
  288. float fontSize = 0;
  289. float samplingPointSize = 0;
  290. float faceScale = 1;
  291. List<string> lines = assetDataFile.Split('\n').ToList();
  292. int serializedVersionInsertionIndex = 0;
  293. int readingFlag = 0;
  294. // Read through each lines of the asset file
  295. for (int i = 0; i < lines.Count; i++)
  296. {
  297. string line = lines[i];
  298. // Track potential line index to insert serializedVersion property
  299. if (line.Contains("MonoBehaviour:"))
  300. {
  301. serializedVersionInsertionIndex = i + 1;
  302. continue;
  303. }
  304. // Read until we find the line that contains a reference to a text component
  305. if (readingFlag == 0 && (line.Contains(k_TextMeshProScriptID) || line.Contains(k_TextMeshProUGUIScriptID)))
  306. {
  307. // Check if spacing values for this component have already been converted
  308. if (lines[serializedVersionInsertionIndex].Contains(" m_SerializedVersion: 1"))
  309. {
  310. readingFlag = 0;
  311. continue;
  312. }
  313. lines.Insert(serializedVersionInsertionIndex, " m_SerializedVersion: 1");
  314. readingFlag = 1;
  315. continue;
  316. }
  317. // Keep reading until we find the font asset property field.
  318. if (readingFlag == 1)
  319. {
  320. // Check for font asset property
  321. if (line.Contains(k_FontAssetProperty))
  322. {
  323. int guidIndex = line.IndexOf("guid: ", StringComparison.InvariantCulture);
  324. if (guidIndex != -1)
  325. {
  326. string guid = line.Substring(guidIndex + 6, 32);
  327. TMP_FontAsset fontAsset = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(AssetDatabase.GUIDToAssetPath(guid));
  328. if (fontAsset != null)
  329. {
  330. samplingPointSize = fontAsset.faceInfo.pointSize;
  331. faceScale = fontAsset.faceInfo.scale;
  332. }
  333. }
  334. readingFlag = 2;
  335. continue;
  336. }
  337. }
  338. // Read font size property
  339. if (readingFlag == 2)
  340. {
  341. if (line.Contains(k_FontSizeProperty))
  342. {
  343. fontSize = float.Parse(line.Split(':')[1], NumberStyles.Float, CultureInfo.InvariantCulture);
  344. readingFlag = 3;
  345. continue;
  346. }
  347. }
  348. // Check for the spacing properties that need to be converted
  349. if (readingFlag == 3)
  350. {
  351. // Read character spacing
  352. if (line.Contains(k_CharacterSpacingProperty))
  353. {
  354. characterSpacingValue = float.Parse(line.Split(':')[1], NumberStyles.Float, CultureInfo.InvariantCulture);
  355. if (characterSpacingValue != 0)
  356. {
  357. // Convert character spacing value.
  358. newCharacterSpacingValue = characterSpacingValue * faceScale / (samplingPointSize * 0.01f);
  359. lines[i] = lines[i].Replace(k_CharacterSpacingProperty + characterSpacingValue, k_CharacterSpacingProperty + newCharacterSpacingValue);
  360. hasDataFileChanged = true;
  361. }
  362. continue;
  363. }
  364. // Read word spacing
  365. if (line.Contains(k_WordSpacingProperty))
  366. {
  367. // Get the character spacing value
  368. wordSpacingValue = float.Parse(line.Split(':')[1], NumberStyles.Float, CultureInfo.InvariantCulture);
  369. if (wordSpacingValue != 0)
  370. {
  371. // Convert character spacing value.
  372. newWordSpacingValue = wordSpacingValue * faceScale / (samplingPointSize * 0.01f);
  373. lines[i] = lines[i].Replace(k_WordSpacingProperty + wordSpacingValue, k_WordSpacingProperty + newWordSpacingValue);
  374. hasDataFileChanged = true;
  375. }
  376. continue;
  377. }
  378. // Read line spacing
  379. if (line.Contains(k_LineSpacingProperty))
  380. {
  381. // Get the value of line spacing value
  382. lineSpacingValue = float.Parse(line.Split(':')[1], NumberStyles.Float, CultureInfo.InvariantCulture);
  383. if (lineSpacingValue != 0)
  384. {
  385. // Convert line spacing value.
  386. newLineSpacingValue = lineSpacingValue / (fontSize * 0.01f) * fontSize / samplingPointSize * faceScale;
  387. lines[i] = lines[i].Replace(k_LineSpacingProperty + lineSpacingValue, k_LineSpacingProperty + newLineSpacingValue);
  388. hasDataFileChanged = true;
  389. }
  390. continue;
  391. }
  392. // Read paragraph spacing
  393. if (line.Contains(k_ParagraphSpacingProperty))
  394. {
  395. // Get the value of line spacing value
  396. paragraphSpacingValue = float.Parse(line.Split(':')[1], NumberStyles.Float, CultureInfo.InvariantCulture);
  397. if (paragraphSpacingValue != 0)
  398. {
  399. // Convert line spacing value.
  400. newParagraphSpacingValue = paragraphSpacingValue / (fontSize * 0.01f) * fontSize / samplingPointSize * faceScale;
  401. lines[i] = lines[i].Replace(k_ParagraphSpacingProperty + paragraphSpacingValue, k_ParagraphSpacingProperty + newParagraphSpacingValue);
  402. hasDataFileChanged = true;
  403. }
  404. readingFlag = 4;
  405. continue;
  406. }
  407. }
  408. // Done reading text component serialized data.
  409. if (readingFlag == 4 && line.Contains("---"))
  410. {
  411. readingFlag = 0;
  412. string characterSpacingFormat = $"{(characterSpacingValue == 0 ? " " : $"{characterSpacingValue,10:F}{newCharacterSpacingValue,10:F}")}";
  413. string wordSpacingFormat = $"{(wordSpacingValue == 0 ? " " : $"{wordSpacingValue,10:F}{newWordSpacingValue,10:F}")}";
  414. string lineSpacingFormat = $"{(lineSpacingValue == 0 ? " " : $"{lineSpacingValue,10:F}{newLineSpacingValue,10:F}")}";
  415. string paragraphSpacingFormat = $"{(paragraphSpacingValue == 0 ? " " : $"{paragraphSpacingValue,10:F}{newParagraphSpacingValue,10:F}")}";
  416. if (characterSpacingValue != 0 || lineSpacingValue != 0)
  417. m_ProjectScanResults += $"{fileRecord.assetFilePath,-100}" + characterSpacingFormat + wordSpacingFormat + lineSpacingFormat + paragraphSpacingFormat + "\n";
  418. // Update asset data file
  419. assetDataFile = string.Join("\n", lines);
  420. newCharacterSpacingValue = 0;
  421. newWordSpacingValue = 0;
  422. newLineSpacingValue = 0;
  423. newParagraphSpacingValue = 0;
  424. }
  425. }
  426. }
  427. // Check if asset file is a font asset
  428. // if (assetDataFile.Contains(k_FontAssetScriptID))
  429. // {
  430. // float samplingPointSize;
  431. // float normalSpacing;
  432. // float newNormalSpacing;
  433. // float boldSpacing;
  434. // float newBoldSpacing;
  435. // }
  436. if (hasDataFileChanged)
  437. {
  438. AssetModificationRecord modifiedAsset;
  439. modifiedAsset.assetFilePath = fileRecord.assetFilePath;
  440. modifiedAsset.assetDataFile = assetDataFile;
  441. m_ModifiedAssetList.Add(modifiedAsset);
  442. }
  443. }
  444. /// <summary>
  445. ///
  446. /// </summary>
  447. private static void ResetScanProcess()
  448. {
  449. m_IsAlreadyScanningProject = false;
  450. m_ScanningCurrentFileName = string.Empty;
  451. m_ProgressPercentage = 0;
  452. m_ScanningCurrentFileIndex = 0;
  453. m_ScanningTotalFiles = 0;
  454. }
  455. /// <summary>
  456. ///
  457. /// </summary>
  458. private static void UpdateProjectFiles()
  459. {
  460. // Make sure Asset Serialization mode is set to ForceText with Visible Meta Files.
  461. CheckProjectSerializationAndSourceControlModes();
  462. string projectPath = Path.GetFullPath("Assets/..");
  463. // Display dialogue to show user a list of project files that will be modified upon their consent.
  464. if (EditorUtility.DisplayDialog("Save Modified Asset(s)?", "Are you sure you want to save all modified assets?", "YES", "NO"))
  465. {
  466. for (int i = 0; i < m_ModifiedAssetList.Count; i++)
  467. {
  468. // Make sure all file streams that might have been opened by Unity are closed.
  469. //AssetDatabase.ReleaseCachedFileHandles();
  470. //Debug.Log("Writing asset file [" + m_ModifiedAssetList[i].assetFilePath + "].");
  471. File.WriteAllText(projectPath + "/" + m_ModifiedAssetList[i].assetFilePath, m_ModifiedAssetList[i].assetDataFile);
  472. }
  473. }
  474. AssetDatabase.Refresh();
  475. m_ProgressPercentage = 0;
  476. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  477. }
  478. /// <summary>
  479. /// Check project Asset Serialization and Source Control modes
  480. /// </summary>
  481. private static bool CheckProjectSerializationAndSourceControlModes()
  482. {
  483. // Check Project Asset Serialization and Visible Meta Files mode.
  484. if (EditorSettings.serializationMode != SerializationMode.ForceText || VersionControlSettings.mode != "Visible Meta Files")
  485. {
  486. return false;
  487. }
  488. return true;
  489. }
  490. }
  491. public class TMP_ProjectConversionUtility : EditorWindow
  492. {
  493. // Create Project Files GUID Remapping Tool window
  494. [MenuItem("Window/TextMeshPro/Project Files GUID Remapping Tool", false, 2100)]
  495. static void ShowConverterWindow()
  496. {
  497. var window = GetWindow<TMP_ProjectConversionUtility>();
  498. window.titleContent = new GUIContent("Conversion Tool");
  499. window.Focus();
  500. }
  501. private static HashSet<Type> m_IgnoreAssetTypes = new HashSet<Type>()
  502. {
  503. typeof(AnimatorOverrideController),
  504. typeof(AudioClip),
  505. typeof(AvatarMask),
  506. typeof(ComputeShader),
  507. typeof(Cubemap),
  508. typeof(DefaultAsset),
  509. typeof(Flare),
  510. typeof(Font),
  511. typeof(GUISkin),
  512. typeof(HumanTemplate),
  513. typeof(LightingDataAsset),
  514. typeof(Mesh),
  515. typeof(MonoScript),
  516. typeof(PhysicMaterial),
  517. typeof(PhysicsMaterial2D),
  518. typeof(RenderTexture),
  519. typeof(Shader),
  520. typeof(TerrainData),
  521. typeof(TextAsset),
  522. typeof(Texture2D),
  523. typeof(Texture2DArray),
  524. typeof(Texture3D),
  525. typeof(UnityEditorInternal.AssemblyDefinitionAsset),
  526. typeof(UnityEngine.AI.NavMeshData),
  527. typeof(UnityEngine.Tilemaps.Tile),
  528. typeof(UnityEngine.U2D.SpriteAtlas),
  529. typeof(UnityEngine.Video.VideoClip),
  530. };
  531. /// <summary>
  532. ///
  533. /// </summary>
  534. struct AssetModificationRecord
  535. {
  536. public string assetFilePath;
  537. public string assetDataFile;
  538. }
  539. struct AssetFileRecord
  540. {
  541. public string assetFilePath;
  542. public string assetMetaFilePath;
  543. public AssetFileRecord(string filePath, string metaFilePath)
  544. {
  545. this.assetFilePath = filePath;
  546. this.assetMetaFilePath = metaFilePath;
  547. }
  548. }
  549. private static string m_ProjectPath;
  550. private static string m_ProjectFolderToScan;
  551. private static bool m_IsAlreadyScanningProject;
  552. private static bool m_CancelScanProcess;
  553. private static string k_ProjectScanReportDefaultText = "<color=#FFFF80><b>Project Scan Results</b></color>\n";
  554. private static string k_ProjectScanLabelPrefix = "Scanning: ";
  555. private static string m_ProjectScanResults = string.Empty;
  556. private static Vector2 m_ProjectScanResultScrollPosition;
  557. private static float m_ProgressPercentage = 0;
  558. private static int m_ScanningTotalFiles;
  559. private static int m_RemainingFilesToScan;
  560. private static int m_ScanningCurrentFileIndex;
  561. private static string m_ScanningCurrentFileName;
  562. private static AssetConversionData m_ConversionData;
  563. private static List<AssetModificationRecord> m_ModifiedAssetList = new List<AssetModificationRecord>();
  564. void OnEnable()
  565. {
  566. // Set Editor Window Size
  567. SetEditorWindowSize();
  568. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  569. }
  570. void OnGUI()
  571. {
  572. GUILayout.BeginVertical();
  573. {
  574. // Scan project files and resources
  575. GUILayout.BeginVertical(EditorStyles.helpBox);
  576. {
  577. GUILayout.Label("Scan Project Files", EditorStyles.boldLabel);
  578. 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);
  579. GUILayout.Space(10f);
  580. GUILayout.Label("Project folder to be scanned. Example \"Assets/TextMesh Pro\"");
  581. m_ProjectFolderToScan = EditorGUILayout.TextField("Folder Path: Assets/", m_ProjectFolderToScan);
  582. GUILayout.Space(5f);
  583. GUI.enabled = m_IsAlreadyScanningProject == false ? true : false;
  584. if (GUILayout.Button("Scan Project Files"))
  585. {
  586. m_CancelScanProcess = false;
  587. // Make sure Asset Serialization mode is set to ForceText and Version Control mode to Visible Meta Files.
  588. if (CheckProjectSerializationAndSourceControlModes() == true)
  589. {
  590. m_ProjectPath = Path.GetFullPath("Assets/..");
  591. TMP_EditorCoroutine.StartCoroutine(ScanProjectFiles());
  592. }
  593. else
  594. {
  595. 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);
  596. }
  597. }
  598. GUI.enabled = true;
  599. // Display progress bar
  600. Rect rect = GUILayoutUtility.GetRect(0f, 20f, GUILayout.ExpandWidth(true));
  601. EditorGUI.ProgressBar(rect, m_ProgressPercentage, "Scan Progress (" + m_ScanningCurrentFileIndex + "/" + m_ScanningTotalFiles + ")");
  602. // Display cancel button and name of file currently being scanned.
  603. if (m_IsAlreadyScanningProject)
  604. {
  605. Rect cancelRect = new Rect(rect.width - 20, rect.y + 2, 20, 16);
  606. if (GUI.Button(cancelRect, "X"))
  607. {
  608. m_CancelScanProcess = true;
  609. }
  610. GUILayout.Label(k_ProjectScanLabelPrefix + m_ScanningCurrentFileName, TMP_UIStyleManager.label);
  611. }
  612. else
  613. GUILayout.Label(string.Empty);
  614. GUILayout.Space(5);
  615. // Creation Feedback
  616. GUILayout.BeginVertical(TMP_UIStyleManager.textAreaBoxWindow, GUILayout.ExpandHeight(true));
  617. {
  618. m_ProjectScanResultScrollPosition = EditorGUILayout.BeginScrollView(m_ProjectScanResultScrollPosition, GUILayout.ExpandHeight(true));
  619. GUILayout.Label(m_ProjectScanResults, TMP_UIStyleManager.label);
  620. EditorGUILayout.EndScrollView();
  621. }
  622. GUILayout.EndVertical();
  623. GUILayout.Space(5f);
  624. }
  625. GUILayout.EndVertical();
  626. // Scan project files and resources
  627. GUILayout.BeginVertical(EditorStyles.helpBox);
  628. {
  629. GUILayout.Label("Save Modified Project Files", EditorStyles.boldLabel);
  630. 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);
  631. GUILayout.Space(5f);
  632. GUI.enabled = m_IsAlreadyScanningProject == false && m_ModifiedAssetList.Count > 0 ? true : false;
  633. if (GUILayout.Button("Save Modified Project Files"))
  634. {
  635. UpdateProjectFiles();
  636. }
  637. GUILayout.Space(10f);
  638. }
  639. GUILayout.EndVertical();
  640. }
  641. GUILayout.EndVertical();
  642. GUILayout.Space(5f);
  643. }
  644. void OnInspectorUpdate()
  645. {
  646. Repaint();
  647. }
  648. /// <summary>
  649. /// Limits the minimum size of the editor window.
  650. /// </summary>
  651. void SetEditorWindowSize()
  652. {
  653. EditorWindow editorWindow = this;
  654. Vector2 currentWindowSize = editorWindow.minSize;
  655. editorWindow.minSize = new Vector2(Mathf.Max(640, currentWindowSize.x), Mathf.Max(420, currentWindowSize.y));
  656. }
  657. /// <summary>
  658. ///
  659. /// </summary>
  660. /// <param name="filePath"></param>
  661. /// <returns></returns>
  662. private static bool ShouldIgnoreFile(string filePath)
  663. {
  664. string fileExtension = Path.GetExtension(filePath);
  665. Type fileType = AssetDatabase.GetMainAssetTypeAtPath(filePath);
  666. if (m_IgnoreAssetTypes.Contains(fileType))
  667. return true;
  668. // Exclude FBX
  669. if (fileType == typeof(GameObject) && (fileExtension.ToLower() == ".fbx" || fileExtension.ToLower() == ".blend"))
  670. return true;
  671. return false;
  672. }
  673. private IEnumerator ScanProjectFiles()
  674. {
  675. m_IsAlreadyScanningProject = true;
  676. string packageFullPath = EditorUtilities.TMP_EditorUtility.packageFullPath;
  677. // List containing assets that have been modified.
  678. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  679. m_ModifiedAssetList.Clear();
  680. m_ProgressPercentage = 0;
  681. // Read Conversion Data from Json file.
  682. if (m_ConversionData == null)
  683. m_ConversionData = JsonUtility.FromJson<AssetConversionData>(File.ReadAllText(packageFullPath + "/PackageConversionData.json"));
  684. // Get list of GUIDs for assets that might contain references to previous GUIDs that require updating.
  685. string searchFolder = string.IsNullOrEmpty(m_ProjectFolderToScan) ? "Assets" : ("Assets/" + m_ProjectFolderToScan);
  686. string[] guids = AssetDatabase.FindAssets("t:Object", new string[] { searchFolder }).Distinct().ToArray();
  687. k_ProjectScanLabelPrefix = "<b>Phase 1 - Filtering:</b> ";
  688. m_ScanningTotalFiles = guids.Length;
  689. m_ScanningCurrentFileIndex = 0;
  690. List<AssetFileRecord> projectFilesToScan = new List<AssetFileRecord>();
  691. foreach (var guid in guids)
  692. {
  693. if (m_CancelScanProcess)
  694. break;
  695. string assetFilePath = AssetDatabase.GUIDToAssetPath(guid);
  696. m_ScanningCurrentFileIndex += 1;
  697. m_ScanningCurrentFileName = assetFilePath;
  698. m_ProgressPercentage = (float)m_ScanningCurrentFileIndex / m_ScanningTotalFiles;
  699. // Filter out file types we have no interest in searching
  700. if (ShouldIgnoreFile(assetFilePath))
  701. continue;
  702. string assetMetaFilePath = AssetDatabase.GetTextMetaFilePathFromAssetPath(assetFilePath);
  703. projectFilesToScan.Add(new AssetFileRecord(assetFilePath, assetMetaFilePath));
  704. yield return null;
  705. }
  706. m_RemainingFilesToScan = m_ScanningTotalFiles = projectFilesToScan.Count;
  707. k_ProjectScanLabelPrefix = "<b>Phase 2 - Scanning:</b> ";
  708. for (int i = 0; i < m_ScanningTotalFiles; i++)
  709. {
  710. if (m_CancelScanProcess)
  711. break;
  712. AssetFileRecord fileRecord = projectFilesToScan[i];
  713. ThreadPool.QueueUserWorkItem(Task =>
  714. {
  715. ScanProjectFileAsync(fileRecord);
  716. m_ScanningCurrentFileName = fileRecord.assetFilePath;
  717. int completedScans = m_ScanningTotalFiles - Interlocked.Decrement(ref m_RemainingFilesToScan);
  718. m_ScanningCurrentFileIndex = completedScans;
  719. m_ProgressPercentage = (float)completedScans / m_ScanningTotalFiles;
  720. });
  721. if (i % 64 == 0)
  722. yield return new WaitForSeconds(2.0f);
  723. }
  724. while (m_RemainingFilesToScan > 0 && !m_CancelScanProcess)
  725. yield return null;
  726. m_IsAlreadyScanningProject = false;
  727. m_ScanningCurrentFileName = string.Empty;
  728. }
  729. static void ScanProjectFileAsync(AssetFileRecord fileRecord)
  730. {
  731. if (m_CancelScanProcess)
  732. return;
  733. // Read the asset data file
  734. string assetDataFile = string.Empty;
  735. bool hasFileChanged = false;
  736. try
  737. {
  738. assetDataFile = File.ReadAllText(m_ProjectPath + "/" + fileRecord.assetFilePath);
  739. }
  740. catch
  741. {
  742. // Continue to the next asset if we can't read the current one.
  743. return;
  744. }
  745. // Read the asset meta data file
  746. string assetMetaFile = File.ReadAllText(m_ProjectPath + "/" + fileRecord.assetMetaFilePath);
  747. bool hasMetaFileChanges = false;
  748. foreach (AssetConversionRecord record in m_ConversionData.assetRecords)
  749. {
  750. if (assetDataFile.Contains(record.target))
  751. {
  752. hasFileChanged = true;
  753. assetDataFile = assetDataFile.Replace(record.target, record.replacement);
  754. }
  755. //// Check meta file
  756. if (assetMetaFile.Contains(record.target))
  757. {
  758. hasMetaFileChanges = true;
  759. assetMetaFile = assetMetaFile.Replace(record.target, record.replacement);
  760. }
  761. }
  762. if (hasFileChanged)
  763. {
  764. AssetModificationRecord modifiedAsset;
  765. modifiedAsset.assetFilePath = fileRecord.assetFilePath;
  766. modifiedAsset.assetDataFile = assetDataFile;
  767. m_ModifiedAssetList.Add(modifiedAsset);
  768. m_ProjectScanResults += fileRecord.assetFilePath + "\n";
  769. }
  770. if (hasMetaFileChanges)
  771. {
  772. AssetModificationRecord modifiedAsset;
  773. modifiedAsset.assetFilePath = fileRecord.assetMetaFilePath;
  774. modifiedAsset.assetDataFile = assetMetaFile;
  775. m_ModifiedAssetList.Add(modifiedAsset);
  776. m_ProjectScanResults += fileRecord.assetMetaFilePath + "\n";
  777. }
  778. }
  779. /// <summary>
  780. ///
  781. /// </summary>
  782. private static void ResetScanProcess()
  783. {
  784. m_IsAlreadyScanningProject = false;
  785. m_ScanningCurrentFileName = string.Empty;
  786. m_ProgressPercentage = 0;
  787. m_ScanningCurrentFileIndex = 0;
  788. m_ScanningTotalFiles = 0;
  789. }
  790. /// <summary>
  791. ///
  792. /// </summary>
  793. private static void UpdateProjectFiles()
  794. {
  795. // Make sure Asset Serialization mode is set to ForceText with Visible Meta Files.
  796. CheckProjectSerializationAndSourceControlModes();
  797. string projectPath = Path.GetFullPath("Assets/..");
  798. // Display dialogue to show user a list of project files that will be modified upon their consent.
  799. if (EditorUtility.DisplayDialog("Save Modified Asset(s)?", "Are you sure you want to save all modified assets?", "YES", "NO"))
  800. {
  801. for (int i = 0; i < m_ModifiedAssetList.Count; i++)
  802. {
  803. // Make sure all file streams that might have been opened by Unity are closed.
  804. //AssetDatabase.ReleaseCachedFileHandles();
  805. //Debug.Log("Writing asset file [" + m_ModifiedAssetList[i].assetFilePath + "].");
  806. File.WriteAllText(projectPath + "/" + m_ModifiedAssetList[i].assetFilePath, m_ModifiedAssetList[i].assetDataFile);
  807. }
  808. }
  809. AssetDatabase.Refresh();
  810. m_ProgressPercentage = 0;
  811. m_ProjectScanResults = k_ProjectScanReportDefaultText;
  812. }
  813. /// <summary>
  814. /// Check project Asset Serialization and Source Control modes
  815. /// </summary>
  816. private static bool CheckProjectSerializationAndSourceControlModes()
  817. {
  818. // Check Project Asset Serialization and Visible Meta Files mode.
  819. if (EditorSettings.serializationMode != SerializationMode.ForceText || VersionControlSettings.mode != "Visible Meta Files")
  820. {
  821. return false;
  822. }
  823. return true;
  824. }
  825. }
  826. public class TMP_PackageUtilities : Editor
  827. {
  828. enum SaveAssetDialogueOptions { Unset = 0, Save = 1, SaveAll = 2, DoNotSave = 3 };
  829. private static SerializationMode m_ProjectAssetSerializationMode;
  830. private static string m_ProjectExternalVersionControl;
  831. struct AssetRemappingRecord
  832. {
  833. public string oldGuid;
  834. public string newGuid;
  835. public string assetPath;
  836. }
  837. struct AssetModificationRecord
  838. {
  839. public string assetFilePath;
  840. public string assetDataFile;
  841. }
  842. /// <summary>
  843. ///
  844. /// </summary>
  845. [MenuItem("Window/TextMeshPro/Import TMP Essential Resources", false, 2050)]
  846. public static void ImportProjectResourcesMenu()
  847. {
  848. ImportEssentialResources();
  849. }
  850. /// <summary>
  851. ///
  852. /// </summary>
  853. [MenuItem("Window/TextMeshPro/Import TMP Examples and Extras", false, 2051)]
  854. public static void ImportExamplesContentMenu()
  855. {
  856. ImportExamplesAndExtras();
  857. }
  858. private static void GetVersionInfo()
  859. {
  860. string version = TMP_Settings.version;
  861. Debug.Log("The version of this TextMesh Pro UPM package is (" + version + ").");
  862. }
  863. /// <summary>
  864. ///
  865. /// </summary>
  866. private static void ImportExamplesAndExtras()
  867. {
  868. string packageFullPath = TMP_EditorUtility.packageFullPath;
  869. AssetDatabase.ImportPackage(packageFullPath + "/Package Resources/TMP Examples & Extras.unitypackage", true);
  870. }
  871. private static string k_SettingsFilePath;
  872. private static byte[] k_SettingsBackup;
  873. /// <summary>
  874. ///
  875. /// </summary>
  876. private static void ImportEssentialResources()
  877. {
  878. // Check if the TMP Settings asset is already present in the project.
  879. string[] settings = AssetDatabase.FindAssets("t:TMP_Settings");
  880. if (settings.Length > 0)
  881. {
  882. // Save assets just in case the TMP Setting were modified before import.
  883. AssetDatabase.SaveAssets();
  884. // Copy existing TMP Settings asset to a byte[]
  885. k_SettingsFilePath = AssetDatabase.GUIDToAssetPath(settings[0]);
  886. k_SettingsBackup = File.ReadAllBytes(k_SettingsFilePath);
  887. RegisterResourceImportCallback();
  888. }
  889. string packageFullPath = TMP_EditorUtility.packageFullPath;
  890. AssetDatabase.ImportPackage(packageFullPath + "/Package Resources/TMP Essential Resources.unitypackage", true);
  891. }
  892. private static void RegisterResourceImportCallback()
  893. {
  894. AssetDatabase.importPackageCompleted += ImportCallback;
  895. }
  896. private static void ImportCallback(string packageName)
  897. {
  898. // Restore backup of TMP Settings from byte[]
  899. File.WriteAllBytes(k_SettingsFilePath, k_SettingsBackup);
  900. AssetDatabase.Refresh();
  901. AssetDatabase.importPackageCompleted -= ImportCallback;
  902. }
  903. }
  904. }