From d63609bedda1248c6c29a38f2b94ba0dfcbaf294 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 18 Dec 2018 18:15:38 -0800 Subject: [PATCH] more file workflow changes including export project popup window, add readme --- .gitignore | 1 + .../Assets/Editor/AvatarExporter.cs | 378 ++++++++++++------ tools/unity-avatar-exporter/Assets/README.txt | 11 + .../Assets/README.txt.meta | 7 + .../avatarExporter.unitypackage | Bin 5864 -> 7145 bytes tools/unity-avatar-exporter/packager.bat | 2 +- 6 files changed, 279 insertions(+), 120 deletions(-) create mode 100644 tools/unity-avatar-exporter/Assets/README.txt create mode 100644 tools/unity-avatar-exporter/Assets/README.txt.meta diff --git a/.gitignore b/.gitignore index 09b58d71ef..f3f6954974 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,4 @@ tools/jsdoc/package-lock.json tools/unity-avatar-exporter/Library tools/unity-avatar-exporter/Packages tools/unity-avatar-exporter/ProjectSettings +tools/unity-avatar-exporter/Temp \ No newline at end of file diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index b62f51f4e8..2e17b04643 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -12,8 +12,8 @@ using System; using System.IO; using System.Collections.Generic; -public class AvatarExporter : MonoBehaviour { - public static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { +class AvatarExporter : MonoBehaviour { + static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { {"Chest", "Spine1"}, {"Head", "Head"}, {"Hips", "Hips"}, @@ -70,7 +70,7 @@ public class AvatarExporter : MonoBehaviour { {"UpperChest", "Spine2"}, }; - public static readonly Dictionary referenceAbsoluteRotations = new Dictionary { + static readonly Dictionary referenceAbsoluteRotations = new Dictionary { {"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, {"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)}, {"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, @@ -127,55 +127,176 @@ public class AvatarExporter : MonoBehaviour { {"Spine2", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, }; - public static Dictionary userBoneToHumanoidMappings = new Dictionary(); - public static Dictionary userParentNames = new Dictionary(); - public static Dictionary userAbsoluteRotations = new Dictionary(); + static Dictionary userBoneToHumanoidMappings = new Dictionary(); + static Dictionary userParentNames = new Dictionary(); + static Dictionary userAbsoluteRotations = new Dictionary(); + + static string assetPath = ""; + static string assetName = ""; + static HumanDescription humanDescription; [MenuItem("High Fidelity/Export New Avatar")] - public static void ExportNewAvatar() { + static void ExportNewAvatar() { ExportSelectedAvatar(false); } - [MenuItem("High Fidelity/Update Avatar")] - public static void UpdateAvatar() { + [MenuItem("High Fidelity/Update Existing Avatar")] + static void UpdateAvatar() { ExportSelectedAvatar(true); } - public static void ExportSelectedAvatar(bool updateAvatar) { + static void ExportSelectedAvatar(bool updateAvatar) { string[] guids = Selection.assetGUIDs; if (guids.Length != 1) { if (guids.Length == 0) { - EditorUtility.DisplayDialog("Error", "Please select an asset to export", "Ok"); + EditorUtility.DisplayDialog("Error", "Please select an asset to export.", "Ok"); } else { - EditorUtility.DisplayDialog("Error", "Please select a single asset to export", "Ok"); + EditorUtility.DisplayDialog("Error", "Please select a single asset to export.", "Ok"); } return; } - string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); - ModelImporter importer = ModelImporter.GetAtPath(assetPath) as ModelImporter; - if (assetPath.LastIndexOf(".fbx") == -1 || importer == null) { - EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export", "Ok"); + assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); + assetName = Path.GetFileNameWithoutExtension(assetPath); + ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + if (assetPath.LastIndexOf(".fbx") == -1 || modelImporter == null) { + EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok"); return; } - if (importer.animationType != ModelImporterAnimationType.Human) { - EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid", "Ok"); + if (modelImporter.animationType != ModelImporterAnimationType.Human) { + EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in the Rig section of it's Inspector window.", "Ok"); return; } + + humanDescription = modelImporter.humanDescription; + if (!SetJointMappingsAndParentNames()) { + return; + } + + string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); + string hifiFolder = documentsFolder + "\\High Fidelity Projects"; + if (updateAvatar) { // Update Existing Avatar menu option + bool copyModelToExport = false; + string initialPath = Directory.Exists(hifiFolder) ? hifiFolder : documentsFolder; + + // open file explorer defaulting to hifi folder in user documents to select target fst to update + string exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", initialPath, "fst"); + if (exportFstPath.Length == 0) { // file selection cancelled + return; + } + string exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; + + if (File.Exists(exportModelPath)) { + // if the fbx in Unity Assets is newer than the fbx in the target export + // folder or vice-versa then ask to replace the older fbx with the newer fbx + DateTime assetModelWriteTime = File.GetLastWriteTime(assetPath); + DateTime targetModelWriteTime = File.GetLastWriteTime(exportModelPath); + if (assetModelWriteTime > targetModelWriteTime) { + int option = EditorUtility.DisplayDialogComplex("Error", "The " + assetName + + ".fbx model in the Unity Assets folder is newer than the " + exportModelPath + + " model.\n\nDo you want to replace the older .fbx with the newer .fbx?", + "Yes", "No", "Cancel"); + if (option == 2) { // Cancel + return; + } + copyModelToExport = option == 0; + } else if (assetModelWriteTime < targetModelWriteTime) { + int option = EditorUtility.DisplayDialogComplex("Error", "The " + exportModelPath + + " model is newer than the " + assetName + ".fbx model in the Unity Assets folder." + + "\n\nDo you want to replace the older .fbx with the newer .fbx and re-import it?", + "Yes", "No" , "Cancel"); + if (option == 2) { // Cancel + return; + } else if (option == 0) { // Yes - copy model to Unity project + // delete existing fbx and associated meta file in Unity Assets + File.Delete(assetPath); + File.Delete(assetPath + ".meta"); + AssetDatabase.Refresh(); + + // copy the fbx from the project folder to Unity Assets and import it + File.Copy(exportModelPath, assetPath); + AssetDatabase.ImportAsset(assetPath); + + // set model to Humanoid animation type and force another refresh on it to process Humanoid + modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + modelImporter.animationType = ModelImporterAnimationType.Human; + EditorUtility.SetDirty(modelImporter); + modelImporter.SaveAndReimport(); + humanDescription = modelImporter.humanDescription; + + // redo joint mappings and parent names due to the fbx change + SetJointMappingsAndParentNames(); + } + } + } else { + // if no matching fbx exists in the target export folder then ask to copy fbx over + int option = EditorUtility.DisplayDialogComplex("Error", "There is no existing " + exportModelPath + + " model.\n\nDo you want to copy over the " + assetName + + ".fbx model from the Unity Assets folder?", "Yes", "No", "Cancel"); + if (option == 2) { // Cancel + return; + } + copyModelToExport = option == 0; + } + + // delete any existing fbx if we agreed to overwrite it, and copy asset fbx over + if (copyModelToExport) { + if (File.Exists(exportModelPath)) { + File.Delete(exportModelPath); + } + File.Copy(assetPath, exportModelPath); + } + + // delete any existing fst since we are re-exporting it + // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file + if (File.Exists(exportFstPath)) { + File.Delete(exportFstPath); + } + + WriteFST(exportFstPath); + } else { // Export New Avatar menu option + // create High Fidelity folder in user documents folder if it doesn't exist + if (!Directory.Exists(hifiFolder)) { + Directory.CreateDirectory(hifiFolder); + } + + // open a popup window to enter new export project name and project location + ExportProjectWindow window = ScriptableObject.CreateInstance(); + window.Init(hifiFolder, OnExportProjectWindowClose); + } + } + + static void OnExportProjectWindowClose(string projectDirectory) { + // copy the fbx from the Unity Assets folder to the project directory, + // and then write out the fst file to the project directory + string exportModelPath = projectDirectory + assetName + ".fbx"; + string exportFstPath = projectDirectory + "avatar.fst"; + File.Copy(assetPath, exportModelPath); + WriteFST(exportFstPath); - userBoneToHumanoidMappings.Clear(); + // create empty Textures and Scripts folders in the project directory + string texturesDirectory = projectDirectory + "\\textures"; + string scriptsDirectory = projectDirectory + "\\scripts"; + Directory.CreateDirectory(texturesDirectory); + Directory.CreateDirectory(scriptsDirectory); + + // open File Explorer to the project directory once finished + System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); + } + + static bool SetJointMappingsAndParentNames() { userParentNames.Clear(); - userAbsoluteRotations.Clear(); + userBoneToHumanoidMappings.Clear(); // instantiate a game object of the user avatar to save out bone parents then destroy it UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); - if (avatarResource) { - GameObject assetGameObject = (GameObject)Instantiate(avatarResource); - SetParentNames(assetGameObject.transform, userParentNames); - DestroyImmediate(assetGameObject); + if (!avatarResource) { + return false; } + GameObject assetGameObject = (GameObject)Instantiate(avatarResource); + SetParentNames(assetGameObject.transform, userParentNames); + DestroyImmediate(assetGameObject); // store joint mappings only for joints that exist in hifi and verify missing joints - HumanDescription humanDescription = importer.humanDescription; HumanBone[] boneMap = humanDescription.human; string chestUserBone = ""; string neckUserBone = ""; @@ -195,11 +316,11 @@ public class AvatarExporter : MonoBehaviour { } if (!userBoneToHumanoidMappings.ContainsValue("Hips")) { EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok"); - return; + return false; } if (!userBoneToHumanoidMappings.ContainsValue("Spine")) { EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok"); - return; + return false; } if (!userBoneToHumanoidMappings.ContainsValue("Chest")) { // check to see if there is a child of Spine that could be mapped to Chest @@ -222,7 +343,7 @@ public class AvatarExporter : MonoBehaviour { chestUserBone = spineChild; } else { EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); - return; + return false; } } if (!userBoneToHumanoidMappings.ContainsValue("UpperChest")) { @@ -240,66 +361,13 @@ public class AvatarExporter : MonoBehaviour { userBoneToHumanoidMappings[chestUserBone] = "UpperChest"; } } - - bool copyModelToExport = false; - string exportFstPath, exportModelPath; - string assetName = Path.GetFileNameWithoutExtension(assetPath); - string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); - if (updateAvatar) { - // open file explorer defaulting to user documents folder to select target fst to update - exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", documentsFolder, "fst"); - if (exportFstPath.Length == 0) { // file selection cancelled - return; - } - exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; - - if (File.Exists(exportModelPath)) { - // if the fbx in Unity Assets/Resources is newer than the fbx in the - // target export folder or vice-versa then ask to copy fbx over - DateTime assetModelWriteTime = File.GetLastWriteTime(assetPath); - DateTime targetModelWriteTime = File.GetLastWriteTime(exportModelPath); - if (assetModelWriteTime > targetModelWriteTime) { - copyModelToExport = EditorUtility.DisplayDialog("Error", "The " + assetName + - ".fbx model in the Unity Assets/Resources folder is newer than the " + exportModelPath + - " model. Do you want to copy the newer .fbx model over?" , "Yes", "No"); - } else if (assetModelWriteTime < targetModelWriteTime) { - bool copyModelToUnity = EditorUtility.DisplayDialog("Error", "The " + exportModelPath + - " model is newer than the " + assetName + - ".fbx model in the Unity Assets/Resources folder. Do you want to copy the newer .fbx model over?", - "Yes", "No"); - if (copyModelToUnity) { - File.Delete(assetPath); - File.Copy(exportModelPath, assetPath); - } - } - } else { - // if no matching fbx exists in the target export folder then ask to copy fbx over - copyModelToExport = EditorUtility.DisplayDialog("Error", "There is no existing " + exportModelPath + - " model. Do you want to copy over the " + assetName + - ".fbx model from the Unity Assets/Resources folder?" , "Yes", "No"); - } - } else { - // open folder explorer defaulting to user documents folder to select target folder to export fst and fbx to - if (!SelectExportFolder(assetName, documentsFolder, out exportFstPath, out exportModelPath)) { - return; - } - copyModelToExport = true; - } - - // delete any existing fbx since we would have agreed to overwrite it, and copy asset fbx over - if (copyModelToExport) { - if (File.Exists(exportModelPath)) { - File.Delete(exportModelPath); - } - File.Copy(assetPath, exportModelPath); - } - - // delete any existing fst since we agreed to overwrite it or are updating it - // TODO: should updating fst only rewrite joint mappings and joint rotation offsets? - if (File.Exists(exportFstPath)) { - File.Delete(exportFstPath); - } + return true; + } + + static void WriteFST(string exportFstPath) { + userAbsoluteRotations.Clear(); + // write out core fields to top of fst file File.WriteAllText(exportFstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " + assetName + ".fbx\n" + "texdir = textures\n"); @@ -310,6 +378,7 @@ public class AvatarExporter : MonoBehaviour { File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + jointMapping.Key + "\n"); } + // calculate and write out joint rotation offsets to fst file SkeletonBone[] skeletonMap = humanDescription.skeleton; foreach (SkeletonBone userBone in skeletonMap) { string userBoneName = userBone.name; @@ -353,37 +422,8 @@ public class AvatarExporter : MonoBehaviour { } } } - - public static bool SelectExportFolder(string assetName, string initialPath, out string fstPath, out string modelPath) { - string selectedPath = EditorUtility.OpenFolderPanel("Select export location", initialPath, ""); - if (selectedPath.Length == 0) { // folder selection cancelled - fstPath = ""; - modelPath = ""; - return false; - } - fstPath = selectedPath + "/" + assetName + ".fst"; - modelPath = selectedPath + "/" + assetName + ".fbx"; - bool fstExists = File.Exists(fstPath); - bool modelExists = File.Exists(modelPath); - if (fstExists || modelExists) { - string overwriteMessage; - if (fstExists && modelExists) { - overwriteMessage = assetName + ".fst and " + assetName + - ".fbx already exist here, would you like to overwrite them?"; - } else if (fstExists) { - overwriteMessage = assetName + ".fst already exists here, would you like to overwrite it?"; - } else { - overwriteMessage = assetName + ".fbx already exists here, would you like to overwrite it?"; - } - bool overwrite = EditorUtility.DisplayDialog("Error", overwriteMessage, "Yes", "No"); - if (!overwrite) { - return SelectExportFolder(assetName, selectedPath, out fstPath, out modelPath); - } - } - return true; - } - - public static void SetParentNames(Transform modelBone, Dictionary parentNames) { + + static void SetParentNames(Transform modelBone, Dictionary parentNames) { for (int i = 0; i < modelBone.childCount; i++) { SetParentNames(modelBone.GetChild(i), parentNames); } @@ -394,7 +434,7 @@ public class AvatarExporter : MonoBehaviour { } } - public static string FindLastRequiredParentBone(string currentBone) { + static string FindLastRequiredParentBone(string currentBone) { string result = currentBone; while (result != "root" && !userBoneToHumanoidMappings.ContainsKey(result)) { result = userParentNames[result]; @@ -402,3 +442,103 @@ public class AvatarExporter : MonoBehaviour { return result; } } + +class ExportProjectWindow : EditorWindow { + string projectName = ""; + string projectLocation = ""; + string projectDirectory = ""; + string errorLabel = ""; + + public delegate void OnCloseDelegate(string projectDirectory); + OnCloseDelegate onCloseCallback; + + public void Init(string initialPath, OnCloseDelegate closeCallback) { + projectLocation = initialPath; + onCloseCallback = closeCallback; + ShowUtility(); + } + + void OnGUI() { + GUIStyle buttonStyle = new GUIStyle(GUI.skin.button); + buttonStyle.fontSize = 20; + GUIStyle labelStyle = new GUIStyle(GUI.skin.label); + labelStyle.fontSize = 16; + GUIStyle errorStyle = new GUIStyle(GUI.skin.label); + errorStyle.fontSize = 12; + errorStyle.normal.textColor = Color.red; + GUIStyle textStyle = new GUIStyle(GUI.skin.textField); + textStyle.fontSize = 16; + + GUILayout.Space(10); + + GUILayout.Label("Export project name:", labelStyle); + projectName = GUILayout.TextField(projectName, textStyle); + + GUILayout.Space(10); + + GUILayout.Label("Export project location:", labelStyle); + projectLocation = GUILayout.TextField(projectLocation, textStyle); + + if (GUILayout.Button("Browse", buttonStyle)) { + string result = EditorUtility.OpenFolderPanel("Select export location", projectLocation, ""); + if (result.Length > 0) { // folder selection not cancelled + projectLocation = result.Replace('/', '\\'); + } + } + + GUILayout.Label(errorLabel, errorStyle); + + GUILayout.Space(30); + + bool export = false; + if (GUILayout.Button("Export", buttonStyle)) { + export = true; + if (!CheckForErrors(true)) { + Close(); + onCloseCallback(projectDirectory); + } + } + + if (GUILayout.Button("Cancel", buttonStyle)) { + Close(); + } + + if (GUI.changed && !export) { + CheckForErrors(false); + } + } + + bool CheckForErrors(bool exporting) { + errorLabel = ""; + projectDirectory = projectLocation + "\\" + projectName + "\\"; + if (projectName.Length > 0) { + if (Directory.Exists(projectDirectory)) { + errorLabel = "A folder with the name " + projectName + " already exists at that location.\nPlease choose a different project name or location."; + return true; + } + } + if (projectLocation.Length > 0) { + if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') { + errorLabel = "Project location is invalid. Please choose a different project location."; + return true; + } + } + if (exporting) { + if (projectName.Length == 0) { + errorLabel = "Please define a project name."; + return true; + } else if (projectLocation.Length == 0) { + errorLabel = "Please define a project location."; + return true; + } else { + try { + Directory.CreateDirectory(projectDirectory); + } catch { + errorLabel = "Project location is invalid. Please choose a different project location."; + return true; + } + } + } + return false; + } +} diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt new file mode 100644 index 0000000000..7e6c6d4f48 --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -0,0 +1,11 @@ +To create a new avatar project: +1. Import your .fbx avatar model into Unity Assets (drag and drop file into Assets window or use Assets menu > Import New Assets). +2. Select the .fbx avatar that you imported in the Assets window, and in the Inspector window set the Animation Type to Humanoid and choose Apply. +3. With the .fbx avatar still selected, select High Fidelity menu > Export New Avatar. +4. Select a name for your avatar project (this will be used to create a directory with that name), as well as the target location for your project folder. +5. Once it is exported, your project directory will open in File Explorer. + +To update an existing avatar project: +1. Select the existing .fbx avatar in the Assets window that you would like to re-export. +2. Select High Fidelity menu > Update Avatar and choose the .fst file you would like to update. +3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file. \ No newline at end of file diff --git a/tools/unity-avatar-exporter/Assets/README.txt.meta b/tools/unity-avatar-exporter/Assets/README.txt.meta new file mode 100644 index 0000000000..d8bc5b9b66 --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 71e72751b2810fc4993ff53291c430b6 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 4134d6f09a52e356fd3e6a543b2bcb134a7daf5b..d1cd4efe8caa08bbb332eacb613993b5e232244f 100644 GIT binary patch literal 7145 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 diff --git a/tools/unity-avatar-exporter/packager.bat b/tools/unity-avatar-exporter/packager.bat index 99932f1ead..55b59a9db6 100644 --- a/tools/unity-avatar-exporter/packager.bat +++ b/tools/unity-avatar-exporter/packager.bat @@ -1 +1 @@ -"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets/Editor" "avatarExporter.unitypackage" +"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets" "avatarExporter.unitypackage"