using UnityEngine; namespace UnityEditor.Formats.Fbx.Exporter { [CustomEditor(typeof(ExportModelSettings))] internal class ExportModelSettingsEditor : UnityEditor.Editor { private const float DefaultLabelWidth = 175; private const float DefaultFieldOffset = 18; public float LabelWidth { get; set; } = DefaultLabelWidth; public float FieldOffset { get; set; } = DefaultFieldOffset; private string[] exportFormatOptions = new string[] { "ASCII", "Binary" }; private string[] includeOptions = new string[] {"Model(s) Only", "Animation Only", "Model(s) + Animation"}; private string[] lodOptions = new string[] {"All Levels", "Highest", "Lowest"}; public const string singleHierarchyOption = "Local Pivot"; public const string multiHerarchyOption = "Local Centered"; private string hierarchyDepOption = singleHierarchyOption; private string[] objPositionOptions { get { return new string[] {hierarchyDepOption, "World Absolute"}; }} private bool disableIncludeDropdown = false; private bool m_exportingOutsideProject = false; public void SetExportingOutsideProject(bool val) { m_exportingOutsideProject = val; } public void SetIsSingleHierarchy(bool singleHierarchy) { if (singleHierarchy) { hierarchyDepOption = singleHierarchyOption; return; } hierarchyDepOption = multiHerarchyOption; } public void DisableIncludeDropdown(bool disable) { disableIncludeDropdown = disable; } public override void OnInspectorGUI() { var exportSettings = ((ExportModelSettings)target).info; EditorGUIUtility.labelWidth = LabelWidth; GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Export Format", "Export the FBX file in the standard binary format." + " Select ASCII to export the FBX file in ASCII format."), GUILayout.Width(LabelWidth - FieldOffset)); exportSettings.SetExportFormat((ExportFormat)EditorGUILayout.Popup((int)exportSettings.ExportFormat, exportFormatOptions)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Include", "Select whether to export models, animation or both."), GUILayout.Width(LabelWidth - FieldOffset)); EditorGUI.BeginDisabledGroup(disableIncludeDropdown); exportSettings.SetModelAnimIncludeOption((Include)EditorGUILayout.Popup((int)exportSettings.ModelAnimIncludeOption, includeOptions)); EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("LOD level", "Select which LOD to export."), GUILayout.Width(LabelWidth - FieldOffset)); // greyed out if animation only EditorGUI.BeginDisabledGroup(exportSettings.ModelAnimIncludeOption == Include.Anim); exportSettings.SetLODExportType((LODExportType)EditorGUILayout.Popup((int)exportSettings.LODExportType, lodOptions)); EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Object(s) Position", "Select an option for exporting object's transform."), GUILayout.Width(LabelWidth - FieldOffset)); // greyed out if animation only EditorGUI.BeginDisabledGroup(exportSettings.ModelAnimIncludeOption == Include.Anim); exportSettings.SetObjectPosition((ObjectPosition)EditorGUILayout.Popup((int)exportSettings.ObjectPosition, objPositionOptions)); EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Animated Skinned Mesh", "If checked, animation on objects with skinned meshes will be exported"), GUILayout.Width(LabelWidth - FieldOffset)); // greyed out if model EditorGUI.BeginDisabledGroup(exportSettings.ModelAnimIncludeOption == Include.Model); exportSettings.SetAnimatedSkinnedMesh(EditorGUILayout.Toggle(exportSettings.AnimateSkinnedMesh)); EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Compatible Naming", "In Maya some symbols such as spaces and accents get replaced when importing an FBX " + "(e.g. \"foo bar\" becomes \"fooFBXASC032bar\"). " + "On export, convert the names of GameObjects so they are Maya compatible." + (exportSettings.UseMayaCompatibleNames ? "" : "\n\nWARNING: Disabling this feature may result in lost material connections," + " and unexpected character replacements in Maya.")), GUILayout.Width(LabelWidth - FieldOffset)); exportSettings.SetUseMayaCompatibleNames(EditorGUILayout.Toggle(exportSettings.UseMayaCompatibleNames)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Export Unrendered", "If checked, meshes will be exported even if they don't have a Renderer component."), GUILayout.Width(LabelWidth - FieldOffset)); // greyed out if animation only EditorGUI.BeginDisabledGroup(exportSettings.ModelAnimIncludeOption == Include.Anim); exportSettings.SetExportUnrendered(EditorGUILayout.Toggle(exportSettings.ExportUnrendered)); EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Preserve Import Settings", "If checked, the import settings from the overwritten FBX will be carried over to the new version."), GUILayout.Width(LabelWidth - FieldOffset)); // greyed out if exporting outside assets folder EditorGUI.BeginDisabledGroup(m_exportingOutsideProject); exportSettings.SetPreserveImportSettings(EditorGUILayout.Toggle(exportSettings.PreserveImportSettings)); EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Keep Instances", "If enabled, instances will be preserved as instances in the FBX file. This can cause issues with e.g. Blender if different instances have different materials assigned."), GUILayout.Width(LabelWidth - FieldOffset)); exportSettings.SetKeepInstances(EditorGUILayout.Toggle(exportSettings.KeepInstances)); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); EditorGUILayout.LabelField(new GUIContent("Embed Textures", "If enabled, textures are embedded into the resulting FBX file instead of referenced."), GUILayout.Width(LabelWidth - FieldOffset)); exportSettings.SetEmbedTextures(EditorGUILayout.Toggle(exportSettings.EmbedTextures)); GUILayout.EndHorizontal(); } } /// /// Interface of export options that you can set when exporting to FBX. /// internal interface IExportOptions { /// /// The export format (binary or ascii). /// ExportFormat ExportFormat { get; } /// /// Option to export the model only, the animation only, or both the model and the animation. /// Include ModelAnimIncludeOption { get; } /// /// The type of LOD to export (All, Highest or Lowest). /// LODExportType LODExportType { get; } /// /// The position to export the object to (Local centered, World absolute, or Reset). Use Reset for converting to a Prefab. /// ObjectPosition ObjectPosition { get; } /// /// Option to export the animation on GameObjects that have a skinned mesh. /// bool AnimateSkinnedMesh { get; } /// /// Option to convert the GameObject and material names to Maya compatible names. /// bool UseMayaCompatibleNames { get; } /// /// Option to change the GameObjects and material names in the scene to keep them /// Maya compatible after the export. Only works if UseMayaCompatibleNames is also enabled. /// bool AllowSceneModification { get; } /// /// Option to export GameObjects that don't have a renderer. /// bool ExportUnrendered { get; } /// /// Option to preserve the previous import settings after the export when overwriting an existing FBX file. /// bool PreserveImportSettings { get; } /// /// Option to keep multiple instances of the same mesh as separate instances on export. /// bool KeepInstances { get; } /// /// Option to embed textures in the exported FBX file. /// /// /// To embed textures, you must set the file ExportFormat to binary. /// bool EmbedTextures { get; } /// /// The transform to transfer the animation from. The animation is transferred to AnimationDest. /// /// /// Transform must be an ancestor of AnimationDest, and may be an ancestor of the selected GameObject. /// Transform AnimationSource { get; } /// /// The transform to transfer the animation to. /// This GameObject receives the transform animation on GameObjects between Source /// and Destination as well as the animation on the Source itself. /// Transform AnimationDest { get; } } internal abstract class ExportOptionsSettingsBase : ScriptableObject where T : ExportOptionsSettingsSerializeBase, new() { [SerializeField] private T m_info = new T(); public T info { get { return m_info; } set { m_info = value; } } public override bool Equals(object e) { var expOptions = e as ExportOptionsSettingsBase; if (expOptions == null) { return false; } return this.info.Equals(expOptions.info); } public override int GetHashCode() { return this.info.GetHashCode(); } } internal class ExportModelSettings : ExportOptionsSettingsBase {} /// /// Base class for the export model settings and convert to prefab settings. /// [System.Serializable] internal abstract class ExportOptionsSettingsSerializeBase : IExportOptions { [SerializeField] private ExportFormat exportFormat = ExportFormat.ASCII; [SerializeField] private bool animatedSkinnedMesh = false; [SerializeField] private bool mayaCompatibleNaming = true; [System.NonSerialized] private Transform animSource; [System.NonSerialized] private Transform animDest; /// public ExportFormat ExportFormat { get { return exportFormat; } } /// /// Specifies the export format to binary or ascii. /// /// Binary or ASCII public void SetExportFormat(ExportFormat format) { this.exportFormat = format; } /// public bool AnimateSkinnedMesh { get { return animatedSkinnedMesh; } } /// /// Specifies whether to export animation on GameObjects containing a skinned mesh. /// /// True to export animation on skinned meshes, false otherwise. public void SetAnimatedSkinnedMesh(bool animatedSkinnedMesh) { this.animatedSkinnedMesh = animatedSkinnedMesh; } /// public bool UseMayaCompatibleNames { get { return mayaCompatibleNaming; } } /// /// Specifies whether to rename the exported GameObjects to Maya compatible names. /// /// True to have export Maya compatible names, false otherwise. public void SetUseMayaCompatibleNames(bool useMayaCompNames) { this.mayaCompatibleNaming = useMayaCompNames; } /// public Transform AnimationSource { get { return animSource; } } /// /// Specifies the transform to transfer the animation from. /// /// The transform to transfer the animation from. public void SetAnimationSource(Transform source) { this.animSource = source; } /// public Transform AnimationDest { get { return animDest; } } /// /// Specifies the transform to transfer the source animation to. /// /// The transform to transfer the animation to. public void SetAnimationDest(Transform dest) { this.animDest = dest; } /// public abstract Include ModelAnimIncludeOption { get; } /// public abstract LODExportType LODExportType { get; } /// public abstract ObjectPosition ObjectPosition { get; } /// public abstract bool ExportUnrendered { get; } /// public virtual bool PreserveImportSettings { get { return false; } } /// public abstract bool AllowSceneModification { get; } /// public virtual bool KeepInstances { get { return true; } } /// public virtual bool EmbedTextures { get { return false; } } /// /// Checks if two instances of the export settings are equal. /// /// The other export setting object to check. /// True if equal, false otherwise. public override bool Equals(object e) { var expOptions = e as ExportOptionsSettingsSerializeBase; if (expOptions == null) { return false; } return animatedSkinnedMesh == expOptions.animatedSkinnedMesh && mayaCompatibleNaming == expOptions.mayaCompatibleNaming && exportFormat == expOptions.exportFormat; } /// /// Gets the hash code for this instance of the export model settings. /// /// Unique hash code for the export model settings. public override int GetHashCode() { return (animatedSkinnedMesh ? 1 : 0) | ((mayaCompatibleNaming ? 1 : 0) << 1) | ((int)exportFormat << 2); } } /// /// Class specifying the settings for exporting to FBX. /// [System.Serializable] internal class ExportModelSettingsSerialize : ExportOptionsSettingsSerializeBase { [SerializeField] private Include include = Include.ModelAndAnim; [SerializeField] private LODExportType lodLevel = LODExportType.All; [SerializeField] private ObjectPosition objectPosition = ObjectPosition.LocalCentered; [SerializeField] private bool exportUnrendered = true; [SerializeField] private bool preserveImportSettings = false; [SerializeField] private bool keepInstances = true; [SerializeField] private bool embedTextures = false; /// public override Include ModelAnimIncludeOption { get { return include; } } /// /// Specifies to export the model only, the animation only, or both the model and the animation. /// /// Model, animation, or model and animation public void SetModelAnimIncludeOption(Include include) { this.include = include; } /// public override LODExportType LODExportType { get { return lodLevel; } } /// /// Specifies the type of LOD to export (All, Highest or Lowest). /// /// All, Highest, or Lowest public void SetLODExportType(LODExportType lodLevel) { this.lodLevel = lodLevel; } /// public override ObjectPosition ObjectPosition { get { return objectPosition; } } /// /// Specifies the position to export the object to (Local centered, World absolute, or Reset). Use Reset for converting to a Prefab). /// /// Local centered, World absolute, or Reset public void SetObjectPosition(ObjectPosition objectPosition) { this.objectPosition = objectPosition; } /// public override bool ExportUnrendered { get { return exportUnrendered; } } /// /// Specifies whether to export GameObjects that don't have a renderer. /// /// True to export unrendered, false otherwise. public void SetExportUnrendered(bool exportUnrendered) { this.exportUnrendered = exportUnrendered; } /// public override bool PreserveImportSettings { get { return preserveImportSettings; } } /// /// Specifies whether to preserve the previous import settings after the export when overwriting /// an existing FBX file. /// /// True to preserve the previous import settings, false otherwise. public void SetPreserveImportSettings(bool preserveImportSettings) { this.preserveImportSettings = preserveImportSettings; } /// public override bool AllowSceneModification { get { return false; } } /// public override bool KeepInstances { get { return keepInstances; } } /// /// Specifies whether to keep multiple instances of the same mesh as separate instances on export. /// /// True to export as separate instances, false otherwise. public void SetKeepInstances(bool keepInstances) { this.keepInstances = keepInstances; } /// public override bool EmbedTextures { get { return embedTextures; } } /// /// Specifies whether to embed textures in the exported FBX file. /// /// True to embed textures, false otherwise. public void SetEmbedTextures(bool embedTextures) { this.embedTextures = embedTextures; } /// public override bool Equals(object e) { var expOptions = e as ExportModelSettingsSerialize; if (expOptions == null) { return false; } return base.Equals(e) && include == expOptions.include && lodLevel == expOptions.lodLevel && objectPosition == expOptions.objectPosition && exportUnrendered == expOptions.exportUnrendered && preserveImportSettings == expOptions.preserveImportSettings; } /// public override int GetHashCode() { var bitmask = base.GetHashCode(); bitmask = (bitmask << 2) ^ (int)include; bitmask = (bitmask << 2) ^ (int)lodLevel; bitmask = (bitmask << 2) ^ (int)objectPosition; bitmask = (bitmask << 1) | (exportUnrendered ? 1 : 0); bitmask = (bitmask << 1) | (preserveImportSettings ? 1 : 0); return bitmask; } } /// /// Class specifying the settings for exporting to FBX. /// [System.Serializable] public class ExportModelOptions : IExportOptions { [SerializeField] private ExportFormat exportFormat = ExportFormat.ASCII; [SerializeField] private bool animatedSkinnedMesh = false; [SerializeField] private bool mayaCompatibleNaming = true; [System.NonSerialized] private Transform animSource; [System.NonSerialized] private Transform animDest; [SerializeField] private Include include = Include.ModelAndAnim; [SerializeField] private LODExportType lodLevel = LODExportType.All; [SerializeField] private ObjectPosition objectPosition = ObjectPosition.LocalCentered; [SerializeField] private bool exportUnrendered = true; [SerializeField] private bool preserveImportSettings = false; [SerializeField] private bool keepInstances = true; [SerializeField] private bool embedTextures = false; /// /// The export format (binary or ascii). /// public ExportFormat ExportFormat { get { return exportFormat; } set { exportFormat = value; } } /// /// Option to export the model only, the animation only, or both the model and the animation. /// public Include ModelAnimIncludeOption { get { return include; } set { include = value; } } /// /// The type of LOD to export (All, Highest or Lowest). /// public LODExportType LODExportType { get { return lodLevel; } set { lodLevel = value; } } /// /// The position to export the object to (Local centered, World absolute, or Reset). Use Reset for converting to a Prefab. /// public ObjectPosition ObjectPosition { get { return objectPosition; } set { objectPosition = value; } } /// /// Option to export the animation on GameObjects that have a skinned mesh. /// public bool AnimateSkinnedMesh { get { return animatedSkinnedMesh; } set { animatedSkinnedMesh = value; } } /// /// Option to convert the GameObject and material names to Maya compatible names. /// public bool UseMayaCompatibleNames { get { return mayaCompatibleNaming; } set { mayaCompatibleNaming = value; } } /// /// Option to change the GameObjects and material names in the scene to keep them /// Maya compatible after the export. Only works if UseMayaCompatibleNames is also enabled. /// bool IExportOptions.AllowSceneModification { get { return false; } } /// /// Option to export GameObjects that don't have a renderer. /// public bool ExportUnrendered { get { return exportUnrendered; } set { exportUnrendered = value; } } /// /// Option to preserve the previous import settings after the export when overwriting an existing FBX file. /// public bool PreserveImportSettings { get { return preserveImportSettings; } set { preserveImportSettings = value; } } /// /// Option to keep multiple instances of the same mesh as separate instances on export. /// public bool KeepInstances { get { return keepInstances; } set { keepInstances = value; } } /// /// Option to embed textures in the exported FBX file. /// /// /// To embed textures, you must set the file ExportFormat to binary. /// public bool EmbedTextures { get { return embedTextures; } set { embedTextures = value; } } /// /// The transform to transfer the animation from. The animation is transferred to AnimationDest. /// /// /// Transform must be an ancestor of AnimationDest, and may be an ancestor of the selected GameObject. /// public Transform AnimationSource { get { return animSource; } set { animSource = value; } } /// /// The transform to transfer the animation to. /// This GameObject receives the transform animation on GameObjects between Source /// and Destination as well as the animation on the Source itself. /// public Transform AnimationDest { get { return animDest; } set { animDest = value; } } internal ExportModelSettingsSerialize ConvertToModelSettingsSerialize() { var exportSettings = new ExportModelSettingsSerialize(); exportSettings.SetAnimatedSkinnedMesh(animatedSkinnedMesh); exportSettings.SetAnimationDest(animDest); exportSettings.SetAnimationSource(animSource); exportSettings.SetEmbedTextures(embedTextures); exportSettings.SetExportFormat(exportFormat); exportSettings.SetExportUnrendered(exportUnrendered); exportSettings.SetKeepInstances(keepInstances); exportSettings.SetLODExportType(lodLevel); exportSettings.SetModelAnimIncludeOption(include); exportSettings.SetObjectPosition(objectPosition); exportSettings.SetPreserveImportSettings(preserveImportSettings); exportSettings.SetUseMayaCompatibleNames(mayaCompatibleNaming); return exportSettings; } } }