diff --git a/.github/ProjectRoot/vpm-manifest-2022.json b/.github/ProjectRoot/vpm-manifest-2022.json index 60d7c06f..ab64302d 100644 --- a/.github/ProjectRoot/vpm-manifest-2022.json +++ b/.github/ProjectRoot/vpm-manifest-2022.json @@ -4,7 +4,7 @@ "version": "3.7.0" }, "nadena.dev.ndmf": { - "version": "1.4.0" + "version": "1.5.6" } }, "locked": { @@ -19,7 +19,7 @@ "dependencies": {} }, "nadena.dev.ndmf": { - "version": "1.5.3" + "version": "1.5.6" } } } \ No newline at end of file diff --git a/Editor/Animation/AnimationServicesContext.cs b/Editor/Animation/AnimationServicesContext.cs index 95d68ec3..bc63cec9 100644 --- a/Editor/Animation/AnimationServicesContext.cs +++ b/Editor/Animation/AnimationServicesContext.cs @@ -7,7 +7,9 @@ using nadena.dev.ndmf; using UnityEditor; using UnityEditor.Animations; using UnityEngine; +#if MA_VRCSDK3_AVATARS using VRC.SDK3.Avatars.Components; +#endif #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 public void AddPropertyDefinition(AnimatorControllerParameter paramDef) { +#if MA_VRCSDK3_AVATARS var fx = (AnimatorController) _context.AvatarDescriptor.baseAnimationLayers .First(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX) .animatorController; fx.parameters = fx.parameters.Concat(new[] { paramDef }).ToArray(); +#endif } public string GetActiveSelfProxy(GameObject obj) diff --git a/Editor/Animation/GameObjectDisableDelayPass.cs b/Editor/Animation/GameObjectDisableDelayPass.cs index 8e8bca4c..87692657 100644 --- a/Editor/Animation/GameObjectDisableDelayPass.cs +++ b/Editor/Animation/GameObjectDisableDelayPass.cs @@ -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 UnityEditor; using UnityEditor.Animations; using UnityEngine; using VRC.SDK3.Avatars.Components; +using BuildContext = nadena.dev.ndmf.BuildContext; namespace nadena.dev.modular_avatar.animation { @@ -23,11 +26,16 @@ namespace nadena.dev.modular_avatar.animation if (fx == null) return; + var nullMotion = new AnimationClip(); + nullMotion.name = "NullMotion"; + var blendTree = new BlendTree(); blendTree.blendType = BlendTreeType.Direct; 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 state = new AnimatorState(); @@ -52,9 +60,24 @@ namespace nadena.dev.modular_avatar.animation defaultWeight = 1, blendingMode = AnimatorLayerBlendingMode.Override }).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 prop = binding.Item2; @@ -64,12 +87,43 @@ namespace nadena.dev.modular_avatar.animation curve.AddKey(0, 1); 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 { - motion = motion, - directBlendParameter = prop, + motion = bufferBlendTree, + directBlendParameter = MergeBlendTreePass.ALWAYS_ONE, timeScale = 1 }; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/Animation/PathMappings.cs b/Editor/Animation/PathMappings.cs index 9546a063..8ea314bc 100644 --- a/Editor/Animation/PathMappings.cs +++ b/Editor/Animation/PathMappings.cs @@ -368,6 +368,7 @@ namespace nadena.dev.modular_avatar.animation } Profiler.EndSample(); +#if MA_VRCSDK3_AVATARS var layers = context.AvatarDescriptor.baseAnimationLayers .Concat(context.AvatarDescriptor.specialAnimationLayers); @@ -383,6 +384,7 @@ namespace nadena.dev.modular_avatar.animation ApplyMappingsToAvatarMask(acLayer.avatarMask); } Profiler.EndSample(); +#endif Profiler.EndSample(); } diff --git a/Editor/ApplyAnimatorDefaultValuesPass.cs b/Editor/ApplyAnimatorDefaultValuesPass.cs index 7b3c91ed..0f9cb15e 100644 --- a/Editor/ApplyAnimatorDefaultValuesPass.cs +++ b/Editor/ApplyAnimatorDefaultValuesPass.cs @@ -1,4 +1,5 @@ -#region +#if MA_VRCSDK3_AVATARS +#region using System; using System.Collections.Immutable; @@ -57,4 +58,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/Inspector/FirstPersonVisibleEditor.cs b/Editor/Inspector/FirstPersonVisibleEditor.cs index b9646ddb..8f7cedb9 100644 --- a/Editor/Inspector/FirstPersonVisibleEditor.cs +++ b/Editor/Inspector/FirstPersonVisibleEditor.cs @@ -1,4 +1,5 @@ -using UnityEditor; +#if MA_VRCSDK3_AVATARS +using UnityEditor; namespace nadena.dev.modular_avatar.core.editor { @@ -45,3 +46,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } + +#endif \ No newline at end of file diff --git a/Editor/Inspector/Menu/MenuInstallerEditor.cs b/Editor/Inspector/Menu/MenuInstallerEditor.cs index 5110aa36..f32efe0f 100644 --- a/Editor/Inspector/Menu/MenuInstallerEditor.cs +++ b/Editor/Inspector/Menu/MenuInstallerEditor.cs @@ -368,7 +368,7 @@ namespace nadena.dev.modular_avatar.core.editor serializedObject.ApplyModifiedProperties(); - Localization.ShowLanguageUI(); + ShowLanguageUI(); } private string ObjectHierarchyOrder(Component arg) @@ -415,6 +415,9 @@ namespace nadena.dev.modular_avatar.core.editor var group = installer.gameObject.AddComponent(); var menuRoot = new GameObject(); menuRoot.name = "Menu"; + + group.targetObject = menuRoot; + Undo.RegisterCreatedObjectUndo(menuRoot, "Extract menu"); menuRoot.transform.SetParent(group.transform, false); foreach (var control in menu.controls) diff --git a/Editor/Inspector/Menu/MenuItemGUI.cs b/Editor/Inspector/Menu/MenuItemGUI.cs index 30439a32..8f4ab313 100644 --- a/Editor/Inspector/Menu/MenuItemGUI.cs +++ b/Editor/Inspector/Menu/MenuItemGUI.cs @@ -301,10 +301,9 @@ namespace nadena.dev.modular_avatar.core.editor EditorGUILayout.BeginVertical(); if (_type.hasMultipleDifferentValues) return; - VRCExpressionsMenu.Control.ControlType type = - (VRCExpressionsMenu.Control.ControlType) Enum - .GetValues(typeof(VRCExpressionsMenu.Control.ControlType)) - .GetValue(_type.enumValueIndex); + var controlTypeArray = Enum.GetValues(typeof(VRCExpressionsMenu.Control.ControlType)); + var index = Math.Clamp(_type.enumValueIndex, 0, controlTypeArray.Length - 1); + var type = (VRCExpressionsMenu.Control.ControlType)controlTypeArray.GetValue(index); switch (type) { diff --git a/Editor/Inspector/Menu/ToggleCreatorShortcut.cs b/Editor/Inspector/Menu/ToggleCreatorShortcut.cs index e549d319..d1027b95 100644 --- a/Editor/Inspector/Menu/ToggleCreatorShortcut.cs +++ b/Editor/Inspector/Menu/ToggleCreatorShortcut.cs @@ -1,4 +1,5 @@ -using nadena.dev.modular_avatar.ui; +#if MA_VRCSDK3_AVATARS +using nadena.dev.modular_avatar.ui; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.ScriptableObjects; @@ -62,4 +63,5 @@ namespace nadena.dev.modular_avatar.core.editor Undo.RegisterCreatedObjectUndo(toggle, "Create Toggle"); } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/Inspector/Parameters/DefaultValueField.cs b/Editor/Inspector/Parameters/DefaultValueField.cs index 6ba6cd22..f29f217b 100644 --- a/Editor/Inspector/Parameters/DefaultValueField.cs +++ b/Editor/Inspector/Parameters/DefaultValueField.cs @@ -20,7 +20,8 @@ namespace nadena.dev.modular_avatar.core.editor private readonly DropdownField _boolField; private ParameterSyncType _syncType; - + private bool _hasInitialBinding; + public DefaultValueField() { // Hidden binding elements @@ -57,28 +58,39 @@ namespace nadena.dev.modular_avatar.core.editor { _numberField.style.display = DisplayStyle.Flex; _boolField.style.display = DisplayStyle.None; - OnUpdateNumberValue(_numberField.value); + OnUpdateNumberValue(_numberField.value, true); } else { _numberField.style.display = DisplayStyle.None; _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)) { - _defaultValueField.value = 0; + if (!implicitUpdate) + { + _defaultValueField.value = 0; + } + + theValue = _defaultValueField.value; + _hasExplicitDefaultValueField.value = false; } else if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) && !float.IsNaN(parsed) && !float.IsInfinity(parsed)) { - _defaultValueField.value = _syncType switch + theValue = _defaultValueField.value = _syncType switch { ParameterSyncType.Int => Mathf.FloorToInt(Mathf.Clamp(parsed, 0, 255)), ParameterSyncType.Float => Mathf.Clamp(parsed, -1, 1), @@ -88,11 +100,15 @@ namespace nadena.dev.modular_avatar.core.editor _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; _hasExplicitDefaultValueField.value = value != V_None; @@ -101,6 +117,8 @@ namespace nadena.dev.modular_avatar.core.editor private void UpdateVisibleField(float value, bool hasExplicitValue) { + _hasInitialBinding = true; + if (hasExplicitValue || Mathf.Abs(value) > 0.0000001) { _numberField.SetValueWithoutNotify(value.ToString(CultureInfo.InvariantCulture)); diff --git a/Editor/Menu/MenuExtractor.cs b/Editor/Menu/MenuExtractor.cs index 89ee232c..9f670382 100644 --- a/Editor/Menu/MenuExtractor.cs +++ b/Editor/Menu/MenuExtractor.cs @@ -119,9 +119,11 @@ namespace nadena.dev.modular_avatar.core.editor internal static VRCExpressionsMenu.Control CloneControl(VRCExpressionsMenu.Control c) { + var type = c.type != 0 ? c.type : VRCExpressionsMenu.Control.ControlType.Button; + return new VRCExpressionsMenu.Control() { - type = c.type, + type = type, name = c.name, icon = c.icon, parameter = new VRCExpressionsMenu.Control.Parameter() { name = c.parameter?.name }, diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 2d258b23..4a31f17a 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -57,26 +57,29 @@ namespace nadena.dev.modular_avatar.core.editor.plugin #endif seq.WithRequiredExtension(typeof(AnimationServicesContext), _s2 => { +#if MA_VRCSDK3_AVATARS seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), new MaterialSetterPreview()); -#if MA_VRCSDK3_AVATARS + // 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 // to retain those objects? maybe? seq.Run(MenuInstallPluginPass.Instance); #endif - + seq.Run(MergeArmaturePluginPass.Instance); seq.Run(BoneProxyPluginPass.Instance); +#if MA_VRCSDK3_AVATARS seq.Run(VisibleHeadAccessoryPluginPass.Instance); +#endif seq.Run("World Fixed Object", ctx => new WorldFixedObjectProcessor().Process(ctx) ); seq.Run(ReplaceObjectPluginPass.Instance); #if MA_VRCSDK3_AVATARS seq.Run(BlendshapeSyncAnimationPluginPass.Instance); -#endif seq.Run(GameObjectDelayDisablePass.Instance); +#endif seq.Run(ConstraintConverterPass.Instance); }); #if MA_VRCSDK3_AVATARS @@ -213,6 +216,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin } } +#if MA_VRCSDK3_AVATARS class VisibleHeadAccessoryPluginPass : MAPass { protected override void Execute(ndmf.BuildContext context) @@ -220,6 +224,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin new VisibleHeadAccessoryProcessor(MAContext(context)).Process(); } } +#endif class ReplaceObjectPluginPass : MAPass { diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs index 179e4ce4..7cc2e403 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#if MA_VRCSDK3_AVATARS +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using nadena.dev.ndmf.preview; @@ -346,4 +347,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs index 923ac135..228f215c 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#if MA_VRCSDK3_AVATARS +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using nadena.dev.modular_avatar.animation; @@ -128,7 +129,7 @@ namespace nadena.dev.modular_avatar.core.editor foreach (var cond in rule.ControllingConditions) { var paramName = cond.Parameter; - if (ForcePropertyOverrides.TryGetValue(paramName, out var value)) + if (ForcePropertyOverrides?.TryGetValue(paramName, out var value) == true) { cond.InitialValue = value; } @@ -304,4 +305,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs index 882f96bf..ae77c80f 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs @@ -1,4 +1,5 @@ -#region +#if MA_VRCSDK3_AVATARS +#region using System; using System.Collections.Generic; @@ -613,3 +614,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } + +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs index 82c5faa8..cec2bf17 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs @@ -1,4 +1,5 @@ -using nadena.dev.ndmf; +#if MA_VRCSDK3_AVATARS +using nadena.dev.ndmf; using UnityEditor.Animations; using UnityEngine; @@ -53,4 +54,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/MaterialSetterPreview.cs b/Editor/ReactiveObjects/MaterialSetterPreview.cs index a181429d..9b1a8e59 100644 --- a/Editor/ReactiveObjects/MaterialSetterPreview.cs +++ b/Editor/ReactiveObjects/MaterialSetterPreview.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#if MA_VRCSDK3_AVATARS +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -144,4 +145,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/MenuItemPreviewCondition.cs b/Editor/ReactiveObjects/MenuItemPreviewCondition.cs index 1d8f8629..04ce486a 100644 --- a/Editor/ReactiveObjects/MenuItemPreviewCondition.cs +++ b/Editor/ReactiveObjects/MenuItemPreviewCondition.cs @@ -1,4 +1,5 @@ -using System; +#if MA_VRCSDK3_AVATARS +using System; using System.Collections.Generic; using nadena.dev.ndmf; using nadena.dev.ndmf.preview; @@ -70,4 +71,5 @@ namespace nadena.dev.modular_avatar.core.editor return _context.Observe(mami, _ => mami.isDefault); } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/ObjectTogglePreview.cs b/Editor/ReactiveObjects/ObjectTogglePreview.cs index 45b4e9fa..3d138ed0 100644 --- a/Editor/ReactiveObjects/ObjectTogglePreview.cs +++ b/Editor/ReactiveObjects/ObjectTogglePreview.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#if MA_VRCSDK3_AVATARS +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; @@ -104,4 +105,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/ParameterAssignerPass.cs b/Editor/ReactiveObjects/ParameterAssignerPass.cs index 9b891f84..b7afd0bf 100644 --- a/Editor/ReactiveObjects/ParameterAssignerPass.cs +++ b/Editor/ReactiveObjects/ParameterAssignerPass.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +#if MA_VRCSDK3_AVATARS +using System.Collections.Generic; using System.Linq; using nadena.dev.ndmf; using UnityEngine; @@ -236,4 +237,5 @@ namespace nadena.dev.modular_avatar.core.editor }; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/ShapeChangerPreview.cs b/Editor/ReactiveObjects/ShapeChangerPreview.cs index 58c09131..d3cde839 100644 --- a/Editor/ReactiveObjects/ShapeChangerPreview.cs +++ b/Editor/ReactiveObjects/ShapeChangerPreview.cs @@ -1,4 +1,5 @@ -#region +#if MA_VRCSDK3_AVATARS +#region using System; using System.Collections.Generic; @@ -294,4 +295,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/Simulator/ROSimulator.cs b/Editor/ReactiveObjects/Simulator/ROSimulator.cs index 40b9be8c..4d192515 100644 --- a/Editor/ReactiveObjects/Simulator/ROSimulator.cs +++ b/Editor/ReactiveObjects/Simulator/ROSimulator.cs @@ -1,4 +1,5 @@ -using System; +#if MA_VRCSDK3_AVATARS +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -637,4 +638,5 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator ve_inactive.style.display = activeState ? DisplayStyle.None : DisplayStyle.Flex; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/ReactiveObjects/Simulator/ROSimulatorButton.cs b/Editor/ReactiveObjects/Simulator/ROSimulatorButton.cs index 285f4c6b..0d1eced6 100644 --- a/Editor/ReactiveObjects/Simulator/ROSimulatorButton.cs +++ b/Editor/ReactiveObjects/Simulator/ROSimulatorButton.cs @@ -1,5 +1,7 @@ using nadena.dev.modular_avatar.core.editor; +#if MA_VRCSDK3_AVATARS using nadena.dev.modular_avatar.core.editor.Simulator; +#endif using UnityEditor; using UnityEngine; using UnityEngine.UIElements; @@ -42,11 +44,13 @@ namespace nadena.dev.modular_avatar.core.editor private void OpenDebugger() { +#if MA_VRCSDK3_AVATARS GameObject target = Selection.activeGameObject; if (ReferenceObject is Component c) target = c.gameObject; else if (ReferenceObject is GameObject go) target = go; ROSimulator.OpenDebugger(target); +#endif } } } \ No newline at end of file diff --git a/Editor/ReactiveObjects/Simulator/StateOverrideController.cs b/Editor/ReactiveObjects/Simulator/StateOverrideController.cs index 74d9c45e..4392f953 100644 --- a/Editor/ReactiveObjects/Simulator/StateOverrideController.cs +++ b/Editor/ReactiveObjects/Simulator/StateOverrideController.cs @@ -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 UnityEngine.UIElements; @@ -74,4 +76,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Editor/SetupOutfit.cs b/Editor/SetupOutfit.cs index 93b587fd..f82510a3 100644 --- a/Editor/SetupOutfit.cs +++ b/Editor/SetupOutfit.cs @@ -146,28 +146,57 @@ namespace nadena.dev.modular_avatar.core.editor out var avatarRoot, out var avatarHips, out var outfitHips) ) return; + Undo.SetCurrentGroupName("Setup Outfit"); + var avatarArmature = avatarHips.transform.parent; var outfitArmature = outfitHips.transform.parent; - if (outfitArmature.GetComponent() == null) + var merge = outfitArmature.GetComponent(); + if (merge == null) + { + merge = Undo.AddComponent(outfitArmature.gameObject); + } else { + Undo.RecordObject(merge, ""); + } + + if (merge.mergeTarget == null || merge.mergeTargetObject == null) { - var merge = Undo.AddComponent(outfitArmature.gameObject); merge.mergeTarget = new AvatarObjectReference(); merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject); merge.LockMode = ArmatureLockMode.BaseToMerge; + } + + if (string.IsNullOrEmpty(merge.prefix) && string.IsNullOrEmpty(merge.suffix)) + { merge.InferPrefixSuffix(); + } - var outfitAnimator = outfitRoot.GetComponent(); - var outfitHumanoidBones = GetOutfitHumanoidBones(outfitRoot.transform, outfitAnimator); - var avatarAnimator = avatarRoot.GetComponent(); - List subRoots = new List(); - HeuristicBoneMapper.RenameBonesByHeuristic(merge, skipped: subRoots, outfitHumanoidBones: outfitHumanoidBones, avatarAnimator: avatarAnimator); + PrefabUtility.RecordPrefabInstancePropertyModifications(merge); - // 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 outfitAnimator = outfitRoot.GetComponent(); + var outfitHumanoidBones = GetOutfitHumanoidBones(outfitRoot.transform, outfitAnimator); + var avatarAnimator = avatarRoot.GetComponent(); + List subRoots = new List(); + 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(); + var subConfigMangleNames = false; + if (subConfig == null) + { + subConfig = Undo.AddComponent(subRoot.gameObject); + } + else + { + Undo.RecordObject(subConfig, ""); + subConfigMangleNames = subConfig.mangleNames; + } + + if (subConfig.mergeTarget == null || subConfig.mergeTargetObject == null) { - var subConfig = Undo.AddComponent(subRoot.gameObject); var parentTransform = subConfig.transform.parent; var parentConfig = parentTransform.GetComponentInParent(); var parentMapping = parentConfig.MapBone(parentTransform); @@ -178,33 +207,47 @@ namespace nadena.dev.modular_avatar.core.editor subConfig.LockMode = ArmatureLockMode.BaseToMerge; subConfig.prefix = merge.prefix; subConfig.suffix = merge.suffix; - subConfig.mangleNames = false; + subConfig.mangleNames = subConfigMangleNames; + PrefabUtility.RecordPrefabInstancePropertyModifications(subConfig); } + } - var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name); - if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null) - { - // 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 - // 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. - outfitArmature.name += ".1"; + var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name); + if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null) + { + // 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 + // 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. + outfitArmature.name += ".1"; - // Also make sure to refresh the avatar's animator humanoid bone cache. - var humanDescription = avatarAnimator.avatar; - avatarAnimator.avatar = null; - // ReSharper disable once Unity.InefficientPropertyAccess - avatarAnimator.avatar = humanDescription; - } + // Also make sure to refresh the avatar's animator humanoid bone cache. + var humanDescription = avatarAnimator.avatar; + avatarAnimator.avatar = null; + // ReSharper disable once Unity.InefficientPropertyAccess + avatarAnimator.avatar = humanDescription; } FixAPose(avatarRoot, outfitArmature); + var meshSettings = outfitRoot.GetComponent(); + var mSInheritProbeAnchor = ModularAvatarMeshSettings.InheritMode.SetOrInherit; + var mSInheritBounds = ModularAvatarMeshSettings.InheritMode.SetOrInherit; if (outfitRoot != null - && outfitRoot.GetComponent() == null + && meshSettings == null && outfitRoot.GetComponentInParent() == null) { - var meshSettings = Undo.AddComponent(outfitRoot.gameObject); + meshSettings = Undo.AddComponent(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; Bounds bounds = ModularAvatarMeshSettings.DEFAULT_BOUNDS; @@ -220,8 +263,8 @@ namespace nadena.dev.modular_avatar.core.editor rootBone = avatarRoot.transform; } - meshSettings.InheritProbeAnchor = ModularAvatarMeshSettings.InheritMode.SetOrInherit; - meshSettings.InheritBounds = ModularAvatarMeshSettings.InheritMode.SetOrInherit; + meshSettings.InheritProbeAnchor = mSInheritProbeAnchor; + meshSettings.InheritBounds = mSInheritBounds; meshSettings.ProbeAnchor = new AvatarObjectReference(); 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.referencePath = RuntimeUtil.RelativePath(avatarRoot, rootBone.gameObject); meshSettings.Bounds = bounds; + + PrefabUtility.RecordPrefabInstancePropertyModifications(meshSettings); } } diff --git a/Editor/VisibleHeadAccessoryProcessor.cs b/Editor/VisibleHeadAccessoryProcessor.cs index b5014d2d..54bbcb1a 100644 --- a/Editor/VisibleHeadAccessoryProcessor.cs +++ b/Editor/VisibleHeadAccessoryProcessor.cs @@ -1,4 +1,5 @@ -#region +#if MA_VRCSDK3_AVATARS +#region using System; using System.Collections.Generic; @@ -251,3 +252,5 @@ namespace nadena.dev.modular_avatar.core.editor } } } + +#endif \ No newline at end of file diff --git a/Runtime/Activator.cs b/Runtime/Activator.cs index 9d45d930..6825177e 100644 --- a/Runtime/Activator.cs +++ b/Runtime/Activator.cs @@ -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 /// scene). /// - [AddComponentMenu("")] + [AddComponentMenu("/")] [ExecuteInEditMode] [DefaultExecutionOrder(-9998)] public class Activator : MonoBehaviour, IEditorOnly @@ -30,7 +30,7 @@ namespace nadena.dev.modular_avatar.core } } - [AddComponentMenu("")] + [AddComponentMenu("/")] [ExecuteInEditMode] [DefaultExecutionOrder(-9997)] public class AvatarActivator : MonoBehaviour, IEditorOnly diff --git a/Runtime/ModularAvatarConvertConstraints.cs b/Runtime/ModularAvatarConvertConstraints.cs index 1440fa25..f4f5ebdd 100644 --- a/Runtime/ModularAvatarConvertConstraints.cs +++ b/Runtime/ModularAvatarConvertConstraints.cs @@ -6,7 +6,7 @@ namespace nadena.dev.modular_avatar.core #if MA_VRCSDK3_AVATARS [AddComponentMenu("Modular Avatar/MA Convert Constraints")] #else - [AddComponentMenu("")] + [AddComponentMenu("/")] #endif [HelpURL("https://modular-avatar.nadena.dev/docs/reference/convert-constraints?lang=auto")] public class ModularAvatarConvertConstraints : AvatarTagComponent diff --git a/UnitTests~/ReactiveComponent/ObjectToggleTests.cs b/UnitTests~/ReactiveComponent/ObjectToggleTests.cs new file mode 100644 index 00000000..6637ff05 --- /dev/null +++ b/UnitTests~/ReactiveComponent/ObjectToggleTests.cs @@ -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(); + + var toggleComponent = toggle.AddComponent(); + 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); + } + } +} \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/ObjectToggleTests.cs.meta b/UnitTests~/ReactiveComponent/ObjectToggleTests.cs.meta new file mode 100644 index 00000000..9e86034a --- /dev/null +++ b/UnitTests~/ReactiveComponent/ObjectToggleTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7c68d69f7b4a46c5b2ce3d8f26b0fa76 +timeCreated: 1729376563 \ No newline at end of file diff --git a/package.json b/package.json index ec982b70..01bfb789 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nadena.dev.modular-avatar", "displayName": "Modular Avatar", - "version": "1.10.4", + "version": "1.10.5", "unity": "2022.3", "description": "A suite of tools for assembling your avatar out of reusable components", "author": { @@ -16,6 +16,6 @@ }, "vpmDependencies": { "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" } }