public void TestConvertToValidFilename() { // test already valid filenames var filename = "foobar.fbx"; var result = ModelExporter.ConvertToValidFilename(filename); Assert.AreEqual(filename, result); filename = "foo_bar 1.fbx"; result = ModelExporter.ConvertToValidFilename(filename); Assert.AreEqual(filename, result); // test invalid filenames filename = "?foo**bar///.fbx"; result = ModelExporter.ConvertToValidFilename(filename); #if UNITY_EDITOR_WIN Assert.AreEqual("_foo__bar___.fbx", result); #else Assert.AreEqual("?foo**bar___.fbx", result); #endif filename = "foo$?ba%r 2.fbx"; result = ModelExporter.ConvertToValidFilename(filename); #if UNITY_EDITOR_WIN Assert.AreEqual("foo$_ba%r 2.fbx", result); #else Assert.AreEqual("foo$?ba%r 2.fbx", result); #endif }
/// <summary> /// Create a prefab from 'instance', or apply 'instance' to its /// prefab if it's already an instance of a prefab. /// /// If it's an instance of a model prefab (an fbx file) we will /// lose that connection and point to a new prefab file. /// /// To avoid applying to an existing prefab, break the prefab /// connection with <c>PrefabUtility.DisconnectPrefabInstance</c>. /// /// Returns the new or updated prefab. /// </summary> /// <param name="instance">A GameObject in the scene. After this /// call, it will be a prefab instance (it might already be /// one).</param> /// <param name="prefabFullPath">The full path to a prefab file we /// will create. Ignored if <paramref name="instance"/> is a /// prefab instance already. May be null, in which case we'll use <paramref /// name="prefabDirectoryFullPath"/> to generate a new name</param> /// <param name="prefabDirectoryFullPath">The full path to a /// directory that will hold the new prefab. Ignored if <paramref /// name="instance"/> is a prefab instance already. Ignored if /// <paramref name="prefabFullPath"/> is provided. May be null, in /// which case we'll use the project export settings.</param> /// <returns>The new or existing prefab.</returns> public static GameObject ApplyOrCreatePrefab(GameObject instance, string prefabDirectoryFullPath = null, string prefabFullPath = null) { if (instance == null) { throw new System.ArgumentNullException("instance"); } if (PrefabUtility.GetPrefabType(instance) == PrefabType.PrefabInstance) { return(PrefabUtility.ReplacePrefab(instance, PrefabUtility.GetCorrespondingObjectFromSource(instance))); } // Otherwise, create a new prefab. First choose its filename/path. if (string.IsNullOrEmpty(prefabFullPath)) { // Generate a unique filename. if (string.IsNullOrEmpty(prefabDirectoryFullPath)) { prefabDirectoryFullPath = UnityEditor.Formats.Fbx.Exporter.ExportSettings.PrefabAbsoluteSavePath; } else { prefabDirectoryFullPath = Path.GetFullPath(prefabDirectoryFullPath); } var prefabBasename = ModelExporter.ConvertToValidFilename(instance.name + ".prefab"); prefabFullPath = Path.Combine(prefabDirectoryFullPath, prefabBasename); if (File.Exists(prefabFullPath)) { prefabFullPath = IncrementFileName(prefabDirectoryFullPath, prefabFullPath); } } var prefabProjectRelativePath = ExportSettings.GetProjectRelativePath(prefabFullPath); var prefabFileName = Path.ChangeExtension(prefabProjectRelativePath, ".prefab"); var prefab = PrefabUtility.CreatePrefab(prefabFileName, instance, ReplacePrefabOptions.ConnectToPrefab); if (!prefab) { throw new ConvertToLinkedPrefabException(string.Format("Failed to create prefab asset in [{0}]", prefabFileName)); } return(prefab); }
// ------------------------------------------------------------------------------------- protected void OnGUI() { // Increasing the label width so that none of the text gets cut off EditorGUIUtility.labelWidth = LabelWidth; GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); #if UNITY_2018_1_OR_NEWER if (EditorGUILayout.DropdownButton(presetIcon, FocusType.Keyboard, presetIconButton)) { ShowPresetReceiver(); } #endif GUILayout.EndHorizontal(); EditorGUILayout.LabelField("Naming"); EditorGUI.indentLevel++; GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Export Name", "Filename to save model to."), GUILayout.Width(LabelWidth - TextFieldAlignOffset)); EditorGUI.BeginDisabledGroup(DisableNameSelection); // Show the export name with an uneditable ".fbx" at the end //------------------------------------- EditorGUILayout.BeginVertical(); EditorGUILayout.BeginHorizontal(EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); EditorGUI.indentLevel--; // continually resize to contents var textFieldSize = NameTextFieldStyle.CalcSize(new GUIContent(ExportFileName)); ExportFileName = EditorGUILayout.TextField(ExportFileName, NameTextFieldStyle, GUILayout.Width(textFieldSize.x + 5), GUILayout.MinWidth(5)); ExportFileName = ModelExporter.ConvertToValidFilename(ExportFileName); EditorGUILayout.LabelField("<color=#808080ff>.fbx</color>", FbxExtLabelStyle, GUILayout.Width(FbxExtLabelWidth)); EditorGUI.indentLevel++; EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); //----------------------------------- EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Export Path", "Location where the FBX will be saved."), GUILayout.Width(LabelWidth - FieldOffset)); var pathLabels = ExportSettings.GetMixedSavePaths(FbxSavePaths); if (this is ConvertToPrefabEditorWindow) { pathLabels = ExportSettings.GetRelativeFbxSavePaths(FbxSavePaths, ref m_selectedFbxPath); } SelectedFbxPath = EditorGUILayout.Popup(SelectedFbxPath, pathLabels, GUILayout.MinWidth(SelectableLabelMinWidth)); if (!(this is ConvertToPrefabEditorWindow)) { var exportSettingsEditor = InnerEditor as ExportModelSettingsEditor; // Set export setting for exporting outside the project on choosing a path var exportOutsideProject = !pathLabels[SelectedFbxPath].Substring(0, 6).Equals("Assets"); exportSettingsEditor.SetExportingOutsideProject(exportOutsideProject); } if (GUILayout.Button(new GUIContent("...", "Browse to a new location to export to"), EditorStyles.miniButton, GUILayout.Width(BrowseButtonWidth))) { string initialPath = Application.dataPath; string fullPath = EditorUtility.OpenFolderPanel( "Select Export Model Path", initialPath, null ); // Unless the user canceled, save path. if (!string.IsNullOrEmpty(fullPath)) { var relativePath = ExportSettings.ConvertToAssetRelativePath(fullPath); // If exporting an fbx for a prefab, not allowed to export outside the Assets folder if (this is ConvertToPrefabEditorWindow && string.IsNullOrEmpty(relativePath)) { Debug.LogWarning("Please select a location in the Assets folder"); } // We're exporting outside Assets folder, so store the absolute path else if (string.IsNullOrEmpty(relativePath)) { ExportSettings.AddSavePath(fullPath, FbxSavePaths, exportOutsideProject: true); SelectedFbxPath = 0; } // Store the relative path to the Assets folder else { ExportSettings.AddSavePath(relativePath, FbxSavePaths, exportOutsideProject: false); SelectedFbxPath = 0; } // Make sure focus is removed from the selectable label // otherwise it won't update GUIUtility.hotControl = 0; GUIUtility.keyboardControl = 0; } } GUILayout.EndHorizontal(); CreateCustomUI(); EditorGUILayout.Space(); EditorGUI.BeginDisabledGroup(DisableTransferAnim); EditorGUI.indentLevel--; GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Transfer Animation", "Transfer transform animation from source to destination. Animation on objects between source and destination will also be transferred to destination." ), GUILayout.Width(LabelWidth - FieldOffset)); GUILayout.EndHorizontal(); EditorGUI.indentLevel++; TransferAnimationSource = EditorGUILayout.ObjectField("Source", TransferAnimationSource, typeof(Transform), allowSceneObjects: true) as Transform; TransferAnimationDest = EditorGUILayout.ObjectField("Destination", TransferAnimationDest, typeof(Transform), allowSceneObjects: true) as Transform; EditorGUILayout.Space(); EditorGUI.EndDisabledGroup(); EditorGUI.indentLevel--; m_showOptions = EditorGUILayout.Foldout(m_showOptions, "Options"); EditorGUI.indentLevel++; if (m_showOptions) { InnerEditor.OnInspectorGUI(); } // if we are exporting or converting a prefab with overrides, then show a warning if (SelectionContainsPrefabInstanceWithAddedObjects()) { EditorGUILayout.Space(); EditorGUILayout.HelpBox("Prefab instance overrides will be exported", MessageType.Warning, true); } GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(); DoNotShowDialogUI(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Cancel", GUILayout.Width(ExportButtonWidth))) { this.Close(); } if (GUILayout.Button(ExportButtonName, GUILayout.Width(ExportButtonWidth))) { if (Export()) { this.Close(); } } GUILayout.EndHorizontal(); EditorGUILayout.Space(); // adding a space at bottom of dialog so buttons aren't right at the edge if (GUI.changed) { SaveExportSettings(); } }
/// <summary> /// Return an FBX asset that corresponds to 'toConvert'. /// /// If 'toConvert' is the root of an FBX asset, return it. /// /// If it's an instance in a scene the points to the root of an FBX /// asset, return that asset. /// /// Otherwise, export according to the paths and options, and /// return the new asset. /// </summary> /// <param name="toConvert">GameObject for which we want an fbx asset</param> /// <param name="fbxDirectoryFullPath">Export will choose an /// appropriate filename in this directory. Ignored if fbxFullPath is /// set. Ignored if toConvert is an fbx asset or an instance of an /// fbx.</param> /// <param name="fbxDirectoryFullPath">Export will create this /// file. Overrides fbxDirectoryFullPath. Ignored if toConvert is an /// fbx asset or an instance of an fbx.</param> /// <returns>The root of a model prefab asset.</returns> internal static GameObject GetOrCreateFbxAsset(GameObject toConvert, string fbxDirectoryFullPath = null, string fbxFullPath = null, ConvertToPrefabSettingsSerialize exportOptions = null) { if (toConvert == null) { throw new System.ArgumentNullException("toConvert"); } var mainAsset = GetFbxAssetOrNull(toConvert); if (mainAsset) { return(mainAsset); } if (string.IsNullOrEmpty(fbxFullPath)) { // Generate a unique filename. if (string.IsNullOrEmpty(fbxDirectoryFullPath)) { fbxDirectoryFullPath = UnityEditor.Formats.Fbx.Exporter.ExportSettings.FbxAbsoluteSavePath; } else { fbxDirectoryFullPath = Path.GetFullPath(fbxDirectoryFullPath); } var fbxBasename = ModelExporter.ConvertToValidFilename(toConvert.name + ".fbx"); fbxFullPath = Path.Combine(fbxDirectoryFullPath, fbxBasename); if (File.Exists(fbxFullPath)) { fbxFullPath = IncrementFileName(fbxDirectoryFullPath, fbxFullPath); } } var projectRelativePath = ExportSettings.GetProjectRelativePath(fbxFullPath); // Make sure that the object names in the hierarchy are unique. // The import back in to Unity would do this automatically but // we prefer to control it so that the Maya artist can see the // same names as exist in Unity. EnforceUniqueNames(new GameObject[] { toConvert }); // Export to FBX. It refreshes the database. { var fbxActualPath = ModelExporter.ExportObject( fbxFullPath, toConvert, exportOptions != null ? exportOptions : new ConvertToPrefabSettingsSerialize() ); if (fbxActualPath != fbxFullPath) { throw new ConvertToNestedPrefabException("Failed to convert " + toConvert.name); } } // Replace w Model asset. LoadMainAssetAtPath wants a path // relative to the project, not relative to the assets folder. var unityMainAsset = AssetDatabase.LoadMainAssetAtPath(projectRelativePath) as GameObject; if (!unityMainAsset) { throw new ConvertToNestedPrefabException("Failed to convert " + toConvert.name); } return(unityMainAsset); }
public static GameObject Convert( GameObject toConvert, string fbxDirectoryFullPath = null, string fbxFullPath = null, string prefabDirectoryFullPath = null, string prefabFullPath = null, ConvertToPrefabSettingsSerialize exportOptions = null) { if (toConvert == null) { throw new System.ArgumentNullException("toConvert"); } if (PrefabUtility.IsPartOfNonAssetPrefabInstance(toConvert) && !PrefabUtility.IsOutermostPrefabInstanceRoot(toConvert)) { return(null); // cannot convert in this scenario } // can't currently handle converting root of prefab in prefab preview scene if (SceneManagement.EditorSceneManager.IsPreviewSceneObject(toConvert) && toConvert.transform.parent == null) { return(null); } // If we selected the something that's already backed by an // FBX, don't export. var mainAsset = GetOrCreateFbxAsset(toConvert, fbxDirectoryFullPath, fbxFullPath, exportOptions); // if toConvert is part of a prefab asset and not an instance, make it an instance in a preview scene // so that we can unpack it and avoid issues with nested prefab references. bool isPrefabAsset = false; UnityEngine.SceneManagement.Scene?previewScene = null; if (PrefabUtility.IsPartOfPrefabAsset(toConvert) && PrefabUtility.GetPrefabInstanceStatus(toConvert) == PrefabInstanceStatus.NotAPrefab) { previewScene = SceneManagement.EditorSceneManager.NewPreviewScene(); toConvert = PrefabUtility.InstantiatePrefab(toConvert, previewScene.Value) as GameObject; isPrefabAsset = true; } // don't need to undo if we are converting a prefab asset if (!isPrefabAsset) { Undo.IncrementCurrentGroup(); Undo.SetCurrentGroupName(string.Format(UndoConversionGroup, toConvert.name)); } // if root is a prefab instance, unpack it. Unpack everything below as well if (PrefabUtility.GetPrefabInstanceStatus(toConvert) == PrefabInstanceStatus.Connected) { Undo.RegisterFullObjectHierarchyUndo(toConvert, "unpack prefab instance"); PrefabUtility.UnpackPrefabInstance(toConvert, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); } // create prefab variant from the fbx var fbxInstance = PrefabUtility.InstantiatePrefab(mainAsset) as GameObject; // replace hierarchy in the scene if (!isPrefabAsset && toConvert != null) { // don't worry about keeping the world position in the prefab, as we will fix the transform on the instance root fbxInstance.transform.SetParent(toConvert.transform.parent, worldPositionStays: false); fbxInstance.transform.SetSiblingIndex(toConvert.transform.GetSiblingIndex()); } // copy components over UpdateFromSourceRecursive(fbxInstance, toConvert); // make sure we have a path for the prefab if (string.IsNullOrEmpty(prefabFullPath)) { // Generate a unique filename. if (string.IsNullOrEmpty(prefabDirectoryFullPath)) { prefabDirectoryFullPath = UnityEditor.Formats.Fbx.Exporter.ExportSettings.PrefabAbsoluteSavePath; } else { prefabDirectoryFullPath = Path.GetFullPath(prefabDirectoryFullPath); } var prefabBasename = ModelExporter.ConvertToValidFilename(toConvert.name + ".prefab"); prefabFullPath = Path.Combine(prefabDirectoryFullPath, prefabBasename); if (File.Exists(prefabFullPath)) { prefabFullPath = IncrementFileName(prefabDirectoryFullPath, prefabFullPath); } } // make sure the directory structure exists var dirName = Path.GetDirectoryName(prefabFullPath); if (!Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); } var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(fbxInstance, ExportSettings.GetProjectRelativePath(prefabFullPath), InteractionMode.AutomatedAction); // replace hierarchy in the scene if (!isPrefabAsset && toConvert != null) { Undo.DestroyObjectImmediate(toConvert); Undo.RegisterCreatedObjectUndo(fbxInstance, UndoConversionCreateObject); SceneManagement.EditorSceneManager.MarkSceneDirty(fbxInstance.scene); Undo.IncrementCurrentGroup(); return(fbxInstance); } else { Undo.ClearUndo(toConvert); Undo.ClearUndo(fbxInstance); Object.DestroyImmediate(fbxInstance); Object.DestroyImmediate(toConvert); } if (previewScene.HasValue) { SceneManagement.EditorSceneManager.ClosePreviewScene(previewScene.Value); } return(prefab); }
protected override void CreateCustomUI() { GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Prefab Name", "Filename to save prefab to."), GUILayout.Width(LabelWidth - TextFieldAlignOffset)); EditorGUI.BeginDisabledGroup(DisableNameSelection); // Show the export name with an uneditable ".prefab" at the end //------------------------------------- EditorGUILayout.BeginVertical(); EditorGUILayout.BeginHorizontal(EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); EditorGUI.indentLevel--; // continually resize to contents var textFieldSize = NameTextFieldStyle.CalcSize(new GUIContent(m_prefabFileName)); m_prefabFileName = EditorGUILayout.TextField(m_prefabFileName, NameTextFieldStyle, GUILayout.Width(textFieldSize.x + 5), GUILayout.MinWidth(5)); m_prefabFileName = ModelExporter.ConvertToValidFilename(m_prefabFileName); EditorGUILayout.LabelField("<color=#808080ff>.prefab</color>", FbxExtLabelStyle, GUILayout.Width(m_prefabExtLabelWidth)); EditorGUI.indentLevel++; EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); //----------------------------------- EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Prefab Path", "Relative path for saving FBX Prefab Variants."), GUILayout.Width(LabelWidth - FieldOffset)); var pathLabels = ExportSettings.GetRelativePrefabSavePaths(); ExportSettings.instance.SelectedPrefabPath = EditorGUILayout.Popup(ExportSettings.instance.SelectedPrefabPath, pathLabels, GUILayout.MinWidth(SelectableLabelMinWidth)); if (GUILayout.Button(new GUIContent("...", "Browse to a new location to save prefab to"), EditorStyles.miniButton, GUILayout.Width(BrowseButtonWidth))) { string initialPath = Application.dataPath; string fullPath = EditorUtility.OpenFolderPanel( "Select FBX Prefab Variant Save Path", initialPath, null ); // Unless the user canceled, make sure they chose something in the Assets folder. if (!string.IsNullOrEmpty(fullPath)) { var relativePath = ExportSettings.ConvertToAssetRelativePath(fullPath); if (string.IsNullOrEmpty(relativePath)) { Debug.LogWarning("Please select a location in the Assets folder"); } else { ExportSettings.AddPrefabSavePath(relativePath); // Make sure focus is removed from the selectable label // otherwise it won't update GUIUtility.hotControl = 0; GUIUtility.keyboardControl = 0; } } } GUILayout.EndHorizontal(); }
// ------------------------------------------------------------------------------------- protected void OnGUI() { // Increasing the label width so that none of the text gets cut off EditorGUIUtility.labelWidth = LabelWidth; GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); #if UNITY_2018_1_OR_NEWER if (EditorGUILayout.DropdownButton(presetIcon, FocusType.Keyboard, presetIconButton)) { ShowPresetReceiver(); } #endif GUILayout.EndHorizontal(); EditorGUILayout.LabelField("Naming"); EditorGUI.indentLevel++; GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Export Name", "Filename to save model to."), GUILayout.Width(LabelWidth - TextFieldAlignOffset)); EditorGUI.BeginDisabledGroup(DisableNameSelection); // Show the export name with an uneditable ".fbx" at the end //------------------------------------- EditorGUILayout.BeginVertical(); EditorGUILayout.BeginHorizontal(EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); EditorGUI.indentLevel--; // continually resize to contents var textFieldSize = NameTextFieldStyle.CalcSize(new GUIContent(ExportFileName)); ExportFileName = EditorGUILayout.TextField(ExportFileName, NameTextFieldStyle, GUILayout.Width(textFieldSize.x + 5), GUILayout.MinWidth(5)); ExportFileName = ModelExporter.ConvertToValidFilename(ExportFileName); EditorGUILayout.LabelField("<color=#808080ff>.fbx</color>", FbxExtLabelStyle, GUILayout.Width(FbxExtLabelWidth)); EditorGUI.indentLevel++; EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); //----------------------------------- EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Export Path", "Relative path for saving Model Prefabs."), GUILayout.Width(LabelWidth - FieldOffset)); var pathLabels = ExportSettings.GetRelativeFbxSavePaths(); ExportSettings.instance.SelectedFbxPath = EditorGUILayout.Popup(ExportSettings.instance.SelectedFbxPath, pathLabels, GUILayout.MinWidth(SelectableLabelMinWidth)); if (GUILayout.Button(new GUIContent("...", "Browse to a new location to export to"), EditorStyles.miniButton, GUILayout.Width(BrowseButtonWidth))) { string initialPath = Application.dataPath; string fullPath = EditorUtility.OpenFolderPanel( "Select Export Model Path", initialPath, null ); // Unless the user canceled, make sure they chose something in the Assets folder. if (!string.IsNullOrEmpty(fullPath)) { var relativePath = ExportSettings.ConvertToAssetRelativePath(fullPath); if (string.IsNullOrEmpty(relativePath)) { Debug.LogWarning("Please select a location in the Assets folder"); } else { ExportSettings.AddFbxSavePath(relativePath); // Make sure focus is removed from the selectable label // otherwise it won't update GUIUtility.hotControl = 0; GUIUtility.keyboardControl = 0; } } } GUILayout.EndHorizontal(); CreateCustomUI(); EditorGUILayout.Space(); EditorGUI.BeginDisabledGroup(DisableTransferAnim); EditorGUI.indentLevel--; GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent( "Transfer Animation", "Transfer transform animation from source to destination. Animation on objects between source and destination will also be transferred to destination." ), GUILayout.Width(LabelWidth - FieldOffset)); GUILayout.EndHorizontal(); EditorGUI.indentLevel++; TransferAnimationSource = EditorGUILayout.ObjectField("Source", TransferAnimationSource, typeof(Transform), allowSceneObjects: true) as Transform; TransferAnimationDest = EditorGUILayout.ObjectField("Destination", TransferAnimationDest, typeof(Transform), allowSceneObjects: true) as Transform; EditorGUILayout.Space(); EditorGUI.EndDisabledGroup(); EditorGUI.indentLevel--; m_showOptions = EditorGUILayout.Foldout(m_showOptions, "Options"); EditorGUI.indentLevel++; if (m_showOptions) { InnerEditor.OnInspectorGUI(); } GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(); DoNotShowDialogUI(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Cancel", GUILayout.Width(ExportButtonWidth))) { this.Close(); } if (GUILayout.Button(ExportButtonName, GUILayout.Width(ExportButtonWidth))) { if (Export()) { this.Close(); } } GUILayout.EndHorizontal(); if (GUI.changed) { SaveExportSettings(); } }