mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
Improve InferPrefixSuffix / Support using Humanoid Rig on Setup Outfit (#1167)
* feat: inferring prefix/suffix now supports infer with HeuristicBoneMapper * feat: inferring prefix/suffix is now triggered when prefix/suffix is empty and merge target changed * chore: add comment for inferring prefix/suffix with HeuristicBoneMapper * feat: support using Humanoid Rig on RenameBonesByHeuristic * feat: support using cloth's Humanoid Rig on merge_armature.adjust_names * feat: support outfits' hips in one more deep place * chore: refine condition on Heuristic Bone Mapper's exact humanoid bone matching * chore: unify the process for get outfit's humanoid bones * chore: rename variable name to clarify means * chore: use InitializeOnLoadMethod instead of reflection to get boneNamePattern from Editor Assembly * test: add some tests for SetupOutfit and InferPrefixSuffix
This commit is contained in:
parent
e752762d21
commit
497d16f89d
@ -243,6 +243,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
internal static readonly ImmutableDictionary<string, List<HumanBodyBones>> NameToBoneMap;
|
||||
internal static readonly ImmutableDictionary<HumanBodyBones, ImmutableList<string>> BoneToNameMap;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void InsertboneNamePatternsToRuntime()
|
||||
{
|
||||
ModularAvatarMergeArmature.boneNamePatterns = boneNamePatterns;
|
||||
}
|
||||
|
||||
static HeuristicBoneMapper()
|
||||
{
|
||||
var pat_end_side = new Regex(@"[_\.]([LR])$");
|
||||
@ -306,7 +312,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
GameObject src,
|
||||
GameObject newParent,
|
||||
List<Transform> skipped = null,
|
||||
HashSet<Transform> unassigned = null
|
||||
HashSet<Transform> unassigned = null,
|
||||
Animator avatarAnimator = null,
|
||||
Dictionary<Transform, HumanBodyBones> outfitHumanoidBones = null
|
||||
)
|
||||
{
|
||||
Dictionary<Transform, Transform> mappings = new Dictionary<Transform, Transform>();
|
||||
@ -355,21 +363,65 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var childName = child.gameObject.name;
|
||||
var targetObjectName = childName.Substring(config.prefix.Length,
|
||||
childName.Length - config.prefix.Length - config.suffix.Length);
|
||||
|
||||
if (!NameToBoneMap.TryGetValue(
|
||||
NormalizeName(targetObjectName), out var bodyBones))
|
||||
List<HumanBodyBones> bodyBones = null;
|
||||
var isMapped = false;
|
||||
|
||||
if (outfitHumanoidBones != null && outfitHumanoidBones.TryGetValue(child, out var outfitHumanoidBone))
|
||||
{
|
||||
if (avatarAnimator != null)
|
||||
{
|
||||
var avatarBone = avatarAnimator.GetBoneTransform(outfitHumanoidBone);
|
||||
if (avatarBone != null && unassigned.Contains(avatarBone))
|
||||
{
|
||||
mappings[child] = avatarBone;
|
||||
unassigned.Remove(avatarBone);
|
||||
lcNameToXform.Remove(NormalizeName(avatarBone.gameObject.name));
|
||||
isMapped = true;
|
||||
} else {
|
||||
bodyBones = new List<HumanBodyBones> { outfitHumanoidBone };
|
||||
}
|
||||
} else {
|
||||
bodyBones = new List<HumanBodyBones>() { outfitHumanoidBone };
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMapped && bodyBones == null && !NameToBoneMap.TryGetValue(
|
||||
NormalizeName(targetObjectName), out bodyBones))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var otherName in bodyBones.SelectMany(bone => BoneToNameMap[bone]))
|
||||
if (!isMapped)
|
||||
{
|
||||
if (lcNameToXform.TryGetValue(otherName, out var targetObject))
|
||||
foreach (var bodyBone in bodyBones)
|
||||
{
|
||||
mappings[child] = targetObject;
|
||||
unassigned.Remove(targetObject);
|
||||
lcNameToXform.Remove(otherName.ToLowerInvariant());
|
||||
break;
|
||||
if (avatarAnimator != null)
|
||||
{
|
||||
var avatarBone = avatarAnimator.GetBoneTransform(bodyBone);
|
||||
if (avatarBone != null && unassigned.Contains(avatarBone))
|
||||
{
|
||||
mappings[child] = avatarBone;
|
||||
unassigned.Remove(avatarBone);
|
||||
lcNameToXform.Remove(NormalizeName(avatarBone.gameObject.name));
|
||||
isMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMapped)
|
||||
{
|
||||
foreach (var otherName in bodyBones.SelectMany(bone => BoneToNameMap[bone]))
|
||||
{
|
||||
if (lcNameToXform.TryGetValue(otherName, out var targetObject))
|
||||
{
|
||||
mappings[child] = targetObject;
|
||||
unassigned.Remove(targetObject);
|
||||
lcNameToXform.Remove(otherName.ToLowerInvariant());
|
||||
isMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,7 +440,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return mappings;
|
||||
}
|
||||
|
||||
internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List<Transform> skipped = null)
|
||||
internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List<Transform> skipped = null, Dictionary<Transform, HumanBodyBones> outfitHumanoidBones = null, Animator avatarAnimator = null)
|
||||
{
|
||||
var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarTransformInParents(config.transform));
|
||||
if (target == null) return;
|
||||
@ -399,7 +451,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
void Traverse(Transform src, Transform dst)
|
||||
{
|
||||
var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject, skipped: skipped);
|
||||
var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject, skipped: skipped, outfitHumanoidBones: outfitHumanoidBones, avatarAnimator: avatarAnimator);
|
||||
|
||||
foreach (var pair in mappings)
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (target.mergeTargetObject != null && priorMergeTarget == null
|
||||
if (target.mergeTargetObject != null && priorMergeTarget != target.mergeTargetObject
|
||||
&& string.IsNullOrEmpty(target.prefix)
|
||||
&& string.IsNullOrEmpty(target.suffix))
|
||||
{
|
||||
@ -115,7 +115,27 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
if (GUILayout.Button(G("merge_armature.adjust_names")))
|
||||
{
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(target);
|
||||
var avatarRoot = RuntimeUtil.FindAvatarTransformInParents(target.mergeTarget.Get(target).transform);
|
||||
var avatarAnimator = avatarRoot != null ? avatarRoot.GetComponent<Animator>() : null;
|
||||
|
||||
// Search Outfit Root Animator
|
||||
var outfitRoot = ((ModularAvatarMergeArmature)serializedObject.targetObject).transform;
|
||||
Animator outfitAnimator = null;
|
||||
while (outfitRoot != null)
|
||||
{
|
||||
if (outfitRoot == avatarRoot)
|
||||
{
|
||||
outfitAnimator = null;
|
||||
break;
|
||||
}
|
||||
outfitAnimator = outfitRoot.GetComponent<Animator>();
|
||||
if (outfitAnimator != null && outfitAnimator.isHuman) break;
|
||||
outfitAnimator = null;
|
||||
outfitRoot = outfitRoot.parent;
|
||||
}
|
||||
|
||||
var outfitHumanoidBones = SetupOutfit.GetOutfitHumanoidBones(outfitRoot, outfitAnimator);
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(target, outfitHumanoidBones: outfitHumanoidBones, avatarAnimator: avatarAnimator);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
using static nadena.dev.modular_avatar.core.editor.Localization;
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -172,8 +173,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(merge);
|
||||
|
||||
var outfitAnimator = outfitRoot.GetComponent<Animator>();
|
||||
var outfitHumanoidBones = GetOutfitHumanoidBones(outfitRoot.transform, outfitAnimator);
|
||||
var avatarAnimator = avatarRoot.GetComponent<Animator>();
|
||||
List<Transform> subRoots = new List<Transform>();
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(merge, skipped: subRoots);
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(merge, skipped: subRoots, outfitHumanoidBones: outfitHumanoidBones, avatarAnimator: avatarAnimator);
|
||||
|
||||
// If the outfit has an UpperChest bone but the avatar doesn't, add an additional MergeArmature to
|
||||
// help with this
|
||||
@ -218,7 +222,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
outfitArmature.name += ".1";
|
||||
|
||||
// Also make sure to refresh the avatar's animator humanoid bone cache.
|
||||
var avatarAnimator = avatarRoot.GetComponent<Animator>();
|
||||
var humanDescription = avatarAnimator.avatar;
|
||||
avatarAnimator.avatar = null;
|
||||
// ReSharper disable once Unity.InefficientPropertyAccess
|
||||
@ -274,6 +277,37 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
internal static Dictionary<Transform, HumanBodyBones> GetOutfitHumanoidBones(Transform outfitRoot, Animator outfitAnimator)
|
||||
{
|
||||
if (outfitAnimator != null)
|
||||
{
|
||||
var hipsCheck = outfitAnimator.isHuman ? outfitAnimator.GetBoneTransform(HumanBodyBones.Hips) : null;
|
||||
if (hipsCheck != null && hipsCheck.parent == outfitRoot)
|
||||
{
|
||||
// Sometimes broken rigs can have the hips as a direct child of the root, instead of having
|
||||
// an intermediate Armature object. We do not currently support this kind of rig, and so we'll
|
||||
// assume the outfit's humanoid rig is broken and move on to heuristic matching.
|
||||
outfitAnimator = null;
|
||||
} else if (hipsCheck == null) {
|
||||
outfitAnimator = null;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<Transform, HumanBodyBones> outfitHumanoidBones = null;
|
||||
if (outfitAnimator != null)
|
||||
{
|
||||
outfitHumanoidBones = new Dictionary<Transform, HumanBodyBones>();
|
||||
foreach (HumanBodyBones boneIndex in Enum.GetValues(typeof(HumanBodyBones)))
|
||||
{
|
||||
var bone = boneIndex != HumanBodyBones.LastBone ? outfitAnimator.GetBoneTransform(boneIndex) : null;
|
||||
if (bone == null) continue;
|
||||
outfitHumanoidBones[bone] = boneIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return outfitHumanoidBones;
|
||||
}
|
||||
|
||||
internal static void FixAPose(GameObject avatarRoot, Transform outfitArmature, bool strictMode = true)
|
||||
{
|
||||
var mergeArmature = outfitArmature.GetComponent<ModularAvatarMergeArmature>();
|
||||
@ -540,6 +574,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
var hipsCandidates = new List<string>();
|
||||
var hipsExtraCandidateRoots = new List<Transform>();
|
||||
|
||||
if (outfitHips == null)
|
||||
{
|
||||
@ -548,6 +583,23 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
foreach (Transform child in outfitRoot.transform)
|
||||
{
|
||||
foreach (Transform tempHip in child)
|
||||
{
|
||||
if (tempHip.name.Contains(avatarHips.name))
|
||||
{
|
||||
outfitHips = tempHip.gameObject;
|
||||
// Prefer the first hips we find
|
||||
break;
|
||||
}
|
||||
hipsExtraCandidateRoots.Add(tempHip);
|
||||
}
|
||||
|
||||
if (outfitHips != null) return true; // found an exact match, bail outgit
|
||||
}
|
||||
|
||||
// Sometimes, Hips is in deeper place(like root -> Armature -> Armature 1 -> Hips).
|
||||
foreach (Transform extraCandidateRoot in hipsExtraCandidateRoots)
|
||||
{
|
||||
foreach (Transform tempHip in extraCandidateRoot)
|
||||
{
|
||||
if (tempHip.name.Contains(avatarHips.name))
|
||||
{
|
||||
@ -561,6 +613,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
hipsCandidates.Add(avatarHips.name);
|
||||
hipsExtraCandidateRoots = new List<Transform>();
|
||||
|
||||
// If that doesn't work out, we'll check for heuristic bone mapper mappings.
|
||||
foreach (var hbm in HeuristicBoneMapper.BoneToNameMap[HumanBodyBones.Hips])
|
||||
@ -581,6 +634,25 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
outfitHips = tempHip.gameObject;
|
||||
}
|
||||
hipsExtraCandidateRoots.Add(tempHip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outfitHips == null)
|
||||
{
|
||||
// Sometimes, Hips is in deeper place(like root -> Armature -> Armature 1 -> Hips).
|
||||
foreach (Transform extraCandidateRoot in hipsExtraCandidateRoots)
|
||||
{
|
||||
foreach (Transform tempHip in extraCandidateRoot)
|
||||
{
|
||||
foreach (var candidate in hipsCandidates)
|
||||
{
|
||||
if (HeuristicBoneMapper.NormalizeName(tempHip.name).Contains(candidate))
|
||||
{
|
||||
outfitHips = tempHip.gameObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,9 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
public bool mangleNames = true;
|
||||
|
||||
// Inserted from HeuristicBoneMapper(Editor Assembly) with InitializeOnLoadMethod
|
||||
// We use raw `boneNamePatterns` instead of `BoneToNameMap` because BoneToNameMap requires matching with normalized bone name, but normalizing makes raw prefix/suffix unavailable.
|
||||
internal static string[][] boneNamePatterns;
|
||||
private ArmatureLockController _lockController;
|
||||
|
||||
internal Transform MapBone(Transform bone)
|
||||
@ -216,14 +219,30 @@ namespace nadena.dev.modular_avatar.core
|
||||
// GameObject we're attached to.
|
||||
var baseName = hips.name;
|
||||
var mergeName = transform.GetChild(0).name;
|
||||
var isInferred = false;
|
||||
|
||||
var prefixLength = mergeName.IndexOf(baseName, StringComparison.InvariantCulture);
|
||||
if (prefixLength < 0) return;
|
||||
foreach (var hipNameCandidate in boneNamePatterns[(int)HumanBodyBones.Hips])
|
||||
{
|
||||
var prefixLength = mergeName.IndexOf(hipNameCandidate, StringComparison.InvariantCultureIgnoreCase);
|
||||
if (prefixLength < 0) continue;
|
||||
|
||||
var suffixLength = mergeName.Length - prefixLength - baseName.Length;
|
||||
var suffixLength = mergeName.Length - prefixLength - hipNameCandidate.Length;
|
||||
|
||||
prefix = mergeName.Substring(0, prefixLength);
|
||||
suffix = mergeName.Substring(mergeName.Length - suffixLength);
|
||||
prefix = mergeName.Substring(0, prefixLength);
|
||||
suffix = mergeName.Substring(mergeName.Length - suffixLength);
|
||||
isInferred = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isInferred) { // Also check with old method as fallback
|
||||
var prefixLength = mergeName.IndexOf(baseName, StringComparison.InvariantCulture);
|
||||
if (prefixLength < 0) return;
|
||||
|
||||
var suffixLength = mergeName.Length - prefixLength - baseName.Length;
|
||||
|
||||
prefix = mergeName.Substring(0, prefixLength);
|
||||
suffix = mergeName.Substring(mergeName.Length - suffixLength);
|
||||
}
|
||||
|
||||
if (prefix == "J_Bip_C_")
|
||||
{
|
||||
@ -242,4 +261,4 @@ namespace nadena.dev.modular_avatar.core
|
||||
if (mergeTarget != null) yield return mergeTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ using nadena.dev.modular_avatar.core.editor;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
public class PreferFirstHipsMatch : TestBase
|
||||
public class HipsMatchTest : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void SetupHeuristicPrefersFirstHipsMatch()
|
||||
@ -21,7 +21,28 @@ public class PreferFirstHipsMatch : TestBase
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "Armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "Hips");
|
||||
|
||||
|
||||
Assert.IsTrue(SetupOutfit.FindBones(outfit, out var det_av_root, out var det_av_hips, out var det_outfit_hips));
|
||||
Assert.AreSame(root, det_av_root);
|
||||
Assert.AreSame(root_hips, det_av_hips);
|
||||
Assert.AreSame(outfit_hips, det_outfit_hips);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutfitDeepHipsMatch()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_hips = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips).gameObject;
|
||||
root_hips.name = "hip";
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_armature2 = CreateChild(outfit_armature, "armature2");
|
||||
var outfit_hips = CreateChild(outfit_armature2, "hips");
|
||||
|
||||
Assert.IsTrue(SetupOutfit.FindBones(outfit, out var det_av_root, out var det_av_hips, out var det_outfit_hips));
|
||||
Assert.AreSame(root, det_av_root);
|
||||
Assert.AreSame(root_hips, det_av_hips);
|
108
UnitTests~/EasySetupOutfit/InferPrefixSuffixTest.cs
Normal file
108
UnitTests~/EasySetupOutfit/InferPrefixSuffixTest.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using modular_avatar_tests;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
public class InferPrefixSuffixTest : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void TestNoPrefixSuffix()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_hips = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
|
||||
root_hips.name = "hip";
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "hips");
|
||||
|
||||
var outfit_mama = outfit_armature.AddComponent<ModularAvatarMergeArmature>();
|
||||
outfit_mama.mergeTarget = new AvatarObjectReference();
|
||||
outfit_mama.mergeTarget.referencePath = RuntimeUtil.RelativePath(root, root_hips.parent.gameObject);
|
||||
outfit_mama.LockMode = ArmatureLockMode.BaseToMerge;
|
||||
|
||||
outfit_mama.InferPrefixSuffix();
|
||||
|
||||
Assert.AreEqual("", outfit_mama.prefix);
|
||||
Assert.AreEqual("", outfit_mama.suffix);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifferentHipsName()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_hips = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
|
||||
root_hips.name = "hip";
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "pre_Hips.suf");
|
||||
|
||||
var outfit_mama = outfit_armature.AddComponent<ModularAvatarMergeArmature>();
|
||||
outfit_mama.mergeTarget = new AvatarObjectReference();
|
||||
outfit_mama.mergeTarget.referencePath = RuntimeUtil.RelativePath(root, root_hips.parent.gameObject);
|
||||
outfit_mama.LockMode = ArmatureLockMode.BaseToMerge;
|
||||
|
||||
outfit_mama.InferPrefixSuffix();
|
||||
|
||||
Assert.AreEqual("pre_", outfit_mama.prefix);
|
||||
Assert.AreEqual(".suf", outfit_mama.suffix);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSameHipsName_Success()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_hips = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
|
||||
root_hips.name = "TEST_HI";
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "pre_TEST_HI2.suf"); // Make it a little bit different name to confirm it matches the current implementation
|
||||
|
||||
var outfit_mama = outfit_armature.AddComponent<ModularAvatarMergeArmature>();
|
||||
outfit_mama.mergeTarget = new AvatarObjectReference();
|
||||
outfit_mama.mergeTarget.referencePath = RuntimeUtil.RelativePath(root, root_hips.parent.gameObject);
|
||||
outfit_mama.LockMode = ArmatureLockMode.BaseToMerge;
|
||||
|
||||
outfit_mama.InferPrefixSuffix();
|
||||
|
||||
Assert.AreEqual("pre_", outfit_mama.prefix);
|
||||
Assert.AreEqual("2.suf", outfit_mama.suffix);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSameHipsName_Fail()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_hips = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
|
||||
root_hips.name = "TE_HIPS_ST";
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "pre_TE_HIPS_ST.suf");
|
||||
|
||||
var outfit_mama = outfit_armature.AddComponent<ModularAvatarMergeArmature>();
|
||||
outfit_mama.mergeTarget = new AvatarObjectReference();
|
||||
outfit_mama.mergeTarget.referencePath = RuntimeUtil.RelativePath(root, root_hips.parent.gameObject);
|
||||
outfit_mama.LockMode = ArmatureLockMode.BaseToMerge;
|
||||
|
||||
outfit_mama.InferPrefixSuffix();
|
||||
|
||||
// Current(v1.10.x) InferPrefixSuffix fail to infer prefix/suffix when avatar has unique prefix/suffix and outfit has their name
|
||||
Assert.AreNotEqual("pre_", outfit_mama.prefix);
|
||||
Assert.AreNotEqual(".suf", outfit_mama.suffix);
|
||||
}
|
||||
}
|
11
UnitTests~/EasySetupOutfit/InferPrefixSuffixTest.cs.meta
Normal file
11
UnitTests~/EasySetupOutfit/InferPrefixSuffixTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 426df05704d87424baeb85496181868d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
71
UnitTests~/EasySetupOutfit/SetupOutfitRenameTest.cs
Normal file
71
UnitTests~/EasySetupOutfit/SetupOutfitRenameTest.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using modular_avatar_tests;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
public class SetupOutfitRenameTest : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void TestSetupHumanoidOutfit()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_chest = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Chest);
|
||||
|
||||
var outfit = CreateCommonPrefab("shapell.fbx");
|
||||
outfit.transform.SetParent(root.transform);
|
||||
var outfit_chest = outfit.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Chest);
|
||||
outfit_chest.name = "c";
|
||||
|
||||
SetupOutfit.SetupOutfitUI(outfit);
|
||||
|
||||
Assert.AreEqual(root_chest.name, outfit_chest.name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetupUpperChestOutfit()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_armature = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips).parent.gameObject;
|
||||
var root_chest = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Chest).gameObject;
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "hips");
|
||||
var outfit_spine = CreateChild(outfit_hips, "spine");
|
||||
var outfit_chest = CreateChild(outfit_spine, "chest");
|
||||
var outfit_upperchest = CreateChild(outfit_chest, "upperchest");
|
||||
|
||||
SetupOutfit.SetupOutfitUI(outfit);
|
||||
|
||||
Assert.AreSame(root_armature, outfit_armature.GetComponent<ModularAvatarMergeArmature>().mergeTargetObject);
|
||||
Assert.AreSame(root_chest, outfit_upperchest.GetComponent<ModularAvatarMergeArmature>().mergeTargetObject);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetupSetupedOutfit()
|
||||
{
|
||||
var root = CreateCommonPrefab("shapell.fbx");
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
root.AddComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
|
||||
#endif
|
||||
var root_hips = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips);
|
||||
var root_armature = root.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Hips).parent.gameObject;
|
||||
|
||||
var outfit = CreateChild(root, "Outfit");
|
||||
var outfit_armature = CreateChild(outfit, "armature");
|
||||
var outfit_hips = CreateChild(outfit_armature, "HIP");
|
||||
outfit_armature.AddComponent<ModularAvatarMergeArmature>();
|
||||
|
||||
SetupOutfit.SetupOutfitUI(outfit);
|
||||
|
||||
Assert.AreEqual(root_armature, outfit_armature.GetComponent<ModularAvatarMergeArmature>().mergeTargetObject);
|
||||
Assert.AreEqual(root_hips.name, outfit_hips.name);
|
||||
}
|
||||
}
|
11
UnitTests~/EasySetupOutfit/SetupOutfitRenameTest.cs.meta
Normal file
11
UnitTests~/EasySetupOutfit/SetupOutfitRenameTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89b8a54f81c4e7244a858b30825de67c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user