mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-04-05 01:05:17 -05:00
421 lines
14 KiB
C#
421 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UniGLTF;
|
|
using UniGLTF.M17N;
|
|
using System.IO;
|
|
using UniGLTF.MeshUtility;
|
|
|
|
namespace VRM
|
|
{
|
|
public class VRMExporterWizard : ExportDialogBase
|
|
{
|
|
public static void OpenExportMenu()
|
|
{
|
|
var window = (VRMExporterWizard)GetWindow(typeof(VRMExporterWizard));
|
|
window.titleContent = new GUIContent("VRM Exporter");
|
|
window.Show();
|
|
}
|
|
|
|
|
|
enum Tabs
|
|
{
|
|
Meta,
|
|
Mesh,
|
|
BlendShape,
|
|
ExportSettings,
|
|
}
|
|
Tabs _tab;
|
|
|
|
|
|
VRMExportSettings m_settings;
|
|
Editor m_settingsInspector;
|
|
|
|
|
|
MeshExportValidator m_meshes;
|
|
Editor m_meshesInspector;
|
|
|
|
|
|
VRMMetaObject m_meta;
|
|
VRMMetaObject Meta
|
|
{
|
|
get { return m_meta; }
|
|
set
|
|
{
|
|
if (m_meta == value)
|
|
{
|
|
return;
|
|
}
|
|
if (m_metaEditor != null)
|
|
{
|
|
UnityEditor.Editor.DestroyImmediate(m_metaEditor);
|
|
m_metaEditor = null;
|
|
}
|
|
m_meta = value;
|
|
}
|
|
}
|
|
VRMMetaObject m_tmpMeta;
|
|
Editor m_metaEditor;
|
|
|
|
|
|
protected override void Initialize()
|
|
{
|
|
m_tmpMeta = ScriptableObject.CreateInstance<VRMMetaObject>();
|
|
|
|
m_settings = ScriptableObject.CreateInstance<VRMExportSettings>();
|
|
m_settingsInspector = Editor.CreateEditor(m_settings);
|
|
|
|
m_meshes = ScriptableObject.CreateInstance<MeshExportValidator>();
|
|
m_meshesInspector = Editor.CreateEditor(m_meshes);
|
|
|
|
State.ExportRootChanged += (root) =>
|
|
{
|
|
// update meta
|
|
if (root == null)
|
|
{
|
|
Meta = null;
|
|
}
|
|
else
|
|
{
|
|
var meta = root.GetComponent<VRMMeta>();
|
|
if (meta != null)
|
|
{
|
|
Meta = meta.Meta;
|
|
}
|
|
else
|
|
{
|
|
Meta = null;
|
|
}
|
|
|
|
// default setting
|
|
m_settings.PoseFreeze =
|
|
HumanoidValidator.HasRotationOrScale(root)
|
|
|| m_meshes.Meshes.Any(x => x.ExportBlendShapeCount > 0 && !x.HasSkinning)
|
|
;
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void Clear()
|
|
{
|
|
// m_settingsInspector
|
|
UnityEditor.Editor.DestroyImmediate(m_settingsInspector);
|
|
m_settingsInspector = null;
|
|
// m_meshesInspector
|
|
UnityEditor.Editor.DestroyImmediate(m_meshesInspector);
|
|
m_meshesInspector = null;
|
|
// m_settings
|
|
ScriptableObject.DestroyImmediate(m_settings);
|
|
m_settings = null;
|
|
|
|
// m_metaEditor
|
|
UnityEditor.Editor.DestroyImmediate(m_metaEditor);
|
|
m_metaEditor = null;
|
|
// Meta
|
|
Meta = null;
|
|
ScriptableObject.DestroyImmediate(m_tmpMeta);
|
|
m_tmpMeta = null;
|
|
// m_meshes
|
|
ScriptableObject.DestroyImmediate(m_meshes);
|
|
m_meshes = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// VRM0
|
|
/// </summary>
|
|
class VRMMaterialValidator : DefaultMaterialValidator
|
|
{
|
|
public override string GetGltfMaterialTypeFromUnityShaderName(string shaderName)
|
|
{
|
|
var name = VRMMaterialExporter.VrmMaterialName(shaderName);
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
return name;
|
|
}
|
|
return base.GetGltfMaterialTypeFromUnityShaderName(shaderName);
|
|
}
|
|
|
|
public override IEnumerable<(string propertyName, Texture texture)> EnumerateTextureProperties(Material m)
|
|
{
|
|
if (m.shader.name != "VRM/MToon")
|
|
{
|
|
foreach (var x in base.EnumerateTextureProperties(m))
|
|
{
|
|
yield return x;
|
|
}
|
|
}
|
|
|
|
var prop = UniGLTF.ShaderPropExporter.PreShaderPropExporter.GetPropsForSupportedShader(m.shader.name);
|
|
foreach (var kv in prop.Properties)
|
|
{
|
|
if (kv.ShaderPropertyType == UniGLTF.ShaderPropExporter.ShaderPropertyType.TexEnv)
|
|
{
|
|
yield return (kv.Key, m.GetTexture(kv.Key));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override IEnumerable<Validator> ValidatorFactory()
|
|
{
|
|
// ヒエラルキー のチェック
|
|
yield return HierarchyValidator.Validate;
|
|
if (!State.ExportRoot)
|
|
{
|
|
// Root が無い
|
|
yield break;
|
|
}
|
|
|
|
// Mesh/Renderer のチェック
|
|
m_meshes.MaterialValidator = new VRMMaterialValidator();
|
|
yield return m_meshes.Validate;
|
|
|
|
// Humanoid のチェック
|
|
HumanoidValidator.MeshInformations = m_meshes.Meshes;
|
|
HumanoidValidator.EnableFreeze = m_settings.PoseFreeze;
|
|
yield return HumanoidValidator.Validate;
|
|
|
|
//
|
|
// VRM のチェック
|
|
//
|
|
VRMExporterValidator.ReduceBlendshape = m_settings.ReduceBlendshape;
|
|
yield return VRMExporterValidator.Validate;
|
|
|
|
yield return VRMSpringBoneValidator.Validate;
|
|
|
|
var firstPerson = State.ExportRoot.GetComponent<VRMFirstPerson>();
|
|
if (firstPerson != null)
|
|
{
|
|
yield return firstPerson.Validate;
|
|
}
|
|
|
|
var proxy = State.ExportRoot.GetComponent<VRMBlendShapeProxy>();
|
|
if (proxy != null)
|
|
{
|
|
yield return proxy.Validate;
|
|
}
|
|
|
|
var meta = Meta ? Meta : m_tmpMeta;
|
|
yield return meta.Validate;
|
|
}
|
|
|
|
protected override void OnLayout()
|
|
{
|
|
m_meshes.SetRoot(State.ExportRoot, m_settings.MeshExportSettings, new VRMBlendShapeExportFilter(State.ExportRoot, m_settings));
|
|
}
|
|
|
|
static bool s_foldT = true;
|
|
|
|
protected override bool DoGUI(bool isValid)
|
|
{
|
|
if (State.ExportRoot == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// T-Pose
|
|
//
|
|
if (State.ExportRoot.GetComponent<Animator>() != null)
|
|
{
|
|
var backup = GUI.enabled;
|
|
GUI.enabled = State.ExportRoot.scene.IsValid();
|
|
|
|
if (s_foldT = EditorGUILayout.Foldout(s_foldT, "T-Pose"))
|
|
{
|
|
if (GUI.enabled)
|
|
{
|
|
EditorGUILayout.HelpBox(EnableTPose.ENALBE_TPOSE_BUTTON.Msg(), MessageType.Info);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox(EnableTPose.DISABLE_TPOSE_BUTTON.Msg(), MessageType.Warning);
|
|
}
|
|
|
|
//
|
|
// T-Pose
|
|
//
|
|
if (GUILayout.Button(VRMExportOptions.DO_TPOSE.Msg()))
|
|
{
|
|
if (State.ExportRoot != null)
|
|
{
|
|
// fallback
|
|
Undo.RecordObjects(State.ExportRoot.GetComponentsInChildren<Transform>(), "tpose");
|
|
VRMBoneNormalizer.EnforceTPose(State.ExportRoot);
|
|
Repaint();
|
|
}
|
|
}
|
|
|
|
if (GUILayout.Button(VRMExportOptions.DO_TPOSE.Msg() + "(unity internal)"))
|
|
{
|
|
if (State.ExportRoot != null)
|
|
{
|
|
Undo.RecordObjects(State.ExportRoot.GetComponentsInChildren<Transform>(), "tpose.internal");
|
|
if (InternalTPose.TryMakePoseValid(State.ExportRoot))
|
|
{
|
|
// done
|
|
Repaint();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("not found");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GUI.enabled = backup;
|
|
}
|
|
|
|
if (!isValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// GUI
|
|
//
|
|
_tab = TabBar.OnGUI(_tab);
|
|
foreach (var meshInfo in m_meshes.Meshes)
|
|
{
|
|
switch (meshInfo.VertexColor)
|
|
{
|
|
case UniGLTF.VertexColorState.ExistsAndMixed:
|
|
Validation.Warning($"{meshInfo.Renderers}: Both vcolor.multiply and not multiply unlit materials exist").DrawGUI();
|
|
break;
|
|
}
|
|
}
|
|
return DrawWizardGUI();
|
|
}
|
|
|
|
protected override string SaveTitle => "Save vrm0";
|
|
protected override string SaveName => $"{State.ExportRoot.name}.vrm";
|
|
protected override string[] SaveExtensions => new string[] { "vrm" };
|
|
|
|
protected override void ExportPath(string path)
|
|
{
|
|
var bytes = VRMEditorExporter.Export(State.ExportRoot, Meta != null ? Meta : m_tmpMeta, m_settings);
|
|
|
|
File.WriteAllBytes(path, bytes);
|
|
|
|
if (path.StartsWithUnityAssetPath())
|
|
{
|
|
// 出力ファイルのインポートを発動
|
|
AssetDatabase.ImportAsset(path.ToUnityRelativePath());
|
|
}
|
|
}
|
|
|
|
bool DrawWizardGUI()
|
|
{
|
|
if (m_tmpMeta == null)
|
|
{
|
|
// disabled
|
|
return false;
|
|
}
|
|
|
|
// tabbar
|
|
switch (_tab)
|
|
{
|
|
case Tabs.Meta:
|
|
if (m_metaEditor == null)
|
|
{
|
|
if (m_meta != null)
|
|
{
|
|
m_metaEditor = Editor.CreateEditor(Meta);
|
|
}
|
|
else
|
|
{
|
|
m_metaEditor = Editor.CreateEditor(m_tmpMeta);
|
|
}
|
|
}
|
|
m_metaEditor.OnInspectorGUI();
|
|
break;
|
|
|
|
case Tabs.Mesh:
|
|
m_meshesInspector.OnInspectorGUI();
|
|
break;
|
|
|
|
case Tabs.BlendShape:
|
|
if (State.ExportRoot)
|
|
{
|
|
OnBlendShapeGUI(State.ExportRoot.GetComponent<VRMBlendShapeProxy>());
|
|
}
|
|
break;
|
|
|
|
case Tabs.ExportSettings:
|
|
m_settings.Root = State.ExportRoot;
|
|
m_settingsInspector.OnInspectorGUI();
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum BlendShapeTabMessages
|
|
{
|
|
[LangMsg(Languages.ja, "prefab は操作できません")]
|
|
[LangMsg(Languages.en, "cannot manipulate prefab")]
|
|
CANNOT_MANIPULATE_PREFAB,
|
|
|
|
[LangMsg(Languages.ja, "シーン上のExportRootにBlendShapeを適用します。Exportすると適用された状態がBakeされます。")]
|
|
[LangMsg(Languages.en, "Apply blendshpae to ExportRoot in scene. Bake scene status if Export.")]
|
|
SCENE_MESSAGE,
|
|
|
|
[LangMsg(Languages.ja, "選択された BlendShapeClip を適用する")]
|
|
[LangMsg(Languages.en, "Apply selected BlendShapeClip")]
|
|
APPLY_BLENDSHAPECLIP_BUTTON,
|
|
|
|
[LangMsg(Languages.ja, "BlendShape を Clear する")]
|
|
[LangMsg(Languages.en, "Clear BlendShape")]
|
|
CLEAR_BLENDSHAPE_BUTTON,
|
|
}
|
|
|
|
BlendShapeMerger m_merger;
|
|
|
|
int m_selected = 0;
|
|
void OnBlendShapeGUI(VRMBlendShapeProxy proxy)
|
|
{
|
|
if (!State.ExportRoot.scene.IsValid())
|
|
{
|
|
EditorGUILayout.HelpBox(BlendShapeTabMessages.CANNOT_MANIPULATE_PREFAB.Msg(), MessageType.Warning);
|
|
return;
|
|
}
|
|
|
|
if (!proxy)
|
|
{
|
|
EditorGUILayout.HelpBox("no BlendShapeProxy", MessageType.Warning);
|
|
return;
|
|
}
|
|
var avatar = proxy.BlendShapeAvatar;
|
|
if (!avatar)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_merger = new BlendShapeMerger(avatar.Clips, proxy.transform);
|
|
|
|
|
|
GUILayout.Space(20);
|
|
|
|
EditorGUILayout.HelpBox(BlendShapeTabMessages.SCENE_MESSAGE.Msg(), MessageType.Info);
|
|
|
|
var options = avatar.Clips.Select(x => x.ToString()).ToArray();
|
|
m_selected = EditorGUILayout.Popup("select blendshape", m_selected, options);
|
|
|
|
if (GUILayout.Button(BlendShapeTabMessages.APPLY_BLENDSHAPECLIP_BUTTON.Msg()))
|
|
{
|
|
m_merger.SetValues(avatar.Clips.Select((x, i) => new KeyValuePair<BlendShapeKey, float>(x.Key, i == m_selected ? 1 : 0)));
|
|
m_merger.Apply();
|
|
m_settings.PoseFreeze = true;
|
|
}
|
|
|
|
if (GUILayout.Button(BlendShapeTabMessages.CLEAR_BLENDSHAPE_BUTTON.Msg()))
|
|
{
|
|
m_merger.SetValues(avatar.Clips.Select(x => new KeyValuePair<BlendShapeKey, float>(x.Key, 0)));
|
|
m_merger.Apply();
|
|
}
|
|
}
|
|
}
|
|
}
|