Export dialog のバリデーターを整理

* vrm-1.0 と共用にできるように移動
* 例外のcatch
This commit is contained in:
ousttrue 2020-11-10 16:37:22 +09:00
parent 27f64943d8
commit 3c86952228
49 changed files with 1161 additions and 994 deletions

View File

@ -113,20 +113,6 @@ namespace MeshUtility
helpString = "select target skinnedMesh and animator";
}
static int IndexOf(Transform[] list, Transform target)
{
for (int i = 0; i < list.Length; ++i)
{
if (list[i] == target)
{
return i;
}
}
return -1;
}
SkinnedMeshRenderer _Erase(GameObject go)
{
if (go == null)

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d7ee61ea6eb4ff541831cba9ce76371f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace MeshUtility
{
public delegate IEnumerable<Validation> Validator(GameObject root);
/// <summary>
/// Exportダイアログの共通機能
///
/// * ExportRoot 管理
/// * Invalidate 管理
///
/// </summary>
public class ExporterDialogState : IDisposable
{
public void Dispose()
{
ExportRoot = null;
ExportRootChanged = null;
}
#region ExportRoot管理
GameObject m_root;
public event Action<GameObject> ExportRootChanged;
void RaiseExportRootChanged()
{
var tmp = ExportRootChanged;
if (tmp == null) return;
tmp(m_root);
}
public GameObject ExportRoot
{
get { return m_root; }
set
{
if (m_root == value)
{
return;
}
m_root = value;
m_requireValidation = true;
RaiseExportRootChanged();
}
}
#endregion
#region Validation管理
bool m_requireValidation = true;
public void Invalidate()
{
// UpdateRoot(ExportRoot);
m_requireValidation = true;
}
List<Validation> m_validations = new List<Validation>();
public IReadOnlyList<Validation> Validations => m_validations;
/// <summary>
/// EditorWindow.OnGUI から
///
/// if (Event.current.type == EventType.Layout)
/// {
/// m_state.Validate(ValidatorFactory());
/// }
///
/// IEnumerable<MeshUtility.Validator> ValidatorFactory()
/// {
/// yield break;
/// }
///
/// のように呼び出してね
/// </summary>
public void Validate(IEnumerable<Validator> validators)
{
if (m_requireValidation)
{
m_validations.Clear();
m_requireValidation = false;
foreach (var validator in validators)
{
foreach (var validation in validator(ExportRoot))
{
try
{
m_validations.Add(validation);
if (validation.ErrorLevel == ErrorLevels.Critical)
{
// 打ち切り
return;
}
}
catch (Exception ex)
{
// ERROR
m_validations.Add(Validation.Critical(ex.Message));
return;
}
}
}
}
}
#endregion
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 7829d135d35830f4bb9235eb10b3de1f
guid: 77f1f7ea6f2c8ef4b8f284d9aa0187a4
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,8 +1,7 @@
using System.Linq;
using MeshUtility;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
public static class ExporterExtensions
{

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 84a3f0f4ae5319a46839f08a76ae0edd
guid: f76ebfda1e249cb48890de41cc221889
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
namespace MeshUtility.M17N
{
/// <summary>
/// 多言語対応
/// </summary>
public enum Languages
{
ja,
en,
}
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public class LangMsgAttribute : System.Attribute
{
public Languages Language;
public string Message;
public LangMsgAttribute(Languages language, string msg)
{
Language = language;
Message = msg;
}
}
public static class MsgCache<T> where T : Enum
{
static Dictionary<Languages, Dictionary<T, string>> s_cache = new Dictionary<Languages, Dictionary<T, string>>();
static LangMsgAttribute GetAttribute(T value, Languages language)
{
var t = typeof(T);
var memberInfos = t.GetMember(value.ToString());
var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == t);
var attr = enumValueMemberInfo.GetCustomAttributes(typeof(LangMsgAttribute), false).Select(x => (LangMsgAttribute)x).ToArray();
if (attr == null || attr.Length == 0)
{
return null;
}
var match = attr.FirstOrDefault(x => x.Language == language);
if (match != null)
{
return match;
}
return attr.First();
}
public static string Get(Languages language, T key)
{
if (!s_cache.TryGetValue(language, out Dictionary<T, string> map))
{
map = new Dictionary<T, string>();
var t = typeof(T);
foreach (T value in Enum.GetValues(t))
{
var match = GetAttribute(value, language);
// Attribute。無かったら enum の ToString
map.Add(value, match != null ? match.Message : key.ToString());
}
s_cache.Add(language, map);
}
return map[key];
}
}
public static class Getter
{
const string LANG_KEY = "VRM_LANG";
static Languages? m_lang;
public static Languages Lang
{
get
{
return m_lang.GetValueOrDefault();
}
}
public static string Msg<T>(this T key) where T : Enum
{
return M17N.MsgCache<T>.Get(Lang, key);
}
public static void OnGuiSelectLang()
{
var lang = (M17N.Languages)EditorGUILayout.EnumPopup("lang", Lang);
if (lang != Lang)
{
m_lang = lang;
EditorPrefs.SetString(LANG_KEY, M17N.Getter.Lang.ToString());
}
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 515984b15b5b5bf4baca4fa2d6344fcf
guid: 3cde586e47f32424a82a4af6c2ce031e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -3,13 +3,25 @@ using System.Linq;
using UnityEditor;
using UnityEngine;
namespace VRM
namespace MeshUtility
{
static class TabBar
public static class TabBar
{
public static T OnGUI<T>(T t, GUIStyle buttonStyle, GUI.ToolbarButtonSize buttonSize) where T : Enum
/// <summary>
/// GUI.ToolbarButtonSize.FitToContentsも設定できる
/// </summary>
/// <param name="t"></param>
/// <param name="buttonStyle"></param>
/// <param name="buttonSize"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T OnGUI<T>(T t, GUIStyle buttonStyle = null, GUI.ToolbarButtonSize buttonSize = GUI.ToolbarButtonSize.Fixed) where T : Enum
{
if (buttonStyle == null)
{
buttonStyle = "LargeButton";
}
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
@ -19,6 +31,7 @@ namespace VRM
return (T)(object)value;
}
}
static class TabCache<T> where T : Enum
{
private static GUIContent[] _tabToggles = null;

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a93e997ef78acca448e14f07cb06e18a
guid: 639b5c2a1cd8f5345b7d8a799712e25f
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1c450c4a7f888424490abbbc29c10a1a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Linq;
using MeshUtility.M17N;
using UnityEngine;
namespace MeshUtility.Validators
{
public static class HierarchyValidator
{
public enum ExportValidatorMessages
{
[LangMsg(Languages.ja, "ExportRootをセットしてください")]
[LangMsg(Languages.en, "Please set up a ExportRoot for model export")]
ROOT_EXISTS,
[LangMsg(Languages.ja, "ExportRootに親はオブジェクトは持てません")]
[LangMsg(Languages.en, "ExportRoot must be topmost parent")]
NO_PARENT,
[LangMsg(Languages.ja, "ヒエラルキーに active なメッシュが含まれていない")]
[LangMsg(Languages.en, "No active mesh")]
NO_ACTIVE_MESH,
[LangMsg(Languages.ja, "ヒエラルキーの中に同じ名前のGameObjectが含まれている。 エクスポートした場合に自動でリネームする")]
[LangMsg(Languages.en, "There are bones with the same name in the hierarchy. They will be automatically renamed after export")]
DUPLICATE_BONE_NAME_EXISTS,
}
/// <summary>
/// ボーン名の重複を確認
/// </summary>
/// <returns></returns>
static bool DuplicateNodeNameExists(GameObject ExportRoot)
{
if (ExportRoot == null)
{
return false;
}
var bones = ExportRoot.transform.GetComponentsInChildren<Transform>();
var duplicates = bones
.GroupBy(p => p.name)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
return (duplicates.Any());
}
public static IEnumerable<Validation> Validate(GameObject ExportRoot)
{
if (ExportRoot == null)
{
yield return Validation.Critical(ExportValidatorMessages.ROOT_EXISTS.Msg());
yield break;
}
if (ExportRoot.transform.parent != null)
{
yield return Validation.Critical(ExportValidatorMessages.NO_PARENT.Msg());
yield break;
}
var renderers = ExportRoot.GetComponentsInChildren<Renderer>();
if (renderers.All(x => !x.EnableForExport()))
{
yield return Validation.Critical(ExportValidatorMessages.NO_ACTIVE_MESH.Msg());
yield break;
}
if (DuplicateNodeNameExists(ExportRoot))
{
yield return Validation.Warning(ExportValidatorMessages.DUPLICATE_BONE_NAME_EXISTS.Msg());
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7c870883375b0c14ca5da057cccc84c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,164 @@
using System.Collections.Generic;
using System.Linq;
using MeshUtility.M17N;
using UnityEngine;
namespace MeshUtility.Validators
{
public static class HumanoidValidator
{
public enum ValidationMessages
{
[LangMsg(Languages.ja, "回転・拡大縮小もしくはWeightの無いBlendShapeが含まれています。正規化が必用です。Setting の PoseFreeze を有効にしてください")]
[LangMsg(Languages.en, " Normalization is required. There are nodes (child GameObject) where rotation and scaling or blendshape without bone weight are not default. Please enable PoseFreeze")]
ROTATION_OR_SCALEING_INCLUDED_IN_NODE,
[LangMsg(Languages.ja, "正規化済みです。Setting の PoseFreeze は不要です")]
[LangMsg(Languages.en, "Normalization has been done. PoseFreeze is not required")]
IS_POSE_FREEZE_DONE,
[LangMsg(Languages.ja, "ExportRootに Animator がありません")]
[LangMsg(Languages.en, "No Animator in ExportRoot")]
NO_ANIMATOR,
[LangMsg(Languages.ja, "Z+ 向きにしてください")]
[LangMsg(Languages.en, "The model needs to face the positive Z-axis")]
FACE_Z_POSITIVE_DIRECTION,
[LangMsg(Languages.ja, "ExportRootの Animator に Avatar がありません")]
[LangMsg(Languages.en, "No Avatar in ExportRoot's Animator")]
NO_AVATAR_IN_ANIMATOR,
[LangMsg(Languages.ja, "ExportRootの Animator.Avatar が不正です")]
[LangMsg(Languages.en, "Animator.avatar in ExportRoot is not valid")]
AVATAR_IS_NOT_VALID,
[LangMsg(Languages.ja, "ExportRootの Animator.Avatar がヒューマイドではありません。FBX importer の Rig で設定してください")]
[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.en, "Jaw bone is included. It may not what you intended. Please check the humanoid avatar setting screen")]
JAW_BONE_IS_INCLUDED,
}
public static bool HasRotationOrScale(GameObject root)
{
foreach (var t in root.GetComponentsInChildren<Transform>())
{
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 == null || r == null)
{
return Vector3.zero;
}
var lr = (r.position - l.position).normalized;
return Vector3.Cross(lr, Vector3.up);
}
public static IReadOnlyList<MeshExportInfo> MeshInformations;
public static bool EnableFreeze;
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 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");
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9013f2150f3c6d943a6f6b6394f7a1ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using MeshUtility.M17N;
using UnityEngine;
namespace MeshUtility.Validators
{
public static class MaterialValidator
{
public enum ValidationMessages
{
[LangMsg(Languages.ja, "Standard, Unlit, MToon 以外のマテリアルは、Standard になります")]
[LangMsg(Languages.en, "It will export as `Standard` fallback")]
UNKNOWN_SHADER,
}
public static IEnumerable<Validation> Validate(GameObject ExportRoot)
{
if (ExportRoot == null)
{
yield break;
}
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 (UniGLTF.ShaderPropExporter.PreShaderPropExporter.UseUnlit(material.shader.name))
{
// unlit
continue;
}
if (UniGLTF.ShaderPropExporter.PreShaderPropExporter.VRMExtensionShaders.Contains(material.shader.name))
{
// VRM supported
continue;
}
yield return Validation.Warning($"Material: {material.name}. Unknown Shader: \"{material.shader.name}\" is used. {ValidationMessages.UNKNOWN_SHADER.Msg()}");
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa8f593f0ade99a4d9cc23947cdded6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using MeshUtility.M17N;
using UnityEditor;
using UnityEngine;
namespace MeshUtility.Validators
{
public class NameValidator
{
public enum ValidationMessages
{
[LangMsg(Languages.ja, "名前が長すぎる。リネームしてください: ")]
[LangMsg(Languages.en, "FileName is too long: ")]
FILENAME_TOO_LONG,
}
public static bool IsFileNameLengthTooLong(string fileName)
{
return fileName.Length > 64;
}
public static IEnumerable<Validation> Validate(GameObject ExportRoot)
{
var renderers = ExportRoot.GetComponentsInChildren<Renderer>();
var materials = renderers.SelectMany(x => x.sharedMaterials).Where(x => x != null).Distinct();
foreach (var material in materials)
{
if (IsFileNameLengthTooLong(material.name))
yield return Validation.Error(ValidationMessages.FILENAME_TOO_LONG.Msg() + 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(ValidationMessages.FILENAME_TOO_LONG.Msg() + textureName);
}
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(ValidationMessages.FILENAME_TOO_LONG.Msg() + 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(ValidationMessages.FILENAME_TOO_LONG.Msg() + skinnedmeshName);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27720b3b18437104b91e44c3f66166ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,8 @@
{
"name": "MeshUtility.Editor",
"references": [
"MeshUtility"
"MeshUtility",
"ShaderProperty.Runtime"
],
"optionalUnityReferences": [],
"includePlatforms": [

View File

@ -0,0 +1,125 @@
using System;
using UnityEngine;
namespace MeshUtility
{
public struct MeshWithRenderer
{
public Mesh Mesh;
[Obsolete("Use Renderer")]
public Renderer Rendererer { get { return Renderer; } set { Renderer = value; } }
public Renderer Renderer;
}
[Serializable]
public struct MeshExportInfo
{
public Renderer Renderer;
public Mesh Mesh;
public bool IsRendererActive;
public bool Skinned;
public bool HasNormal => Mesh != null && Mesh.normals != null && Mesh.normals.Length == Mesh.vertexCount;
public bool HasUV => Mesh != null && Mesh.uv != null && Mesh.uv.Length == Mesh.vertexCount;
public bool HasVertexColor => Mesh.colors != null && Mesh.colors.Length == Mesh.vertexCount
&& VertexColor == VertexColorState.ExistsAndIsUsed
|| VertexColor == VertexColorState.ExistsAndMixed // Export する
;
public bool HasSkinning => Mesh.boneWeights != null && Mesh.boneWeights.Length == Mesh.vertexCount;
/// <summary>
/// Mesh に頂点カラーが含まれているか。
/// 含まれている場合にマテリアルは Unlit.VColorMultiply になっているか?
/// </summary>
public enum VertexColorState
{
// VColorが存在しない
None,
// VColorが存在して使用している(UnlitはすべてVColorMultiply)
ExistsAndIsUsed,
// VColorが存在するが使用していない(UnlitはすべてVColorNone。もしくはUnlitが存在しない)
ExistsButNotUsed,
// VColorが存在して、Unlit.Multiply と Unlit.NotMultiply が混在している。 Unlit.NotMultiply を MToon か Standardに変更した方がよい
ExistsAndMixed,
}
public VertexColorState VertexColor;
static bool MaterialUseVertexColor(Material m)
{
if (m == null)
{
return false;
}
if (m.shader.name != UniGLTF.UniUnlit.Utils.ShaderName)
{
return false;
}
if (UniGLTF.UniUnlit.Utils.GetVColBlendMode(m) != UniGLTF.UniUnlit.UniUnlitVertexColorBlendOp.Multiply)
{
return false;
}
return true;
}
public static VertexColorState DetectVertexColor(Mesh mesh, Material[] materials)
{
if (mesh != null && mesh.colors != null && mesh.colors.Length == mesh.vertexCount)
{
// mesh が 頂点カラーを保持している
VertexColorState? state = default;
if (materials != null)
{
foreach (var m in materials)
{
var currentState = MaterialUseVertexColor(m)
? MeshUtility.MeshExportInfo.VertexColorState.ExistsAndIsUsed
: MeshUtility.MeshExportInfo.VertexColorState.ExistsButNotUsed
;
if (state.HasValue)
{
if (state.Value != currentState)
{
state = MeshUtility.MeshExportInfo.VertexColorState.ExistsAndMixed;
break;
}
}
else
{
state = currentState;
}
}
}
return state.GetValueOrDefault(VertexColorState.None);
}
else
{
return VertexColorState.None;
}
}
public int VertexCount;
/// <summary>
/// Position, UV, Normal
/// [Color]
/// [SkinningWeight]
/// </summary>
public int ExportVertexSize;
public int IndexCount;
// int 決め打ち
public int IndicesSize => IndexCount * 4;
public int ExportBlendShapeVertexSize;
public int TotalBlendShapeCount;
public int ExportBlendShapeCount;
public int ExportByteSize => ExportVertexSize * VertexCount + IndicesSize + ExportBlendShapeCount * ExportBlendShapeVertexSize * VertexCount;
public string Summary;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2af6d95e4a0e06944b34d891d798b88b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,3 +1,15 @@
{
"name": "MeshUtility"
}
{
"name": "MeshUtility",
"references": [
"UniUnlit",
"ShaderProperty.Runtime"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace MeshUtility
{
public enum ErrorLevels
{
// Exportできる。お知らせ
Info,
// Exportできる。不具合の可能性
Warning,
// Exportするために修正が必用
Error,
// Exportの前提を満たさない
Critical,
}
public struct Validation
{
/// <summary>
/// エクスポート可能か否か。
/// true のメッセージは警告
/// false のメッセージはエラー
/// </summary>
public readonly ErrorLevels ErrorLevel;
public bool CanExport
{
get
{
switch (ErrorLevel)
{
case ErrorLevels.Info:
case ErrorLevels.Warning:
return true;
case ErrorLevels.Error:
case ErrorLevels.Critical:
return false;
}
throw new NotImplementedException();
}
}
public readonly String Message;
Validation(ErrorLevels canExport, string message)
{
ErrorLevel = canExport;
Message = message;
}
#if UNITY_EDITOR
public void DrawGUI()
{
if (string.IsNullOrEmpty(Message))
{
return;
}
switch (ErrorLevel)
{
case ErrorLevels.Info:
EditorGUILayout.HelpBox(Message, MessageType.Info);
break;
case ErrorLevels.Warning:
EditorGUILayout.HelpBox(Message, MessageType.Warning);
break;
case ErrorLevels.Critical:
case ErrorLevels.Error:
EditorGUILayout.HelpBox(Message, MessageType.Error);
break;
default:
throw new NotImplementedException();
}
}
#endif
public static Validation Critical(string msg)
{
return new Validation(ErrorLevels.Critical, msg);
}
public static Validation Error(string msg)
{
return new Validation(ErrorLevels.Error, msg);
}
public static Validation Warning(string msg)
{
return new Validation(ErrorLevels.Warning, msg);
}
public static Validation Info(string msg)
{
return new Validation(ErrorLevels.Info, msg);
}
public void AddTo(IList<Validation> dst)
{
dst.Add(this);
}
}
}

View File

@ -1,132 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UniGLTF
{
public struct MeshWithRenderer
{
public Mesh Mesh;
[Obsolete("Use Renderer")]
public Renderer Rendererer { get { return Renderer; } set { Renderer = value; } }
public Renderer Renderer;
}
[Serializable]
public struct MeshExportInfo
{
public Renderer Renderer;
public Mesh Mesh;
public bool IsRendererActive;
public bool Skinned;
public bool HasNormal => Mesh != null && Mesh.normals != null && Mesh.normals.Length == Mesh.vertexCount;
public bool HasUV => Mesh != null && Mesh.uv != null && Mesh.uv.Length == Mesh.vertexCount;
public bool HasVertexColor => Mesh.colors != null && Mesh.colors.Length == Mesh.vertexCount
&& VertexColor == VertexColorState.ExistsAndIsUsed
|| VertexColor == VertexColorState.ExistsAndMixed // Export する
;
public bool HasSkinning => Mesh.boneWeights != null && Mesh.boneWeights.Length == Mesh.vertexCount;
/// <summary>
/// Mesh に頂点カラーが含まれているか。
/// 含まれている場合にマテリアルは Unlit.VColorMultiply になっているか?
/// </summary>
public enum VertexColorState
{
// VColorが存在しない
None,
// VColorが存在して使用している(UnlitはすべてVColorMultiply)
ExistsAndIsUsed,
// VColorが存在するが使用していない(UnlitはすべてVColorNone。もしくはUnlitが存在しない)
ExistsButNotUsed,
// VColorが存在して、Unlit.Multiply と Unlit.NotMultiply が混在している。 Unlit.NotMultiply を MToon か Standardに変更した方がよい
ExistsAndMixed,
}
public VertexColorState VertexColor;
static bool MaterialUseVertexColor(Material m)
{
if (m == null)
{
return false;
}
if (m.shader.name != UniGLTF.UniUnlit.Utils.ShaderName)
{
return false;
}
if (UniGLTF.UniUnlit.Utils.GetVColBlendMode(m) != UniGLTF.UniUnlit.UniUnlitVertexColorBlendOp.Multiply)
{
return false;
}
return true;
}
public static VertexColorState DetectVertexColor(Mesh mesh, Material[] materials)
{
if (mesh != null && mesh.colors != null && mesh.colors.Length == mesh.vertexCount)
{
// mesh が 頂点カラーを保持している
VertexColorState? state = default;
if (materials != null)
{
foreach (var m in materials)
{
var currentState = MaterialUseVertexColor(m)
? UniGLTF.MeshExportInfo.VertexColorState.ExistsAndIsUsed
: UniGLTF.MeshExportInfo.VertexColorState.ExistsButNotUsed
;
if (state.HasValue)
{
if (state.Value != currentState)
{
state = UniGLTF.MeshExportInfo.VertexColorState.ExistsAndMixed;
break;
}
}
else
{
state = currentState;
}
}
}
return state.GetValueOrDefault(VertexColorState.None);
}
else
{
return VertexColorState.None;
}
}
public int VertexCount;
/// <summary>
/// Position, UV, Normal
/// [Color]
/// [SkinningWeight]
/// </summary>
public int ExportVertexSize;
public int IndexCount;
// int 決め打ち
public int IndicesSize => IndexCount * 4;
public int ExportBlendShapeVertexSize;
public int TotalBlendShapeCount;
public int ExportBlendShapeCount;
public int ExportByteSize => ExportVertexSize * VertexCount + IndicesSize + ExportBlendShapeCount * ExportBlendShapeVertexSize * VertexCount;
public string Summary;
}
public struct MeshExportSettings
{
// MorphTarget に Sparse Accessor を使う
@ -163,9 +41,9 @@ namespace UniGLTF
var colorAccessorIndex = -1;
var vColorState = MeshExportInfo.DetectVertexColor(mesh, materials);
if (vColorState == MeshExportInfo.VertexColorState.ExistsAndIsUsed // VColor使っている
|| vColorState == MeshExportInfo.VertexColorState.ExistsAndMixed // VColorを使っているところと使っていないところが混在(とりあえずExportする)
var vColorState = MeshUtility.MeshExportInfo.DetectVertexColor(mesh, materials);
if (vColorState == MeshUtility.MeshExportInfo.VertexColorState.ExistsAndIsUsed // VColor使っている
|| vColorState == MeshUtility.MeshExportInfo.VertexColorState.ExistsAndMixed // VColorを使っているところと使っていないところが混在(とりあえずExportする)
)
{
// UniUnlit で Multiply 設定になっている
@ -373,7 +251,7 @@ namespace UniGLTF
}
public static IEnumerable<(Mesh, glTFMesh, Dictionary<int, int>)> ExportMeshes(glTF gltf, int bufferIndex,
List<MeshWithRenderer> unityMeshes, List<Material> unityMaterials,
List<MeshUtility.MeshWithRenderer> unityMeshes, List<Material> unityMaterials,
MeshExportSettings settings)
{
for (int i = 0; i < unityMeshes.Count; ++i)

View File

@ -253,7 +253,7 @@ namespace UniGLTF
#region Meshes
var unityMeshes = Nodes
.Select(x => new MeshWithRenderer
.Select(x => new MeshUtility.MeshWithRenderer
{
Mesh = x.GetSharedMesh(),
Renderer = x.GetComponent<Renderer>(),

View File

@ -1,11 +1,12 @@
using System.Collections.Generic;
using MeshUtility;
using UnityEngine;
namespace VRM
{
public static class VRMBlendShapeProxyValidator
{
public static IEnumerable<Validation> Validate(this VRMBlendShapeProxy p)
public static IEnumerable<Validation> Validate(this VRMBlendShapeProxy p, GameObject _)
{
if (p == null)
{

View File

@ -1,179 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
namespace VRM.M17N
{
/// <summary>
/// 多言語対応
/// </summary>
public enum Languages
{
ja,
en,
}
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public class LangMsgAttribute : System.Attribute
{
public Languages Language;
public string Message;
public LangMsgAttribute(Languages language, string msg)
{
Language = language;
Message = msg;
}
}
public enum VRMExporterWizardMessages
{
[LangMsg(Languages.ja, "ExportRootをセットしてください")]
[LangMsg(Languages.en, "Please set up a ExportRoot for model export")]
ROOT_EXISTS,
[LangMsg(Languages.ja, "ExportRootに親はオブジェクトは持てません")]
[LangMsg(Languages.en, "ExportRoot must be topmost parent")]
NO_PARENT,
[LangMsg(Languages.ja, "ExportRootに回転・拡大縮小は持てません。子階層で回転・拡大縮小してください")]
[LangMsg(Languages.en, "ExportRoot's rotation and scaling are not allowed to change. Please set up rotation and scaling in child node")]
ROOT_WITHOUT_ROTATION_AND_SCALING_CHANGED,
[LangMsg(Languages.ja, "シーンに出していない Prefab はエクスポートできません(細かい挙動が違い、想定外の動作をところがあるため)。シーンに展開してからエクスポートしてください")]
[LangMsg(Languages.en, "Prefab Asset cannot be exported. Prefab Asset has different behaviour with Scene GameObject. Please put the prefab into the scene")]
PREFAB_CANNOT_EXPORT,
[LangMsg(Languages.ja, "回転・拡大縮小を持つードが含まれています。正規化が必用です。Setting の PoseFreeze を有効にしてください")]
[LangMsg(Languages.en, " Normalization is required. There are nodes (child GameObject) where rotation and scaling are not default. Please enable PoseFreeze")]
ROTATION_OR_SCALEING_INCLUDED_IN_NODE,
[LangMsg(Languages.ja, "正規化済みです。Setting の PoseFreeze は不要です")]
[LangMsg(Languages.en, "Normalization has been done. PoseFreeze is not required")]
IS_POSE_FREEZE_DONE,
[LangMsg(Languages.ja, "ExportRootに Animator がありません")]
[LangMsg(Languages.en, "No Animator in ExportRoot")]
NO_ANIMATOR,
[LangMsg(Languages.ja, "Z+ 向きにしてください")]
[LangMsg(Languages.en, "The model needs to face the positive Z-axis")]
FACE_Z_POSITIVE_DIRECTION,
[LangMsg(Languages.ja, "ExportRootの Animator に Avatar がありません")]
[LangMsg(Languages.en, "No Avatar in ExportRoot's Animator")]
NO_AVATAR_IN_ANIMATOR,
[LangMsg(Languages.ja, "ExportRootの Animator.Avatar が不正です")]
[LangMsg(Languages.en, "Animator.avatar in ExportRoot is not valid")]
AVATAR_IS_NOT_VALID,
[LangMsg(Languages.ja, "ExportRootの Animator.Avatar がヒューマイドではありません。FBX importer の Rig で設定してください")]
[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.en, "Jaw bone is included. It may not what you intended. Please check the humanoid avatar setting screen")]
JAW_BONE_IS_INCLUDED,
[LangMsg(Languages.ja, "ヒエラルキーの中に同じ名前のGameObjectが含まれている。 エクスポートした場合に自動でリネームする")]
[LangMsg(Languages.en, "There are bones with the same name in the hierarchy. They will be automatically renamed after export")]
DUPLICATE_BONE_NAME_EXISTS,
[LangMsg(Languages.ja, "VRMBlendShapeProxyが必要です。先にVRMフォーマットに変換してください")]
[LangMsg(Languages.en, "VRMBlendShapeProxy is required. Please convert to VRM format first")]
NEEDS_VRM_BLENDSHAPE_PROXY,
[LangMsg(Languages.en, "This model contains vertex color")]
[LangMsg(Languages.ja, "ヒエラルキーに含まれる mesh に頂点カラーが含まれている")]
VERTEX_COLOR_IS_INCLUDED,
[LangMsg(Languages.ja, "ヒエラルキーに active なメッシュが含まれていない")]
[LangMsg(Languages.en, "No active mesh")]
NO_ACTIVE_MESH,
[LangMsg(Languages.ja, "Standard, Unlit, MToon 以外のマテリアルは、Standard になります")]
[LangMsg(Languages.en, "It will export as `Standard` fallback")]
UNKNOWN_SHADER,
[LangMsg(Languages.ja, "名前が長すぎる。リネームしてください: ")]
[LangMsg(Languages.en, "FileName is too long: ")]
FILENAME_TOO_LONG,
}
static class MsgCache<T> where T : Enum
{
static Dictionary<Languages, Dictionary<T, string>> s_cache = new Dictionary<Languages, Dictionary<T, string>>();
static LangMsgAttribute GetAttribute(T value, Languages language)
{
var t = typeof(T);
var memberInfos = t.GetMember(value.ToString());
var enumValueMemberInfo = memberInfos.FirstOrDefault(m => m.DeclaringType == t);
var attr = enumValueMemberInfo.GetCustomAttributes(typeof(LangMsgAttribute), false).Select(x => (LangMsgAttribute)x).ToArray();
if (attr == null || attr.Length == 0)
{
return null;
}
var match = attr.FirstOrDefault(x => x.Language == language);
if (match != null)
{
return match;
}
return attr.First();
}
public static string Get(Languages language, T key)
{
if (!s_cache.TryGetValue(language, out Dictionary<T, string> map))
{
map = new Dictionary<T, string>();
var t = typeof(T);
foreach (T value in Enum.GetValues(t))
{
var match = GetAttribute(value, language);
// Attribute。無かったら enum の ToString
map.Add(value, match != null ? match.Message : key.ToString());
}
s_cache.Add(language, map);
}
return map[key];
}
}
public static class Getter
{
const string LANG_KEY = "VRM_LANG";
static Languages? m_lang;
public static Languages Lang
{
get
{
if (!m_lang.HasValue)
{
m_lang = EnumUtil.TryParseOrDefault<Languages>(EditorPrefs.GetString(LANG_KEY, default(Languages).ToString()));
}
return m_lang.Value;
}
}
public static string Msg<T>(T key) where T : Enum
{
return M17N.MsgCache<T>.Get(Lang, key);
}
public static void OnGuiSelectLang()
{
var lang = (M17N.Languages)EditorGUILayout.EnumPopup("lang", Lang);
if (lang != Lang)
{
m_lang = lang;
EditorPrefs.SetString(LANG_KEY, M17N.Getter.Lang.ToString());
}
}
}
}

View File

@ -1,4 +1,5 @@
using UnityEditor;
using MeshUtility;
using UnityEditor;
using UnityEngine;
namespace VRM

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using MeshUtility;
using UnityEngine;
namespace VRM
@ -32,7 +33,7 @@ namespace VRM
return true;
}
public static IEnumerable<Validation> Validate(this VRMFirstPerson self)
public static IEnumerable<Validation> Validate(this VRMFirstPerson self, GameObject _)
{
Hierarchy = self.GetComponentsInChildren<Transform>(true);

View File

@ -16,7 +16,7 @@ namespace VRM
/// </summary>
/// <param name="path">出力先</param>
/// <param name="settings">エクスポート設定</param>
public static void Export(string path, GameObject exportRoot, VRMMetaObject meta, VRMExportSettings settings, IReadOnlyList<MeshExportInfo> info)
public static void Export(string path, GameObject exportRoot, VRMMetaObject meta, VRMExportSettings settings, IReadOnlyList<MeshUtility.MeshExportInfo> info)
{
List<GameObject> destroy = new List<GameObject>();
try
@ -137,7 +137,7 @@ namespace VRM
/// <param name="settings"></param>
/// <param name="destroy">作業が終わったらDestoryするべき一時オブジェクト</param>
static void Export(string path, GameObject exportRoot, VRMMetaObject meta,
VRMExportSettings settings, IReadOnlyList<UniGLTF.MeshExportInfo> info,
VRMExportSettings settings, IReadOnlyList<MeshUtility.MeshExportInfo> info,
List<GameObject> destroy)
{
var target = exportRoot;

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MeshUtility;
using UnityEngine;
namespace VRM
@ -55,14 +56,14 @@ namespace VRM
return false;
}
public List<UniGLTF.MeshExportInfo> Meshes = new List<UniGLTF.MeshExportInfo>();
public List<MeshUtility.MeshExportInfo> Meshes = new List<MeshUtility.MeshExportInfo>();
public int ExpectedExportByteSize => Meshes.Where(x => x.IsRendererActive).Sum(x => x.ExportByteSize);
List<Validation> m_validations = new List<Validation>();
public IEnumerable<Validation> Validations => m_validations;
public static void CalcMeshSize(ref UniGLTF.MeshExportInfo info,
public static void CalcMeshSize(ref MeshUtility.MeshExportInfo info,
string relativePath, VRMExportSettings settings, IReadOnlyList<BlendShapeClip> clips)
{
var sb = new StringBuilder();
@ -132,11 +133,11 @@ namespace VRM
sb.Append($") x {info.Mesh.vertexCount}");
switch (info.VertexColor)
{
case UniGLTF.MeshExportInfo.VertexColorState.ExistsAndIsUsed:
case UniGLTF.MeshExportInfo.VertexColorState.ExistsAndMixed: // エクスポートする
case MeshUtility.MeshExportInfo.VertexColorState.ExistsAndIsUsed:
case MeshUtility.MeshExportInfo.VertexColorState.ExistsAndMixed: // エクスポートする
sb.Insert(0, "[use vcolor]");
break;
case UniGLTF.MeshExportInfo.VertexColorState.ExistsButNotUsed:
case MeshUtility.MeshExportInfo.VertexColorState.ExistsButNotUsed:
sb.Insert(0, "[remove vcolor]");
break;
}
@ -150,7 +151,7 @@ namespace VRM
info.Summary = sb.ToString();
}
bool TryGetMeshInfo(GameObject root, Renderer renderer, IReadOnlyList<BlendShapeClip> clips, VRMExportSettings settings, out UniGLTF.MeshExportInfo info)
bool TryGetMeshInfo(GameObject root, Renderer renderer, IReadOnlyList<BlendShapeClip> clips, VRMExportSettings settings, out MeshUtility.MeshExportInfo info)
{
info = default;
if (root == null)
@ -186,7 +187,7 @@ namespace VRM
return false;
}
info.VertexColor = UniGLTF.MeshExportInfo.DetectVertexColor(info.Mesh, info.Renderer.sharedMaterials);
info.VertexColor = MeshUtility.MeshExportInfo.DetectVertexColor(info.Mesh, info.Renderer.sharedMaterials);
var relativePath = UniGLTF.UnityExtensions.RelativePathFrom(renderer.transform, root.transform);
CalcMeshSize(ref info, relativePath, settings, clips);
@ -216,7 +217,7 @@ namespace VRM
foreach (var renderer in ExportRoot.GetComponentsInChildren<Renderer>(true))
{
if (TryGetMeshInfo(ExportRoot, renderer, clips, settings, out UniGLTF.MeshExportInfo info))
if (TryGetMeshInfo(ExportRoot, renderer, clips, settings, out MeshUtility.MeshExportInfo info))
{
Meshes.Add(info);
}

View File

@ -2,7 +2,7 @@
using System;
using UnityEditor;
using UnityEngine;
using VRM.M17N;
using MeshUtility.M17N;
namespace VRM
{
@ -32,7 +32,7 @@ namespace VRM
);
}
void DrawElement(int i, UniGLTF.MeshExportInfo info)
void DrawElement(int i, MeshUtility.MeshExportInfo info)
{
var r = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(EditorGUIUtility.singleLineHeight * 3 + 20));
var col0 = 32;

View File

@ -2,7 +2,7 @@
using System;
using UnityEditor;
using UnityEngine;
using VRM.M17N;
using MeshUtility.M17N;
namespace VRM
{
@ -87,7 +87,7 @@ namespace VRM
static string Msg(Options key)
{
return M17N.Getter.Msg(key);
return MeshUtility.M17N.Getter.Msg(key);
}
enum Options

View File

@ -1,326 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using MeshUtility;
using MeshUtility.M17N;
using UnityEngine;
namespace VRM
{
public class VRMExporterValidator
public static class VRMExporterValidator
{
// Allows you to enable and disable the wizard create button, so that the user can not click it.
public bool IsValid
public enum VRMExporterWizardMessages
{
get
{
var hasError = m_validations.Any(x => !x.CanExport);
return !hasError && !MetaHasError;
}
[LangMsg(Languages.ja, "VRMBlendShapeProxyが必要です。先にVRMフォーマットに変換してください")]
[LangMsg(Languages.en, "VRMBlendShapeProxy is required. Please convert to VRM format first")]
NEEDS_VRM_BLENDSHAPE_PROXY,
}
bool MetaHasError = false;
public static bool ReduceBlendshape;
List<Validation> m_validations = new List<Validation>();
public IEnumerable<Validation> Validations => m_validations;
/// <summary>
/// ボーン名の重複を確認
/// </summary>
/// <returns></returns>
bool DuplicateBoneNameExists(GameObject ExportRoot)
{
if (ExportRoot == null)
{
return false;
}
var bones = ExportRoot.transform.GetComponentsInChildren<Transform>();
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;
}
public static bool HasRotationOrScale(GameObject root)
{
foreach (var t in root.GetComponentsInChildren<Transform>())
{
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 == null || r == null)
{
return Vector3.zero;
}
var lr = (r.position - l.position).normalized;
return Vector3.Cross(lr, Vector3.up);
}
static string Msg(VRMExporterWizardMessages key)
{
return M17N.Getter.Msg(key);
}
/// <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;
}
/// <summary>
/// エクスポート可能か検証する。
/// </summary>
/// <returns></returns>
public void Validate(GameObject ExportRoot, VRMExportSettings m_settings, VRMMetaObject meta)
{
m_validations.Clear();
if (ExportRoot == null)
{
return;
}
var proxy = ExportRoot.GetComponent<VRMBlendShapeProxy>();
m_validations.AddRange(_Validate(ExportRoot, m_settings));
m_validations.AddRange(VRMSpringBoneValidator.Validate(ExportRoot));
var firstPerson = ExportRoot.GetComponent<VRMFirstPerson>();
if (firstPerson != null)
{
m_validations.AddRange(firstPerson.Validate());
}
if (proxy != null)
{
m_validations.AddRange(proxy.Validate());
}
MetaHasError = meta.Validate().Any();
}
IEnumerable<Validation> _Validate(GameObject ExportRoot, VRMExportSettings m_settings)
public static IEnumerable<Validation> Validate(GameObject ExportRoot)
{
if (ExportRoot == null)
{
yield break;
}
if (DuplicateBoneNameExists(ExportRoot))
if (ReduceBlendshape && ExportRoot.GetComponent<VRMBlendShapeProxy>() == null)
{
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);
yield return Validation.Error(VRMExporterWizardMessages.NEEDS_VRM_BLENDSHAPE_PROXY.Msg());
}
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);
if (MeshUtility.Validators.NameValidator.IsFileNameLengthTooLong(thumbnailName))
{
yield return Validation.Error(MeshUtility.Validators.NameValidator.ValidationMessages.FILENAME_TOO_LONG.Msg() + thumbnailName);
}
}
}
}

View File

@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
@ -6,9 +7,6 @@ using UnityEngine;
namespace VRM
{
/// <summary>
/// エクスポートダイアログ
/// </summary>
public class VRMExporterWizard : EditorWindow
{
const string CONVERT_HUMANOID_KEY = VRMVersion.MENU + "/Export humanoid";
@ -16,7 +14,9 @@ namespace VRM
[MenuItem(CONVERT_HUMANOID_KEY, false, 1)]
private static void ExportFromMenu()
{
VRMExporterWizard.CreateWizard();
var window = (VRMExporterWizard)GetWindow(typeof(VRMExporterWizard));
window.titleContent = new GUIContent("VRM Exporter");
window.Show();
}
enum Tabs
@ -27,16 +27,7 @@ namespace VRM
}
Tabs _tab;
GUIStyle TabButtonStyle => "LargeButton";
// GUI.ToolbarButtonSize.FitToContentsも設定できる
GUI.ToolbarButtonSize TabButtonSize => GUI.ToolbarButtonSize.Fixed;
const string EXTENSION = ".vrm";
private static string m_lastExportDir;
GameObject ExportRoot;
MeshUtility.ExporterDialogState m_state;
VRMExportSettings m_settings;
VRMExportMeshes m_meshes;
@ -51,7 +42,6 @@ namespace VRM
{
return;
}
m_requireValidation = true;
if (m_metaEditor != null)
{
UnityEditor.Editor.DestroyImmediate(m_metaEditor);
@ -61,71 +51,17 @@ namespace VRM
}
}
void UpdateRoot(GameObject root)
{
if (root == ExportRoot)
{
return;
}
m_requireValidation = true;
ExportRoot = root;
UnityEditor.Editor.DestroyImmediate(m_metaEditor);
m_metaEditor = null;
if (ExportRoot == null)
{
Meta = null;
}
else
{
// do validation
Validate();
// default setting
m_settings.PoseFreeze =
VRMExporterValidator.HasRotationOrScale(ExportRoot)
|| m_meshes.Meshes.Any(x => x.ExportBlendShapeCount > 0 && !x.HasSkinning)
;
var meta = ExportRoot.GetComponent<VRMMeta>();
if (meta != null)
{
Meta = meta.Meta;
}
else
{
Meta = null;
}
}
}
void Validate()
{
if (!m_requireValidation)
{
return;
}
m_validator.Validate(ExportRoot, m_settings, Meta != null ? Meta : m_tmpMeta);
m_requireValidation = false;
m_meshes.SetRoot(ExportRoot, m_settings);
}
VRMMetaObject m_tmpMeta;
Editor m_metaEditor;
Editor m_settingsInspector;
Editor m_meshesInspector;
VRMExporterValidator m_validator = new VRMExporterValidator();
bool m_requireValidation = true;
private Vector2 m_ScrollPosition;
void OnEnable()
{
// Debug.Log("OnEnable");
Undo.willFlushUndoRecord += OnWizardUpdate;
Selection.selectionChanged += OnWizardUpdate;
Undo.willFlushUndoRecord += Repaint;
Selection.selectionChanged += Repaint;
m_tmpMeta = ScriptableObject.CreateInstance<VRMMetaObject>();
@ -134,15 +70,46 @@ namespace VRM
m_meshes = ScriptableObject.CreateInstance<VRMExportMeshes>();
m_meshesInspector = Editor.CreateEditor(m_meshes);
m_state = new MeshUtility.ExporterDialogState();
m_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 =
MeshUtility.Validators.HumanoidValidator.HasRotationOrScale(root)
|| m_meshes.Meshes.Any(x => x.ExportBlendShapeCount > 0 && !x.HasSkinning)
;
}
Repaint();
};
m_state.ExportRoot = Selection.activeObject as GameObject;
}
void OnDisable()
{
ExportRoot = null;
m_state.Dispose();
// Debug.Log("OnDisable");
Selection.selectionChanged -= OnWizardUpdate;
Undo.willFlushUndoRecord -= OnWizardUpdate;
Selection.selectionChanged -= Repaint;
Undo.willFlushUndoRecord -= Repaint;
// m_metaEditor
UnityEditor.Editor.DestroyImmediate(m_metaEditor);
@ -165,20 +132,6 @@ namespace VRM
m_meshes = null;
}
private void InvokeWizardUpdate()
{
const BindingFlags kInstanceInvokeFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
MethodInfo method = GetType().GetMethod("OnWizardUpdate", kInstanceInvokeFlags);
if (method != null)
method.Invoke(this, null);
}
private class Styles
{
public static string errorText = "Wizard Error";
public static string box = "Wizard Box";
}
public delegate Vector2 BeginVerticalScrollViewFunc(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options);
static BeginVerticalScrollViewFunc s_func;
static BeginVerticalScrollViewFunc BeginVerticalScrollView
@ -194,67 +147,68 @@ namespace VRM
return s_func;
}
}
private Vector2 m_ScrollPosition;
IEnumerable<MeshUtility.Validator> ValidatorFactory()
{
MeshUtility.Validators.HumanoidValidator.MeshInformations = m_meshes.Meshes;
MeshUtility.Validators.HumanoidValidator.EnableFreeze = m_settings.PoseFreeze;
VRMExporterValidator.ReduceBlendshape = m_settings.ReduceBlendshape;
yield return MeshUtility.Validators.HierarchyValidator.Validate;
if (!m_state.ExportRoot)
{
yield break;
}
yield return MeshUtility.Validators.HumanoidValidator.Validate;
yield return VRMExporterValidator.Validate;
yield return VRMSpringBoneValidator.Validate;
var firstPerson = m_state.ExportRoot.GetComponent<VRMFirstPerson>();
if (firstPerson != null)
{
yield return firstPerson.Validate;
}
var proxy = m_state.ExportRoot.GetComponent<VRMBlendShapeProxy>();
if (proxy != null)
{
yield return proxy.Validate;
}
var meta = Meta ? Meta : m_tmpMeta;
yield return meta.Validate;
}
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();
// m_settings, m_meshes.Meshes
m_meshes.SetRoot(m_state.ExportRoot, m_settings);
m_state.Validate(ValidatorFactory());
}
//
// Humanoid として適正か? ここで失敗する場合は Export UI を表示しない
//
if (!m_validator.RootAndHumanoidCheck(ExportRoot, m_settings, m_meshes.Meshes))
EditorGUIUtility.labelWidth = 150;
// lang
MeshUtility.M17N.Getter.OnGuiSelectLang();
EditorGUILayout.LabelField("ExportRoot");
{
return;
m_state.ExportRoot = (GameObject)EditorGUILayout.ObjectField(m_state.ExportRoot, typeof(GameObject), true);
}
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 = ScrollArea();
bool modified = DrawWizardGUI();
EditorGUILayout.EndScrollView();
// Create and Other Buttons
@ -266,11 +220,11 @@ namespace VRM
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUI.enabled = m_validator.IsValid;
GUI.enabled = m_state.Validations.All(x => x.CanExport);
if (GUILayout.Button("Export", GUILayout.MinWidth(100)))
{
OnWizardCreate();
OnExportClicked(m_state.ExportRoot, Meta != null ? Meta : m_tmpMeta, m_settings, m_meshes);
Close();
GUIUtility.ExitGUI();
}
@ -285,11 +239,42 @@ namespace VRM
if (modified)
{
m_requireValidation = true;
Repaint();
m_state.Invalidate();
}
}
bool ScrollArea()
{
//
// Validation
//
foreach (var v in m_state.Validations)
{
v.DrawGUI();
if (v.ErrorLevel == MeshUtility.ErrorLevels.Critical)
{
// Export UI を表示しない
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 MeshUtility.MeshExportInfo.VertexColorState.ExistsAndMixed:
MeshUtility.Validation.Warning($"{meshInfo.Renderer}: Both vcolor.multiply and not multiply unlit materials exist").DrawGUI();
break;
}
}
return DrawWizardGUI();
}
bool DrawWizardGUI()
{
if (m_tmpMeta == null)
@ -328,36 +313,9 @@ namespace VRM
return true;
}
// Creates a wizard.
public static VRMExporterWizard DisplayWizard()
{
VRMExporterWizard wizard = CreateInstance<VRMExporterWizard>();
wizard.titleContent = new GUIContent("VRM Exporter");
if (wizard != null)
{
wizard.InvokeWizardUpdate();
wizard.ShowUtility();
}
return wizard;
}
public static void CreateWizard()
{
var wiz = VRMExporterWizard.DisplayWizard();
var go = Selection.activeObject as GameObject;
// update checkbox
wiz.UpdateRoot(go);
if (go != null)
{
wiz.m_settings.PoseFreeze = VRMExporterValidator.HasRotationOrScale(go);
}
wiz.OnWizardUpdate();
}
void OnWizardCreate()
const string EXTENSION = ".vrm";
private static string m_lastExportDir;
static void OnExportClicked(GameObject root, VRMMetaObject meta, VRMExportSettings settings, VRMExportMeshes meshes)
{
string directory;
if (string.IsNullOrEmpty(m_lastExportDir))
@ -369,7 +327,7 @@ namespace VRM
var path = EditorUtility.SaveFilePanel(
"Save vrm",
directory,
ExportRoot.name + EXTENSION,
root.name + EXTENSION,
EXTENSION.Substring(1));
if (string.IsNullOrEmpty(path))
{
@ -378,14 +336,7 @@ namespace VRM
m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/");
// export
VRMEditorExporter.Export(path, ExportRoot, Meta != null ? Meta : m_tmpMeta, m_settings, m_meshes.Meshes);
}
void OnWizardUpdate()
{
UpdateRoot(ExportRoot);
m_requireValidation = true;
Repaint();
VRMEditorExporter.Export(path, root, meta, settings, meshes.Meshes);
}
}
}

View File

@ -1,80 +0,0 @@
using VRM.M17N;
namespace VRM
{
public enum VRMExporterWizardMessages
{
[LangMsg(Languages.ja, "ExportRootをセットしてください")]
[LangMsg(Languages.en, "Please set up a ExportRoot for model export")]
ROOT_EXISTS,
[LangMsg(Languages.ja, "ExportRootに親はオブジェクトは持てません")]
[LangMsg(Languages.en, "ExportRoot must be topmost parent")]
NO_PARENT,
[LangMsg(Languages.ja, "ExportRootに回転・拡大縮小は持てません。子階層で回転・拡大縮小してください")]
[LangMsg(Languages.en, "ExportRoot's rotation and scaling are not allowed to change. Please set up rotation and scaling in child node")]
ROOT_WITHOUT_ROTATION_AND_SCALING_CHANGED,
[LangMsg(Languages.ja, "シーンに出していない Prefab はエクスポートできません(細かい挙動が違い、想定外の動作をところがあるため)。シーンに展開してからエクスポートしてください")]
[LangMsg(Languages.en, "Prefab Asset cannot be exported. Prefab Asset has different behaviour with Scene GameObject. Please put the prefab into the scene")]
PREFAB_CANNOT_EXPORT,
[LangMsg(Languages.ja, "回転・拡大縮小もしくはWeightの無いBlendShapeが含まれています。正規化が必用です。Setting の PoseFreeze を有効にしてください")]
[LangMsg(Languages.en, " Normalization is required. There are nodes (child GameObject) where rotation and scaling or blendshape without bone weight are not default. Please enable PoseFreeze")]
ROTATION_OR_SCALEING_INCLUDED_IN_NODE,
[LangMsg(Languages.ja, "正規化済みです。Setting の PoseFreeze は不要です")]
[LangMsg(Languages.en, "Normalization has been done. PoseFreeze is not required")]
IS_POSE_FREEZE_DONE,
[LangMsg(Languages.ja, "ExportRootに Animator がありません")]
[LangMsg(Languages.en, "No Animator in ExportRoot")]
NO_ANIMATOR,
[LangMsg(Languages.ja, "Z+ 向きにしてください")]
[LangMsg(Languages.en, "The model needs to face the positive Z-axis")]
FACE_Z_POSITIVE_DIRECTION,
[LangMsg(Languages.ja, "ExportRootの Animator に Avatar がありません")]
[LangMsg(Languages.en, "No Avatar in ExportRoot's Animator")]
NO_AVATAR_IN_ANIMATOR,
[LangMsg(Languages.ja, "ExportRootの Animator.Avatar が不正です")]
[LangMsg(Languages.en, "Animator.avatar in ExportRoot is not valid")]
AVATAR_IS_NOT_VALID,
[LangMsg(Languages.ja, "ExportRootの Animator.Avatar がヒューマイドではありません。FBX importer の Rig で設定してください")]
[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.en, "Jaw bone is included. It may not what you intended. Please check the humanoid avatar setting screen")]
JAW_BONE_IS_INCLUDED,
[LangMsg(Languages.ja, "ヒエラルキーの中に同じ名前のGameObjectが含まれている。 エクスポートした場合に自動でリネームする")]
[LangMsg(Languages.en, "There are bones with the same name in the hierarchy. They will be automatically renamed after export")]
DUPLICATE_BONE_NAME_EXISTS,
[LangMsg(Languages.ja, "VRMBlendShapeProxyが必要です。先にVRMフォーマットに変換してください")]
[LangMsg(Languages.en, "VRMBlendShapeProxy is required. Please convert to VRM format first")]
NEEDS_VRM_BLENDSHAPE_PROXY,
[LangMsg(Languages.en, "This model contains vertex color")]
[LangMsg(Languages.ja, "ヒエラルキーに含まれる mesh に頂点カラーが含まれている")]
VERTEX_COLOR_IS_INCLUDED,
[LangMsg(Languages.ja, "ヒエラルキーに active なメッシュが含まれていない")]
[LangMsg(Languages.en, "No active mesh")]
NO_ACTIVE_MESH,
[LangMsg(Languages.ja, "Standard, Unlit, MToon 以外のマテリアルは、Standard になります")]
[LangMsg(Languages.en, "It will export as `Standard` fallback")]
UNKNOWN_SHADER,
[LangMsg(Languages.ja, "名前が長すぎる。リネームしてください: ")]
[LangMsg(Languages.en, "FileName is too long: ")]
FILENAME_TOO_LONG,
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UniGLTF;
@ -36,7 +37,17 @@ namespace VRM
throw new Exception();
}
var context = new VRMImporterContext();
context.ParseGlb(File.ReadAllBytes(path.FullPath));
try
{
context.ParseGlb(File.ReadAllBytes(path.FullPath));
}
catch (KeyNotFoundException)
{
// invalid VRM-0.X.
// maybe VRM-1.0.do nothing
return;
}
var prefabPath = path.Parent.Child(path.FileNameWithoutExtension + ".prefab");

View File

@ -1,6 +1,6 @@
using UnityEditor;
using UnityEngine;
using VRM.M17N;
using MeshUtility.M17N;
namespace VRM
{
@ -54,12 +54,12 @@ namespace VRM
static string RequiredMessage(string name)
{
switch (M17N.Getter.Lang)
switch (MeshUtility.M17N.Getter.Lang)
{
case M17N.Languages.ja:
case MeshUtility.M17N.Languages.ja:
return $"必須項目。{name} を入力してください";
case M17N.Languages.en:
case MeshUtility.M17N.Languages.en:
return $"{name} is required";
default:
@ -153,7 +153,7 @@ namespace VRM
static string Msg(MessageKeys key)
{
return M17N.Getter.Msg(key);
return MeshUtility.M17N.Getter.Msg(key);
}
bool m_foldoutInfo = true;

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using MeshUtility;
using UnityEngine;
namespace VRM

View File

@ -14,7 +14,7 @@ namespace VRM
[TestCase("AliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAliciaAlicia", true)]
public void DetectFileNameLength(string fileName, bool isIllegal)
{
var result = VRMExporterValidator.IsFileNameLengthTooLong(fileName);
var result = MeshUtility.Validators.NameValidator.IsFileNameLengthTooLong(fileName);
Assert.AreEqual(result, isIllegal);
}

View File

@ -4,7 +4,8 @@
"VRM",
"UniJSON",
"UniVRM.Editor",
"MeshUtility"
"MeshUtility",
"MeshUtility.Editor"
],
"optionalUnityReferences": [
"TestAssemblies"

View File

@ -5,6 +5,7 @@
"UniJSON",
"UniHumanoid",
"MeshUtility",
"MeshUtility.Editor",
"UniUnlit"
],
"optionalUnityReferences": [],

View File

@ -10,23 +10,6 @@ namespace VRM
{
public class VRMMaterialExporter : MaterialExporter
{
public static bool UseUnlit(string shaderName)
{
switch (shaderName)
{
case "Unlit/Color":
case "Unlit/Texture":
case "Unlit/Transparent":
case "Unlit/Transparent Cutout":
case "UniGLTF/UniUnlit":
case "VRM/UnlitTexture":
case "VRM/UnlitTransparent":
case "VRM/UnlitCutout":
return true;
}
return false;
}
protected override glTFMaterial CreateMaterial(Material m)
{
switch (m.shader.name)
@ -118,11 +101,6 @@ namespace VRM
}
#region CreateFromMaterial
public static readonly string[] VRMExtensionShaders = new string[]
{
"VRM/UnlitTransparentZWrite",
"VRM/MToon"
};
static readonly string[] TAGS = new string[]{
"RenderType",
@ -138,7 +116,7 @@ namespace VRM
renderQueue = m.renderQueue,
};
if (!VRMExtensionShaders.Contains(m.shader.name))
if (!PreShaderPropExporter.VRMExtensionShaders.Contains(m.shader.name))
{
material.shader = glTF_VRM_Material.VRM_USE_GLTFSHADER;
return material;

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using MeshUtility;
using UnityEngine;
@ -65,7 +66,7 @@ namespace VRM
}
*/
public IEnumerable<Validation> Validate()
public IEnumerable<Validation> Validate(GameObject _)
{
if (string.IsNullOrEmpty(Title))
{

View File

@ -1,55 +0,0 @@
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VRM
{
public struct Validation
{
/// <summary>
/// エクスポート可能か否か。
/// true のメッセージは警告
/// false のメッセージはエラー
/// </summary>
public readonly bool CanExport;
public readonly String Message;
Validation(bool canExport, string message)
{
CanExport = canExport;
Message = message;
}
#if UNITY_EDITOR
public void DrawGUI()
{
if (string.IsNullOrEmpty(Message))
{
return;
}
if (CanExport)
{
// warning
EditorGUILayout.HelpBox(Message, MessageType.Warning);
}
else
{
// error
EditorGUILayout.HelpBox(Message, MessageType.Error);
}
}
#endif
public static Validation Error(string msg)
{
return new Validation(false, msg);
}
public static Validation Warning(string msg)
{
return new Validation(true, msg);
}
}
}

View File

@ -2,7 +2,25 @@
VRM model's supported shaders in Unity.
## Import VRMShaders (Unity 2019.3.4f1~)
Shader と関連するユーティリティを切り離したパッケージ。
## 含まれるシェーダー
### UniUnlit
* Gltfの Unlit に適合するようにした。Unlit シェーダー
### MToon
* https://github.com/Santarh/MToon
## UniGLTF.ShaderPropExporter.PreShaderPropExporter
Unityでは、ランタイムにMaterialのPropertyを列挙することができない。
Set/Get はできる。
事前に一覧を作成するユーティリティ。
## UPM usage (Unity 2019.3.4f1~)
`Window` -> `Package Manager` -> `Add package from git URL` and paste `https://github.com/vrm-c/UniVRM.git?path=/Assets/VRMShaders`.

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
@ -28,6 +27,29 @@ namespace UniGLTF.ShaderPropExporter
public static partial class PreShaderPropExporter
{
public static bool UseUnlit(string shaderName)
{
switch (shaderName)
{
case "Unlit/Color":
case "Unlit/Texture":
case "Unlit/Transparent":
case "Unlit/Transparent Cutout":
case "UniGLTF/UniUnlit":
case "VRM/UnlitTexture":
case "VRM/UnlitTransparent":
case "VRM/UnlitCutout":
return true;
}
return false;
}
public static readonly string[] VRMExtensionShaders = new string[]
{
"VRM/UnlitTransparentZWrite",
"VRM/MToon"
};
static Dictionary<string, ShaderProps> m_shaderPropMap;
public static ShaderProps GetPropsForSupportedShader(string shaderName)