297 lines
11 KiB
C#
297 lines
11 KiB
C#
#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<ARKitAdder>("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<SkinnedMeshRenderer>();
|
|
|
|
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 |