diff --git a/Assets/MeshUtility/Editor/ExportDialog/Valildators/HumanoidValidator.cs b/Assets/MeshUtility/Editor/ExportDialog/Valildators/HumanoidValidator.cs index a1100d465..cc89267b5 100644 --- a/Assets/MeshUtility/Editor/ExportDialog/Valildators/HumanoidValidator.cs +++ b/Assets/MeshUtility/Editor/ExportDialog/Valildators/HumanoidValidator.cs @@ -25,6 +25,10 @@ namespace MeshUtility.Validators [LangMsg(Languages.en, "The model needs to face the positive Z-axis")] FACE_Z_POSITIVE_DIRECTION, + [LangMsg(Languages.ja, "T-Pose にしてください")] + [LangMsg(Languages.en, "Set T-Pose")] + NOT_TPOSE, + [LangMsg(Languages.ja, "ExportRootの Animator に Avatar がありません")] [LangMsg(Languages.en, "No Avatar in ExportRoot's Animator")] NO_AVATAR_IN_ANIMATOR, @@ -37,7 +41,7 @@ namespace MeshUtility.Validators [LangMsg(Languages.en, "Animator.avatar is not humanoid. Please change model's AnimationType to humanoid")] AVATAR_IS_NOT_HUMANOID, - [LangMsg(Languages.ja, "humanoid設定に顎が含まれている。FBX importer の rig 設定に戻って設定を解除することをおすすめします")] + [LangMsg(Languages.ja, "Jaw(顎)ボーンが含まれています。意図していない場合は設定解除をおすすめします。FBX importer の rig 設定から変更できます")] [LangMsg(Languages.en, "Jaw bone is included. It may not what you intended. Please check the humanoid avatar setting screen")] JAW_BONE_IS_INCLUDED, } @@ -150,15 +154,23 @@ namespace MeshUtility.Validators } } + { + var lu = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm); + var ll = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm); + var ru = animator.GetBoneTransform(HumanBodyBones.RightUpperArm); + var rl = animator.GetBoneTransform(HumanBodyBones.RightLowerArm); + if (Vector3.Dot((ll.position - lu.position).normalized, Vector3.left) < 0.8f + || Vector3.Dot((rl.position - ru.position).normalized, Vector3.right) < 0.8f) + { + yield return Validation.Error(ValidationMessages.NOT_TPOSE.Msg()); + } + } + var jaw = animator.GetBoneTransform(HumanBodyBones.Jaw); if (jaw != null) { yield return Validation.Warning(ValidationMessages.JAW_BONE_IS_INCLUDED.Msg()); } - else - { - yield return Validation.Info("Animator OK"); - } } } } diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs index 35205b54a..2e0f2e89e 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs @@ -48,5 +48,7 @@ namespace VRM UseSparseAccessorForMorphTarget = UseSparseAccessor, ExportOnlyBlendShapePosition = OnlyBlendshapePosition, }; + + public GameObject Root { get; set; } } } diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettingsEditor.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettingsEditor.cs index e5853dbbb..a4c3512a8 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettingsEditor.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettingsEditor.cs @@ -36,12 +36,6 @@ namespace VRM } } - /// - /// エクスポート時に強制的にT-Pose化する - /// - [Tooltip("Option")] - public bool ForceTPose = false; - /// /// エクスポート時にヒエラルキーの正規化を実施する /// @@ -78,7 +72,6 @@ namespace VRM [Tooltip("Remove blendShapeClip that preset is Unknown")] public bool ReduceBlendshapeClip = false; - CheckBoxProp m_forceTPose; CheckBoxProp m_poseFreeze; CheckBoxProp m_useSparseAccessor; CheckBoxProp m_onlyBlendShapePosition; @@ -123,11 +116,22 @@ namespace VRM [LangMsg(Languages.ja, "エクスポートに頂点カラーを含めない")] [LangMsg(Languages.en, "Vertex color will not be exported")] REMOVE_VERTEX_COLOR, + + [LangMsg(Languages.ja, "このボタンで自動で T-Pose にできます。手動で T-Pose にしたり、ボタンの後で手直ししてもOKです。")] + [LangMsg(Languages.en, "You can automatically set the T pose with this button. You can change it manually to T-pose or after the button.")] + ENALBE_TPOSE_BUTTON, + + [LangMsg(Languages.ja, "このボタンで自動で T-Pose にできます。prefab には実行できません。")] + [LangMsg(Languages.en, "You can set the T pose automatically with this button. It cannot be run on prefabs.")] + DISABLE_TPOSE_BUTTON, + + [LangMsg(Languages.ja, "T-Pose にする")] + [LangMsg(Languages.en, "Make T-Pose")] + DO_TPOSE, } private void OnEnable() { - m_forceTPose = new CheckBoxProp(serializedObject.FindProperty(nameof(ForceTPose)), Options.FORCE_T_POSE); m_poseFreeze = new CheckBoxProp(serializedObject.FindProperty(nameof(PoseFreeze)), Options.NORMALIZE); m_useSparseAccessor = new CheckBoxProp(serializedObject.FindProperty(nameof(UseSparseAccessor)), Options.BLENDSHAPE_USE_SPARSE); m_onlyBlendShapePosition = new CheckBoxProp(serializedObject.FindProperty(nameof(OnlyBlendshapePosition)), Options.BLENDSHAPE_EXCLUDE_NORMAL_AND_TANGENT); @@ -137,9 +141,33 @@ namespace VRM public override void OnInspectorGUI() { + GUILayout.Space(20); + var settings = (VRMExportSettings)target; + var root = settings.Root; + var backup = GUI.enabled; + GUI.enabled = root.scene.IsValid(); + if (GUI.enabled) + { + EditorGUILayout.HelpBox(Options.ENALBE_TPOSE_BUTTON.Msg(), MessageType.Info); + } + else + { + EditorGUILayout.HelpBox(Options.DISABLE_TPOSE_BUTTON.Msg(), MessageType.Warning); + } + if (GUILayout.Button(Options.DO_TPOSE.Msg())) + { + if (settings.Root) + { + VRMBoneNormalizer.EnforceTPose(settings.Root); + } + } + GUI.enabled = backup; + GUILayout.Space(20); + + // ToDo: 任意の BlendShapeClip を適用する + EditorGUIUtility.labelWidth = 160; serializedObject.Update(); - m_forceTPose.Draw(); m_poseFreeze.Draw(); m_useSparseAccessor.Draw(); m_onlyBlendShapePosition.Draw(); diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs index 1eefffcbf..c13794e1f 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs @@ -23,6 +23,7 @@ namespace VRM { Meta, Mesh, + BlendShape, ExportSettings, } Tabs _tab; @@ -301,18 +302,71 @@ namespace VRM m_metaEditor.OnInspectorGUI(); break; - case Tabs.ExportSettings: - m_settingsInspector.OnInspectorGUI(); - break; - case Tabs.Mesh: m_meshesInspector.OnInspectorGUI(); break; + + case Tabs.BlendShape: + if (m_state.ExportRoot) + { + OnBlendShapeGUI(m_state.ExportRoot.GetComponent()); + } + break; + + case Tabs.ExportSettings: + m_settings.Root = m_state.ExportRoot; + m_settingsInspector.OnInspectorGUI(); + break; } return true; } + BlendShapeMerger m_merger; + + int m_selected = 0; + void OnBlendShapeGUI(VRMBlendShapeProxy proxy) + { + if (!m_state.ExportRoot.scene.IsValid()) + { + EditorGUILayout.HelpBox("prefab は操作できません", MessageType.Warning); + return; + } + + if (!proxy) + { + return; + } + var avatar = proxy.BlendShapeAvatar; + if (!avatar) + { + return; + } + + m_merger = new BlendShapeMerger(avatar.Clips, proxy.transform); + + + GUILayout.Space(20); + + EditorGUILayout.HelpBox("シーン上のExportRootにBlendShapeを適用します。Exportすると適用された状態がBakeされます。", MessageType.Info); + + var options = avatar.Clips.Select(x => x.ToString()).ToArray(); + m_selected = EditorGUILayout.Popup("select blendshape", m_selected, options); + + if (GUILayout.Button("選択されたBlendShapeを適用する")) + { + m_merger.SetValues(avatar.Clips.Select((x, i) => new KeyValuePair(x.Key, i == m_selected ? 1 : 0))); + m_merger.Apply(); + m_settings.PoseFreeze = true; + } + + if (GUILayout.Button("BlendShapeをClearする")) + { + m_merger.SetValues(avatar.Clips.Select(x => new KeyValuePair(x.Key, 0))); + m_merger.Apply(); + } + } + const string EXTENSION = ".vrm"; private static string m_lastExportDir; static void OnExportClicked(GameObject root, VRMMetaObject meta, VRMExportSettings settings, VRMExportMeshes meshes) diff --git a/Assets/VRM/UniVRM/Scripts/BlendShape/BlendShapeMerger.cs b/Assets/VRM/UniVRM/Scripts/BlendShape/BlendShapeMerger.cs index 02e60ab7b..6fb388ad8 100644 --- a/Assets/VRM/UniVRM/Scripts/BlendShape/BlendShapeMerger.cs +++ b/Assets/VRM/UniVRM/Scripts/BlendShape/BlendShapeMerger.cs @@ -11,7 +11,7 @@ namespace VRM /// /// ブレンドシェイプを蓄えてまとめて適用するクラス /// - class BlendShapeMerger + public class BlendShapeMerger { /// /// Key からBlendShapeClipを得る diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs index ce0b8170a..7c5bacee3 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -9,7 +9,7 @@ namespace VRM { public static class VRMBoneNormalizer { - static void EnforceTPose(GameObject go) + public static void EnforceTPose(GameObject go) { var animator = go.GetComponent(); if (animator == null)