Merge pull request #258 from akamicah/update-unity-avatar-exporter

Update unity avatar exporter
This commit is contained in:
ksuprynowicz 2023-04-17 22:57:26 +02:00 committed by GitHub
commit 4d782bc28b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 286 additions and 241 deletions

View file

@ -2,10 +2,12 @@
// //
// Created by David Back on 28 Nov 2018 // Created by David Back on 28 Nov 2018
// Copyright 2018 High Fidelity, Inc. // Copyright 2018 High Fidelity, Inc.
// Copyright 2022 Overte e.V.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
using UnityEditor; using UnityEditor;
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
using UnityEngine; using UnityEngine;
@ -17,7 +19,7 @@ using System.Text.RegularExpressions;
class AvatarExporter : MonoBehaviour { class AvatarExporter : MonoBehaviour {
// update version number for every PR that changes this file, also set updated version in README file // update version number for every PR that changes this file, also set updated version in README file
static readonly string AVATAR_EXPORTER_VERSION = "0.4.1"; static readonly string AVATAR_EXPORTER_VERSION = "0.5.0";
static readonly float HIPS_MIN_Y_PERCENT_OF_HEIGHT = 0.03f; static readonly float HIPS_MIN_Y_PERCENT_OF_HEIGHT = 0.03f;
static readonly float BELOW_GROUND_THRESHOLD_PERCENT_OF_HEIGHT = -0.15f; static readonly float BELOW_GROUND_THRESHOLD_PERCENT_OF_HEIGHT = -0.15f;
@ -61,7 +63,7 @@ class AvatarExporter : MonoBehaviour {
"2017.4.15f1", "2017.4.15f1",
}; };
static readonly Dictionary<string, string> HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary<string, string> { static readonly Dictionary<string, string> HUMANOID_TO_OVERTE_JOINT_NAME = new Dictionary<string, string> {
{"Chest", "Spine1"}, {"Chest", "Spine1"},
{"Head", "Head"}, {"Head", "Head"},
{"Hips", "Hips"}, {"Hips", "Hips"},
@ -353,19 +355,19 @@ class AvatarExporter : MonoBehaviour {
static GameObject avatarPreviewObject; static GameObject avatarPreviewObject;
static GameObject heightReferenceObject; static GameObject heightReferenceObject;
[MenuItem("High Fidelity/Export New Avatar")] [MenuItem("Overte/Export New Avatar")]
static void ExportNewAvatar() { static void ExportNewAvatar() {
ExportSelectedAvatar(false); ExportSelectedAvatar(false);
} }
[MenuItem("High Fidelity/Update Existing Avatar")] [MenuItem("Overte/Update Existing Avatar")]
static void UpdateAvatar() { static void UpdateAvatar() {
ExportSelectedAvatar(true); ExportSelectedAvatar(true);
} }
[MenuItem("High Fidelity/About")] [MenuItem("Overte/About")]
static void About() { static void About() {
EditorUtility.DisplayDialog("About", "High Fidelity, Inc.\nAvatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION, "Ok"); EditorUtility.DisplayDialog("About", "Avatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION + "\nCopyright 2022 Overte e.V.\nCopyright 2018 High Fidelity, Inc.", "Ok");
} }
static void ExportSelectedAvatar(bool updateExistingAvatar) { static void ExportSelectedAvatar(bool updateExistingAvatar) {
@ -462,23 +464,23 @@ class AvatarExporter : MonoBehaviour {
} }
string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
string hifiFolder = documentsFolder + "\\High Fidelity Projects"; string overteFolder = documentsFolder + "/Overte Projects";
if (updateExistingAvatar) { // Update Existing Avatar menu option if (updateExistingAvatar) { // Update Existing Avatar menu option
// open update existing project popup window including project to update, scale, and warnings // open update existing project popup window including project to update, scale, and warnings
// default the initial file chooser location to HiFi projects folder in user documents folder // default the initial file chooser location to Overte Projects folder in user documents folder
ExportProjectWindow window = ScriptableObject.CreateInstance<ExportProjectWindow>(); ExportProjectWindow window = ScriptableObject.CreateInstance<ExportProjectWindow>();
string initialPath = Directory.Exists(hifiFolder) ? hifiFolder : documentsFolder; string initialPath = Directory.Exists(overteFolder) ? overteFolder : documentsFolder;
window.Init(initialPath, warnings, updateExistingAvatar, avatarPreviewObject, OnUpdateExistingProject, OnExportWindowClose); window.Init(initialPath, warnings, updateExistingAvatar, avatarPreviewObject, OnUpdateExistingProject, OnExportWindowClose);
} else { // Export New Avatar menu option } else { // Export New Avatar menu option
// create High Fidelity Projects folder in user documents folder if it doesn't exist // create Overte Projects folder in user documents folder if it doesn't exist
if (!Directory.Exists(hifiFolder)) { if (!Directory.Exists(overteFolder)) {
Directory.CreateDirectory(hifiFolder); Directory.CreateDirectory(overteFolder);
} }
// open export new project popup window including project name, project location, scale, and warnings // open export new project popup window including project name, project location, scale, and warnings
// default the initial project location path to the High Fidelity Projects folder above // default the initial project location path to the Overte Projects folder above
ExportProjectWindow window = ScriptableObject.CreateInstance<ExportProjectWindow>(); ExportProjectWindow window = ScriptableObject.CreateInstance<ExportProjectWindow>();
window.Init(hifiFolder, warnings, updateExistingAvatar, avatarPreviewObject, OnExportNewProject, OnExportWindowClose); window.Init(overteFolder, warnings, updateExistingAvatar, avatarPreviewObject, OnExportNewProject, OnExportWindowClose);
} }
} }
@ -505,7 +507,7 @@ class AvatarExporter : MonoBehaviour {
return; return;
} }
string exportModelPath = Path.GetDirectoryName(exportFstPath) + "\\" + assetName + ".fbx"; string exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx";
if (File.Exists(exportModelPath)) { if (File.Exists(exportModelPath)) {
// if the fbx in Unity Assets is newer than the fbx in the target export // 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 // folder or vice-versa then ask to replace the older fbx with the newer fbx
@ -608,7 +610,7 @@ class AvatarExporter : MonoBehaviour {
// create empty Textures and Scripts folders in the project directory // create empty Textures and Scripts folders in the project directory
string texturesDirectory = GetTextureDirectory(projectDirectory); string texturesDirectory = GetTextureDirectory(projectDirectory);
string scriptsDirectory = projectDirectory + "\\scripts"; string scriptsDirectory = projectDirectory + "/scripts";
Directory.CreateDirectory(texturesDirectory); Directory.CreateDirectory(texturesDirectory);
Directory.CreateDirectory(scriptsDirectory); Directory.CreateDirectory(scriptsDirectory);
@ -639,7 +641,7 @@ class AvatarExporter : MonoBehaviour {
ClosePreviewScene(); ClosePreviewScene();
} }
// The High Fidelity FBX Serializer omits the colon based prefixes. This will make the jointnames compatible. // The Overte FBX Serializer omits the colon based prefixes. This will make the jointnames compatible.
static string removeTypeFromJointname(string jointName) { static string removeTypeFromJointname(string jointName) {
return jointName.Substring(jointName.IndexOf(':') + 1); return jointName.Substring(jointName.IndexOf(':') + 1);
} }
@ -659,8 +661,8 @@ class AvatarExporter : MonoBehaviour {
// write out joint mappings to fst file // write out joint mappings to fst file
foreach (var userBoneInfo in userBoneInfos) { foreach (var userBoneInfo in userBoneInfos) {
if (userBoneInfo.Value.HasHumanMapping()) { if (userBoneInfo.Value.HasHumanMapping()) {
string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.Value.humanName]; string overteJointName = HUMANOID_TO_OVERTE_JOINT_NAME[userBoneInfo.Value.humanName];
File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + removeTypeFromJointname(userBoneInfo.Key) + "\n"); File.AppendAllText(exportFstPath, "jointMap = " + overteJointName + " = " + removeTypeFromJointname(userBoneInfo.Key) + "\n");
} }
} }
@ -699,7 +701,7 @@ class AvatarExporter : MonoBehaviour {
} }
} }
// swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst // swap from left-handed (Unity) to right-handed (Overte) coordinates and write out joint rotation offset to fst
jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w);
File.AppendAllText(exportFstPath, "jointRotationOffset2 = " + removeTypeFromJointname(userBoneName) + " = (" + jointOffset.x + ", " + File.AppendAllText(exportFstPath, "jointRotationOffset2 = " + removeTypeFromJointname(userBoneName) + " = (" + jointOffset.x + ", " +
jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n");
@ -725,8 +727,10 @@ class AvatarExporter : MonoBehaviour {
File.AppendAllText(exportFstPath, "materialMap = " + materialJson); File.AppendAllText(exportFstPath, "materialMap = " + materialJson);
} }
// open File Explorer to the project directory once finished if(SystemInfo.operatingSystemFamily == OperatingSystemFamily.Windows) {
System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); // open File Explorer to the project directory once finished
System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath);
}
return true; return true;
} }
@ -756,10 +760,10 @@ class AvatarExporter : MonoBehaviour {
foreach (HumanBone bone in boneMap) { foreach (HumanBone bone in boneMap) {
string humanName = bone.humanName; string humanName = bone.humanName;
string userBoneName = bone.boneName; string userBoneName = bone.boneName;
string hifiJointName; string overteJointName;
if (userBoneInfos.ContainsKey(userBoneName)) { if (userBoneInfos.ContainsKey(userBoneName)) {
++userBoneInfos[userBoneName].mappingCount; ++userBoneInfos[userBoneName].mappingCount;
if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) { if (HUMANOID_TO_OVERTE_JOINT_NAME.TryGetValue(humanName, out overteJointName)) {
userBoneInfos[userBoneName].humanName = humanName; userBoneInfos[userBoneName].humanName = humanName;
humanoidToUserBoneMappings.Add(humanName, userBoneName); humanoidToUserBoneMappings.Add(humanName, userBoneName);
} }
@ -1135,8 +1139,8 @@ class AvatarExporter : MonoBehaviour {
} }
static string GetTextureDirectory(string basePath) { static string GetTextureDirectory(string basePath) {
string textureDirectory = Path.GetDirectoryName(basePath) + "\\" + TEXTURES_DIRECTORY; string textureDirectory = Path.GetDirectoryName(basePath) + "/" + TEXTURES_DIRECTORY;
textureDirectory = textureDirectory.Replace("\\\\", "\\"); textureDirectory = textureDirectory.Replace("//", "/");
return textureDirectory; return textureDirectory;
} }
@ -1166,7 +1170,7 @@ class AvatarExporter : MonoBehaviour {
static bool CopyExternalTextures(string texturesDirectory) { static bool CopyExternalTextures(string texturesDirectory) {
// copy the found dependency textures from the local asset folder to the textures folder in the target export project // copy the found dependency textures from the local asset folder to the textures folder in the target export project
foreach (var texture in textureDependencies) { foreach (var texture in textureDependencies) {
string targetPath = texturesDirectory + "\\" + texture.Key; string targetPath = texturesDirectory + "/" + texture.Key;
try { try {
File.Copy(texture.Value, targetPath, true); File.Copy(texture.Value, targetPath, true);
} catch { } catch {
@ -1474,7 +1478,7 @@ class ExportProjectWindow : EditorWindow {
if (GUILayout.Button("Browse", buttonStyle)) { if (GUILayout.Button("Browse", buttonStyle)) {
string result = ""; string result = "";
if (updateExistingAvatar) { if (updateExistingAvatar) {
// open file explorer starting at hifi projects folder in user documents and select target fst to update // open file explorer starting at overte projects folder in user documents and select target fst to update
string initialPath = string.IsNullOrEmpty(projectLocation) ? initialProjectLocation : projectLocation; string initialPath = string.IsNullOrEmpty(projectLocation) ? initialProjectLocation : projectLocation;
result = EditorUtility.OpenFilePanel("Select .fst to update", initialPath, "fst"); result = EditorUtility.OpenFilePanel("Select .fst to update", initialPath, "fst");
} else { } else {
@ -1482,7 +1486,7 @@ class ExportProjectWindow : EditorWindow {
result = EditorUtility.OpenFolderPanel("Select export location", projectLocation, ""); result = EditorUtility.OpenFolderPanel("Select export location", projectLocation, "");
} }
if (!string.IsNullOrEmpty(result)) { // file/folder selection not cancelled if (!string.IsNullOrEmpty(result)) { // file/folder selection not cancelled
projectLocation = result.Replace('/', '\\'); projectLocation = result.Replace('\\', '/');
} }
} }
@ -1511,8 +1515,10 @@ class ExportProjectWindow : EditorWindow {
GUILayout.Space(15); GUILayout.Space(15);
// red error label text to display any file-related errors // red error label text to display any file-related errors
GUILayout.Label("Error:", errorStyle); if(errorText != EMPTY_ERROR_TEXT) {
GUILayout.Label(errorText, errorStyle); GUILayout.Label("Error:", errorStyle);
GUILayout.Label(errorText, errorStyle);
}
GUILayout.Space(10); GUILayout.Space(10);
@ -1561,7 +1567,7 @@ class ExportProjectWindow : EditorWindow {
return true; return true;
} }
} else { } else {
projectDirectory = projectLocation + "\\" + projectName + "\\"; projectDirectory = projectLocation + "/" + projectName + "/";
if (projectName.Length > 0) { if (projectName.Length > 0) {
// new project must have a unique folder name since the folder will be created for it // new project must have a unique folder name since the folder will be created for it
if (Directory.Exists(projectDirectory)) { if (Directory.Exists(projectDirectory)) {
@ -1571,10 +1577,18 @@ class ExportProjectWindow : EditorWindow {
} }
} }
if (projectLocation.Length > 0) { if (projectLocation.Length > 0) {
// before clicking Export we can verify that the project location at least starts with a drive // Check to ensure provided path is absolute, not relative.
if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') { if(SystemInfo.operatingSystemFamily == OperatingSystemFamily.Windows) {
errorText = "Project location is invalid. Please choose a different project location.\n"; if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') {
return true; errorText = "Project location is invalid. Please choose a different project location.\n";
return true;
}
}
else {
if (projectLocation[0] != '/') {
errorText = "Project location is invalid. Please choose a different project location.\n";
return true;
}
} }
} }
if (exporting) { if (exporting) {

View file

@ -1,6 +1,7 @@
High Fidelity, Inc.
Avatar Exporter Avatar Exporter
Version 0.4.1 Version 0.5.0
Copyright 2018 High Fidelity, Inc.
Copyright 2022 Overte e.V.
Note: It is recommended to use Unity versions between 2017.4.15f1 and 2018.2.12f1 for this Avatar Exporter. Note: It is recommended to use Unity versions between 2017.4.15f1 and 2018.2.12f1 for this Avatar Exporter.
@ -21,4 +22,4 @@ To update an existing avatar project:
* WARNING * * WARNING *
If you are using any external textures as part of your .fbx model, be sure they are copied into the textures folder that is created in the project folder after exporting a new avatar. If you are using any external textures as part of your .fbx model, be sure they are copied into the textures folder that is created in the project folder after exporting a new avatar.
For further details including troubleshooting tips, see the full documentation at https://docs.highfidelity.com/create-and-explore/avatars/create-avatars/unity-extension For further details including troubleshooting tips, see the full documentation at https://docs.overte.org/create/avatars/find-avatars.html#overte-avatar-exporter-for-unity

View file

@ -0,0 +1,30 @@
#!/bin/bash
projectPath=$(dirname $0)
helpFunction()
{
echo ""
echo "Usage: $0 -u <UnityPath> -p <ProjectPath>"
echo -e "\t-u The path in which Unity exists"
echo -e "\t-p The path to build the project files (Default: ${projectPath})"
exit 1
}
while getopts "u:p" opt
do
case "$opt" in
u ) unityPath="$OPTARG" ;;
p ) projectPath="$OPTARG" ;;
? ) helpFunction ;;
esac
done
if [ -z "$unityPath" ]
then
echo "Unity path was not provided";
helpFunction
fi
${unityPath}/Unity -quit -batchmode -projectPath ${projectPath} -exportPackage "Assets" "avatarExporter.unitypackage"