Init
This commit is contained in:
8
Assets/DaddyFrosty/Editor.meta
Normal file
8
Assets/DaddyFrosty/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7844f4d67c65f134e941813c38650c07
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/DaddyFrosty/Editor/FBX.meta
Normal file
8
Assets/DaddyFrosty/Editor/FBX.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8e069023e8a06b498c07049662f1c63
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/DaddyFrosty/Editor/FBX/Unity.meta
Normal file
8
Assets/DaddyFrosty/Editor/FBX/Unity.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4731a204ecb13a942aad1252a74b8f71
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
989
Assets/DaddyFrosty/Editor/FBX/Unity/ExportModelEditorWindow.cs
Normal file
989
Assets/DaddyFrosty/Editor/FBX/Unity/ExportModelEditorWindow.cs
Normal 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
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 954c3dda5c0398a4292e34227b6cebad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
696
Assets/DaddyFrosty/Editor/FBX/Unity/ExportModelSettings.cs
Normal file
696
Assets/DaddyFrosty/Editor/FBX/Unity/ExportModelSettings.cs
Normal 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;
|
||||
// }
|
||||
// }
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63180e849b84d344d81d213c1e4e944b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1959
Assets/DaddyFrosty/Editor/FBX/Unity/ExportSettings.cs
Normal file
1959
Assets/DaddyFrosty/Editor/FBX/Unity/ExportSettings.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/DaddyFrosty/Editor/FBX/Unity/ExportSettings.cs.meta
Normal file
11
Assets/DaddyFrosty/Editor/FBX/Unity/ExportSettings.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76a11434abcb460489b23b95aa26ee71
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
5079
Assets/DaddyFrosty/Editor/FBX/Unity/FbxExporter.cs
Normal file
5079
Assets/DaddyFrosty/Editor/FBX/Unity/FbxExporter.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/DaddyFrosty/Editor/FBX/Unity/FbxExporter.cs.meta
Normal file
11
Assets/DaddyFrosty/Editor/FBX/Unity/FbxExporter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 849017d7ef6c589448dcbf335cda940f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
210
Assets/DaddyFrosty/Editor/FBX/Unity/IExportData.cs
Normal file
210
Assets/DaddyFrosty/Editor/FBX/Unity/IExportData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/DaddyFrosty/Editor/FBX/Unity/IExportData.cs.meta
Normal file
11
Assets/DaddyFrosty/Editor/FBX/Unity/IExportData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 020f058b66da64746830aa60391539dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
3
Assets/DaddyFrosty/Editor/FBX/Unity/Misc.meta
Normal file
3
Assets/DaddyFrosty/Editor/FBX/Unity/Misc.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eeebc6a06d294fb494ef7a9697faba57
|
||||
timeCreated: 1671143407
|
3
Assets/DaddyFrosty/Editor/FBX/Unity/Misc/AssetsMenu.meta
Normal file
3
Assets/DaddyFrosty/Editor/FBX/Unity/Misc/AssetsMenu.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cae2b7a32f684525875a7ef9f0edf5ce
|
||||
timeCreated: 1671154957
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f227242526649c4a3668a2adfc1e184
|
||||
timeCreated: 1671162404
|
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94d85be9434e481896f4c461319d54cf
|
||||
timeCreated: 1671163247
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 500cbd797b024292aa9e25661f50abb9
|
||||
timeCreated: 1671160494
|
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f6b5c316ea646e5b75b2c473998168c
|
||||
timeCreated: 1671160149
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 572d5e1936574f0182ffc3896882391d
|
||||
timeCreated: 1671159589
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbc05f6a5648492b91c9799cfbf77423
|
||||
timeCreated: 1671158894
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7d9c7fd478c45cda37a8e4078ab8821
|
||||
timeCreated: 1671154962
|
46
Assets/DaddyFrosty/Editor/FBX/Unity/Misc/CustomFBX.cs
Normal file
46
Assets/DaddyFrosty/Editor/FBX/Unity/Misc/CustomFBX.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b09b952f5cb40789169ec3d2ffe71da
|
||||
timeCreated: 1671143548
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 736e57e7cca54e95b0447f83e419380c
|
||||
timeCreated: 1671142838
|
@@ -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}";
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ff8397ea8124438a6e486833291fe11
|
||||
timeCreated: 1671142843
|
214
Assets/DaddyFrosty/Editor/FBX/Unity/Misc/PixelBenchmark/MathX.cs
Normal file
214
Assets/DaddyFrosty/Editor/FBX/Unity/Misc/PixelBenchmark/MathX.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45978a3e2c074d2a8a7129d9ccbfb1c5
|
||||
timeCreated: 1671150414
|
8
Assets/DaddyFrosty/Editor/FBX/Unity/Sources.meta
Normal file
8
Assets/DaddyFrosty/Editor/FBX/Unity/Sources.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6000a9455111214ebda68b014dba7fa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
135
Assets/DaddyFrosty/Editor/FBX/Unity/Sources/CameraVisitor.cs
Normal file
135
Assets/DaddyFrosty/Editor/FBX/Unity/Sources/CameraVisitor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f112f025b26d64345936e9d9c6752f50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
310
Assets/DaddyFrosty/Editor/FBX/Unity/Sources/CustomExtensions.cs
Normal file
310
Assets/DaddyFrosty/Editor/FBX/Unity/Sources/CustomExtensions.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1719f38fb4e4691439146f2d8d66cc22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6d24eae8578a914a8169f974b2cc03c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 573e216414ac007408e419f5bd6f79b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dc7ecef19dfd6a4d84f3e2e890dfe19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6b42abf99d4bdf4c8798184fb5c936d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
276
Assets/DaddyFrosty/Editor/FBX/Unity/Sources/FbxRotationCurve.cs
Normal file
276
Assets/DaddyFrosty/Editor/FBX/Unity/Sources/FbxRotationCurve.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1df2cf4a1fa06b64bac747b1d3730d6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9e9d20e0d3cf8143aabac612af5c880
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c65c04234d53a1748bf2c0838e4ec66b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbb835d424d3b1b47b0e81c4394ceb50
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0adf7beba2647444d937ef44201d8f7c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c5070c8d7ee03d42bd534f93b4fc347
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c66f1cb43a625494b931ece4280e5b23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d34f7bd290a32e041869e7e04815ee26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
16
Assets/DaddyFrosty/Editor/TextureExport.cs
Normal file
16
Assets/DaddyFrosty/Editor/TextureExport.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Assets/DaddyFrosty/Editor/TextureExport.cs.meta
Normal file
11
Assets/DaddyFrosty/Editor/TextureExport.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7524d6c15639ee04f83432f108698644
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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.
|
Reference in New Issue
Block a user