This commit is contained in:
2025-08-19 17:26:12 +02:00
parent b8f6b624ca
commit 426dd356c6
65 changed files with 13126 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7844f4d67c65f134e941813c38650c07
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b8e069023e8a06b498c07049662f1c63
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4731a204ecb13a942aad1252a74b8f71
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,989 @@
using System.Collections.Generic;
using UnityEngine;
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Presets;
#endif
using System.Linq;
using UnityEditor;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using UnityEditor.Timeline;
namespace DaddyFrosty.Fbx
{
public abstract class ExportOptionsEditorWindow : EditorWindow
{
internal const string DefaultWindowTitle = "Export Options";
protected const float SelectableLabelMinWidth = 120;
protected const float BrowseButtonWidth = 25;
protected const float LabelWidth = 175;
protected const float FieldOffset = 18;
protected const float TextFieldAlignOffset = 3;
protected const float ExportButtonWidth = 100;
protected const float FbxExtOffset = -7;
protected virtual float MinWindowHeight { get { return 300; } }
protected virtual string ExportButtonName { get { return "Export"; } }
protected virtual GUIContent WindowTitle { get { return new GUIContent(DefaultWindowTitle); } }
private string m_exportFileName = "";
protected string ExportFileName
{
get { return m_exportFileName; }
set { m_exportFileName = value; }
}
private UnityEditor.Editor m_innerEditor;
protected UnityEditor.Editor InnerEditor
{
get { return m_innerEditor; }
set { m_innerEditor = value; }
}
#if UNITY_2018_1_OR_NEWER
private FbxExportPresetSelectorReceiver m_receiver;
protected FbxExportPresetSelectorReceiver Receiver
{
get { return m_receiver; }
set { m_receiver = value; }
}
#endif
private static GUIContent presetIcon { get { return EditorGUIUtility.IconContent("Preset.Context"); } }
private static GUIStyle presetIconButton { get { return new GUIStyle("IconButton"); } }
private bool m_showOptions;
private GUIStyle m_nameTextFieldStyle;
protected GUIStyle NameTextFieldStyle
{
get
{
if (m_nameTextFieldStyle == null)
{
m_nameTextFieldStyle = new GUIStyle(GUIStyle.none);
m_nameTextFieldStyle.alignment = TextAnchor.MiddleCenter;
m_nameTextFieldStyle.clipping = TextClipping.Clip;
m_nameTextFieldStyle.normal.textColor = EditorStyles.textField.normal.textColor;
}
return m_nameTextFieldStyle;
}
set { m_nameTextFieldStyle = value; }
}
private GUIStyle m_fbxExtLabelStyle;
protected GUIStyle FbxExtLabelStyle
{
get
{
if (m_fbxExtLabelStyle == null)
{
m_fbxExtLabelStyle = new GUIStyle(GUIStyle.none);
m_fbxExtLabelStyle.alignment = TextAnchor.MiddleLeft;
m_fbxExtLabelStyle.richText = true;
m_fbxExtLabelStyle.contentOffset = new Vector2(FbxExtOffset, 0);
}
return m_fbxExtLabelStyle;
}
set { m_fbxExtLabelStyle = value; }
}
private float m_fbxExtLabelWidth = -1;
protected float FbxExtLabelWidth
{
get
{
if (m_fbxExtLabelWidth < 0)
{
m_fbxExtLabelWidth = FbxExtLabelStyle.CalcSize(new GUIContent(".fbx")).x;
}
return m_fbxExtLabelWidth;
}
set { m_fbxExtLabelWidth = value; }
}
protected abstract bool DisableTransferAnim { get; }
protected abstract bool DisableNameSelection { get; }
protected abstract ExportOptionsSettingsSerializeBase SettingsObject { get; }
// Helper functions for persisting the Export Settings for the session
protected abstract string SessionStoragePrefix { get; }
public const string k_SessionSettingsName = "Settings";
public const string k_SessionFbxPathsName = "FbxSavePath";
public const string k_SessionSelectedFbxPathName = "SelectedFbxPath";
public const string k_SessionPrefabPathsName = "PrefabSavePath";
public const string k_SessionSelectedPrefabPathName = "SelectedPrefabPath";
protected void StorePathsInSession(string varName, List<string> paths)
{
if (paths == null)
{
return;
}
var n = paths.Count;
SessionState.SetInt(string.Format(SessionStoragePrefix, varName), n);
for (int i = 0; i < n; i++)
{
SessionState.SetString(string.Format(SessionStoragePrefix + "_{1}", varName, i), paths[i]);
}
}
protected void RestorePathsFromSession(string varName, List<string> defaultsPaths, out List<string> paths)
{
var n = SessionState.GetInt(string.Format(SessionStoragePrefix, varName), 0);
if (n <= 0)
{
paths = defaultsPaths;
return;
}
paths = new List<string>();
for (var i = 0; i < n; i++)
{
var path = SessionState.GetString(string.Format(SessionStoragePrefix + "_{1}", varName, i), null);
if (!string.IsNullOrEmpty(path))
{
paths.Add(path);
}
}
}
protected static void ClearPathsFromSession(string varName, string prefix)
{
var n = SessionState.GetInt(string.Format(prefix, varName), 0);
SessionState.EraseInt(string.Format(prefix, varName));
for (int i = 0; i < n; i++)
{
SessionState.EraseString(string.Format(prefix + "_{1}", varName, i));
}
}
protected virtual void StoreSettingsInSession()
{
var settings = SettingsObject;
var json = EditorJsonUtility.ToJson(settings);
SessionState.SetString(string.Format(SessionStoragePrefix, k_SessionSettingsName), json);
StorePathsInSession(k_SessionFbxPathsName, m_fbxSavePaths);
SessionState.SetInt(string.Format(SessionStoragePrefix, k_SessionSelectedFbxPathName), SelectedFbxPath);
}
protected virtual void RestoreSettingsFromSession(ExportOptionsSettingsSerializeBase defaults)
{
var settings = SettingsObject;
var json = SessionState.GetString(string.Format(SessionStoragePrefix, k_SessionSettingsName), EditorJsonUtility.ToJson(defaults));
if (!string.IsNullOrEmpty(json))
{
EditorJsonUtility.FromJsonOverwrite(json, settings);
}
}
public static void ResetAllSessionSettings(string prefix, string settingsDefaults = null)
{
SessionState.EraseString(string.Format(prefix, k_SessionSettingsName));
// Set the defaults of the settings.
// If there exists a Default Preset for the Convert/Export settings, then if the project settings are modified,
// the Default Preset will be reloaded instead of the project settings. Therefore, set them explicitely if projects settings desired.
if (!string.IsNullOrEmpty(settingsDefaults))
{
SessionState.SetString(string.Format(prefix, k_SessionSettingsName), settingsDefaults);
}
ClearPathsFromSession(k_SessionFbxPathsName, prefix);
SessionState.EraseInt(string.Format(prefix, k_SessionSelectedFbxPathName));
ClearPathsFromSession(k_SessionPrefabPathsName, prefix);
SessionState.EraseInt(string.Format(prefix, k_SessionSelectedPrefabPathName));
}
public virtual void ResetSessionSettings(string settingsDefaults = null)
{
ResetAllSessionSettings(SessionStoragePrefix, settingsDefaults);
m_fbxSavePaths = null;
SelectedFbxPath = 0;
}
private List<string> m_fbxSavePaths;
internal List<string> FbxSavePaths
{
get
{
if (m_fbxSavePaths == null)
{
// Try to restore from session, fall back to Fbx Export Settings
RestorePathsFromSession(k_SessionFbxPathsName, ExportSettings.instance.GetCopyOfFbxSavePaths(), out m_fbxSavePaths);
SelectedFbxPath = SessionState.GetInt(string.Format(SessionStoragePrefix, k_SessionSelectedFbxPathName), ExportSettings.instance.SelectedFbxPath);
}
return m_fbxSavePaths;
}
}
[SerializeField]
private int m_selectedFbxPath = 0;
internal int SelectedFbxPath
{
get { return m_selectedFbxPath; }
set { m_selectedFbxPath = value; }
}
/// <summary>
/// Caches the result of SelectionContainsPrefabInstanceWithAddedObjects() as it
/// only needs to be updated when ToExport is modified.
/// </summary>
private bool m_exportSetContainsPrefabInstanceWithAddedObjects;
private Object[] m_toExport;
protected Object[] ToExport
{
get
{
return m_toExport;
}
set
{
m_toExport = value;
m_exportSetContainsPrefabInstanceWithAddedObjects = SelectionContainsPrefabInstanceWithAddedObjects();
}
}
protected virtual void OnEnable()
{
#if UNITY_2018_1_OR_NEWER
InitializeReceiver();
#endif
m_showOptions = true;
this.minSize = new Vector2(SelectableLabelMinWidth + LabelWidth + BrowseButtonWidth + ExportButtonWidth, MinWindowHeight);
}
protected static T CreateWindow<T>() where T : EditorWindow
{
return (T)EditorWindow.GetWindow<T>(DefaultWindowTitle, focus: true);
}
protected virtual void InitializeWindow(string filename = "")
{
this.titleContent = WindowTitle;
this.SetFilename(filename);
}
#if UNITY_2018_1_OR_NEWER
protected void InitializeReceiver()
{
if (!Receiver)
{
Receiver = ScriptableObject.CreateInstance<FbxExportPresetSelectorReceiver>() as FbxExportPresetSelectorReceiver;
Receiver.SelectionChanged -= OnPresetSelectionChanged;
Receiver.SelectionChanged += OnPresetSelectionChanged;
Receiver.DialogClosed -= SaveExportSettings;
Receiver.DialogClosed += SaveExportSettings;
}
}
#endif
internal void SetFilename(string filename)
{
// remove .fbx from end of filename
int extIndex = filename.LastIndexOf(".fbx");
if (extIndex < 0)
{
ExportFileName = filename;
return;
}
ExportFileName = filename.Remove(extIndex);
}
public abstract void SaveExportSettings();
public void OnPresetSelectionChanged()
{
this.Repaint();
}
protected bool SelectionContainsPrefabInstanceWithAddedObjects()
{
var exportSet = ToExport;
// FBX-60 (fogbug 1307749):
// On Linux OnGUI() sometimes gets called a few times before
// the export set is set and window.show() is called.
// This leads to this function being called from OnGUI() with a
// null or empty export set, and an ArgumentNullException when
// creating the stack.
// Check that the set exists and has values before creating the stack.
if (exportSet == null || exportSet.Length <= 0)
{
return false;
}
Stack<Object> stack = new Stack<Object>(exportSet);
while (stack.Count > 0)
{
var go = ModelExporter.GetGameObject(stack.Pop());
if (!go)
{
continue;
}
if (PrefabUtility.IsAnyPrefabInstanceRoot(go) && PrefabUtility.GetAddedGameObjects(go).Count > 0)
{
return true;
}
foreach (Transform child in go.transform)
{
stack.Push(child.gameObject);
}
}
return false;
}
protected abstract bool Export();
/// <summary>
/// Function to be used by derived classes to add custom UI between the file path selector and export options.
/// </summary>
protected virtual void CreateCustomUI() {}
#if UNITY_2018_1_OR_NEWER
protected abstract void ShowPresetReceiver();
protected void ShowPresetReceiver(UnityEngine.Object target)
{
InitializeReceiver();
Receiver.SetTarget(target);
Receiver.SetInitialValue(new Preset(target));
UnityEditor.Presets.PresetSelector.ShowSelector(target, null, true, Receiver);
}
#endif
protected Transform TransferAnimationSource
{
get
{
return SettingsObject.AnimationSource;
}
set
{
if (!TransferAnimationSourceIsValid(value))
{
return;
}
SettingsObject.SetAnimationSource(value);
}
}
protected Transform TransferAnimationDest
{
get
{
return SettingsObject.AnimationDest;
}
set
{
if (!TransferAnimationDestIsValid(value))
{
return;
}
SettingsObject.SetAnimationDest(value);
}
}
//-------Helper functions for determining if Animation source and dest are valid---------
/// <summary>
/// Determines whether p is an ancestor to t.
/// </summary>
/// <returns><c>true</c> if p is ancestor to t; otherwise, <c>false</c>.</returns>
/// <param name="p">P.</param>
/// <param name="t">T.</param>
protected bool IsAncestor(Transform p, Transform t)
{
var curr = t;
while (curr != null)
{
if (curr == p)
{
return true;
}
curr = curr.parent;
}
return false;
}
/// <summary>
/// Determines whether t1 and t2 are in the same hierarchy.
/// </summary>
/// <returns><c>true</c> if t1 is in same hierarchy as t2; otherwise, <c>false</c>.</returns>
/// <param name="t1">T1.</param>
/// <param name="t2">T2.</param>
protected bool IsInSameHierarchy(Transform t1, Transform t2)
{
return (IsAncestor(t1, t2) || IsAncestor(t2, t1));
}
protected GameObject m_firstGameObjectToExport;
protected virtual GameObject FirstGameObjectToExport
{
get
{
if (!m_firstGameObjectToExport)
{
if (ToExport == null || ToExport.Length == 0)
{
return null;
}
m_firstGameObjectToExport = ModelExporter.GetGameObject(ToExport[0]);
}
return m_firstGameObjectToExport;
}
}
protected bool TransferAnimationSourceIsValid(Transform newValue)
{
if (!newValue)
{
return true;
}
var selectedGO = FirstGameObjectToExport;
if (!selectedGO)
{
Debug.LogWarning("FbxExportSettings: no Objects selected for export, can't transfer animation");
return false;
}
// source must be ancestor to dest
if (TransferAnimationDest && !IsAncestor(newValue, TransferAnimationDest))
{
Debug.LogWarningFormat("FbxExportSettings: Source {0} must be an ancestor of {1}", newValue.name, TransferAnimationDest.name);
return false;
}
// must be in same hierarchy as selected GO
if (!selectedGO || !IsInSameHierarchy(newValue, selectedGO.transform))
{
Debug.LogWarningFormat("FbxExportSettings: Source {0} must be in the same hierarchy as {1}", newValue.name, selectedGO ? selectedGO.name : "the selected object");
return false;
}
return true;
}
protected bool TransferAnimationDestIsValid(Transform newValue)
{
if (!newValue)
{
return true;
}
var selectedGO = FirstGameObjectToExport;
if (!selectedGO)
{
Debug.LogWarning("FbxExportSettings: no Objects selected for export, can't transfer animation");
return false;
}
// source must be ancestor to dest
if (TransferAnimationSource && !IsAncestor(TransferAnimationSource, newValue))
{
Debug.LogWarningFormat("FbxExportSettings: Destination {0} must be a descendant of {1}", newValue.name, TransferAnimationSource.name);
return false;
}
// must be in same hierarchy as selected GO
if (!selectedGO || !IsInSameHierarchy(newValue, selectedGO.transform))
{
Debug.LogWarningFormat("FbxExportSettings: Destination {0} must be in the same hierarchy as {1}", newValue.name, selectedGO ? selectedGO.name : "the selected object");
return false;
}
return true;
}
/// <summary>
/// Add UI to turn the dialog off next time the user exports
/// </summary>
protected virtual void DoNotShowDialogUI()
{
EditorGUI.indentLevel--;
ExportSettings.instance.DisplayOptionsWindow = !EditorGUILayout.Toggle(
new GUIContent("Don't ask me again", "Don't ask me again, use the last used paths and options instead"),
!ExportSettings.instance.DisplayOptionsWindow
);
}
// -------------------------------------------------------------------------------------
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.SaveFolderPanel(
"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))
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 (m_exportSetContainsPrefabInstanceWithAddedObjects)
{
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>
/// Checks whether the file exists and if it does then asks if it should be overwritten.
/// </summary>
/// <returns><c>true</c>, if file should be overwritten, <c>false</c> otherwise.</returns>
/// <param name="filePath">File path.</param>
protected bool OverwriteExistingFile(string filePath)
{
// check if file already exists, give a warning if it does
if (System.IO.File.Exists(filePath))
{
bool overwrite = UnityEditor.EditorUtility.DisplayDialog(
string.Format("{0} Warning", ModelExporter.PACKAGE_UI_NAME),
string.Format("File {0} already exists.\nOverwrite cannot be undone.", filePath),
"Overwrite", "Cancel");
if (!overwrite)
{
if (GUI.changed)
{
SaveExportSettings();
}
return false;
}
}
return true;
}
}
internal class ExportModelEditorWindow : ExportOptionsEditorWindow
{
public const string k_SessionStoragePrefix = "FbxExporterOptions_DaddyFrosty_{0}";
protected override string SessionStoragePrefix => k_SessionStoragePrefix;
protected override float MinWindowHeight { get { return 310; } } // determined by trial and error
protected override bool DisableNameSelection
{
get
{
return false;
}
}
protected override GameObject FirstGameObjectToExport
{
get
{
if (!m_firstGameObjectToExport)
{
if (IsTimelineAnim)
{
m_firstGameObjectToExport = AnimationOnlyExportData.GetGameObjectAndAnimationClip(TimelineClipToExport).Key;
}
else if (ToExport != null && ToExport.Length > 0)
{
m_firstGameObjectToExport = ModelExporter.GetGameObject(ToExport[0]);
}
}
return m_firstGameObjectToExport;
}
}
protected override bool DisableTransferAnim
{
get
{
// don't transfer animation if we are exporting more than one hierarchy, the timeline clips from
// a playable director, or if only the model is being exported
// if we are on the timeline then export length can be more than 1
return SettingsObject.ModelAnimIncludeOption == Include.Model || (!IsTimelineAnim && (ToExport == null || ToExport.Length != 1));
}
}
protected TimelineClip TimelineClipToExport { get; set; }
protected PlayableDirector PlayableDirector { get; set; }
private bool m_isTimelineAnim = false;
protected bool IsTimelineAnim
{
get { return m_isTimelineAnim; }
set
{
m_isTimelineAnim = value;
if (m_isTimelineAnim)
{
m_previousInclude = ExportModelSettingsInstance.info.ModelAnimIncludeOption;
ExportModelSettingsInstance.info.SetModelAnimIncludeOption(Include.Anim);
}
if (InnerEditor)
{
var exportModelSettingsEditor = InnerEditor as ExportModelSettingsEditor;
if (exportModelSettingsEditor)
{
exportModelSettingsEditor.DisableIncludeDropdown(m_isTimelineAnim);
}
}
}
}
private bool m_singleHierarchyExport = true;
protected bool SingleHierarchyExport
{
get { return m_singleHierarchyExport; }
set
{
m_singleHierarchyExport = value;
if (InnerEditor)
{
var exportModelSettingsEditor = InnerEditor as ExportModelSettingsEditor;
if (exportModelSettingsEditor)
{
exportModelSettingsEditor.SetIsSingleHierarchy(m_singleHierarchyExport);
}
}
}
}
public override void ResetSessionSettings(string defaultSettings = null)
{
base.ResetSessionSettings(defaultSettings);
// save the source and dest as these are not serialized
var source = m_exportModelSettingsInstance.info.AnimationSource;
var dest = m_exportModelSettingsInstance.info.AnimationDest;
m_exportModelSettingsInstance = null;
ExportModelSettingsInstance.info.SetAnimationSource(source);
ExportModelSettingsInstance.info.SetAnimationDest(dest);
InnerEditor = Editor.CreateEditor(ExportModelSettingsInstance);
}
private ExportModelSettings m_exportModelSettingsInstance;
public ExportModelSettings ExportModelSettingsInstance
{
get
{
if (m_exportModelSettingsInstance == null)
{
// make a copy of the settings
m_exportModelSettingsInstance = ScriptableObject.CreateInstance(typeof(ExportModelSettings)) as ExportModelSettings;
// load settings stored in Unity session, default to DefaultPreset, if none then Export Settings
var defaultPresets = Preset.GetDefaultPresetsForObject(m_exportModelSettingsInstance);
if (defaultPresets.Length <= 0)
{
RestoreSettingsFromSession(ExportSettings.instance.ExportModelSettings.info);
}
else
{
// apply the first default preset
// TODO: figure out what it means to have multiple default presets, when would they be applied?
defaultPresets[0].ApplyTo(m_exportModelSettingsInstance);
RestoreSettingsFromSession(m_exportModelSettingsInstance.info);
}
}
return m_exportModelSettingsInstance;
}
}
public override void SaveExportSettings()
{
// check if the settings are different from what is in the Project Settings and only store
// if they are. Otherwise we want to keep them updated with changes to the Project Settings.
bool settingsChanged = !(ExportModelSettingsInstance.Equals(ExportSettings.instance.ExportModelSettings));
var projectSettingsPaths = ExportSettings.instance.GetCopyOfFbxSavePaths();
settingsChanged |= !projectSettingsPaths.SequenceEqual(FbxSavePaths);
settingsChanged |= SelectedFbxPath != ExportSettings.instance.SelectedFbxPath;
if (settingsChanged)
{
StoreSettingsInSession();
}
}
protected override ExportOptionsSettingsSerializeBase SettingsObject => ExportModelSettingsInstance.info;
private Include m_previousInclude = Include.ModelAndAnim;
public static ExportModelEditorWindow Init(IEnumerable<UnityEngine.Object> toExport, string filename = "", TimelineClip timelineClip = null, PlayableDirector director = null)
{
ExportModelEditorWindow window = CreateWindow<ExportModelEditorWindow>();
window.IsTimelineAnim = (timelineClip != null);
window.TimelineClipToExport = timelineClip;
window.PlayableDirector = director ? director : TimelineEditor.inspectedDirector;
int numObjects = window.SetGameObjectsToExport(toExport);
if (string.IsNullOrEmpty(filename))
{
filename = window.DefaultFilename;
}
window.InitializeWindow(filename);
window.SingleHierarchyExport = (numObjects == 1);
window.Show();
return window;
}
protected int SetGameObjectsToExport(IEnumerable<UnityEngine.Object> toExport)
{
ToExport = toExport?.ToArray();
if (!IsTimelineAnim && (ToExport == null || ToExport.Length == 0)) return 0;
TransferAnimationSource = null;
TransferAnimationDest = null;
// if only one object selected, set transfer source/dest to this object
if (IsTimelineAnim || (ToExport != null && ToExport.Length == 1))
{
GameObject go = FirstGameObjectToExport;
if (go)
{
TransferAnimationSource = go.transform;
TransferAnimationDest = go.transform;
}
}
return IsTimelineAnim ? 1 : ToExport.Length;
}
/// <summary>
/// Gets the filename from objects to export.
/// </summary>
/// <returns>The object's name if one object selected, "Untitled" if multiple
/// objects selected for export.</returns>
protected string DefaultFilename
{
get
{
string filename;
if (ToExport.Length == 1)
{
filename = ToExport[0].name;
}
else
{
filename = "Untitled";
}
return filename;
}
}
protected override void OnEnable()
{
base.OnEnable();
if (!InnerEditor)
{
InnerEditor = UnityEditor.Editor.CreateEditor(ExportModelSettingsInstance);
this.SingleHierarchyExport = m_singleHierarchyExport;
this.IsTimelineAnim = m_isTimelineAnim;
}
}
protected void OnDisable()
{
RestoreSettings();
}
/// <summary>
/// Restore changed export settings after export
/// </summary>
protected virtual void RestoreSettings()
{
if (IsTimelineAnim)
{
ExportModelSettingsInstance.info.SetModelAnimIncludeOption(m_previousInclude);
}
}
protected override bool Export()
{
if (string.IsNullOrEmpty(ExportFileName))
{
Debug.LogError("FbxExporter: Please specify an fbx filename");
return false;
}
var folderPath = ExportSettings.GetAbsoluteSavePath(FbxSavePaths[SelectedFbxPath]);
var filePath = System.IO.Path.Combine(folderPath, ExportFileName + ".fbx");
if (!OverwriteExistingFile(filePath))
{
return false;
}
string exportResult;
if (IsTimelineAnim)
{
exportResult = ModelExporter.ExportTimelineClip(filePath, TimelineClipToExport, PlayableDirector, SettingsObject);
}
else
{
exportResult = ModelExporter.ExportObjects(filePath, ToExport, SettingsObject);
}
if (!string.IsNullOrEmpty(exportResult))
{
// refresh the asset database so that the file appears in the
// asset folder view.
AssetDatabase.Refresh();
}
return true;
}
#if UNITY_2018_1_OR_NEWER
protected override void ShowPresetReceiver()
{
ShowPresetReceiver(ExportModelSettingsInstance);
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 954c3dda5c0398a4292e34227b6cebad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,696 @@
using DaddyFrosty.Fbx.Misc;
using UnityEditor;
using UnityEngine;
namespace DaddyFrosty.Fbx
{
[CustomEditor(typeof(ExportModelSettings))]
public 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();
// CUSTOM SETTINGS.
CustomFBX.AddSettings( exportSettings, GUILayout.Width( LabelWidth - FieldOffset ) );
// GUILayout.BeginHorizontal();
// EditorGUILayout.LabelField( new GUIContent( "Use Maya Cordinate Conversion",
// "If enabled axis will be properly converted." ), GUILayout.Width( LabelWidth - FieldOffset ) );
// exportSettings.SetConvertCordinateSpace( EditorGUILayout.Toggle( exportSettings.ConvertCordinateSpace ) );
// GUILayout.EndHorizontal();
}
}
/// <summary>
/// Interface of export options that you can set when exporting to FBX.
/// </summary>
public interface IExportOptions
{
/// <summary>
/// The export format (binary or ascii).
/// </summary>
ExportFormat ExportFormat { get; }
/// <summary>
/// Option to export the model only, the animation only, or both the model and the animation.
/// </summary>
Include ModelAnimIncludeOption { get; }
/// <summary>
/// The type of LOD to export (All, Highest or Lowest).
/// </summary>
LODExportType LODExportType { get; }
/// <summary>
/// The position to export the object to (Local centered, World absolute, or Reset). Use Reset for converting to a Prefab.
/// </summary>
ObjectPosition ObjectPosition { get; }
/// <summary>
/// Option to export the animation on GameObjects that have a skinned mesh.
/// </summary>
bool AnimateSkinnedMesh { get; }
/// <summary>
/// Option to convert the GameObject and material names to Maya compatible names.
/// </summary>
bool UseMayaCompatibleNames { get; }
/// <summary>
/// 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.
/// </summary>
bool AllowSceneModification { get; }
/// <summary>
/// Option to export GameObjects that don't have a renderer.
/// </summary>
bool ExportUnrendered { get; }
/// <summary>
/// Option to preserve the previous import settings after the export when overwriting an existing FBX file.
/// </summary>
bool PreserveImportSettings { get; }
/// <summary>
/// Option to keep multiple instances of the same mesh as separate instances on export.
/// </summary>
bool KeepInstances { get; }
/// <summary>
/// Option to embed textures in the exported FBX file.
/// </summary>
/// <remarks>
/// To embed textures, you must set the file ExportFormat to binary.
/// </remarks>
bool EmbedTextures { get; }
/// <summary>
/// The transform to transfer the animation from. The animation is transferred to AnimationDest.
/// </summary>
/// <remarks>
/// Transform must be an ancestor of AnimationDest, and may be an ancestor of the selected GameObject.
/// </remarks>
Transform AnimationSource { get; }
/// <summary>
/// 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.
/// </summary>
Transform AnimationDest { get; }
// Custom
bool ConvertCordinateSpace { get; }
float UnitScaleFactor { get; }
}
public abstract class ExportOptionsSettingsBase<T> : 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<T>;
if (expOptions == null)
{
return false;
}
return this.info.Equals(expOptions.info);
}
public override int GetHashCode()
{
return this.info.GetHashCode();
}
}
public class ExportModelSettings : ExportOptionsSettingsBase<ExportModelSettingsSerialize>
{}
/// <summary>
/// Base class for the export model settings and convert to prefab settings.
/// </summary>
[System.Serializable]
public 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;
// CUSTOM START ======================
[SerializeField] private bool convertCordinateSpace = true;
public void SetConvertCordinateSpace( bool v ) => convertCordinateSpace = v;
public bool ConvertCordinateSpace => convertCordinateSpace;
[SerializeField] private float unitScaleFactor = 100f;
public void SetUnitScaleFactor( float v ) => unitScaleFactor = v;
public float UnitScaleFactor => unitScaleFactor;
// CUSTOM END ========================
/// <inheritdoc/>
public ExportFormat ExportFormat { get { return exportFormat; } }
/// <summary>
/// Specifies the export format to binary or ascii.
/// </summary>
/// <param name="format">Binary or ASCII</param>
public void SetExportFormat(ExportFormat format) { this.exportFormat = format; }
/// <inheritdoc/>
public bool AnimateSkinnedMesh { get { return animatedSkinnedMesh; } }
/// <summary>
/// Specifies whether to export animation on GameObjects containing a skinned mesh.
/// </summary>
/// <param name="animatedSkinnedMesh">True to export animation on skinned meshes, false otherwise.</param>
public void SetAnimatedSkinnedMesh(bool animatedSkinnedMesh) { this.animatedSkinnedMesh = animatedSkinnedMesh; }
/// <inheritdoc/>
public bool UseMayaCompatibleNames { get { return mayaCompatibleNaming; } }
/// <summary>
/// Specifies whether to rename the exported GameObjects to Maya compatible names.
/// </summary>
/// <param name="useMayaCompNames">True to have export Maya compatible names, false otherwise.</param>
public void SetUseMayaCompatibleNames(bool useMayaCompNames) { this.mayaCompatibleNaming = useMayaCompNames; }
/// <inheritdoc/>
public Transform AnimationSource { get { return animSource; } }
/// <summary>
/// Specifies the transform to transfer the animation from.
/// </summary>
/// <param name="source">The transform to transfer the animation from.</param>
public void SetAnimationSource(Transform source) { this.animSource = source; }
/// <inheritdoc/>
public Transform AnimationDest { get { return animDest; } }
/// <summary>
/// Specifies the transform to transfer the source animation to.
/// </summary>
/// <param name="dest">The transform to transfer the animation to.</param>
public void SetAnimationDest(Transform dest) { this.animDest = dest; }
/// <inheritdoc/>
public abstract Include ModelAnimIncludeOption { get; }
/// <inheritdoc/>
public abstract LODExportType LODExportType { get; }
/// <inheritdoc/>
public abstract ObjectPosition ObjectPosition { get; }
/// <inheritdoc/>
public abstract bool ExportUnrendered { get; }
/// <inheritdoc/>
public virtual bool PreserveImportSettings { get { return false; } }
/// <inheritdoc/>
public abstract bool AllowSceneModification { get; }
/// <inheritdoc/>
public virtual bool KeepInstances { get { return true; } }
/// <inheritdoc/>
public virtual bool EmbedTextures { get { return false; } }
/// <summary>
/// Checks if two instances of the export settings are equal.
/// </summary>
/// <param name="e">The other export setting object to check.</param>
/// <returns>True if equal, false otherwise.</returns>
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
// Custom
&& convertCordinateSpace == expOptions.convertCordinateSpace
&& unitScaleFactor == expOptions.UnitScaleFactor;
}
/// <summary>
/// Gets the hash code for this instance of the export model settings.
/// </summary>
/// <returns>Unique hash code for the export model settings.</returns>
public override int GetHashCode()
{
return (animatedSkinnedMesh ? 1 : 0) | ((mayaCompatibleNaming ? 1 : 0) << 1) | ((int)exportFormat << 2)
// Custom
| ( ( convertCordinateSpace ? 1 : 0 ) << 3 )
| unitScaleFactor.GetHashCode();
}
}
/// <summary>
/// Class specifying the settings for exporting to FBX.
/// </summary>
[System.Serializable]
public 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;
/// <inheritdoc/>
public override Include ModelAnimIncludeOption { get { return include; } }
/// <summary>
/// Specifies to export the model only, the animation only, or both the model and the animation.
/// </summary>
/// <param name="include">Model, animation, or model and animation</param>
public void SetModelAnimIncludeOption(Include include) { this.include = include; }
/// <inheritdoc/>
public override LODExportType LODExportType { get { return lodLevel; } }
/// <summary>
/// Specifies the type of LOD to export (All, Highest or Lowest).
/// </summary>
/// <param name="lodLevel">All, Highest, or Lowest</param>
public void SetLODExportType(LODExportType lodLevel) { this.lodLevel = lodLevel; }
/// <inheritdoc/>
public override ObjectPosition ObjectPosition { get { return objectPosition; } }
/// <summary>
/// Specifies the position to export the object to (Local centered, World absolute, or Reset). Use Reset for converting to a Prefab).
/// </summary>
/// <param name="objectPosition">Local centered, World absolute, or Reset</param>
public void SetObjectPosition(ObjectPosition objectPosition) { this.objectPosition = objectPosition; }
/// <inheritdoc/>
public override bool ExportUnrendered { get { return exportUnrendered; } }
/// <summary>
/// Specifies whether to export GameObjects that don't have a renderer.
/// </summary>
/// <param name="exportUnrendered">True to export unrendered, false otherwise.</param>
public void SetExportUnrendered(bool exportUnrendered) { this.exportUnrendered = exportUnrendered; }
/// <inheritdoc/>
public override bool PreserveImportSettings { get { return preserveImportSettings; } }
/// <summary>
/// Specifies whether to preserve the previous import settings after the export when overwriting
/// an existing FBX file.
/// </summary>
/// <param name="preserveImportSettings">True to preserve the previous import settings, false otherwise.</param>
public void SetPreserveImportSettings(bool preserveImportSettings) { this.preserveImportSettings = preserveImportSettings; }
/// <inheritdoc/>
public override bool AllowSceneModification { get { return false; } }
/// <inheritdoc/>
public override bool KeepInstances { get { return keepInstances; } }
/// <summary>
/// Specifies whether to keep multiple instances of the same mesh as separate instances on export.
/// </summary>
/// <param name="keepInstances">True to export as separate instances, false otherwise.</param>
public void SetKeepInstances(bool keepInstances) { this.keepInstances = keepInstances; }
/// <inheritdoc/>
public override bool EmbedTextures { get { return embedTextures; } }
/// <summary>
/// Specifies whether to embed textures in the exported FBX file.
/// </summary>
/// <param name="embedTextures">True to embed textures, false otherwise.</param>
public void SetEmbedTextures(bool embedTextures) { this.embedTextures = embedTextures; }
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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;
}
}
// /// <summary>
// /// Class specifying the settings for exporting to FBX.
// /// </summary>
// [System.Serializable]
// public class ExportModelOptions : IExportOptions
// {
// [SerializeField]
// private ExportFormat exportFormat = ExportFormat.ASCII;
// [SerializeField]
// private bool animatedSkinnedMesh = false;
// [SerializeField]
// private bool mayaCompatibleNaming = true;
//
// // CUSTOM START
// [SerializeField] private bool convertCordinateSpace = true;
// public void SetConvertCordinateSpace( bool v ) => convertCordinateSpace = v;
// public bool ConvertCordinateSpace => convertCordinateSpace;
//
// [SerializeField] private float unitScaleFactor = 100f;
// public void SetUnitScaleFactor( float v ) => unitScaleFactor = v;
//
// public float UnitScaleFactor => unitScaleFactor;
// // CUSTOM END
//
// [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;
//
// /// <summary>
// /// The export format (binary or ascii).
// /// </summary>
// public ExportFormat ExportFormat
// {
// get { return exportFormat; }
// set { exportFormat = value; }
// }
//
// /// <summary>
// /// Option to export the model only, the animation only, or both the model and the animation.
// /// </summary>
// public Include ModelAnimIncludeOption
// {
// get { return include; }
// set { include = value; }
// }
//
// /// <summary>
// /// The type of LOD to export (All, Highest or Lowest).
// /// </summary>
// public LODExportType LODExportType
// {
// get { return lodLevel; }
// set { lodLevel = value; }
// }
//
// /// <summary>
// /// The position to export the object to (Local centered, World absolute, or Reset). Use Reset for converting to a Prefab.
// /// </summary>
// public ObjectPosition ObjectPosition
// {
// get { return objectPosition; }
// set { objectPosition = value; }
// }
//
// /// <summary>
// /// Option to export the animation on GameObjects that have a skinned mesh.
// /// </summary>
// public bool AnimateSkinnedMesh
// {
// get { return animatedSkinnedMesh; }
// set { animatedSkinnedMesh = value; }
// }
//
// /// <summary>
// /// Option to convert the GameObject and material names to Maya compatible names.
// /// </summary>
// public bool UseMayaCompatibleNames
// {
// get { return mayaCompatibleNaming; }
// set { mayaCompatibleNaming = value; }
// }
//
// /// <summary>
// /// 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.
// /// </summary>
// bool IExportOptions.AllowSceneModification
// {
// get { return false; }
// }
//
// /// <summary>
// /// Option to export GameObjects that don't have a renderer.
// /// </summary>
// public bool ExportUnrendered
// {
// get { return exportUnrendered; }
// set { exportUnrendered = value; }
// }
//
// /// <summary>
// /// Option to preserve the previous import settings after the export when overwriting an existing FBX file.
// /// </summary>
// public bool PreserveImportSettings
// {
// get { return preserveImportSettings; }
// set { preserveImportSettings = value; }
// }
//
// /// <summary>
// /// Option to keep multiple instances of the same mesh as separate instances on export.
// /// </summary>
// public bool KeepInstances
// {
// get { return keepInstances; }
// set { keepInstances = value; }
// }
//
// /// <summary>
// /// Option to embed textures in the exported FBX file.
// /// </summary>
// /// <remarks>
// /// To embed textures, you must set the file ExportFormat to binary.
// /// </remarks>
// public bool EmbedTextures
// {
// get { return embedTextures; }
// set { embedTextures = value; }
// }
//
// /// <summary>
// /// The transform to transfer the animation from. The animation is transferred to AnimationDest.
// /// </summary>
// /// <remarks>
// /// Transform must be an ancestor of AnimationDest, and may be an ancestor of the selected GameObject.
// /// </remarks>
// public Transform AnimationSource
// {
// get { return animSource; }
// set { animSource = value; }
// }
//
// /// <summary>
// /// 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.
// /// </summary>
// 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;
// }
// }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 63180e849b84d344d81d213c1e4e944b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76a11434abcb460489b23b95aa26ee71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 849017d7ef6c589448dcbf335cda940f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,210 @@
using UnityEngine;
using UnityEngine.Timeline;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.Playables;
namespace DaddyFrosty.Fbx
{
/// <summary>
/// Export data containing extra information required to export
/// </summary>
internal interface IExportData
{
HashSet<GameObject> Objects { get; }
}
/// <summary>
/// Export data containing what to export when
/// exporting animation only.
/// </summary>
internal class AnimationOnlyExportData : IExportData
{
// map from animation clip to GameObject that has Animation/Animator
// component containing clip
public Dictionary<AnimationClip, GameObject> animationClips;
// set of all GameObjects to export
public HashSet<GameObject> goExportSet;
public HashSet<GameObject> Objects { get { return goExportSet; } }
// map from GameObject to component type to export
public Dictionary<GameObject, System.Type> exportComponent;
// first clip to export
public AnimationClip defaultClip;
public AnimationOnlyExportData(
Dictionary<AnimationClip, GameObject> animClips,
HashSet<GameObject> exportSet,
Dictionary<GameObject, System.Type> exportComponent
)
{
this.animationClips = animClips;
this.goExportSet = exportSet;
this.exportComponent = exportComponent;
this.defaultClip = null;
}
public AnimationOnlyExportData()
{
this.animationClips = new Dictionary<AnimationClip, GameObject>();
this.goExportSet = new HashSet<GameObject>();
this.exportComponent = new Dictionary<GameObject, System.Type>();
this.defaultClip = null;
}
/// <summary>
/// collect all object dependencies for given animation clip
/// </summary>
public void CollectDependencies(
AnimationClip animClip,
GameObject rootObject,
IExportOptions exportOptions
)
{
Debug.Assert(rootObject != null);
Debug.Assert(exportOptions != null);
if (this.animationClips.ContainsKey(animClip))
{
// we have already exported gameobjects for this clip
return;
}
// NOTE: the object (animationRootObject) containing the animation is not necessarily animated
// when driven by an animator or animation component.
this.animationClips.Add(animClip, rootObject);
foreach (EditorCurveBinding uniCurveBinding in AnimationUtility.GetCurveBindings(animClip))
{
Object uniObj = AnimationUtility.GetAnimatedObject(rootObject, uniCurveBinding);
if (!uniObj)
{
continue;
}
GameObject unityGo = ModelExporter.GetGameObject(uniObj);
if (!unityGo)
{
continue;
}
if (!exportOptions.AnimateSkinnedMesh && unityGo.GetComponent<SkinnedMeshRenderer>())
{
continue;
}
// If we have a clip driving a camera or light then force the export of FbxNodeAttribute
// so that they point the right way when imported into Maya.
if (unityGo.GetComponent<Light>())
this.exportComponent[unityGo] = typeof(Light);
else if (unityGo.GetComponent<Camera>())
this.exportComponent[unityGo] = typeof(Camera);
else if ((uniCurveBinding.type == typeof(SkinnedMeshRenderer)) && unityGo.GetComponent<SkinnedMeshRenderer>())
{
// only export mesh if there are animation keys for it (e.g. for blendshapes)
if (FbxPropertyChannelPair.TryGetValue(uniCurveBinding.propertyName, out FbxPropertyChannelPair[] channelPairs))
{
this.exportComponent[unityGo] = typeof(SkinnedMeshRenderer);
}
}
this.goExportSet.Add(unityGo);
}
}
/// <summary>
/// collect all objects dependencies for animation clips.
/// </summary>
public void CollectDependencies(
AnimationClip[] animClips,
GameObject rootObject,
IExportOptions exportOptions
)
{
Debug.Assert(rootObject != null);
Debug.Assert(exportOptions != null);
foreach (var animClip in animClips)
{
CollectDependencies(animClip, rootObject, exportOptions);
}
}
/// <summary>
/// Get the GameObject that the clip is bound to in the timeline.
/// </summary>
/// <param name="timelineClip"></param>
/// <returns>The GameObject bound to the timeline clip or null if none.</returns>
private static GameObject GetGameObjectBoundToTimelineClip(TimelineClip timelineClip, PlayableDirector director = null)
{
object parentTrack = timelineClip.GetParentTrack();
AnimationTrack animTrack = parentTrack as AnimationTrack;
var inspectedDirector = director ? director : UnityEditor.Timeline.TimelineEditor.inspectedDirector;
if (!inspectedDirector)
{
Debug.LogWarning("No Timeline selected in inspector, cannot retrieve GameObject bound to track");
return null;
}
Object animationTrackObject = inspectedDirector.GetGenericBinding(animTrack);
GameObject animationTrackGO = null;
if (animationTrackObject is GameObject)
{
animationTrackGO = animationTrackObject as GameObject;
}
else if (animationTrackObject is Animator)
{
animationTrackGO = (animationTrackObject as Animator).gameObject;
}
if (animationTrackGO == null)
{
Debug.LogErrorFormat("Could not export animation track object of type {0}", animationTrackObject.GetType().Name);
return null;
}
return animationTrackGO;
}
/// <summary>
/// Get the GameObject and it's corresponding animation clip from the given timeline clip.
/// </summary>
/// <param name="timelineClip"></param>
/// <returns>KeyValuePair containing GameObject and corresponding AnimationClip</returns>
public static KeyValuePair<GameObject, AnimationClip> GetGameObjectAndAnimationClip(TimelineClip timelineClip, PlayableDirector director = null)
{
var animationTrackGO = GetGameObjectBoundToTimelineClip(timelineClip, director);
if (!animationTrackGO)
{
return new KeyValuePair<GameObject, AnimationClip>();
}
return new KeyValuePair<GameObject, AnimationClip>(animationTrackGO, timelineClip.animationClip);
}
/// <summary>
/// Get the filename of the format {model}@{anim}.fbx from the given timeline clip
/// </summary>
/// <param name="timelineClip"></param>
/// <returns>filename for use for exporting animation clip</returns>
public static string GetFileName(TimelineClip timelineClip)
{
// if the timeline clip name already contains an @, then take this as the
// filename to avoid duplicate @
if (timelineClip.displayName.Contains("@"))
{
return timelineClip.displayName;
}
var goBound = GetGameObjectBoundToTimelineClip(timelineClip);
if (goBound == null)
{
return timelineClip.displayName;
}
return string.Format("{0}@{1}", goBound.name, timelineClip.displayName);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 020f058b66da64746830aa60391539dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eeebc6a06d294fb494ef7a9697faba57
timeCreated: 1671143407

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cae2b7a32f684525875a7ef9f0edf5ce
timeCreated: 1671154957

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3f227242526649c4a3668a2adfc1e184
timeCreated: 1671162404

View File

@@ -0,0 +1,17 @@
using JetBrains.Annotations;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc.AssetsMenu
{
public class AnimationCover
{
public Animation Animator { get; set; }
public AnimationClip[] Clips { get; set; }
[CanBeNull] public AnimationClip FirstClip { get; set; }
public AnimationCover()
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 94d85be9434e481896f4c461319d54cf
timeCreated: 1671163247

View File

@@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc.AssetsMenu
{
public partial class ExportAnimations
{
// public GameObject GameObject { get; private set; }
// public Animation Animator { get; private set; }
// protected ExportModelSettingsEditor InnerEditor { get; private set; }
private bool Export()
{
var clips = AnimationUtility.GetAnimationClips( Animator.gameObject );
var folderPath = ExportSettings.GetAbsoluteSavePath( FbxSavePaths[SelectedFbxPath] );
var finalPath = $"{folderPath}{Path.DirectorySeparatorChar}{GameObject.name}@";
foreach ( var clip in clips )
{
// Log.Info( $"Clip: {clip.name}, {clip.clip.frameRate}. | {GameObject.name}." );
if ( ExportClip( finalPath, ExportModelSettingsInstance, clip ) )
continue;
Log.Error( $"Export failed: {clip.name} ( {clip} ) | {GameObject.name}." );
}
AssetDatabase.Refresh();
SaveExportSettings();
return true;
}
private bool ExportClip( string finalPath, ExportModelSettings settings, AnimationClip clip )
{
var savePath = finalPath + clip.name + ".fbx";
if ( !OverwriteExistingFile( savePath ) )
return false;
var targetAnimation = new AnimationCover()
{
Animator = Animator,
Clips = new[] { clip },
FirstClip = null
};
var exportResult = ModelExporter.ExportObjects( savePath, new Object[]{ GameObject }, settings.info, targetAnimation: targetAnimation );
if ( string.IsNullOrEmpty( exportResult ) )
{
Log.Error( $"exportResult = null. {finalPath}, {clip}." );
return false;
}
return true;
}
private bool OverwriteExistingFile( string filePath )
{
// check if file already exists, give a warning if it does
if ( _overwriteAll || !System.IO.File.Exists( filePath ) )
return true;
var shouldOverwrite = UnityEditor.EditorUtility.DisplayDialogComplex(
$"{ModelExporter.PACKAGE_UI_NAME} Warning",
$"File {filePath} already exists.\nOverwrite cannot be undone.",
"Overwrite", "Overwrite All", "Cancel" );
if ( shouldOverwrite == 2 )
{
if ( GUI.changed )
SaveExportSettings();
return false;
}
if ( shouldOverwrite == 1 )
_overwriteAll = true;
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 500cbd797b024292aa9e25661f50abb9
timeCreated: 1671160494

View File

@@ -0,0 +1,79 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc.AssetsMenu
{
public partial class ExportAnimations
{
private static void RestorePathsFromSession( string varName, List<string> defaultsPaths, out List<string> paths )
{
var n = SessionState.GetInt( string.Format( ExportModelEditorWindow.k_SessionStoragePrefix, varName ), 0 );
if ( n <= 0 )
{
paths = defaultsPaths;
return;
}
paths = new();
for ( var i = 0; i < n; i++ )
{
var path = SessionState.GetString( string.Format( ExportModelEditorWindow.k_SessionStoragePrefix + "_{1}", varName, i ), null );
if ( !string.IsNullOrEmpty( path ) )
paths.Add( path );
}
}
private const string k_SessionFbxPathsName = "FbxAnimAllPath";
private const string k_SessionSelectedFbxPathName = "SelectedFbxAnimAllPath";
// FBX Paths.
private List<string> m_fbxSavePaths;
public List<string> FbxSavePaths
{
get
{
if ( m_fbxSavePaths == null )
{
// Try to restore from session, fall back to Fbx Export Settings
RestorePathsFromSession( k_SessionFbxPathsName, ExportSettings.instance.GetCopyOfFbxSavePaths(), out m_fbxSavePaths );
SelectedFbxPath = SessionState.GetInt( string.Format( ExportModelEditorWindow.k_SessionStoragePrefix,
k_SessionSelectedFbxPathName ), ExportSettings.instance.SelectedFbxPath );
}
return m_fbxSavePaths;
}
}
[SerializeField] private int _selectedFbxPath = 0;
public int SelectedFbxPath
{
get => _selectedFbxPath;
set => _selectedFbxPath = value;
}
private static void StorePathsInSession( string varName, [CanBeNull] List<string> paths )
{
if ( paths == null )
return;
var n = paths.Count;
SessionState.SetInt( string.Format( SessionStoragePrefix, varName ), n );
for ( var i = 0; i < n; i++ )
SessionState.SetString( string.Format( SessionStoragePrefix + "_{1}", varName, i ), paths[i] );
}
private void SaveExportSettings()
{
var settings = ExportModelSettingsInstance.info;
var json = EditorJsonUtility.ToJson( settings );
SessionState.SetString( string.Format( SessionStoragePrefix, SessionSettingsName ), json );
StorePathsInSession( k_SessionFbxPathsName, m_fbxSavePaths );
SessionState.SetInt( string.Format( SessionStoragePrefix, k_SessionSelectedFbxPathName ), SelectedFbxPath );
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1f6b5c316ea646e5b75b2c473998168c
timeCreated: 1671160149

View File

@@ -0,0 +1,100 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEditor.Presets;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc.AssetsMenu
{
public partial class ExportAnimations
{
private static string SessionStoragePrefix => ExportModelEditorWindow.k_SessionStoragePrefix;
private static string SessionSettingsName => ExportOptionsEditorWindow.k_SessionSettingsName;
private ExportModelSettings _exportModelSettingsInstance;
public ExportModelSettings ExportModelSettingsInstance
{
get
{
if ( _exportModelSettingsInstance != null )
return _exportModelSettingsInstance;
// make a copy of the settings
_exportModelSettingsInstance = CreateInstance( typeof( ExportModelSettings ) ) as ExportModelSettings;
// load settings stored in Unity session, default to DefaultPreset, if none then Export Settings
var defaultPresets = Preset.GetDefaultPresetsForObject( _exportModelSettingsInstance );
if ( defaultPresets.Length <= 0 )
{
RestoreSettingsFromSession( ExportSettings.instance.ExportModelSettings.info );
return _exportModelSettingsInstance;
}
// apply the first default preset
// TODO: figure out what it means to have multiple default presets, when would they be applied?
defaultPresets[0].ApplyTo( _exportModelSettingsInstance );
RestoreSettingsFromSession( _exportModelSettingsInstance!.info );
return _exportModelSettingsInstance;
}
}
private void RestoreSettingsFromSession( ExportOptionsSettingsSerializeBase defaults )
{
var settings = ExportModelSettingsInstance.info;
var key = string.Format( SessionStoragePrefix, SessionSettingsName );
var json = SessionState.GetString( key, EditorJsonUtility.ToJson( defaults ) );
if ( string.IsNullOrEmpty( json ) )
{
return;
}
EditorJsonUtility.FromJsonOverwrite( json, settings );
}
private void AddPathUI()
{
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField( new GUIContent(
"Export Path",
"Location where the FBXs will be saved." ), GUILayout.Width( LabelWidth - FieldOffset ) );
var pathLabels = ExportSettings.GetMixedSavePaths( FbxSavePaths );
SelectedFbxPath = EditorGUILayout.Popup( SelectedFbxPath, pathLabels, GUILayout.MinWidth( SelectableLabelMinWidth ) );
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 ) ) )
{
var initialPath = Application.dataPath;
var fullPath = EditorUtility.SaveFolderPanel(
"Select Export Model Path", initialPath, null
);
// Unless the user canceled, save path.
if ( !string.IsNullOrEmpty( fullPath ) )
{
var relativePath = ExportSettings.ConvertToAssetRelativePath( fullPath );
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;
}
GUIUtility.hotControl = 0;
GUIUtility.keyboardControl = 0;
}
}
GUILayout.EndHorizontal();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 572d5e1936574f0182ffc3896882391d
timeCreated: 1671159589

View File

@@ -0,0 +1,97 @@
using System;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc.AssetsMenu
{
public partial class ExportAnimations : EditorWindow
{
public const string kMenuPrefix = RemoveAllScripts.kMenuPrefix;
public const string kMenuName = kMenuPrefix + "Export Animations";
[MenuItem( kMenuName, true, 100 )]
private static bool CanExportAnimations() => Selection.activeObject is GameObject go && go.TryGetComponent<Animation>( out _ );
[MenuItem( kMenuName, false, 100 )]
private static void ExportAnimation()
{
if ( Selection.activeObject is not GameObject go || !go.TryGetComponent<Animation>( out var animator ) )
return;
var window = CreateWindow<ExportAnimations>();
window.Setup( go, animator );
window.Show();
}
public GameObject GameObject { get; private set; }
public Animation Animator { get; private set; }
protected ExportModelSettingsEditor InnerEditor { get; private set; }
private bool _showOptions;
private bool _overwriteAll;
private void Setup( GameObject go, Animation animator )
{
titleContent = new( "Animation Exporter" );
GameObject = go;
Animator = animator;
}
protected const float SelectableLabelMinWidth = 120;
protected const float BrowseButtonWidth = 25;
protected const float LabelWidth = 175;
protected const float FieldOffset = 18;
protected const float TextFieldAlignOffset = 3;
protected const float ExportButtonWidth = 100;
protected const float FbxExtOffset = -7;
private void OnEnable()
{
_showOptions = true;
_overwriteAll = false;
if ( !InnerEditor )
InnerEditor = Editor.CreateEditor( ExportModelSettingsInstance ) as ExportModelSettingsEditor;
InnerEditor!.DisableIncludeDropdown( true );
ExportModelSettingsInstance.info.SetModelAnimIncludeOption( Include.Anim );
}
private void OnGUI()
{
if ( GameObject == null || Animator == null )
{
return;
}
// Path.
AddPathUI();
// Spacer
GUILayout.BeginVertical();
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
// Show Options.
EditorGUI.indentLevel++;
_showOptions = EditorGUILayout.Foldout( _showOptions, "Options" );
if ( _showOptions )
{
InnerEditor.OnInspectorGUI();
}
EditorGUI.indentLevel--;
// Spacer
GUILayout.BeginVertical();
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
if ( GUILayout.Button( "Cancel", GUILayout.Width( ExportButtonWidth ) ) )
Close();
if ( GUILayout.Button( "Export all", GUILayout.Width( ExportButtonWidth ) ) )
{
if ( Export() )
Close();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fbc05f6a5648492b91c9799cfbf77423
timeCreated: 1671158894

View File

@@ -0,0 +1,72 @@
using UnityEditor;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc.AssetsMenu
{
public class RemoveAllScripts
{
public const string kMenuPrefix = "GameObject/DaddyFrosty/";
public const string kRemoveAllMenu = kMenuPrefix + "Remove All Scripts";
public const string kRemoveAllMenuChildren = kMenuPrefix + "Remove All Scripts ( Traverse Children )";
[MenuItem( kRemoveAllMenu, false, 1 )]
private static void RemoveAllMonoBehvaiours()
{
// var go = Selection.activeObject;
// Log.Info( $"go: {go}, {go?.GetType()} {go?.name}" );
if ( Selection.activeObject is not GameObject go )
return;
var destroyed = RemoveOn( go, false );
Log.Info( $"Destroyed: {destroyed} script(s). With Children: False." );
}
[MenuItem( kRemoveAllMenuChildren, false, 10 )]
private static void RemoveAllMonoBehvaioursChildren()
{
if ( Selection.activeObject is not GameObject go )
return;
var destroyed = RemoveOn( go, true );
Log.Info( $"Destroyed: {destroyed} script(s). With Children: True." );
}
private static int RemoveOn( GameObject go, bool traverseChildren )
{
// var scripts = go.GetComponents<MonoBehaviour>();
// var scripts = go.GetComponents( typeof( MonoBehaviour ) );
// Log.Info( $"Found: {scripts.Length} Script(s) on {go.name}." );
var destroyed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript( go );
// for ( var i = 0; i < scripts.Length; i++ )
// {
// // Log.Info( $"script: {script} | {script?.GetInstanceID()} | {(bool)script} | {script == null}" );
// var script = scripts[i];
// if ( script != null )
// continue;
//
// Log.Info( $"script: {script} | PrefabUtility.IsPartOfPrefabInstance:." );
// Object.DestroyImmediate( script );
// GameObjectUtility.RemoveMonoBehavioursWithMissingScript( go )
// destroyed++;
// }
if ( !traverseChildren )
return destroyed;
foreach ( var child in go.transform )
{
if ( child is not Transform cT )
{
// Log.Info( $"Skipping {child}, {child?.ToString()}" );
continue;
}
// Log.Info( "Found child." );
destroyed += RemoveOn( cT.gameObject, true );
}
return destroyed;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e7d9c7fd478c45cda37a8e4078ab8821
timeCreated: 1671154962

View File

@@ -0,0 +1,46 @@
using UnityEditor;
using UnityEngine;
namespace DaddyFrosty.Fbx.Misc
{
public partial class CustomFBX
{
public static void AddSettings( ExportModelSettingsSerialize options, GUILayoutOption guiWidth )
{
GUILayout.BeginVertical();
GUILayout.FlexibleSpace();
GUILayout.EndVertical();
GUILayout.BeginHorizontal();
GUILayout.Label( "------ Custom ------", guiWidth );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField( new GUIContent( "Verbose",
"Extra debug info." ), guiWidth );
ExportSettings.instance.VerboseProperty = EditorGUILayout.Toggle( ExportSettings.instance.VerboseProperty );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField( new GUIContent( "Use Maya Cordinate Conversion",
"If enabled axis will be properly converted." ), guiWidth );
options.SetConvertCordinateSpace( EditorGUILayout.Toggle( options.ConvertCordinateSpace ) );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField( new GUIContent( "Bake Animation",
"If enabled BakeAnimationProperty will be true." ), guiWidth );
ModelExporter.ExportSettings.BakeAnimationProperty = EditorGUILayout.Toggle( ModelExporter.ExportSettings.BakeAnimationProperty );
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField( new GUIContent( "Unit Scale Factor",
"Scaling applied to export." ), guiWidth );
var val = EditorGUILayout.FloatField( options.UnitScaleFactor );
options.SetUnitScaleFactor( val );
ExportSettings.instance.ExportModelSettings.info.SetUnitScaleFactor( val );
GUILayout.EndHorizontal();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b09b952f5cb40789169ec3d2ffe71da
timeCreated: 1671143548

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 736e57e7cca54e95b0447f83e419380c
timeCreated: 1671142838

View File

@@ -0,0 +1,33 @@
#nullable enable
using UnityEngine;
using UnityEditor;
namespace DaddyFrosty
{
public static class Log
{
public static void Info( object? str )
{
Debug.Log( str );
}
public static void Error( object? str )
{
Debug.LogError( $"[ERROR] {str}" );
}
public static void Warning( object? str )
{
Debug.LogWarning( $"[Warning] {str}" );
}
public static string LineS => "-------------";
public static void Line()
=> Log.Info( LineS );
// "VARIDAIC"
private static string VarInternal( string? varName, object? obj )
=> varName == null ? ( obj?.ToString() ?? string.Empty ) : $"{varName}: {obj}";
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3ff8397ea8124438a6e486833291fe11
timeCreated: 1671142843

View File

@@ -0,0 +1,214 @@
using System;
namespace DaddyFrosty
{
public static class MathX
{
/// <summary>Returns a double between min and max</summary>
public static double Double( this Random self, double min, double max )
{
return min + ( max - min ) * self.NextDouble();
}
/// <summary>Returns a random float between min and max</summary>
public static float Float( this Random self, float min, float max )
{
return min + ( max - min ) * (float)self.NextDouble();
}
/// <summary>Returns a random float between 0 and max (or 1)</summary>
public static float Float( this Random self, float max = 1f )
{
return self.Float( 0.0f, max );
}
/// <summary>Returns a random double between 0 and max (or 1)</summary>
public static double Double( Random self, double max = 1.0 )
{
return self.Double( 0.0, max );
}
/// <summary>Returns a random int between min and max (inclusive)</summary>
public static int Int( this Random self, int min, int max )
{
return self.Next( min, max + 1 );
}
/// <summary>Returns a random int between 0 and max (inclusive)</summary>
public static int Int( this Random self, int max )
{
return self.Int( 0, max );
}
internal const float toDegrees = 57.2958f;
internal const float toRadians = 0.0174533f;
internal const float toGradiansDegrees = 0.9f;
internal const float toGradiansRadians = 0.01570796f;
internal const float toMeters = 0.0254f;
internal const float toInches = 39.37008f;
internal const float toMilimeters = 0.0393701f;
public static float DegreeToRadian( this float f )
{
return f * 0.0174533f;
}
public static float RadianToDegree( this float f )
{
return f * 57.2958f;
}
public static float GradiansToDegrees( this float f )
{
return f * 0.9f;
}
public static float GradiansToRadians( this float f )
{
return f * 0.01570796f;
}
public static float MeterToInch( this float f )
{
return f * 39.37008f;
}
public static float InchToMeter( this float f )
{
return f * 0.0254f;
}
public static float InchToMilimeter( this float f )
{
return f * 0.0393701f;
}
/// <summary>Snap number to grid</summary>
public static float SnapToGrid( this float f, float gridSize )
{
return MathF.Round( f / gridSize ) * gridSize;
}
/// <summary>
/// Remove the fractional part and return the float as an integer.
/// </summary>
public static int FloorToInt( this float f )
{
return (int)MathF.Floor( f );
}
public static float Floor( this float f )
{
return MathF.Floor( f );
}
public static int CeilToInt( this float f )
{
return (int)MathF.Ceiling( f );
}
private static void Order( ref float a, ref float b )
{
if ( a <= (double)b )
return;
( a, b ) = ( b, a );
}
public static float Clamp( this float v, float min, float max )
{
Order( ref min, ref max );
return v < (double)min ? min : v < (double)max ? v : max;
}
public static float Lerp( float from, float to, float delta, bool clamp = true )
{
if ( clamp )
delta = delta.Clamp( 0.0f, 1f );
return from + delta * ( to - from );
}
public static float LerpTo( this float from, float to, float delta, bool clamp = true )
{
if ( clamp )
delta = delta.Clamp( 0.0f, 1f );
return from + delta * ( to - from );
}
public static float[] LerpTo( this float[] from, float[] to, float delta, bool clamp = true )
{
if ( from == null )
return null;
if ( to == null )
return from;
var numArray = new float[Math.Min( from.Length, to.Length )];
for ( var index = 0; index < numArray.Length; ++index )
numArray[index] = from[index].LerpTo( to[index], delta, clamp );
return numArray;
}
public static float Approach( this float f, float target, float delta )
{
if ( f > (double)target )
{
f -= delta;
if ( f < (double)target )
return target;
}
else
{
f += delta;
if ( f > (double)target )
return target;
}
return f;
}
public static float LerpInverse( this float value, float a, float b, bool clamp = true )
{
if ( clamp )
value = value.Clamp( a, b );
value -= a;
b -= a;
return value / b;
}
public static bool AlmostEqual( this float value, float b, float within = 0.0001f )
{
return MathF.Abs( value - b ) <= (double)within;
}
/// <summary>Does what you expected to happen when you did "a % b"</summary>
public static float UnsignedMod( this float a, float b )
{
return a - b * ( a / b ).Floor();
}
/// <summary>Convert angle to between 0 - 360</summary>
public static float NormalizeDegrees( this float degree )
{
degree %= 360f;
if ( degree < 0.0 )
degree += 360f;
return degree;
}
/// <summary>Remap a float value from a one range to another</summary>
public static float Remap(
this float value,
float oldLow,
float oldHigh,
float newLow = 0.0f,
float newHigh = 1f )
{
return newLow +
(float)( ( value - (double)oldLow ) * ( newHigh - (double)newLow ) / ( oldHigh - (double)oldLow ) );
}
/// <summary>Remap an integer value from a one range to another</summary>
public static int Remap( this int value, int oldLow, int oldHigh, int newLow, int newHigh )
{
return newLow + ( value - oldLow ) * ( newHigh - newLow ) / ( oldHigh - oldLow );
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45978a3e2c074d2a8a7129d9ccbfb1c5
timeCreated: 1671150414

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e6000a9455111214ebda68b014dba7fa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,135 @@
using UnityEngine;
using Autodesk.Fbx;
using DaddyFrosty.Fbx.CustomExtensions;
using System.Collections.Generic;
using UnityEditor;
namespace DaddyFrosty.Fbx
{
namespace Visitors
{
public static class CameraVisitor
{
private static Dictionary<Camera.GateFitMode, FbxCamera.EGateFit> s_mapGateFit = new Dictionary<Camera.GateFitMode, FbxCamera.EGateFit>()
{
{ Camera.GateFitMode.Fill, FbxCamera.EGateFit.eFitFill },
{ Camera.GateFitMode.Horizontal, FbxCamera.EGateFit.eFitHorizontal },
{ Camera.GateFitMode.None, FbxCamera.EGateFit.eFitNone },
{ Camera.GateFitMode.Overscan, FbxCamera.EGateFit.eFitOverscan },
{ Camera.GateFitMode.Vertical, FbxCamera.EGateFit.eFitVertical }
};
/// <summary>
/// Visit Object and configure FbxCamera
/// </summary>
public static void ConfigureCamera(Camera unityCamera, FbxCamera fbxCamera)
{
if (unityCamera.usePhysicalProperties)
ConfigurePhysicalCamera(fbxCamera, unityCamera);
else
ConfigureGameCamera(fbxCamera, unityCamera);
}
/// <summary>
/// Configure FbxCameras from GameCamera
/// </summary>
private static void ConfigureGameCamera(FbxCamera fbxCamera, Camera unityCamera)
{
// Configure FilmBack settings as a 35mm TV Projection (0.816 x 0.612)
float aspectRatio = unityCamera.aspect;
float apertureHeightInInches = 0.612f;
float apertureWidthInInches = aspectRatio * apertureHeightInInches;
FbxCamera.EProjectionType projectionType =
unityCamera.orthographic ? FbxCamera.EProjectionType.eOrthogonal : FbxCamera.EProjectionType.ePerspective;
fbxCamera.ProjectionType.Set(projectionType);
fbxCamera.FilmAspectRatio.Set(aspectRatio);
fbxCamera.SetApertureWidth(apertureWidthInInches);
fbxCamera.SetApertureHeight(apertureHeightInInches);
fbxCamera.SetApertureMode(FbxCamera.EApertureMode.eVertical);
// Focal Length
double focalLength = fbxCamera.ComputeFocalLength(unityCamera.fieldOfView);
fbxCamera.FocalLength.Set(focalLength);
// Field of View
fbxCamera.FieldOfView.Set(unityCamera.fieldOfView);
// NearPlane
fbxCamera.SetNearPlane(unityCamera.nearClipPlane.Meters().ToCentimeters());
// FarPlane
fbxCamera.SetFarPlane(unityCamera.farClipPlane.Meters().ToCentimeters());
return;
}
public static Vector2 GetSizeOfMainGameView()
{
#if UNITY_2020_1_OR_NEWER
return Handles.GetMainGameViewSize();
#else
System.Type T = System.Type.GetType("UnityEditor.GameView,UnityEditor");
System.Reflection.MethodInfo GetSizeOfMainGameView = T.GetMethod("GetSizeOfMainGameView", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
System.Object Res = GetSizeOfMainGameView.Invoke(null, null);
return (Vector2)Res;
#endif // UNITY_2020_1_OR_NEWER
}
/// <summary>
/// Configure FbxCameras from a Physical Camera
/// </summary>
private static void ConfigurePhysicalCamera(FbxCamera fbxCamera, Camera unityCamera)
{
Debug.Assert(unityCamera.usePhysicalProperties);
// Configure FilmBack settings
float apertureHeightInInches = unityCamera.sensorSize.y.Millimeters().ToInches();
float apertureWidthInInches = unityCamera.sensorSize.x.Millimeters().ToInches();
float aspectRatio = apertureWidthInInches / apertureHeightInInches;
FbxCamera.EProjectionType projectionType = unityCamera.orthographic
? FbxCamera.EProjectionType.eOrthogonal
: FbxCamera.EProjectionType.ePerspective;
// NOTE: it is possible to match some of the sensor sizes to the
// predefined EApertureFormats : e16mmTheatrical, eSuper16mm,
// e35mmFullAperture, eIMAX. However the round in the sizes is not
// consistent between Unity and FBX so we choose
// to leave the values as a eCustomAperture setting.
fbxCamera.ProjectionType.Set(projectionType);
fbxCamera.FilmAspectRatio.Set(aspectRatio);
Vector2 gameViewSize = GetSizeOfMainGameView();
fbxCamera.SetAspect(FbxCamera.EAspectRatioMode.eFixedRatio, gameViewSize.x / gameViewSize.y, 1.0);
fbxCamera.SetApertureWidth(apertureWidthInInches);
fbxCamera.SetApertureHeight(apertureHeightInInches);
// Fit the resolution gate horizontally within the film gate.
fbxCamera.GateFit.Set(s_mapGateFit[unityCamera.gateFit]);
// Lens Shift ( Film Offset ) as a percentage 0..1
// FBX FilmOffset is in inches
fbxCamera.FilmOffsetX.Set(apertureWidthInInches * Mathf.Clamp(Mathf.Abs(unityCamera.lensShift.x), 0f, 1f) * Mathf.Sign(unityCamera.lensShift.x));
fbxCamera.FilmOffsetY.Set(apertureHeightInInches * Mathf.Clamp(Mathf.Abs(unityCamera.lensShift.y), 0f, 1f) * Mathf.Sign(unityCamera.lensShift.y));
// Focal Length
fbxCamera.SetApertureMode(FbxCamera.EApertureMode.eFocalLength);
double focalLength = (double)unityCamera.focalLength;
fbxCamera.FocalLength.Set(focalLength); /* in millimeters */
// NearPlane
fbxCamera.SetNearPlane((double)unityCamera.nearClipPlane.Meters().ToCentimeters());
// FarPlane
fbxCamera.SetFarPlane((float)unityCamera.farClipPlane.Meters().ToCentimeters());
return;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f112f025b26d64345936e9d9c6752f50
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,310 @@
using System;
using UnityEngine;
using Autodesk.Fbx;
namespace DaddyFrosty.Fbx
{
namespace CustomExtensions
{
public class MetricDistance : object
{
public static readonly MetricDistance Millimeter = new MetricDistance(0.001f);
public static readonly MetricDistance Centimeter = new MetricDistance(0.01f);
public static readonly MetricDistance Meter = new MetricDistance(1.0f);
private float _meters;
public MetricDistance(float m)
{
this._meters = m;
}
public float ToMeters()
{
return this._meters;
}
public float ToCentimeters()
{
return this._meters / Centimeter._meters;
}
public float ToMillimeters()
{
return this._meters / Millimeter._meters;
}
public ImperialDistance ToImperial()
{
return new ImperialDistance(this._meters * 39.3701f);
}
public float ToInches()
{
return ToImperial().ToInches();
}
public override int GetHashCode()
{
return _meters.GetHashCode();
}
public override bool Equals(object obj)
{
var o = obj as MetricDistance;
if (o == null) return false;
return _meters.Equals(o._meters);
}
public static bool operator==(MetricDistance a, MetricDistance b)
{
// If both are null, or both are same instance, return true
if (ReferenceEquals(a, b)) return true;
// if either one or the other are null, return false
if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
return a._meters == b._meters;
}
public static bool operator!=(MetricDistance a, MetricDistance b)
{
return !(a == b);
}
public static MetricDistance operator+(MetricDistance a, MetricDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new MetricDistance(a._meters + b._meters);
}
public static MetricDistance Add(MetricDistance a, MetricDistance b)
{
return a + b;
}
public static MetricDistance operator-(MetricDistance a, MetricDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new MetricDistance(a._meters - b._meters);
}
public static MetricDistance Subtract(MetricDistance a, MetricDistance b)
{
return a - b;
}
public static MetricDistance operator*(MetricDistance a, MetricDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new MetricDistance(a._meters * b._meters);
}
public static MetricDistance Multiply(MetricDistance a, MetricDistance b)
{
return a * b;
}
public static MetricDistance operator/(MetricDistance a, MetricDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new MetricDistance(a._meters / b._meters);
}
public static MetricDistance Divide(MetricDistance a, MetricDistance b)
{
return a / b;
}
}
public class ImperialDistance
{
public static readonly ImperialDistance Inch = new ImperialDistance(1.0f);
public static readonly ImperialDistance Foot = new ImperialDistance(12.0f);
private float _inches;
public ImperialDistance(float m)
{
this._inches = m;
}
public MetricDistance ToMetric()
{
return new MetricDistance(this._inches * 0.0254f);
}
public float ToMeters()
{
return this.ToMetric().ToMeters();
}
public float ToInches()
{
return _inches;
}
public override int GetHashCode()
{
return _inches.GetHashCode();
}
public override bool Equals(object obj)
{
var o = obj as ImperialDistance;
if (o == null) return false;
return _inches.Equals(o._inches);
}
public static bool operator==(ImperialDistance a, ImperialDistance b)
{
// If both are null, or both are same instance, return true
if (ReferenceEquals(a, b)) return true;
// if either one or the other are null, return false
if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
return a._inches == b._inches;
}
public static bool operator!=(ImperialDistance a, ImperialDistance b)
{
return !(a == b);
}
public static ImperialDistance operator+(ImperialDistance a, ImperialDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new ImperialDistance(a._inches + b._inches);
}
public static ImperialDistance Add(ImperialDistance a, ImperialDistance b)
{
return a + b;
}
public static ImperialDistance operator-(ImperialDistance a, ImperialDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new ImperialDistance(a._inches - b._inches);
}
public static ImperialDistance Subtract(ImperialDistance a, ImperialDistance b)
{
return a - b;
}
public static ImperialDistance operator*(ImperialDistance a, ImperialDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new ImperialDistance(a._inches * b._inches);
}
public static ImperialDistance Multiply(ImperialDistance a, ImperialDistance b)
{
return a * b;
}
public static ImperialDistance operator/(ImperialDistance a, ImperialDistance b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
return new ImperialDistance(a._inches / b._inches);
}
public static ImperialDistance Divide(ImperialDistance a, ImperialDistance b)
{
return a / b;
}
}
//Extension methods must be defined in a static class
internal static class FloatExtension
{
public static MetricDistance Meters(this float that)
{
return new MetricDistance(that);
}
public static MetricDistance Millimeters(this float that)
{
return new MetricDistance(MetricDistance.Millimeter.ToMeters() * that);
}
public static MetricDistance Centimeters(this float that)
{
return new MetricDistance(MetricDistance.Centimeter.ToMeters() * that);
}
public static ImperialDistance Inches(this float that)
{
return new ImperialDistance(that);
}
public static ImperialDistance Feet(this float that)
{
return new ImperialDistance(ImperialDistance.Foot.ToInches() * that);
}
}
//Extension methods must be defined in a static class
internal static class Vector3Extension
{
public static Vector3 RightHanded(this Vector3 leftHandedVector)
{
// negating the x component of the vector converts it from left to right handed coordinates
return new Vector3(
-leftHandedVector[0],
leftHandedVector[1],
leftHandedVector[2]);
}
public static FbxVector4 FbxVector4(this Vector3 uniVector)
{
return new FbxVector4(
uniVector[0],
uniVector[1],
uniVector[2]);
}
}
//Extension methods must be defined in a static class
internal static class AnimationCurveExtension
{
// This is an extension method for the AnimationCurve class
// The first parameter takes the "this" modifier
// and specifies the type for which the method is defined.
public static void Dump(this AnimationCurve animCurve, string message = "", float[] keyTimesExpected = null, float[] keyValuesExpected = null)
{
if (animCurve == null)
{
throw new System.ArgumentNullException("animCurve");
}
int idx = 0;
foreach (var key in animCurve.keys)
{
if (keyTimesExpected != null && keyValuesExpected != null && keyTimesExpected.Length == keyValuesExpected.Length)
{
Debug.Log(string.Format("{5} keys[{0}] {1}({3}) {2} ({4})",
idx, key.time, key.value,
keyTimesExpected[idx], keyValuesExpected[idx],
message));
}
else
{
Debug.Log(string.Format("{3} keys[{0}] {1} {2}", idx, key.time, key.value, message));
}
idx++;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1719f38fb4e4691439146f2d8d66cc22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
#if UNITY_2018_1_OR_NEWER
using UnityEditor.Presets;
namespace DaddyFrosty.Fbx
{
public delegate void SelectionChangedDelegate();
public delegate void DialogClosedDelegate();
public class FbxExportPresetSelectorReceiver : PresetSelectorReceiver
{
UnityEngine.Object m_Target;
Preset m_InitialValue;
public event SelectionChangedDelegate SelectionChanged;
public event DialogClosedDelegate DialogClosed;
public override void OnSelectionClosed(Preset selection)
{
OnSelectionChanged(selection);
if (DialogClosed != null)
{
DialogClosed();
}
DestroyImmediate(this);
}
public override void OnSelectionChanged(Preset selection)
{
if (selection != null)
{
selection.ApplyTo(m_Target);
}
else
{
m_InitialValue.ApplyTo(m_Target);
}
if (SelectionChanged != null)
{
SelectionChanged();
}
}
public void SetTarget(UnityEngine.Object target)
{
m_Target = target;
}
public void SetInitialValue(Preset initialValue)
{
m_InitialValue = initialValue;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6d24eae8578a914a8169f974b2cc03c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor.Timeline.Actions;
using UnityEngine.Timeline;
using System.Linq;
using UnityEngine.Playables;
using UnityEditor.Timeline;
namespace DaddyFrosty.Fbx
{
[MenuEntry("Export Clip To FBX...", MenuPriority.CustomClipActionSection.start + MenuPriority.separatorAt), UsedImplicitly]
class FbxExportTimelineAction : ClipAction
{
public override bool Execute(IEnumerable<TimelineClip> clips)
{
PlayableDirector director = TimelineEditor.inspectedDirector;
ModelExporter.ExportSingleTimelineClip(clips.First(), director);
return true;
}
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
if (clips.Count() != 1)
{
return ActionValidity.NotApplicable;
}
// has to be an animation clip
if (clips.Any((clip) => { return clip.animationClip == null; }))
{
return ActionValidity.NotApplicable;
}
return ActionValidity.Valid;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 573e216414ac007408e419f5bd6f79b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,276 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEditor;
namespace DaddyFrosty.Fbx
{
internal class RepairMissingScripts
{
private const string ForumPackageGUID = "2d81c55c4d9d85146b1d2de96e084b63";
private const string AssetStorePackageGUID = "628ffbda3fdf4df4588770785d91a698";
private const string FbxPrefabDLLFileId = "69888640";
private const string IdFormat = "{{fileID: {0}, guid: {1}, type:";
private static List<string> s_searchIDsToReplace;
private static List<string> SearchIDsToReplace
{
get
{
if (s_searchIDsToReplace == null || s_searchIDsToReplace.Count <= 0)
{
s_searchIDsToReplace = new List<string>()
{
string.Format(IdFormat, FbxPrefabDLLFileId, ForumPackageGUID),
string.Format(IdFormat, FbxPrefabDLLFileId, AssetStorePackageGUID)
};
}
return s_searchIDsToReplace;
}
}
private string[] m_assetsToRepair;
private string[] AssetsToRepair
{
get
{
if (m_assetsToRepair == null)
{
m_assetsToRepair = FindAssetsToRepair();
}
return m_assetsToRepair;
}
}
public static string SourceCodeSearchID
{
get
{
var fbxPrefabObj = AssetDatabase.LoadMainAssetAtPath(FindFbxPrefabAssetPath());
string searchID = null;
string guid;
long fileId;
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(fbxPrefabObj, out guid, out fileId))
{
searchID = string.Format(IdFormat, fileId, guid);
}
return searchID;
}
}
#if COM_UNITY_FORMATS_FBX_AS_ASSET
public const string FbxPrefabFile = "/UnityFbxPrefab.dll";
#else
public const string FbxPrefabFile = "Packages/com.unity.formats.fbx/Runtime/FbxPrefab.cs";
#endif
public static string FindFbxPrefabAssetPath()
{
#if COM_UNITY_FORMATS_FBX_AS_ASSET
// Find guids that are scripts that look like FbxPrefab.
// That catches FbxPrefabTest too, so we have to make sure.
var allGuids = AssetDatabase.FindAssets("FbxPrefab t:MonoScript");
string foundPath = "";
foreach (var guid in allGuids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (path.EndsWith(FbxPrefabFile))
{
if (!string.IsNullOrEmpty(foundPath))
{
// How did this happen? Anyway, just don't try.
Debug.LogWarning(string.Format("{0} found in multiple places; did you forget to delete one of these?\n{1}\n{2}",
FbxPrefabFile.Substring(1), foundPath, path));
return "";
}
foundPath = path;
}
}
if (string.IsNullOrEmpty(foundPath))
{
Debug.LogWarning(string.Format("{0} not found; are you trying to uninstall {1}?", FbxPrefabFile.Substring(1), ModelExporter.PACKAGE_UI_NAME));
}
return foundPath;
#else
// In Unity 2018.1 and 2018.2.0b7, FindAssets can't find FbxPrefab.cs in a package.
// So we hardcode the path.
var path = FbxPrefabFile;
if (System.IO.File.Exists(System.IO.Path.GetFullPath(path)))
{
return path;
}
else
{
Debug.LogWarningFormat("{0} not found; update FbxPrefabFile variable in FbxExporterRepairMissingScripts.cs to point to FbxPrefab.cs path.", FbxPrefabFile);
return "";
}
#endif
}
public int AssetsToRepairCount
{
get
{
return AssetsToRepair.Length;
}
}
public string[] GetAssetsToRepair()
{
return AssetsToRepair;
}
public static string[] FindAssetsToRepair()
{
// search project for assets containing old GUID
// ignore if forced binary
if (UnityEditor.EditorSettings.serializationMode == SerializationMode.ForceBinary)
{
return new string[] {};
}
// check all scenes and prefabs
string[] searchFilePatterns = new string[] { "*.prefab", "*.unity" };
List<string> assetsToRepair = new List<string>();
foreach (string searchPattern in searchFilePatterns)
{
foreach (string file in Directory.GetFiles(Application.dataPath, searchPattern, SearchOption.AllDirectories))
{
if (AssetNeedsRepair(file))
{
assetsToRepair.Add(file);
}
}
}
return assetsToRepair.ToArray();
}
private static bool AssetNeedsRepair(string filePath)
{
try
{
using (var sr = new StreamReader(filePath))
{
if (sr.Peek() > -1)
{
var firstLine = sr.ReadLine();
if (!firstLine.StartsWith("%YAML"))
{
return false;
}
}
var contents = sr.ReadToEnd();
if (SearchIDsToReplace.Exists(searchId => contents.Contains(searchId)))
{
return true;
}
}
}
catch (IOException e)
{
Debug.LogError(string.Format("Failed to check file for component update: {0} (error={1})", filePath, e));
}
return false;
}
public bool ReplaceGUIDInTextAssets()
{
string sourceCodeSearchID = SourceCodeSearchID;
if (string.IsNullOrEmpty(sourceCodeSearchID))
{
return false;
}
bool replacedGUID = false;
foreach (string file in AssetsToRepair)
{
replacedGUID |= ReplaceGUIDInFile(file, sourceCodeSearchID);
}
if (replacedGUID)
{
AssetDatabase.Refresh();
}
return replacedGUID;
}
private static bool ReplaceID(string searchId, string replacementId, ref string line)
{
if (line.Contains(searchId))
{
line = line.Replace(searchId, replacementId);
return true;
}
return false;
}
private static bool ReplaceGUIDInFile(string path, string replacementSearchID)
{
// try to read file, assume it's a text file for now
bool modified = false;
try
{
var tmpFile = Path.GetTempFileName();
if (string.IsNullOrEmpty(tmpFile))
{
return false;
}
using (var sr = new StreamReader(path))
{
// verify that this is a text file
var firstLine = "";
if (sr.Peek() > -1)
{
firstLine = sr.ReadLine();
if (!firstLine.StartsWith("%YAML"))
{
return false;
}
}
using (var sw = new StreamWriter(tmpFile, false))
{
if (!string.IsNullOrEmpty(firstLine))
{
sw.WriteLine(firstLine);
}
while (sr.Peek() > -1)
{
var line = sr.ReadLine();
SearchIDsToReplace.ForEach(searchId =>
modified |= ReplaceID(searchId, replacementSearchID, ref line)
);
sw.WriteLine(line);
}
}
}
if (modified)
{
File.Delete(path);
File.Move(tmpFile, path);
Debug.LogFormat("Updated FbxPrefab components in file {0}", path);
return true;
}
else
{
File.Delete(tmpFile);
}
}
catch (IOException e)
{
Debug.LogError(string.Format("Failed to replace GUID in file {0} (error={1})", path, e));
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1dc7ecef19dfd6a4d84f3e2e890dfe19
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,375 @@
using Autodesk.Fbx;
using System.Collections.Generic;
namespace DaddyFrosty.Fbx
{
/// <summary>
/// Store FBX property name and channel name
/// Default constructor added because it needs to be called before autoimplemented properties can be assigned. Otherwise we get build errors
/// </summary>
struct FbxPropertyChannelPair
{
public string Property { get; private set; }
public string Channel { get; private set; }
public FbxPropertyChannelPair(string p, string c) : this()
{
Property = p;
Channel = c;
}
struct UnityPropertyChannelPair
{
public string property;
public string channel;
public UnityPropertyChannelPair(string p, string c)
{
property = p;
channel = c;
}
}
/// <summary>
/// Contains the two lists that map Unity property to FBX property and Unity channel to Fbx channel
/// for a set of properties.
/// </summary>
struct PropertyChannelMap
{
public List<(string, string)> MapUnityPropToFbxProp;
public List<(string, string)> MapUnityChannelToFbxChannel;
public PropertyChannelMap(List<(string, string)> propertyMap, List<(string, string)> channelMap)
{
MapUnityPropToFbxProp = propertyMap;
MapUnityChannelToFbxChannel = channelMap;
}
private string GetFbxValue(string uniValue, List<(string, string)> list)
{
return list.Find(x => x.Item1 == uniValue).Item2;
}
/// <summary>
/// Get the Fbx property name for the given Unity property name from the given list.
/// </summary>
/// <param name="uniProperty"></param>
/// <param name="propertyMap"></param>
/// <returns>The Fbx property name or null if there was no match in the list</returns>
public string GetFbxProperty(string uniProperty)
{
return GetFbxValue(uniProperty, MapUnityPropToFbxProp);
}
/// <summary>
/// Get the Fbx channel name for the given Unity channel from the given list.
/// </summary>
/// <param name="uniChannel"></param>
/// <param name="channelMap"></param>
/// <returns>The Fbx channel name or null if there was no match in the list</returns>
public string GetFbxChannel(string uniChannel)
{
return GetFbxValue(uniChannel, MapUnityChannelToFbxChannel);
}
}
// =========== Property Maps ================
// These are lists that map a Unity property name to it's corresponding Fbx property name.
// Split up into multiple lists as some are channel and object dependant.
/// <summary>
/// Map of Unity transform properties to their FBX equivalent.
/// </summary>
private static List<(string, string)> MapTransformPropToFbxProp = new List<(string, string)>()
{
("m_LocalScale", "Lcl Scaling"),
("Motion S", "Lcl Scaling"),
("m_LocalPosition", "Lcl Translation"),
("Motion T", "Lcl Translation"),
("m_TranslationOffset", "Translation"),
("m_ScaleOffset", "Scaling"),
("m_RotationOffset", "Rotation"),
("localEulerAnglesRaw", "Lcl Rotation")
};
/// <summary>
/// Map of Unity Aim constraint properties to their FBX equivalent.
/// </summary>
private static List<(string, string)> MapAimConstraintPropToFbxProp = new List<(string, string)>()
{
("m_AimVector", "AimVector"),
("m_UpVector", "UpVector"),
("m_WorldUpVector", "WorldUpVector"),
("m_RotationOffset", "RotationOffset")
};
/// <summary>
/// Map of Unity color properties to their FBX equivalent.
/// </summary>
private static List<(string, string)> MapColorPropToFbxProp = new List<(string, string)>()
{
("m_Color", "Color")
};
/// <summary>
/// Map of Unity properties to their FBX equivalent.
/// </summary>
private static List<(string, string)> MapPropToFbxProp = new List<(string, string)>()
{
("m_Intensity", "Intensity"),
("field of view", "FieldOfView"),
("m_Weight", "Weight"),
("m_FocalLength", "FocalLength"),
("m_LensShift.x", "FilmOffsetX"),
("m_LensShift.y", "FilmOffsetY")
};
/// <summary>
/// Map of Unity constraint source property name as a regular expression to the FBX property as a string format.
/// This is necessary because the Unity property contains an index in to an array, and the FBX property contains
/// the name of the source object.
/// </summary>
private static List<(string, string)> MapConstraintSourcePropToFbxProp = new List<(string, string)>()
{
(@"m_Sources\.Array\.data\[(\d+)\]\.weight", "{0}.Weight")
};
/// <summary>
/// Map of Unity constraint source transform property name as a regular expression to the FBX property as a string format.
/// This is necessary because the Unity property contains an index in to an array, and the FBX property contains
/// the name of the source object.
/// </summary>
private static List<(string, string)> MapConstraintSourceTransformPropToFbxProp = new List<(string, string)>()
{
(@"m_TranslationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset T"),
(@"m_RotationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset R")
};
/// <summary>
/// Map of Unity blendshape property name as a regular expression to the FBX property.
/// This is necessary because the Unity property contains the name of the target object.
/// </summary>
private static List<(string, string)> MapBlendshapesPropToFbxProp = new List<(string, string)>()
{
(@"blendShape\.(\S+)", "DeformPercent")
};
// ================== Channel Maps ======================
/// <summary>
/// Map of Unity transform channels to their FBX equivalent.
/// </summary>
private static List<(string, string)> MapTransformChannelToFbxChannel = new List<(string, string)>()
{
("x", Globals.FBXSDK_CURVENODE_COMPONENT_X),
("y", Globals.FBXSDK_CURVENODE_COMPONENT_Y),
("z", Globals.FBXSDK_CURVENODE_COMPONENT_Z)
};
/// <summary>
/// Map of Unity color channels to their FBX equivalent.
/// </summary>
private static List<(string, string)> MapColorChannelToFbxChannel = new List<(string, string)>()
{
("b", Globals.FBXSDK_CURVENODE_COLOR_BLUE),
("g", Globals.FBXSDK_CURVENODE_COLOR_GREEN),
("r", Globals.FBXSDK_CURVENODE_COLOR_RED)
};
// =======================================================
private static PropertyChannelMap TransformPropertyMap = new PropertyChannelMap(MapTransformPropToFbxProp, MapTransformChannelToFbxChannel);
private static PropertyChannelMap AimConstraintPropertyMap = new PropertyChannelMap(MapAimConstraintPropToFbxProp, MapTransformChannelToFbxChannel);
private static PropertyChannelMap ColorPropertyMap = new PropertyChannelMap(MapColorPropToFbxProp, MapColorChannelToFbxChannel);
private static PropertyChannelMap ConstraintSourcePropertyMap = new PropertyChannelMap(MapConstraintSourcePropToFbxProp, null);
private static PropertyChannelMap ConstraintSourceTransformPropertyMap = new PropertyChannelMap(MapConstraintSourceTransformPropToFbxProp, MapTransformChannelToFbxChannel);
private static PropertyChannelMap BlendshapeMap = new PropertyChannelMap(MapBlendshapesPropToFbxProp, null);
private static PropertyChannelMap OtherPropertyMap = new PropertyChannelMap(MapPropToFbxProp, null);
/// <summary>
/// Separates and returns the property and channel from the full Unity property name.
///
/// Takes what is after the last period as the channel.
/// In order to use this have to be certain that there are channels, as there are cases where what is after
/// the last period is still the property name. E.g. m_Sources.Array.data[0].weight has no channel.
/// </summary>
/// <param name="fullPropertyName"></param>
/// <returns></returns>
private static UnityPropertyChannelPair GetUnityPropertyChannelPair(string fullPropertyName)
{
int index = fullPropertyName.LastIndexOf('.');
if (index < 0)
{
return new UnityPropertyChannelPair(fullPropertyName, null);
}
var property = fullPropertyName.Substring(0, index);
var channel = fullPropertyName.Substring(index + 1);
return new UnityPropertyChannelPair(property, channel);
}
/// <summary>
/// Get the Fbx property name for the given Unity constraint source property name from the given list.
///
/// This is different from GetFbxProperty() because the Unity constraint source properties contain indices, and
/// the Fbx constraint source property contains the name of the source object.
/// </summary>
/// <param name="uniProperty"></param>
/// <param name="constraint"></param>
/// <param name="propertyMap"></param>
/// <returns>The Fbx property name or null if there was no match in the list</returns>
private static string GetFbxConstraintSourceProperty(string uniProperty, FbxConstraint constraint, List<(string, string)> propertyMap)
{
foreach (var prop in propertyMap)
{
var match = System.Text.RegularExpressions.Regex.Match(uniProperty, prop.Item1);
if (match.Success && match.Groups.Count > 0)
{
var matchedStr = match.Groups[1].Value;
int index;
if (!int.TryParse(matchedStr, out index))
{
continue;
}
var source = constraint.GetConstraintSource(index);
return string.Format(prop.Item2, source.GetName());
}
}
return null;
}
/// <summary>
/// Get the Fbx property name for the given Unity blendshape property name from the given list.
///
/// This is different from GetFbxProperty() because the Unity blendshape properties contain the name
/// of the target object.
/// </summary>
/// <param name="uniProperty"></param>
/// <param name="propertyMap"></param>
/// <returns>The Fbx property name or null if there was no match in the list</returns>
private static string GetFbxBlendshapeProperty(string uniProperty, List<(string, string)> propertyMap)
{
foreach (var prop in propertyMap)
{
var match = System.Text.RegularExpressions.Regex.Match(uniProperty, prop.Item1);
if (match.Success)
{
return prop.Item2;
}
}
return null;
}
/// <summary>
/// Try to get the property channel pairs for the given Unity property from the given property channel mapping.
/// </summary>
/// <param name="uniPropertyName"></param>
/// <param name="propertyChannelMap"></param>
/// <param name="constraint"></param>
/// <returns>The property channel pairs or null if there was no match</returns>
private static FbxPropertyChannelPair[] GetChannelPairs(string uniPropertyName, PropertyChannelMap propertyChannelMap, FbxConstraint constraint = null)
{
// Unity property name is of the format "property.channel" or "property". Handle both cases.
var possibleUniPropChannelPairs = new List<UnityPropertyChannelPair>();
// could give same result as already in the list, avoid checking this case twice
var propChannelPair = GetUnityPropertyChannelPair(uniPropertyName);
possibleUniPropChannelPairs.Add(propChannelPair);
if (propChannelPair.property != uniPropertyName)
{
possibleUniPropChannelPairs.Add(new UnityPropertyChannelPair(uniPropertyName, null));
}
foreach (var uniPropChannelPair in possibleUniPropChannelPairs)
{
// try to match property
var fbxProperty = propertyChannelMap.GetFbxProperty(uniPropChannelPair.property);
if (string.IsNullOrEmpty(fbxProperty))
{
if (constraint != null)
{
// check if it's a constraint source property
fbxProperty = GetFbxConstraintSourceProperty(uniPropChannelPair.property, constraint, propertyChannelMap.MapUnityPropToFbxProp);
}
else if (propertyChannelMap.MapUnityPropToFbxProp == MapBlendshapesPropToFbxProp)
{
// check if it's a blendshape property
fbxProperty = GetFbxBlendshapeProperty(uniPropChannelPair.property, propertyChannelMap.MapUnityPropToFbxProp);
}
}
if (string.IsNullOrEmpty(fbxProperty))
{
continue;
}
// matched property, now try to match channel
string fbxChannel = null;
if (!string.IsNullOrEmpty(uniPropChannelPair.channel) && propertyChannelMap.MapUnityChannelToFbxChannel != null)
{
fbxChannel = propertyChannelMap.GetFbxChannel(uniPropChannelPair.channel);
if (string.IsNullOrEmpty(fbxChannel))
{
// couldn't match the Unity channel to the fbx channel
continue;
}
}
return new FbxPropertyChannelPair[] { new FbxPropertyChannelPair(fbxProperty, fbxChannel) };
}
return null;
}
/// <summary>
/// Map a Unity property name to the corresponding FBX property and
/// channel names.
/// </summary>
public static bool TryGetValue(string uniPropertyName, out FbxPropertyChannelPair[] prop, FbxConstraint constraint = null)
{
prop = new FbxPropertyChannelPair[] {};
// spot angle is a special case as it returns two channel pairs instead of one
System.StringComparison ct = System.StringComparison.CurrentCulture;
if (uniPropertyName.StartsWith("m_SpotAngle", ct))
{
prop = new FbxPropertyChannelPair[]
{
new FbxPropertyChannelPair("OuterAngle", null),
new FbxPropertyChannelPair("InnerAngle", null)
};
return true;
}
var propertyMaps = new List<PropertyChannelMap>();
// Try get constraint specific channel pairs first as we know this is a constraint
if (constraint != null)
{
// Aim constraint shares the RotationOffset property with RotationConstraint, so make sure that the correct FBX property is returned
if (constraint.GetConstraintType() == FbxConstraint.EType.eAim)
{
propertyMaps.Add(AimConstraintPropertyMap);
}
propertyMaps.Add(ConstraintSourcePropertyMap);
propertyMaps.Add(ConstraintSourceTransformPropertyMap);
}
// Check if this is a transform, color, or other property and return the channel pairs if they match.
propertyMaps.Add(TransformPropertyMap);
propertyMaps.Add(ColorPropertyMap);
propertyMaps.Add(OtherPropertyMap);
propertyMaps.Add(BlendshapeMap);
foreach (var propMap in propertyMaps)
{
prop = GetChannelPairs(uniPropertyName, propMap, constraint);
if (prop != null)
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a6b42abf99d4bdf4c8798184fb5c936d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,276 @@
using Autodesk.Fbx;
using UnityEngine;
using System.Collections.Generic;
namespace DaddyFrosty.Fbx
{
/// <summary>
/// Base class for QuaternionCurve and EulerCurve.
/// Provides implementation for computing keys and generating FbxAnimCurves
/// for euler rotation.
/// </summary>
internal abstract class RotationCurve
{
private double m_sampleRate;
public double SampleRate
{
get { return m_sampleRate; }
set { m_sampleRate = value; }
}
private AnimationCurve[] m_curves;
public AnimationCurve[] GetCurves() { return m_curves; }
public void SetCurves(AnimationCurve[] value) { m_curves = value; }
protected struct Key
{
private FbxTime m_time;
public FbxTime time
{
get { return m_time; }
set { m_time = value; }
}
private FbxVector4 m_euler;
public FbxVector4 euler
{
get { return m_euler; }
set { m_euler = value; }
}
}
protected RotationCurve() {}
public void SetCurve(int i, AnimationCurve curve)
{
GetCurves()[i] = curve;
}
protected abstract FbxQuaternion GetConvertedQuaternionRotation(float seconds, UnityEngine.Quaternion restRotation);
private Key[] ComputeKeys(UnityEngine.Quaternion restRotation, FbxNode node)
{
// Get the source pivot pre-rotation if any, so we can
// remove it from the animation we get from Unity.
var fbxPreRotationEuler = node.GetRotationActive()
? node.GetPreRotation(FbxNode.EPivotSet.eSourcePivot)
: new FbxVector4();
// Get the inverse of the prerotation
var fbxPreRotationInverse = ModelExporter.EulerToQuaternionXYZ(fbxPreRotationEuler);
fbxPreRotationInverse.Inverse();
// Find when we have keys set.
var keyTimes = ModelExporter.GetSampleTimes(GetCurves(), SampleRate);
// Convert to the Key type.
var keys = new Key[keyTimes.Count];
int i = 0;
foreach (var seconds in keyTimes)
{
var fbxFinalAnimation = GetConvertedQuaternionRotation(seconds, restRotation);
// Cancel out the pre-rotation. Order matters. FBX reads left-to-right.
// When we run animation we will apply:
// pre-rotation
// then pre-rotation inverse
// then animation.
var fbxFinalQuat = fbxPreRotationInverse * fbxFinalAnimation;
var finalUnityQuat = new Quaternion((float)fbxFinalQuat.X, (float)fbxFinalQuat.Y, (float)fbxFinalQuat.Z, (float)fbxFinalQuat.W);
// Store the key so we can sort them later.
Key key = new Key();
key.time = FbxTime.FromSecondDouble(seconds);
key.euler = ModelExporter.ConvertToFbxVector4(finalUnityQuat.eulerAngles);
keys[i++] = key;
}
// Sort the keys by time
System.Array.Sort(keys, (Key a, Key b) => a.time.CompareTo(b.time));
return keys;
}
public void Animate(Transform unityTransform, FbxNode fbxNode, FbxAnimLayer fbxAnimLayer, bool Verbose)
{
if (!unityTransform || fbxNode == null)
{
return;
}
/* Find or create the three curves. */
var fbxAnimCurveX = fbxNode.LclRotation.GetCurve(fbxAnimLayer, Globals.FBXSDK_CURVENODE_COMPONENT_X, true);
var fbxAnimCurveY = fbxNode.LclRotation.GetCurve(fbxAnimLayer, Globals.FBXSDK_CURVENODE_COMPONENT_Y, true);
var fbxAnimCurveZ = fbxNode.LclRotation.GetCurve(fbxAnimLayer, Globals.FBXSDK_CURVENODE_COMPONENT_Z, true);
/* set the keys */
using (new FbxAnimCurveModifyHelper(new List<FbxAnimCurve> {fbxAnimCurveX, fbxAnimCurveY, fbxAnimCurveZ}))
{
foreach (var key in ComputeKeys(unityTransform.localRotation, fbxNode))
{
int i = fbxAnimCurveX.KeyAdd(key.time);
fbxAnimCurveX.KeySet(i, key.time, (float)key.euler.X);
i = fbxAnimCurveY.KeyAdd(key.time);
fbxAnimCurveY.KeySet(i, key.time, (float)key.euler.Y);
i = fbxAnimCurveZ.KeyAdd(key.time);
fbxAnimCurveZ.KeySet(i, key.time, (float)key.euler.Z);
}
}
if (Verbose)
{
Debug.Log("Exported rotation animation for " + fbxNode.GetName());
}
}
}
/// <summary>
/// Convert from ZXY to XYZ euler, and remove
/// prerotation from animated rotation.
/// </summary>
internal class EulerCurve : RotationCurve
{
public EulerCurve() { SetCurves(new AnimationCurve[3]); }
/// <summary>
/// Gets the index of the euler curve by property name.
/// x = 0, y = 1, z = 2
/// </summary>
/// <returns>The index of the curve, or -1 if property doesn't map to Euler curve.</returns>
/// <param name="uniPropertyName">Unity property name.</param>
public static int GetEulerIndex(string uniPropertyName)
{
if (string.IsNullOrEmpty(uniPropertyName))
{
return -1;
}
System.StringComparison ct = System.StringComparison.CurrentCulture;
bool isEulerComponent = uniPropertyName.StartsWith("localEulerAnglesRaw.", ct);
if (!isEulerComponent) { return -1; }
switch (uniPropertyName[uniPropertyName.Length - 1])
{
case 'x': return 0;
case 'y': return 1;
case 'z': return 2;
default: return -1;
}
}
protected override FbxQuaternion GetConvertedQuaternionRotation(float seconds, Quaternion restRotation)
{
var eulerRest = restRotation.eulerAngles;
AnimationCurve x = GetCurves()[0], y = GetCurves()[1], z = GetCurves()[2];
// The final animation, including the effect of pre-rotation.
// If we have no curve, assume the node has the correct rotation right now.
// We need to evaluate since we might only have keys in one of the axes.
var unityFinalAnimation = new Vector3(
(x == null) ? eulerRest[0] : x.Evaluate(seconds),
(y == null) ? eulerRest[1] : y.Evaluate(seconds),
(z == null) ? eulerRest[2] : z.Evaluate(seconds)
);
// convert the final animation to righthanded coords
return ModelExporter.EulerToQuaternionZXY(unityFinalAnimation);
}
}
/// <summary>
/// Exporting rotations is more complicated. We need to convert
/// from quaternion to euler. We use this class to help.
/// </summary>
internal class QuaternionCurve : RotationCurve
{
public QuaternionCurve() { SetCurves(new AnimationCurve[4]); }
/// <summary>
/// Gets the index of the curve by property name.
/// x = 0, y = 1, z = 2, w = 3
/// </summary>
/// <returns>The index of the curve, or -1 if property doesn't map to Quaternion curve.</returns>
/// <param name="uniPropertyName">Unity property name.</param>
public static int GetQuaternionIndex(string uniPropertyName)
{
if (string.IsNullOrEmpty(uniPropertyName))
{
return -1;
}
System.StringComparison ct = System.StringComparison.CurrentCulture;
bool isQuaternionComponent = false;
isQuaternionComponent |= uniPropertyName.StartsWith("m_LocalRotation.", ct);
isQuaternionComponent |= uniPropertyName.EndsWith("Q.x", ct);
isQuaternionComponent |= uniPropertyName.EndsWith("Q.y", ct);
isQuaternionComponent |= uniPropertyName.EndsWith("Q.z", ct);
isQuaternionComponent |= uniPropertyName.EndsWith("Q.w", ct);
if (!isQuaternionComponent) { return -1; }
switch (uniPropertyName[uniPropertyName.Length - 1])
{
case 'x': return 0;
case 'y': return 1;
case 'z': return 2;
case 'w': return 3;
default: return -1;
}
}
protected override FbxQuaternion GetConvertedQuaternionRotation(float seconds, Quaternion restRotation)
{
AnimationCurve x = GetCurves()[0], y = GetCurves()[1], z = GetCurves()[2], w = GetCurves()[3];
// The final animation, including the effect of pre-rotation.
// If we have no curve, assume the node has the correct rotation right now.
// We need to evaluate since we might only have keys in one of the axes.
var fbxFinalAnimation = new FbxQuaternion(
(x == null) ? restRotation[0] : x.Evaluate(seconds),
(y == null) ? restRotation[1] : y.Evaluate(seconds),
(z == null) ? restRotation[2] : z.Evaluate(seconds),
(w == null) ? restRotation[3] : w.Evaluate(seconds));
return fbxFinalAnimation;
}
}
/// <summary>
/// Exporting rotations is more complicated. We need to convert
/// from quaternion to euler. We use this class to help.
/// </summary>
internal class FbxAnimCurveModifyHelper : System.IDisposable
{
public List<FbxAnimCurve> Curves { get; private set; }
public FbxAnimCurveModifyHelper(List<FbxAnimCurve> list)
{
Curves = list;
foreach (var curve in Curves)
curve.KeyModifyBegin();
}
~FbxAnimCurveModifyHelper()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool cleanUpManaged)
{
foreach (var curve in Curves)
curve.KeyModifyEnd();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1df2cf4a1fa06b64bac747b1d3730d6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,991 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Security.Permissions;
using UnityEditor;
namespace DaddyFrosty.Fbx
{
internal abstract class DCCIntegration
{
public abstract string DccDisplayName { get; }
public abstract string IntegrationZipPath { get; }
private static string s_integrationFolderPath = null;
public static string IntegrationFolderPath
{
get
{
if (string.IsNullOrEmpty(s_integrationFolderPath))
{
s_integrationFolderPath = Application.dataPath;
}
return s_integrationFolderPath;
}
set
{
if (!string.IsNullOrEmpty(value) && System.IO.Directory.Exists(value))
{
s_integrationFolderPath = value;
}
else
{
Debug.LogError(string.Format("Failed to set integration folder path, invalid directory \"{0}\"", value));
}
}
}
public void SetIntegrationFolderPath(string path)
{
IntegrationFolderPath = path;
}
/// <summary>
/// Gets the integration zip full path as an absolute Unity-style path.
/// </summary>
/// <returns>The integration zip full path.</returns>
public string IntegrationZipFullPath
{
get
{
return System.IO.Path.GetFullPath("Packages/com.unity.formats.fbx/Editor/Integrations~").Replace("\\", "/") + "/" + IntegrationZipPath;
}
}
/// <summary>
/// Gets the project path.
/// </summary>
/// <returns>The project path.</returns>
public static string ProjectPath
{
get
{
return System.IO.Directory.GetParent(Application.dataPath).FullName.Replace("\\", "/");
}
}
/// <summary>
/// Installs the integration using the provided executable.
/// </summary>
/// <returns>The integration.</returns>
/// <param name="exe">Exe.</param>
[SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public abstract int InstallIntegration(string exe);
/// <summary>
/// Determines if folder is already unzipped at the specified path.
/// </summary>
/// <returns><c>true</c> if folder is already unzipped at the specified path; otherwise, <c>false</c>.</returns>
/// <param name="path">Path.</param>
public abstract bool FolderAlreadyUnzippedAtPath(string path);
/// <summary>
/// Launches application at given path
/// </summary>
/// <param name="AppPath"></param>
[SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void LaunchDCCApplication(string AppPath)
{
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
myProcess.StartInfo.FileName = AppPath;
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
myProcess.StartInfo.CreateNoWindow = false;
myProcess.StartInfo.UseShellExecute = false;
myProcess.EnableRaisingEvents = false;
myProcess.Start();
}
}
internal class MayaIntegration : DCCIntegration
{
public override string DccDisplayName { get { return "Maya"; } }
public override string IntegrationZipPath { get { return "UnityFbxForMaya.7z"; } }
private string FBX_EXPORT_SETTINGS_PATH { get { return "/Integrations/Autodesk/maya/scripts/unityFbxExportSettings.mel"; } }
private string FBX_IMPORT_SETTINGS_PATH { get { return "/Integrations/Autodesk/maya/scripts/unityFbxImportSettings.mel"; } }
private string MODULE_TEMPLATE_PATH { get { return "Integrations/Autodesk/maya/" + MODULE_FILENAME + ".txt"; } }
private string MODULE_FILENAME { get { return "UnityFbxForMaya"; } }
private const string PACKAGE_NAME = "com.unity.formats.fbx";
private const string VERSION_FIELD = "VERSION";
private const string VERSION_TAG = "{Version}";
private const string PROJECT_TAG = "{UnityProject}";
private const string INTEGRATION_TAG = "{UnityIntegrationsPath}";
private const string MAYA_USER_STARTUP_SCRIPT = "userSetup.mel";
private const string UI_SETUP_FUNCTION = "unitySetupUI";
private string USER_STARTUP_CALL { get { return string.Format("if(`exists {0}`){{ {0}; }}", UI_SETUP_FUNCTION); } }
private static string MAYA_DOCUMENTS_PATH
{
get
{
switch (Application.platform)
{
case RuntimePlatform.WindowsEditor:
return "maya";
case RuntimePlatform.OSXEditor:
return "Library/Preferences/Autodesk/Maya";
default:
throw new NotImplementedException();
}
}
}
private static string MAYA_MODULES_PATH
{
get
{
return System.IO.Path.Combine(UserFolder, MAYA_DOCUMENTS_PATH + "/modules");
}
}
private static string MAYA_SCRIPTS_PATH
{
get
{
return System.IO.Path.Combine(UserFolder, MAYA_DOCUMENTS_PATH + "/scripts");
}
}
// Use string to define escaped quote
// Windows needs the backslash
protected static string EscapedQuote
{
get
{
switch (Application.platform)
{
case RuntimePlatform.WindowsEditor:
return "\\\"";
case RuntimePlatform.OSXEditor:
return "\"";
default:
throw new NotSupportedException();
}
}
}
protected string MayaConfigCommand
{
get
{
return string.Format("unityConfigure {0}{1}{0} {0}{2}{0} {0}{3}{0} {4} {5};",
EscapedQuote, ProjectPath, ExportSettingsPath, ImportSettingsPath, (IsHeadlessInstall()), (HideSendToUnityMenu));
}
}
private string MAYA_CLOSE_COMMAND
{
get
{
return string.Format("scriptJob -idleEvent quit;");
}
}
protected static string UserFolder
{
get
{
switch (Application.platform)
{
case RuntimePlatform.WindowsEditor:
return System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
case RuntimePlatform.OSXEditor:
return System.Environment.GetEnvironmentVariable("HOME");
default:
throw new NotSupportedException();
}
}
}
public static int IsHeadlessInstall()
{
return 0;
}
public static int HideSendToUnityMenu
{
get
{
return ExportSettings.instance.HideSendToUnityMenuProperty ? 1 : 0;
}
}
public string ModuleTemplatePath
{
get
{
return System.IO.Path.Combine(IntegrationFolderPath, MODULE_TEMPLATE_PATH);
}
}
public static string PackagePath
{
get
{
return System.IO.Path.Combine(Application.dataPath, PACKAGE_NAME);
}
}
/// <summary>
/// Gets the path to the export settings file.
/// Returns a relative path with forward slashes as path separators.
/// </summary>
/// <returns>The export settings path.</returns>
public string ExportSettingsPath
{
get
{
return IntegrationFolderPath + FBX_EXPORT_SETTINGS_PATH;
}
}
/// <summary>
/// Gets the path to the import settings file.
/// Returns a relative path with forward slashes as path separators.
/// </summary>
/// <returns>The import settings path.</returns>
public string ImportSettingsPath
{
get
{
return IntegrationFolderPath + FBX_IMPORT_SETTINGS_PATH;
}
}
/// <summary>
/// Gets the user startup script path.
/// Returns a relative path with forward slashes as path separators.
/// </summary>
/// <returns>The user startup script path.</returns>
private static string GetUserStartupScriptPath()
{
return MAYA_SCRIPTS_PATH + "/" + MAYA_USER_STARTUP_SCRIPT;
}
public static string PackageVersion
{
get
{
return ModelExporter.GetVersionFromReadme();
}
}
private static List<string> ParseTemplateFile(string FileName, Dictionary<string, string> Tokens)
{
List<string> lines = new List<string>();
try
{
// Pass the file path and file name to the StreamReader constructor
System.IO.StreamReader sr = new System.IO.StreamReader(FileName);
// Read the first line of text
string line = sr.ReadLine();
// Continue to read until you reach end of file
while (line != null)
{
foreach (KeyValuePair<string, string> entry in Tokens)
{
line = line.Replace(entry.Key, entry.Value);
}
lines.Add(line);
//Read the next line
line = sr.ReadLine();
}
//close the file
sr.Close();
}
catch (Exception e)
{
Debug.LogError(string.Format("Exception reading module file template ({0})", e.Message));
}
return lines;
}
private static void WriteFile(string FileName, List<string> Lines)
{
try
{
//Pass the filepath and filename to the StreamWriter Constructor
System.IO.StreamWriter sw = new System.IO.StreamWriter(FileName);
foreach (string line in Lines)
{
//Write a line of text
sw.WriteLine(line);
}
//Close the file
sw.Close();
}
catch (Exception e)
{
Debug.LogException(e);
Debug.LogError(string.Format("Exception while writing module file ({0})", e.Message));
}
}
/// <summary>
/// Creates the missing directories in path.
/// </summary>
/// <returns><c>true</c>, if directory was created, <c>false</c> otherwise.</returns>
/// <param name="path">Path to create.</param>
protected static bool CreateDirectory(string path)
{
try
{
System.IO.Directory.CreateDirectory(path);
}
catch (Exception xcp)
{
Debug.LogException(xcp);
return false;
}
if (!System.IO.Directory.Exists(path))
{
return false;
}
return true;
}
[SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public int ConfigureMaya(string mayaPath)
{
int ExitCode = 0;
try
{
if (!System.IO.File.Exists(mayaPath))
{
Debug.LogError(string.Format("No maya installation found at {0}", mayaPath));
return -1;
}
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
myProcess.StartInfo.FileName = mayaPath;
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.UseShellExecute = false;
if (!ExportSettings.instance.LaunchAfterInstallation)
{
myProcess.StartInfo.RedirectStandardError = true;
}
string commandString;
switch (Application.platform)
{
case RuntimePlatform.WindowsEditor:
commandString = "-command \"{0}\"";
break;
case RuntimePlatform.OSXEditor:
commandString = @"-command '{0}'";
break;
default:
throw new NotImplementedException();
}
if (ExportSettings.instance.LaunchAfterInstallation)
{
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
myProcess.StartInfo.CreateNoWindow = false;
myProcess.StartInfo.Arguments = string.Format(commandString, MayaConfigCommand);
}
else
{
myProcess.StartInfo.Arguments = string.Format(commandString, MayaConfigCommand + MAYA_CLOSE_COMMAND);
}
myProcess.EnableRaisingEvents = true;
myProcess.Start();
if (!ExportSettings.instance.LaunchAfterInstallation)
{
string stderr = myProcess.StandardError.ReadToEnd();
myProcess.WaitForExit();
ExitCode = myProcess.ExitCode;
Debug.Log(string.Format("Ran maya: [{0}]\nWith args [{1}]\nResult {2}",
mayaPath, myProcess.StartInfo.Arguments, ExitCode));
// see if we got any error messages
if (ExitCode != 0)
{
if (!string.IsNullOrEmpty(stderr))
{
Debug.LogError(string.Format("Maya installation error (exit code: {0}): {1}", ExitCode, stderr));
}
}
}
else
{
ExitCode = 0;
}
}
catch (Exception e)
{
UnityEngine.Debug.LogError(string.Format("Exception failed to start Maya ({0})", e.Message));
ExitCode = -1;
}
return ExitCode;
}
public bool InstallMaya(bool verbose = false)
{
// What's happening here is that we copy the module template to
// the module path, basically:
// - copy the template to the user Maya module path
// - search-and-replace its tags
// - done.
// But it's complicated because we can't trust any files actually exist.
string moduleTemplatePath = ModuleTemplatePath;
if (!System.IO.File.Exists(moduleTemplatePath))
{
Debug.LogError(string.Format("Missing Maya module file at: \"{0}\"", moduleTemplatePath));
return false;
}
// Create the {USER} modules folder and empty it so it's ready to set up.
string modulePath = MAYA_MODULES_PATH;
string moduleFilePath = System.IO.Path.Combine(modulePath, MODULE_FILENAME + ".mod");
bool installed = false;
if (!System.IO.Directory.Exists(modulePath))
{
if (verbose) { Debug.Log(string.Format("Creating Maya Modules Folder {0}", modulePath)); }
if (!CreateDirectory(modulePath))
{
Debug.LogError(string.Format("Failed to create Maya Modules Folder {0}", modulePath));
return false;
}
installed = false;
}
else
{
// detect if UnityFbxForMaya.mod is installed
installed = System.IO.File.Exists(moduleFilePath);
if (installed)
{
// (Uni-31606): remove this when we support parsing existing .mod files
try
{
if (verbose) { Debug.Log(string.Format("Deleting module file {0}", moduleFilePath)); }
System.IO.File.Delete(moduleFilePath);
installed = false;
}
catch (Exception xcp)
{
Debug.LogException(xcp);
Debug.LogWarning(string.Format("Failed to delete plugin module file {0}", moduleFilePath));
}
}
}
// if not installed
if (!installed)
{
Dictionary<string, string> Tokens = new Dictionary<string, string>()
{
{VERSION_TAG, PackageVersion },
{PROJECT_TAG, ProjectPath },
{INTEGRATION_TAG, IntegrationFolderPath },
};
// parse template, replace "{UnityProject}" with project path
List<string> lines = ParseTemplateFile(moduleTemplatePath, Tokens);
if (verbose) Debug.Log(string.Format("Copying plugin module file to {0}", moduleFilePath));
// write out .mod file
WriteFile(moduleFilePath, lines);
}
else
{
throw new NotImplementedException();
// (Uni-31606) Parse maya mod file during installation and find location
}
return SetupUserStartupScript(verbose);
}
private bool SetupUserStartupScript(bool verbose = false)
{
// setup user startup script
string mayaStartupScript = GetUserStartupScriptPath();
string fileContents = string.Format("\n{0}", USER_STARTUP_CALL);
// make sure scripts directory exists
if (!System.IO.Directory.Exists(MAYA_SCRIPTS_PATH))
{
if (verbose) { Debug.Log(string.Format("Creating Maya Scripts Folder {0}", MAYA_SCRIPTS_PATH)); }
if (!CreateDirectory(MAYA_SCRIPTS_PATH))
{
Debug.LogError(string.Format("Failed to create Maya Scripts Folder {0}", MAYA_SCRIPTS_PATH));
return false;
}
}
else if (System.IO.File.Exists(mayaStartupScript))
{
// script exists, check that the UI setup is being called
try
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(mayaStartupScript))
{
while (sr.Peek() >= 0)
{
string line = sr.ReadLine();
if (line.Trim().Contains(UI_SETUP_FUNCTION))
{
// startup call already in the file, nothing to do
return true;
}
}
}
}
catch (Exception e)
{
Debug.LogException(e);
Debug.LogError(string.Format("Exception while reading user startup file ({0})", e.Message));
return false;
}
}
// append text to file
try
{
System.IO.File.AppendAllText(mayaStartupScript, fileContents);
}
catch (Exception e)
{
Debug.LogException(e);
Debug.LogError(string.Format("Exception while writing to user startup file ({0})", e.Message));
return false;
}
return true;
}
[SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public override int InstallIntegration(string exe)
{
if (!InstallMaya(verbose: true))
{
return -1;
}
return ConfigureMaya(exe);
}
/// <summary>
/// Determines if folder is already unzipped at the specified path
/// by checking if UnityFbxForMaya.mod exists at expected location.
/// </summary>
/// <returns><c>true</c> if folder is already unzipped at the specified path; otherwise, <c>false</c>.</returns>
/// <param name="path">Path.</param>
public override bool FolderAlreadyUnzippedAtPath(string path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}
return System.IO.File.Exists(System.IO.Path.Combine(path, MODULE_TEMPLATE_PATH));
}
}
internal class MayaLTIntegration : MayaIntegration
{
public override string DccDisplayName { get { return "Maya LT"; } }
}
internal class MaxIntegration : DCCIntegration
{
public override string DccDisplayName { get { return "3Ds Max"; } }
private const string MaxScriptsPath = "Integrations/Autodesk/max/scripts/";
private const string PluginName = "UnityFbxForMaxPlugin.ms";
public const string PluginPath = MaxScriptsPath + PluginName;
private const string ConfigureMaxScript = MaxScriptsPath + "configureUnityFbxForMax.ms";
private const string ExportSettingsFile = MaxScriptsPath + "unityFbxExportSettings.ms";
private const string ImportSettingsFile = MaxScriptsPath + "unityFbxImportSettings.ms";
private const string PluginSourceTag = "UnityPluginScript_Source";
private const string PluginNameTag = "UnityPluginScript_Name";
private const string ProjectTag = "UnityProject";
private const string ExportSettingsTag = "UnityFbxExportSettings";
private const string ImportSettingsTag = "UnityFbxImportSettings";
public override string IntegrationZipPath { get { return "UnityFbxForMax.7z"; } }
/// <summary>
/// Gets the absolute Unity path for relative path in Integrations folder.
/// </summary>
/// <returns>The absolute path.</returns>
/// <param name="relPath">Relative path.</param>
public static string GetAbsPath(string relPath)
{
return MayaIntegration.IntegrationFolderPath + "/" + relPath;
}
private static string GetInstallScript()
{
Dictionary<string, string> Tokens = new Dictionary<string, string>()
{
{PluginSourceTag, GetAbsPath(PluginPath) },
{PluginNameTag, PluginName },
{ProjectTag, ProjectPath },
{ExportSettingsTag, GetAbsPath(ExportSettingsFile) },
{ImportSettingsTag, GetAbsPath(ImportSettingsFile) }
};
var installScript = "";
// setup the variables to be used in the configure max script
foreach (var t in Tokens)
{
installScript += string.Format(@"global {0} = @\""{1}\"";", t.Key, t.Value);
}
installScript += string.Format(@"filein \""{0}\""", GetAbsPath(ConfigureMaxScript));
return installScript;
}
[SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static int InstallMaxPlugin(string maxExe)
{
if (Application.platform != RuntimePlatform.WindowsEditor)
{
Debug.LogError("The 3DsMax Unity plugin is Windows only, please try installing a Maya plugin instead");
return -1;
}
var installScript = GetInstallScript();
int ExitCode = 0;
try
{
if (!System.IO.File.Exists(maxExe))
{
Debug.LogError(string.Format("No 3DsMax installation found at {0}", maxExe));
return -1;
}
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
myProcess.StartInfo.FileName = maxExe;
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.Arguments = string.Format("-q -silent -mxs \"{0}\"", installScript);
myProcess.EnableRaisingEvents = true;
myProcess.Start();
string stderr = myProcess.StandardOutput.ReadToEnd();
myProcess.WaitForExit();
ExitCode = myProcess.ExitCode;
if (ExportSettings.instance.LaunchAfterInstallation)
{
LaunchDCCApplication(maxExe);
}
// TODO (UNI-29910): figure out what exactly causes this exit code + how to resolve
if (ExitCode == -1073740791)
{
Debug.Log(string.Format("Detected 3ds max exitcode {0} -- safe to ignore", ExitCode));
ExitCode = 0;
}
// print any errors
if (ExitCode != 0)
{
if (!string.IsNullOrEmpty(stderr))
{
Debug.LogError(string.Format("3ds Max installation error (exit code: {0}): {1}", ExitCode, stderr));
}
}
Debug.Log(string.Format("Ran max: [{0}]\nWith args [{1}]\nResult {2}",
maxExe, myProcess.StartInfo.Arguments, ExitCode));
}
catch (Exception e)
{
UnityEngine.Debug.LogError(string.Format("Exception failed to start Max ({0})", e.Message));
ExitCode = -1;
}
return ExitCode;
}
[SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public override int InstallIntegration(string exe)
{
return MaxIntegration.InstallMaxPlugin(exe);
}
/// <summary>
/// Determines if folder is already unzipped at the specified path
/// by checking if plugin exists at expected location.
/// </summary>
/// <returns><c>true</c> if folder is already unzipped at the specified path; otherwise, <c>false</c>.</returns>
/// <param name="path">Path.</param>
public override bool FolderAlreadyUnzippedAtPath(string path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}
return System.IO.File.Exists(System.IO.Path.Combine(path, MaxIntegration.PluginPath));
}
}
static class IntegrationsUI
{
/// <summary>
/// The path of the DCC executable.
/// </summary>
public static string GetDCCExe()
{
return ExportSettings.SelectedDCCPath;
}
/// <summary>
/// Gets the name of the selected DCC.
/// </summary>
/// <returns>The DCC name.</returns>
public static string GetDCCName()
{
return ExportSettings.SelectedDCCName;
}
/// <summary>
/// Opens a dialog showing whether the installation succeeded.
/// </summary>
/// <param name="dcc">Dcc name.</param>
private static void ShowSuccessDialog(string dcc, int exitCode)
{
string title, message, customMessage;
if (exitCode != 0)
{
title = string.Format("Failed to install {0} Integration.", dcc);
message = string.Format("Failed to configure {0}, please check logs (exitcode={1}).", dcc, exitCode);
}
else
{
if (ExportSettings.instance.LaunchAfterInstallation)
{
customMessage = "Installing Unity menu in {0}, application will open once installation is complete";
}
else
{
customMessage = "Enjoy the new Unity menu in {0}.";
}
title = string.Format("Completing installation of {0} Integration.", dcc);
message = string.Format(customMessage, dcc);
}
UnityEditor.EditorUtility.DisplayDialog(title, message, "Ok");
}
public static void InstallDCCIntegration()
{
var dccExe = GetDCCExe();
if (string.IsNullOrEmpty(dccExe))
{
return;
}
string dccType = System.IO.Path.GetFileNameWithoutExtension(dccExe).ToLower();
DCCIntegration dccIntegration;
if (dccType.Equals("maya"))
{
// could be Maya or Maya LT
if (GetDCCName().ToLower().Contains("lt"))
{
dccIntegration = new MayaLTIntegration();
}
else
{
dccIntegration = new MayaIntegration();
}
}
else if (dccType.Equals("3dsmax"))
{
dccIntegration = new MaxIntegration();
}
else
{
throw new System.NotImplementedException();
}
if (!GetIntegrationFolder(dccIntegration))
{
// failed to get integration folder
return;
}
int exitCode = dccIntegration.InstallIntegration(dccExe);
ShowSuccessDialog(dccIntegration.DccDisplayName, exitCode);
}
private static bool GetIntegrationFolder(DCCIntegration dcc)
{
// decompress zip file if it exists, otherwise try using default location
var zipPath = dcc.IntegrationZipFullPath;
if (System.IO.File.Exists(zipPath))
{
return DecompressIntegrationZipFile(zipPath, dcc);
}
dcc.SetIntegrationFolderPath(ExportSettings.IntegrationSavePath);
return true;
}
private static bool DecompressIntegrationZipFile(string zipPath, DCCIntegration dcc)
{
// prompt user to enter location to unzip file
var unzipFolder = EditorUtility.OpenFolderPanel(string.Format("Select Location to Save {0} Integration", dcc.DccDisplayName), ExportSettings.IntegrationSavePath, "");
if (string.IsNullOrEmpty(unzipFolder))
{
// user has cancelled, do nothing
return false;
}
ExportSettings.IntegrationSavePath = unzipFolder;
// check that this is a valid location to unzip the file
if (!DirectoryHasWritePermission(unzipFolder))
{
// display dialog to try again or cancel
var result = UnityEditor.EditorUtility.DisplayDialog("No Write Permission",
string.Format("Directory \"{0}\" does not have write access", unzipFolder),
"Select another Directory",
"Cancel"
);
if (result)
{
InstallDCCIntegration();
}
else
{
return false;
}
}
// if file already unzipped in this location, then prompt user
// if they would like to continue unzipping or use what is there
if (dcc.FolderAlreadyUnzippedAtPath(unzipFolder))
{
var result = UnityEditor.EditorUtility.DisplayDialogComplex("Integrations Exist at Path",
string.Format("Directory \"{0}\" already contains the decompressed integration", unzipFolder),
"Overwrite",
"Use Existing",
"Cancel"
);
if (result == 0)
{
DecompressZip(zipPath, unzipFolder);
}
else if (result == 2)
{
return false;
}
}
else
{
// unzip Integration folder
DecompressZip(zipPath, unzipFolder);
}
dcc.SetIntegrationFolderPath(unzipFolder);
return true;
}
/// <summary>
/// Make sure we can write to this directory.
/// Try creating a file in path directory, if it raises an error, then we can't
/// write here.
/// TODO: find a more reliable way to check this
/// </summary>
/// <returns><c>true</c>, if possible to write to path, <c>false</c> otherwise.</returns>
/// <param name="path">Path.</param>
public static bool DirectoryHasWritePermission(string path)
{
try
{
using (System.IO.FileStream fs = System.IO.File.Create(
System.IO.Path.Combine(
path,
System.IO.Path.GetRandomFileName()
),
1,
System.IO.FileOptions.DeleteOnClose)
)
{}
return true;
}
catch (Exception)
{
return false;
}
}
public static void DecompressZip(string zipPath, string destPath)
{
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
string zipApp;
switch (Application.platform)
{
case RuntimePlatform.WindowsEditor:
zipApp = "7z.exe";
break;
case RuntimePlatform.OSXEditor:
case RuntimePlatform.LinuxEditor:
zipApp = "7za";
break;
default:
throw new NotImplementedException();
}
myProcess.StartInfo.FileName = EditorApplication.applicationContentsPath + "/Tools/" + zipApp;
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.UseShellExecute = false;
// Command line flags used:
// x : extract the zip contents so that they maintain the file hierarchy
// -o : specify where to extract contents
// -r : recurse subdirectories
// -y : auto yes to all questions (without this Unity freezes as the process waits for a response)
myProcess.StartInfo.Arguments = string.Format("x \"{0}\" -o\"{1}\" -r -y", zipPath, destPath);
myProcess.EnableRaisingEvents = true;
myProcess.Start();
myProcess.WaitForExit();
// in case we unzip inside the Assets folder, make sure it updates
AssetDatabase.Refresh();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9e9d20e0d3cf8143aabac612af5c880
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,197 @@
#if !UNITY_2018_3_OR_NEWER
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEngine.Formats.Fbx.Exporter;
using System.Linq;
namespace DaddyFrosty.Fbx
{
internal class ManualUpdateEditorWindow : EditorWindow
{
int[] selectedNodesToDestroy;
int[] selectedNodesToRename;
FbxPrefabUtility m_fbxPrefabUtility;
FbxPrefab m_fbxPrefab;
GUIContent[] options;
List<string> m_nodesToCreate;
List<string> m_nodesToDestroy;
List<string> m_nodesToRename;
List<string> m_nodeNameToSuggest;
public bool Verbose { get { return UnityEditor.Formats.Fbx.Exporter.ExportSettings.instance.VerboseProperty; } }
public void Init(FbxPrefabUtility fbxPrefabUtility, FbxPrefab fbxPrefab)
{
if (fbxPrefab == null)
{
return;
}
FbxPrefabUtility.UpdateList updates = new FbxPrefabUtility.UpdateList(new FbxRepresentation(fbxPrefab.FbxHistory), fbxPrefab.FbxModel.transform, fbxPrefab);
m_fbxPrefabUtility = fbxPrefabUtility;
m_fbxPrefab = fbxPrefab;
// Convert Hashset into List
m_nodesToCreate = updates.NodesToCreate.ToList();
m_nodesToDestroy = updates.NodesToDestroy.ToList();
m_nodesToRename = updates.NodesToRename.ToList();
// Create the dropdown list
m_nodeNameToSuggest = new List<string>();
m_nodeNameToSuggest.AddRange(m_nodesToCreate);
m_nodeNameToSuggest.AddRange(m_nodesToRename);
// Keep track of the selected combo option in each type
selectedNodesToDestroy = new int[m_nodesToDestroy.Count];
selectedNodesToRename = new int[m_nodesToRename.Count];
// Default option for nodes to rename. Shows the current name mapping
for (int i = 0; i < m_nodesToRename.Count; i++)
{
for (int j = 0; j < m_nodeNameToSuggest.Count; j++)
{
if (m_nodeNameToSuggest[j] == m_nodesToRename[i])
{
// Add extra 1 for the [Delete] option
selectedNodesToRename[i] = j + 1;
}
}
}
}
void OnGUI()
{
// If there is nothing to map, sync prefab automatically and close the window
if (m_nodesToDestroy.Count == 0 && m_nodesToRename.Count == 0)
{
m_fbxPrefabUtility.SyncPrefab();
Close();
}
//Titles of the columns
GUILayout.BeginHorizontal();
GUILayout.Label("Unity Names", EditorStyles.boldLabel);
GUILayout.Label("FBX Names", EditorStyles.boldLabel);
GUILayout.EndHorizontal();
// List of nodes that will be destroyed on the Unity object, unless the user wants to map them
for (int i = 0; i < m_nodesToDestroy.Count; i++)
{
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField(m_nodesToDestroy[i]);
List<GUIContent> listFbxNames = new List<GUIContent>();
listFbxNames.Add(new GUIContent("[Delete]"));
for (int j = 0; j < m_nodeNameToSuggest.Count; j++)
{
listFbxNames.Add(new GUIContent(m_fbxPrefabUtility.GetFBXObjectName(m_nodeNameToSuggest[j])));
}
options = listFbxNames.ToArray();
selectedNodesToDestroy[i] = EditorGUILayout.Popup(selectedNodesToDestroy[i], options);
GUILayout.EndHorizontal();
}
// List of nodes that will be renamed on the Unity object, unless the user wants to map them or delete them
for (int i = 0; i < m_nodesToRename.Count; i++)
{
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField(m_fbxPrefabUtility.GetUnityObjectName(m_nodesToRename[i]));
List<GUIContent> listFbxNames = new List<GUIContent>();
listFbxNames.Add(new GUIContent("[Delete]"));
for (int j = 0; j < m_nodeNameToSuggest.Count; j++)
{
listFbxNames.Add(new GUIContent(m_fbxPrefabUtility.GetFBXObjectName(m_nodeNameToSuggest[j])));
}
options = listFbxNames.ToArray();
selectedNodesToRename[i] = EditorGUILayout.Popup(selectedNodesToRename[i], options);
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal();
if (GUILayout.Button("Apply Changes"))
{
ApplyChanges();
//Close editor window
Close();
}
if (GUILayout.Button("Cancel"))
{
//Close editor window
Close();
}
GUILayout.EndHorizontal();
}
void ApplyChanges()
{
// Nodes to Destroy have Unity names
for (int i = 0; i < m_nodesToDestroy.Count; i++)
{
// != [Delete]
if (selectedNodesToDestroy[i] != 0)
{
StringPair stringpair = new StringPair();
stringpair.FBXObjectName = options[selectedNodesToDestroy[i]].text;
stringpair.UnityObjectName = m_nodesToDestroy[i];
m_fbxPrefab.NameMapping.Add(stringpair);
if (Verbose)
{
Debug.Log("Mapped Unity: " + stringpair.UnityObjectName + " to FBX: " + stringpair.FBXObjectName);
}
}
}
// Nodes to Rename have FBX names
for (int i = 0; i < m_nodesToRename.Count; i++)
{
string currentUnityNodeName = m_fbxPrefabUtility.GetUnityObjectName(m_nodesToRename[i]);
// == [Delete]
if (selectedNodesToRename[i] == 0)
{
// Remove previous mapping
m_fbxPrefabUtility.RemoveMappingUnityObjectName(currentUnityNodeName);
}
else
{
if (currentUnityNodeName != m_fbxPrefabUtility.GetUnityObjectName(options[selectedNodesToRename[i]].text))
{
m_fbxPrefabUtility.RemoveMappingUnityObjectName(currentUnityNodeName);
StringPair stringpair = new StringPair();
stringpair.FBXObjectName = options[selectedNodesToRename[i]].text;
stringpair.UnityObjectName = currentUnityNodeName;
m_fbxPrefab.NameMapping.Add(stringpair);
if (Verbose)
{
Debug.Log("Mapped Unity: " + stringpair.UnityObjectName + " to FBX: " + stringpair.FBXObjectName);
}
}
else
{
if (Verbose)
{
Debug.Log("ALREADY Mapped Unity: " + currentUnityNodeName + " to FBX: " + options[selectedNodesToRename[i]].text);
}
}
}
}
m_fbxPrefabUtility.SyncPrefab();
}
}
}
#endif // !UNITY_2018_3_OR_NEWER

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c65c04234d53a1748bf2c0838e4ec66b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dbb835d424d3b1b47b0e81c4394ceb50
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0adf7beba2647444d937ef44201d8f7c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,100 @@
// #if ENABLE_FBX_RECORDER
// using System.Collections.Generic;
// using UnityEngine;
// using UnityEditor.Recorder;
// using UnityEditor.Recorder.Input;
//
// namespace DaddyFrosty.Fbx
// {
// class FbxRecorder : GenericRecorder<FbxRecorderSettings>
// {
// protected override void RecordFrame(RecordingSession ctx)
// {
// }
//
// private void EndRecordingInternal(RecordingSession session)
// {
// var settings = (FbxRecorderSettings)session.settings;
//
// foreach (var input in m_Inputs)
// {
// var aInput = (AnimationInput)input;
//
// if (aInput.GameObjectRecorder == null)
// continue;
//
// var clip = new AnimationClip();
//
// settings.FileNameGenerator.CreateDirectory(session);
//
// var absolutePath = FileNameGenerator.SanitizePath(settings.FileNameGenerator.BuildAbsolutePath(session));
// var clipName = absolutePath.Replace(FileNameGenerator.SanitizePath(Application.dataPath), "Assets");
//
// #if UNITY_2019_3_OR_NEWER
// var options = settings.GetCurveFilterOptions(settings.AnimationInputSettings.SimplyCurves);
// aInput.GameObjectRecorder.SaveToClip(clip, settings.FrameRate, options);
// #else
// aInput.GameObjectRecorder.SaveToClip(clip, settings.FrameRate);
// #endif
// if (settings.AnimationInputSettings.ClampedTangents)
// {
// FilterClip(clip);
// }
//
// var root = ((AnimationInputSettings)aInput.settings).gameObject;
// clip.name = "recorded_clip";
//
// var exportSettings = new ExportModelSettingsSerialize();
// exportSettings.SetAnimationSource(settings.TransferAnimationSource);
// exportSettings.SetAnimationDest(settings.TransferAnimationDest);
// exportSettings.SetObjectPosition(ObjectPosition.WorldAbsolute);
// // export animated skinned meshes so that blendshape animation will export
// exportSettings.SetAnimatedSkinnedMesh(true);
// var toInclude = Include.ModelAndAnim;
// if (!settings.ExportGeometry)
// {
// toInclude = Include.Anim;
// }
// exportSettings.SetModelAnimIncludeOption(toInclude);
//
// var exportData = new AnimationOnlyExportData();
// exportData.CollectDependencies(clip, root, exportSettings);
// var exportDataContainer = new Dictionary<GameObject, IExportData>();
// exportDataContainer.Add(root, exportData);
//
// ModelExporter.ExportObjects(clipName, new UnityEngine.Object[] { root }, exportSettings, exportDataContainer);
//
// aInput.GameObjectRecorder.ResetRecording();
// }
// }
//
// protected override void EndRecording(RecordingSession session)
// {
// if (session == null)
// {
// throw new System.ArgumentNullException("session");
// }
//
// if (Recording)
// {
// EndRecordingInternal(session);
// }
// base.EndRecording(session);
// }
//
// void FilterClip(AnimationClip clip)
// {
// foreach (var bind in AnimationUtility.GetCurveBindings(clip))
// {
// var curve = AnimationUtility.GetEditorCurve(clip, bind);
// for (var i = 0; i < curve.keys.Length; ++i)
// {
// AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
// AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
// }
// AnimationUtility.SetEditorCurve(clip, bind, curve);
// }
// }
// }
// }
// #endif // ENABLE_FBX_RECORDER

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c5070c8d7ee03d42bd534f93b4fc347
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,352 @@
// #if ENABLE_FBX_RECORDER
// using System.Collections.Generic;
// using UnityEngine;
// using UnityEditor.Recorder;
// using UnityEditor.Recorder.Input;
// using UnityEditor.Animations;
// using System;
//
// namespace DaddyFrosty.Fbx
// {
// /// <summary>
// /// Class describing the settings for the FBX Recorder.
// /// </summary>
// [RecorderSettings(typeof(FbxRecorder), "FBX")]
// public class FbxRecorderSettings : RecorderSettings
// {
// [SerializeField] bool m_exportGeometry = true;
//
// /// <summary>
// /// Option to export the geometry/meshes of the recorded hierarchy to FBX.
// /// </summary>
// public bool ExportGeometry
// {
// get
// {
// return m_exportGeometry;
// }
// set
// {
// m_exportGeometry = value;
// }
// }
//
// [SerializeField]
// private string m_animSourceBindingId;
// [SerializeField]
// private string m_animDestBindingId;
//
// /// <summary>
// /// Option to transfer the transform animation from this transform to the destination.
// /// This also transfers to the destination any animation on GameObjects between the source and the destination.
// /// </summary>
// public Transform TransferAnimationSource
// {
// get
// {
// if (string.IsNullOrEmpty(m_animSourceBindingId))
// return null;
//
// return GetBinding(m_animSourceBindingId);
// }
//
// set
// {
// if (!TransferAnimationSourceIsValid(value))
// {
// return;
// }
// if (string.IsNullOrEmpty(m_animSourceBindingId))
// m_animSourceBindingId = GenerateBindingId();
//
// SetBinding(m_animSourceBindingId, value);
// }
// }
//
// /// <summary>
// /// Option to transfer the transform animation from the source to this transform.
// /// This also transfers to the destination any animation on GameObjects between the source and the destination.
// /// </summary>
// public Transform TransferAnimationDest
// {
// get
// {
// if (string.IsNullOrEmpty(m_animDestBindingId))
// return null;
//
// return GetBinding(m_animDestBindingId);
// }
//
// set
// {
// if (!TransferAnimationDestIsValid(value))
// {
// return;
// }
// if (string.IsNullOrEmpty(m_animDestBindingId))
// m_animDestBindingId = GenerateBindingId();
//
// SetBinding(m_animDestBindingId, value);
// }
// }
//
// static readonly CurveFilterOptions DefaultCurveFilterOptions = new CurveFilterOptions()
// {
// keyframeReduction = true,
// positionError = 0.5f,
// rotationError = 0.5f,
// scaleError = 0.5f,
// floatError = 0.5f
// };
//
// static readonly CurveFilterOptions RegularCurveFilterOptions = new CurveFilterOptions
// {
// keyframeReduction = true
// };
//
// static readonly CurveFilterOptions NoCurveFilterOptions = new CurveFilterOptions
// {
// keyframeReduction = false
// };
//
// internal CurveFilterOptions GetCurveFilterOptions(AnimationInputSettings.CurveSimplificationOptions options)
// {
// switch (options)
// {
// case AnimationInputSettings.CurveSimplificationOptions.Lossy:
// return DefaultCurveFilterOptions;
// case AnimationInputSettings.CurveSimplificationOptions.Lossless:
// return RegularCurveFilterOptions;
// case AnimationInputSettings.CurveSimplificationOptions.Disabled:
// return NoCurveFilterOptions;
// default:
// throw new NotImplementedException();
// }
// }
//
// /// <summary>
// /// Get a binding ID for the transform, so that the reference survives domain reload. Maintaining a direct reference would not work
// /// as all scene objects are destroyed and recreated on reload (e.g. when entering/exiting playmode).
// /// </summary>
// /// <returns>Binding ID</returns>
// static string GenerateBindingId()
// {
// return GUID.Generate().ToString();
// }
//
// /// <summary>
// /// Get the Unity object (in this case transform), associated with the given binding ID.
// /// </summary>
// /// <param name="id">Binding ID</param>
// /// <returns>Transform associated with binding ID</returns>
// static Transform GetBinding(string id)
// {
// return BindingManager.Get(id) as Transform;
// }
//
// /// <summary>
// /// Set the binding ID to be associated with the given Unity object.
// /// This information will be saved on domain reload, so that the object can still be found
// /// with the binding ID.
// /// </summary>
// /// <param name="id">Binding ID</param>
// /// <param name="obj">Unity Object</param>
// static void SetBinding(string id, UnityEngine.Object obj)
// {
// BindingManager.Set(id, obj);
// }
//
// /// <summary>
// /// Determines whether p is an ancestor to t.
// /// </summary>
// /// <returns><c>true</c> if p is ancestor to t; otherwise, <c>false</c>.</returns>
// /// <param name="p">P.</param>
// /// <param name="t">T.</param>
// internal bool IsAncestor(Transform p, Transform t)
// {
// var curr = t;
// while (curr != null)
// {
// if (curr == p)
// {
// return true;
// }
// curr = curr.parent;
// }
// return false;
// }
//
// /// <summary>
// /// Determines whether t1 and t2 are in the same hierarchy.
// /// </summary>
// /// <returns><c>true</c> if t1 is in same hierarchy as t2; otherwise, <c>false</c>.</returns>
// /// <param name="t1">T1.</param>
// /// <param name="t2">T2.</param>
// internal bool IsInSameHierarchy(Transform t1, Transform t2)
// {
// return (IsAncestor(t1, t2) || IsAncestor(t2, t1));
// }
//
// internal bool TransferAnimationSourceIsValid(Transform newValue)
// {
// if (!newValue)
// {
// return true;
// }
//
// var selectedGO = m_AnimationInputSettings.gameObject;
//
// if (!selectedGO)
// {
// Debug.LogWarning("FbxRecorderSettings: no Objects selected for export, can't transfer animation");
// return false;
// }
//
// // source must be ancestor to dest
// if (TransferAnimationDest && !IsAncestor(newValue, TransferAnimationDest))
// {
// Debug.LogWarningFormat("FbxRecorderSettings: Source {0} must be an ancestor of {1}", newValue.name, TransferAnimationDest.name);
// return false;
// }
// // must be in same hierarchy as selected GO
// if (!selectedGO || !IsInSameHierarchy(newValue, selectedGO.transform))
// {
// Debug.LogWarningFormat("FbxRecorderSettings: Source {0} must be in the same hierarchy as {1}", newValue.name, selectedGO ? selectedGO.name : "the selected object");
// return false;
// }
// return true;
// }
//
// internal bool TransferAnimationDestIsValid(Transform newValue)
// {
// if (!newValue)
// {
// return true;
// }
//
// var selectedGO = m_AnimationInputSettings.gameObject;
//
// if (!selectedGO)
// {
// Debug.LogWarning("FbxRecorderSettings: no Objects selected for export, can't transfer animation");
// return false;
// }
//
// // source must be ancestor to dest
// if (TransferAnimationSource && !IsAncestor(TransferAnimationSource, newValue))
// {
// Debug.LogWarningFormat("FbxRecorderSettings: Destination {0} must be a descendant of {1}", newValue.name, TransferAnimationSource.name);
// return false;
// }
// // must be in same hierarchy as selected GO
// if (!selectedGO || !IsInSameHierarchy(newValue, selectedGO.transform))
// {
// Debug.LogWarningFormat("FbxRecorderSettings: Destination {0} must be in the same hierarchy as {1}", newValue.name, selectedGO ? selectedGO.name : "the selected object");
// return false;
// }
// return true;
// }
//
// [SerializeField] AnimationInputSettings m_AnimationInputSettings = new AnimationInputSettings();
//
// /// <summary>
// /// Stores the reference to the current FBX Recorder's input settings.
// /// </summary>
// public AnimationInputSettings AnimationInputSettings
// {
// get { return m_AnimationInputSettings; }
// set { m_AnimationInputSettings = value; }
// }
//
// /// <summary>
// /// Default constructor for FbxRecorderSettings.
// /// </summary>
// public FbxRecorderSettings()
// {
// var goWildcard = DefaultWildcard.GeneratePattern("GameObject");
//
// FileNameGenerator.AddWildcard(goWildcard, GameObjectNameResolver);
// FileNameGenerator.AddWildcard(DefaultWildcard.GeneratePattern("GameObjectScene"), GameObjectSceneNameResolver);
//
// FileNameGenerator.ForceAssetsFolder = false;
// FileNameGenerator.Root = OutputPath.Root.AssetsFolder;
// FileNameGenerator.FileName = "animation_" + goWildcard + "_" + DefaultWildcard.Take;
// }
//
// string GameObjectNameResolver(RecordingSession session)
// {
// var go = m_AnimationInputSettings.gameObject;
// return go != null ? go.name : "None";
// }
//
// string GameObjectSceneNameResolver(RecordingSession session)
// {
// var go = m_AnimationInputSettings.gameObject;
// return go != null ? go.scene.name : "None";
// }
//
// /// <summary>
// /// Indicates if the current platform is supported (True) or not (False).
// /// </summary>
// /// <remarks>
// /// FBX Recorder currently supports the following platforms: LinuxEditor, OSXEditor, WindowsEditor.
// /// </remarks>
// public override bool IsPlatformSupported
// {
// get
// {
// return Application.platform == RuntimePlatform.LinuxEditor ||
// Application.platform == RuntimePlatform.OSXEditor ||
// Application.platform == RuntimePlatform.WindowsEditor;
// }
// }
//
// /// <summary>
// /// Stores the list of Input settings required by this Recorder.
// /// </summary>
// public override IEnumerable<RecorderInputSettings> InputsSettings
// {
// get { yield return m_AnimationInputSettings; }
// }
//
// /// <summary>
// /// Tests if the Recorder has any errors.
// /// </summary>
// /// <param name="errors">List of errors encountered.</param>
// protected override void GetErrors(List<string> errors)
// {
// base.GetErrors(errors);
//
// if (m_AnimationInputSettings.gameObject == null)
// {
// if (errors == null)
// {
// throw new System.ArgumentNullException("errors");
// }
// errors.Add("No input object set");
// }
// }
//
// /// <summary>
// /// Override this method if you need to do any post treatment after duplicating this Recorder in the Recorder Window.
// /// </summary>
// public override void OnAfterDuplicate()
// {
// m_AnimationInputSettings.DuplicateExposedReference();
// }
//
// void OnDestroy()
// {
// m_AnimationInputSettings.ClearExposedReference();
// }
//
// /// <summary>
// /// Stores the file extension this Recorder uses (without the dot).
// /// </summary>
// protected override string Extension
// {
// get { return "fbx"; }
// }
// }
// }
// #endif // ENABLE_FBX_RECORDER

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c66f1cb43a625494b931ece4280e5b23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
// #if ENABLE_FBX_RECORDER
// using System.Collections;
// using System.Collections.Generic;
// using UnityEngine;
// using UnityEditor.Recorder;
// using UnityEditor;
//
// namespace DaddyFrosty.Fbx
// {
// [CustomEditor(typeof(FbxRecorderSettings))]
// class FbxRecorderSettingsEditor : RecorderEditor
// {
// protected override void FileTypeAndFormatGUI()
// {
// EditorGUILayout.LabelField("Format", "FBX");
//
// FbxRecorderSettings settings = target as FbxRecorderSettings;
//
// settings.ExportGeometry = EditorGUILayout.Toggle("Export Geometry", settings.ExportGeometry);
// }
//
// protected override void OnEncodingGui()
// {
// base.OnEncodingGui();
//
// DrawSeparator();
//
// 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."
// ));
//
// FbxRecorderSettings settings = target as FbxRecorderSettings;
//
// settings.TransferAnimationSource = EditorGUILayout.ObjectField("Source", settings.TransferAnimationSource, typeof(Transform), allowSceneObjects: true) as Transform;
// settings.TransferAnimationDest = EditorGUILayout.ObjectField("Destination", settings.TransferAnimationDest, typeof(Transform), allowSceneObjects: true) as Transform;
// }
// }
// }
// #endif // ENABLE_FBX_RECORDER

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d34f7bd290a32e041869e7e04815ee26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TextureExport : MonoBehaviour
{
public Texture2D tex;
[ContextMenu("Export")]
public void Export()
{
byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/savedScreen.png", bytes);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7524d6c15639ee04f83432f108698644
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,2 +1,11 @@
# FrostyToolz
A collection of unity editor tools I've made / improved
# FBX Exporter
I spent countless nights fixing Unity's FBX Exporter. The code sucks, the exporter sucks.
<br>
With my version you can actually export skinned meshes properly with animations etc.
<br>
It's been really long since I made it so honestly don't remember exactly what it helped with but it was bones and skinned mesh renderer exporting to FBX.
## Pro Tip
FBX is the most supported file type by the Exporter, even Unity's.