using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.IO; using System.Linq; using System; namespace VRCDeveloperTool { public class ShapeKeyNameChanger : EditorWindow { private List shapeKeyNames; private SkinnedMeshRenderer renderer; private string[] posNames; private bool useDuplication = false; private Vector2 scrollPos = Vector2.zero; enum SelectType { Input, Select } private SelectType selectTab = SelectType.Input; public static GUIContent[] tabToggles { get { return System.Enum.GetNames(typeof(SelectType)).Select(x => new GUIContent(x)).ToArray(); } } [Serializable] public class JsonData { public string[] ShapeKeyNames; } private string[] selectableNames; private int[] selectedIndices; private void OnEnable() { shapeKeyNames = null; renderer = null; posNames = null; selectableNames = LoadJsonDataFromResourceFolder("ShapeKeyNameChanger/shapekeynames"); } [MenuItem("VRCDeveloperTool/Mesh/ShapeKeyName Changer")] private static void Create() { GetWindow("ShapeKeyName Changer"); } private void OnGUI() { using (var check = new EditorGUI.ChangeCheckScope()) { renderer = EditorGUILayout.ObjectField( "Renderer", renderer, typeof(SkinnedMeshRenderer), true ) as SkinnedMeshRenderer; if (check.changed) { if (renderer == null) { shapeKeyNames = null; posNames = null; selectedIndices = null; } else { shapeKeyNames = GetBlendShapeListFromRenderer(renderer); posNames = shapeKeyNames.ToArray(); selectedIndices = new int[shapeKeyNames.Count]; for (int i = 0; i < selectedIndices.Length; i++) { selectedIndices[i] = -1; } } } } selectTab = (SelectType)GUILayout.Toolbar((int)selectTab, tabToggles, "LargeButton", GUI.ToolbarButtonSize.Fixed); if (shapeKeyNames != null) { using (var pos = new GUILayout.ScrollViewScope(scrollPos)) { scrollPos = pos.scrollPosition; if (selectTab == SelectType.Input) { for (int i = 0; i < shapeKeyNames.Count; i++) { using (new EditorGUILayout.HorizontalScope()) { using (var toggle = new EditorGUI.ChangeCheckScope()) { EditorGUILayout.Toggle(shapeKeyNames[i] != posNames[i], GUILayout.Width(30)); if (toggle.changed && shapeKeyNames[i] != posNames[i]) { posNames[i] = shapeKeyNames[i]; selectedIndices[i] = -1; } } posNames[i] = EditorGUILayout.TextField(shapeKeyNames[i], posNames[i]); } } } else { for (int i = 0; i < shapeKeyNames.Count; i++) { using (new EditorGUILayout.HorizontalScope()) { using (var toggle = new EditorGUI.ChangeCheckScope()) { EditorGUILayout.Toggle(shapeKeyNames[i] != posNames[i], GUILayout.Width(30)); if (toggle.changed && shapeKeyNames[i] != posNames[i]) { posNames[i] = shapeKeyNames[i]; selectedIndices[i] = -1; } } using (var check = new EditorGUI.ChangeCheckScope()) { selectedIndices[i] = EditorGUILayout.Popup(shapeKeyNames[i], selectedIndices[i], selectableNames); if (check.changed && selectedIndices[i] != -1) posNames[i] = selectableNames[selectedIndices[i]]; } } } } } } using (new EditorGUI.DisabledScope(renderer == null)) { useDuplication = EditorGUILayout.Toggle("Duplication ShapeKeys", useDuplication); // 대문자 -> 소문자 변환 버튼 if (GUILayout.Button("Convert First Letter to Lowercase")) { ConvertFirstLetterToLowercase(); } // 소문자 -> 대문자 변환 버튼 if (GUILayout.Button("Convert First Letter to Uppercase")) { ConvertFirstLetterToUppercase(); } if (GUILayout.Button("Change ShapeKeyName")) { CreateNewShapeKeyNameMesh(renderer, posNames, useDuplication, shapeKeyNames); shapeKeyNames = GetBlendShapeListFromRenderer(renderer); posNames = shapeKeyNames.ToArray(); selectedIndices = new int[shapeKeyNames.Count]; for (int i = 0; i < selectedIndices.Length; i++) { selectedIndices[i] = -1; } } } } // 첫 글자를 소문자로 변환하는 함수 private void ConvertFirstLetterToLowercase() { for (int i = 0; i < posNames.Length; i++) { if (!char.IsLower(posNames[i][0])) { posNames[i] = char.ToLower(posNames[i][0]) + posNames[i].Substring(1); } } } // 첫 글자를 대문자로 변환하는 함수 private void ConvertFirstLetterToUppercase() { for (int i = 0; i < posNames.Length; i++) { if (!char.IsUpper(posNames[i][0])) { posNames[i] = char.ToUpper(posNames[i][0]) + posNames[i].Substring(1); } } } private bool CreateNewShapeKeyNameMesh(SkinnedMeshRenderer renderer, string[] posShapeKeyNames, bool useDuplication, List preShapeKeyNames) { var mesh = renderer.sharedMesh; if (mesh == null) return false; if (posShapeKeyNames.Length != mesh.blendShapeCount) return false; var mesh_custom = UnityEngine.Object.Instantiate(mesh); mesh_custom.ClearBlendShapes(); var frameIndex = 0; var shapeKeyName = string.Empty; Vector3[] deltaVertices, deltaNormals, deltaTangents; for (int blendShapeIndex = 0; blendShapeIndex < mesh.blendShapeCount; blendShapeIndex++) { deltaVertices = new Vector3[mesh.vertexCount]; deltaNormals = new Vector3[mesh.vertexCount]; deltaTangents = new Vector3[mesh.vertexCount]; mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents); var weight = mesh.GetBlendShapeFrameWeight(blendShapeIndex, frameIndex); shapeKeyName = posNames[blendShapeIndex]; if (useDuplication && !preShapeKeyNames[blendShapeIndex].Equals(shapeKeyName)) { mesh_custom.AddBlendShapeFrame(preShapeKeyNames[blendShapeIndex], weight, deltaVertices, deltaNormals, deltaTangents); } mesh_custom.AddBlendShapeFrame(shapeKeyName, weight, deltaVertices, deltaNormals, deltaTangents); } Undo.RecordObject(renderer, "Renderer " + renderer.name); renderer.sharedMesh = mesh_custom; var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)) + "/" + mesh.name + "_custom.asset"; AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(path)); AssetDatabase.SaveAssets(); return true; } private List GetBlendShapeListFromRenderer(SkinnedMeshRenderer renderer) { List shapeKeyNames = new List(); var mesh = renderer.sharedMesh; if (mesh != null) for (int i = 0; i < mesh.blendShapeCount; i++) shapeKeyNames.Add(mesh.GetBlendShapeName(i)); return shapeKeyNames; } private string[] LoadJsonDataFromResourceFolder(string path) { var textAsset = Resources.Load(path); var jsonData = JsonUtility.FromJson(textAsset.ToString()); return jsonData.ShapeKeyNames; } } }