// https://github.com/vrm-c/UniVRM/issues/474 public static IEnumerable <Validation> Validate(GameObject root) { if (root == null) { yield break; } Dictionary <Transform, List <VRMSpringBone> > rootMap = new Dictionary <Transform, List <VRMSpringBone> >(); foreach (var sb in root.GetComponentsInChildren <VRMSpringBone>()) { foreach (var springRoot in sb.RootBones) { if (!rootMap.TryGetValue(springRoot, out List <VRMSpringBone> list)) { list = new List <VRMSpringBone>(); rootMap.Add(springRoot, list); } list.Add(sb); } } foreach (var kv in rootMap) { if (kv.Value.Count > 1) { // * GameObjectが複数回ルートとして指定されてる(SpringBoneが同じでも別でも) var list = string.Join(", ", kv.Value.Select(x => string.IsNullOrEmpty(x.m_comment) ? x.name : x.m_comment)); yield return(Validation.Warning($"{kv.Key} found multiple. {list}")); } var rootInRootMap = new Dictionary <Transform, List <Transform> >(); foreach (var child in kv.Key.GetComponentsInChildren <Transform>()) { // * Rootから子をだどって、別のRootが現れる if (child == kv.Key) { continue; } if (!rootMap.ContainsKey(child)) { continue; } if (!rootInRootMap.TryGetValue(kv.Key, out List <Transform> rootInRoot)) { rootInRoot = new List <Transform>(); rootInRootMap.Add(kv.Key, rootInRoot); } rootInRoot.Add(child); } foreach (var rootList in rootInRootMap) { var list = string.Join(", ", rootList.Value.Select(x => x.name)); yield return(Validation.Warning($"{rootList.Key} hierarchy contains other root: {list}")); } } }
public IEnumerable <Validation> Validate() { if (transform.localScale != Vector3.one) { yield return(Validation.Warning($"'{name}' GameObject has none 1 scaling")); } else if (transform.lossyScale != Vector3.one) { yield return(Validation.Warning($"'{name}' parent GameObject has none 1 scaling")); } }
// https://github.com/vrm-c/UniVRM/issues/474 public static IEnumerable <Validation> Validate(GameObject root) { if (root == null) { yield break; } var hierarchy = root.GetComponentsInChildren <Transform>(true); Dictionary <Transform, List <VRMSpringBone> > rootMap = new Dictionary <Transform, List <VRMSpringBone> >(); foreach (var sb in root.GetComponentsInChildren <VRMSpringBone>()) { for (int i = 0; i < sb.RootBones.Count; ++i) { var springRoot = sb.RootBones[i]; if (springRoot == null) { yield return(Validation.Error($"[VRMSpringBone]{sb.name}.RootBones[{i}] is null")); continue; } if (!hierarchy.Contains(springRoot)) { yield return(Validation.Error($"[VRMSpringBone]{sb.name}.RootBones[{i}] is out of hierarchy")); continue; } if (!springRoot.transform.EnableForExport()) { yield return(Validation.Error($"[VRMSpringBone]{sb.name}.RootBones[{i}] is not active")); continue; } if (!rootMap.TryGetValue(springRoot, out List <VRMSpringBone> list)) { list = new List <VRMSpringBone>(); rootMap.Add(springRoot, list); } list.Add(sb); } for (int i = 0; i < sb.ColliderGroups.Length; ++i) { var c = sb.ColliderGroups[i]; if (c == null) { yield return(Validation.Error($"{sb.name}.ColliderGroups[{i}] is null")); continue; } if (!hierarchy.Contains(c.transform)) { yield return(Validation.Error($"{sb.name}.ColliderGroups[{i}] is out of hierarchy")); continue; } } } foreach (var kv in rootMap) { if (kv.Value.Count > 1) { // * GameObjectが複数回ルートとして指定されてる(SpringBoneが同じでも別でも) var list = string.Join(", ", kv.Value.Select(x => string.IsNullOrEmpty(x.m_comment) ? x.name : x.m_comment)); yield return(Validation.Warning($"{kv.Key} found multiple. {list}")); } var rootInRootMap = new Dictionary <Transform, List <Transform> >(); foreach (var child in kv.Key.GetComponentsInChildren <Transform>()) { // * Rootから子をだどって、別のRootが現れる if (child == kv.Key) { continue; } if (!rootMap.ContainsKey(child)) { continue; } if (!rootInRootMap.TryGetValue(kv.Key, out List <Transform> rootInRoot)) { rootInRoot = new List <Transform>(); rootInRootMap.Add(kv.Key, rootInRoot); } rootInRoot.Add(child); } foreach (var rootList in rootInRootMap) { var list = string.Join(", ", rootList.Value.Select(x => x.name)); yield return(Validation.Warning($"{rootList.Key} hierarchy contains other root: {list}")); } } }
//@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); // // ここでも validate している。ここで失敗して return した場合は Export UI を表示しない // // // 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 (!root.scene.IsValid()) { // Prefab でシーンに出していないものを判定したい // FIXME: もっと適切な判定があればそれに Validation.Error("シーンに出していない Prefab はエクスポートできません(細かい挙動が違い、想定外の動作をところがあるため)。シーンに展開してからエクスポートしてください").DrawGUI(); return; } if (HasRotationOrScale(ExportRoot)) { if (m_settings.PoseFreeze) { EditorGUILayout.HelpBox("Root OK", MessageType.Info); } else { Validation.Warning("回転・拡大縮小を持つノードが含まれています。正規化が必用です。Setting の PoseFreeze を有効にしてください").DrawGUI(); } } else { if (m_settings.PoseFreeze) { Validation.Warning("正規化済みです。Setting の PoseFreeze は不要です").DrawGUI(); } else { EditorGUILayout.HelpBox("Root OK", MessageType.Info); } } // // animator // var animator = root.GetComponent <Animator>(); if (animator == null) { Validation.Error("ExportRootに Animator がありません").DrawGUI(); return; } 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) { Validation.Error("Z+ 向きにしてください").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); bool modified = DrawWizardGUI(); EditorGUILayout.EndScrollView(); // Create and Other Buttons { // errors GUILayout.BeginVertical(); // GUILayout.FlexibleSpace(); { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUI.enabled = m_IsValid; const BindingFlags kInstanceInvokeFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy; if (m_OtherButton != "" && GUILayout.Button(m_OtherButton, GUILayout.MinWidth(100))) { MethodInfo method = GetType().GetMethod("OnWizardOtherButton", kInstanceInvokeFlags); if (method != null) { method.Invoke(this, null); GUIUtility.ExitGUI(); } else { Debug.LogError("OnWizardOtherButton has not been implemented in script"); } } if (m_CreateButton != "" && GUILayout.Button(m_CreateButton, GUILayout.MinWidth(100))) { MethodInfo method = GetType().GetMethod("OnWizardCreate", kInstanceInvokeFlags); if (method != null) { method.Invoke(this, null); } else { Debug.LogError("OnWizardCreate has not been implemented in script"); } Close(); GUIUtility.ExitGUI(); } GUI.enabled = true; GUILayout.EndHorizontal(); } GUILayout.EndVertical(); } if (modified) { InvokeWizardUpdate(); } GUILayout.Space(8); }
/// <summary> /// エクスポート可能か検証する /// </summary> /// <returns></returns> public IEnumerable <Validation> 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 (m_settings.ReduceBlendshape && ExportRoot.GetComponent <VRMBlendShapeProxy>() == null) { yield return(Validation.Error("ReduceBlendshapeSize needs VRMBlendShapeProxy. You need to convert to VRM once.")); } var vertexColor = ExportRoot.GetComponentsInChildren <SkinnedMeshRenderer>().Any(x => x.sharedMesh.colors.Length > 0); if (vertexColor) { yield return(Validation.Warning("This model contains vertex color")); } var renderers = ExportRoot.GetComponentsInChildren <Renderer>(); 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 <string>(); 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 <VRMMeta>(); 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 <MeshFilter>(); 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 <SkinnedMeshRenderer>(); 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))); } } }
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 (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(VRMExportSettingsEditor.Options.DO_TPOSE.Msg())) { if (State.ExportRoot != null) { // fallback Undo.RecordObjects(State.ExportRoot.GetComponentsInChildren <Transform>(), "tpose"); VRMBoneNormalizer.EnforceTPose(State.ExportRoot); Repaint(); } } if (GUILayout.Button(VRMExportSettingsEditor.Options.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); } EditorGUILayout.HelpBox($"Mesh size: {m_meshes.ExpectedExportByteSize / 1000000.0f:0.0} MByte", MessageType.Info); // // GUI // _tab = MeshUtility.TabBar.OnGUI(_tab); foreach (var meshInfo in m_meshes.Meshes) { switch (meshInfo.VertexColor) { case UniGLTF.MeshExportInfo.VertexColorState.ExistsAndMixed: Validation.Warning($"{meshInfo.Renderer}: Both vcolor.multiply and not multiply unlit materials exist").DrawGUI(); break; } } return(DrawWizardGUI()); }
/// <summary> /// ExportDialogを表示する前に確認する。 /// </summary> /// <param name="ExportRoot"></param> /// <param name="m_settings"></param> /// <returns></returns> public bool RootAndHumanoidCheck(GameObject ExportRoot, VRMExportSettings m_settings, IReadOnlyList <UniGLTF.MeshExportInfo> info) { // // root // if (ExportRoot == null) { Validation.Error(Msg(VRMExporterWizardMessages.ROOT_EXISTS)).DrawGUI(); return(false); } if (ExportRoot.transform.parent != null) { Validation.Error(Msg(VRMExporterWizardMessages.NO_PARENT)).DrawGUI(); return(false); } var renderers = ExportRoot.GetComponentsInChildren <Renderer>(); if (renderers.All(x => !x.EnableForExport())) { Validation.Error(Msg(VRMExporterWizardMessages.NO_ACTIVE_MESH)).DrawGUI(); return(false); } if (HasRotationOrScale(ExportRoot) || info.Any(x => x.ExportBlendShapeCount > 0 && !x.HasSkinning)) { // 正規化必用 if (m_settings.PoseFreeze) { // する EditorGUILayout.HelpBox("PoseFreeze checked. OK", MessageType.Info); } else { // しない Validation.Warning(Msg(VRMExporterWizardMessages.ROTATION_OR_SCALEING_INCLUDED_IN_NODE)).DrawGUI(); } } else { // 不要 if (m_settings.PoseFreeze) { // する Validation.Warning(Msg(VRMExporterWizardMessages.IS_POSE_FREEZE_DONE)).DrawGUI(); } else { // しない EditorGUILayout.HelpBox("Root OK", MessageType.Info); } } // // animator // var animator = ExportRoot.GetComponent <Animator>(); if (animator == null) { Validation.Error(Msg(VRMExporterWizardMessages.NO_ANIMATOR)).DrawGUI(); return(false); } var avatar = animator.avatar; if (avatar == null) { Validation.Error(Msg(VRMExporterWizardMessages.NO_AVATAR_IN_ANIMATOR)).DrawGUI(); return(false); } if (!avatar.isValid) { Validation.Error(Msg(VRMExporterWizardMessages.AVATAR_IS_NOT_VALID)).DrawGUI(); return(false); } if (!avatar.isHuman) { Validation.Error(Msg(VRMExporterWizardMessages.AVATAR_IS_NOT_HUMANOID)).DrawGUI(); return(false); } { 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) { Validation.Error(Msg(VRMExporterWizardMessages.FACE_Z_POSITIVE_DIRECTION)).DrawGUI(); return(false); } } var jaw = animator.GetBoneTransform(HumanBodyBones.Jaw); if (jaw != null) { Validation.Warning(Msg(VRMExporterWizardMessages.JAW_BONE_IS_INCLUDED)).DrawGUI(); } else { EditorGUILayout.HelpBox("Animator OK", MessageType.Info); } return(true); }
IEnumerable <Validation> _Validate(GameObject ExportRoot, VRMExportSettings m_settings) { if (ExportRoot == null) { yield break; } if (DuplicateBoneNameExists(ExportRoot)) { yield return(Validation.Warning(Msg(VRMExporterWizardMessages.DUPLICATE_BONE_NAME_EXISTS))); } if (m_settings.ReduceBlendshape && ExportRoot.GetComponent <VRMBlendShapeProxy>() == null) { yield return(Validation.Error(Msg(VRMExporterWizardMessages.NEEDS_VRM_BLENDSHAPE_PROXY))); } var renderers = ExportRoot.GetComponentsInChildren <Renderer>(); foreach (var r in renderers) { for (int i = 0; i < r.sharedMaterials.Length; ++i) { if (r.sharedMaterials[i] == null) { yield return(Validation.Error($"Renderer: {r.name}.Materials[{i}] is null. please fix it")); } } } var materials = renderers.SelectMany(x => x.sharedMaterials).Where(x => x != null).Distinct(); foreach (var material in materials) { if (material == null) { continue; } 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($"Material: {material.name}. Unknown Shader: \"{material.shader.name}\" is used. {Msg(VRMExporterWizardMessages.UNKNOWN_SHADER)}")); } foreach (var material in materials) { if (IsFileNameLengthTooLong(material.name)) { yield return(Validation.Error(Msg(VRMExporterWizardMessages.FILENAME_TOO_LONG) + material.name)); } } var textureNameList = new List <string>(); 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(Msg(VRMExporterWizardMessages.FILENAME_TOO_LONG) + textureName)); } } var vrmMeta = ExportRoot.GetComponent <VRMMeta>(); if (vrmMeta != null && vrmMeta.Meta != null && vrmMeta.Meta.Thumbnail != null) { var thumbnailName = vrmMeta.Meta.Thumbnail.name; if (IsFileNameLengthTooLong(thumbnailName)) { yield return(Validation.Error(Msg(VRMExporterWizardMessages.FILENAME_TOO_LONG) + thumbnailName)); } } var meshFilters = ExportRoot.GetComponentsInChildren <MeshFilter>(); var meshesName = meshFilters.Select(x => x.sharedMesh.name).Distinct(); foreach (var meshName in meshesName) { if (IsFileNameLengthTooLong(meshName)) { yield return(Validation.Error(Msg(VRMExporterWizardMessages.FILENAME_TOO_LONG) + meshName)); } } var skinnedmeshRenderers = ExportRoot.GetComponentsInChildren <SkinnedMeshRenderer>(); var skinnedmeshesName = skinnedmeshRenderers.Select(x => x.sharedMesh.name).Distinct(); foreach (var skinnedmeshName in skinnedmeshesName) { if (IsFileNameLengthTooLong(skinnedmeshName)) { yield return(Validation.Error(Msg(VRMExporterWizardMessages.FILENAME_TOO_LONG) + skinnedmeshName)); } } }
public static IEnumerable <Validation> Validate(this VRMBlendShapeProxy p, GameObject _) { if (p == null) { yield return(Validation.Error("VRMBlendShapeProxy is null")); yield break; } if (p.BlendShapeAvatar == null) { yield return(Validation.Error("BlendShapeAvatar is null")); yield break; } // presetがユニークか var used = new HashSet <BlendShapeKey>(); foreach (var c in p.BlendShapeAvatar.Clips) { var key = c.Key; if (used.Contains(key)) { yield return(Validation.Error($"duplicated BlendShapeKey: {key}")); } else { used.Add(key); } } var materialNames = new HashSet <string>(); foreach (var r in p.GetComponentsInChildren <Renderer>(true)) { foreach (var m in r.sharedMaterials) { if (m != null) { if (!materialNames.Contains(m.name)) { materialNames.Add(m.name); } } } } // 参照が生きているか foreach (var c in p.BlendShapeAvatar.Clips) { for (int i = 0; i < c.Values.Length; ++i) { var v = c.Values[i]; var target = p.transform.Find(v.RelativePath); if (target == null) { yield return(Validation.Warning($"{c}.Values[{i}].RelativePath({v.RelativePath}) is not found", ValidationContext.Create(c))); } } for (int i = 0; i < c.MaterialValues.Length; ++i) { var v = c.MaterialValues[i]; if (!materialNames.Contains(v.MaterialName)) { yield return(Validation.Warning($"{c}.MaterialValues[{i}].MaterialName({v.MaterialName} is not found")); } } } }
public static IEnumerable <Validation> Validate(GameObject ExportRoot) { if (!ExportRoot) { yield break; } if (MeshInformations != null) { if (HasRotationOrScale(ExportRoot) || MeshInformations.Any(x => x.ExportBlendShapeCount > 0 && !x.HasSkinning)) { // 正規化必用 if (EnableFreeze) { // する yield return(Validation.Info("PoseFreeze checked. OK")); } else { // しない yield return(Validation.Warning(ValidationMessages.ROTATION_OR_SCALEING_INCLUDED_IN_NODE.Msg())); } } else { // 不要 if (EnableFreeze) { // する yield return(Validation.Warning(ValidationMessages.IS_POSE_FREEZE_DONE.Msg())); } else { // しない yield return(Validation.Info("Root OK")); } } } // // animator // var animator = ExportRoot.GetComponent <Animator>(); if (animator == null) { yield return(Validation.Critical(ValidationMessages.NO_ANIMATOR.Msg())); yield break; } // avatar var avatar = animator.avatar; if (avatar == null) { yield return(Validation.Critical(ValidationMessages.NO_AVATAR_IN_ANIMATOR.Msg())); yield break; } if (!avatar.isValid) { yield return(Validation.Critical(ValidationMessages.AVATAR_IS_NOT_VALID.Msg())); yield break; } if (!avatar.isHuman) { yield return(Validation.Critical(ValidationMessages.AVATAR_IS_NOT_HUMANOID.Msg())); yield break; } // direction { 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.Critical(ValidationMessages.FACE_Z_POSITIVE_DIRECTION.Msg())); yield break; } } { 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())); } }
//@TODO: Force repaint if scripts recompile private void OnGUI() { if (m_tmpMeta == null) { // OnDisable return; } EditorGUIUtility.labelWidth = 150; // lang var lang = (VRMExporterWizardMessages.Languages)EditorGUILayout.EnumPopup("lang", m_lang); if (lang != m_lang) { m_lang = lang; EditorPrefs.SetString(LANG_KEY, m_lang.ToString()); } EditorGUILayout.LabelField("ExportRoot"); { var root = (GameObject)EditorGUILayout.ObjectField(ExportRoot, typeof(GameObject), true); UpdateRoot(root); } // // ここでも validate している。ここで失敗して return した場合は Export UI を表示しない // // // root // if (ExportRoot == null) { Validation.Error(Msg.ROOT_EXISTS).DrawGUI(); return; } if (ExportRoot.transform.parent != null) { Validation.Error(Msg.NO_PARENT).DrawGUI(); return; } if (ExportRoot.transform.localRotation != Quaternion.identity || ExportRoot.transform.localScale != Vector3.one) { Validation.Error(Msg.ROOT_WITHOUT_ROTATION_AND_SCALING_CHANGED).DrawGUI(); return; } var renderers = ExportRoot.GetComponentsInChildren <Renderer>(); if (renderers.All(x => !EnableRenderer(x))) { Validation.Error(Msg.NO_ACTIVE_MESH).DrawGUI(); return; } if (HasRotationOrScale(ExportRoot)) { if (m_settings.PoseFreeze) { EditorGUILayout.HelpBox("Root OK", MessageType.Info); } else { Validation.Warning(Msg.ROTATION_OR_SCALEING_INCLUDED_IN_NODE).DrawGUI(); } } else { if (m_settings.PoseFreeze) { Validation.Warning(Msg.IS_POSE_FREEZE_DONE).DrawGUI(); } else { EditorGUILayout.HelpBox("Root OK", MessageType.Info); } } // // animator // var animator = ExportRoot.GetComponent <Animator>(); if (animator == null) { Validation.Error(Msg.NO_ANIMATOR).DrawGUI(); return; } 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) { Validation.Error(Msg.FACE_Z_POSITIVE_DIRECTION).DrawGUI(); return; } var avatar = animator.avatar; if (avatar == null) { Validation.Error(Msg.NO_AVATAR_IN_ANIMATOR).DrawGUI(); return; } if (!avatar.isValid) { Validation.Error(Msg.AVATAR_IS_NOT_VALID).DrawGUI(); return; } if (!avatar.isHuman) { Validation.Error(Msg.AVATAR_IS_NOT_HUMANOID).DrawGUI(); return; } var jaw = animator.GetBoneTransform(HumanBodyBones.Jaw); if (jaw != null) { Validation.Warning(Msg.JAW_BONE_IS_INCLUDED).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); bool modified = DrawWizardGUI(); EditorGUILayout.EndScrollView(); // Create and Other Buttons { // errors GUILayout.BeginVertical(); // GUILayout.FlexibleSpace(); { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUI.enabled = m_IsValid; const BindingFlags kInstanceInvokeFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy; if (m_OtherButton != "" && GUILayout.Button(m_OtherButton, GUILayout.MinWidth(100))) { MethodInfo method = GetType().GetMethod("OnWizardOtherButton", kInstanceInvokeFlags); if (method != null) { method.Invoke(this, null); GUIUtility.ExitGUI(); } else { Debug.LogError("OnWizardOtherButton has not been implemented in script"); } } if (m_CreateButton != "" && GUILayout.Button(m_CreateButton, GUILayout.MinWidth(100))) { MethodInfo method = GetType().GetMethod("OnWizardCreate", kInstanceInvokeFlags); if (method != null) { method.Invoke(this, null); } else { Debug.LogError("OnWizardCreate has not been implemented in script"); } Close(); GUIUtility.ExitGUI(); } GUI.enabled = true; GUILayout.EndHorizontal(); } GUILayout.EndVertical(); } if (modified) { InvokeWizardUpdate(); } GUILayout.Space(8); }
/// <summary> /// エクスポート可能か検証する /// </summary> /// <returns></returns> public IEnumerable <Validation> Validate() { if (ExportRoot == null) { yield break; } if (DuplicateBoneNameExists()) { yield return(Validation.Warning(Msg.DUPLICATE_BONE_NAME_EXISTS)); } if (m_settings.ReduceBlendshape && ExportRoot.GetComponent <VRMBlendShapeProxy>() == null) { yield return(Validation.Error(Msg.NEEDS_VRM_BLENDSHAPE_PROXY)); } var vertexColor = ExportRoot.GetComponentsInChildren <SkinnedMeshRenderer>().Any(x => x.sharedMesh.colors.Length > 0); if (vertexColor) { yield return(Validation.Warning(Msg.VERTEX_COLOR_IS_INCLUDED)); } var renderers = ExportRoot.GetComponentsInChildren <Renderer>(); 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($"Material: {material.name}. Unknown Shader: \"{material.shader.name}\" is used. {Msg.UNKNOWN_SHADER}")); } foreach (var material in materials) { if (IsFileNameLengthTooLong(material.name)) { yield return(Validation.Error(Msg.FILENAME_TOO_LONG + material.name)); } } var textureNameList = new List <string>(); 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(Msg.FILENAME_TOO_LONG + textureName)); } } var vrmMeta = ExportRoot.GetComponent <VRMMeta>(); if (vrmMeta != null && vrmMeta.Meta != null && vrmMeta.Meta.Thumbnail != null) { var thumbnailName = vrmMeta.Meta.Thumbnail.name; if (IsFileNameLengthTooLong(thumbnailName)) { yield return(Validation.Error(Msg.FILENAME_TOO_LONG + thumbnailName)); } } var meshFilters = ExportRoot.GetComponentsInChildren <MeshFilter>(); var meshesName = meshFilters.Select(x => x.sharedMesh.name).Distinct(); foreach (var meshName in meshesName) { if (IsFileNameLengthTooLong(meshName)) { yield return(Validation.Error(Msg.FILENAME_TOO_LONG + meshName)); } } var skinnedmeshRenderers = ExportRoot.GetComponentsInChildren <SkinnedMeshRenderer>(); var skinnedmeshesName = skinnedmeshRenderers.Select(x => x.sharedMesh.name).Distinct(); foreach (var skinnedmeshName in skinnedmeshesName) { if (IsFileNameLengthTooLong(skinnedmeshName)) { yield return(Validation.Error(Msg.FILENAME_TOO_LONG + skinnedmeshName)); } } }
private void OnGUI() { if (m_tmpMeta == null) { // OnDisable return; } EditorGUIUtility.labelWidth = 150; // lang M17N.Getter.OnGuiSelectLang(); EditorGUILayout.LabelField("ExportRoot"); { var root = (GameObject)EditorGUILayout.ObjectField(ExportRoot, typeof(GameObject), true); UpdateRoot(root); } // 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) { Validate(); } // // Humanoid として適正か? ここで失敗する場合は Export UI を表示しない // if (!m_validator.RootAndHumanoidCheck(ExportRoot, m_settings, m_meshes.Meshes)) { return; } EditorGUILayout.HelpBox($"Mesh size: {m_meshes.ExpectedExportByteSize / 1000000.0f:0.0} MByte", MessageType.Info); _tab = TabBar.OnGUI(_tab, TabButtonStyle, TabButtonSize); // Render contents using Generic Inspector GUI m_ScrollPosition = BeginVerticalScrollView(m_ScrollPosition, false, GUI.skin.verticalScrollbar, "OL Box"); GUIUtility.GetControlID(645789, FocusType.Passive); // // VRM の Validation // foreach (var v in m_validator.Validations) { v.DrawGUI(); } foreach (var meshInfo in m_meshes.Meshes) { switch (meshInfo.VertexColor) { case UniGLTF.MeshExportInfo.VertexColorState.ExistsAndMixed: Validation.Warning($"{meshInfo.Renderer}: Both vcolor.multiply and not multiply unlit materials exist").DrawGUI(); break; } } bool modified = DrawWizardGUI(); EditorGUILayout.EndScrollView(); // Create and Other Buttons { // errors GUILayout.BeginVertical(); // GUILayout.FlexibleSpace(); { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUI.enabled = m_validator.IsValid; if (GUILayout.Button("Export", GUILayout.MinWidth(100))) { OnWizardCreate(); Close(); GUIUtility.ExitGUI(); } GUI.enabled = true; GUILayout.EndHorizontal(); } GUILayout.EndVertical(); } GUILayout.Space(8); if (modified) { m_requireValidation = true; Repaint(); } }
/// <summary> /// エクスポート可能か検証する /// </summary> /// <returns></returns> public IEnumerable <Validation> Validate() { if (Source == null) { yield return(Validation.Error("Require source")); yield break; } if (Source.transform.position != Vector3.zero || Source.transform.rotation != Quaternion.identity || Source.transform.localScale != Vector3.one) { EditorUtility.DisplayDialog("Error", "The Root transform should have Default translation, rotation and scale.", "ok"); yield return(Validation.Error("The Root transform should have Default translation, rotation and scale.")); } var animator = Source.GetComponent <Animator>(); 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.Error("Find duplicate Bone names. Please check model's bone names. ")); } if (string.IsNullOrEmpty(Title)) { yield return(Validation.Error("Require Title. ")); } if (string.IsNullOrEmpty(Version)) { yield return(Validation.Error("Require Version. ")); } if (string.IsNullOrEmpty(Author)) { yield return(Validation.Error("Require Author. ")); } if (ReduceBlendshape && Source.GetComponent <VRMBlendShapeProxy>() == null) { yield return(Validation.Error("ReduceBlendshapeSize needs VRMBlendShapeProxy. You need to convert to VRM once.")); } var vertexColor = Source.GetComponentsInChildren <SkinnedMeshRenderer>().Any(x => x.sharedMesh.colors.Length > 0); if (vertexColor) { yield return(Validation.Warning("This model contains vertex color")); } var renderers = Source.GetComponentsInChildren <Renderer>(); 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 (MaterialExporter.UseUnlit(material.shader.name)) { // unlit continue; } if (VRMMaterialExporter.VRMExtensionShaders.Contains(material.shader.name)) { // VRM supported continue; } yield return(Validation.Warning(string.Format("unknown material '{0}' is used. this will export as `Standard` fallback", 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 <string>(); 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 = Source.GetComponent <VRMMeta>(); 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 = Source.GetComponentsInChildren <MeshFilter>(); 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 = Source.GetComponentsInChildren <SkinnedMeshRenderer>(); 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))); } } }
/// <summary> /// エクスポート可能か検証する /// </summary> /// <returns></returns> public IEnumerable <Validation> Validate() { if (Source == null) { yield return(Validation.Error("Require source")); yield break; } var animator = Source.GetComponent <Animator>(); 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.Error("Find duplicate Bone names. Please check model's bone names. ")); } if (string.IsNullOrEmpty(Title)) { yield return(Validation.Error("Require Title. ")); } if (string.IsNullOrEmpty(Version)) { yield return(Validation.Error("Require Version. ")); } if (string.IsNullOrEmpty(Author)) { yield return(Validation.Error("Require Author. ")); } if (ReduceBlendshape && Source.GetComponent <VRMBlendShapeProxy>() == null) { yield return(Validation.Error("ReduceBlendshapeSize is need VRMBlendShapeProxy, you need to convert to VRM once.")); } var vertexColor = Source.GetComponentsInChildren <SkinnedMeshRenderer>().Any(x => x.sharedMesh.colors.Length > 0); if (vertexColor) { yield return(Validation.Warning("This model contains vertex color")); } var renderers = Source.GetComponentsInChildren <Renderer>(); 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 (MaterialExporter.UseUnlit(material.shader.name)) { // unlit continue; } if (VRMMaterialExporter.VRMExtensionShaders.Contains(material.shader.name)) { // VRM supported continue; } yield return(Validation.Warning(string.Format("unknown material '{0}' is used. this will export as `Standard` fallback", material.shader.name))); } }