mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 16:18:05 +02:00
Merge pull request #258 from akamicah/update-unity-avatar-exporter
Update unity avatar exporter
This commit is contained in:
commit
4d782bc28b
4 changed files with 286 additions and 241 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Binary file not shown.
30
tools/unity-avatar-exporter/packager.sh
Executable file
30
tools/unity-avatar-exporter/packager.sh
Executable 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"
|
Loading…
Reference in a new issue