using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.Linq; using System.Text.RegularExpressions; // ver 1.0 // © 2019 gatosyocora namespace VRCDeveloperTool { public class AnimationPropertyConverter : EditorWindow { private SkinnedMeshRenderer avatarMesh; private List preAnimationClips; private List animPropertyList; private string[] blendShapeNameList; private string saveFolder = "Assets/"; private bool convertBlendShapeName = false; private bool isOpeningPreAnimationClips = true; private bool isOpeningAnimationPropertyList = true; private Vector2 propertyScrollPos = Vector2.zero; private bool isConvertAll = false; public class AnimationProperty { public string propertyType; public string preName; public string posName; public bool isConvert; public int selectedBlendShapeIndex; public List animIndexHavingThisProperty; public AnimationProperty(string type, string name, int animIndex) { propertyType = type; preName = name; posName = preName; isConvert = false; selectedBlendShapeIndex = 0; animIndexHavingThisProperty = new List() { animIndex }; } public void AddAnimIndexHavingThisProperty(int animIndex) { animIndexHavingThisProperty.Add(animIndex); } public bool RemoveAnimIndexHavingThisProperty(int animIndex) { return animIndexHavingThisProperty.Remove(animIndex); } public bool existAnimHavingThisProperty() { return animIndexHavingThisProperty.Count > 0; } } [MenuItem("VRCDeveloperTool/AnimationPropertyConverter")] private static void Create() { GetWindow("AnimationProperty Converter"); } private void OnEnable() { avatarMesh = null; preAnimationClips = new List(); preAnimationClips.Add(null); animPropertyList = new List(); blendShapeNameList = null; } private void OnGUI() { convertBlendShapeName = EditorGUILayout.ToggleLeft("Convert BlendShapeName", convertBlendShapeName); if (convertBlendShapeName) { using (var check = new EditorGUI.ChangeCheckScope()) { avatarMesh = EditorGUILayout.ObjectField( "Avatar's SkinnedMeshRenderer", avatarMesh, typeof(SkinnedMeshRenderer), true ) as SkinnedMeshRenderer; if (check.changed && avatarMesh != null) blendShapeNameList = GetBlendShapeNameList(avatarMesh); } } isOpeningPreAnimationClips = EditorGUILayout.Foldout(isOpeningPreAnimationClips, "Pre AnimationClips"); if (isOpeningPreAnimationClips) { using (new EditorGUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); if (GUILayout.Button("+")) { preAnimationClips.Add(null); } if (GUILayout.Button("-") && preAnimationClips.Count > 1) { var animIndex = preAnimationClips.Count - 1; CheckAndRemoveAnimProperties(ref animPropertyList, animIndex); preAnimationClips.RemoveAt(animIndex); } } using (new EditorGUI.IndentLevelScope()) { for (int animIndex = 0; animIndex < preAnimationClips.Count; animIndex++) { using (var check = new EditorGUI.ChangeCheckScope()) { preAnimationClips[animIndex] = EditorGUILayout.ObjectField( "AnimationClip " + (animIndex + 1), preAnimationClips[animIndex], typeof(AnimationClip), true ) as AnimationClip; if (check.changed) { CheckAndRemoveAnimProperties(ref animPropertyList, animIndex); if (preAnimationClips[animIndex] != null) UpdateAnimationPropertyNameList(preAnimationClips[animIndex], ref animPropertyList, animIndex); } } } } } EditorGUILayout.Space(); isOpeningAnimationPropertyList = EditorGUILayout.Foldout(isOpeningAnimationPropertyList, "AnimationPropertyList"); if (isOpeningAnimationPropertyList) { using (new EditorGUI.IndentLevelScope()) { using (new EditorGUILayout.HorizontalScope()) { using (var check = new EditorGUI.ChangeCheckScope()) { isConvertAll = EditorGUILayout.Toggle(isConvertAll, GUILayout.Width(30f)); if (check.changed) ChangeAllIsConvertParams(isConvertAll, ref animPropertyList); } EditorGUILayout.LabelField("prePropertyName", EditorStyles.boldLabel); EditorGUILayout.LabelField("posPropertyName", EditorStyles.boldLabel); } using (var scrollPos = new GUILayout.ScrollViewScope(propertyScrollPos)) { propertyScrollPos = scrollPos.scrollPosition; foreach (var animProperty in animPropertyList) { if (!convertBlendShapeName || animProperty.propertyType == "blendShape") { using (new EditorGUILayout.HorizontalScope()) { animProperty.isConvert = EditorGUILayout.Toggle(animProperty.isConvert, GUILayout.Width(30f)); if (convertBlendShapeName && avatarMesh != null) { EditorGUILayout.LabelField(animProperty.preName); } else { EditorGUILayout.LabelField(animProperty.propertyType + "." + animProperty.preName); } using (var check = new EditorGUI.ChangeCheckScope()) { if (convertBlendShapeName && avatarMesh != null) { animProperty.selectedBlendShapeIndex = EditorGUILayout.Popup(animProperty.selectedBlendShapeIndex, blendShapeNameList); } else { animProperty.posName = EditorGUILayout.TextField(animProperty.posName); } if (check.changed) { if (convertBlendShapeName && avatarMesh != null) animProperty.posName = blendShapeNameList[animProperty.selectedBlendShapeIndex]; animProperty.isConvert = true; } } } } } } } } using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("AnimClipSaveFolder", saveFolder); if (GUILayout.Button("Select Folder", GUILayout.Width(100))) { saveFolder = EditorUtility.OpenFolderPanel("Select saved folder", saveFolder, ""); var match = Regex.Match(saveFolder, @"Assets/.*"); saveFolder = match.Value + "/"; if (saveFolder == "/") saveFolder = "Assets/"; } } if (GUILayout.Button("Convert Property & Save as New File")) { ConvertAndCreateAnimationClips(preAnimationClips, animPropertyList, saveFolder, convertBlendShapeName); } } /// /// アニメーションファイルのプロパティの名前を変更して新しいファイルとして書き出す /// /// /// /// /// private void ConvertAndCreateAnimationClips(List baseAnimClips, List animPropertyList, string saveFolder, bool convertBlendShapeName) { // 変換するプロパティ(isConvert == true)のものだけのリストをつくる var shoundConvertedPropertyList = animPropertyList.Where(x => x.isConvert).ToList(); foreach (var baseAnimClip in baseAnimClips) { if (baseAnimClip == null) continue; var convertedAnimClip = Object.Instantiate(baseAnimClip); var bindings = AnimationUtility.GetCurveBindings(convertedAnimClip); for (int i = 0; i < bindings.Length; i++) { // binding.propertyName == blendShape.vrc.v_silみたいになっている var propertyType = bindings[i].propertyName.Split('.')[0]; var blendShapeName = bindings[i].propertyName.Replace(propertyType + ".", ""); // blendShapeだけ変更するモードだったらそれ以外の場合は処理しない if (convertBlendShapeName && propertyType != "blendShape") continue; // 変換するプロパティのリストに含まれるプロパティだけ変換する var targetAnimProperty = shoundConvertedPropertyList.Find(x => x.propertyType == propertyType && x.preName == blendShapeName); if (targetAnimProperty != null) { var curve = AnimationUtility.GetEditorCurve(convertedAnimClip, bindings[i]); AnimationUtility.SetEditorCurve(convertedAnimClip, bindings[i], null); bindings[i].propertyName = targetAnimProperty.propertyType + "." + targetAnimProperty.posName; AnimationUtility.SetEditorCurve(convertedAnimClip, bindings[i], curve); } } AssetDatabase.CreateAsset(convertedAnimClip, AssetDatabase.GenerateUniqueAssetPath(saveFolder + baseAnimClip.name + ".anim")); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } /// /// アニメーションプロパティリストを更新する /// /// /// private void UpdateAnimationPropertyNameList(AnimationClip animClips, ref List animPropertyList, int animIndex) { var bindings = AnimationUtility.GetCurveBindings(animClips); foreach (var binding in bindings) { // binding.propertyName == blendShape.vrc.v_silみたいになっている var propertyType = binding.propertyName.Split('.')[0]; var blendShapeName = binding.propertyName.Replace(propertyType + ".", ""); // animPropertyListに含まれていないプロパティだけ追加する // すでに含まれている場合AnimIndexHavingThisPropertyに追加する var animProperty = animPropertyList.Find(x => x.propertyType == propertyType && x.preName == blendShapeName); if (animProperty == null) animPropertyList.Add(new AnimationProperty(propertyType, blendShapeName, animIndex)); else animProperty.AddAnimIndexHavingThisProperty(animIndex); } } /// /// ブレンドシェイプの名前一覧を取得する /// /// /// private string[] GetBlendShapeNameList(SkinnedMeshRenderer meshRenderer) { var mesh = meshRenderer.sharedMesh; if (mesh == null) return null; var blendShapeNameList = new string[mesh.blendShapeCount]; for (int i = 0; i < mesh.blendShapeCount; i++) blendShapeNameList[i] = mesh.GetBlendShapeName(i); return blendShapeNameList; } /// /// すべてのプロパティリストのisConvertを変更する /// /// /// private void ChangeAllIsConvertParams(bool isConvertAll, ref List animPropertyList) { foreach (var animProperty in animPropertyList) { animProperty.isConvert = isConvertAll; } } /// /// プロパティリストに含まれるすべてのプロパティのAnimIndexHavingThisPropertyからanimIndexを削除して /// リストに不要なプロパティか調べ、削除する /// /// /// private void CheckAndRemoveAnimProperties(ref List animPropertyList, int animIndex) { foreach (var animProperty in animPropertyList.ToArray()) { animProperty.RemoveAnimIndexHavingThisProperty(animIndex); if (!animProperty.existAnimHavingThisProperty()) animPropertyList.Remove(animProperty); } } } }