ConvertToPrefabEditorWindow.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. #if UNITY_2018_1_OR_NEWER
  4. using UnityEditor.Presets;
  5. #endif
  6. using System.Linq;
  7. using System.Security.Permissions;
  8. namespace UnityEditor.Formats.Fbx.Exporter
  9. {
  10. internal class ConvertToPrefabEditorWindow : ExportOptionsEditorWindow
  11. {
  12. protected override GUIContent WindowTitle { get { return new GUIContent("Convert Options"); } }
  13. protected override float MinWindowHeight { get { return 350; } } // determined by trial and error
  14. protected override string ExportButtonName { get { return "Convert"; } }
  15. private string m_prefabFileName = "";
  16. private float m_prefabExtLabelWidth;
  17. protected override bool DisableNameSelection
  18. {
  19. get
  20. {
  21. return (GetToExport() != null && GetToExport().Length > 1);
  22. }
  23. }
  24. protected override bool DisableTransferAnim
  25. {
  26. get
  27. {
  28. return GetToExport() == null || GetToExport().Length > 1;
  29. }
  30. }
  31. public static void Init(IEnumerable<GameObject> toConvert)
  32. {
  33. ConvertToPrefabEditorWindow window = CreateWindow<ConvertToPrefabEditorWindow>();
  34. window.InitializeWindow();
  35. window.SetGameObjectsToConvert(toConvert);
  36. window.Show();
  37. }
  38. protected void SetGameObjectsToConvert(IEnumerable<GameObject> toConvert)
  39. {
  40. SetToExport(toConvert.OrderBy(go => go.name).ToArray());
  41. TransferAnimationSource = null;
  42. TransferAnimationDest = null;
  43. string fbxFileName = null;
  44. if (GetToExport().Length == 1)
  45. {
  46. var go = ModelExporter.GetGameObject(GetToExport()[0]);
  47. // check if the GameObject is a model instance, use as default filename and path if it is
  48. GameObject mainAsset = ConvertToNestedPrefab.GetFbxAssetOrNull(go);
  49. if (!mainAsset)
  50. {
  51. // Use the game object's name
  52. m_prefabFileName = go.name;
  53. }
  54. else
  55. {
  56. // Use the asset's name
  57. var mainAssetRelPath = AssetDatabase.GetAssetPath(mainAsset);
  58. // remove Assets/ from beginning of path
  59. mainAssetRelPath = mainAssetRelPath.Substring("Assets".Length);
  60. m_prefabFileName = System.IO.Path.GetFileNameWithoutExtension(mainAssetRelPath);
  61. ExportSettings.AddFbxSavePath(System.IO.Path.GetDirectoryName(mainAssetRelPath));
  62. fbxFileName = m_prefabFileName;
  63. }
  64. var fullPrefabPath = System.IO.Path.Combine(ExportSettings.PrefabAbsoluteSavePath, m_prefabFileName + ".prefab");
  65. if (System.IO.File.Exists(fullPrefabPath))
  66. {
  67. m_prefabFileName = System.IO.Path.GetFileNameWithoutExtension(ConvertToNestedPrefab.IncrementFileName(ExportSettings.PrefabAbsoluteSavePath, m_prefabFileName + ".prefab"));
  68. }
  69. // if only one object selected, set transfer source/dest to this object
  70. if (go)
  71. {
  72. TransferAnimationSource = go.transform;
  73. TransferAnimationDest = go.transform;
  74. }
  75. }
  76. else if (GetToExport().Length > 1)
  77. {
  78. m_prefabFileName = "(automatic)";
  79. }
  80. // if there is an existing fbx file then use its name, otherwise use the same name as for the prefab
  81. this.SetFilename(fbxFileName != null? fbxFileName : m_prefabFileName);
  82. }
  83. protected override void OnEnable()
  84. {
  85. base.OnEnable();
  86. if (!InnerEditor)
  87. {
  88. InnerEditor = UnityEditor.Editor.CreateEditor(ExportSettings.instance.ConvertToPrefabSettings);
  89. }
  90. m_prefabExtLabelWidth = FbxExtLabelStyle.CalcSize(new GUIContent(".prefab")).x;
  91. }
  92. /// <summary>
  93. /// Return the number of objects in the selection that contain RectTransforms.
  94. /// </summary>
  95. protected int GetUIElementsInExportSetCount()
  96. {
  97. int count = 0;
  98. foreach (var obj in GetToExport())
  99. {
  100. var go = ModelExporter.GetGameObject(obj);
  101. var rectTransforms = go.GetComponentsInChildren<RectTransform>();
  102. count += rectTransforms.Length;
  103. }
  104. return count;
  105. }
  106. protected bool ExportSetContainsAnimation()
  107. {
  108. foreach (var obj in GetToExport())
  109. {
  110. var go = ModelExporter.GetGameObject(obj);
  111. if (go.GetComponentInChildren<Animation>() || go.GetComponentInChildren<Animator>())
  112. {
  113. return true;
  114. }
  115. }
  116. return false;
  117. }
  118. [SecurityPermission(SecurityAction.LinkDemand)]
  119. protected override bool Export()
  120. {
  121. if (string.IsNullOrEmpty(ExportFileName))
  122. {
  123. Debug.LogError("FbxExporter: Please specify an fbx filename");
  124. return false;
  125. }
  126. if (string.IsNullOrEmpty(m_prefabFileName))
  127. {
  128. Debug.LogError("FbxExporter: Please specify a prefab filename");
  129. return false;
  130. }
  131. var fbxDirPath = ExportSettings.FbxAbsoluteSavePath;
  132. var fbxPath = System.IO.Path.Combine(fbxDirPath, ExportFileName + ".fbx");
  133. var prefabDirPath = ExportSettings.PrefabAbsoluteSavePath;
  134. var prefabPath = System.IO.Path.Combine(prefabDirPath, m_prefabFileName + ".prefab");
  135. if (GetToExport() == null)
  136. {
  137. Debug.LogError("FbxExporter: missing object for conversion");
  138. return false;
  139. }
  140. int rectTransformCount = GetUIElementsInExportSetCount();
  141. if (rectTransformCount > 0)
  142. {
  143. // Warn that UI elements will break if converted
  144. string warning = string.Format("Warning: UI Components (ie, RectTransform) are not saved when converting to FBX.\n{0} item(s) in the selection will lose their UI components.",
  145. rectTransformCount);
  146. bool result = UnityEditor.EditorUtility.DisplayDialog(
  147. string.Format("{0} Warning", ModelExporter.PACKAGE_UI_NAME), warning, "Convert and Lose UI", "Cancel");
  148. if (!result)
  149. {
  150. return false;
  151. }
  152. }
  153. if (SettingsObject.UseMayaCompatibleNames && SettingsObject.AllowSceneModification)
  154. {
  155. string warning = "Names of objects in the hierarchy may change with the Compatible Naming option turned on";
  156. if (ExportSetContainsAnimation())
  157. {
  158. warning = "Compatible Naming option turned on. Names of objects in hierarchy may change and break animations.";
  159. }
  160. // give a warning dialog that indicates that names in the scene may change
  161. int result = UnityEditor.EditorUtility.DisplayDialogComplex(
  162. string.Format("{0} Warning", ModelExporter.PACKAGE_UI_NAME), warning, "OK", "Turn off and continue", "Cancel"
  163. );
  164. if (result == 1)
  165. {
  166. // turn compatible naming off
  167. SettingsObject.SetUseMayaCompatibleNames(false);
  168. }
  169. else if (result == 2)
  170. {
  171. return false;
  172. }
  173. }
  174. if (GetToExport().Length == 1)
  175. {
  176. var go = ModelExporter.GetGameObject(GetToExport()[0]);
  177. // Check if we'll be clobbering files. If so, warn the user
  178. // first and let them cancel out.
  179. if (!OverwriteExistingFile(prefabPath))
  180. {
  181. return false;
  182. }
  183. if (ConvertToNestedPrefab.WillExportFbx(go))
  184. {
  185. if (!OverwriteExistingFile(fbxPath))
  186. {
  187. return false;
  188. }
  189. }
  190. ConvertToNestedPrefab.Convert(
  191. go, fbxFullPath: fbxPath, prefabFullPath: prefabPath, exportOptions: ExportSettings.instance.ConvertToPrefabSettings.info
  192. );
  193. return true;
  194. }
  195. bool onlyPrefabAssets = ConvertToNestedPrefab.SetContainsOnlyPrefabAssets(GetToExport());
  196. int groupIndex = -1;
  197. // no need to undo if we aren't converting anything that's in the scene
  198. if (!onlyPrefabAssets)
  199. {
  200. Undo.IncrementCurrentGroup();
  201. groupIndex = Undo.GetCurrentGroup();
  202. Undo.SetCurrentGroupName(ConvertToNestedPrefab.UndoConversionCreateObject);
  203. }
  204. foreach (var obj in GetToExport())
  205. {
  206. // Convert, automatically choosing a file path that won't clobber any existing files.
  207. var go = ModelExporter.GetGameObject(obj);
  208. ConvertToNestedPrefab.Convert(
  209. go, fbxDirectoryFullPath: fbxDirPath, prefabDirectoryFullPath: prefabDirPath, exportOptions: ExportSettings.instance.ConvertToPrefabSettings.info
  210. );
  211. }
  212. if (!onlyPrefabAssets && groupIndex >= 0)
  213. {
  214. Undo.CollapseUndoOperations(groupIndex);
  215. Undo.IncrementCurrentGroup();
  216. }
  217. return true;
  218. }
  219. protected override ExportOptionsSettingsSerializeBase SettingsObject
  220. {
  221. get { return ExportSettings.instance.ConvertToPrefabSettings.info; }
  222. }
  223. #if UNITY_2018_1_OR_NEWER
  224. protected override void ShowPresetReceiver()
  225. {
  226. ShowPresetReceiver(ExportSettings.instance.ConvertToPrefabSettings);
  227. }
  228. #endif
  229. protected override void CreateCustomUI()
  230. {
  231. GUILayout.BeginHorizontal();
  232. EditorGUILayout.LabelField(new GUIContent(
  233. "Prefab Name",
  234. "Filename to save prefab to."), GUILayout.Width(LabelWidth - TextFieldAlignOffset));
  235. EditorGUI.BeginDisabledGroup(DisableNameSelection);
  236. // Show the export name with an uneditable ".prefab" at the end
  237. //-------------------------------------
  238. EditorGUILayout.BeginVertical();
  239. EditorGUILayout.BeginHorizontal(EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
  240. EditorGUI.indentLevel--;
  241. // continually resize to contents
  242. var textFieldSize = NameTextFieldStyle.CalcSize(new GUIContent(m_prefabFileName));
  243. m_prefabFileName = EditorGUILayout.TextField(m_prefabFileName, NameTextFieldStyle, GUILayout.Width(textFieldSize.x + 5), GUILayout.MinWidth(5));
  244. m_prefabFileName = ModelExporter.ConvertToValidFilename(m_prefabFileName);
  245. EditorGUILayout.LabelField("<color=#808080ff>.prefab</color>", FbxExtLabelStyle, GUILayout.Width(m_prefabExtLabelWidth));
  246. EditorGUI.indentLevel++;
  247. EditorGUILayout.EndHorizontal();
  248. EditorGUILayout.EndVertical();
  249. //-----------------------------------
  250. EditorGUI.EndDisabledGroup();
  251. GUILayout.EndHorizontal();
  252. GUILayout.BeginHorizontal();
  253. EditorGUILayout.LabelField(new GUIContent(
  254. "Prefab Path",
  255. "Relative path for saving FBX Prefab Variants."), GUILayout.Width(LabelWidth - FieldOffset));
  256. var pathLabels = ExportSettings.GetRelativePrefabSavePaths();
  257. ExportSettings.instance.SelectedPrefabPath = EditorGUILayout.Popup(ExportSettings.instance.SelectedPrefabPath, pathLabels, GUILayout.MinWidth(SelectableLabelMinWidth));
  258. if (GUILayout.Button(new GUIContent("...", "Browse to a new location to save prefab to"), EditorStyles.miniButton, GUILayout.Width(BrowseButtonWidth)))
  259. {
  260. string initialPath = Application.dataPath;
  261. string fullPath = EditorUtility.OpenFolderPanel(
  262. "Select FBX Prefab Variant Save Path", initialPath, null
  263. );
  264. // Unless the user canceled, make sure they chose something in the Assets folder.
  265. if (!string.IsNullOrEmpty(fullPath))
  266. {
  267. var relativePath = ExportSettings.ConvertToAssetRelativePath(fullPath);
  268. if (string.IsNullOrEmpty(relativePath))
  269. {
  270. Debug.LogWarning("Please select a location in the Assets folder");
  271. }
  272. else
  273. {
  274. ExportSettings.AddPrefabSavePath(relativePath);
  275. // Make sure focus is removed from the selectable label
  276. // otherwise it won't update
  277. GUIUtility.hotControl = 0;
  278. GUIUtility.keyboardControl = 0;
  279. }
  280. }
  281. }
  282. GUILayout.EndHorizontal();
  283. }
  284. protected override void DoNotShowDialogUI()
  285. {
  286. EditorGUI.indentLevel--;
  287. ExportSettings.instance.ShowConvertToPrefabDialog = !EditorGUILayout.Toggle(
  288. new GUIContent("Don't ask me again", "Don't ask me again, use the last used paths and options instead"),
  289. !ExportSettings.instance.ShowConvertToPrefabDialog
  290. );
  291. }
  292. }
  293. }