您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
620 行
23 KiB
620 行
23 KiB
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UniGLTF;
|
|
using UniVRM10;
|
|
using UnityEditor;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using UniGLTF.M17N;
|
|
using UniGLTF.MeshUtility;
|
|
using VRMShaders;
|
|
using System.IO;
|
|
using System;
|
|
using System.Net.Http.Headers;
|
|
using System.Threading.Tasks;
|
|
using COSXML;
|
|
using COSXML.Model.Object;
|
|
namespace MetaCity.BundleKit.Editor.VRM
|
|
{
|
|
public class MetacityAvatorVRMBuildWindow : EditorWindow
|
|
{
|
|
|
|
[MenuItem("Metacity/AvatarVRMBuildWindow")]
|
|
public static void Open()
|
|
{
|
|
var window =GetWindow<MetacityAvatorVRMBuildWindow>();
|
|
window.titleContent = new GUIContent("Metacity Avator VRM Exporter");
|
|
window.Show();
|
|
}
|
|
enum Tabs
|
|
{
|
|
Meta,
|
|
Mesh,
|
|
ExportSettings,
|
|
}
|
|
Tabs _tab;
|
|
|
|
|
|
VRM10ExportSettings m_settings;
|
|
UnityEditor.Editor m_settingsInspector;
|
|
|
|
|
|
MetaCityMeshExportValidator m_meshes;
|
|
UnityEditor.Editor m_meshesInspector;
|
|
|
|
VRM10Object m_meta;
|
|
VRM10Object Vrm
|
|
{
|
|
get { return m_meta; }
|
|
set
|
|
{
|
|
if (value != null && AssetDatabase.IsSubAsset(value))
|
|
{
|
|
// SubAsset is readonly. copy
|
|
Debug.Log("copy VRM10ObjectMeta");
|
|
value.Meta.CopyTo(m_tmpObject.Meta);
|
|
return;
|
|
}
|
|
|
|
if (m_meta == value)
|
|
{
|
|
return;
|
|
}
|
|
m_metaEditor = default;
|
|
m_meta = value;
|
|
}
|
|
}
|
|
VRM10Object m_tmpObject;
|
|
VRM10MetaEditor m_metaEditor;
|
|
ExporterDialogState m_state;
|
|
/// <summary>
|
|
/// Delate bundles in the folder
|
|
/// </summary>
|
|
private void PrepareBuild()
|
|
{
|
|
var buildPath = Constants.AvatarBundleFolderPath;
|
|
if (Directory.Exists(buildPath))
|
|
{
|
|
if(m_settings.incrementalExport)return;
|
|
DirectoryInfo di = new DirectoryInfo(buildPath);
|
|
foreach (FileInfo file in di.GetFiles())
|
|
{
|
|
file.Delete();
|
|
}
|
|
foreach (DirectoryInfo dir in di.GetDirectories())
|
|
{
|
|
dir.Delete(true);
|
|
}
|
|
|
|
Directory.Delete(buildPath);
|
|
}
|
|
Directory.CreateDirectory(buildPath);
|
|
}
|
|
private void OnEnable()
|
|
{
|
|
Undo.willFlushUndoRecord += Repaint;
|
|
Selection.selectionChanged += Repaint;
|
|
|
|
m_state = new ExporterDialogState();
|
|
|
|
Initialize();
|
|
|
|
m_state.ExportRootChanged += (root) =>
|
|
{
|
|
Repaint();
|
|
};
|
|
m_state.ExportRoot = Selection.activeObject as GameObject;
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
m_tmpObject = ScriptableObject.CreateInstance<VRM10Object>();
|
|
m_tmpObject.name = "_vrm1_";
|
|
m_tmpObject.Meta.Authors = new List<string> { "" };
|
|
|
|
m_settings = ScriptableObject.CreateInstance<VRM10ExportSettings>();
|
|
m_settingsInspector = UnityEditor.Editor.CreateEditor(m_settings);
|
|
|
|
m_meshes = ScriptableObject.CreateInstance<MetaCityMeshExportValidator>();
|
|
m_meshes.UseCustomShaderGetter=()=>m_settings.useCustomShader;
|
|
m_meshesInspector = UnityEditor.Editor.CreateEditor(m_meshes);
|
|
|
|
m_state.ExportRootChanged += (root) =>
|
|
{
|
|
// update meta
|
|
if (root == null)
|
|
{
|
|
Vrm = null;
|
|
}
|
|
else
|
|
{
|
|
var controller = root.GetComponent<Vrm10Instance>();
|
|
if (controller != null)
|
|
{
|
|
Vrm = controller.Vrm;
|
|
}
|
|
else
|
|
{
|
|
Vrm = null;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
Clear();
|
|
|
|
m_state.Dispose();
|
|
|
|
Selection.selectionChanged -= Repaint;
|
|
Undo.willFlushUndoRecord -= Repaint;
|
|
}
|
|
|
|
protected void Clear()
|
|
{
|
|
UnityEditor.Editor.DestroyImmediate(m_settingsInspector);
|
|
m_settingsInspector = null;
|
|
// m_meshesInspector
|
|
UnityEditor.Editor.DestroyImmediate(m_meshesInspector);
|
|
m_meshesInspector = null;
|
|
// Meta
|
|
Vrm = null;
|
|
ScriptableObject.DestroyImmediate(m_tmpObject);
|
|
m_tmpObject = null;
|
|
// m_settings
|
|
ScriptableObject.DestroyImmediate(m_settings);
|
|
m_settings = null;
|
|
// m_meshes
|
|
ScriptableObject.DestroyImmediate(m_meshes);
|
|
m_meshes = null;
|
|
}
|
|
|
|
//
|
|
// scroll
|
|
//
|
|
public delegate Vector2 BeginVerticalScrollViewFunc(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options);
|
|
static BeginVerticalScrollViewFunc s_func;
|
|
static BeginVerticalScrollViewFunc BeginVerticalScrollView
|
|
{
|
|
get
|
|
{
|
|
if (s_func == null)
|
|
{
|
|
var methods = typeof(EditorGUILayout).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Where(x => x.Name == "BeginVerticalScrollView").ToArray();
|
|
var method = methods.First(x => x.GetParameters()[1].ParameterType == typeof(bool));
|
|
s_func = (BeginVerticalScrollViewFunc)method.CreateDelegate(typeof(BeginVerticalScrollViewFunc));
|
|
}
|
|
return s_func;
|
|
}
|
|
}
|
|
private Vector2 m_ScrollPosition;
|
|
|
|
//
|
|
// validation
|
|
//
|
|
protected IEnumerable<Validator> ValidatorFactory()
|
|
{
|
|
HumanoidValidator.MeshInformations = m_meshes.Meshes;
|
|
// HumanoidValidator.EnableFreeze = m_settings.PoseFreeze;
|
|
|
|
yield return HierarchyValidator.Validate;
|
|
if (!m_state.ExportRoot)
|
|
{
|
|
yield break;
|
|
}
|
|
// Mesh/Renderer のチェック
|
|
m_meshes.MaterialValidator = new MetacityVRMMaterialValidator();
|
|
yield return m_meshes.Validate;
|
|
|
|
yield return HumanoidValidator.Validate_TPose;
|
|
|
|
var vrm = Vrm ? Vrm : m_tmpObject;
|
|
yield return vrm.Meta.Validate;
|
|
}
|
|
|
|
void OnGUI()
|
|
{
|
|
var modified = false;
|
|
var isValid = BeginGUI();
|
|
modified = DoVRMGUI(isValid);
|
|
EndGUI();
|
|
|
|
if (modified)
|
|
{
|
|
m_state.Invalidate();
|
|
}
|
|
}
|
|
|
|
private bool DoVRMGUI(bool isValid)
|
|
{
|
|
if (m_state.ExportRoot == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_state.ExportRoot.GetComponent<Animator>() != null)
|
|
{
|
|
var backup = GUI.enabled;
|
|
GUI.enabled = m_state.ExportRoot.scene.IsValid();
|
|
if (GUI.enabled)
|
|
{
|
|
EditorGUILayout.HelpBox(EnableTPose.ENALBE_TPOSE_BUTTON.Msg(), MessageType.Info);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox(EnableTPose.DISABLE_TPOSE_BUTTON.Msg(), MessageType.Warning);
|
|
}
|
|
|
|
if (GUILayout.Button("T-Pose" + "(unity internal)"))
|
|
{
|
|
if (m_state.ExportRoot != null)
|
|
{
|
|
Undo.RecordObjects(m_state.ExportRoot.GetComponentsInChildren<Transform>(), "tpose.internal");
|
|
if (InternalTPose.TryMakePoseValid(m_state.ExportRoot))
|
|
{
|
|
// done
|
|
Repaint();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("not found");
|
|
}
|
|
}
|
|
}
|
|
|
|
GUI.enabled = backup;
|
|
}
|
|
|
|
if (!isValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_tmpObject == null)
|
|
{
|
|
// disabled
|
|
return false;
|
|
}
|
|
|
|
// tabbar
|
|
_tab = TabBar.OnGUI(_tab);
|
|
switch (_tab)
|
|
{
|
|
case Tabs.Meta:
|
|
if (m_metaEditor == null)
|
|
{
|
|
SerializedObject so;
|
|
if (m_meta != null)
|
|
{
|
|
so = new SerializedObject(Vrm);
|
|
}
|
|
else
|
|
{
|
|
so = new SerializedObject(m_tmpObject);
|
|
}
|
|
m_metaEditor = VRM10MetaEditor.Create(so);
|
|
}
|
|
m_metaEditor.OnInspectorGUI();
|
|
break;
|
|
|
|
case Tabs.Mesh:
|
|
m_meshesInspector.OnInspectorGUI();
|
|
break;
|
|
|
|
case Tabs.ExportSettings:
|
|
m_settingsInspector.OnInspectorGUI();
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnLayout()
|
|
{
|
|
m_meshes.SetRoot(m_state.ExportRoot, m_settings.MeshExportSettings, new DefualtBlendShapeExportFilter());
|
|
}
|
|
const string LANG_KEY = "VRM_LANG";
|
|
const string English ="en";
|
|
bool BeginGUI()
|
|
{
|
|
// ArgumentException: Getting control 1's position in a group with only 1 controls when doing repaint Aborting
|
|
// Validation により GUI の表示項目が変わる場合があるので、
|
|
// EventType.Layout と EventType.Repaint 間で内容が変わらないようしている。
|
|
if (Event.current.type == EventType.Layout)
|
|
{
|
|
OnLayout();
|
|
m_state.Validate(ValidatorFactory());
|
|
}
|
|
|
|
EditorGUIUtility.labelWidth = 150;
|
|
|
|
// lang
|
|
EditorPrefs.SetString(LANG_KEY, English);
|
|
if(m_state.ExportRoot is null)
|
|
{
|
|
EditorGUILayout.HelpBox("You can convert existing vrm file to avator catalog", MessageType.Info);
|
|
m_settings.incrementalExport=EditorGUILayout.ToggleLeft("Use Incremental Export",m_settings.incrementalExport);
|
|
if(GUILayout.Button("Convert", GUILayout.MinWidth(100)))
|
|
{
|
|
var path=EditorUtility.OpenFilePanel("Manuallly add VRM File to catalog",Application.dataPath,"vrm");
|
|
if(string.IsNullOrEmpty(path))return false;
|
|
PrepareBuild();
|
|
ConvertVRM(path);
|
|
}
|
|
}
|
|
EditorGUILayout.LabelField("ExportRoot");
|
|
{
|
|
m_state.ExportRoot = (GameObject)EditorGUILayout.ObjectField(m_state.ExportRoot, typeof(GameObject), true);
|
|
}
|
|
// Render contents using Generic Inspector GUI
|
|
m_ScrollPosition = BeginVerticalScrollView(m_ScrollPosition, false, GUI.skin.verticalScrollbar, "OL Box");
|
|
GUIUtility.GetControlID(645789, FocusType.Passive);
|
|
|
|
// validation
|
|
foreach (var v in m_state.Validations)
|
|
{
|
|
v.DrawGUI();
|
|
if (v.ErrorLevel == ErrorLevels.Critical)
|
|
{
|
|
// Export UI を表示しない
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
string m_logLabel;
|
|
|
|
void EndGUI()
|
|
{
|
|
EditorGUILayout.EndScrollView();
|
|
|
|
//
|
|
// export button
|
|
//
|
|
// Create and Other Buttons
|
|
{
|
|
// errors
|
|
GUILayout.BeginVertical();
|
|
// GUILayout.FlexibleSpace();
|
|
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
GUI.enabled = m_state.Validations.All(x => x.CanExport);
|
|
if (GUILayout.Button("Export", GUILayout.MinWidth(100)))
|
|
{
|
|
PrepareBuild();
|
|
ExportPath($"{Constants.AvatarBundleFolderPath}/{m_state.ExportRoot.name}.vrm");
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
GUI.enabled = true;
|
|
// if (GUILayout.Button("Publish",GUILayout.MinWidth(100)))
|
|
// {
|
|
// EditorApplication.delayCall += DoPublish;
|
|
// }
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
GUILayout.EndVertical();
|
|
}
|
|
|
|
GUILayout.Space(8);
|
|
}
|
|
private void ConvertVRM(string path)
|
|
{
|
|
List<BuildCatalog> catalogs;
|
|
try
|
|
{
|
|
//增量式创建Catalogs
|
|
catalogs=AvatarBundleExporter.LoadLastAvatarBuiltCatalog();
|
|
}
|
|
catch
|
|
{
|
|
catalogs=new List<BuildCatalog>();
|
|
}
|
|
var objectPath=path.Replace(Application.dataPath,string.Empty);
|
|
var root=AssetDatabase.LoadAssetAtPath<GameObject>($"Assets/{objectPath}");
|
|
var vrmHash=System.Guid.NewGuid().ToString().Replace("-",string.Empty);
|
|
catalogs.RemoveAll(x=>x.bundleName==root.name);
|
|
catalogs.Add(CatalogUtilities.CreateVRMAvatarCatalog(EditorUserBuildSettings.activeBuildTarget,root.name,vrmHash,null,path));
|
|
var catalogCollection = new BuildCatalogCollection
|
|
{
|
|
catalogs = catalogs.ToArray()
|
|
};
|
|
var writer = new StreamWriter(Constants.AvatarBundleLocalCatalogPath, false);
|
|
writer.WriteLine(JsonUtility.ToJson(catalogCollection));
|
|
writer.Close();
|
|
}
|
|
/// <summary>
|
|
/// Exporting VRM to the path
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
private void ExportPath(string path)
|
|
{
|
|
m_logLabel = "";
|
|
|
|
m_logLabel += $"export...\n";
|
|
|
|
var root = m_state.ExportRoot;
|
|
|
|
try
|
|
{
|
|
using (var arrayManager = new NativeArrayManager())
|
|
{
|
|
List<BuildCatalog> catalogs;
|
|
try
|
|
{
|
|
//增量式创建Catalogs
|
|
catalogs=AvatarBundleExporter.LoadLastAvatarBuiltCatalog();
|
|
}
|
|
catch
|
|
{
|
|
catalogs=new List<BuildCatalog>();
|
|
}
|
|
var converter = new UniVRM10.ModelExporter();
|
|
var model = converter.Export(arrayManager, root);
|
|
// 右手系に変換
|
|
m_logLabel += $"convert to right handed coordinate...\n";
|
|
model.ConvertCoordinate(VrmLib.Coordinates.Vrm1, ignoreVrm: false);
|
|
// export vrm-1.0
|
|
var exporter = new UniVRM10.Vrm10Exporter(new EditorTextureSerializer(), m_settings.MeshExportSettings);
|
|
|
|
//Build VRM Custom Material Bundle
|
|
if(m_settings.useCustomShader)
|
|
{
|
|
(catalogs,exporter.BundleManifest)=VRMMaterialBundleExporter.BuildMaterialBundle(root.name,model.Materials,catalogs);
|
|
}
|
|
var option = new VrmLib.ExportArgs
|
|
{
|
|
sparse = m_settings.MorphTargetUseSparse,
|
|
};
|
|
exporter.Export(root, model, converter, option, Vrm ? Vrm.Meta : m_tmpObject.Meta);
|
|
|
|
var exportedBytes = exporter.Storage.ToGlbBytes();
|
|
|
|
m_logLabel += $"VRM write to {path}...\n";
|
|
File.WriteAllBytes(path, exportedBytes);
|
|
Debug.Log("VRM ExportedBytes: " + exportedBytes.Length);
|
|
var platformTarget=EditorUserBuildSettings.activeBuildTarget;
|
|
var vrmHash=System.Guid.NewGuid().ToString().Replace("-",string.Empty);
|
|
string dependency=null;
|
|
if(exporter.BundleManifest!=null)
|
|
{
|
|
string bundleName=exporter.BundleManifest.GetAllAssetBundles()[0];
|
|
// Since we need to download dependecy bundle from remote when user load vrm model,
|
|
// we need to write the remote bundle key into catolog instead of just name
|
|
dependency=$"{bundleName}-{exporter.BundleManifest.GetAssetBundleHash(bundleName)}";
|
|
Debug.Log($"Include dependency bundle: {dependency}");
|
|
}
|
|
catalogs.RemoveAll(x=>x.bundleName==root.name);
|
|
catalogs.Add(CatalogUtilities.CreateVRMAvatarCatalog(platformTarget,root.name,vrmHash,dependency,path));
|
|
var catalogCollection = new BuildCatalogCollection
|
|
{
|
|
catalogs = catalogs.ToArray()
|
|
};
|
|
var writer = new StreamWriter(Constants.AvatarBundleLocalCatalogPath, false);
|
|
writer.WriteLine(JsonUtility.ToJson(catalogCollection));
|
|
writer.Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_logLabel += ex.ToString();
|
|
// rethrow
|
|
throw;
|
|
}
|
|
}
|
|
private float _process = 0;
|
|
private string _processNote = "";
|
|
private PutObjectRequest _request;
|
|
private async Task<bool> UploadBundles(List<MetaCity.BundleKit.Editor.AssetBundle> assetBundles, FailedCallBack onFailed)
|
|
{
|
|
CosXmlConfig config = new CosXmlConfig.Builder()
|
|
.SetRegion("ap-shanghai")
|
|
.SetDebugLog(true)
|
|
.Build();
|
|
var cosXml = new CosXmlServer(config, null);
|
|
|
|
int totalCount = assetBundles.Count;
|
|
int count = 1;
|
|
|
|
foreach (var assetBundle in assetBundles)
|
|
{
|
|
if (!File.Exists(assetBundle.bundlePath))
|
|
{
|
|
onFailed.Invoke();
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
var cosPath = $"{assetBundle.bundleName}-{assetBundle.bundleHash}";
|
|
string requestSignURL = await MetacityClient.GetUploadUrlAsync(cosPath);
|
|
_request = new PutObjectRequest(null, null, assetBundle.bundlePath);
|
|
_request.RequestURLWithSign = requestSignURL;
|
|
_request.SetCosProgressCallback(delegate(long completed, long total)
|
|
{
|
|
_process = (float) completed / total;
|
|
_processNote = string.Format("Upload({0}/{1})\n" +
|
|
"uploaded {2} of {3} bytes. {4:##.##} % complete...",
|
|
count,
|
|
totalCount,
|
|
completed,
|
|
total,
|
|
completed * 100.0 / total);
|
|
});
|
|
PutObjectResult result = cosXml.PutObject(_request);
|
|
|
|
if (result.IsSuccessful())
|
|
{
|
|
count++;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
catch (COSXML.CosException.CosClientException clientEx)
|
|
{
|
|
Debug.LogException(clientEx);
|
|
}
|
|
catch (COSXML.CosException.CosServerException serverEx)
|
|
{
|
|
Debug.LogException(serverEx);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void _failedCallBack()
|
|
{
|
|
Debug.Log("Upload failed >>>");
|
|
}
|
|
// private async void DoPublish()
|
|
// {
|
|
//
|
|
// MetacityClient.client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", MetacityTokenWindow.AccessToken);
|
|
// bool isValid = await MetacityClient.CheckAccessToken();
|
|
// if (!isValid)
|
|
// {
|
|
// Debug.LogError("Token is not valid, please get your access token from metacity website and enter it in the token window");
|
|
// return;
|
|
// }
|
|
// var catalogs = AvatarBundleExporter.LoadLastAvatarBuiltCatalog();
|
|
// var assetBundles = new List<MetaCity.BundleKit.Editor.AssetBundle>();
|
|
// if (catalogs != null)
|
|
// {
|
|
// for (var i = 0; i < catalogs.Count; i++)
|
|
// {
|
|
// assetBundles.Add(new MetaCity.BundleKit.Editor.AssetBundle()
|
|
// {
|
|
// bundlePath = catalogs[i].bundleLocalPath,
|
|
// bundleName = catalogs[i].bundleName,
|
|
// bundleType = catalogs[i].bundleType,
|
|
// bundleHash = catalogs[i].bundleHash,
|
|
// bundleDependencies = catalogs[i].bundleDependencies,
|
|
// platform = catalogs[i].bundlePlatform
|
|
// });
|
|
// }
|
|
//
|
|
// var isSuccess = await UploadBundles(assetBundles, _failedCallBack);
|
|
// if (!isSuccess)
|
|
// {
|
|
// return;
|
|
// }
|
|
//
|
|
// isSuccess = await MetacityClient.PublishAssetBundles(assetBundles);
|
|
// if (!isSuccess)
|
|
// {
|
|
// Debug.LogError("fail to publish avatar bundles to COS");
|
|
// }
|
|
// else
|
|
// {
|
|
// Debug.Log("Publish succeed");
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
}
|