#if UNITY_EDITOR using UnityEngine; using UnityEditor; using System; using System.Collections; using System.Collections.Generic; using System.IO; using VRM; using UniGLTF; namespace Bitd { public class ARKitAdder : EditorWindow { string[] blendShapeNames = new string[] { "eyeBlinkLeft", "eyeBlinkRight","eyeLookDownLeft", "eyeLookDownRight", "eyeLookInLeft", "eyeLookInRight", "eyeLookOutLeft", "eyeLookOutRight", "eyeLookUpLeft", "eyeLookUpRight", "eyeSquintLeft", "eyeSquintRight", "eyeWideLeft", "eyeWideRight", "browDownLeft", "browDownRight", "browInnerUp", "browOuterUpLeft", "browOuterUpRight", "mouthClose", "mouthFunnel", "mouthPucker", "mouthLeft", "mouthRight", "mouthSmileLeft", "mouthSmileRight", "mouthFrownLeft", "mouthFrownRight", "mouthDimpleLeft", "mouthDimpleRight", "mouthStretchLeft", "mouthStretchRight", "mouthRollLower", "mouthRollUpper", "mouthShrugLower", "mouthShrugUpper", "mouthPressLeft", "mouthPressRight", "mouthLowerDownLeft", "mouthLowerDownRight", "mouthUpperUpLeft", "mouthUpperUpRight", "cheekPuff", "cheekSquintLeft", "cheekSquintRight", "noseSneerLeft", "noseSneerRight", "jawOpen", "jawForward", "jawLeft", "jawRight", "tongueOut" }; VRMBlendShapeProxy proxy; [MenuItem("Bitd/ARKit 생성기", false, 301)] public static void ShowWindow() { var window = GetWindow("ARKit 생성기"); } void OnGUI() { GUILayout.Label("파일 생성기", EditorStyles.boldLabel); // 선택된 오브젝트 표시 proxy = (VRMBlendShapeProxy)EditorGUILayout.ObjectField("타겟 아바타", proxy, typeof(VRMBlendShapeProxy), true); if (proxy == null) { EditorGUILayout.HelpBox("아바타를 넣어주세요.", MessageType.Warning); } else { // 버튼: 파일 생성 시작 if (GUILayout.Button("ARKit 생성")) { GenerateFiles(); } } } private void GenerateFiles() { // 파일 경로 선택 string path = EditorUtility.SaveFolderPanel("파일 저장 경로 선택", Application.dataPath, ""); if (string.IsNullOrEmpty(path)) { Debug.LogWarning("경로 선택이 취소되었습니다."); return; } // Unity 경로로 변환 if (path.StartsWith(Application.dataPath)) { path = "Assets" + path.Substring(Application.dataPath.Length); } else { Debug.LogError("경로는 반드시 Unity 프로젝트 내부여야 합니다."); return; } Debug.Log($"파일 생성 경로: {path}"); // 파일 생성 함수 호출 CreateFilesAtPath(path); // 에디터 윈도우 닫기 this.Close(); // 에디터 리컴파일 강제 AssetDatabase.Refresh(); } private void CreateFilesAtPath(string path) { // 파일 생성 로직을 여기에 구현 Debug.Log($"'{proxy.name}'에 있는 블렌드쉐이프 값을 찾아서 '{path}'에 클립들을 생성합니다!"); SkinnedMeshRenderer[] targetObjs = proxy.GetComponentsInChildren(); for (int i = 0; i < targetObjs.Length; i++) { for (int j = 0; j < blendShapeNames.Length; j++) { CreateClipByName(path, blendShapeNames[j], targetObjs[i]); //CreateClipByName(path, CapitalizeFirstLetter(blendShapeNames[j]), targetObjs[i]); } } } private void CreateClipByName(string path, string clipName, SkinnedMeshRenderer body) { BlendShapeClip bsClip; bool alreadyIn = false; int targetIndex = -1; string lowerFirstName = LowercaseFirstLetter(clipName); // 앞글자 소문자 들어옴 string upperFirstName = CapitalizeFirstLetter(clipName); // 앞글자 대문자 들어옴 // 블쉪클립을 찾기 시작함 // 앞글자가 소문자인 경우 체크 for (int i = 0; i < proxy.BlendShapeAvatar.Clips.Count; i++) { if (proxy.BlendShapeAvatar.Clips[i].BlendShapeName.Contains(clipName)) { alreadyIn = true; targetIndex = i; break; } } // 소문자로 체크되지 않을 경우 앞글자가 대문자인 경우 체크 if (!alreadyIn) { for (int i = 0; i < proxy.BlendShapeAvatar.Clips.Count; i++) { if (proxy.BlendShapeAvatar.Clips[i].BlendShapeName.Contains(upperFirstName)) { alreadyIn = true; targetIndex = i; break; } } } // 블쉪클립 유무를 체크 완료함 (클립 네임을 찾았던 거임) // 아래는 쉐이프키 네임을 찾음 // 소문자로 쭉 찾아봄 int blendshapeIndex = body.sharedMesh.GetBlendShapeIndex(lowerFirstName); // 해당하는 블랜드쉐이프가 없을 경우 대문자로 한번 찾아봄 if (blendshapeIndex < 0) { blendshapeIndex = body.sharedMesh.GetBlendShapeIndex(upperFirstName); // 아직도 없으면 반환 if (blendshapeIndex < 0) { return; } } // 블랜드쉐이프키가 존재할 경우 로직을 수행함 if (alreadyIn) { // 블쉪클립이 이미 존재한다면 그걸로 등록 bsClip = proxy.BlendShapeAvatar.Clips[targetIndex]; } else { // 블쉪클립이 존재하지 않는다면 새로 생성 string fileName = $"{path}/{clipName}.asset"; fileName = AssetDatabase.GenerateUniqueAssetPath(fileName); // Create new BlendShapeClip bsClip = BlendShapeAvatar.CreateBlendShapeClip(fileName); bsClip.BlendShapeName = lowerFirstName; bsClip.Preset = BlendShapePreset.Unknown; proxy.BlendShapeAvatar.Clips.Add(bsClip); } // Add BlendShapeBinding var bsb = new BlendShapeBinding { RelativePath = body.name, Index = blendshapeIndex, Weight = 100 }; if (!IsBindingAlreadySet(bsClip, bsb)) { Array.Resize(ref bsClip.Values, bsClip.Values.Length + 1); bsClip.Values[bsClip.Values.Length - 1] = bsb; } // Save changes EditorUtility.SetDirty(bsClip); EditorUtility.SetDirty(proxy.BlendShapeAvatar); AssetDatabase.SaveAssets(); Debug.Log($"Successfully created BlendShapeClip: {clipName}"); } //private void CreateClipByName(string path, string clipName, SkinnedMeshRenderer body) //{ // Debug.Log($"Creating BlendShapeClip: {clipName} / {body.name}"); // // Lowercase/Uppercase 처리 // clipName = LowercaseFirstLetter(clipName); // string upperFirstName = CapitalizeFirstLetter(clipName); // // Check if BlendShapeClip already exists // BlendShapeClip bsClip = proxy.BlendShapeAvatar.Clips.Find( // clip => clip.BlendShapeName.Equals(clipName, StringComparison.OrdinalIgnoreCase) || // clip.BlendShapeName.Equals(upperFirstName, StringComparison.OrdinalIgnoreCase) // ); // // Find BlendShape index // string blendshapeName = bsClip != null && bsClip.BlendShapeName.Equals(upperFirstName) ? upperFirstName : clipName; // int blendshapeIndex = body.sharedMesh.GetBlendShapeIndex(blendshapeName); // if (blendshapeIndex < 0) // { // Debug.LogWarning($"BlendShape '{blendshapeName}' not found in SkinnedMeshRenderer '{body.name}'."); // return; // } // if (bsClip == null) // { // // Generate unique path for the new BlendShapeClip // string fileName = $"{path}/{clipName}.asset"; // fileName = AssetDatabase.GenerateUniqueAssetPath(fileName); // // Create new BlendShapeClip // bsClip = BlendShapeAvatar.CreateBlendShapeClip(fileName); // if (bsClip == null) // { // Debug.LogError($"Failed to create BlendShapeClip: {fileName}"); // return; // } // bsClip.BlendShapeName = clipName; // proxy.BlendShapeAvatar.Clips.Add(bsClip); // } // // Add BlendShapeBinding // var bsb = new BlendShapeBinding // { // RelativePath = body.name, // Index = blendshapeIndex, // Weight = 100 // }; // if (!IsBindingAlreadySet(bsClip, bsb)) // { // Array.Resize(ref bsClip.Values, bsClip.Values.Length + 1); // bsClip.Values[bsClip.Values.Length - 1] = bsb; // } // // Save changes // EditorUtility.SetDirty(proxy.BlendShapeAvatar); // AssetDatabase.SaveAssets(); // Debug.Log($"Successfully created BlendShapeClip: {clipName}"); //} private bool IsBindingAlreadySet(BlendShapeClip clip, BlendShapeBinding binding) { foreach (var value in clip.Values) { if (value.RelativePath == binding.RelativePath && value.Index == binding.Index) { return true; } } return false; } private string CapitalizeFirstLetter(string input) { if (string.IsNullOrEmpty(input)) return input; return char.ToUpper(input[0]) + input.Substring(1); } private string LowercaseFirstLetter(string input) { if (string.IsNullOrEmpty(input)) return input; return char.ToLower(input[0]) + input.Substring(1); } } } #endif