diff --git a/Assets/VRM/UniVRM/Editor/BlendShape/VRMBlendShapeProxyValidator.cs b/Assets/VRM/UniVRM/Editor/BlendShape/VRMBlendShapeProxyValidator.cs new file mode 100644 index 000000000..37eace657 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/BlendShape/VRMBlendShapeProxyValidator.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace VRM +{ + public static class VRMBlendShapeProxyValidator + { + public static IEnumerable Validate(this VRMBlendShapeProxy p) + { + 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(); + foreach (var c in p.BlendShapeAvatar.Clips) + { + var key = c.Key; + if (used.Contains(key)) + { + yield return Validation.Error($"duplicated BlendShapeKey: {c}"); + } + else + { + used.Add(key); + } + } + + var materialNames = new HashSet(); + foreach (var r in p.GetComponentsInChildren(true)) + { + foreach (var m in r.sharedMaterials) + { + 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.Error($"{c}.Values[{i}].RelativePath({v.RelativePath} is not found"); + } + } + + for (int i = 0; i < c.MaterialValues.Length; ++i) + { + var v = c.MaterialValues[i]; + if (!materialNames.Contains(v.MaterialName)) + { + yield return Validation.Error($"{c}.MaterialValues[{i}].MaterialName({v.MaterialName} is not found"); + } + } + } + } + } +} diff --git a/Assets/VRM/UniVRM/Editor/BlendShape/VRMBlendShapeProxyValidator.cs.meta b/Assets/VRM/UniVRM/Editor/BlendShape/VRMBlendShapeProxyValidator.cs.meta new file mode 100644 index 000000000..c17ae8d8a --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/BlendShape/VRMBlendShapeProxyValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50a28b39ccee4874b85d99c095180e5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExporterVaildator.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExporterVaildator.cs index 3a1fd6ea2..4fbc00a88 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExporterVaildator.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExporterVaildator.cs @@ -182,7 +182,7 @@ namespace VRM } /// - /// エクスポート可能か検証する + /// エクスポート可能か検証する。 /// /// public void Validate(GameObject ExportRoot, VRMExportSettings m_settings, VRMMetaObject meta) @@ -192,34 +192,35 @@ namespace VRM { return; } + var proxy = ExportRoot.GetComponent(); m_validations.AddRange(_Validate(ExportRoot, m_settings)); - if (ExportRoot != null) + m_validations.AddRange(VRMSpringBoneValidator.Validate(ExportRoot)); + var firstPerson = ExportRoot.GetComponent(); + if (firstPerson != null) { - m_validations.AddRange(VRMSpringBoneValidator.Validate(ExportRoot)); - var firstPerson = ExportRoot.GetComponent(); - if (firstPerson != null) + m_validations.AddRange(firstPerson.Validate()); + } + if (proxy != null) + { + m_validations.AddRange(proxy.Validate()); + + // Export サイズ の 計算 + var clips = new List(); + if (proxy.BlendShapeAvatar != null) { - m_validations.AddRange(firstPerson.Validate()); + clips.AddRange(proxy.BlendShapeAvatar.Clips); + } + + ExpectedByteSize = 0; + foreach (var renderer in ExportRoot.GetComponentsInChildren()) + { + var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(renderer.transform, ExportRoot.transform); + var mesh = GetMesh(renderer); + ExpectedByteSize += CalcMeshSize(relativePath, mesh, m_settings, clips); } } MetaHasError = meta.Validate().Any(); - - // サイズ の 計算 - var proxy = ExportRoot.GetComponent(); - var clips = new List(); - if (proxy != null && proxy.BlendShapeAvatar != null) - { - clips.AddRange(proxy.BlendShapeAvatar.Clips); - } - - ExpectedByteSize = 0; - foreach (var renderer in ExportRoot.GetComponentsInChildren()) - { - var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(renderer.transform, ExportRoot.transform); - var mesh = GetMesh(renderer); - ExpectedByteSize += CalcMeshSize(relativePath, mesh, m_settings, clips); - } } static bool ClipsContainsName(List clips, bool onlyPreset, BlendShapeBinding binding) diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs index 127d8e2fa..8d6f92c65 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs @@ -178,6 +178,8 @@ namespace VRM if (Event.current.type == EventType.Layout) { // ArgumentException: Getting control 1's position in a group with only 1 controls when doing repaint Aborting + // Validation により GUI の表示項目が変わる場合があるので、 + // EventType.Layout と EventType.Repaint 間で内容が変わらないようしている。 if (m_requireValidation) { m_validator.Validate(ExportRoot, m_settings, Meta != null ? Meta : m_tmpMeta); @@ -186,7 +188,7 @@ namespace VRM } // - // 事前チェック。ここで失敗する場合は Export UI を表示しない + // Humanoid として適正か? ここで失敗する場合は Export UI を表示しない // if (!m_validator.RootAndHumanoidCheck(ExportRoot, m_settings)) { @@ -202,7 +204,7 @@ namespace VRM GUIUtility.GetControlID(645789, FocusType.Passive); // - // その他の Validation + // VRM の Validation // foreach (var v in m_validator.Validations) {