Merge branch 'main' into enhance_EasySetupOutfit_humanoid

This commit is contained in:
Sayamame-beans 2024-10-20 13:56:05 +09:00 committed by GitHub
commit 05b97e74e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 311 additions and 88 deletions

View File

@ -4,7 +4,7 @@
"version": "3.7.0" "version": "3.7.0"
}, },
"nadena.dev.ndmf": { "nadena.dev.ndmf": {
"version": "1.4.0" "version": "1.5.6"
} }
}, },
"locked": { "locked": {
@ -19,7 +19,7 @@
"dependencies": {} "dependencies": {}
}, },
"nadena.dev.ndmf": { "nadena.dev.ndmf": {
"version": "1.5.3" "version": "1.5.6"
} }
} }
} }

View File

@ -7,7 +7,9 @@ using nadena.dev.ndmf;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
#if MA_VRCSDK3_AVATARS
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
#endif
#endregion #endregion
@ -89,12 +91,14 @@ namespace nadena.dev.modular_avatar.animation
// HACK: This is a temporary crutch until we rework the entire animator services system // HACK: This is a temporary crutch until we rework the entire animator services system
public void AddPropertyDefinition(AnimatorControllerParameter paramDef) public void AddPropertyDefinition(AnimatorControllerParameter paramDef)
{ {
#if MA_VRCSDK3_AVATARS
var fx = (AnimatorController) var fx = (AnimatorController)
_context.AvatarDescriptor.baseAnimationLayers _context.AvatarDescriptor.baseAnimationLayers
.First(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX) .First(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX)
.animatorController; .animatorController;
fx.parameters = fx.parameters.Concat(new[] { paramDef }).ToArray(); fx.parameters = fx.parameters.Concat(new[] { paramDef }).ToArray();
#endif
} }
public string GetActiveSelfProxy(GameObject obj) public string GetActiveSelfProxy(GameObject obj)

View File

@ -1,9 +1,12 @@
using System.Linq; #if MA_VRCSDK3_AVATARS
using System.Linq;
using nadena.dev.modular_avatar.core.editor;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using UnityEditor; using UnityEditor;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.Components;
using BuildContext = nadena.dev.ndmf.BuildContext;
namespace nadena.dev.modular_avatar.animation namespace nadena.dev.modular_avatar.animation
{ {
@ -23,11 +26,16 @@ namespace nadena.dev.modular_avatar.animation
if (fx == null) return; if (fx == null) return;
var nullMotion = new AnimationClip();
nullMotion.name = "NullMotion";
var blendTree = new BlendTree(); var blendTree = new BlendTree();
blendTree.blendType = BlendTreeType.Direct; blendTree.blendType = BlendTreeType.Direct;
blendTree.useAutomaticThresholds = false; blendTree.useAutomaticThresholds = false;
blendTree.children = asc.BoundReadableProperties.Select(GenerateDelayChild).ToArray(); blendTree.children = asc.BoundReadableProperties
.Select(prop => GenerateDelayChild(nullMotion, prop))
.ToArray();
var asm = new AnimatorStateMachine(); var asm = new AnimatorStateMachine();
var state = new AnimatorState(); var state = new AnimatorState();
@ -52,9 +60,24 @@ namespace nadena.dev.modular_avatar.animation
defaultWeight = 1, defaultWeight = 1,
blendingMode = AnimatorLayerBlendingMode.Override blendingMode = AnimatorLayerBlendingMode.Override
}).ToArray(); }).ToArray();
// Ensure the initial state of readable props matches the actual state of the gameobject
var parameters = fx.parameters;
var paramToIndex = parameters.Select((p, i) => (p, i)).ToDictionary(x => x.p.name, x => x.i);
foreach (var (binding, prop) in asc.BoundReadableProperties)
{
var obj = asc.PathMappings.PathToObject(binding.path);
if (obj != null && paramToIndex.TryGetValue(prop, out var index))
{
parameters[index].defaultFloat = obj.activeSelf ? 1 : 0;
}
}
fx.parameters = parameters;
} }
private ChildMotion GenerateDelayChild((EditorCurveBinding, string) binding) private ChildMotion GenerateDelayChild(Motion nullMotion, (EditorCurveBinding, string) binding)
{ {
var ecb = binding.Item1; var ecb = binding.Item1;
var prop = binding.Item2; var prop = binding.Item2;
@ -64,12 +87,43 @@ namespace nadena.dev.modular_avatar.animation
curve.AddKey(0, 1); curve.AddKey(0, 1);
AnimationUtility.SetEditorCurve(motion, ecb, curve); AnimationUtility.SetEditorCurve(motion, ecb, curve);
// Occasionally, we'll have a very small value pop up, probably due to FP errors.
// To correct for this, instead of directly using the property in the direct blend tree,
// we'll use a 1D blend tree to give ourselves a buffer.
var bufferBlendTree = new BlendTree();
bufferBlendTree.blendType = BlendTreeType.Simple1D;
bufferBlendTree.useAutomaticThresholds = false;
bufferBlendTree.blendParameter = prop;
bufferBlendTree.children = new[]
{
new ChildMotion
{
motion = nullMotion,
timeScale = 1,
threshold = 0
},
new ChildMotion
{
motion = nullMotion,
timeScale = 1,
threshold = 0.01f
},
new ChildMotion
{
motion = motion,
timeScale = 1,
threshold = 1
}
};
return new ChildMotion return new ChildMotion
{ {
motion = motion, motion = bufferBlendTree,
directBlendParameter = prop, directBlendParameter = MergeBlendTreePass.ALWAYS_ONE,
timeScale = 1 timeScale = 1
}; };
} }
} }
} }
#endif

View File

@ -368,6 +368,7 @@ namespace nadena.dev.modular_avatar.animation
} }
Profiler.EndSample(); Profiler.EndSample();
#if MA_VRCSDK3_AVATARS
var layers = context.AvatarDescriptor.baseAnimationLayers var layers = context.AvatarDescriptor.baseAnimationLayers
.Concat(context.AvatarDescriptor.specialAnimationLayers); .Concat(context.AvatarDescriptor.specialAnimationLayers);
@ -383,6 +384,7 @@ namespace nadena.dev.modular_avatar.animation
ApplyMappingsToAvatarMask(acLayer.avatarMask); ApplyMappingsToAvatarMask(acLayer.avatarMask);
} }
Profiler.EndSample(); Profiler.EndSample();
#endif
Profiler.EndSample(); Profiler.EndSample();
} }

View File

@ -1,4 +1,5 @@
#region #if MA_VRCSDK3_AVATARS
#region
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -57,4 +58,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using UnityEditor; #if MA_VRCSDK3_AVATARS
using UnityEditor;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
@ -45,3 +46,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
#endif

View File

@ -368,7 +368,7 @@ namespace nadena.dev.modular_avatar.core.editor
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
Localization.ShowLanguageUI(); ShowLanguageUI();
} }
private string ObjectHierarchyOrder(Component arg) private string ObjectHierarchyOrder(Component arg)
@ -415,6 +415,9 @@ namespace nadena.dev.modular_avatar.core.editor
var group = installer.gameObject.AddComponent<ModularAvatarMenuGroup>(); var group = installer.gameObject.AddComponent<ModularAvatarMenuGroup>();
var menuRoot = new GameObject(); var menuRoot = new GameObject();
menuRoot.name = "Menu"; menuRoot.name = "Menu";
group.targetObject = menuRoot;
Undo.RegisterCreatedObjectUndo(menuRoot, "Extract menu"); Undo.RegisterCreatedObjectUndo(menuRoot, "Extract menu");
menuRoot.transform.SetParent(group.transform, false); menuRoot.transform.SetParent(group.transform, false);
foreach (var control in menu.controls) foreach (var control in menu.controls)

View File

@ -301,10 +301,9 @@ namespace nadena.dev.modular_avatar.core.editor
EditorGUILayout.BeginVertical(); EditorGUILayout.BeginVertical();
if (_type.hasMultipleDifferentValues) return; if (_type.hasMultipleDifferentValues) return;
VRCExpressionsMenu.Control.ControlType type = var controlTypeArray = Enum.GetValues(typeof(VRCExpressionsMenu.Control.ControlType));
(VRCExpressionsMenu.Control.ControlType) Enum var index = Math.Clamp(_type.enumValueIndex, 0, controlTypeArray.Length - 1);
.GetValues(typeof(VRCExpressionsMenu.Control.ControlType)) var type = (VRCExpressionsMenu.Control.ControlType)controlTypeArray.GetValue(index);
.GetValue(_type.enumValueIndex);
switch (type) switch (type)
{ {

View File

@ -1,4 +1,5 @@
using nadena.dev.modular_avatar.ui; #if MA_VRCSDK3_AVATARS
using nadena.dev.modular_avatar.ui;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using VRC.SDK3.Avatars.ScriptableObjects; using VRC.SDK3.Avatars.ScriptableObjects;
@ -62,4 +63,5 @@ namespace nadena.dev.modular_avatar.core.editor
Undo.RegisterCreatedObjectUndo(toggle, "Create Toggle"); Undo.RegisterCreatedObjectUndo(toggle, "Create Toggle");
} }
} }
} }
#endif

View File

@ -20,7 +20,8 @@ namespace nadena.dev.modular_avatar.core.editor
private readonly DropdownField _boolField; private readonly DropdownField _boolField;
private ParameterSyncType _syncType; private ParameterSyncType _syncType;
private bool _hasInitialBinding;
public DefaultValueField() public DefaultValueField()
{ {
// Hidden binding elements // Hidden binding elements
@ -57,28 +58,39 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
_numberField.style.display = DisplayStyle.Flex; _numberField.style.display = DisplayStyle.Flex;
_boolField.style.display = DisplayStyle.None; _boolField.style.display = DisplayStyle.None;
OnUpdateNumberValue(_numberField.value); OnUpdateNumberValue(_numberField.value, true);
} }
else else
{ {
_numberField.style.display = DisplayStyle.None; _numberField.style.display = DisplayStyle.None;
_boolField.style.display = DisplayStyle.Flex; _boolField.style.display = DisplayStyle.Flex;
OnUpdateBoolValue(_boolField.value); OnUpdateBoolValue(_boolField.value, true);
} }
} }
private void OnUpdateNumberValue(string value) private void OnUpdateNumberValue(string value, bool implicitUpdate = false)
{ {
// Upon initial creation, sometimes the OnUpdateSyncType fires before we receive the initial value event.
// In this case, suppress the update to avoid losing data.
if (implicitUpdate && !_hasInitialBinding) return;
var theValue = _defaultValueField.value;
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
_defaultValueField.value = 0; if (!implicitUpdate)
{
_defaultValueField.value = 0;
}
theValue = _defaultValueField.value;
_hasExplicitDefaultValueField.value = false; _hasExplicitDefaultValueField.value = false;
} }
else if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) else if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed)
&& !float.IsNaN(parsed) && !float.IsNaN(parsed)
&& !float.IsInfinity(parsed)) && !float.IsInfinity(parsed))
{ {
_defaultValueField.value = _syncType switch theValue = _defaultValueField.value = _syncType switch
{ {
ParameterSyncType.Int => Mathf.FloorToInt(Mathf.Clamp(parsed, 0, 255)), ParameterSyncType.Int => Mathf.FloorToInt(Mathf.Clamp(parsed, 0, 255)),
ParameterSyncType.Float => Mathf.Clamp(parsed, -1, 1), ParameterSyncType.Float => Mathf.Clamp(parsed, -1, 1),
@ -88,11 +100,15 @@ namespace nadena.dev.modular_avatar.core.editor
_hasExplicitDefaultValueField.value = true; _hasExplicitDefaultValueField.value = true;
} }
UpdateVisibleField(_defaultValueField.value, _hasExplicitDefaultValueField.value); UpdateVisibleField(theValue, _hasExplicitDefaultValueField.value);
} }
private void OnUpdateBoolValue(string value) private void OnUpdateBoolValue(string value, bool implicitUpdate = false)
{ {
// Upon initial creation, sometimes the OnUpdateSyncType fires before we receive the initial value event.
// In this case, suppress the update to avoid losing data.
if (implicitUpdate && !_hasInitialBinding) return;
_defaultValueField.value = value == V_True ? 1 : 0; _defaultValueField.value = value == V_True ? 1 : 0;
_hasExplicitDefaultValueField.value = value != V_None; _hasExplicitDefaultValueField.value = value != V_None;
@ -101,6 +117,8 @@ namespace nadena.dev.modular_avatar.core.editor
private void UpdateVisibleField(float value, bool hasExplicitValue) private void UpdateVisibleField(float value, bool hasExplicitValue)
{ {
_hasInitialBinding = true;
if (hasExplicitValue || Mathf.Abs(value) > 0.0000001) if (hasExplicitValue || Mathf.Abs(value) > 0.0000001)
{ {
_numberField.SetValueWithoutNotify(value.ToString(CultureInfo.InvariantCulture)); _numberField.SetValueWithoutNotify(value.ToString(CultureInfo.InvariantCulture));

View File

@ -119,9 +119,11 @@ namespace nadena.dev.modular_avatar.core.editor
internal static VRCExpressionsMenu.Control CloneControl(VRCExpressionsMenu.Control c) internal static VRCExpressionsMenu.Control CloneControl(VRCExpressionsMenu.Control c)
{ {
var type = c.type != 0 ? c.type : VRCExpressionsMenu.Control.ControlType.Button;
return new VRCExpressionsMenu.Control() return new VRCExpressionsMenu.Control()
{ {
type = c.type, type = type,
name = c.name, name = c.name,
icon = c.icon, icon = c.icon,
parameter = new VRCExpressionsMenu.Control.Parameter() { name = c.parameter?.name }, parameter = new VRCExpressionsMenu.Control.Parameter() { name = c.parameter?.name },

View File

@ -57,26 +57,29 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
#endif #endif
seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 => seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 =>
{ {
#if MA_VRCSDK3_AVATARS
seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute())
.PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), new MaterialSetterPreview()); .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), new MaterialSetterPreview());
#if MA_VRCSDK3_AVATARS
// TODO: We currently run this above MergeArmaturePlugin, because Merge Armature might destroy // TODO: We currently run this above MergeArmaturePlugin, because Merge Armature might destroy
// game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature // game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature
// to retain those objects? maybe? // to retain those objects? maybe?
seq.Run(MenuInstallPluginPass.Instance); seq.Run(MenuInstallPluginPass.Instance);
#endif #endif
seq.Run(MergeArmaturePluginPass.Instance); seq.Run(MergeArmaturePluginPass.Instance);
seq.Run(BoneProxyPluginPass.Instance); seq.Run(BoneProxyPluginPass.Instance);
#if MA_VRCSDK3_AVATARS
seq.Run(VisibleHeadAccessoryPluginPass.Instance); seq.Run(VisibleHeadAccessoryPluginPass.Instance);
#endif
seq.Run("World Fixed Object", seq.Run("World Fixed Object",
ctx => new WorldFixedObjectProcessor().Process(ctx) ctx => new WorldFixedObjectProcessor().Process(ctx)
); );
seq.Run(ReplaceObjectPluginPass.Instance); seq.Run(ReplaceObjectPluginPass.Instance);
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
seq.Run(BlendshapeSyncAnimationPluginPass.Instance); seq.Run(BlendshapeSyncAnimationPluginPass.Instance);
#endif
seq.Run(GameObjectDelayDisablePass.Instance); seq.Run(GameObjectDelayDisablePass.Instance);
#endif
seq.Run(ConstraintConverterPass.Instance); seq.Run(ConstraintConverterPass.Instance);
}); });
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
@ -213,6 +216,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
} }
} }
#if MA_VRCSDK3_AVATARS
class VisibleHeadAccessoryPluginPass : MAPass<VisibleHeadAccessoryPluginPass> class VisibleHeadAccessoryPluginPass : MAPass<VisibleHeadAccessoryPluginPass>
{ {
protected override void Execute(ndmf.BuildContext context) protected override void Execute(ndmf.BuildContext context)
@ -220,6 +224,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
new VisibleHeadAccessoryProcessor(MAContext(context)).Process(); new VisibleHeadAccessoryProcessor(MAContext(context)).Process();
} }
} }
#endif
class ReplaceObjectPluginPass : MAPass<ReplaceObjectPluginPass> class ReplaceObjectPluginPass : MAPass<ReplaceObjectPluginPass>
{ {

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; #if MA_VRCSDK3_AVATARS
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using nadena.dev.ndmf.preview; using nadena.dev.ndmf.preview;
@ -346,4 +347,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; #if MA_VRCSDK3_AVATARS
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.animation;
@ -128,7 +129,7 @@ namespace nadena.dev.modular_avatar.core.editor
foreach (var cond in rule.ControllingConditions) foreach (var cond in rule.ControllingConditions)
{ {
var paramName = cond.Parameter; var paramName = cond.Parameter;
if (ForcePropertyOverrides.TryGetValue(paramName, out var value)) if (ForcePropertyOverrides?.TryGetValue(paramName, out var value) == true)
{ {
cond.InitialValue = value; cond.InitialValue = value;
} }
@ -304,4 +305,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
#region #if MA_VRCSDK3_AVATARS
#region
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -613,3 +614,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using nadena.dev.ndmf; #if MA_VRCSDK3_AVATARS
using nadena.dev.ndmf;
using UnityEditor.Animations; using UnityEditor.Animations;
using UnityEngine; using UnityEngine;
@ -53,4 +54,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; #if MA_VRCSDK3_AVATARS
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -144,4 +145,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using System; #if MA_VRCSDK3_AVATARS
using System;
using System.Collections.Generic; using System.Collections.Generic;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.preview; using nadena.dev.ndmf.preview;
@ -70,4 +71,5 @@ namespace nadena.dev.modular_avatar.core.editor
return _context.Observe(mami, _ => mami.isDefault); return _context.Observe(mami, _ => mami.isDefault);
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; #if MA_VRCSDK3_AVATARS
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -104,4 +105,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; #if MA_VRCSDK3_AVATARS
using System.Collections.Generic;
using System.Linq; using System.Linq;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using UnityEngine; using UnityEngine;
@ -236,4 +237,5 @@ namespace nadena.dev.modular_avatar.core.editor
}; };
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
#region #if MA_VRCSDK3_AVATARS
#region
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -294,4 +295,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -1,4 +1,5 @@
using System; #if MA_VRCSDK3_AVATARS
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
@ -637,4 +638,5 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
ve_inactive.style.display = activeState ? DisplayStyle.None : DisplayStyle.Flex; ve_inactive.style.display = activeState ? DisplayStyle.None : DisplayStyle.Flex;
} }
} }
} }
#endif

View File

@ -1,5 +1,7 @@
using nadena.dev.modular_avatar.core.editor; using nadena.dev.modular_avatar.core.editor;
#if MA_VRCSDK3_AVATARS
using nadena.dev.modular_avatar.core.editor.Simulator; using nadena.dev.modular_avatar.core.editor.Simulator;
#endif
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@ -42,11 +44,13 @@ namespace nadena.dev.modular_avatar.core.editor
private void OpenDebugger() private void OpenDebugger()
{ {
#if MA_VRCSDK3_AVATARS
GameObject target = Selection.activeGameObject; GameObject target = Selection.activeGameObject;
if (ReferenceObject is Component c) target = c.gameObject; if (ReferenceObject is Component c) target = c.gameObject;
else if (ReferenceObject is GameObject go) target = go; else if (ReferenceObject is GameObject go) target = go;
ROSimulator.OpenDebugger(target); ROSimulator.OpenDebugger(target);
#endif
} }
} }
} }

View File

@ -1,4 +1,6 @@
using nadena.dev.modular_avatar.core.editor.Simulator; #if MA_VRCSDK3_AVATARS
using System;
using nadena.dev.modular_avatar.core.editor.Simulator;
using UnityEditor; using UnityEditor;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@ -74,4 +76,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
} }
#endif

View File

@ -146,28 +146,57 @@ namespace nadena.dev.modular_avatar.core.editor
out var avatarRoot, out var avatarHips, out var outfitHips) out var avatarRoot, out var avatarHips, out var outfitHips)
) return; ) return;
Undo.SetCurrentGroupName("Setup Outfit");
var avatarArmature = avatarHips.transform.parent; var avatarArmature = avatarHips.transform.parent;
var outfitArmature = outfitHips.transform.parent; var outfitArmature = outfitHips.transform.parent;
if (outfitArmature.GetComponent<ModularAvatarMergeArmature>() == null) var merge = outfitArmature.GetComponent<ModularAvatarMergeArmature>();
if (merge == null)
{
merge = Undo.AddComponent<ModularAvatarMergeArmature>(outfitArmature.gameObject);
} else {
Undo.RecordObject(merge, "");
}
if (merge.mergeTarget == null || merge.mergeTargetObject == null)
{ {
var merge = Undo.AddComponent<ModularAvatarMergeArmature>(outfitArmature.gameObject);
merge.mergeTarget = new AvatarObjectReference(); merge.mergeTarget = new AvatarObjectReference();
merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject); merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject);
merge.LockMode = ArmatureLockMode.BaseToMerge; merge.LockMode = ArmatureLockMode.BaseToMerge;
}
if (string.IsNullOrEmpty(merge.prefix) && string.IsNullOrEmpty(merge.suffix))
{
merge.InferPrefixSuffix(); merge.InferPrefixSuffix();
}
var outfitAnimator = outfitRoot.GetComponent<Animator>(); PrefabUtility.RecordPrefabInstancePropertyModifications(merge);
var outfitHumanoidBones = GetOutfitHumanoidBones(outfitRoot.transform, outfitAnimator);
var avatarAnimator = avatarRoot.GetComponent<Animator>();
List<Transform> subRoots = new List<Transform>();
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 var outfitAnimator = outfitRoot.GetComponent<Animator>();
// help with this var outfitHumanoidBones = GetOutfitHumanoidBones(outfitRoot.transform, outfitAnimator);
foreach (var subRoot in subRoots) var avatarAnimator = avatarRoot.GetComponent<Animator>();
List<Transform> subRoots = new List<Transform>();
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
foreach (var subRoot in subRoots)
{
var subConfig = subRoot.GetComponent<ModularAvatarMergeArmature>();
var subConfigMangleNames = false;
if (subConfig == null)
{
subConfig = Undo.AddComponent<ModularAvatarMergeArmature>(subRoot.gameObject);
}
else
{
Undo.RecordObject(subConfig, "");
subConfigMangleNames = subConfig.mangleNames;
}
if (subConfig.mergeTarget == null || subConfig.mergeTargetObject == null)
{ {
var subConfig = Undo.AddComponent<ModularAvatarMergeArmature>(subRoot.gameObject);
var parentTransform = subConfig.transform.parent; var parentTransform = subConfig.transform.parent;
var parentConfig = parentTransform.GetComponentInParent<ModularAvatarMergeArmature>(); var parentConfig = parentTransform.GetComponentInParent<ModularAvatarMergeArmature>();
var parentMapping = parentConfig.MapBone(parentTransform); var parentMapping = parentConfig.MapBone(parentTransform);
@ -178,33 +207,47 @@ namespace nadena.dev.modular_avatar.core.editor
subConfig.LockMode = ArmatureLockMode.BaseToMerge; subConfig.LockMode = ArmatureLockMode.BaseToMerge;
subConfig.prefix = merge.prefix; subConfig.prefix = merge.prefix;
subConfig.suffix = merge.suffix; subConfig.suffix = merge.suffix;
subConfig.mangleNames = false; subConfig.mangleNames = subConfigMangleNames;
PrefabUtility.RecordPrefabInstancePropertyModifications(subConfig);
} }
}
var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name); var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name);
if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null) if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null)
{ {
// We have an armature whose names exactly match the root armature - this can cause some serious // We have an armature whose names exactly match the root armature - this can cause some serious
// confusion in Unity's humanoid armature matching system. Fortunately, we can avoid this by // confusion in Unity's humanoid armature matching system. Fortunately, we can avoid this by
// renaming a bone close to the root; this will ensure the number of matching bones is small, and // renaming a bone close to the root; this will ensure the number of matching bones is small, and
// Unity's heuristics (apparently) will choose the base avatar's armature as the "true" armature. // Unity's heuristics (apparently) will choose the base avatar's armature as the "true" armature.
outfitArmature.name += ".1"; outfitArmature.name += ".1";
// Also make sure to refresh the avatar's animator humanoid bone cache. // Also make sure to refresh the avatar's animator humanoid bone cache.
var humanDescription = avatarAnimator.avatar; var humanDescription = avatarAnimator.avatar;
avatarAnimator.avatar = null; avatarAnimator.avatar = null;
// ReSharper disable once Unity.InefficientPropertyAccess // ReSharper disable once Unity.InefficientPropertyAccess
avatarAnimator.avatar = humanDescription; avatarAnimator.avatar = humanDescription;
}
} }
FixAPose(avatarRoot, outfitArmature); FixAPose(avatarRoot, outfitArmature);
var meshSettings = outfitRoot.GetComponent<ModularAvatarMeshSettings>();
var mSInheritProbeAnchor = ModularAvatarMeshSettings.InheritMode.SetOrInherit;
var mSInheritBounds = ModularAvatarMeshSettings.InheritMode.SetOrInherit;
if (outfitRoot != null if (outfitRoot != null
&& outfitRoot.GetComponent<ModularAvatarMeshSettings>() == null && meshSettings == null
&& outfitRoot.GetComponentInParent<ModularAvatarMeshSettings>() == null) && outfitRoot.GetComponentInParent<ModularAvatarMeshSettings>() == null)
{ {
var meshSettings = Undo.AddComponent<ModularAvatarMeshSettings>(outfitRoot.gameObject); meshSettings = Undo.AddComponent<ModularAvatarMeshSettings>(outfitRoot.gameObject);
} else if (outfitRoot != null && meshSettings != null) {
Undo.RecordObject(meshSettings, "");
mSInheritProbeAnchor = meshSettings.InheritProbeAnchor;
mSInheritBounds = meshSettings.InheritBounds;
}
if (meshSettings != null
&& (meshSettings.ProbeAnchor == null || meshSettings.ProbeAnchor.Get(meshSettings) == null
|| meshSettings.RootBone == null || meshSettings.RootBone.Get(meshSettings) == null))
{
Transform rootBone = null, probeAnchor = null; Transform rootBone = null, probeAnchor = null;
Bounds bounds = ModularAvatarMeshSettings.DEFAULT_BOUNDS; Bounds bounds = ModularAvatarMeshSettings.DEFAULT_BOUNDS;
@ -220,8 +263,8 @@ namespace nadena.dev.modular_avatar.core.editor
rootBone = avatarRoot.transform; rootBone = avatarRoot.transform;
} }
meshSettings.InheritProbeAnchor = ModularAvatarMeshSettings.InheritMode.SetOrInherit; meshSettings.InheritProbeAnchor = mSInheritProbeAnchor;
meshSettings.InheritBounds = ModularAvatarMeshSettings.InheritMode.SetOrInherit; meshSettings.InheritBounds = mSInheritBounds;
meshSettings.ProbeAnchor = new AvatarObjectReference(); meshSettings.ProbeAnchor = new AvatarObjectReference();
meshSettings.ProbeAnchor.referencePath = RuntimeUtil.RelativePath(avatarRoot, probeAnchor.gameObject); meshSettings.ProbeAnchor.referencePath = RuntimeUtil.RelativePath(avatarRoot, probeAnchor.gameObject);
@ -229,6 +272,8 @@ namespace nadena.dev.modular_avatar.core.editor
meshSettings.RootBone = new AvatarObjectReference(); meshSettings.RootBone = new AvatarObjectReference();
meshSettings.RootBone.referencePath = RuntimeUtil.RelativePath(avatarRoot, rootBone.gameObject); meshSettings.RootBone.referencePath = RuntimeUtil.RelativePath(avatarRoot, rootBone.gameObject);
meshSettings.Bounds = bounds; meshSettings.Bounds = bounds;
PrefabUtility.RecordPrefabInstancePropertyModifications(meshSettings);
} }
} }

View File

@ -1,4 +1,5 @@
#region #if MA_VRCSDK3_AVATARS
#region
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -251,3 +252,5 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
} }
#endif

View File

@ -19,7 +19,7 @@ namespace nadena.dev.modular_avatar.core
/// initially inactive in the scene (which can have high overhead if the user has a lot of inactive avatars in the /// initially inactive in the scene (which can have high overhead if the user has a lot of inactive avatars in the
/// scene). /// scene).
/// </summary> /// </summary>
[AddComponentMenu("")] [AddComponentMenu("/")]
[ExecuteInEditMode] [ExecuteInEditMode]
[DefaultExecutionOrder(-9998)] [DefaultExecutionOrder(-9998)]
public class Activator : MonoBehaviour, IEditorOnly public class Activator : MonoBehaviour, IEditorOnly
@ -30,7 +30,7 @@ namespace nadena.dev.modular_avatar.core
} }
} }
[AddComponentMenu("")] [AddComponentMenu("/")]
[ExecuteInEditMode] [ExecuteInEditMode]
[DefaultExecutionOrder(-9997)] [DefaultExecutionOrder(-9997)]
public class AvatarActivator : MonoBehaviour, IEditorOnly public class AvatarActivator : MonoBehaviour, IEditorOnly

View File

@ -6,7 +6,7 @@ namespace nadena.dev.modular_avatar.core
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
[AddComponentMenu("Modular Avatar/MA Convert Constraints")] [AddComponentMenu("Modular Avatar/MA Convert Constraints")]
#else #else
[AddComponentMenu("")] [AddComponentMenu("/")]
#endif #endif
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/convert-constraints?lang=auto")] [HelpURL("https://modular-avatar.nadena.dev/docs/reference/convert-constraints?lang=auto")]
public class ModularAvatarConvertConstraints : AvatarTagComponent public class ModularAvatarConvertConstraints : AvatarTagComponent

View File

@ -0,0 +1,50 @@
using System.Linq;
using modular_avatar_tests;
using nadena.dev.modular_avatar.core;
using nadena.dev.modular_avatar.core.editor;
using NUnit.Framework;
using UnityEditor.Animations;
using UnityEngine;
namespace UnitTests.ReactiveComponent
{
internal class ObjectToggleTests : TestBase
{
[Test]
public void WhenObjectIsAlwaysOn_CorrectProxyParameterIsGenerated()
{
var root = CreateRoot("root");
var obj = CreateChild(root, "obj");
var toggle = CreateChild(root, "toggle");
// Prevent obj from being removed by the GC game objects pass
obj.AddComponent<MeshRenderer>();
var toggleComponent = toggle.AddComponent<ModularAvatarObjectToggle>();
var aor = new AvatarObjectReference();
aor.Set(obj);
toggleComponent.Objects = new()
{
new()
{
Active = false,
Object = aor
}
};
AvatarProcessor.ProcessAvatar(root);
// TODO: Ideally we should start using play mode testing for these things...
var fx = (AnimatorController)FindFxController(root).animatorController;
var readableProp = fx.parameters.FirstOrDefault(
p => p.name.StartsWith("__MA/ReadableProp/obj/UnityEngine.GameObject/m_IsActive")
);
Assert.IsNotNull(readableProp);
Assert.AreEqual(readableProp.defaultFloat, 0);
Assert.IsFalse(obj.activeSelf);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c68d69f7b4a46c5b2ce3d8f26b0fa76
timeCreated: 1729376563

View File

@ -1,7 +1,7 @@
{ {
"name": "nadena.dev.modular-avatar", "name": "nadena.dev.modular-avatar",
"displayName": "Modular Avatar", "displayName": "Modular Avatar",
"version": "1.10.4", "version": "1.10.5",
"unity": "2022.3", "unity": "2022.3",
"description": "A suite of tools for assembling your avatar out of reusable components", "description": "A suite of tools for assembling your avatar out of reusable components",
"author": { "author": {
@ -16,6 +16,6 @@
}, },
"vpmDependencies": { "vpmDependencies": {
"com.vrchat.avatars": ">=3.7.0", "com.vrchat.avatars": ">=3.7.0",
"nadena.dev.ndmf": ">=1.5.4 <2.0.0-a" "nadena.dev.ndmf": ">=1.5.6 <2.0.0-a"
} }
} }