using UnityEditor; using UnityEngine; using System.IO; using UniGLTF; using UniGLTF.M17N; namespace VRM { [CustomEditor(typeof(VRMMetaObject))] public class VRMMetaObjectEditor : Editor { class ValidateProperty { public SerializedProperty m_prop; public delegate (string, MessageType) Validator(SerializedProperty prop); Validator m_validator; public ValidateProperty(SerializedProperty prop, Validator validator) { m_prop = prop; m_validator = validator; } public void OnGUI() { // var old = m_prop.stringValue; EditorGUILayout.PropertyField(m_prop); var (msg, msgType) = m_validator(m_prop); if (!string.IsNullOrEmpty(msg)) { EditorGUILayout.HelpBox(msg, msgType); } // return old != m_prop.stringValue; } } VRMMetaObject m_target; SerializedProperty m_Script; SerializedProperty m_exporterVersion; SerializedProperty m_thumbnail; ValidateProperty m_title; ValidateProperty m_version; ValidateProperty m_author; ValidateProperty m_contact; ValidateProperty m_reference; SerializedProperty m_AllowedUser; SerializedProperty m_ViolentUssage; SerializedProperty m_SexualUssage; SerializedProperty m_CommercialUssage; SerializedProperty m_OtherPermissionUrl; SerializedProperty m_LicenseType; SerializedProperty m_OtherLicenseUrl; static string RequiredMessage(string name) { switch (LanguageGetter.Lang) { case Languages.ja: return $"必須項目。{name} を入力してください"; case Languages.en: return $"{name} is required"; default: throw new System.NotImplementedException(); } } private void OnEnable() { if (target == null) { return; } m_target = (VRMMetaObject)target; m_Script = serializedObject.FindProperty("m_Script"); m_exporterVersion = serializedObject.FindProperty(nameof(m_target.ExporterVersion)); m_thumbnail = serializedObject.FindProperty(nameof(m_target.Thumbnail)); m_title = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Title)), prop => { if (string.IsNullOrEmpty(prop.stringValue)) { return (RequiredMessage(prop.name), MessageType.Error); } return ("", MessageType.None); }); m_version = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Version)), prop => { if (string.IsNullOrEmpty(prop.stringValue)) { return (RequiredMessage(prop.name), MessageType.Error); } return ("", MessageType.None); }); m_author = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Author)), prop => { if (string.IsNullOrEmpty(prop.stringValue)) { return (RequiredMessage(prop.name), MessageType.Error); } return ("", MessageType.None); }); m_contact = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.ContactInformation)), prop => { return ("", MessageType.None); }); m_reference = new ValidateProperty(serializedObject.FindProperty(nameof(m_target.Reference)), prop => { return ("", MessageType.None); }); m_AllowedUser = serializedObject.FindProperty(nameof(m_target.AllowedUser)); m_ViolentUssage = serializedObject.FindProperty(nameof(m_target.ViolentUssage)); m_SexualUssage = serializedObject.FindProperty(nameof(m_target.SexualUssage)); m_CommercialUssage = serializedObject.FindProperty(nameof(m_target.CommercialUssage)); m_OtherPermissionUrl = serializedObject.FindProperty(nameof(m_target.OtherPermissionUrl)); m_LicenseType = serializedObject.FindProperty(nameof(m_target.LicenseType)); m_OtherLicenseUrl = serializedObject.FindProperty(nameof(m_target.OtherLicenseUrl)); } enum MessageKeys { [LangMsg(Languages.ja, "アバターの人格に関する許諾範囲")] [LangMsg(Languages.en, "Personation / Characterization Permission")] PERSONATION, [LangMsg(Languages.ja, "アバターに人格を与えることの許諾範囲")] [LangMsg(Languages.en, "A person who can perform with this avatar")] ALLOWED_USER, [LangMsg(Languages.ja, "このアバターを用いて暴力表現を演じることの許可")] [LangMsg(Languages.en, "Violent acts using this avatar")] VIOLENT_USAGE, [LangMsg(Languages.ja, "このアバターを用いて性的表現を演じることの許可")] [LangMsg(Languages.en, "Sexuality acts using this avatar")] SEXUAL_USAGE, [LangMsg(Languages.ja, "商用利用の許可")] [LangMsg(Languages.en, "For commercial use")] COMMERCIAL_USAGE, [LangMsg(Languages.ja, "再配布・改変に関する許諾範囲")] [LangMsg(Languages.en, "Redistribution / Modifications License")] REDISTRIBUTION_MODIFICATIONS, [LangMsg(Languages.ja, "Camera.main で画像を Render します")] [LangMsg(Languages.en, "Create a thumbnail image by Camera.main")] SCREENSHOT, [LangMsg(Languages.ja, "スクリーンショット")] [LangMsg(Languages.en, "Screenshot")] SCREENSHOT_BUTTON, // [LangMsg(Languages.ja, "")] // [LangMsg(Languages.en, "")] } static string Msg(MessageKeys key) { return LanguageGetter.Msg(key); } bool m_foldoutInfo = true; bool m_foldoutPermission = true; bool m_foldoutDistribution = true; const string RELEASE_URL = "https://github.com/vrm-c/UniVRM/releases"; public override void OnInspectorGUI() { serializedObject.Update(); if (VRMVersion.IsNewer(m_exporterVersion.stringValue)) { // モデルのバージョンが、ライブラリのバージョンより新しい EditorGUILayout.HelpBox("Check UniVRM new version.", MessageType.Warning); if (GUILayout.Button(RELEASE_URL)) { Application.OpenURL(RELEASE_URL); } } // texture EditorGUILayout.BeginHorizontal(); { // 左側 EditorGUILayout.BeginVertical(); { GUI.enabled = false; EditorGUILayout.PropertyField(m_exporterVersion); GUI.enabled = true; EditorGUILayout.PropertyField(m_thumbnail); if (Camera.main) { EditorGUILayout.HelpBox(MessageKeys.SCREENSHOT.Msg(), MessageType.Info); if (GUILayout.Button(MessageKeys.SCREENSHOT_BUTTON.Msg())) { TakeScreenShot(); } } } EditorGUILayout.EndVertical(); // 右側 m_thumbnail.objectReferenceValue = TextureField("", (Texture2D)m_thumbnail.objectReferenceValue, 100); } EditorGUILayout.EndHorizontal(); m_foldoutInfo = EditorGUILayout.Foldout(m_foldoutInfo, "Information"); if (m_foldoutInfo) { m_title.OnGUI(); m_version.OnGUI(); m_author.OnGUI(); m_contact.OnGUI(); m_reference.OnGUI(); } // EditorGUILayout.LabelField("License ", EditorStyles.boldLabel); m_foldoutPermission = EditorGUILayout.Foldout(m_foldoutPermission, Msg(MessageKeys.PERSONATION)); if (m_foldoutPermission) { var backup = EditorGUIUtility.labelWidth; RightFixedPropField(m_AllowedUser, Msg(MessageKeys.ALLOWED_USER)); RightFixedPropField(m_ViolentUssage, Msg(MessageKeys.VIOLENT_USAGE)); RightFixedPropField(m_SexualUssage, Msg(MessageKeys.SEXUAL_USAGE)); RightFixedPropField(m_CommercialUssage, Msg(MessageKeys.COMMERCIAL_USAGE)); EditorGUILayout.PropertyField(m_OtherPermissionUrl, new GUIContent("Other Permission Url")); EditorGUIUtility.labelWidth = backup; } m_foldoutDistribution = EditorGUILayout.Foldout(m_foldoutDistribution, Msg(MessageKeys.REDISTRIBUTION_MODIFICATIONS)); if (m_foldoutDistribution) { var licenseType = m_LicenseType; EditorGUILayout.PropertyField(licenseType); if ((LicenseType)licenseType.intValue == LicenseType.Other) { EditorGUILayout.PropertyField(m_OtherLicenseUrl); } } serializedObject.ApplyModifiedProperties(); } void TakeScreenShot() { var dst = SaveDialog(m_target, "png"); if (string.IsNullOrEmpty(dst)) { return; } var backup = RenderTexture.active; var backup2 = Camera.main.targetTexture; var rt = new RenderTexture(1024, 1024, 16, RenderTextureFormat.ARGB32); var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); try { RenderTexture.active = rt; Camera.main.targetTexture = rt; Camera.main.Render(); tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); tex.Apply(); var ext = Path.GetExtension(dst).ToLower(); switch (ext) { case ".png": File.WriteAllBytes(dst, tex.EncodeToPNG()); break; case ".jpg": File.WriteAllBytes(dst, tex.EncodeToJPG()); break; } var assetPath = UnityPath.FromFullpath(dst); EditorApplication.delayCall += () => { assetPath.ImportAsset(); m_target.Thumbnail = assetPath.LoadAsset(); }; } finally { RenderTexture.active = backup; Camera.main.targetTexture = backup2; Texture2D.DestroyImmediate(tex); RenderTexture.DestroyImmediate(rt); } } static string SaveDialog(VRMMetaObject meta, string ext) { var directory = Application.dataPath; var assetPath = AssetDatabase.GetAssetPath(meta); if (!string.IsNullOrEmpty(assetPath)) { directory = Path.Combine(directory + "/..", Path.GetDirectoryName(assetPath)); } return EditorUtility.SaveFilePanel( "Save thumbnail", directory, $"thumbnail.{ext}", ext); } static (Rect, Rect) FixedRight(Rect r, int width) { if (width > r.width) { width = (int)r.width; } return ( new Rect(r.x, r.y, r.width - width, r.height), new Rect(r.x + r.width - width, r.y, width, r.height) ); } static void RightFixedPropField(SerializedProperty prop, string label) { var r = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(EditorGUIUtility.singleLineHeight)); var (left, right) = FixedRight(r, 64); // Debug.Log($"{left}, {right}"); EditorGUI.LabelField(left, label); EditorGUI.PropertyField(right, prop, new GUIContent(""), false); } private static Texture2D TextureField(string name, Texture2D texture, int size) { GUILayout.BeginHorizontal(); var style = new GUIStyle(GUI.skin.label); style.alignment = TextAnchor.UpperCenter; //style.fixedWidth = size; GUILayout.Label(name, style); var result = (Texture2D)EditorGUILayout.ObjectField(texture, typeof(Texture2D), false, GUILayout.Width(size), GUILayout.Height(size)); GUILayout.EndVertical(); return result; } } }