KINDNICK_URP/Assets/External/YAMOScripts/AvatarComponetCopier.cs
2025-05-15 00:18:25 +09:00

556 lines
25 KiB
C#

using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Collections.Generic;
using VRM;
using MagicaCloth2;
using MagicaClothType = MagicaCloth2.MagicaCloth;
using UnityEngine.Animations;
public class AvatarComponetCopier : EditorWindow
{
GameObject sourcePrefab;
GameObject destinationPrefab;
[MenuItem("Tools/Avatar Component Mover (SpringBone+MagicaCloth2)")]
static void ShowWindow()
{
GetWindow<AvatarComponetCopier>("Avatar Component Mover");
}
void OnGUI()
{
GUILayout.Label("Move VRMSpringBone/MagicaCloth2 & Collider Components", EditorStyles.boldLabel);
sourcePrefab = (GameObject)EditorGUILayout.ObjectField("Source Object", sourcePrefab, typeof(GameObject), true);
destinationPrefab = (GameObject)EditorGUILayout.ObjectField("Target Object", destinationPrefab, typeof(GameObject), true);
if (GUILayout.Button("Spring본콜라이더 복사"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopySpringBoneAndMagicaColliders();
}
if (GUILayout.Button("Spring본/Cloth 옮기기"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
MoveSpringBonesAndMagicaCloth();
}
if (GUILayout.Button("Constraint 값 복사"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopyConstraints();
}
if (GUILayout.Button("BlendShapeCopy"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopyBlendShapes();
}
if (GUILayout.Button("ActiveStateCopy"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopyActiveStates();
}
}
void CopySpringBoneAndMagicaColliders()
{
// VRMSpringBoneColliderGroup
var srcColliders = sourcePrefab.GetComponentsInChildren<VRMSpringBoneColliderGroup>(true)
.OrderBy(c => GetTransformPath(c.transform, sourcePrefab.transform)).ToArray();
foreach (var srcCollider in srcColliders)
{
string path = GetTransformPath(srcCollider.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
if (tgtTransform.GetComponent<VRMSpringBoneColliderGroup>() != null) continue;
var tgtCollider = tgtTransform.gameObject.AddComponent<VRMSpringBoneColliderGroup>();
CopyColliderGroupParameters(srcCollider, tgtCollider);
}
// MagicaCloth2 Colliders
CopyMagicaCollider<MagicaSphereCollider>();
CopyMagicaCollider<MagicaCapsuleCollider>();
CopyMagicaCollider<MagicaPlaneCollider>();
Debug.Log("VRMSpringBoneColliderGroup & MagicaCloth2 Collider 복사가 완료되었습니다.");
}
void CopyMagicaCollider<T>() where T : Component
{
var srcColliders = sourcePrefab.GetComponentsInChildren<T>(true)
.OrderBy(c => GetTransformPath(((Component)c).transform, sourcePrefab.transform)).ToArray();
foreach (var srcCollider in srcColliders)
{
var srcTr = ((Component)srcCollider).transform;
string path = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
if (tgtTransform.GetComponent<T>() != null) continue;
var tgtCollider = tgtTransform.gameObject.AddComponent<T>();
CopyMagicaColliderComponents(srcCollider, tgtCollider);
}
}
void CopyMagicaColliderComponents(Component src, Component tgt)
{
var fields = src.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
var value = field.GetValue(src);
if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
{
Object refObj = value as Object;
if (refObj == null) { field.SetValue(tgt, null); continue; }
if (refObj is Transform tr)
{
string refPath = GetTransformPath(tr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(refPath);
if (tgtTr != null)
field.SetValue(tgt, tgtTr);
}
else if (refObj is GameObject go)
{
string refPath = GetTransformPath(go.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
field.SetValue(tgt, tgtGo.gameObject);
}
else if (refObj is Component comp)
{
string refPath = GetTransformPath(comp.gameObject.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
{
var tgtComps = tgtGo.GetComponents(comp.GetType());
int compIdx = comp.gameObject.GetComponents(comp.GetType()).ToList().IndexOf(comp);
if (compIdx >= 0 && compIdx < tgtComps.Length)
field.SetValue(tgt, tgtComps[compIdx]);
}
}
else
{
// 기타 UnityEngine.Object 타입은 그대로 복사
field.SetValue(tgt, refObj);
}
}
else
{
// 값 타입, enum, string 등은 그대로 복사
field.SetValue(tgt, value);
}
}
}
void MoveSpringBonesAndMagicaCloth()
{
// VRMSpringBone
var srcSpringBones = sourcePrefab.GetComponentsInChildren<VRMSpringBone>(true)
.OrderBy(s => GetTransformPath(s.transform, sourcePrefab.transform)).ToArray();
foreach (var srcSpringBone in srcSpringBones)
{
string path = GetTransformPath(srcSpringBone.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
var tgtSpringBone = tgtTransform.gameObject.AddComponent<VRMSpringBone>();
CopyVRMSpringBoneComponents(srcSpringBone, tgtSpringBone);
DestroyImmediate(srcSpringBone);
}
// MagicaCloth
var srcMagicaCloths = sourcePrefab.GetComponentsInChildren<MagicaClothType>(true)
.OrderBy(s => GetTransformPath(s.transform, sourcePrefab.transform)).ToArray();
foreach (var srcCloth in srcMagicaCloths)
{
string path = GetTransformPath(srcCloth.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
var tgtCloth = tgtTransform.gameObject.AddComponent<MagicaClothType>();
CopyMagicaClothComponents(srcCloth, tgtCloth);
DestroyImmediate(srcCloth);
}
Debug.Log("VRMSpringBone & MagicaCloth 옮기기가 완료되었습니다.");
}
void CopyColliderGroupParameters(VRMSpringBoneColliderGroup source, VRMSpringBoneColliderGroup target)
{
target.Colliders = source.Colliders.Select(c => new VRMSpringBoneColliderGroup.SphereCollider
{
Offset = c.Offset,
Radius = c.Radius
}).ToArray();
}
void CopyVRMSpringBoneComponents(VRMSpringBone original, VRMSpringBone copy)
{
copy.m_comment = original.m_comment;
copy.m_stiffnessForce = original.m_stiffnessForce;
copy.m_gravityPower = original.m_gravityPower;
copy.m_gravityDir = original.m_gravityDir;
copy.m_dragForce = original.m_dragForce;
copy.m_center = FindCorrespondingTransform(original.m_center, destinationPrefab.transform);
copy.m_hitRadius = original.m_hitRadius;
copy.m_updateType = original.m_updateType;
copy.RootBones = original.RootBones
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
copy.ColliderGroups = original.ColliderGroups
.Select(colliderGroup => FindCorrespondingColliderGroup(colliderGroup, destinationPrefab.transform))
.Where(cg => cg != null)
.ToArray();
}
Transform FindCorrespondingTransform(Transform original, Transform searchRoot)
{
if (original == null) return null;
var path = GetTransformPath(original, sourcePrefab.transform);
return searchRoot.Find(path);
}
string GetTransformPath(Transform current, Transform root)
{
if (current == root) return "";
var path = current.name;
while (current.parent != null && current.parent != root)
{
current = current.parent;
path = current.name + "/" + path;
}
return path;
}
VRMSpringBoneColliderGroup FindCorrespondingColliderGroup(VRMSpringBoneColliderGroup original, Transform searchRoot)
{
if (original == null) return null;
var path = GetTransformPath(original.transform, sourcePrefab.transform);
var correspondingTransform = searchRoot.Find(path);
return correspondingTransform != null ? correspondingTransform.GetComponent<VRMSpringBoneColliderGroup>() : null;
}
void CopyMagicaClothComponents(MagicaClothType src, MagicaClothType tgt)
{
var fields = typeof(MagicaClothType).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
var value = field.GetValue(src);
// ClothType 우선 복사 및 분기 처리
if (field.Name == "serializeData" && value is MagicaCloth2.ClothSerializeData sdata)
{
var tdata = new MagicaCloth2.ClothSerializeData();
// ClothType을 먼저 복사
tdata.clothType = sdata.clothType;
// ClothType에 따라 분기
if (sdata.clothType == MagicaCloth2.ClothProcess.ClothType.MeshCloth)
{
// sourceRenderers 복사
tdata.sourceRenderers = sdata.sourceRenderers
.Select(r => {
if (r == null) return null;
string path = GetTransformPath(r.transform, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
return tgtTr ? tgtTr.GetComponent<Renderer>() : null;
})
.Where(r => r != null)
.ToList();
}
else if (sdata.clothType == MagicaCloth2.ClothProcess.ClothType.BoneCloth || sdata.clothType == MagicaCloth2.ClothProcess.ClothType.BoneSpring)
{
// rootBones 복사
tdata.rootBones = sdata.rootBones
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
}
// colliderCollisionConstraint 복사
if (sdata.colliderCollisionConstraint != null)
{
var srcCol = sdata.colliderCollisionConstraint;
var tgtCol = new MagicaCloth2.ColliderCollisionConstraint.SerializeData();
tgtCol.mode = srcCol.mode;
tgtCol.friction = srcCol.friction;
tgtCol.limitDistance = srcCol.limitDistance;
// colliderList 변환
tgtCol.colliderList = srcCol.colliderList
.Select(c => {
if (c == null) return null;
string path = GetTransformPath(c.transform, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
return tgtTr ? tgtTr.GetComponent<MagicaCloth2.ColliderComponent>() : null;
})
.Where(c => c != null)
.ToList();
// collisionBones 변환
tgtCol.collisionBones = srcCol.collisionBones
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
tdata.colliderCollisionConstraint = tgtCol;
}
// 공통 필드 복사
tdata.paintMaps = new List<Texture2D>(sdata.paintMaps);
tdata.paintMode = sdata.paintMode;
tdata.connectionMode = sdata.connectionMode;
tdata.rotationalInterpolation = sdata.rotationalInterpolation;
tdata.rootRotation = sdata.rootRotation;
tdata.updateMode = sdata.updateMode;
tdata.animationPoseRatio = sdata.animationPoseRatio;
tdata.reductionSetting = sdata.reductionSetting;
tdata.customSkinningSetting = sdata.customSkinningSetting;
tdata.normalAlignmentSetting = sdata.normalAlignmentSetting;
tdata.cullingSettings = sdata.cullingSettings;
tdata.normalAxis = sdata.normalAxis;
tdata.gravity = sdata.gravity;
tdata.gravityDirection = sdata.gravityDirection;
tdata.gravityFalloff = sdata.gravityFalloff;
tdata.stablizationTimeAfterReset = sdata.stablizationTimeAfterReset;
tdata.blendWeight = sdata.blendWeight;
tdata.damping = sdata.damping;
tdata.radius = sdata.radius;
// 기타 값들도 필요시 추가 복사
field.SetValue(tgt, tdata);
continue;
}
if (field.Name == "serializeData2" && value is MagicaCloth2.ClothSerializeData2 sdata2)
{
var tdata2 = new MagicaCloth2.ClothSerializeData2();
// boneAttributeDict 변환
tdata2.boneAttributeDict = sdata2.boneAttributeDict.ToDictionary(
kvp => FindCorrespondingTransform(kvp.Key, destinationPrefab.transform),
kvp => kvp.Value
);
// selectionData 등 나머지 값은 그대로 복사
tdata2.selectionData = sdata2.selectionData;
tdata2.preBuildData = sdata2.preBuildData;
field.SetValue(tgt, tdata2);
continue;
}
// customSkinningSetting.skinningBones 변환
if (field.Name == "customSkinningSetting" && value != null)
{
var srcSkin = value;
var skinField = value.GetType().GetField("skinningBones");
if (skinField != null)
{
var srcList = skinField.GetValue(srcSkin) as List<Transform>;
if (srcList != null)
{
var tgtList = srcList
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
skinField.SetValue(srcSkin, tgtList);
}
}
field.SetValue(tgt, srcSkin);
continue;
}
// 이하 기존 로직
if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
{
Object refObj = value as Object;
if (refObj == null) { field.SetValue(tgt, null); continue; }
if (refObj is Transform tr)
{
string refPath = GetTransformPath(tr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(refPath);
if (tgtTr != null)
field.SetValue(tgt, tgtTr);
}
else if (refObj is GameObject go)
{
string refPath = GetTransformPath(go.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
field.SetValue(tgt, tgtGo.gameObject);
}
else if (refObj is Component comp)
{
string refPath = GetTransformPath(comp.gameObject.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
{
var tgtComps = tgtGo.GetComponents(comp.GetType());
int compIdx = comp.gameObject.GetComponents(comp.GetType()).ToList().IndexOf(comp);
if (compIdx >= 0 && compIdx < tgtComps.Length)
field.SetValue(tgt, tgtComps[compIdx]);
}
}
else
{
// 기타 UnityEngine.Object 타입은 그대로 복사
field.SetValue(tgt, refObj);
}
}
else
{
// 값 타입, enum, string 등은 그대로 복사
field.SetValue(tgt, value);
}
}
}
void CopyConstraints()
{
var srcTransforms = sourcePrefab.GetComponentsInChildren<Transform>(true);
foreach (var srcTr in srcTransforms)
{
string path = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
if (tgtTr == null) continue;
// PositionConstraint
var srcPos = srcTr.GetComponent<PositionConstraint>();
if (srcPos != null)
CopyConstraintComponent<PositionConstraint>(srcPos, tgtTr);
// RotationConstraint
var srcRot = srcTr.GetComponent<RotationConstraint>();
if (srcRot != null)
CopyConstraintComponent<RotationConstraint>(srcRot, tgtTr);
// ParentConstraint
var srcParent = srcTr.GetComponent<ParentConstraint>();
if (srcParent != null)
CopyConstraintComponent<ParentConstraint>(srcParent, tgtTr);
}
Debug.Log("Constraint 값 복사가 완료되었습니다.");
}
void CopyConstraintComponent<T>(T srcConstraint, Transform tgtTr) where T : Behaviour
{
// 이미 있으면 삭제 후 새로 추가
var tgtConstraint = tgtTr.GetComponent<T>();
if (tgtConstraint != null)
DestroyImmediate(tgtConstraint);
tgtConstraint = tgtTr.gameObject.AddComponent<T>();
if (srcConstraint is PositionConstraint srcPos && tgtConstraint is PositionConstraint tgtPos)
{
tgtPos.weight = srcPos.weight;
tgtPos.constraintActive = srcPos.constraintActive;
tgtPos.locked = srcPos.locked;
for (int i = 0; i < srcPos.sourceCount; i++)
{
var src = srcPos.GetSource(i);
var srcTr = src.sourceTransform;
if (srcTr == null) continue;
string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
if (tgtSourceTr == null) continue;
var newSource = src;
newSource.sourceTransform = tgtSourceTr;
tgtPos.AddSource(newSource);
}
tgtPos.translationAtRest = srcPos.translationAtRest;
tgtPos.translationOffset = srcPos.translationOffset;
tgtPos.translationAxis = srcPos.translationAxis;
}
else if (srcConstraint is RotationConstraint srcRot && tgtConstraint is RotationConstraint tgtRot)
{
tgtRot.weight = srcRot.weight;
tgtRot.constraintActive = srcRot.constraintActive;
tgtRot.locked = srcRot.locked;
for (int i = 0; i < srcRot.sourceCount; i++)
{
var src = srcRot.GetSource(i);
var srcTr = src.sourceTransform;
if (srcTr == null) continue;
string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
if (tgtSourceTr == null) continue;
var newSource = src;
newSource.sourceTransform = tgtSourceTr;
tgtRot.AddSource(newSource);
}
tgtRot.rotationAtRest = srcRot.rotationAtRest;
tgtRot.rotationOffset = srcRot.rotationOffset;
tgtRot.rotationAxis = srcRot.rotationAxis;
}
else if (srcConstraint is ParentConstraint srcParent && tgtConstraint is ParentConstraint tgtParent)
{
tgtParent.weight = srcParent.weight;
tgtParent.constraintActive = srcParent.constraintActive;
tgtParent.locked = srcParent.locked;
for (int i = 0; i < srcParent.sourceCount; i++)
{
var src = srcParent.GetSource(i);
var srcTr = src.sourceTransform;
if (srcTr == null) continue;
string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
if (tgtSourceTr == null) continue;
var newSource = src;
newSource.sourceTransform = tgtSourceTr;
tgtParent.AddSource(newSource);
// Offset도 복사
tgtParent.SetTranslationOffset(i, srcParent.GetTranslationOffset(i));
tgtParent.SetRotationOffset(i, srcParent.GetRotationOffset(i));
}
tgtParent.translationAtRest = srcParent.translationAtRest;
tgtParent.rotationAtRest = srcParent.rotationAtRest;
tgtParent.translationAxis = srcParent.translationAxis;
tgtParent.rotationAxis = srcParent.rotationAxis;
}
}
void CopyBlendShapes()
{
var srcRenderers = sourcePrefab.GetComponentsInChildren<SkinnedMeshRenderer>(true);
foreach (var srcRenderer in srcRenderers)
{
string path = GetTransformPath(srcRenderer.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
var tgtRenderer = tgtTransform.GetComponent<SkinnedMeshRenderer>();
if (tgtRenderer == null) continue;
int blendShapeCount = srcRenderer.sharedMesh != null ? srcRenderer.sharedMesh.blendShapeCount : 0;
for (int i = 0; i < blendShapeCount; i++)
{
string shapeName = srcRenderer.sharedMesh.GetBlendShapeName(i);
int tgtIndex = tgtRenderer.sharedMesh != null ? tgtRenderer.sharedMesh.GetBlendShapeIndex(shapeName) : -1;
if (tgtIndex >= 0)
{
float value = srcRenderer.GetBlendShapeWeight(i);
tgtRenderer.SetBlendShapeWeight(tgtIndex, value);
}
}
}
Debug.Log("BlendShape 값 복사가 완료되었습니다.");
}
void CopyActiveStates()
{
var srcTransforms = sourcePrefab.GetComponentsInChildren<Transform>(true);
foreach (var srcTr in srcTransforms)
{
string path = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
if (tgtTr == null) continue;
tgtTr.gameObject.SetActive(srcTr.gameObject.activeSelf);
}
Debug.Log("ActiveState 복사가 완료되었습니다.");
}
}