From 6a9d309042a2a75482609db4ad6f937622bbd52b Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 11 Aug 2020 16:11:49 +0900 Subject: [PATCH] restore validation. separate TabBar.cs --- .../UniVRM/Editor/Format/VRMExportSettings.cs | 221 +----------- .../UniVRM/Editor/Format/VRMExporterWizard.cs | 326 +++++++++++++++--- .../UniVRM/Editor/Meta/VRMMetaObjectEditor.cs | 2 +- Assets/VRM/UniVRM/Editor/TabBar.cs | 39 +++ Assets/VRM/UniVRM/Editor/TabBar.cs.meta | 11 + .../Editor/Tests/InvalidFileNameTest.cs | 2 +- 6 files changed, 334 insertions(+), 267 deletions(-) create mode 100644 Assets/VRM/UniVRM/Editor/TabBar.cs create mode 100644 Assets/VRM/UniVRM/Editor/TabBar.cs.meta diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs index 62dc72c40..33f2c1501 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using UniGLTF; -using UnityEditor; using UnityEngine; namespace VRM @@ -59,218 +55,5 @@ namespace VRM [Tooltip("Remove vertex color")] public bool RemoveVertexColor = false; #endregion - - public static bool IsFileNameLengthTooLong(string fileName) - { - return fileName.Length > 64; - } - - // /// - // /// ボーン名の重複を確認 - // /// - // /// - // bool DuplicateBoneNameExists() - // { - // var bones = ExportRoot.transform.Traverse().ToArray(); - // var duplicates = bones - // .GroupBy(p => p.name) - // .Where(g => g.Count() > 1) - // .Select(g => g.Key); - - // return (duplicates.Any()); - // } - - // /// - // /// エクスポート可能か検証する - // /// - // /// - // public IEnumerable Validate() - // { - // if (ExportRoot == null) - // { - // yield return Validation.Error("Require source"); - // yield break; - // } - - // if (ExportRoot.transform.position != Vector3.zero || - // ExportRoot.transform.rotation != Quaternion.identity || - // ExportRoot.transform.localScale != Vector3.one) - // { - // // EditorUtility.DisplayDialog("Error", "The Root transform should have Default translation, rotation and scale.", "ok"); - // yield return Validation.Warning("The Root translation, rotation and scale will be dropped."); - // } - - // var animator = ExportRoot.GetComponent(); - // if (animator == null) - // { - // yield return Validation.Error("Require animator. "); - // } - // else - // { - // if (animator.avatar == null) - // { - // yield return Validation.Error("Require animator.avatar. "); - // } - // else if (!animator.avatar.isValid) - // { - // yield return Validation.Error("Animator.avatar is not valid. "); - // } - // else if (!animator.avatar.isHuman) - // { - // yield return Validation.Error("Animator.avatar is not humanoid. Please change model's AnimationType to humanoid. "); - // } - - // var jaw = animator.GetBoneTransform(HumanBodyBones.Jaw); - // if (jaw != null) - // { - // yield return Validation.Warning("Jaw bone is included. It may not be what you intended. Please check the humanoid avatar setting screen"); - // } - // } - - // if (DuplicateBoneNameExists()) - // { - // yield return Validation.Warning("There is a bone with the same name in the hierarchy. If exported, these bones will be automatically renamed."); - // } - - // if (ReduceBlendshape && ExportRoot.GetComponent() == null) - // { - // yield return Validation.Error("ReduceBlendshapeSize needs VRMBlendShapeProxy. You need to convert to VRM once."); - // } - - // var vertexColor = ExportRoot.GetComponentsInChildren().Any(x => x.sharedMesh.colors.Length > 0); - // if (vertexColor) - // { - // yield return Validation.Warning("This model contains vertex color"); - // } - - // var renderers = ExportRoot.GetComponentsInChildren(); - // if (renderers.All(x => !x.gameObject.activeInHierarchy)) - // { - // yield return Validation.Error("No active mesh"); - // } - - // var materials = renderers.SelectMany(x => x.sharedMaterials).Distinct(); - // foreach (var material in materials) - // { - // if (material.shader.name == "Standard") - // { - // // standard - // continue; - // } - - // if (VRMMaterialExporter.UseUnlit(material.shader.name)) - // { - // // unlit - // continue; - // } - - // if (VRMMaterialExporter.VRMExtensionShaders.Contains(material.shader.name)) - // { - // // VRM supported - // continue; - // } - - // yield return Validation.Warning(string.Format("{0}: unknown shader '{1}' is used. this will export as `Standard` fallback", - // material.name, - // material.shader.name)); - // } - - // foreach (var material in materials) - // { - // if (IsFileNameLengthTooLong(material.name)) - // yield return Validation.Error(string.Format("FileName '{0}' is too long. ", material.name)); - // } - - // var textureNameList = new List(); - // foreach (var material in materials) - // { - // var shader = material.shader; - // int propertyCount = ShaderUtil.GetPropertyCount(shader); - // for (int i = 0; i < propertyCount; i++) - // { - // if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) - // { - // if ((material.GetTexture(ShaderUtil.GetPropertyName(shader, i)) != null)) - // { - // var textureName = material.GetTexture(ShaderUtil.GetPropertyName(shader, i)).name; - // if (!textureNameList.Contains(textureName)) - // textureNameList.Add(textureName); - // } - // } - // } - // } - - // foreach (var textureName in textureNameList) - // { - // if (IsFileNameLengthTooLong(textureName)) - // yield return Validation.Error(string.Format("FileName '{0}' is too long. ", textureName)); - // } - - // var vrmMeta = ExportRoot.GetComponent(); - // if (vrmMeta != null && vrmMeta.Meta != null && vrmMeta.Meta.Thumbnail != null) - // { - // var thumbnailName = vrmMeta.Meta.Thumbnail.name; - // if (IsFileNameLengthTooLong(thumbnailName)) - // yield return Validation.Error(string.Format("FileName '{0}' is too long. ", thumbnailName)); - // } - - // var meshFilters = ExportRoot.GetComponentsInChildren(); - // var meshesName = meshFilters.Select(x => x.sharedMesh.name).Distinct(); - // foreach (var meshName in meshesName) - // { - // if (IsFileNameLengthTooLong(meshName)) - // yield return Validation.Error(string.Format("FileName '{0}' is too long. ", meshName)); - // } - - // var skinnedmeshRenderers = ExportRoot.GetComponentsInChildren(); - // var skinnedmeshesName = skinnedmeshRenderers.Select(x => x.sharedMesh.name).Distinct(); - // foreach (var skinnedmeshName in skinnedmeshesName) - // { - // if (IsFileNameLengthTooLong(skinnedmeshName)) - // yield return Validation.Error(string.Format("FileName '{0}' is too long. ", skinnedmeshName)); - // } - // } - - // /// - // /// 対象のモデルからMeta情報を取得し、エクスポート設定を初期する - // /// - // /// - // public void InitializeFrom(GameObject go) - // { - // // - // // initialize - // // - // // var desc = ExportRoot == null ? null : go.GetComponent(); - // // if (desc == null) - // // { - // // // 初回のVRMエクスポートとみなす - // // ForceTPose = false; // option - // // PoseFreeze = true; - // // } - // // else - // // { - // // // すでに正規化済みとみなす - // // ForceTPose = false; - // // PoseFreeze = false; - // // } - - // // - // // Meta - // // - // // var meta = ExportRoot == null ? null : go.GetComponent(); - // // if (meta != null && meta.Meta != null) - // // { - // // Title = meta.Meta.Title; - // // Version = string.IsNullOrEmpty(meta.Meta.Version) ? "0.0" : meta.Meta.Version; - // // Author = meta.Meta.Author; - // // ContactInformation = meta.Meta.ContactInformation; - // // Reference = meta.Meta.Reference; - // // } - // // else - // // { - // // Title = go.name; - // // Version = "0.0"; - // // } - // } - } -} \ No newline at end of file + } +} diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs index 9108b2a4a..433062a72 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs @@ -63,6 +63,181 @@ namespace VRM } } + /// + /// ボーン名の重複を確認 + /// + /// + bool DuplicateBoneNameExists() + { + if (ExportRoot == null) + { + return false; + } + var bones = ExportRoot.transform.GetComponentsInChildren(); + var duplicates = bones + .GroupBy(p => p.name) + .Where(g => g.Count() > 1) + .Select(g => g.Key); + + return (duplicates.Any()); + } + + public static bool IsFileNameLengthTooLong(string fileName) + { + return fileName.Length > 64; + } + + static bool HasRotationOrScale(GameObject root) + { + foreach (var t in root.GetComponentsInChildren()) + { + if (t.localRotation != Quaternion.identity) + { + return true; + } + if (t.localScale != Vector3.one) + { + return true; + } + } + + return false; + } + + static Vector3 GetForward(Transform l, Transform r) + { + if (l is null || r is null) + { + return Vector3.zero; + } + var lr = (r.position - l.position).normalized; + return Vector3.Cross(lr, Vector3.up); + } + + /// + /// エクスポート可能か検証する + /// + /// + public IEnumerable Validate() + { + if (ExportRoot == null) + { + yield break; + } + + if (DuplicateBoneNameExists()) + { + yield return Validation.Warning("There is a bone with the same name in the hierarchy. If exported, these bones will be automatically renamed."); + } + + // if (ReduceBlendshape && ExportRoot.GetComponent() == null) + // { + // yield return Validation.Error("ReduceBlendshapeSize needs VRMBlendShapeProxy. You need to convert to VRM once."); + // } + + var vertexColor = ExportRoot.GetComponentsInChildren().Any(x => x.sharedMesh.colors.Length > 0); + if (vertexColor) + { + yield return Validation.Warning("This model contains vertex color"); + } + + var renderers = ExportRoot.GetComponentsInChildren(); + if (renderers.All(x => !x.gameObject.activeInHierarchy)) + { + yield return Validation.Error("No active mesh"); + } + + var materials = renderers.SelectMany(x => x.sharedMaterials).Distinct(); + foreach (var material in materials) + { + if (material.shader.name == "Standard") + { + // standard + continue; + } + + if (VRMMaterialExporter.UseUnlit(material.shader.name)) + { + // unlit + continue; + } + + if (VRMMaterialExporter.VRMExtensionShaders.Contains(material.shader.name)) + { + // VRM supported + continue; + } + + yield return Validation.Warning(string.Format("{0}: unknown shader '{1}' is used. this will export as `Standard` fallback", + material.name, + material.shader.name)); + } + + foreach (var material in materials) + { + if (IsFileNameLengthTooLong(material.name)) + yield return Validation.Error(string.Format("FileName '{0}' is too long. ", material.name)); + } + + var textureNameList = new List(); + foreach (var material in materials) + { + var shader = material.shader; + int propertyCount = ShaderUtil.GetPropertyCount(shader); + for (int i = 0; i < propertyCount; i++) + { + if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv) + { + if ((material.GetTexture(ShaderUtil.GetPropertyName(shader, i)) != null)) + { + var textureName = material.GetTexture(ShaderUtil.GetPropertyName(shader, i)).name; + if (!textureNameList.Contains(textureName)) + textureNameList.Add(textureName); + } + } + } + } + + foreach (var textureName in textureNameList) + { + if (IsFileNameLengthTooLong(textureName)) + yield return Validation.Error(string.Format("FileName '{0}' is too long. ", textureName)); + } + + var vrmMeta = ExportRoot.GetComponent(); + if (vrmMeta != null && vrmMeta.Meta != null && vrmMeta.Meta.Thumbnail != null) + { + var thumbnailName = vrmMeta.Meta.Thumbnail.name; + if (IsFileNameLengthTooLong(thumbnailName)) + yield return Validation.Error(string.Format("FileName '{0}' is too long. ", thumbnailName)); + } + + var meshFilters = ExportRoot.GetComponentsInChildren(); + var meshesName = meshFilters.Select(x => x.sharedMesh.name).Distinct(); + foreach (var meshName in meshesName) + { + if (IsFileNameLengthTooLong(meshName)) + yield return Validation.Error(string.Format("FileName '{0}' is too long. ", meshName)); + } + + var skinnedmeshRenderers = ExportRoot.GetComponentsInChildren(); + var skinnedmeshesName = skinnedmeshRenderers.Select(x => x.sharedMesh.name).Distinct(); + foreach (var skinnedmeshName in skinnedmeshesName) + { + if (IsFileNameLengthTooLong(skinnedmeshName)) + yield return Validation.Error(string.Format("FileName '{0}' is too long. ", skinnedmeshName)); + } + + var animator = ExportRoot.GetComponent(); + var l = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg); + var r = animator.GetBoneTransform(HumanBodyBones.RightUpperLeg); + var f = GetForward(l, r); + if (Vector3.Dot(f, Vector3.forward) < 0.8f) + { + yield return Validation.Error("Z+ 向きにしてください"); + } + } + VRMMetaObject m_tmpMeta; Editor m_metaEditor; @@ -132,16 +307,89 @@ namespace VRM } } - //@TODO: Force repaint if scripts recompile private void OnGUI() { + if(m_tmpMeta==null) + { + // OnDisable + return; + } + EditorGUIUtility.labelWidth = 150; EditorGUILayout.LabelField("ExportRoot"); var root = (GameObject)EditorGUILayout.ObjectField(ExportRoot, typeof(GameObject), true); UpdateRoot(root); + // + // root + // + if (root == null) + { + Validation.Error("ExportRootをセットしてください").DrawGUI(); + return; + } + if (root.transform.parent != null) + { + Validation.Error("ExportRootに親はオブジェクトは持てません").DrawGUI(); + return; + } + if (root.transform.localRotation != Quaternion.identity || root.transform.localScale != Vector3.one) + { + Validation.Error("ExportRootに回転・拡大縮小は持てません。子階層で回転・拡大縮小してください").DrawGUI(); + return; + } + if (HasRotationOrScale(ExportRoot)) + { + Validation.Warning("回転・拡大縮小を持つノードが含まれています。正規化が必用です").DrawGUI(); + } + else + { + EditorGUILayout.HelpBox("Root OK", MessageType.Info); + } + + // + // animator + // + var animator = root.GetComponent(); + if (animator == null) + { + Validation.Error("ExportRootに Animator がありません").DrawGUI(); + return; + } + var avatar = animator.avatar; + if (avatar == null) + { + Validation.Error("ExportRootの Animator に Avatar がありません").DrawGUI(); + return; + } + if (!avatar.isValid) + { + Validation.Error("ExportRootの Animator.Avatar が不正です").DrawGUI(); + return; + } + if (!avatar.isHuman) + { + Validation.Error("ExportRootの Animator.Avatar がヒューマノイドではありません。FBX importer の Rig で設定してください").DrawGUI(); + return; + } + var jaw = animator.GetBoneTransform(HumanBodyBones.Jaw); + if (jaw != null) + { + Validation.Warning("Jaw bone is included. It may not be what you intended. Please check the humanoid avatar setting screen").DrawGUI(); + } + else + { + EditorGUILayout.HelpBox("Animator OK", MessageType.Info); + } + + // validation + foreach (var v in m_validations) + { + v.DrawGUI(); + } + // Render contents using Generic Inspector GUI m_ScrollPosition = BeginVerticalScrollView(m_ScrollPosition, false, GUI.skin.verticalScrollbar, "OL Box"); GUIUtility.GetControlID(645789, FocusType.Passive); @@ -152,11 +400,7 @@ namespace VRM { // errors GUILayout.BeginVertical(); - // foreach (var v in m_validations) - // { - // v.DrawGUI(); - // } - GUILayout.FlexibleSpace(); + // GUILayout.FlexibleSpace(); { GUILayout.BeginHorizontal(); @@ -198,50 +442,28 @@ namespace VRM GUILayout.Space(8); } - // Style定義 enum Tabs { Meta, ExportSettings, } - - static class TabStyles - { - private static GUIContent[] _tabToggles = null; - public static GUIContent[] TabToggles - { - get - { - if (_tabToggles == null) - { - _tabToggles = System.Enum.GetNames(typeof(Tabs)).Select(x => new GUIContent(x)).ToArray(); - } - return _tabToggles; - } - } - - public static readonly GUIStyle TabButtonStyle = "LargeButton"; - - // GUI.ToolbarButtonSize.FitToContentsも設定できる - public static readonly GUI.ToolbarButtonSize TabButtonSize = GUI.ToolbarButtonSize.Fixed; - } - Tabs _tab; - protected virtual bool DrawWizardGUI() + GUIStyle TabButtonStyle => "LargeButton"; + + // GUI.ToolbarButtonSize.FitToContentsも設定できる + GUI.ToolbarButtonSize TabButtonSize => GUI.ToolbarButtonSize.Fixed; + + bool DrawWizardGUI() { - if(m_tmpMeta == null) + if (m_tmpMeta == null) { + // disabled return false; } - using (new EditorGUILayout.HorizontalScope()) - { - GUILayout.FlexibleSpace(); - // タブを描画する - _tab = (Tabs)GUILayout.Toolbar((int)_tab, TabStyles.TabToggles, TabStyles.TabButtonStyle, TabStyles.TabButtonSize); - GUILayout.FlexibleSpace(); - } + // tabbar + _tab = TabBar.OnGUI(_tab, TabButtonStyle, TabButtonSize); { if (m_metaEditor == null) @@ -411,29 +633,41 @@ namespace VRM m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/"); // export + if (Meta == null) + { + var metaB = ExportRoot.GetComponent(); + if (metaB == null) + { + metaB = ExportRoot.AddComponent(); + } + metaB.Meta = m_tmpMeta; + } VRMEditorExporter.Export(path, ExportRoot, m_settings); + if (Meta == null) + { + UnityEngine.GameObject.DestroyImmediate(ExportRoot.GetComponent()); + } } void OnWizardUpdate() { - // m_validations.Clear(); - // m_validations.AddRange(m_settings.Validate()); + UpdateRoot(ExportRoot); + + m_validations.Clear(); + m_validations.AddRange(Validate()); // if (Meta != null) // { // m_validations.AddRange(Meta.Validate()); // } // else // { - // m_validations.Add(Validation.Error("meta がありません")); + // // m_validations.Add(Validation.Error("meta がありません")); // } - // var hasError = m_validations.Any(x => !x.CanExport); - // m_IsValid = !hasError; - - UpdateRoot(ExportRoot); + var hasError = m_validations.Any(x => !x.CanExport); + m_IsValid = !hasError; Repaint(); - // GUIUtility.ExitGUI(); } } } diff --git a/Assets/VRM/UniVRM/Editor/Meta/VRMMetaObjectEditor.cs b/Assets/VRM/UniVRM/Editor/Meta/VRMMetaObjectEditor.cs index 5a6e21e80..c06ca3675 100644 --- a/Assets/VRM/UniVRM/Editor/Meta/VRMMetaObjectEditor.cs +++ b/Assets/VRM/UniVRM/Editor/Meta/VRMMetaObjectEditor.cs @@ -88,7 +88,7 @@ namespace VRM { // m_ScriptProp = serializedObject.FindProperty("m_Script"); InitMap(serializedObject); - } + } public override void OnInspectorGUI() { diff --git a/Assets/VRM/UniVRM/Editor/TabBar.cs b/Assets/VRM/UniVRM/Editor/TabBar.cs new file mode 100644 index 000000000..76a1c748b --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/TabBar.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace VRM +{ + + static class TabBar + { + public static T OnGUI(T t, GUIStyle buttonStyle, GUI.ToolbarButtonSize buttonSize) where T : Enum + { + using (new EditorGUILayout.HorizontalScope()) + { + GUILayout.FlexibleSpace(); + // タブを描画する + var value = GUILayout.Toolbar((int)(object)t, TabCache.TabToggles, buttonStyle, buttonSize); + GUILayout.FlexibleSpace(); + return (T)(object)value; + } + } + static class TabCache where T : Enum + { + private static GUIContent[] _tabToggles = null; + + public static GUIContent[] TabToggles + { + get + { + if (_tabToggles == null) + { + _tabToggles = System.Enum.GetNames(typeof(T)).Select(x => new GUIContent(x)).ToArray(); + } + return _tabToggles; + } + } + } + } +} diff --git a/Assets/VRM/UniVRM/Editor/TabBar.cs.meta b/Assets/VRM/UniVRM/Editor/TabBar.cs.meta new file mode 100644 index 000000000..ee1d88d40 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/TabBar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a93e997ef78acca448e14f07cb06e18a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Editor/Tests/InvalidFileNameTest.cs b/Assets/VRM/UniVRM/Editor/Tests/InvalidFileNameTest.cs index ef6d0831c..d7727ce78 100644 --- a/Assets/VRM/UniVRM/Editor/Tests/InvalidFileNameTest.cs +++ b/Assets/VRM/UniVRM/Editor/Tests/InvalidFileNameTest.cs @@ -14,7 +14,7 @@ namespace VRM [TestCase("AliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAlicia", true)] public void DetectFileNameLength(string fileName, bool isIllegal) { - var result = VRMExportSettings.IsFileNameLengthTooLong(fileName); + var result = VRMExporterWizard.IsFileNameLengthTooLong(fileName); Assert.AreEqual(result, isIllegal); }