Merge branch 'main' into vrm

This commit is contained in:
bd_ 2024-09-01 17:25:41 -07:00 committed by GitHub
commit c32bde7d9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
413 changed files with 17439 additions and 4875 deletions

View File

@ -1,7 +1,7 @@
{
"dependencies": {
"com.vrchat.avatars": {
"version": "3.5.0"
"version": "3.7.0"
},
"nadena.dev.ndmf": {
"version": "1.4.0"
@ -9,17 +9,17 @@
},
"locked": {
"com.vrchat.avatars": {
"version": "3.5.0",
"version": "3.7.0",
"dependencies": {
"com.vrchat.base": "3.5.0"
"com.vrchat.base": "3.7.0"
}
},
"com.vrchat.base": {
"version": "3.5.0",
"version": "3.7.0",
"dependencies": {}
},
"nadena.dev.ndmf": {
"version": "1.4.0"
"version": "1.5.0-rc.3"
}
}
}

View File

@ -64,12 +64,14 @@ jobs:
- name: Set Environment Variables
run: |
echo "zipFile=${{ env.packageName }}-${{ steps.version.outputs.prop }}".zip >> $GITHUB_ENV
echo "zipFileSHA256=${{ env.packageName }}-${{ steps.version.outputs.prop }}".zip.sha256 >> $GITHUB_ENV
echo "unityPackage=${{ env.packageName }}-${{ steps.version.outputs.prop }}.unitypackage" >> $GITHUB_ENV
- name: Create Zip
run: |
zip ".github/${{env.zipFile}}" ./* -r -x .github .git '.git/*' '*~/*' '*.ps1*'
mv ".github/${{env.zipFile}}" "${{env.zipFile}}"
sha256sum "${{env.zipFile}}" > "${{env.zipFileSHA256}}"
- uses: actions/upload-artifact@v4
with:
@ -77,7 +79,7 @@ jobs:
path: ${{ env.zipFile }}
- name: Make Release
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
@ -85,4 +87,5 @@ jobs:
tag_name: ${{ steps.version.outputs.prop }}
files: |
${{ env.zipFile }}
${{ env.zipFileSHA256 }}
package.json

View File

@ -122,7 +122,7 @@ jobs:
workingDirectory: docs-site~
- name: Purge cache
uses: nathanvaughn/actions-cloudflare-purge@3ae45672b053b6e981788270206025fb3358d502
uses: nathanvaughn/actions-cloudflare-purge@367672c723960cd03bb7d8c2c4d89062a3fc1fac
continue-on-error: true
with:
cf_zone: ${{ secrets.CF_ZONE_ID }}

View File

@ -45,7 +45,7 @@ jobs:
name: Unit tests
strategy:
matrix:
unity_major_version: [ 2019, 2022 ]
unity_major_version: [ 2022 ]
sdk: [ vrcsdk ]
runs-on: ubuntu-latest
permissions:

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
UnitTests/
UnitTests
UnitTests.meta
/UnitTests/

View File

@ -4,6 +4,7 @@ using System.Linq;
using nadena.dev.modular_avatar.animation;
using UnityEditor;
using UnityEngine;
using EditorCurveBinding = UnityEditor.EditorCurveBinding;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor

View File

@ -1,19 +1,20 @@
using System;
#region
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Data.Odbc;
using nadena.dev.modular_avatar.core.editor;
using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using BuildContext = nadena.dev.ndmf.BuildContext;
#if MA_VRCSDK3_AVATARS
using VRC.SDK3.Avatars.Components;
#endif
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.animation
{
@ -72,12 +73,20 @@ namespace nadena.dev.modular_avatar.animation
{
return _currentClip;
}
public void SetCurrentNoInvalidate(Motion newMotion)
{
_currentClip = newMotion;
}
}
private ndmf.BuildContext _context;
private BuildContext _context;
private List<Action> _clipCommitActions = new List<Action>();
private List<ClipHolder> _clips = new List<ClipHolder>();
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
private HashSet<VRCAnimatorPlayAudio> _playAudios = new HashSet<VRCAnimatorPlayAudio>();
#endif
private Dictionary<string, HashSet<ClipHolder>> _pathToClip = null;
@ -119,7 +128,7 @@ namespace nadena.dev.modular_avatar.animation
}
}
internal void OnActivate(ndmf.BuildContext context)
internal void OnActivate(BuildContext context)
{
_context = context;
@ -170,6 +179,16 @@ namespace nadena.dev.modular_avatar.animation
if (processClip == null) processClip = (_) => { };
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
foreach (var behavior in state.behaviours)
{
if (behavior is VRCAnimatorPlayAudio playAudio)
{
_playAudios.Add(playAudio);
}
}
#endif
if (state.motion == null) return;
var clipHolder = RegisterMotion(state.motion, state, processClip, _originalToHolder);
@ -186,6 +205,16 @@ namespace nadena.dev.modular_avatar.animation
}
}
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
internal void ForeachPlayAudio(Action<VRCAnimatorPlayAudio> processPlayAudio)
{
foreach (var playAudioHolder in _playAudios)
{
processPlayAudio(playAudioHolder);
}
}
#endif
/// <summary>
/// Returns a list of clips which touched the given _original_ path. This path is subject to basepath remapping,
/// but not object movement remapping.
@ -272,12 +301,12 @@ namespace nadena.dev.modular_avatar.animation
_pathToClip = new Dictionary<string, HashSet<ClipHolder>>();
foreach (var clip in _clips)
{
recordPaths(clip);
RecordPaths(clip);
}
}
}
private void recordPaths(ClipHolder holder)
private void RecordPaths(ClipHolder holder)
{
var clip = holder.GetCurrentClipUnsafe() as AnimationClip;

View File

@ -1,5 +1,15 @@
using System;
#region
using System;
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
#endregion
namespace nadena.dev.modular_avatar.animation
{
@ -17,16 +27,24 @@ namespace nadena.dev.modular_avatar.animation
/// </summary>
internal sealed class AnimationServicesContext : IExtensionContext
{
private BuildContext _context;
private AnimationDatabase _animationDatabase;
private PathMappings _pathMappings;
private ReadableProperty _readableProperty;
private Dictionary<GameObject, string> _selfProxies = new();
public void OnActivate(BuildContext context)
{
_context = context;
_animationDatabase = new AnimationDatabase();
_animationDatabase.OnActivate(context);
_pathMappings = new PathMappings();
_pathMappings.OnActivate(context, _animationDatabase);
_readableProperty = new ReadableProperty(_context, _animationDatabase, this);
}
public void OnDeactivate(BuildContext context)
@ -65,5 +83,37 @@ namespace nadena.dev.modular_avatar.animation
return _pathMappings;
}
}
public IEnumerable<(EditorCurveBinding, string)> BoundReadableProperties => _readableProperty.BoundProperties;
// HACK: This is a temporary crutch until we rework the entire animator services system
public void AddPropertyDefinition(AnimatorControllerParameter paramDef)
{
var fx = (AnimatorController)
_context.AvatarDescriptor.baseAnimationLayers
.First(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX)
.animatorController;
fx.parameters = fx.parameters.Concat(new[] { paramDef }).ToArray();
}
public string GetActiveSelfProxy(GameObject obj)
{
if (_selfProxies.TryGetValue(obj, out var paramName) && !string.IsNullOrEmpty(paramName)) return paramName;
var path = PathMappings.GetObjectIdentifier(obj);
paramName = _readableProperty.ForActiveSelf(path);
_selfProxies[obj] = paramName;
return paramName;
}
public bool ObjectHasAnimations(GameObject obj)
{
var path = PathMappings.GetObjectIdentifier(obj);
var clips = AnimationDatabase.ClipsForPath(path);
return clips != null && !clips.IsEmpty;
}
}
}

View File

@ -302,6 +302,13 @@ namespace nadena.dev.modular_avatar.animation
private void PruneEmptyLayers()
{
#if MA_VRCSDK3_AVATARS
// We can't safely correct the layer index of a VRCAnimatorLayerControl without knowing if it refers to
// _this_ animator controller, so just skip this. We'll do the empty layer pruning later when we merge
// everything together.
if (BlendableLayer == null) return;
#endif
var originalLayers = _layers;
int[] layerIndexMappings = new int[originalLayers.Count];
@ -496,7 +503,7 @@ namespace nadena.dev.modular_avatar.animation
for (int i = 0; i < overrideBehaviors.Length; i++)
{
overrideBehaviors[i] = _deepClone.DoClone(overrideBehaviors[i]);
AdjustBehavior(overrideBehaviors[i]);
AdjustBehavior(overrideBehaviors[i], basePath);
}
newLayer.SetOverrideBehaviours((AnimatorState)_cloneMap[state], overrideBehaviors);
@ -534,7 +541,6 @@ namespace nadena.dev.modular_avatar.animation
if (child.stateMachine == null) continue;
if (visited.Contains(child.stateMachine)) continue;
visited.Add(child.stateMachine);
foreach (var state in VisitStateMachine(child.stateMachine))
{
yield return state;
@ -579,7 +585,7 @@ namespace nadena.dev.modular_avatar.animation
{
foreach (var behavior in state.behaviours)
{
AdjustBehavior(behavior);
AdjustBehavior(behavior, basePath);
}
}
@ -587,7 +593,7 @@ namespace nadena.dev.modular_avatar.animation
return asm;
}
private void AdjustBehavior(StateMachineBehaviour behavior)
private void AdjustBehavior(StateMachineBehaviour behavior, string basePath)
{
#if MA_VRCSDK3_AVATARS
switch (behavior)
@ -599,6 +605,16 @@ namespace nadena.dev.modular_avatar.animation
layerControl.layer += _controllerBaseLayer;
break;
}
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
case VRCAnimatorPlayAudio playAudio:
{
if (!string.IsNullOrEmpty(playAudio.SourcePath) && !string.IsNullOrEmpty(basePath) && !playAudio.SourcePath.StartsWith(basePath))
{
playAudio.SourcePath = $"{basePath}/{playAudio.SourcePath}";
}
break;
}
#endif
}
#endif
}

View File

@ -6,10 +6,11 @@ using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using BuildContext = nadena.dev.ndmf.BuildContext;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.animation
{
using UnityObject = UnityEngine.Object;
using UnityObject = Object;
internal class DeepClone
{
@ -125,8 +126,12 @@ namespace nadena.dev.modular_avatar.animation
{
case SerializedPropertyType.ObjectReference:
{
var newObj = DoClone(prop.objectReferenceValue, basePath, cloneMap);
prop.objectReferenceValue = newObj;
if (prop.objectReferenceValue != null && prop.objectReferenceValue != obj)
{
var newObj = DoClone(prop.objectReferenceValue, basePath, cloneMap);
prop.objectReferenceValue = newObj;
}
break;
}
// Iterating strings can get super slow...
@ -161,7 +166,10 @@ namespace nadena.dev.modular_avatar.animation
{
var newBinding = binding;
newBinding.path = MapPath(binding, basePath);
newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName,
// https://github.com/bdunderscore/modular-avatar/issues/950
// It's reported that sometimes using SetObjectReferenceCurve right after SetCurve might cause the
// curves to be forgotten; use SetEditorCurve instead.
AnimationUtility.SetEditorCurve(newClip, newBinding,
AnimationUtility.GetEditorCurve(clip, binding));
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using UnityEditor;
namespace nadena.dev.modular_avatar.animation
{
internal class EditorCurveBindingComparer : IEqualityComparer<EditorCurveBinding>
{
public bool Equals(UnityEditor.EditorCurveBinding x, UnityEditor.EditorCurveBinding y)
{
return x.path == y.path && x.type == y.type && x.propertyName == y.propertyName;
}
public int GetHashCode(UnityEditor.EditorCurveBinding obj)
{
return obj.path.GetHashCode() ^ obj.type.GetHashCode() ^ obj.propertyName.GetHashCode();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e751f7889323485bbe202285a47cb0d4
timeCreated: 1719196767

View File

@ -0,0 +1,75 @@
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
namespace nadena.dev.modular_avatar.animation
{
/// <summary>
/// This pass delays turning GameObjects OFF by one frame when those objects control a ReadableProperty. This
/// ensures that we don't expose hidden meshes when removing articles of clothing, for example.
/// </summary>
internal class GameObjectDelayDisablePass : Pass<GameObjectDelayDisablePass>
{
protected override void Execute(BuildContext context)
{
var asc = context.Extension<AnimationServicesContext>();
if (!asc.BoundReadableProperties.Any()) return;
var fx = (AnimatorController)context.AvatarDescriptor.baseAnimationLayers
.FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX).animatorController;
if (fx == null) return;
var blendTree = new BlendTree();
blendTree.blendType = BlendTreeType.Direct;
blendTree.useAutomaticThresholds = false;
blendTree.children = asc.BoundReadableProperties.Select(GenerateDelayChild).ToArray();
var asm = new AnimatorStateMachine();
var state = new AnimatorState();
state.name = "DelayDisable";
state.motion = blendTree;
state.writeDefaultValues = true;
asm.defaultState = state;
asm.states = new[]
{
new ChildAnimatorState
{
state = state,
position = Vector3.zero
}
};
fx.layers = fx.layers.Append(new AnimatorControllerLayer
{
name = "DelayDisable",
stateMachine = asm,
defaultWeight = 1,
blendingMode = AnimatorLayerBlendingMode.Override
}).ToArray();
}
private ChildMotion GenerateDelayChild((EditorCurveBinding, string) binding)
{
var ecb = binding.Item1;
var prop = binding.Item2;
var motion = new AnimationClip();
var curve = new AnimationCurve();
curve.AddKey(0, 1);
AnimationUtility.SetEditorCurve(motion, ecb, curve);
return new ChildMotion
{
motion = motion,
directBlendParameter = prop,
timeScale = 1
};
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9b3eb561f76b459fbfbcf29fc4484261
timeCreated: 1722222066

View File

@ -7,7 +7,8 @@ using nadena.dev.ndmf;
using nadena.dev.ndmf.util;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
#endif
#endregion
@ -15,8 +16,6 @@ namespace nadena.dev.modular_avatar.animation
{
#region
using UnityObject = Object;
#endregion
/// <summary>
@ -34,6 +33,7 @@ namespace nadena.dev.modular_avatar.animation
private HashSet<GameObject> _transformLookthroughObjects = new HashSet<GameObject>();
private ImmutableDictionary<string, string> _originalPathToMappedPath = null;
private ImmutableDictionary<string, string> _transformOriginalPathToMappedPath = null;
private ImmutableDictionary<string, GameObject> _pathToObject = null;
internal void OnActivate(BuildContext context, AnimationDatabase animationDatabase)
{
@ -52,6 +52,7 @@ namespace nadena.dev.modular_avatar.animation
{
_originalPathToMappedPath = null;
_transformOriginalPathToMappedPath = null;
_pathToObject = null;
}
/// <summary>
@ -248,7 +249,10 @@ namespace nadena.dev.modular_avatar.animation
{
var newBinding = binding;
newBinding.path = MapPath(binding);
newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName,
// https://github.com/bdunderscore/modular-avatar/issues/950
// It's reported that sometimes using SetObjectReferenceCurve right after SetCurve might cause the
// curves to be forgotten; use SetEditorCurve instead.
AnimationUtility.SetEditorCurve(newClip, newBinding,
AnimationUtility.GetEditorCurve(originalClip, binding));
}
@ -260,10 +264,10 @@ namespace nadena.dev.modular_avatar.animation
AnimationUtility.GetObjectReferenceCurve(originalClip, objBinding));
}
newClip.wrapMode = newClip.wrapMode;
newClip.legacy = newClip.legacy;
newClip.frameRate = newClip.frameRate;
newClip.localBounds = newClip.localBounds;
newClip.wrapMode = originalClip.wrapMode;
newClip.legacy = originalClip.legacy;
newClip.frameRate = originalClip.frameRate;
newClip.localBounds = originalClip.localBounds;
AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(originalClip));
if (clipCache != null)
@ -286,10 +290,41 @@ namespace nadena.dev.modular_avatar.animation
}
});
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
_animationDatabase.ForeachPlayAudio(playAudio =>
{
if (playAudio == null) return;
playAudio.SourcePath = MapPath(playAudio.SourcePath, true);
});
#endif
foreach (var listener in context.AvatarRootObject.GetComponentsInChildren<IOnCommitObjectRenames>())
{
listener.OnCommitObjectRenames(context, this);
}
}
public GameObject PathToObject(string path)
{
if (_pathToObject == null)
{
var builder = ImmutableDictionary.CreateBuilder<string, GameObject>();
foreach (var kvp in _objectToOriginalPaths)
foreach (var p in kvp.Value)
builder[p] = kvp.Key;
_pathToObject = builder.ToImmutable();
}
if (_pathToObject.TryGetValue(path, out var obj))
{
return obj;
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.animation
{
internal class ReadableProperty
{
private readonly BuildContext _context;
private readonly AnimationDatabase _animDB;
private readonly AnimationServicesContext _asc;
private readonly Dictionary<EditorCurveBinding, string> _alreadyBound = new();
private long _nextIndex;
public ReadableProperty(BuildContext context, AnimationDatabase animDB, AnimationServicesContext asc)
{
_context = context;
_animDB = animDB;
_asc = asc;
}
public IEnumerable<(EditorCurveBinding, string)> BoundProperties =>
_alreadyBound.Select(kv => (kv.Key, kv.Value));
/// <summary>
/// Creates an animator parameter which tracks the effective value of a property on a component. This only
/// tracks FX layer properties.
/// </summary>
/// <param name="ecb"></param>
/// <returns></returns>
public string ForBinding(string path, Type componentType, string property)
{
var ecb = new EditorCurveBinding
{
path = path,
type = componentType,
propertyName = property
};
if (_alreadyBound.TryGetValue(ecb, out var reader))
{
return reader;
}
var lastComponent = path.Split("/")[^1];
var emuPropName = $"__MA/ReadableProp/{lastComponent}/{componentType}/{property}#{_nextIndex++}";
float initialValue = 0;
var gameObject = _asc.PathMappings.PathToObject(path);
Object component = componentType == typeof(GameObject)
? gameObject
: gameObject?.GetComponent(componentType);
if (component != null)
{
var so = new SerializedObject(component);
var prop = so.FindProperty(property);
if (prop != null)
switch (prop.propertyType)
{
case SerializedPropertyType.Boolean:
initialValue = prop.boolValue ? 1 : 0;
break;
case SerializedPropertyType.Float:
initialValue = prop.floatValue;
break;
case SerializedPropertyType.Integer:
initialValue = prop.intValue;
break;
default: throw new NotImplementedException($"Property type {prop.type} not supported");
}
}
_asc.AddPropertyDefinition(new AnimatorControllerParameter
{
defaultFloat = initialValue,
name = emuPropName,
type = AnimatorControllerParameterType.Float
});
BindProperty(ecb, emuPropName);
_alreadyBound[ecb] = emuPropName;
return emuPropName;
}
private void BindProperty(EditorCurveBinding ecb, string propertyName)
{
var boundProp = new EditorCurveBinding
{
path = "",
type = typeof(Animator),
propertyName = propertyName
};
foreach (var clip in _animDB.ClipsForPath(ecb.path)) ProcessAnyClip(clip);
void ProcessBlendTree(BlendTree blendTree)
{
foreach (var child in blendTree.children)
switch (child.motion)
{
case AnimationClip animationClip:
ProcessAnimationClip(animationClip);
break;
case BlendTree subBlendTree:
ProcessBlendTree(subBlendTree);
break;
}
}
void ProcessAnimationClip(AnimationClip animationClip)
{
var curve = AnimationUtility.GetEditorCurve(animationClip, ecb);
if (curve == null) return;
AnimationUtility.SetEditorCurve(animationClip, boundProp, curve);
}
void ProcessAnyClip(AnimationDatabase.ClipHolder clip)
{
switch (clip.CurrentClip)
{
case AnimationClip animationClip:
ProcessAnimationClip(animationClip);
break;
case BlendTree blendTree:
ProcessBlendTree(blendTree);
break;
}
}
}
public string ForActiveSelf(string path)
{
return ForBinding(path, typeof(GameObject), "m_IsActive");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1074339e2a59465ba585cb8cbbc4a88c
timeCreated: 1719195449

View File

@ -22,25 +22,29 @@
* SOFTWARE.
*/
#region
using System;
using System.Runtime.CompilerServices;
using nadena.dev.modular_avatar.ui;
using UnityEditor;
using UnityEngine;
#endregion
[assembly: InternalsVisibleTo("Tests")]
namespace nadena.dev.modular_avatar.core.editor
{
public class AvatarProcessor
{
[MenuItem("GameObject/ModularAvatar/Manual bake avatar", true, 100)]
[MenuItem(UnityMenuItems.GameObject_ManualBake, true, UnityMenuItems.GameObject_ManualBakeOrder)]
static bool ValidateApplyToCurrentAvatarGameobject()
{
return ValidateApplyToCurrentAvatar();
}
[MenuItem("GameObject/ModularAvatar/Manual bake avatar", false, 100)]
[MenuItem(UnityMenuItems.GameObject_ManualBake, false, UnityMenuItems.GameObject_ManualBakeOrder)]
static void ApplyToCurrentAvatarGameobject()
{
ApplyToCurrentAvatar();

View File

@ -1,84 +0,0 @@
#region
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using JetBrains.Annotations;
using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
{
internal static class HandleUtilityPatches
{
internal static void Patch_FilterInstanceIDs(Harmony h)
{
var t_HandleUtility = AccessTools.TypeByName("UnityEditor.HandleUtility");
var m_orig = AccessTools.Method(t_HandleUtility, "FilterInstanceIDs");
var m_prefix = AccessTools.Method(typeof(HandleUtilityPatches), "Prefix_FilterInstanceIDs");
var m_postfix = AccessTools.Method(typeof(HandleUtilityPatches), "Postfix_FilterInstanceIDs");
h.Patch(original: m_orig, prefix: new HarmonyMethod(m_prefix), postfix: new HarmonyMethod(m_postfix));
}
[UsedImplicitly]
private static bool Prefix_FilterInstanceIDs(
ref IEnumerable<GameObject> gameObjects,
out int[] parentInstanceIDs,
out int[] childInstanceIDs
)
{
gameObjects = RemapObjects(gameObjects);
parentInstanceIDs = childInstanceIDs = null;
return true;
}
private static void Postfix_FilterInstanceIDs(
ref IEnumerable<GameObject> gameObjects,
ref int[] parentInstanceIDs,
ref int[] childInstanceIDs
)
{
HashSet<int> newChildInstanceIDs = null;
foreach (var parent in gameObjects)
{
foreach (var renderer in parent.GetComponentsInChildren<Renderer>())
{
if (renderer is SkinnedMeshRenderer smr &&
ProxyManager.OriginalToProxyRenderer.TryGetValue(smr, out var proxy) &&
proxy != null)
{
if (newChildInstanceIDs == null) newChildInstanceIDs = new HashSet<int>(childInstanceIDs);
newChildInstanceIDs.Add(proxy.GetInstanceID());
}
}
}
if (newChildInstanceIDs != null)
{
childInstanceIDs = newChildInstanceIDs.ToArray();
}
}
private static IEnumerable<GameObject> RemapObjects(IEnumerable<GameObject> objs)
{
return objs.Select(
obj =>
{
if (obj == null) return obj;
if (ProxyManager.OriginalToProxyObject.TryGetValue(obj, out var proxy) && proxy != null)
{
return proxy.gameObject;
}
else
{
return obj;
}
}
).ToArray();
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 807736f252df4b1b8402827257dcbea3
timeCreated: 1709354699

View File

@ -1,175 +0,0 @@
#region
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using JetBrains.Annotations;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
{
internal static class HierarchyViewPatches
{
private static readonly Type t_HierarchyProperty = AccessTools.TypeByName("UnityEditor.HierarchyProperty");
private static readonly PropertyInfo p_pptrValue = AccessTools.Property(t_HierarchyProperty, "pptrValue");
private static FieldInfo f_m_Rows; // List<TreeViewItem>
private static FieldInfo f_m_RowCount; // int
private static PropertyInfo p_objectPPTR;
internal static void Patch(Harmony h)
{
#if MODULAR_AVATAR_DEBUG_HIDDEN
return;
#endif
var t_GameObjectTreeViewDataSource = AccessTools.TypeByName("UnityEditor.GameObjectTreeViewDataSource");
var t_GameObjectTreeViewItem = AccessTools.TypeByName("UnityEditor.GameObjectTreeViewItem");
f_m_Rows = t_GameObjectTreeViewDataSource.GetField("m_Rows",
BindingFlags.NonPublic | BindingFlags.Instance);
f_m_RowCount =
t_GameObjectTreeViewDataSource.GetField("m_RowCount", BindingFlags.NonPublic | BindingFlags.Instance);
p_objectPPTR = t_GameObjectTreeViewItem.GetProperty("objectPPTR");
var m_orig = AccessTools.Method(t_GameObjectTreeViewDataSource, "InitTreeViewItem",
new[]
{
t_GameObjectTreeViewItem,
typeof(int),
typeof(Scene),
typeof(bool),
typeof(int),
typeof(Object),
typeof(bool),
typeof(int)
});
var m_patch = AccessTools.Method(typeof(HierarchyViewPatches), "Prefix_InitTreeViewItem");
h.Patch(original: m_orig, prefix: new HarmonyMethod(m_patch));
var m_InitRows = AccessTools.Method(t_GameObjectTreeViewDataSource, "InitializeRows");
var m_transpiler = AccessTools.Method(typeof(HierarchyViewPatches), "Transpile_InitializeRows");
h.Patch(original: m_InitRows,
transpiler: new HarmonyMethod(m_transpiler),
postfix: new HarmonyMethod(AccessTools.Method(typeof(HierarchyViewPatches), "Postfix_InitializeRows")),
prefix: new HarmonyMethod(AccessTools.Method(typeof(HierarchyViewPatches), "Prefix_InitializeRows"))
);
}
private static int skipped = 0;
private static void Prefix_InitializeRows()
{
skipped = 0;
}
[UsedImplicitly]
private static void Postfix_InitializeRows(object __instance)
{
var rows = (IList<TreeViewItem>)f_m_Rows.GetValue(__instance);
var rowCount = (int)f_m_RowCount.GetValue(__instance);
f_m_RowCount.SetValue(__instance, rowCount - skipped);
for (int i = 0; i < skipped; i++)
{
rows.RemoveAt(rows.Count - 1);
}
}
[UsedImplicitly]
private static IEnumerable<CodeInstruction> Transpile_InitializeRows(IEnumerable<CodeInstruction> instructions,
ILGenerator generator)
{
foreach (var c in Transpile_InitializeRows0(instructions, generator))
{
//Debug.Log(c);
yield return c;
}
}
[UsedImplicitly]
private static IEnumerable<CodeInstruction> Transpile_InitializeRows0(IEnumerable<CodeInstruction> instructions,
ILGenerator generator)
{
var m_shouldLoop = AccessTools.Method(typeof(HierarchyViewPatches), "ShouldLoop");
var m_Next = AccessTools.Method(t_HierarchyProperty, "Next", new[] { typeof(int[]) });
foreach (var c in instructions)
{
if (c.Is(OpCodes.Callvirt, m_Next))
{
var loopLabel = generator.DefineLabel();
var stash_arg = generator.DeclareLocal(typeof(int[]));
var stash_obj = generator.DeclareLocal(t_HierarchyProperty);
yield return new CodeInstruction(OpCodes.Stloc, stash_arg);
yield return new CodeInstruction(OpCodes.Stloc, stash_obj);
var tmp = new CodeInstruction(OpCodes.Ldloc, stash_obj);
tmp.labels.Add(loopLabel);
yield return tmp;
yield return new CodeInstruction(OpCodes.Ldloc, stash_arg);
yield return new CodeInstruction(OpCodes.Call, m_Next);
// Check if this item should be ignored.
yield return new CodeInstruction(OpCodes.Ldloc, stash_obj);
yield return new CodeInstruction(OpCodes.Call, m_shouldLoop);
yield return new CodeInstruction(OpCodes.Brtrue_S, loopLabel);
}
else
{
yield return c;
}
}
}
[UsedImplicitly]
private static bool ShouldLoop(object hierarchyProperty)
{
if (hierarchyProperty == null) return false;
var pptrValue = p_pptrValue.GetValue(hierarchyProperty);
if (pptrValue == null) return false;
var skip = ProxyManager.ProxyToOriginalObject.ContainsKey((GameObject)pptrValue);
if (skip) skipped++;
return skip;
}
private static bool Prefix_InitTreeViewItem(
object __instance,
ref object item,
int itemID,
Scene scene,
bool isSceneHeader,
int colorCode,
Object pptrObject,
ref bool hasChildren,
int depth
)
{
if (pptrObject == null || isSceneHeader) return true;
if (hasChildren && ProxyManager.ProxyToOriginalObject.ContainsKey((GameObject)pptrObject))
{
// See if there are any other children...
hasChildren = ((GameObject)pptrObject).transform.childCount > 1;
}
return true;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 42f70698a5df48c0908400c425a2f6ee
timeCreated: 1709356304

View File

@ -1,102 +0,0 @@
#region
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
{
internal static class InjectParamsUsageUI
{
private static readonly Type type = AccessTools.TypeByName("UnityEditor.PropertyEditor");
private static readonly PropertyInfo _editorsElement = AccessTools.Property(type, "editorsElement");
private static readonly Type editorElem = AccessTools.TypeByName("UnityEditor.UIElements.EditorElement");
private static readonly PropertyInfo editorElem_editor = AccessTools.Property(editorElem, "editor");
public static void Patch(Harmony h)
{
var type = AccessTools.TypeByName("UnityEditor.PropertyEditor");
var drawEditors = AccessTools.Method(type, "DrawEditors");
h.Patch(drawEditors, transpiler: new HarmonyMethod(typeof(InjectParamsUsageUI), nameof(Transpile)));
var objNames = AccessTools.TypeByName("UnityEditor.ObjectNames");
var m_GetObjectTypeName = AccessTools.Method(objNames, "GetObjectTypeName");
var postfix_GetObjectTypeName =
AccessTools.Method(typeof(InjectParamsUsageUI), nameof(Postfix_GetObjectTypeName));
h.Patch(m_GetObjectTypeName, postfix: new HarmonyMethod(postfix_GetObjectTypeName));
}
private static void Postfix_GetObjectTypeName(ref string __result, Object o)
{
if (o is ModularAvatarInformation)
{
__result = "Modular Avatar Information";
}
}
private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> ci)
{
var target = AccessTools.Method(typeof(VisualElement), "Add");
foreach (var i in ci)
{
if (i.opcode != OpCodes.Callvirt)
{
yield return i;
continue;
}
if (i.opcode == OpCodes.Callvirt
&& i.operand is MethodInfo method
&& method == target
)
{
yield return new CodeInstruction(OpCodes.Ldarg_0);
yield return new CodeInstruction(OpCodes.Call,
AccessTools.Method(typeof(InjectParamsUsageUI), nameof(EditorAdd)));
continue;
}
yield return i;
}
}
private static void EditorAdd(VisualElement container, VisualElement child, object caller)
{
container.Add(child);
var editorsElement = _editorsElement.GetValue(caller) as VisualElement;
if (editorsElement != container)
{
return;
}
if (!child.ClassListContains("game-object-inspector"))
{
return;
}
var editor = editorElem_editor.GetValue(child) as Editor;
if (editor == null) return;
if (editor.targets.Length != 1) return;
if (editor.target is GameObject obj)
{
var elem = new ParamsUsageUI();
container.Add(elem);
}
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5d62a8f41641443ea8bffdc0429e0ad1
timeCreated: 1710223876

View File

@ -13,12 +13,7 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
{
private static readonly Action<Harmony>[] patches = new Action<Harmony>[]
{
HierarchyViewPatches.Patch,
#if UNITY_2022_3_OR_NEWER
HandleUtilityPatches.Patch_FilterInstanceIDs,
PickingObjectPatch.Patch,
InjectParamsUsageUI.Patch,
#endif
//HierarchyViewPatches.Patch,
};
[InitializeOnLoadMethod]

View File

@ -1,78 +0,0 @@
#region
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using UnityEngine;
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
{
internal static class PickingObjectPatch
{
private static Type t_PickingObject = AccessTools.TypeByName("UnityEditor.PickingObject");
private static Type l_PickingObject =
typeof(List<>).MakeGenericType(new[] { t_PickingObject });
private static ConstructorInfo ctor_l = AccessTools.Constructor(l_PickingObject);
private static ConstructorInfo ctor_PickingObject =
AccessTools.Constructor(t_PickingObject, new[] { typeof(Object), typeof(int) });
private static PropertyInfo p_materialIndex = AccessTools.Property(t_PickingObject, "materialIndex");
private static MethodInfo m_TryGetGameObject = AccessTools.Method(t_PickingObject, "TryGetGameObject");
internal static void Patch(Harmony h)
{
var t_PickingObject = AccessTools.TypeByName("UnityEditor.PickingObject");
var ctor_PickingObject = AccessTools.Constructor(t_PickingObject, new[] { typeof(Object), typeof(int) });
var t_SceneViewPicking = AccessTools.TypeByName("UnityEditor.SceneViewPicking");
var m_GetAllOverlapping = AccessTools.Method(t_SceneViewPicking, "GetAllOverlapping");
var m_postfix = AccessTools.Method(typeof(PickingObjectPatch), nameof(Postfix_GetAllOverlapping));
h.Patch(original: m_GetAllOverlapping, postfix: new HarmonyMethod(m_postfix));
}
private static void Postfix_GetAllOverlapping(ref object __result)
{
var erased = (IEnumerable)__result;
var list = (IList)ctor_l.Invoke(new object[0]);
foreach (var obj in erased)
{
if (obj == null)
{
list.Add(obj);
continue;
}
var args = new object[] { null };
if ((bool)m_TryGetGameObject.Invoke(obj, args))
{
var go = args[0] as GameObject;
if (go != null && ProxyManager.ProxyToOriginalObject.TryGetValue(go, out var original))
{
list.Add(ctor_PickingObject.Invoke(new[]
{
original,
p_materialIndex.GetValue(obj)
}));
continue;
}
}
list.Add(obj);
}
__result = list;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: cf06818f1c0c436fbae7f755d7110aba
timeCreated: 1709359553

View File

@ -23,30 +23,30 @@ namespace nadena.dev.modular_avatar.core.editor
// Licensed under the MIT License
private static string[][] boneNamePatterns = new[]
{
new[] {"Hips", "Hip"},
new[] {"Hips", "Hip", "pelvis"},
new[]
{
"LeftUpperLeg", "UpperLeg_Left", "UpperLeg_L", "Leg_Left", "Leg_L", "ULeg_L", "Left leg", "LeftUpLeg",
"UpLeg.L"
"UpLeg.L", "Thigh_L"
},
new[]
{
"RightUpperLeg", "UpperLeg_Right", "UpperLeg_R", "Leg_Right", "Leg_R", "ULeg_R", "Right leg",
"RightUpLeg", "UpLeg.R"
"RightUpLeg", "UpLeg.R", "Thigh_R"
},
new[]
{
"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L", "LLeg_L", "Left knee", "LeftLeg"
"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L", "LLeg_L", "Left knee", "LeftLeg", "leg_L"
},
new[]
{
"RightLowerLeg", "LowerLeg_Right", "LowerLeg_R", "Knee_Right", "Knee_R", "LLeg_R", "Right knee",
"RightLeg"
"RightLeg", "leg_R"
},
new[] {"LeftFoot", "Foot_Left", "Foot_L", "Ankle_L", "Foot.L.001", "Left ankle", "heel.L", "heel"},
new[] {"RightFoot", "Foot_Right", "Foot_R", "Ankle_R", "Foot.R.001", "Right ankle", "heel.R", "heel"},
new[] {"Spine"},
new[] {"Chest", "Bust"},
new[] {"Spine", "spine01"},
new[] {"Chest", "Bust", "spine02"},
new[] {"Neck"},
new[] {"Head"},
new[] {"LeftShoulder", "Shoulder_Left", "Shoulder_L"},
@ -60,8 +60,8 @@ namespace nadena.dev.modular_avatar.core.editor
"RightUpperArm", "UpperArm_Right", "UpperArm_R", "Arm_Right", "Arm_R", "UArm_R", "Right arm",
"UpperRightArm"
},
new[] {"LeftLowerArm", "LowerArm_Left", "LowerArm_L", "LArm_L", "Left elbow", "LeftForeArm", "Elbow_L"},
new[] {"RightLowerArm", "LowerArm_Right", "LowerArm_R", "LArm_R", "Right elbow", "RightForeArm", "Elbow_R"},
new[] {"LeftLowerArm", "LowerArm_Left", "LowerArm_L", "LArm_L", "Left elbow", "LeftForeArm", "Elbow_L", "forearm_L", "ForArm_L"},
new[] {"RightLowerArm", "LowerArm_Right", "LowerArm_R", "LArm_R", "Right elbow", "RightForeArm", "Elbow_R", "forearm_R", "ForArm_R"},
new[] {"LeftHand", "Hand_Left", "Hand_L", "Left wrist", "Wrist_L"},
new[] {"RightHand", "Hand_Right", "Hand_R", "Right wrist", "Wrist_R"},
new[]
@ -80,152 +80,152 @@ namespace nadena.dev.modular_avatar.core.editor
new[]
{
"LeftThumbProximal", "ProximalThumb_Left", "ProximalThumb_L", "Thumb1_L", "ThumbFinger1_L",
"LeftHandThumb1", "Thumb Proximal.L", "Thunb1_L"
"LeftHandThumb1", "Thumb Proximal.L", "Thunb1_L", "finger01_01_L"
},
new[]
{
"LeftThumbIntermediate", "IntermediateThumb_Left", "IntermediateThumb_L", "Thumb2_L", "ThumbFinger2_L",
"LeftHandThumb2", "Thumb Intermediate.L", "Thunb2_L"
"LeftHandThumb2", "Thumb Intermediate.L", "Thunb2_L", "finger01_02_L"
},
new[]
{
"LeftThumbDistal", "DistalThumb_Left", "DistalThumb_L", "Thumb3_L", "ThumbFinger3_L", "LeftHandThumb3",
"Thumb Distal.L", "Thunb3_L"
"Thumb Distal.L", "Thunb3_L", "finger01_03_L"
},
new[]
{
"LeftIndexProximal", "ProximalIndex_Left", "ProximalIndex_L", "Index1_L", "IndexFinger1_L",
"LeftHandIndex1", "Index Proximal.L"
"LeftHandIndex1", "Index Proximal.L", "finger02_01_L"
},
new[]
{
"LeftIndexIntermediate", "IntermediateIndex_Left", "IntermediateIndex_L", "Index2_L", "IndexFinger2_L",
"LeftHandIndex2", "Index Intermediate.L"
"LeftHandIndex2", "Index Intermediate.L", "finger02_02_L"
},
new[]
{
"LeftIndexDistal", "DistalIndex_Left", "DistalIndex_L", "Index3_L", "IndexFinger3_L", "LeftHandIndex3",
"Index Distal.L"
"Index Distal.L", "finger02_03_L"
},
new[]
{
"LeftMiddleProximal", "ProximalMiddle_Left", "ProximalMiddle_L", "Middle1_L", "MiddleFinger1_L",
"LeftHandMiddle1", "Middle Proximal.L"
"LeftHandMiddle1", "Middle Proximal.L", "finger03_01_L"
},
new[]
{
"LeftMiddleIntermediate", "IntermediateMiddle_Left", "IntermediateMiddle_L", "Middle2_L",
"MiddleFinger2_L", "LeftHandMiddle2", "Middle Intermediate.L"
"MiddleFinger2_L", "LeftHandMiddle2", "Middle Intermediate.L", "finger03_02_L"
},
new[]
{
"LeftMiddleDistal", "DistalMiddle_Left", "DistalMiddle_L", "Middle3_L", "MiddleFinger3_L",
"LeftHandMiddle3", "Middle Distal.L"
"LeftHandMiddle3", "Middle Distal.L", "finger03_03_L"
},
new[]
{
"LeftRingProximal", "ProximalRing_Left", "ProximalRing_L", "Ring1_L", "RingFinger1_L", "LeftHandRing1",
"Ring Proximal.L"
"Ring Proximal.L", "finger04_01_L"
},
new[]
{
"LeftRingIntermediate", "IntermediateRing_Left", "IntermediateRing_L", "Ring2_L", "RingFinger2_L",
"LeftHandRing2", "Ring Intermediate.L"
"LeftHandRing2", "Ring Intermediate.L", "finger04_02_L"
},
new[]
{
"LeftRingDistal", "DistalRing_Left", "DistalRing_L", "Ring3_L", "RingFinger3_L", "LeftHandRing3",
"Ring Distal.L"
"Ring Distal.L", "finger04_03_L"
},
new[]
{
"LeftLittleProximal", "ProximalLittle_Left", "ProximalLittle_L", "Little1_L", "LittleFinger1_L",
"LeftHandPinky1", "Little Proximal.L"
"LeftHandPinky1", "Little Proximal.L", "finger05_01_L"
},
new[]
{
"LeftLittleIntermediate", "IntermediateLittle_Left", "IntermediateLittle_L", "Little2_L",
"LittleFinger2_L", "LeftHandPinky2", "Little Intermediate.L"
"LittleFinger2_L", "LeftHandPinky2", "Little Intermediate.L", "finger05_02_L"
},
new[]
{
"LeftLittleDistal", "DistalLittle_Left", "DistalLittle_L", "Little3_L", "LittleFinger3_L",
"LeftHandPinky3", "Little Distal.L"
"LeftHandPinky3", "Little Distal.L", "finger05_03_L"
},
new[]
{
"RightThumbProximal", "ProximalThumb_Right", "ProximalThumb_R", "Thumb1_R", "ThumbFinger1_R",
"RightHandThumb1", "Thumb Proximal.R", "Thunb1_R"
"RightHandThumb1", "Thumb Proximal.R", "Thunb1_R", "finger01_01_R"
},
new[]
{
"RightThumbIntermediate", "IntermediateThumb_Right", "IntermediateThumb_R", "Thumb2_R",
"ThumbFinger2_R", "RightHandThumb2", "Thumb Intermediate.R", "Thunb2_R"
"ThumbFinger2_R", "RightHandThumb2", "Thumb Intermediate.R", "Thunb2_R", "finger01_02_R"
},
new[]
{
"RightThumbDistal", "DistalThumb_Right", "DistalThumb_R", "Thumb3_R", "ThumbFinger3_R",
"RightHandThumb3", "Thumb Distal.R", "Thunb3_R"
"RightHandThumb3", "Thumb Distal.R", "Thunb3_R", "finger01_03_R"
},
new[]
{
"RightIndexProximal", "ProximalIndex_Right", "ProximalIndex_R", "Index1_R", "IndexFinger1_R",
"RightHandIndex1", "Index Proximal.R"
"RightHandIndex1", "Index Proximal.R", "finger02_01_R"
},
new[]
{
"RightIndexIntermediate", "IntermediateIndex_Right", "IntermediateIndex_R", "Index2_R",
"IndexFinger2_R", "RightHandIndex2", "Index Intermediate.R"
"IndexFinger2_R", "RightHandIndex2", "Index Intermediate.R", "finger02_02_R"
},
new[]
{
"RightIndexDistal", "DistalIndex_Right", "DistalIndex_R", "Index3_R", "IndexFinger3_R",
"RightHandIndex3", "Index Distal.R"
"RightHandIndex3", "Index Distal.R", "finger02_03_R"
},
new[]
{
"RightMiddleProximal", "ProximalMiddle_Right", "ProximalMiddle_R", "Middle1_R", "MiddleFinger1_R",
"RightHandMiddle1", "Middle Proximal.R"
"RightHandMiddle1", "Middle Proximal.R", "finger03_01_R"
},
new[]
{
"RightMiddleIntermediate", "IntermediateMiddle_Right", "IntermediateMiddle_R", "Middle2_R",
"MiddleFinger2_R", "RightHandMiddle2", "Middle Intermediate.R"
"MiddleFinger2_R", "RightHandMiddle2", "Middle Intermediate.R", "finger03_02_R"
},
new[]
{
"RightMiddleDistal", "DistalMiddle_Right", "DistalMiddle_R", "Middle3_R", "MiddleFinger3_R",
"RightHandMiddle3", "Middle Distal.R"
"RightHandMiddle3", "Middle Distal.R", "finger03_03_R"
},
new[]
{
"RightRingProximal", "ProximalRing_Right", "ProximalRing_R", "Ring1_R", "RingFinger1_R",
"RightHandRing1", "Ring Proximal.R"
"RightHandRing1", "Ring Proximal.R", "finger04_01_R"
},
new[]
{
"RightRingIntermediate", "IntermediateRing_Right", "IntermediateRing_R", "Ring2_R", "RingFinger2_R",
"RightHandRing2", "Ring Intermediate.R"
"RightHandRing2", "Ring Intermediate.R", "finger04_02_R"
},
new[]
{
"RightRingDistal", "DistalRing_Right", "DistalRing_R", "Ring3_R", "RingFinger3_R", "RightHandRing3",
"Ring Distal.R"
"Ring Distal.R", "finger04_03_R"
},
new[]
{
"RightLittleProximal", "ProximalLittle_Right", "ProximalLittle_R", "Little1_R", "LittleFinger1_R",
"RightHandPinky1", "Little Proximal.R"
"RightHandPinky1", "Little Proximal.R", "finger05_01_R"
},
new[]
{
"RightLittleIntermediate", "IntermediateLittle_Right", "IntermediateLittle_R", "Little2_R",
"LittleFinger2_R", "RightHandPinky2", "Little Intermediate.R"
"LittleFinger2_R", "RightHandPinky2", "Little Intermediate.R", "finger05_02_R"
},
new[]
{
"RightLittleDistal", "DistalLittle_Right", "DistalLittle_R", "Little3_R", "LittleFinger3_R",
"RightHandPinky3", "Little Distal.R"
"RightHandPinky3", "Little Distal.R", "finger05_03_R"
},
new[] {"UpperChest", "UChest"},
};
@ -235,7 +235,7 @@ namespace nadena.dev.modular_avatar.core.editor
internal static string NormalizeName(string name)
{
name = name.ToLowerInvariant();
name = Regex.Replace(name, "[0-9 ._]", "");
name = Regex.Replace(name, "^bone_|[0-9 ._]", "");
return name;
}
@ -419,4 +419,4 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
}
}
}

View File

@ -29,13 +29,14 @@ namespace nadena.dev.modular_avatar.core.editor
{
var color = GUI.contentColor;
var targetObjectProp = property.FindPropertyRelative(nameof(AvatarObjectReference.targetObject));
property = property.FindPropertyRelative(nameof(AvatarObjectReference.referencePath));
try
{
var avatarTransform = findContainingAvatarTransform(property);
if (avatarTransform == null) return false;
bool isRoot = property.stringValue == AvatarObjectReference.AVATAR_ROOT;
bool isNull = string.IsNullOrEmpty(property.stringValue);
Transform target;
@ -43,6 +44,14 @@ namespace nadena.dev.modular_avatar.core.editor
else if (isRoot) target = avatarTransform;
else target = avatarTransform.Find(property.stringValue);
if (targetObjectProp.objectReferenceValue is GameObject go &&
(go.transform == avatarTransform || go.transform.IsChildOf(avatarTransform)))
{
target = go.transform;
isNull = false;
isRoot = target == avatarTransform;
}
var labelRect = position;
position = EditorGUI.PrefixLabel(position, label);
labelRect.width = position.x - labelRect.x;
@ -73,6 +82,8 @@ namespace nadena.dev.modular_avatar.core.editor
property.stringValue = relPath;
}
targetObjectProp.objectReferenceValue = ((Transform)newTarget)?.gameObject;
}
}
else
@ -104,6 +115,8 @@ namespace nadena.dev.modular_avatar.core.editor
property.stringValue = relPath;
}
targetObjectProp.objectReferenceValue = ((Transform)newTarget)?.gameObject;
}
else
{

View File

@ -38,7 +38,7 @@ namespace nadena.dev.modular_avatar.core.editor
if (_window != null) DestroyImmediate(_window);
}
protected virtual void OnDestroy()
protected override void OnDestroy()
{
base.OnDestroy();
if (_window != null) DestroyImmediate(_window);

View File

@ -1,11 +1,13 @@
using System.Collections.Generic;
#region
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
public class LogoElement : VisualElement
@ -18,6 +20,14 @@ namespace nadena.dev.modular_avatar.core.editor
private VisualElement _inner;
public new class UxmlFactory : UxmlFactory<LogoElement, UxmlTraits>
{
}
public new class UxmlTraits : VisualElement.UxmlTraits
{
}
private static void RegisterNode(LogoElement target)
{
if (_logoDisplayNode == null)
@ -85,19 +95,8 @@ namespace nadena.dev.modular_avatar.core.editor
public LogoElement()
{
_inner = new VisualElement();
_inner = new LogoImage();
_inner.style.display = DisplayStyle.None;
_inner.style.flexDirection = FlexDirection.Row;
_inner.style.alignItems = Align.Center;
_inner.style.justifyContent = Justify.Center;
var image = new Image();
image.image = LogoDisplay.LOGO_ASSET;
image.style.width = new Length(LogoDisplay.ImageWidth(LogoDisplay.TARGET_HEIGHT), LengthUnit.Pixel);
image.style.height = new Length(LogoDisplay.TARGET_HEIGHT, LengthUnit.Pixel);
_inner.Add(image);
this.Add(_inner);
RegisterCallback<GeometryChangedEvent>(OnGeomChanged);

View File

@ -0,0 +1,54 @@
#region
using UnityEditor;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
public class LogoImage : VisualElement
{
VisualElement _inner;
public new class UxmlFactory : UxmlFactory<LogoImage, UxmlTraits>
{
}
public new class UxmlTraits : VisualElement.UxmlTraits
{
}
public LogoImage()
{
_inner = new VisualElement();
_inner.style.flexDirection = FlexDirection.Row;
_inner.style.alignItems = Align.Center;
_inner.style.justifyContent = Justify.Center;
var image = new Image();
image.image = LogoDisplay.LOGO_ASSET;
SetImageSize(image);
_inner.Add(image);
Add(_inner);
}
private static void SetImageSize(Image image, int maxTries = 10)
{
var targetHeight = LogoDisplay.TARGET_HEIGHT;
if (targetHeight == 0)
{
if (maxTries <= 0) return;
EditorApplication.delayCall += () => SetImageSize(image, maxTries - 1);
targetHeight = 45;
}
image.style.width = new Length(LogoDisplay.ImageWidth(targetHeight), LengthUnit.Pixel);
image.style.height = new Length(targetHeight, LengthUnit.Pixel);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d12c8166c3f14b78a76dbef22b07fad1
timeCreated: 1715480586

View File

@ -1,13 +1,30 @@
using System;
#region
using System;
using UnityEditor;
using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
internal static class LogoDisplay
{
internal static readonly Texture2D LOGO_ASSET;
internal static float TARGET_HEIGHT => EditorStyles.label.lineHeight * 3;
internal static float TARGET_HEIGHT
{
get {
try
{
return (EditorStyles.label?.lineHeight ?? 0) * 3;
}
catch (NullReferenceException e)
{
// This can happen in early initialization...
return 0;
}
}
}
internal static float ImageWidth(float height)
{
@ -37,7 +54,7 @@ namespace nadena.dev.modular_avatar.core.editor
internal static void DisplayLogo()
{
if (LOGO_ASSET == null) return;
if (LOGO_ASSET == null || EditorStyles.label == null) return;
var height = TARGET_HEIGHT;
var width = ImageWidth(height);

View File

@ -0,0 +1,14 @@
using UnityEditor;
namespace nadena.dev.modular_avatar.core.editor
{
[CustomEditor(typeof(ModularAvatarConvertConstraints))]
[CanEditMultipleObjects]
internal class MAConvertConstraintsEditor : MAEditorBase
{
protected override void OnInnerInspectorGUI()
{
// no UI
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 484ea04548b945ce9cf5fd6d49b50244
timeCreated: 1723778102

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 131d9706ddc04331bd09cf13b863c537
timeCreated: 1723334567

View File

@ -0,0 +1,25 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements"
xmlns:ma="nadena.dev.modular_avatar.core.editor">
<ui:VisualElement name="root-box">
<ui:VisualElement name="group-box">
<ed:PropertyField binding-path="m_inverted" label="reactive_object.inverse" class="ndmf-tr"/>
<ui:VisualElement name="ListViewContainer">
<ui:ListView virtualization-method="DynamicHeight"
reorder-mode="Animated"
reorderable="true"
show-add-remove-footer="true"
show-border="true"
show-foldout-header="false"
name="Shapes"
item-height="100"
binding-path="m_objects"
style="flex-grow: 1;"
/>
</ui:VisualElement>
</ui:VisualElement>
<ma:ROSimulatorButton/>
<ma:LanguageSwitcherElement/>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cd5c518316b2435d8a666911d4131903
timeCreated: 1723334567

View File

@ -0,0 +1,43 @@
#region
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
{
[CustomEditor(typeof(ModularAvatarMaterialSetter))]
public class MaterialSetterEditor : MAEditorBase
{
[SerializeField] private StyleSheet uss;
[SerializeField] private VisualTreeAsset uxml;
protected override void OnInnerInspectorGUI()
{
throw new NotImplementedException();
}
protected override VisualElement CreateInnerInspectorGUI()
{
var root = uxml.CloneTree();
Localization.UI.Localize(root);
root.styleSheets.Add(uss);
root.Bind(serializedObject);
ROSimulatorButton.BindRefObject(root, target);
var listView = root.Q<ListView>("Shapes");
listView.showBoundCollectionSize = false;
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
return root;
}
}
}

View File

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 339dd3848a2044b1aa04f543226de0e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- uss: {fileID: 7433441132597879392, guid: fce9f3fe74434b718abac5ea66775acb, type: 3}
- uxml: {fileID: 9197481963319205126, guid: cd5c518316b2435d8a666911d4131903, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,56 @@
VisualElement {
}
#group-box {
margin-top: 4px;
margin-bottom: 4px;
padding: 4px;
border-width: 3px;
border-left-color: rgba(0, 1, 0, 0.2);
border-top-color: rgba(0, 1, 0, 0.2);
border-right-color: rgba(0, 1, 0, 0.2);
border-bottom-color: rgba(0, 1, 0, 0.2);
border-radius: 4px;
/* background-color: rgba(0, 0, 0, 0.1); */
}
#ListViewContainer {
margin-top: 4px;
}
#group-box > Label {
-unity-font-style: bold;
}
.horizontal {
flex-direction: row;
}
.horizontal #f-object {
flex-grow: 1;
margin-bottom: 1px;
}
#f-material-index {
display: none;
}
#f-material-index-dropdown {
width: 100px;
}
#f-material-index-original {
flex-grow: 1;
}
#f-material {
flex-grow: 1;
}
.horizontal > Label {
align-self: center;
width: 100px;
height: 19px;
margin: 1px -2px 1px 3px;
-unity-text-align: middle-left;
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fce9f3fe74434b718abac5ea66775acb
timeCreated: 1723334567

View File

@ -0,0 +1,158 @@
#region
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
{
[CustomPropertyDrawer(typeof(MaterialSwitchObject))]
public class MaterialSwitchObjectEditor : PropertyDrawer
{
private const string Root = "Packages/nadena.dev.modular-avatar/Editor/Inspector/MaterialSetter/";
private const string UxmlPath = Root + "MaterialSwitchObjectEditor.uxml";
private const string UssPath = Root + "MaterialSetterStyles.uss";
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath).CloneTree();
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
Localization.UI.Localize(uxml);
uxml.styleSheets.Add(uss);
uxml.BindProperty(property);
var f_material_index = uxml.Q<IntegerField>("f-material-index");
var f_material_index_dropdown = uxml.Q<DropdownField>("f-material-index-dropdown");
var f_material_index_original = uxml.Q<ObjectField>("f-material-index-original");
var f_object = uxml.Q<PropertyField>("f-object");
f_object.RegisterValueChangeCallback(evt =>
{
EditorApplication.delayCall += UpdateMaterialDropdown;
});
UpdateMaterialDropdown();
// Link dropdown and original field to material index field
f_material_index.RegisterValueChangedCallback(evt =>
{
f_material_index_dropdown.SetValueWithoutNotify(evt.newValue.ToString());
UpdateOriginalMaterial();
});
f_material_index_dropdown.RegisterValueChangedCallback(evt =>
{
if (evt.newValue != null && int.TryParse(evt.newValue, out var i))
{
f_material_index.value = i;
}
});
f_material_index_original.SetEnabled(false);
return uxml;
void UpdateMaterialDropdown()
{
var sharedMaterials = GetSharedMaterials();
if (sharedMaterials != null)
{
var matCount = sharedMaterials.Length;
f_material_index_dropdown.SetEnabled(true);
f_material_index_dropdown.choices.Clear();
for (int i = 0; i < matCount; i++)
{
f_material_index_dropdown.choices.Add(i.ToString());
}
f_material_index_dropdown.formatListItemCallback = idx_s =>
{
if (string.IsNullOrWhiteSpace(idx_s)) return "";
var idx = int.Parse(idx_s);
if (idx < 0 || idx >= sharedMaterials.Length)
{
return $"<color=\"red\">Element {idx_s}: <???></color>";
}
else if (sharedMaterials[idx] == null)
{
return $"Element {idx_s}: <None>";
}
else
{
return $"Element {idx_s}: {sharedMaterials[idx].name}";
}
};
f_material_index_dropdown.formatSelectedValueCallback = idx_s =>
{
if (string.IsNullOrWhiteSpace(idx_s)) return "";
var idx = int.Parse(idx_s);
if (idx < 0 || idx >= sharedMaterials.Length)
{
return $"<color=\"red\">Element {idx_s}</color>";
}
else
{
return $"Element {idx_s}";
}
};
}
else
{
f_material_index_dropdown.SetEnabled(false);
if (f_material_index_dropdown.choices.Count == 0)
{
f_material_index_dropdown.choices.Add("0");
}
f_material_index_dropdown.formatListItemCallback = idx_s => "<Missing Renderer>";
f_material_index_dropdown.formatSelectedValueCallback = f_material_index_dropdown.formatListItemCallback;
}
UpdateOriginalMaterial();
}
void UpdateOriginalMaterial()
{
var sharedMaterials = GetSharedMaterials();
if (sharedMaterials != null)
{
var idx = f_material_index.value;
if (idx < 0 || idx >= sharedMaterials.Length)
{
f_material_index_original.SetValueWithoutNotify(null);
}
else
{
f_material_index_original.SetValueWithoutNotify(sharedMaterials[idx]);
}
}
else
{
f_material_index_original.SetValueWithoutNotify(null);
}
}
Material[] GetSharedMaterials()
{
var targetObject = AvatarObjectReference.Get(property.FindPropertyRelative("Object"));
try
{
return targetObject?.GetComponent<Renderer>()?.sharedMaterials;
}
catch (MissingComponentException e)
{
return null;
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6361a17f884644988ef3ece7fbe73ab7
timeCreated: 1723334567

View File

@ -0,0 +1,16 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
<ui:VisualElement class="toggled-object-editor">
<ui:VisualElement class="horizontal">
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
</ui:VisualElement>
<ui:VisualElement class="horizontal">
<ed:IntegerField name="f-material-index" binding-path="MaterialIndex"/>
<ui:DropdownField name="f-material-index-dropdown"/>
<ed:ObjectField name="f-material-index-original"/>
</ui:VisualElement>
<ui:VisualElement class="horizontal">
<ui:Label text="reactive_object.material-setter.set-to" class="ndmf-tr"/>
<ed:PropertyField binding-path="Material" label="" name="f-material"/>
</ui:VisualElement>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 55b5e53f6c364089a1871b68e0de17c6
timeCreated: 1723334567

View File

@ -1,13 +1,17 @@
#if MA_VRCSDK3_AVATARS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using nadena.dev.modular_avatar.core.menu;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using static nadena.dev.modular_avatar.core.editor.Localization;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor
{
@ -32,6 +36,7 @@ namespace nadena.dev.modular_avatar.core.editor
private readonly SerializedProperty _submenu;
private readonly ParameterGUI _parameterGUI;
private readonly SerializedProperty _parameterName;
private readonly SerializedProperty _subParamsRoot;
private readonly SerializedProperty _labelsRoot;
@ -46,9 +51,16 @@ namespace nadena.dev.modular_avatar.core.editor
private readonly SerializedProperty _prop_submenuSource;
private readonly SerializedProperty _prop_otherObjSource;
private readonly SerializedProperty _prop_isSynced;
private readonly SerializedProperty _prop_isSaved;
private readonly SerializedProperty _prop_isDefault;
public bool AlwaysExpandContents = false;
public bool ExpandContents = false;
private readonly Dictionary<string, ProvidedParameter> _knownParameters = new();
private bool _parameterSourceNotDetermined;
public MenuItemCoreGUI(SerializedObject obj, Action redraw)
{
_obj = obj;
@ -62,9 +74,11 @@ namespace nadena.dev.modular_avatar.core.editor
_parameterReference = parameterReference;
_redraw = redraw;
InitKnownParameters();
var gameObjects = new SerializedObject(
obj.targetObjects.Select(o =>
(UnityEngine.Object) ((ModularAvatarMenuItem) o).gameObject
(Object) ((ModularAvatarMenuItem) o).gameObject
).ToArray()
);
@ -74,21 +88,68 @@ namespace nadena.dev.modular_avatar.core.editor
_texture = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.icon));
_type = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.type));
var parameter = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
_parameterName = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter.name));
_value = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.value));
_submenu = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
_parameterGUI = new ParameterGUI(parameterReference, parameter, redraw);
_parameterGUI = new ParameterGUI(parameterReference, _parameterName, redraw);
_subParamsRoot = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subParameters));
_labelsRoot = control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.labels));
_prop_submenuSource = obj.FindProperty(nameof(ModularAvatarMenuItem.MenuSource));
_prop_otherObjSource = obj.FindProperty(nameof(ModularAvatarMenuItem.menuSource_otherObjectChildren));
_prop_isSynced = obj.FindProperty(nameof(ModularAvatarMenuItem.isSynced));
_prop_isSaved = obj.FindProperty(nameof(ModularAvatarMenuItem.isSaved));
_prop_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault));
_previewGUI = new MenuPreviewGUI(redraw);
}
private void InitKnownParameters()
{
var paramRef = _parameterReference;
if (_parameterReference == null)
// TODO: This could give incorrect results in some cases when we have multiple objects selected with
// different rename contexts.
paramRef = (_obj.targetObjects[0] as Component)?.gameObject;
if (paramRef == null)
{
_parameterSourceNotDetermined = true;
return;
}
var parentAvatar = RuntimeUtil.FindAvatarInParents(paramRef.transform);
if (parentAvatar == null)
{
_parameterSourceNotDetermined = true;
return;
}
Dictionary<string, ProvidedParameter> rootParameters = new();
foreach (var param in ParameterInfo.ForUI.GetParametersForObject(parentAvatar.gameObject)
.Where(p => p.Namespace == ParameterNamespace.Animator)
)
rootParameters[param.EffectiveName] = param;
var remaps = ParameterInfo.ForUI.GetParameterRemappingsAt(paramRef);
foreach (var remap in remaps)
{
if (remap.Key.Item1 != ParameterNamespace.Animator) continue;
if (rootParameters.ContainsKey(remap.Value.ParameterName))
_knownParameters[remap.Key.Item2] = rootParameters[remap.Value.ParameterName];
}
foreach (var rootParam in rootParameters)
if (!remaps.ContainsKey((ParameterNamespace.Animator, rootParam.Key)))
_knownParameters[rootParam.Key] = rootParam.Value;
}
/// <summary>
/// Builds a menu item GUI for a raw VRCExpressionsMenu.Control reference.
/// </summary>
@ -99,25 +160,62 @@ namespace nadena.dev.modular_avatar.core.editor
{
_obj = _control.serializedObject;
_parameterReference = parameterReference;
InitKnownParameters();
_redraw = redraw;
_name = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.name));
_texture = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.icon));
_type = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.type));
var parameter = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
_parameterName = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter))
.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.parameter.name));
_value = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.value));
_submenu = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subMenu));
_parameterGUI = new ParameterGUI(parameterReference, parameter, redraw);
_parameterGUI = new ParameterGUI(parameterReference, _parameterName, redraw);
_subParamsRoot = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.subParameters));
_labelsRoot = _control.FindPropertyRelative(nameof(VRCExpressionsMenu.Control.labels));
_prop_isSynced = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSynced));
_prop_isSaved = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSaved));
_prop_isDefault = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isDefault));
_prop_submenuSource = null;
_prop_otherObjSource = null;
_previewGUI = new MenuPreviewGUI(redraw);
}
private void DrawHorizontalToggleProp(
SerializedProperty prop,
GUIContent label,
bool? forceMixedValues = null,
bool? forceValue = null
)
{
var toggleSize = EditorStyles.toggle.CalcSize(new GUIContent());
var labelSize = EditorStyles.label.CalcSize(label);
var width = toggleSize.x + labelSize.x + 4;
var rect = EditorGUILayout.GetControlRect(GUILayout.Width(width));
EditorGUI.BeginProperty(rect, label, prop);
if (forceMixedValues != null) EditorGUI.showMixedValue = forceMixedValues.Value;
if (forceValue != null)
{
EditorGUI.ToggleLeft(rect, label, forceValue.Value);
}
else
{
EditorGUI.BeginChangeCheck();
var value = EditorGUI.ToggleLeft(rect, label, prop.boolValue);
if (EditorGUI.EndChangeCheck()) prop.boolValue = value;
}
EditorGUI.EndProperty();
}
public void DoGUI()
{
EditorGUILayout.BeginHorizontal();
@ -136,6 +234,8 @@ namespace nadena.dev.modular_avatar.core.editor
_parameterGUI.DoGUI(true);
ShowInnateParameterGUI();
EditorGUILayout.EndVertical();
if (_texture != null)
@ -329,6 +429,91 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
private void ShowInnateParameterGUI()
{
if (_prop_isDefault == null)
// This is probably coming from a VRC Expressions menu asset.
// For now, don't show the UI in this case.
return;
var paramName = _parameterName.stringValue;
EditorGUILayout.BeginHorizontal();
bool? forceMixedValues = _parameterName.hasMultipleDifferentValues ? true : null;
var knownParameter = _parameterName.hasMultipleDifferentValues
? null
: _knownParameters.GetValueOrDefault(paramName);
var knownParamDefault = knownParameter?.DefaultValue;
var isDefaultByKnownParam =
knownParamDefault != null ? _value.floatValue == knownParamDefault : (bool?)null;
Object controller = knownParameter?.Source;
var controllerIsElsewhere = controller != null && !(controller is ModularAvatarMenuItem);
// If we can't figure out what to reference the parameter names to, disable the UI
controllerIsElsewhere = controllerIsElsewhere || _parameterSourceNotDetermined;
using (new EditorGUI.DisabledScope(
_parameterName.hasMultipleDifferentValues || controllerIsElsewhere)
)
{
// If we have multiple menu items selected, it probably doesn't make sense to make them all default.
// But, we do want to see if _any_ are default.
var anyIsDefault = _prop_isDefault.hasMultipleDifferentValues || _prop_isDefault.boolValue;
var multipleSelections = _obj.targetObjects.Length > 1;
var mixedIsDefault = multipleSelections && anyIsDefault;
using (new EditorGUI.DisabledScope(multipleSelections))
{
DrawHorizontalToggleProp(_prop_isDefault, G("menuitem.prop.is_default"), mixedIsDefault,
multipleSelections ? false : isDefaultByKnownParam);
}
GUILayout.FlexibleSpace();
var isSavedMixed = forceMixedValues ??
(_parameterName.hasMultipleDifferentValues || controllerIsElsewhere ? true : null);
DrawHorizontalToggleProp(_prop_isSaved, G("menuitem.prop.is_saved"), isSavedMixed);
GUILayout.FlexibleSpace();
DrawHorizontalToggleProp(_prop_isSynced, G("menuitem.prop.is_synced"), forceMixedValues,
knownParameter?.WantSynced);
}
if (controllerIsElsewhere)
{
var refStyle = EditorStyles.toggle;
var refContent = new GUIContent("test");
var refRect = refStyle.CalcSize(refContent);
var height = refRect.y + EditorStyles.toggle.margin.top + EditorStyles.toggle.margin.bottom;
GUILayout.FlexibleSpace();
var style = new GUIStyle(EditorStyles.miniButton);
style.fixedWidth = 0;
style.fixedHeight = 0;
style.stretchHeight = true;
style.stretchWidth = true;
style.imagePosition = ImagePosition.ImageOnly;
var icon = EditorGUIUtility.FindTexture("d_Search Icon");
var rect = GUILayoutUtility.GetRect(new GUIContent(), style, GUILayout.ExpandWidth(false),
GUILayout.Width(height), GUILayout.Height(height));
if (GUI.Button(rect, new GUIContent(), style))
{
if (controller is VRCAvatarDescriptor desc) controller = desc.expressionParameters;
Selection.activeObject = controller;
EditorGUIUtility.PingObject(controller);
}
rect.xMin += 2;
rect.yMin += 2;
rect.xMax -= 2;
rect.yMax -= 2;
GUI.DrawTexture(rect, icon);
}
EditorGUILayout.EndHorizontal();
}
private void EnsureLabelCount(int i)
{
if (_labels == null || _labelsRoot.arraySize < i || _labels.Length < i)

View File

@ -206,6 +206,7 @@ namespace nadena.dev.modular_avatar.core.editor
if (source is MenuNodesUnder nodesUnder)
{
GUILayout.BeginHorizontal();
if (GUILayout.Button(G("menuitem.misc.add_item")))
{
var newChild = new GameObject();
@ -214,6 +215,29 @@ namespace nadena.dev.modular_avatar.core.editor
newChild.AddComponent<ModularAvatarMenuItem>();
Undo.RegisterCreatedObjectUndo(newChild, "Added menu item");
}
if (GUILayout.Button(G("menuitem.misc.add_toggle")))
{
var newChild = new GameObject();
newChild.name = "New toggle";
newChild.transform.SetParent(nodesUnder.root.transform, false);
var mami = newChild.AddComponent<ModularAvatarMenuItem>();
mami.Control = new VRCExpressionsMenu.Control()
{
type = VRCExpressionsMenu.Control.ControlType.Toggle,
value = 1,
};
mami.isSaved = true;
mami.isSynced = true;
newChild.AddComponent<ModularAvatarObjectToggle>();
Selection.activeObject = newChild;
Undo.RegisterCreatedObjectUndo(newChild, "Added menu toggle");
}
GUILayout.EndHorizontal();
}
}
}

View File

@ -275,10 +275,11 @@ namespace nadena.dev.modular_avatar.core.editor
}
var desc = node?.GetComponent<VRCAvatarDescriptor>();
if (desc != null)
if (desc?.expressionParameters?.parameters != null)
{
foreach (var param in desc.expressionParameters.parameters)
{
if (param == null) continue;
if (emitted.Add(param.name)) yield return (node, param.name);
}
}

View File

@ -0,0 +1,62 @@
using nadena.dev.modular_avatar.ui;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.ScriptableObjects;
namespace nadena.dev.modular_avatar.core.editor
{
internal static class ToggleCreatorShortcut
{
[MenuItem(UnityMenuItems.GameObject_CreateToggle, false, UnityMenuItems.GameObject_CreateToggleOrder)]
private static void CreateToggle()
{
var selected = Selection.activeGameObject;
if (selected == null) return;
var avatarRoot = RuntimeUtil.FindAvatarTransformInParents(selected.transform);
if (avatarRoot == null) return;
bool createInstaller = true;
Transform parent = avatarRoot;
try
{
var selectedMenuItem = selected.GetComponent<ModularAvatarMenuItem>();
if (selectedMenuItem?.Control?.type == VRCExpressionsMenu.Control.ControlType.SubMenu
&& selectedMenuItem.MenuSource == SubmenuSource.Children
)
{
parent = selected.transform;
createInstaller = false;
}
}
catch (MissingComponentException e)
{
// ignore
}
var toggle = new GameObject("New Toggle");
var objToggle = toggle.AddComponent<ModularAvatarObjectToggle>();
toggle.transform.SetParent(parent, false);
toggle.AddComponent<ModularAvatarMenuItem>().Control = new VRCExpressionsMenu.Control
{
type = VRCExpressionsMenu.Control.ControlType.Toggle,
name = "New Toggle",
value = 1,
};
if (createInstaller)
{
toggle.AddComponent<ModularAvatarMenuInstaller>();
}
Selection.activeGameObject = toggle;
EditorGUIUtility.PingObject(objToggle);
Undo.RegisterCreatedObjectUndo(toggle, "Create Toggle");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7e15fef260544783af5ff1fd5f13acd3
timeCreated: 1723341065

View File

@ -54,7 +54,7 @@ namespace nadena.dev.modular_avatar.core.editor
EditorGUILayout.LabelField(G("mesh_settings.header_probe_anchor"), EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_prop_inherit_probe_anchor, G("mesh_settings.inherit_probe_anchor"));
if (_prop_inherit_probe_anchor.enumValueIndex == (int) ModularAvatarMeshSettings.InheritMode.Set)
if (_prop_inherit_probe_anchor.enumValueIndex is (int) ModularAvatarMeshSettings.InheritMode.Set or (int) ModularAvatarMeshSettings.InheritMode.SetOrInherit)
{
EditorGUILayout.PropertyField(_prop_probe_anchor, G("mesh_settings.probe_anchor"));
}
@ -72,7 +72,7 @@ namespace nadena.dev.modular_avatar.core.editor
EditorGUILayout.LabelField(G("mesh_settings.header_bounds"), EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_prop_inherit_bounds, G("mesh_settings.inherit_bounds"));
if (_prop_inherit_bounds.enumValueIndex == (int) ModularAvatarMeshSettings.InheritMode.Set)
if (_prop_inherit_bounds.enumValueIndex is (int) ModularAvatarMeshSettings.InheritMode.Set or (int) ModularAvatarMeshSettings.InheritMode.SetOrInherit)
{
EditorGUILayout.PropertyField(_prop_root_bone, G("mesh_settings.root_bone"));
EditorGUILayout.PropertyField(_prop_bounds, G("mesh_settings.bounds"));

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using nadena.dev.modular_avatar.core.ArmatureAwase;
using nadena.dev.ndmf.preview;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
@ -15,24 +16,54 @@ namespace nadena.dev.modular_avatar.core.editor
[SerializeField] private StyleSheet uss;
[SerializeField] private VisualTreeAsset uxml;
private ComputeContext _ctx;
private VisualElement _root;
private TransformChildrenNode _groupedNodesElem;
protected override void OnInnerInspectorGUI()
{
throw new System.NotImplementedException();
throw new NotImplementedException();
}
protected override VisualElement CreateInnerInspectorGUI()
{
_root = new VisualElement();
RebuildInnerGUI();
return _root;
}
private void RebuildInnerGUI()
{
_root.Clear();
_ctx = new ComputeContext("MoveIndependentlyEditor");
_root.Add(BuildInnerGUI(_ctx));
}
private VisualElement BuildInnerGUI(ComputeContext ctx)
{
if (this.target == null) return new VisualElement();
_ctx.InvokeOnInvalidate(this, editor => editor.RebuildInnerGUI());
#pragma warning disable CS0618 // Type or member is obsolete
var root = uxml.Localize();
#pragma warning restore CS0618 // Type or member is obsolete
root.styleSheets.Add(uss);
var container = root.Q<VisualElement>("group-container");
MAMoveIndependently target = (MAMoveIndependently) this.target;
var grouped = (target.GroupedBones ?? Array.Empty<GameObject>())
.Select(obj => obj.transform)
.ToImmutableHashSet();
// Note: We specifically _don't_ use an ImmutableHashSet here as we want to update the previously-returned
// set in place to avoid rebuilding GUI elements after the user changes the grouping.
var grouped = ctx.Observe(target,
t => (t.GroupedBones ?? Array.Empty<GameObject>())
.Select(obj => obj.transform)
.ToHashSet(new ObjectIdentityComparer<Transform>()),
(x, y) => x.SetEquals(y)
);
_groupedNodesElem = new TransformChildrenNode(target.transform, grouped);
_groupedNodesElem.AddToClassList("group-root");
@ -41,6 +72,8 @@ namespace nadena.dev.modular_avatar.core.editor
{
Undo.RecordObject(target, "Toggle grouped nodes");
target.GroupedBones = _groupedNodesElem.Active().Select(t => t.gameObject).ToArray();
grouped.Clear();
grouped.UnionWith(target.GroupedBones.Select(obj => obj.transform));
PrefabUtility.RecordPrefabInstancePropertyModifications(target);
};

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 61045dcdc7f24658a5b47fb0b67ab9fe
timeCreated: 1722736548

View File

@ -0,0 +1,25 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements"
xmlns:ma="nadena.dev.modular_avatar.core.editor">
<ui:VisualElement name="root-box">
<ui:VisualElement name="group-box">
<ed:PropertyField binding-path="m_inverted" label="reactive_object.inverse" class="ndmf-tr"/>
<ui:VisualElement name="ListViewContainer">
<ui:ListView virtualization-method="DynamicHeight"
reorder-mode="Animated"
reorderable="true"
show-add-remove-footer="true"
show-border="true"
show-foldout-header="false"
name="Shapes"
item-height="100"
binding-path="m_objects"
style="flex-grow: 1;"
/>
</ui:VisualElement>
</ui:VisualElement>
<ma:ROSimulatorButton/>
<ma:LanguageSwitcherElement/>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 02f9cb4b3be34457870f111d73e2fd2f
timeCreated: 1722736548

View File

@ -0,0 +1,43 @@
#region
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
{
[CustomEditor(typeof(ModularAvatarObjectToggle))]
public class ObjectSwitcherEditor : MAEditorBase
{
[SerializeField] private StyleSheet uss;
[SerializeField] private VisualTreeAsset uxml;
protected override void OnInnerInspectorGUI()
{
throw new NotImplementedException();
}
protected override VisualElement CreateInnerInspectorGUI()
{
var root = uxml.CloneTree();
Localization.UI.Localize(root);
root.styleSheets.Add(uss);
root.Bind(serializedObject);
ROSimulatorButton.BindRefObject(root, target);
var listView = root.Q<ListView>("Shapes");
listView.showBoundCollectionSize = false;
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
return root;
}
}
}

View File

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a77f3b5f35d04831a7896261cabd3370
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- uss: {fileID: 7433441132597879392, guid: b7559b81cea245b68c66602ea0cbbbcf, type: 3}
- uxml: {fileID: 9197481963319205126, guid: 02f9cb4b3be34457870f111d73e2fd2f, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,114 @@
VisualElement {
}
#group-box {
margin-top: 4px;
margin-bottom: 4px;
padding: 4px;
border-width: 3px;
border-left-color: rgba(0, 1, 0, 0.2);
border-top-color: rgba(0, 1, 0, 0.2);
border-right-color: rgba(0, 1, 0, 0.2);
border-bottom-color: rgba(0, 1, 0, 0.2);
border-radius: 4px;
/* background-color: rgba(0, 0, 0, 0.1); */
}
#ListViewContainer {
margin-top: 4px;
}
#group-box > Label {
-unity-font-style: bold;
}
.group-root {
margin-top: 4px;
}
.group-root Toggle {
margin-left: 0;
}
.group-children {
padding-left: 10px;
}
.left-toggle {
display: flex;
flex-direction: row;
}
.toggled-object-editor {
flex-direction: row;
justify-content: center;
align-items: center;
}
.toggled-object-editor #f-object {
flex-grow: 1;
height: 100%;
}
#f-active > Toggle {
margin-top: 0;
margin-bottom: 0;
margin-left: -12px;
margin-right: 3px;
}
.toggled-object-editor PropertyField Label {
display: none;
}
#f-change-type {
width: 75px;
}
.f-value {
width: 40px;
}
#f-value-delete {
display: none;
}
.change-type-delete #f-value {
display: none;
}
.change-type-delete #f-value-delete {
display: flex;
}
/* Add shape window */
.add-shape-popup {
margin: 2px;
}
.vline {
width: 100%;
height: 4px;
border-top-width: 4px;
margin-top: 2px;
margin-bottom: 2px;
border-top-color: rgba(0, 0, 0, 0.2);
}
.add-shape-row {
flex-direction: row;
}
.add-shape-row Button {
flex-grow: 0;
}
.add-shape-popup ScrollView Label.placeholder {
-unity-text-align: middle-center;
}
.add-shape-row Label {
flex-grow: 1;
-unity-text-align: middle-left;
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b7559b81cea245b68c66602ea0cbbbcf
timeCreated: 1722736548

View File

@ -0,0 +1,30 @@
#region
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
{
[CustomPropertyDrawer(typeof(ToggledObject))]
public class ToggledObjectEditor : PropertyDrawer
{
private const string Root = "Packages/nadena.dev.modular-avatar/Editor/Inspector/ObjectToggle/";
private const string UxmlPath = Root + "ToggledObjectEditor.uxml";
private const string UssPath = Root + "ObjectSwitcherStyles.uss";
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath).CloneTree();
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
Localization.UI.Localize(uxml);
uxml.styleSheets.Add(uss);
uxml.BindProperty(property);
return uxml;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d18528c5f704d3daf1160d9672bd09e
timeCreated: 1722736548

View File

@ -0,0 +1,6 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
<ui:VisualElement class="toggled-object-editor">
<ed:PropertyField binding-path="Active" label="" name="f-active"/>
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 565803cb95a04d1f98f7050c18234cdd
timeCreated: 1722736548

View File

@ -1,5 +1,4 @@
using System.Globalization;
using nadena.dev.modular_avatar.core;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
@ -13,8 +12,12 @@ namespace nadena.dev.modular_avatar.core.editor
{
}
private const string V_True = "ON";
private const string V_False = "OFF";
private readonly TextField _visibleField;
private readonly FloatField _defaultValueField;
private readonly DropdownField _boolField;
private readonly Toggle _hasExplicitDefaultSetField;
public DefaultValueField()
@ -22,6 +25,11 @@ namespace nadena.dev.modular_avatar.core.editor
// Hidden binding elements
_defaultValueField = new FloatField();
_hasExplicitDefaultSetField = new Toggle();
_boolField = new DropdownField();
_boolField.choices.Add("");
_boolField.choices.Add(V_True);
_boolField.choices.Add(V_False);
_defaultValueField.RegisterValueChangedCallback(
evt => UpdateVisibleField(evt.newValue, _hasExplicitDefaultSetField.value));
@ -50,10 +58,22 @@ namespace nadena.dev.modular_avatar.core.editor
_defaultValueField.SetEnabled(false);
_hasExplicitDefaultSetField.style.width = 0;
_hasExplicitDefaultSetField.SetEnabled(false);
_boolField.RegisterValueChangedCallback(evt =>
{
if (evt.newValue == V_True)
_defaultValueField.value = 1;
else
_defaultValueField.value = 0;
_hasExplicitDefaultSetField.value = evt.newValue != "";
});
style.flexDirection = FlexDirection.Row;
Add(_visibleField);
Add(_boolField);
Add(_defaultValueField);
Add(_hasExplicitDefaultSetField);
}
@ -73,6 +93,16 @@ namespace nadena.dev.modular_avatar.core.editor
var str = hasExplicitValue ? value.ToString(CultureInfo.InvariantCulture) : "";
_visibleField.SetValueWithoutNotify(str);
string boolStr;
if (!hasExplicitValue)
boolStr = "";
else if (value > 0.5)
boolStr = V_True;
else
boolStr = V_False;
_boolField.SetValueWithoutNotify(boolStr);
}
}
}

View File

@ -2,8 +2,8 @@
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using Toggle = UnityEngine.UIElements.Toggle;
namespace nadena.dev.modular_avatar.core.editor.Parameters
{
@ -20,105 +20,248 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters
Localization.UI.Localize(root);
root.styleSheets.Add(uss);
var foldout = root.Q<Foldout>();
var foldoutLabel = foldout?.Q<Label>();
if (foldoutLabel != null)
{
foldoutLabel.bindingPath = "nameOrPrefix";
}
var miniDisplay = root.Q<VisualElement>("MiniDisplay");
miniDisplay.RemoveFromHierarchy();
foldoutLabel.parent.Add(miniDisplay);
miniDisplay.styleSheets.Add(uss);
var proot = root.Q<VisualElement>("Root");
var type_field = proot.Q<DropdownField>("f-type");
var isPrefixProp = root.Q<PropertyField>("isPrefix");
bool isPrefix = false;
Action evaluateMiniDisplay = () =>
var f_sync_type = proot.Q<VisualElement>("f-sync-type");
SetupPairedDropdownField(
proot,
type_field,
f_sync_type,
proot.Q<VisualElement>("f-is-prefix"),
("Bool", "False", "params.syncmode.Bool"),
("Float", "False", "params.syncmode.Float"),
("Int", "False", "params.syncmode.Int"),
("Not Synced", "False", "params.syncmode.NotSynced"),
(null, "True", "params.syncmode.PhysBonesPrefix")
);
f_sync_type.Q<DropdownField>().RegisterValueChangedCallback(evt =>
{
miniDisplay.style.display = (isPrefix || foldout.value) ? DisplayStyle.None : DisplayStyle.Flex;
var is_anim_only = evt.newValue == "Not Synced";
if (is_anim_only)
proot.AddToClassList("st-anim-only");
else
proot.RemoveFromClassList("st-anim-only");
});
var f_synced = proot.Q<Toggle>("f-synced");
var f_local_only = proot.Q<Toggle>("f-local-only");
// Invert f_local_only and f_synced
f_local_only.RegisterValueChangedCallback(evt => { f_synced.SetValueWithoutNotify(!evt.newValue); });
f_synced.RegisterValueChangedCallback(evt => { f_local_only.value = !evt.newValue; });
var internalParamAccessor = proot.Q<Toggle>("f-internal-parameter");
internalParamAccessor.RegisterValueChangedCallback(evt =>
{
if (evt.newValue)
proot.AddToClassList("st-internal-parameter");
else
proot.RemoveFromClassList("st-internal-parameter");
});
var remapTo = proot.Q<TextField>("f-remap-to");
var defaultParam = proot.Q<Label>("f-default-param");
var name = proot.Q<TextField>("f-name");
var remapToInner = remapTo.Q<TextElement>();
Action updateDefaultParam = () =>
{
if (string.IsNullOrWhiteSpace(remapTo.value))
defaultParam.text = name.value;
else
defaultParam.text = "";
};
foldout.RegisterValueChangedCallback(evt => evaluateMiniDisplay());
isPrefixProp.RegisterValueChangeCallback(evt =>
{
var value = evt.changedProperty.boolValue;
if (value)
{
root.AddToClassList("ParameterConfig__isPrefix_true");
root.RemoveFromClassList("ParameterConfig__isPrefix_false");
}
else
{
root.AddToClassList("ParameterConfig__isPrefix_false");
root.RemoveFromClassList("ParameterConfig__isPrefix_true");
}
isPrefix = value;
evaluateMiniDisplay();
});
var syncTypeProp = root.Q<PropertyField>("syncType");
// TODO: This callback is not actually invoked on initial bind...
syncTypeProp.RegisterValueChangeCallback(evt =>
{
var value = (ParameterSyncType) evt.changedProperty.enumValueIndex;
if (value == ParameterSyncType.NotSynced)
{
root.AddToClassList("ParameterConfig__animatorOnly_true");
root.RemoveFromClassList("ParameterConfig__animatorOnly_false");
}
else
{
root.AddToClassList("ParameterConfig__animatorOnly_false");
name.RegisterValueChangedCallback(evt => { updateDefaultParam(); });
root.RemoveFromClassList("ParameterConfig__animatorOnly_true");
}
});
remapTo.RegisterValueChangedCallback(evt => { updateDefaultParam(); });
/*
var overridePlaceholder = root.Q<Toggle>("overridePlaceholder");
overridePlaceholder.labelElement.AddToClassList("ndmf-tr");
overridePlaceholder.SetEnabled(false);
*/
var remapTo = root.Q<PropertyField>("remapTo");
var remapToPlaceholder = root.Q<TextField>("remapToPlaceholder");
remapToPlaceholder.labelElement.AddToClassList("ndmf-tr");
remapToPlaceholder.SetEnabled(false);
Localization.UI.Localize(remapToPlaceholder.labelElement);
root.Q<PropertyField>("internalParameter").RegisterValueChangeCallback(evt =>
{
remapTo.style.display = evt.changedProperty.boolValue ? DisplayStyle.None : DisplayStyle.Flex;
remapToPlaceholder.style.display = evt.changedProperty.boolValue ? DisplayStyle.Flex : DisplayStyle.None;
});
// This is a bit of a hack, but I'm not sure of another way to properly align property labels with a custom
// field, when we only want to manipulate a subset of fields on an object...
var defaultValueField = root.Q<VisualElement>("innerDefaultValueField"); // create ahead of time so it's bound...
// Then move it into the property field once the property field has created its inner controls
var defaultValueProp = root.Q<PropertyField>("defaultValueProp");
defaultValueProp.RegisterCallback<GeometryChangedEvent>(evt =>
{
var floatField = defaultValueProp.Q<FloatField>();
var innerField = floatField?.Q<DefaultValueField>();
defaultParam.RemoveFromHierarchy();
remapToInner.Add(defaultParam);
if (floatField != null && innerField == null)
{
defaultValueField.RemoveFromHierarchy();
floatField.contentContainer.Add(defaultValueField);
}
});
updateDefaultParam();
return root;
}
private interface Accessor
{
Action<string> OnValueChanged { get; set; }
string Value { get; set; }
}
private class ToggleAccessor : Accessor
{
private readonly Toggle _toggle;
public ToggleAccessor(Toggle toggle)
{
_toggle = toggle;
_toggle.RegisterValueChangedCallback(evt => OnValueChanged?.Invoke(evt.newValue.ToString()));
}
public Action<string> OnValueChanged { get; set; }
public string Value
{
get => _toggle.value.ToString();
set => _toggle.value = value == "True";
}
}
private class DropdownAccessor : Accessor
{
private readonly DropdownField _dropdown;
public DropdownAccessor(DropdownField dropdown)
{
_dropdown = dropdown;
_dropdown.RegisterValueChangedCallback(evt => OnValueChanged?.Invoke(evt.newValue));
}
public Action<string> OnValueChanged { get; set; }
public string Value
{
get => _dropdown.value;
set => _dropdown.value = value;
}
}
private Accessor GetAccessor(VisualElement elem)
{
var toggle = elem.Q<Toggle>();
if (toggle != null) return new ToggleAccessor(toggle);
var dropdown = elem.Q<DropdownField>();
if (dropdown != null)
{
return new DropdownAccessor(dropdown);
}
throw new ArgumentException("Unsupported element type");
}
private void SetupPairedDropdownField(
VisualElement root,
DropdownField target,
VisualElement v_type,
VisualElement v_pbPrefix,
// p1, p2, localization key
params (string, string, string)[] choices
)
{
var p_type = GetAccessor(v_type);
var p_prefix = GetAccessor(v_pbPrefix);
v_type.style.display = DisplayStyle.None;
v_pbPrefix.style.display = DisplayStyle.None;
for (var i = 0; i < choices.Length; i++) target.choices.Add("" + i);
target.formatListItemCallback = s_n =>
{
if (int.TryParse(s_n, out var n) && n >= 0 && n < choices.Length)
{
return Localization.S(choices[n].Item3);
}
else
{
return "";
}
};
target.formatSelectedValueCallback = target.formatListItemCallback;
var inLoop = false;
string current_type_class = null;
target.RegisterValueChangedCallback(evt =>
{
if (inLoop) return;
if (int.TryParse(evt.newValue, out var n) && n >= 0 && n < choices.Length)
{
p_type.Value = choices[n].Item1;
p_prefix.Value = choices[n].Item2;
}
else
{
p_type.Value = "";
p_prefix.Value = "";
}
});
p_type.OnValueChanged = s =>
{
inLoop = true;
try
{
if (!string.IsNullOrWhiteSpace(s))
{
var new_class = "st-ty-" + s.Replace(" ", "-");
root.RemoveFromClassList(current_type_class);
current_type_class = null;
root.AddToClassList(new_class);
current_type_class = new_class;
}
if (string.IsNullOrEmpty(s)) return;
for (var i = 0; i < choices.Length; i++)
if (choices[i].Item1 == s && (choices[i].Item2 == null || choices[i].Item2 == p_prefix.Value))
{
target.SetValueWithoutNotify("" + i);
break;
}
}
finally
{
inLoop = false;
}
};
p_prefix.OnValueChanged = s =>
{
inLoop = true;
try
{
if (string.IsNullOrEmpty(s)) return;
if (bool.TryParse(s, out var b))
{
if (b) root.AddToClassList("st-pb-prefix");
else root.RemoveFromClassList("st-pb-prefix");
}
for (var i = 0; i < choices.Length; i++)
if ((choices[i].Item1 == null || choices[i].Item1 == p_type.Value) && choices[i].Item2 == s)
{
target.SetValueWithoutNotify("" + i);
break;
}
}
finally
{
inLoop = false;
}
};
inLoop = true;
for (var i = 0; i < choices.Length; i++)
if (choices[i].Item1 == p_type.Value && choices[i].Item2 == p_prefix.Value)
{
target.SetValueWithoutNotify("" + i);
break;
}
inLoop = false;
}
}
}
#endif

View File

@ -1,47 +1,48 @@
<ui:UXML
xmlns:ui="UnityEngine.UIElements"
xmlns:engine="UnityEditor.UIElements"
xmlns:ma="nadena.dev.modular_avatar.core.editor"
xmlns:ui="UnityEngine.UIElements"
xmlns:ma="nadena.dev.modular_avatar.core.editor"
editor-extension-mode="False"
>
<ui:VisualElement name="MiniDisplay">
<ui:Label text="merge_parameter.ui.defaultValue" class="ndmf-tr"/>
<ma:DefaultValueField/>
<ui:Label text="merge_parameter.ui.saved" class="ndmf-tr"/>
<ui:Toggle binding-path="saved"/>
<ui:VisualElement name="Root">
<ui:VisualElement class="horizontal no-label">
<ui:TextField binding-path="nameOrPrefix" label="merge_parameter.ui.name" name="f-name" class="ndmf-tr"/>
<ui:DropdownField name="f-type"/>
</ui:VisualElement>
<ui:Toggle binding-path="isPrefix" name="f-is-prefix"/>
<ui:DropdownField binding-path="syncType" name="f-sync-type"/>
<ui:VisualElement class="horizontal small-label">
<ui:Toggle binding-path="internalParameter" name="f-internal-parameter"
text="merge_parameter.ui.internalParameter" class="ndmf-tr no-left-margin"/>
<ui:VisualElement class="v-separator hide-with-internal-param">
<ui:VisualElement/>
</ui:VisualElement>
<ui:Label text="merge_parameter.ui.remapTo"
class="ndmf-tr inner-label hide-with-internal-param no-left-margin"/>
<ui:TextField name="f-remap-to" binding-path="remapTo" class="hide-with-internal-param"/>
<ui:Label name="f-default-param" text="test test test"/>
</ui:VisualElement>
<ui:VisualElement class="horizontal small-label st-pb-prefix__hide ">
<ui:VisualElement class="horizontal">
<ui:Label text="merge_parameter.ui.defaultValue" class="ndmf-tr no-left-margin"/>
<ma:DefaultValueField/>
</ui:VisualElement>
<ui:VisualElement class="horizontal st-anim-only__hide">
<ui:VisualElement class="v-separator">
<ui:VisualElement/>
</ui:VisualElement>
<ui:Toggle binding-path="saved" text="merge_parameter.ui.saved"
class="ndmf-tr st-pb-prefix__first-retained"/>
<ui:Toggle binding-path="localOnly" text="merge_parameter.ui.localOnly" class="ndmf-tr"
name="f-local-only"/>
<ui:Toggle text="merge_parameter.ui.synced" class="ndmf-tr" name="f-synced"/>
<ui:Toggle binding-path="m_overrideAnimatorDefaults" text="merge_parameter.ui.overrideAnimatorDefaults"
class="ndmf-tr"/>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ui:Foldout name="ParameterConfigRoot" text="(placeholder)" value="false">
<engine:PropertyField binding-path="nameOrPrefix" label="merge_parameter.ui.name" name="f-name" class="ndmf-tr ParameterConfig__isPrefix_falseOnly" />
<engine:PropertyField binding-path="nameOrPrefix" label="merge_parameter.ui.prefix" name="f-prefix" class="ndmf-tr ParameterConfig__isPrefix_trueOnly" />
<engine:PropertyField binding-path="remapTo" label="merge_parameter.ui.remapTo" name="remapTo" class="ndmf-tr" />
<ui:TextField label="merge_parameter.ui.remapTo" text="merge_parameter.ui.remapTo.automatic"
name="remapToPlaceholder" enabled="false"
class="ndmf-tr unity-base-field__aligned disabledPlaceholder"/>
<!-- this field is not visible until it's moved into the PropertyField below -->
<ma:DefaultValueField
name="innerDefaultValueField"
class="unity-base-field__input unity-property-field__input"
/>
<engine:PropertyField binding-path="defaultValue" name="defaultValueProp" label="merge_parameter.ui.defaultValue" class="ndmf-tr ParameterConfig__isPrefix_falseOnly">
</engine:PropertyField>
<engine:PropertyField binding-path="saved" label="merge_parameter.ui.saved" class="ndmf-tr ParameterConfig__isPrefix_falseOnly" />"
<engine:PropertyField binding-path="internalParameter" label="merge_parameter.ui.internalParameter" name="internalParameter" class="ndmf-tr" />
<engine:PropertyField binding-path="isPrefix" label="merge_parameter.ui.isPrefix" name="isPrefix" class="ndmf-tr" />
<engine:PropertyField binding-path="syncType" label="merge_parameter.ui.syncType"
class="ParameterConfig__isPrefix_falseOnly ndmf-tr" name="syncType"/>
<engine:PropertyField binding-path="m_overrideAnimatorDefaults" name="overrideDefaults"
label="merge_parameter.ui.overrideAnimatorDefaults"
class="ParameterConfig__isPrefix_falseOnly ndmf-tr"/>
<!-- <ui:Toggle label="merge_parameter.ui.overrideAnimatorDefaults" value="true" enabled="false" name="overridePlaceholder" class="ParameterConfig__isPrefix_falseOnly ParameterConfig__animatorOnly_trueOnly ndmf-tr" /> -->
<engine:PropertyField binding-path="localOnly" label="merge_parameter.ui.localOnly" class="ParameterConfig__isPrefix_falseOnly ParameterConfig__animatorOnly_falseOnly ndmf-tr" />
</ui:Foldout>
</ui:UXML>

View File

@ -1,85 +1,159 @@
VisualElement {}
.ParameterConfig__isPrefix_true .ParameterConfig__isPrefix_falseOnly {
display: none;
/* I hate CSS precedence rules... */
.horizontal .no-left-margin {
margin-left: 0 !important;
}
.ParameterConfig__isPrefix_false .ParameterConfig__isPrefix_trueOnly {
display: none;
.horizontal .no-left-margin.unity-label {
margin-left: 0 !important;
}
.ParameterConfig__animatorOnly_true .ParameterConfig__animatorOnly_falseOnly {
display: none;
.horizontal .no-left-margin Label.unity-label {
margin-left: 0 !important;
}
.ParameterConfig__animatorOnly_false .ParameterConfig__animatorOnly_trueOnly {
display: none;
.horizontal > Label {
height: auto;
}
#defaultValueGroup {
display: flex;
flex-direction: row;
.horizontal > * {
margin-top: 0;
margin-bottom: 0;
}
#defaultValueGroup > .unity-base-field__input {
flex-grow: 1;
}
#defaultValueGroup > .unity-base-field__input > * {
flex-grow: 1;
.v-separator {
width: 1px;
height: 100%;
margin-left: 8px;
margin-right: 8px;
justify-content: center;
align-content: center;
flex-shrink: 0;
}
#defaultValueProp > FloatField > FloatInput {
display: none;
.v-separator VisualElement {
width: 100%;
height: 80%;
background-color: rgba(0, 0, 0, 0.4);
}
#ParameterConfigRoot > DefaultValueField {
display: none;
}
#innerDefaultValueField {
flex-grow: 1;
}
DefaultValueField > TextField {
flex-grow: 1;
margin-left: 0;
}
#MiniDisplay {
flex-direction: row;
align-self: flex-end;
}
#MiniDisplay > * {
align-self: center;
}
#MiniDisplay > DefaultValueField {
max-width: 60px;
flex-grow: 0;
margin-right: 10px;
}
#MiniDisplay > DefaultValueField TextElement {
.horizontal TextField {
margin-left: 0px;
}
#MiniDisplay > Toggle {
margin-right: 5px;
.horizontal TextField Label.unity-label {
margin-left: 0px !important;
}
#ParameterConfigRoot > Toggle .unity-toggle__text {
flex-grow: 1;
.horizontal Label {
padding-top: 0;
padding-bottom: 0;
align-self: center;
}
#UnregisteredParameters #unity-list-view__footer {
.horizontal {
flex-direction: row;
align-content: center;
margin-top: 1px;
}
.horizontal > * {
height: 100%;
}
.no-label Label.unity-base-field__label {
display: none;
}
#UnregisteredParameters Label {
align-self: center;
#Root .horizontal #f-rename-destination {
flex-grow: 1;
}
.inner-label > Label {
margin-left: 6px;
display: none;
}
.small-label Label.unity-label {
min-width: 0;
margin-left: 4px;
}
VisualElement.small-label > * {
flex-grow: 0;
}
VisualElement.small-label > PropertyField {
flex-direction: row;
}
#Root #f-name {
flex-grow: 1;
}
#Root DefaultValueField {
width: 60px;
flex-grow: 0;
}
.st-internal-parameter .hide-with-internal-param {
display: none;
}
DefaultValueField DropdownField {
display: none;
}
.st-ty-Bool DefaultValueField DropdownField {
display: flex;
}
.st-ty-Bool DefaultValueField TextField {
display: none;
}
.st-ty-NotSynced DefaultValueField {
display: none;
}
.st-pb-prefix .st-pb-prefix__hide {
display: none;
}
.st-anim-only .st-anim-only__hide {
display: none;
}
.st-anim-only .st-pb-prefix__first-retained {
margin-left: 0;
}
.st-anim-only .st-pb-prefix__first-retained Label.unity-label {
margin-left: 0;
}
#f-remap-to {
flex-grow: 1;
}
#f-local-only {
display: none;
}
DefaultValueField TextInput {
min-width: 30px;
}
/** Ghostly text for the renameTo text box **/
Label#f-default-param {
position: absolute;
width: 100%;
height: 100%;
margin: 0 0 0 0;
overflow: hidden;
color: rgba(255, 255, 255, 0.4) !important;
}
.DetectedParameter {
@ -90,27 +164,14 @@ DefaultValueField > TextField {
.DetectedParameter > Label {
flex-grow: 1;
align-self: center;
}
.SourceButton {
flex-grow:0;
flex-grow: 0;
align-self: flex-end;
height: 24px;
width: 24px;
padding: 1px;
}
/* Vertically align the reorder handle with the foldout chevron */
#Parameters #unity-list-view__reorderable-handle {
padding-top: 10px;
}
#ParameterConfigRoot .unity-foldout__input > #unity-checkmark {
margin-top: 4px;
align-self: flex-start;
}
#ParameterConfigRoot .unity-foldout__input > Label {
margin-top: 4px;
align-self: flex-start;
}

View File

@ -179,6 +179,8 @@ namespace nadena.dev.modular_avatar.core.editor
}
var handleSize = HandleUtility.GetHandleSize(Tools.handlePosition);
_handleRotation = Tools.handleRotation;
EditorGUI.BeginChangeCheck();
_gizmoScale = Handles.ScaleHandle(_gizmoScale, Tools.handlePosition, _handleRotation, handleSize);
if (EditorGUI.EndChangeCheck())

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2400605596824043a59b55dcb2a8e89a
timeCreated: 1717196942

View File

@ -0,0 +1,80 @@
#region
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
{
[CustomPropertyDrawer(typeof(ChangedShape))]
public class ChangedShapeEditor : PropertyDrawer
{
private const string Root = "Packages/nadena.dev.modular-avatar/Editor/Inspector/ShapeChanger/";
const string UxmlPath = Root + "ChangedShapeEditor.uxml";
const string UssPath = Root + "ShapeChangerStyles.uss";
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UxmlPath).CloneTree();
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
Localization.UI.Localize(uxml);
uxml.styleSheets.Add(uss);
uxml.BindProperty(property);
var f_shape_name = uxml.Q<DropdownField>("f-shape-name");
var f_object = uxml.Q<PropertyField>("f-object");
f_object.RegisterValueChangeCallback(evt =>
{
EditorApplication.delayCall += UpdateShapeDropdown;
});
UpdateShapeDropdown();
uxml.Q<PropertyField>("f-change-type").RegisterCallback<ChangeEvent<string>>(
e =>
{
if (e.newValue == "Delete")
{
uxml.AddToClassList("change-type-delete");
}
else
{
uxml.RemoveFromClassList("change-type-delete");
}
}
);
return uxml;
void UpdateShapeDropdown()
{
var targetObject = AvatarObjectReference.Get(property.FindPropertyRelative("Object"));
List<string> shapeNames;
try
{
var mesh = targetObject?.GetComponent<SkinnedMeshRenderer>()?.sharedMesh;
shapeNames = mesh == null ? null : Enumerable.Range(0, mesh.blendShapeCount)
.Select(x => mesh.GetBlendShapeName(x))
.ToList();
}
catch (MissingComponentException e)
{
shapeNames = null;
}
f_shape_name.SetEnabled(shapeNames != null);
f_shape_name.choices = shapeNames ?? new();
f_shape_name.formatListItemCallback = name => shapeNames != null ? name : "<Missing SkinnedMeshRenderer>";
f_shape_name.formatSelectedValueCallback = f_shape_name.formatListItemCallback;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c0778099a393468c9775fd9a99b321af
timeCreated: 1717198244

View File

@ -0,0 +1,13 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
<ui:VisualElement class="changed-shape-editor">
<ui:VisualElement class="horizontal">
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
<ed:PropertyField binding-path="ChangeType" label="" name="f-change-type"/>
</ui:VisualElement>
<ui:VisualElement class="horizontal">
<ui:DropdownField name="f-shape-name" binding-path="ShapeName"/>
<ed:PropertyField binding-path="Value" label="" name="f-value" class="f-value"/>
<ui:VisualElement name="f-value-delete" class="f-value"/>
</ui:VisualElement>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d8131b91fff04f9e94b71d70105ae05b
timeCreated: 1717198707

View File

@ -0,0 +1,25 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements"
xmlns:ma="nadena.dev.modular_avatar.core.editor">
<ui:VisualElement name="root-box">
<ui:VisualElement name="group-box">
<ed:PropertyField binding-path="m_inverted" label="reactive_object.inverse" class="ndmf-tr"/>
<ui:VisualElement name="ListViewContainer">
<ui:ListView virtualization-method="DynamicHeight"
reorder-mode="Animated"
reorderable="true"
show-add-remove-footer="true"
show-border="true"
show-foldout-header="false"
name="Shapes"
item-height="100"
binding-path="m_shapes"
style="flex-grow: 1;"
/>
</ui:VisualElement>
</ui:VisualElement>
<ma:ROSimulatorButton/>
<ma:LanguageSwitcherElement/>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c8267295b2dc4d90b92dcc289f6d31c4
timeCreated: 1717197307

View File

@ -0,0 +1,94 @@
#region
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using static System.Reflection.BindingFlags;
using PopupWindow = UnityEditor.PopupWindow;
#endregion
namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
{
[CustomEditor(typeof(ModularAvatarShapeChanger))]
public class ShapeChangerEditor : MAEditorBase
{
[SerializeField] private StyleSheet uss;
[SerializeField] private VisualTreeAsset uxml;
private BlendshapeSelectWindow _window;
protected override void OnInnerInspectorGUI()
{
throw new NotImplementedException();
}
protected override VisualElement CreateInnerInspectorGUI()
{
var root = uxml.CloneTree();
Localization.UI.Localize(root);
root.styleSheets.Add(uss);
root.Bind(serializedObject);
ROSimulatorButton.BindRefObject(root, target);
var listView = root.Q<ListView>("Shapes");
listView.showBoundCollectionSize = false;
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
// The Add button callback isn't exposed publicly for some reason...
var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance);
var addButton = (Button)field_addButton.GetValue(listView);
addButton.clickable = new Clickable(OpenAddWindow);
return root;
}
private void OnDisable()
{
if (_window != null) DestroyImmediate(_window);
}
protected override void OnDestroy()
{
base.OnDestroy();
if (_window != null) DestroyImmediate(_window);
}
private void OpenAddWindow()
{
if (_window != null) DestroyImmediate(_window);
_window = CreateInstance<BlendshapeSelectWindow>();
_window.AvatarRoot = RuntimeUtil.FindAvatarTransformInParents((target as ModularAvatarShapeChanger).transform).gameObject;
_window.OfferBinding += OfferBinding;
_window.Show();
}
private void OfferBinding(BlendshapeBinding binding)
{
var changer = target as ModularAvatarShapeChanger;
if (changer.Shapes.Any(x => x.Object.Equals(binding.ReferenceMesh) && x.ShapeName == binding.Blendshape))
{
return;
}
Undo.RecordObject(changer, "Add Shape");
changer.Shapes.Add(new ChangedShape()
{
Object = binding.ReferenceMesh,
ShapeName = binding.Blendshape,
ChangeType = ShapeChangeType.Delete,
Value = 100
});
PrefabUtility.RecordPrefabInstancePropertyModifications(changer);
}
}
}

View File

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 419e9c8f5ec74da9a4cdd702f7e63902
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- uss: {fileID: 7433441132597879392, guid: 8b52b9cfc8514c55af39aeb11de7f279, type: 3}
- uxml: {fileID: 9197481963319205126, guid: c8267295b2dc4d90b92dcc289f6d31c4, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,117 @@
VisualElement {
}
#group-box {
margin-top: 4px;
margin-bottom: 4px;
padding: 4px;
border-width: 3px;
border-left-color: rgba(0, 1, 0, 0.2);
border-top-color: rgba(0, 1, 0, 0.2);
border-right-color: rgba(0, 1, 0, 0.2);
border-bottom-color: rgba(0, 1, 0, 0.2);
border-radius: 4px;
/* background-color: rgba(0, 0, 0, 0.1); */
}
#ListViewContainer {
margin-top: 4px;
}
#group-box > Label {
-unity-font-style: bold;
}
.group-root {
margin-top: 4px;
}
.group-root Toggle {
margin-left: 0;
}
.group-children {
padding-left: 10px;
}
.left-toggle {
display: flex;
flex-direction: row;
}
.changed-shape-editor .horizontal {
flex-direction: row;
}
.changed-shape-editor #f-object {
flex-grow: 1;
}
.changed-shape-editor #f-shape-name {
flex-grow: 1;
}
.changed-shape-editor #f-change-type {
flex-grow: 0;
}
.changed-shape-editor #f-value {
flex-grow: 0;
}
.changed-shape-editor PropertyField Label {
display: none;
}
#f-change-type {
width: 75px;
}
.f-value {
width: 75px;
}
#f-value-delete {
display: none;
}
.change-type-delete #f-value {
display: none;
}
.change-type-delete #f-value-delete {
display: flex;
height: 20px;
}
/* Add shape window */
.add-shape-popup {
margin: 2px;
}
.vline {
width: 100%;
height: 4px;
border-top-width: 4px;
margin-top: 2px;
margin-bottom: 2px;
border-top-color: rgba(0, 0, 0, 0.2);
}
.add-shape-row {
flex-direction: row;
}
.add-shape-row Button {
flex-grow: 0;
}
.add-shape-popup ScrollView Label.placeholder {
-unity-text-align: middle-center;
}
.add-shape-row Label {
flex-grow: 1;
-unity-text-align: middle-left;
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b52b9cfc8514c55af39aeb11de7f279
timeCreated: 1717197307

View File

@ -18,6 +18,7 @@
"params.syncmode.Int": "Int",
"params.syncmode.Float": "Float",
"params.syncmode.Bool": "Bool",
"params.syncmode.PhysBonesPrefix": "PB Prefix",
"params.__comment__": "=== Unity 2019 only strings ===",
"params.autodetect_header": " Autodetected Parameters ",
"params.internal": "Internal",
@ -44,8 +45,8 @@
"merge_parameter.ui.internalParameter.tooltip": "If set, this parameter will be automatically renamed to avoid conflicts with other parameters",
"merge_parameter.ui.isPrefix": "Is PhysBone Prefix",
"merge_parameter.ui.syncType": "Parameter type",
"merge_parameter.ui.localOnly": "Local Only",
"merge_parameter.ui.localOnly.tooltip": "If set, this parameter will not be synced across the network",
"merge_parameter.ui.synced": "Synced",
"merge_parameter.ui.synced.tooltip": "If set, this parameter will be synced across the network",
"merge_parameter.ui.unregistered_foldout": "Unregistered Parameters",
"merge_parameter.ui.add_button": "Add",
"merge_parameter.ui.details": "Parameter Configuration",
@ -125,6 +126,7 @@
"mesh_settings.inherit_mode.Inherit": "Inherit",
"mesh_settings.inherit_mode.Set": "Set",
"mesh_settings.inherit_mode.DontSet": "Don't Set (use mesh as-is)",
"mesh_settings.inherit_mode.SetOrInherit": "Set or inherit if set by parent",
"pb_blocker.help": "This object will not be affected by PhysBones attached to parents.",
"hint.bad_vrcsdk": "Incompatible version of VRCSDK detected.\n\nPlease try upgrading your VRCSDK; if this does not work, check for a newer version of Modular Avatar as well.",
"error.stack_trace": "Stack trace (provide this when reporting bugs!)",
@ -193,6 +195,8 @@
"menuitem.prop.submenu_source.tooltip": "Where to find the items to put inside this submenu",
"menuitem.prop.source_override": "Source object override",
"menuitem.prop.source_override.tooltip": "If specified, this object will be used as the source for the contents of the submenu. Otherwise, children of this menu item will be used.",
"menuitem.prop.is_default": "Is Default",
"menuitem.prop.is_default.tooltip": "If true, this menu item will be selected when the avatar is initially loaded",
"menuitem.prop.is_saved": "Saved",
"menuitem.prop.is_saved.tooltip": "If true, the value of this menu item will be saved when you change avatars or worlds.",
"menuitem.prop.is_synced": "Synced",
@ -221,9 +225,6 @@
"control_group.is_synced.tooltip": "If true, the value of this menu item will be synced to other players across the network.",
"control_group.default_value": "Initial setting",
"control_group.default_value.unset": "(none selected)",
"menuitem.prop.control_group": "Control Group",
"menuitem.prop.control_group.tooltip": "Only one toggle in a given group can be selected at the same time",
"menuitem.prop.is_default": "Is Group Default",
"animation_gen.duplicate_binding": "Controls from different control groups are trying to animate the same parameter. Parameter: {0}",
"animation_gen.multiple_defaults": "Multiple default menu items were found in the same control group.",
"menuitem.misc.add_item": "Add menu item",
@ -246,5 +247,36 @@
"ma_info.param_usage_ui.header": "Expressions Parameter Usage",
"ma_info.param_usage_ui.other_objects": "Other objects on this avatar",
"ma_info.param_usage_ui.free_space": "Unused parameter space ({0} bits)",
"ma_info.param_usage_ui.bits_template": "{0} ({1} bits)"
"ma_info.param_usage_ui.bits_template": "{0} ({1} bits)",
"ma_info.param_usage_ui.no_data": "[ NO DATA ]",
"reactive_object.inverse": "Inverse Condition",
"reactive_object.material-setter.set-to": "Set material to: ",
"menuitem.misc.add_toggle": "Add toggle",
"ro_sim.open_debugger_button": "Open reaction debugger",
"ro_sim.window.title": "MA Reaction Debugger",
"ro_sim.header.inspecting": "Inspecting object",
"ro_sim.header.clear_overrides": "Clear all overrides",
"ro_sim.header.object_state": "Object state",
"ro_sim.state.active": "ACTIVE",
"ro_sim.state.inactive": "INACTIVE",
"ro_sim.header.override_gameobject_state": "Override GameObject state",
"ro_sim.header.override_menuitem_state": "Override MenuItem state",
"ro_sim.affected_by.title": "Affected by:",
"ro_sim.effect_group.component": "Reactive Component",
"ro_sim.effect_group.controls_obj_state": "Controls object state:",
"ro_sim.effect_group.target_component": "Target Component",
"ro_sim.effect_group.target_component.tooltip": "The component that will be affected by the Reactive Object",
"ro_sim.effect_group.property": "Property",
"ro_sim.effect_group.property.tooltip": "The property of the target component that will be affected by the Reactive Object",
"ro_sim.effect_group.value": "Value",
"ro_sim.effect_group.value.tooltip": "The value that the property will be set to when the Reactive Object is active",
"ro_sim.effect_group.material": "Material",
"ro_sim.effect_group.material.tooltip": "The material to set on the target component when the Reactive Object is active",
"ro_sim.effect_group.rule_inverted": "This rule is inverted",
"ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met",
"ro_sim.effect_group.conditions": "Conditions"
}

View File

@ -16,9 +16,10 @@
"params.syncmode.Int": "Int",
"params.syncmode.Float": "Float",
"params.syncmode.Bool": "Bool",
"params.syncmode.PhysBonesPrefix": "PB前置詞",
"params.autodetect_header": " 自動検出されたパラメーター ",
"params.internal": "内部値",
"params.pb_prefix": "PhysBones前置詞",
"params.pb_prefix": "PhysBones接頭辞",
"params.syncmode": "パラメーター型",
"params.saved": "保存する",
"params.synced": "同期する",
@ -28,7 +29,7 @@
"params.remapto.tooltip": "ここに新しい名前を入れることで、名前かぶりを解消できます",
"params.devmode": "プレハブ開発者向け設定を表示",
"merge_parameter.ui.name": "パラメーター名",
"merge_parameter.ui.prefix": "PhysBone 前置詞名",
"merge_parameter.ui.prefix": "PhysBone 接頭辞",
"merge_parameter.ui.remapTo": "名前を変更",
"merge_parameter.ui.remapTo.tooltip": "ここに新しい名前を入れることで、このパラメーターの名前を変更できます。これで名前かぶりを回避したり、あるいはあえて複数のギミックを連動できます。",
"merge_parameter.ui.remapTo.automatic": "(自動的に設定)",
@ -38,20 +39,20 @@
"merge_parameter.ui.saved.tooltip": "保存されたパラメーターは、アバター変更やワールド移動で保持されます",
"merge_parameter.ui.internalParameter": "自動リネーム",
"merge_parameter.ui.internalParameter.tooltip": "有効にすると、名前かぶりを回避するために自動的に名前を変更します",
"merge_parameter.ui.isPrefix": "PhysBone 前置詞名",
"merge_parameter.ui.isPrefix": "PhysBone 接頭辞",
"merge_parameter.ui.syncType": "パラメーター型",
"merge_parameter.ui.localOnly": "ローカルのみ",
"merge_parameter.ui.localOnly.tooltip": "有効にすると、ネットワーク上同期されなくなります",
"merge_parameter.ui.synced": "同期する",
"merge_parameter.ui.synced.tooltip": "有効にすると、ネットワーク上同期されます",
"merge_parameter.ui.unregistered_foldout": "未登録パラメーター",
"merge_parameter.ui.add_button": "追加",
"merge_parameter.ui.details": "パラメーターの詳細設定",
"merge_parameter.ui.overrideAnimatorDefaults": "アニメーターの初期値を設定",
"merge_armature.merge_target": "統合先",
"merge_armature.merge_target.tooltip": "このオブジェクトを統合先のアーマチュアに統合します",
"merge_armature.prefix": "前置詞",
"merge_armature.prefix.tooltip": "このオブジェクトの子に付く前置詞",
"merge_armature.suffix": "後置詞",
"merge_armature.suffix.tooltip": "このオブジェクトの子に付く後置詞",
"merge_armature.prefix": "接頭辞",
"merge_armature.prefix.tooltip": "マージするボーンに付いている接頭辞",
"merge_armature.suffix": "接尾辞",
"merge_armature.suffix.tooltip": "マージするボーンに付いている接尾辞",
"merge_armature.locked": "位置を固定",
"merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け",
"merge_armature.adjust_names": "ボーン名を統合先に合わせる",
@ -121,6 +122,7 @@
"mesh_settings.inherit_mode.Inherit": "継承",
"mesh_settings.inherit_mode.Set": "設定",
"mesh_settings.inherit_mode.DontSet": "設定しない(メッシュ本体の設定のまま)",
"mesh_settings.inherit_mode.SetOrInherit": "親が指定されてる時は継承、または設定",
"pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。",
"hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。",
"error.stack_trace": "スタックトレース(バグを報告する時は必ず添付してください!)",
@ -189,6 +191,8 @@
"menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきかを指定",
"menuitem.prop.source_override": "引用元オブジェクト",
"menuitem.prop.source_override.tooltip": "指定した場合は、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。",
"menuitem.prop.is_default": "初期設定にする",
"menuitem.prop.is_default.tooltip": "ONの場合、アバター初期化の際にこのメニューアイテムを選択します",
"menuitem.prop.is_saved": "保存する",
"menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。",
"menuitem.prop.is_synced": "同期する",
@ -217,9 +221,6 @@
"control_group.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。",
"control_group.default_value": "初期値",
"control_group.default_value.unset": "(どれも選択されない)",
"menuitem.prop.control_group": "コントロールグループ",
"menuitem.prop.control_group.tooltip": "同じグループ内では、一つのトグルしか起動できません",
"menuitem.prop.is_default": "グループの初期設定にする",
"animation_gen.duplicate_binding": "別々のコントロールグループから、同じパラメーターが操作されています。パラメーター:{0}",
"animation_gen.multiple_defaults": "同じコントロールグループに初期設定に指定されたメニューアイテムが複数あります。",
"menuitem.misc.add_item": "メニューアイテムを追加",
@ -242,5 +243,35 @@
"ma_info.param_usage_ui.header": "Expressions Parameter 使用状況",
"ma_info.param_usage_ui.other_objects": "このアバター内の他のオブジェクト",
"ma_info.param_usage_ui.free_space": "未使用領域 ({0} 個のビット)",
"ma_info.param_usage_ui.bits_template": "{0} ({1} 個のビットを使用中)"
"ma_info.param_usage_ui.bits_template": "{0} ({1} 個のビットを使用中)",
"ma_info.param_usage_ui.no_data": "[ NO DATA ]",
"reactive_object.inverse": "条件を反転",
"reactive_object.material-setter.set-to": "変更先のマテリアル ",
"menuitem.misc.add_toggle": "トグルを追加",
"ro_sim.open_debugger_button": "リアクションデバッグツールを開く",
"ro_sim.window.title": "MA リアクションデバッグツール",
"ro_sim.header.inspecting": "表示中のオブジェクト",
"ro_sim.header.clear_overrides": "すべてのオーバーライドを解除",
"ro_sim.header.object_state": "オブジェクトのアクティブ状態",
"ro_sim.state.active": "アクティブ",
"ro_sim.state.inactive": "非アクティブ",
"ro_sim.header.override_gameobject_state": "GameObject のアクティブ状態をオーバーライド",
"ro_sim.header.override_menuitem_state": "MenuItem の選択状態をオーバーライト",
"ro_sim.affected_by.title": "以下のルールに影響されています",
"ro_sim.effect_group.component": "Reactive Component",
"ro_sim.effect_group.controls_obj_state": "オブジェクトのアクティブ状態を設定する➡",
"ro_sim.effect_group.target_component": "コンポーネント",
"ro_sim.effect_group.target_component.tooltip": "Reactive Componentに影響されるコンポーネント",
"ro_sim.effect_group.property": "プロパティ",
"ro_sim.effect_group.property.tooltip": "設定されるプロパティ",
"ro_sim.effect_group.value": "値",
"ro_sim.effect_group.value.tooltip": "上記 Reactive Component が活性状態の時に設定される値",
"ro_sim.effect_group.material": "マテリアル",
"ro_sim.effect_group.material.tooltip": "上記 Reactive Component が活性状態の時に設定されるマテリアル",
"ro_sim.effect_group.rule_inverted": "このルールの条件が反転されています",
"ro_sim.effect_group.rule_inverted.tooltip": "このルールは、いずれかの条件が満たされていない場合に適用されます",
"ro_sim.effect_group.conditions": "条件"
}

View File

@ -44,8 +44,6 @@
"merge_parameter.ui.internalParameter.tooltip": "활성화시, 이름이 중복되는것을 피하여 자동으로 이름을 변경합니다",
"merge_parameter.ui.isPrefix": "PhysBones 접두사",
"merge_parameter.ui.syncType": "파라미터 타입",
"merge_parameter.ui.localOnly": "로컬 전용",
"merge_parameter.ui.localOnly.tooltip": "활성화시, 네트워크상에서 동기화되지 않습니다.",
"merge_parameter.ui.unregistered_foldout": "등록되지않은 파라미터",
"merge_parameter.ui.add_button": "추가",
"merge_parameter.ui.details": "파라미터 상세설정",

View File

@ -42,8 +42,6 @@
"merge_parameter.ui.internalParameter.tooltip": "如果勾选,此参数将会被自动重命名以防止参数名称冲突",
"merge_parameter.ui.isPrefix": "是 PhysBone 的前缀",
"merge_parameter.ui.syncType": "参数模式",
"merge_parameter.ui.localOnly": "仅限本地",
"merge_parameter.ui.localOnly.tooltip": "如果勾选,此参数将不会在网络上同步",
"merge_parameter.ui.unregistered_foldout": "未注册的参数",
"merge_parameter.ui.add_button": "添加",
"merge_parameter.ui.details": "参数设置",
@ -216,9 +214,6 @@
"control_group.is_synced.tooltip": "如果勾选,此菜单项的值将和网络上的其他玩家同步。",
"control_group.default_value": "初始设置",
"control_group.default_value.unset": "(未选择)",
"menuitem.prop.control_group": "控制组",
"menuitem.prop.control_group.tooltip": "在给定的组中一次只能选择一个切换",
"menuitem.prop.is_default": "是组预设",
"animation_gen.duplicate_binding": "来自不同控制组的控制项尝试修改动画相同的参数。参数:{0}",
"animation_gen.multiple_defaults": "在同一个控制组中找到多个默认的菜单项。",
"menuitem.misc.add_item": "添加菜单项",

View File

@ -1,4 +1,6 @@
{
"test0.test_a": "test_a",
"test0.test_b": "test_b",
"boneproxy.foldout.advanced": "進階設定",
"boneproxy.target": "目標",
"menuinstall.help.hint_set_menu": "此 Prefab 的選單預設會安裝到 Avatar 的頂部選單中。如果不需要可以選擇其他選單或取消勾選此元件。",
@ -11,74 +13,77 @@
"menuinstall.devoptions": "Prefab 開發者選項",
"menuinstall.menu_icon_too_large": "選單圖示過大,圖示應小於 256 像素。",
"menuinstall.menu_icon_uncompressed": "選單圖示未設定壓縮。",
"menuinstall.srcmenu": "安裝的選單",
"menuinstall.srcmenu": "安裝的選單",
"params.syncmode.NotSynced": "僅 Animator不同步",
"params.syncmode.Int": "Int",
"params.syncmode.Float": "Float",
"params.syncmode.Bool": "Bool",
"params.syncmode.PhysBonesPrefix": "PB 前綴",
"params.__comment__": "=== Unity 2019 only strings ===",
"params.autodetect_header": " 自動檢測參數 ",
"params.internal": "內部",
"params.pb_prefix": "PhysBones 前綴",
"params.syncmode": "參數模式",
"params.syncmode": "參數類型",
"params.saved": "保存",
"params.synced": "同步",
"params.default": "預設值",
"params.fieldname": "字段名",
"params.remapto": "字段名映射到",
"params.remapto.tooltip": "輸入新的字段名以防止參數名稱衝突",
"params.remapto.tooltip": "輸入新的以防止參數名稱衝突",
"params.devmode": "顯示 Prefab 開發者選項",
"params.__comment1__": "=== Unity 2022 only strings ===",
"merge_parameter.ui.name": "參數名稱",
"merge_parameter.ui.prefix": "PhysBone 前綴名稱",
"merge_parameter.ui.remapTo": "將名稱更改為",
"merge_parameter.ui.remapTo.tooltip": "在這裡輸入一個新名稱以重新命名此參數或前綴。這可以用來解決名稱衝突或連結多種功能模塊。",
"merge_parameter.ui.remapTo.automatic": "(自動鎖定)",
"merge_parameter.ui.defaultValue": "預設值",
"merge_parameter.ui.defaultValue.tooltip": "當重置 Avatar 或第一次使用時,此參數將被設定為該值",
"merge_parameter.ui.defaultValue.tooltip": "首次使用或重置 Avatar 時,參數將被設定為此值",
"merge_parameter.ui.saved": "保存",
"merge_parameter.ui.saved.tooltip": "如果為打勾,當你更換 Avatar 或房間時,參數的值將被保存。",
"merge_parameter.ui.saved.tooltip": "如果為打勾,更換 Avatar 或房間時,參數的值將被保存。",
"merge_parameter.ui.internalParameter": "自動重命名",
"merge_parameter.ui.internalParameter.tooltip": "如果為打勾,參數將會自動重命名以防止參數名稱衝突",
"merge_parameter.ui.internalParameter.tooltip": "如果為打勾,參數將會自動重命名以防止名稱和其他參數衝突",
"merge_parameter.ui.isPrefix": "是 PhysBone 前綴",
"merge_parameter.ui.syncType": "參數模式",
"merge_parameter.ui.localOnly": "僅限本地",
"merge_parameter.ui.localOnly.tooltip": "如果為打勾,參數將會在網路上同步",
"merge_parameter.ui.syncType": "參數類型",
"merge_parameter.ui.synced": "同步",
"merge_parameter.ui.synced.tooltip": "如果為打勾,參數將會在網路上同步",
"merge_parameter.ui.unregistered_foldout": "未註冊的參數",
"merge_parameter.ui.add_button": "添加",
"merge_parameter.ui.details": "參數設定",
"merge_parameter.ui.overrideAnimatorDefaults": "覆蓋 Animator 預設值",
"merge_armature.merge_target": "合併目標",
"merge_armature.merge_target.tooltip": "將當前對象合併到合併目標中",
"merge_armature.merge_target.tooltip": "當前物件要合併到的骨架(或其子級)",
"merge_armature.prefix": "骨骼前綴",
"merge_armature.prefix.tooltip": "合併目標的骨骼前綴",
"merge_armature.prefix.tooltip": "當前物件裡要合併的骨骼前綴",
"merge_armature.suffix": "骨骼後綴",
"merge_armature.suffix.tooltip": "合併目標的骨骼後綴",
"merge_armature.suffix.tooltip": "當前物件裡要合併的骨骼後綴",
"merge_armature.locked": "鎖定位置",
"merge_armature.locked.tooltip": "將當前對象的骨架與合併目標的骨架鎖定,用於動畫的創建。",
"merge_armature.locked.tooltip": "將當前物件的骨架與合併目標的骨架鎖定(反之亦然),常用於創建動畫。",
"merge_armature.adjust_names": "根據合併目標調整骨骼名稱",
"merge_armature.adjust_names.tooltip": "根據合併目標調整骨骼名稱,通常用於與非 Avatar 對應的服裝。",
"merge_armature.mangle_names": "避免命名衝突",
"merge_armature.mangle_names.tooltip": "通過重新命名新添加的骨骼來避免與其他資源發生命名衝突。",
"path_mode.Relative": "相對路徑(基於當前對象",
"path_mode.Absolute": "絕對路徑(基於 Avatar 的 Root",
"merge_animator.animator": "合併的目標 Animator",
"path_mode.Relative": "相對路徑(基於當前物件",
"path_mode.Absolute": "絕對路徑(基於 Avatar 的",
"merge_animator.animator": "合併的 Animator",
"merge_animator.layer_type": "Layer 類型",
"merge_animator.delete_attached_animator": "刪除額外的 Animator",
"merge_animator.delete_attached_animator.tooltip": "合併後刪除當前對象上的 Animator",
"merge_animator.delete_attached_animator.tooltip": "合併後刪除當前物件上的 Animator 元件",
"merge_animator.path_mode": "路徑模式",
"merge_animator.path_mode.tooltip": "在動畫中路徑的工作模式。\n使用相對路徑可以讓你在當前對象上錄制動畫。",
"merge_animator.path_mode.tooltip": "在動畫中路徑的工作模式。\n使用相對路徑可以讓你在當前物件上錄制動畫。",
"merge_animator.match_avatar_write_defaults": "配合 Avatar 的 Write Defaults 設定",
"merge_animator.match_avatar_write_defaults.tooltip": "使用與 Avatar 一樣的 Write Defaults 設定。\n如果設定存在衝突,將保持目標 Animator 的設定。",
"merge_animator.relative_path_root": "相對路徑根對象",
"merge_animator.relative_path_root.tooltip": "解析相對路徑時使用的根對象。\n如果未指定則使用當前對象。",
"merge_animator.match_avatar_write_defaults.tooltip": "使用與 Avatar 一樣的 Write Defaults 設定。\n如果 Avatar 為 WD 混用Animator 的 WD 設定將維持不變。",
"merge_animator.relative_path_root": "相對路徑根物件",
"merge_animator.relative_path_root.tooltip": "解析相對路徑時使用的根物件。\n如果未指定則使用當前物件。",
"merge_animator.layer_priority": "動畫層優先級",
"merge_animator.layer_priority.tooltip": "控制動畫層合併到目標 Animator 中的順序\n由小到大。負值將合併於原有動畫層之前\n0 或正值將合併於之後。",
"merge_animator.layer_priority.tooltip": "控制動畫層合併後在 Animator 裡的位置\n由小到大。負值將合併於原有動畫層之前\n0 或正值將合併於之後。",
"merge_armature.lockmode": "位置同步模式",
"merge_armature.lockmode.not_locked.title": "不同步",
"merge_armature.lockmode.not_locked.body": "Avatar 骨骼和合併目標骨骼不進行位置同步",
"merge_armature.lockmode.base_to_merge.title": "Avatar =====> 合併目標(單向)",
"merge_armature.lockmode.base_to_merge.body": "Avatar 骨骼位置改變,合併目標骨骼也會改變。\n合併目標骨骼位置改變Avatar 骨骼不會改變。\n建議一般服裝使用此模式因為此模式允許調整合併目標的骨骼。",
"merge_armature.lockmode.bidirectional.title": "Avatar <=====> 合併目標(雙向)",
"merge_armature.lockmode.bidirectional.body": "Avatar 骨骼和合併目標骨骼的位置始終相同。\n此模式對創建基於 Avatar 骨骼的動畫時非常有用。\n啟用此模式要求 Avatar 骨骼與合併目標骨骼位置完全相同。",
"merge_armature.lockmode.not_locked.body": "當前物件的骨骼不會和 Avatar 骨骼位置同步",
"merge_armature.lockmode.base_to_merge.title": "Avatar =====> 物件(單向)",
"merge_armature.lockmode.base_to_merge.body": "Avatar 骨骼位置改變,當前物件的骨骼也會改變。\n當前物件的骨骼位置改變Avatar 骨骼不會改變。\n建議一般服裝使用此模式因為此模式允許調整服裝的骨骼。",
"merge_armature.lockmode.bidirectional.title": "Avatar <=====> 物件(雙向)",
"merge_armature.lockmode.bidirectional.body": "Avatar 骨骼和當前物件的骨骼的位置始終相同。\n此模式對創建基於 Avatar 骨骼的動畫時非常有用。\n啟用此模式要求 Avatar 骨骼和當前物件的骨骼位置完全相同。",
"merge_armature.reset_pos": "將位置與 Avatar 進行對齊",
"merge_armature.reset_pos.info": "此命令將強制服裝骨骼與 Avatar 骨骼進行對齊,在使用非 Avatar 對應的服裝時可能有幫助。",
"merge_armature.reset_pos.adjust_rotation": "也對齊旋轉",
@ -88,21 +93,21 @@
"merge_armature.reset_pos.heuristic_scale.tooltip": "以臂展作為參考,調整服裝的整體比例。\n推薦用於非 Avatar 對應的服裝。",
"merge_blend_tree.blend_tree": "Blend Tree",
"merge_blend_tree.path_mode": "路徑模式",
"merge_blend_tree.path_mode.tooltip": "在動畫中路徑的工作模式。\n使用相對路徑可以讓你在當前對象上錄制動畫。",
"merge_blend_tree.relative_path_root": "相對路徑根對象",
"merge_blend_tree.relative_path_root.tooltip": "解析相對路徑時使用的根對象。\n如果未指定則使用當前對象。",
"merge_blend_tree.path_mode.tooltip": "在動畫中路徑的工作模式。\n使用相對路徑可以讓你在當前物件上錄制動畫。",
"merge_blend_tree.relative_path_root": "相對路徑根物件",
"merge_blend_tree.relative_path_root.tooltip": "解析相對路徑時使用的根物件。\n如果未指定則使用當前物件。",
"worldfixed.quest": "此元件未生效,因為它與 Android 環境不相容。",
"worldfixed.normal": "當前對象將會固定於世界,除非你使用約束將它綁在 Avatar 內。",
"fpvisible.normal": "當前對象將在第一人稱視角中可見。",
"worldfixed.normal": "當前物件將會固定於世界,除非你使用約束將它綁在 Avatar 內。",
"fpvisible.normal": "當前物件將在第一人稱視角中可見。",
"fpvisible.NotUnderHead": "此元件未生效,因為它需要放置在 Head 骨骼下。",
"fpvisible.quest": "此元件未生效,因為它與 Android 環境不相容。",
"fpvisible.InPhysBoneChain": "當前對象由 PhysicsBone 控制,可能無法在第一人稱視角中可見;請指定 PhysicsBone 鏈的起點。",
"fpvisible.InPhysBoneChain": "當前物件由 Physics Bone 控制,可能無法在第一人稱視角中可見;請指定 Physics Bone 鏈的起點。",
"blendshape.mesh": "網格",
"blendshape.source": "源 blendshape",
"blendshape.target": "目標 blendshape",
"blendshape.target": "當前網格的 blendshape",
"hint.not_in_avatar": "此元件需要放置於你的 Avatar 內才能工作。",
"boneproxy.err.MovingTarget": "您不能指定將由其他 Modular Avatar 元件移動的目標對象",
"boneproxy.err.NotInAvatar": "你必須指定一個在 Avatar 內的對象",
"boneproxy.err.MovingTarget": "你不能指定將由其他 Modular Avatar 元件移動的物件",
"boneproxy.err.NotInAvatar": "你必須指定一個在 Avatar 內的物件",
"boneproxy.attachment": "附加模式",
"boneproxy.attachment.AsChildAtRoot": "作為子級,放置於 Root",
"boneproxy.attachment.AsChildKeepWorldPose": "作為子級,保持原有位置和選轉",
@ -111,7 +116,7 @@
"mesh_settings.header_probe_anchor": "錨點覆蓋設定",
"mesh_settings.inherit_probe_anchor": "錨點覆蓋模式",
"mesh_settings.probe_anchor": "錨點覆蓋",
"mesh_settings.probe_anchor.tooltip": "Anchor Override\n設定用於當前對象和子對象內的渲染器的錨點覆蓋。",
"mesh_settings.probe_anchor.tooltip": "Anchor Override\n設定用於當前物件和子物件內的渲染器的錨點覆蓋",
"mesh_settings.header_bounds": "網格邊界設定",
"mesh_settings.inherit_bounds": "網格邊界模式",
"mesh_settings.root_bone": "根骨骼",
@ -119,18 +124,19 @@
"mesh_settings.bounds": "網格邊界",
"mesh_settings.bounds.tooltip": "網格的邊界Bounds\n用於確定何時要略過螢幕外的網格渲染。",
"mesh_settings.inherit_mode.Inherit": "繼承",
"mesh_settings.inherit_mode.Set": "設定",
"mesh_settings.inherit_mode.DontSet": "不設定(保持原有的設定)",
"pb_blocker.help": "當前對象不會受到附加的父對象的 PhysBones 的影響。",
"mesh_settings.inherit_mode.Set": "指定",
"mesh_settings.inherit_mode.DontSet": "保持原設定",
"mesh_settings.inherit_mode.SetOrInherit": "父級設定優先,否則指定",
"pb_blocker.help": "當前物件不會受到附加在父物件的 PhysBones 影響。",
"hint.bad_vrcsdk": "檢測到不相容的 VRCSDK 版本。\n\n請嘗試升級 VRCSDK如果這不起作用請嘗試新版本的 Modular Avatar。",
"error.stack_trace": "Stack trace報告錯誤時提供此資訊!)",
"error.stack_trace": "Stack trace報告錯誤時提供此資訊!)",
"error.merge_armature.circular_dependency": "[MA-0001] 在 Merge armature 中存在循環引用",
"error.merge_armature.circular_dependency:description": "你的 Merge Armature 元件正在將自身或其子級作為合併目標。",
"error.merge_armature.circular_dependency:hint": "通常應在 Merge Armature 的合併目標欄位指定 Avatar 本身的 Armture。不要指定服裝本身",
"error.merge_armature.physbone_on_humanoid_bone": "[MA-0002] 在 Humanoid 骨骼上有 PhysBones 元件",
"error.merge_armature.physbone_on_humanoid_bone:hint": "在要合併的骨架中,某些 Humanoid 骨骼受到 PhysBones 控制,其位置與合併目標中相應的 Humanoid 骨骼不同。你應該在要合併的骨架中,移除這些 Humanoid 骨骼上的 PhysBones。",
"error.merge_blend_tree.missing_tree": "[MA-0009] 未指定 Blend Tree",
"error.merge_blend_tree.missing_tree:hint": "Merge Blend Tree 需要知道要合併到哪個 Blend Tree。請嘗試在「Blend Tree」中設定一個對象。",
"error.merge_blend_tree.missing_tree:hint": "Merge Blend Tree 需要知道要合併到哪個 Blend Tree。請嘗試在「Blend Tree」中設定一個物件。",
"error.internal_error": "[MA-9999] 發生內部錯誤:{0}\nwhen processing:",
"error.merge_animator.param_type_mismatch": "[MA-0003] 參數類型不符",
"error.merge_animator.param_type_mismatch:description": "參數 {0} 具有多種類型:{1} != {2}",
@ -141,37 +147,37 @@
"error.rename_params.default_value_conflict": "[MA-0007] 預設值衝突",
"error.rename_params.default_value_conflict:description": "參數 {0} 指定了多個預設值:{1} != {2}",
"error.rename_params.default_value_conflict:hint": "為了避免不可預測的行為,請將 MA Parameters 元件中所有預設值留空只留一個。如果存在多個值Modular Avatar 將選擇第一個預設值。",
"error.replace_object.null_target": "[MA-0008] 未指定替代對象",
"error.replace_object.null_target:hint": "Replace object 需要一個對象來替代。嘗試指定一個。",
"error.replace_object.null_target": "[MA-0008] 未指定要替換的物件",
"error.replace_object.null_target:hint": "Replace object 需要知道要替換掉哪個物件。嘗試指定一個。",
"validation.blendshape_sync.no_local_renderer": "[MA-1000] 在此物件上找不到 Renderer",
"validation.blendshape_sync.no_local_renderer:hint": "Blendshape Sync 作用於相同物件上的 Skinned Mesh Renderer。你是否將它附加到正確的對象上?",
"validation.blendshape_sync.no_local_renderer:hint": "Blendshape Sync 作用於所在物件上的 Skinned Mesh Renderer。你是否將它附加到正確的物件上?",
"validation.blendshape_sync.no_local_mesh": "[MA-1001] 在此物件的 Renderer 上找不到網格Mesh",
"validation.blendshape_sync.no_local_mesh:hint": "當前對象上的 Skinned Mesh Renderer 配置可能有問題。請嘗試從原 Prefab 或 FBX 重新設定對象。",
"validation.blendshape_sync.no_local_mesh:hint": "當前物件上的 Skinned Mesh Renderer 配置可能有問題。請嘗試從原 Prefab 或 FBX 重新設定物件。",
"validation.blendshape_sync.no_bindings": "[MA-1002] 在此物件上找不到 BlendShape",
"validation.blendshape_sync.no_bindings:hint": "Blendshape Sync 需知道要同步哪些 blendshapes。點擊「+」新增。",
"validation.blendshape_sync.missing_local_shape": "[MA-1003] 找不到目標 BlendShape「{0}」",
"validation.blendshape_sync.missing_local_shape:description": "找不到目標 BlendShape{0}",
"validation.blendshape_sync.missing_local_shape:hint": "配置為從目標對象「接收」值的 BlendShape 缺失。請嘗試更換被標紅的 Blendshape。",
"validation.blendshape_sync.no_bindings:hint": "Blendshape Sync 知道要同步哪些 blendshape。點擊「+」新增。",
"validation.blendshape_sync.missing_local_shape": "[MA-1003] 找不到當前網格的 BlendShape「{0}」",
"validation.blendshape_sync.missing_local_shape:description": "找不到當前網格的 BlendShape{0}",
"validation.blendshape_sync.missing_local_shape:hint": "找不到從「接收」數值的 BlendShape。請更換成被標紅的 Blendshape。",
"validation.blendshape_sync.missing_target_shape": "[MA-1004] 找不到源 BlendShape「{0}」",
"validation.blendshape_sync.missing_target_shape:description": "找不到源 BlendShape{0}",
"validation.blendshape_sync.missing_target_shape:hint": "配置為向本地對象「發送」值的 BlendShape 缺失。請嘗試更換被標紅的 Blendshape。",
"validation.blendshape_sync.no_target": "[MA-1005] 未指定目標對象(網格)",
"validation.blendshape_sync.no_target:hint": "Blendshape Sync 需要知道要從哪個對象同步 Blendshape。請嘗試在「網格」中設定一個對象。",
"validation.blendshape_sync.missing_target_renderer": "[MA-1006] 在目標對象上找不到 Renderer",
"validation.blendshape_sync.missing_target_renderer:hint": "Blendshape Sync 會從目標對象上的 Skinned Mesh Renderer 接收 blendshape 的值。你是否將它附加到正確的對象上?",
"validation.blendshape_sync.missing_target_mesh": "[MA-1007] 在目標對象的渲染器上找不到網格Mesh",
"validation.blendshape_sync.missing_target_mesh:hint": "目標對象上的 Skinned Mesh Renderer 配置可能有問題。請嘗試從原 Prefab 或 FBX 重新設定對象。",
"validation.bone_proxy.no_target": "[MA-1100] 未指定目標對象,或未找到目標對象",
"validation.bone_proxy.no_target:hint": "Bone Proxy 需要知道要將當前對象綁定到哪個目標對象。請嘗試在「目標」中設定一個當前對象應該綁定到的目標對象。",
"validation.menu_installer.no_menu": "[MA-1200] 未指定安裝的選單",
"validation.menu_installer.no_menu:hint": "Menu Installer 需要知道要安裝哪個選單。請嘗試設定「Prefab 開發者選項」裡的「安裝的選單」或是新增一個「MA Menu Item」元件。",
"validation.merge_animator.no_animator": "[MA-1300] 未指定要合併的目標 Animator",
"validation.merge_animator.no_animator:hint": "Merge Animator 需要知道要合併到哪個 Animator。請嘗試在「合併的目標 Animator」中設定一個對象。",
"validation.blendshape_sync.missing_target_shape:hint": "找不到向當前物件「發送」數值的 BlendShape。請更換被標紅的 Blendshape。",
"validation.blendshape_sync.no_target": "[MA-1005] 未指定目標物件(網格)",
"validation.blendshape_sync.no_target:hint": "Blendshape Sync 需要知道要從哪個物件那同步 Blendshape。請在「網格」中指定。",
"validation.blendshape_sync.missing_target_renderer": "[MA-1006] 在目標物件上找不到 Renderer",
"validation.blendshape_sync.missing_target_renderer:hint": "Blendshape Sync 會從目標物件上的 Skinned Mesh Renderer 接收 blendshape 的值。你是否將它附加到正確的物件上?",
"validation.blendshape_sync.missing_target_mesh": "[MA-1007] 在目標物件的 Renderer 上找不到網格Mesh",
"validation.blendshape_sync.missing_target_mesh:hint": "目標物件上的 Skinned Mesh Renderer 配置可能有問題。請嘗試從原 Prefab 或 FBX 重新設定對象。",
"validation.bone_proxy.no_target": "[MA-1100] 未指定目標物件,或未找到目標物件",
"validation.bone_proxy.no_target:hint": "Bone Proxy 需要知道要將當前物件綁定到哪個物件。請在「目標」中指定。",
"validation.menu_installer.no_menu": "[MA-1200] 未指定安裝的選單",
"validation.menu_installer.no_menu:hint": "Menu Installer 需要知道要安裝哪個選單。請在「Prefab 開發者選項」裡「要安裝的選單」指定或是新增一個「MA Menu Item」元件。",
"validation.merge_animator.no_animator": "[MA-1300] 未指定要合併的 Animator",
"validation.merge_animator.no_animator:hint": "Merge Animator 需要知道要合併哪個 Animator。請在「要合併的 Animator」中指定。",
"validation.merge_armature.no_target": "[MA-1400] 未指定合併目標",
"validation.merge_armature.no_target:hint": "Merge Armature 需要知道要合併到哪個骨架裡。請嘗試在「合併目標」中設定一個對象。",
"validation.merge_armature.target_is_child": "[MA-1500] 合併目標不能是此對象的子級",
"validation.merge_armature.target_is_child:hint": "Merge Armature 不能合併一個骨架到其自身。請嘗試將「合併目標」設定為其他對象。",
"submenu_source.Children": "子對象",
"validation.merge_armature.no_target:hint": "Merge Armature 需要知道要合併到哪個骨架裡。請在「合併目標」中指定。",
"validation.merge_armature.target_is_child": "[MA-1500] 合併目標不能是此物件的子級",
"validation.merge_armature.target_is_child:hint": "Merge Armature 不能合併骨架到其自身。請將「合併目標」設定為其他物件。",
"submenu_source.Children": "子物件",
"submenu_source.MenuAsset": "角色控制選單資源 (Expressions Menu)",
"menuitem.showcontents": "顯示選單內容",
"menuitem.prop.name": "名稱",
@ -180,21 +186,23 @@
"menuitem.prop.type": "類型",
"menuitem.prop.type.tooltip": "此選單項的類型",
"menuitem.prop.value": "參數值",
"menuitem.prop.value.tooltip": "選單項觸發時設定的參數值",
"menuitem.prop.value.tooltip": "設定選單項觸發時的參數值",
"menuitem.prop.parameter": "參數",
"menuitem.prop.label": "名稱",
"menuitem.prop.submenu_asset": "子選單資源",
"menuitem.prop.submenu_asset.tooltip": "用作子選單的資源文件",
"menuitem.prop.submenu_source": "子選單來源",
"menuitem.prop.submenu_source.tooltip": "尋找子選單的選單項的方式",
"menuitem.prop.source_override": "源對象",
"menuitem.prop.source_override.tooltip": "如果指定,此對象將被用作子選單內容的來源。\n否則將使用此選單項目的子級。",
"menuitem.prop.source_override": "源物件",
"menuitem.prop.source_override.tooltip": "如果指定,這個物件將被用作子選單內容的來源。\n否則將使用此選單項的子級。",
"menuitem.prop.is_default": "預設啟用",
"menuitem.prop.is_default.tooltip": "如果打勾,初次使用或重置 Avatar 時會啟用此選單項",
"menuitem.prop.is_saved": "保存",
"menuitem.prop.is_saved.tooltip": "如果打勾,當您更換 Avatar 或房間時,此選單項目的值將被保存。",
"menuitem.prop.is_saved.tooltip": "如果打勾,當您更換 Avatar 或房間時,此選單項的值將被保存。",
"menuitem.prop.is_synced": "同步",
"menuitem.prop.is_synced.tooltip": "如果打勾,此選單項的值將和網路上的其他玩家同步。",
"menuitem.prop.is_synced.tooltip": "如果打勾,此選單項的值將和網路上的其他玩家同步。",
"menuitem.param.rotation": "參數: 旋轉 (Rotation)",
"menuitem.param.rotation.tooltip": "基於此選單項的旋轉設定的參數。",
"menuitem.param.rotation.tooltip": "基於此選單項的旋轉設定的參數。",
"menuitem.param.horizontal": "參數: 水平 (Horizontal)",
"menuitem.param.horizontal.tooltip": "基於搖桿的水平位置設定的參數。",
"menuitem.param.vertical": "參數: 垂直 (Vertical)",
@ -203,38 +211,35 @@
"menuitem.label.control_labels": "設定名稱",
"menuitem.misc.multiple": "(複數設定)",
"menuitem.misc.no_icon": "(無圖示)",
"menuitem.misc.extract": "提取物件",
"menuitem.misc.extract": "提取物件",
"menuitem.label.parameters": "參數",
"action.toggle_object.header.object": "要顯示 / 隱藏的對象",
"action.toggle_object.header.object": "要顯示 / 隱藏的物件",
"action.toggle_object.header.show": "顯示",
"menu_tree.title": "選擇選單",
"menuitem.param.controlled_by_action": "< 由 Action 控制 >",
"control_group.foldout.actions": "Actions",
"control_group.foldout.menu_items": "相關選單項",
"control_group.is_saved": "保存",
"control_group.is_saved.tooltip": "如果打勾,當您更換 Avatar 或世界時,此選單項的值將被保存。",
"control_group.is_saved.tooltip": "如果打勾,當您更換 Avatar 或世界時,此選單項的值將被保存。",
"control_group.is_synced": "同步",
"control_group.is_synced.tooltip": "如果打勾,此選單項的值將和網路上的其他玩家同步。",
"control_group.is_synced.tooltip": "如果打勾,此選單項的值將和網路上的其他玩家同步。",
"control_group.default_value": "初始設定",
"control_group.default_value.unset": "(未選擇)",
"menuitem.prop.control_group": "控制群組",
"menuitem.prop.control_group.tooltip": "在給定的組中一次只能選擇一個切換",
"menuitem.prop.is_default": "是組預設",
"animation_gen.duplicate_binding": "來自不同控制群組的控制項嘗試動畫相同的參數。參數:{0}",
"animation_gen.multiple_defaults": "在同一個控制群組中找到多個默認的選單項。",
"menuitem.misc.add_item": "添加選單項",
"replace_object.target_object": "要替換的對象",
"replace_object.target_object": "要替換的物件",
"setup_outfit.err.header.notarget": "Setup Outfit 失敗",
"setup_outfit.err.header": "對 {0} 進行 Setup Outfit 失敗",
"setup_outfit.err.unknown": "未知錯誤",
"setup_outfit.err.no_selection": "沒有選擇對象。",
"setup_outfit.err.no_selection": "沒有選擇物件。",
"setup_outfit.err.run_on_avatar_itself": "Setup outfit 必須在服裝物件上運行,而不是在 Avatar 本身。\n\n你是要製作「混種 Avatar」嗎如果是請從「裡 Avatar」中移除 Avatar descriptor 元件,然後對其執行 Setup outfit。",
"setup_outfit.err.multiple_avatar_descriptors": "在 {0} 和其父級中有多個 Avatar descriptor。\n\n你是要製作「混種 Avatar」嗎如果是請從「裡 Avatar」中移除 Avatar descriptor 元件,然後對其執行 Setup outfit。",
"setup_outfit.err.no_avatar_descriptor": "在 {0} 的父級中找不到 VRC Avatar Descriptor。請確保你的服裝放置在 Avatar 裡。",
"setup_outfit.err.no_animator": "你的 Avatar 沒有 Animator 元件。",
"setup_outfit.err.no_hips": "你的 Avatar 沒有 Hips 骨骼。Setup Outfit 只能用於 humanoid Avatars。",
"setup_outfit.err.no_outfit_hips": "識別不到服裝的 Hips已搜尋包含以下名稱的對象",
"move_independently.group-header": "要一起移動的對象",
"setup_outfit.err.no_outfit_hips": "識別不到服裝的 Hips已搜尋包含以下名稱的物件",
"move_independently.group-header": "要一起移動的物件",
"scale_adjuster.scale": "調整比例",
"scale_adjuster.adjust_children": "調整子級的位置",
"world_fixed_object.err.unsupported_platform": "此平台不支援 World Fixed Object 元件。",
@ -242,5 +247,10 @@
"ma_info.param_usage_ui.header": "Expressions 的參數使用狀況",
"ma_info.param_usage_ui.other_objects": "此 Avatar 中的其他東西",
"ma_info.param_usage_ui.free_space": "未使用的參數空間 ({0} bits)",
"ma_info.param_usage_ui.bits_template": "{0} ({1} bits)"
"ma_info.param_usage_ui.bits_template": "{0} ({1} bits)",
"ma_info.param_usage_ui.no_data": "【無資訊】",
"reactive_object.inverse": "反轉條件",
"reactive_object.material-setter.set-to": "將材質設定為:",
"menuitem.misc.add_toggle": "新增開關",
"ro_sim.effect_group.material": "材質"
}

View File

@ -1,19 +1,23 @@
#if MA_VRCSDK3_AVATARS
#region
using System;
using System.IO;
using System.Linq;
using nadena.dev.modular_avatar.ui;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
internal class MenuExtractor
{
private const int PRIORITY = 49;
[MenuItem("GameObject/ModularAvatar/Extract menu", false, PRIORITY)]
[MenuItem(UnityMenuItems.GameObject_ExtractMenu, false, UnityMenuItems.GameObject_ExtractMenuOrder)]
static void ExtractMenu(MenuCommand menuCommand)
{
if (!(menuCommand.context is GameObject gameObj)) return;
@ -32,10 +36,10 @@ namespace nadena.dev.modular_avatar.core.editor
var assetPath = AssetDatabase.GetAssetPath(avatar.expressionsMenu);
var dummyAssetPathBase = assetPath.Replace(".asset", " placeholder");
if (dummyAssetPathBase.StartsWith("Packages" + System.IO.Path.DirectorySeparatorChar))
if (dummyAssetPathBase.StartsWith("Packages" + Path.DirectorySeparatorChar))
{
var filename = System.IO.Path.GetFileName(dummyAssetPathBase);
dummyAssetPathBase = System.IO.Path.Combine("Assets", filename);
var filename = Path.GetFileName(dummyAssetPathBase);
dummyAssetPathBase = Path.Combine("Assets", filename);
}
// Check that a similarly-named file doesn't already exist
@ -43,7 +47,7 @@ namespace nadena.dev.modular_avatar.core.editor
do
{
var fullPath = dummyAssetPathBase + (i > 0 ? " " + i : "") + ".asset";
if (System.IO.File.Exists(fullPath))
if (File.Exists(fullPath))
{
var asset = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(fullPath);
if (asset != null && asset.controls.Count == 0)
@ -52,7 +56,7 @@ namespace nadena.dev.modular_avatar.core.editor
break;
}
}
else if (!System.IO.File.Exists(fullPath))
else if (!File.Exists(fullPath))
{
var dummyAsset = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
AssetDatabase.CreateAsset(dummyAsset, fullPath);

View File

@ -151,7 +151,9 @@ namespace nadena.dev.modular_avatar.core.editor
basePath = "";
}
bool? writeDefaults = merge.matchAvatarWriteDefaults ? writeDefaults_[merge.layerType] : null;
var writeDefaults = merge.matchAvatarWriteDefaults
? writeDefaults_.GetValueOrDefault(merge.layerType)
: null;
var controller = _context.ConvertAnimatorController(merge.animator);
session.AddController(basePath, controller, writeDefaults);

View File

@ -94,10 +94,10 @@ namespace nadena.dev.modular_avatar.core.editor
TopoProcessMergeArmatures(mergeArmatures);
#if MA_VRCSDK3_AVATARS
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<ScaleProxy>(true))
/*foreach (var c in avatarGameObject.transform.GetComponentsInChildren<ScaleProxy>(true))
{
BoneDatabase.AddMergedBone(c.transform);
}
}*/
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
{
@ -116,6 +116,9 @@ namespace nadena.dev.modular_avatar.core.editor
if (c.rootTransform == null) c.rootTransform = c.transform;
RetainBoneReferences(c);
}
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCConstraintBase>(true))
RetainBoneReferences(c);
#endif
#if MA_VRM0

View File

@ -7,7 +7,7 @@ namespace nadena.dev.modular_avatar.core.editor
{
internal static bool NotFinal(this ModularAvatarMeshSettings.InheritMode mode)
{
return mode == ModularAvatarMeshSettings.InheritMode.Inherit;
return mode is ModularAvatarMeshSettings.InheritMode.Inherit or ModularAvatarMeshSettings.InheritMode.SetOrInherit;
}
}
@ -37,15 +37,45 @@ namespace nadena.dev.modular_avatar.core.editor
public Bounds Bounds;
}
private static bool Inherit(ref ModularAvatarMeshSettings.InheritMode mode,
ModularAvatarMeshSettings.InheritMode srcmode)
// current Mode is the mode of current value, and the current value is came from MA Mesh Settings of child GameObject
// the srcMode is the mode of currently processing MA Mesh Settings, which is the parent component of the current value
private static bool ShouldUseSrcValue(
ref ModularAvatarMeshSettings.InheritMode currentMode,
ModularAvatarMeshSettings.InheritMode srcMode)
{
if (mode != ModularAvatarMeshSettings.InheritMode.Inherit ||
srcmode == ModularAvatarMeshSettings.InheritMode.Inherit)
return false;
switch (currentMode, srcMode)
{
// invalid cases
case (not (ModularAvatarMeshSettings.InheritMode.Set
or ModularAvatarMeshSettings.InheritMode.Inherit
or ModularAvatarMeshSettings.InheritMode.DontSet
or ModularAvatarMeshSettings.InheritMode.SetOrInherit), _):
throw new System.InvalidOperationException($"Logic failure: invalid InheritMode: {currentMode}");
case (_, not (ModularAvatarMeshSettings.InheritMode.Set
or ModularAvatarMeshSettings.InheritMode.Inherit
or ModularAvatarMeshSettings.InheritMode.DontSet
or ModularAvatarMeshSettings.InheritMode.SetOrInherit)):
throw new System.ArgumentOutOfRangeException(nameof(srcMode), $"Invalid InheritMode: {srcMode}");
mode = srcmode;
return true;
// If current value is came from Set or DontSet, it should not be changed
case (ModularAvatarMeshSettings.InheritMode.Set, _):
case (ModularAvatarMeshSettings.InheritMode.DontSet, _):
return false;
// If srcMode is Inherit, it should not be changed
case (_, ModularAvatarMeshSettings.InheritMode.Inherit):
return false;
// If srcMode is DontSet, the value will not be used but mode should be used
case (_, ModularAvatarMeshSettings.InheritMode.DontSet):
currentMode = srcMode;
return true;
// if SrcMode is Set or SetOrInherit, it should be used.
case (_, ModularAvatarMeshSettings.InheritMode.Set):
case (_, ModularAvatarMeshSettings.InheritMode.SetOrInherit):
currentMode = srcMode;
return true;
}
}
internal static MergedSettings MergeSettings(Transform avatarRoot, Transform referenceObject)
@ -74,20 +104,20 @@ namespace nadena.dev.modular_avatar.core.editor
continue;
}
if (Inherit(ref inheritProbeAnchor, settings.InheritProbeAnchor))
if (ShouldUseSrcValue(ref inheritProbeAnchor, settings.InheritProbeAnchor))
{
merged.ProbeAnchor = settings.ProbeAnchor.Get(settings)?.transform;
}
if (Inherit(ref inheritBounds, settings.InheritBounds))
if (ShouldUseSrcValue(ref inheritBounds, settings.InheritBounds))
{
merged.RootBone = settings.RootBone.Get(settings)?.transform;
merged.Bounds = settings.Bounds;
}
} while (current != null && (inheritProbeAnchor.NotFinal() || inheritBounds.NotFinal()));
merged.SetAnchor = inheritProbeAnchor == ModularAvatarMeshSettings.InheritMode.Set;
merged.SetBounds = inheritBounds == ModularAvatarMeshSettings.InheritMode.Set;
merged.SetAnchor = inheritProbeAnchor is ModularAvatarMeshSettings.InheritMode.Set or ModularAvatarMeshSettings.InheritMode.SetOrInherit;
merged.SetBounds = inheritBounds is ModularAvatarMeshSettings.InheritMode.Set or ModularAvatarMeshSettings.InheritMode.SetOrInherit;
return merged;
}

View File

@ -0,0 +1,125 @@
using System.Collections.Generic;
using nadena.dev.ndmf;
using UnityEditor;
#if MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER
using UnityEngine;
using UnityEngine.Animations;
using VRC.SDK3.Avatars;
using System.Linq;
using nadena.dev.modular_avatar.animation;
using VRC.Dynamics;
#endif
namespace nadena.dev.modular_avatar.core.editor
{
internal class ConstraintConverterPass : Pass<ConstraintConverterPass>
{
#if MA_VRCSDK3_AVATARS_3_7_0_OR_NEWER
[InitializeOnLoadMethod]
private static void Init()
{
AvatarDynamicsSetup.IsUnityConstraintAutoConverted += constraint =>
{
var component = constraint as Component;
if (component == null) return false;
var converted = component.GetComponentInParent<ModularAvatarConvertConstraints>();
return converted != null && RuntimeUtil.FindAvatarInParents(converted.transform) ==
RuntimeUtil.FindAvatarInParents(component.transform);
};
AvatarDynamicsSetup.OnConvertUnityConstraintsAcrossGameObjects += (constraints, isAutoFix) =>
{
if (!isAutoFix) return false;
var avatars = constraints.Select(c => RuntimeUtil.FindAvatarInParents(c.transform)).Distinct();
foreach (var avatar in avatars) Undo.AddComponent<ModularAvatarConvertConstraints>(avatar.gameObject);
return true;
};
}
protected override void Execute(ndmf.BuildContext context)
{
var converters = context.AvatarRootObject.GetComponentsInChildren<ModularAvatarConvertConstraints>(true)
.Select(c => c.gameObject)
.ToHashSet(new ObjectIdentityComparer<GameObject>());
if (converters.Count == 0) return;
var constraintGameObjects = context.AvatarRootObject.GetComponentsInChildren<IConstraint>(true)
.Select(c => (c as Component)?.gameObject)
.Distinct()
.Where(go => go.GetComponentsInParent<ModularAvatarConvertConstraints>(true)
.Select(c => c.gameObject)
.Any(converters.Contains)
).ToArray();
var targetConstraintComponents =
constraintGameObjects.SelectMany(go => go.GetComponents<IConstraint>()).ToArray();
AvatarDynamicsSetup.DoConvertUnityConstraints(targetConstraintComponents, null, false);
var asc = context.Extension<AnimationServicesContext>();
// Also look for preexisting VRCConstraints so we can go fix up any broken animation clips from people who
// clicked auto fix :(
var existingVRCConstraints = converters.SelectMany(c => c.GetComponentsInChildren<VRCConstraintBase>(true))
.Select(c => c.gameObject)
.Distinct();
var targetPaths = constraintGameObjects
.Union(existingVRCConstraints)
.Select(c => asc.PathMappings.GetObjectIdentifier(c))
.ToHashSet();
// Update animation clips
var clips = targetPaths.SelectMany(tp => asc.AnimationDatabase.ClipsForPath(tp))
.ToHashSet();
foreach (var clip in clips) RemapSingleClip(clip, targetPaths);
}
private void RemapSingleClip(AnimationDatabase.ClipHolder clip, HashSet<string> targetPaths)
{
var motion = clip.CurrentClip as AnimationClip;
if (motion == null) return;
var bindings = AnimationUtility.GetCurveBindings(motion);
var toUpdateBindings = new List<EditorCurveBinding>();
var toUpdateCurves = new List<AnimationCurve>();
foreach (var ecb in bindings)
{
if (!targetPaths.Contains(ecb.path)) continue;
if (typeof(IConstraint).IsAssignableFrom(ecb.type))
if (AvatarDynamicsSetup.TryGetSubstituteAnimationBinding(ecb.type, ecb.propertyName,
out var newType, out var newProp, out var isArray))
{
var newBinding = new EditorCurveBinding
{
path = ecb.path,
type = newType,
propertyName = newProp
};
var curve = AnimationUtility.GetEditorCurve(motion, ecb);
if (curve != null)
{
toUpdateBindings.Add(newBinding);
toUpdateCurves.Add(curve);
toUpdateBindings.Add(ecb);
toUpdateCurves.Add(null);
}
}
}
if (toUpdateBindings.Count == 0) return;
AnimationUtility.SetEditorCurves(motion, toUpdateBindings.ToArray(), toUpdateCurves.ToArray());
}
#else
protected override void Execute(ndmf.BuildContext context) {}
#endif
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5c172d4eac3d4902826a96656cf1ce34
timeCreated: 1723776385

View File

@ -134,7 +134,7 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
}
catch (MissingComponentException e)
catch (MissingComponentException _)
{
// No animator? weird. Move on.
}

View File

@ -1,5 +1,6 @@
#if MA_VRCSDK3_AVATARS
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@ -11,6 +12,7 @@ using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using VRC.SDK3.Dynamics.Contact.Components;
using VRC.SDK3.Dynamics.PhysBone.Components;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor
{
@ -156,6 +158,12 @@ namespace nadena.dev.modular_avatar.core.editor
WalkBlendTree(parameters, mergeBlendTree.BlendTree as BlendTree);
break;
}
case ModularAvatarMenuItem menuItem:
{
AddMenuItemParameters(parameters, menuItem);
break;
}
}
}
@ -171,6 +179,28 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
private static void AddMenuItemParameters(Dictionary<string, DetectedParameter> parameters,
ModularAvatarMenuItem menuItem)
{
AddParam(menuItem.Control.parameter.name);
foreach (var subParam in menuItem.Control.subParameters ??
Array.Empty<VRCExpressionsMenu.Control.Parameter>()) AddParam(subParam.name);
void AddParam(string name)
{
if (string.IsNullOrWhiteSpace(name)) return;
var param = new DetectedParameter
{
OriginalName = name,
IsPrefix = false,
Source = menuItem
};
parameters[param.MapKey] = param;
}
}
private static void WalkMenu(Dictionary<string, DetectedParameter> parameters, VRCExpressionsMenu menu,
HashSet<VRCExpressionsMenu> visited)
{

View File

@ -5,13 +5,56 @@ using System.Collections.Immutable;
using System.Linq;
using nadena.dev.modular_avatar.core.editor.plugin;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
[ParameterProviderFor(typeof(ModularAvatarMenuItem))]
internal class MAMenuItemIntrospection : IParameterProvider
{
private readonly ModularAvatarMenuItem _component;
public MAMenuItemIntrospection(ModularAvatarMenuItem menuItem)
{
_component = menuItem;
}
public IEnumerable<ProvidedParameter> GetSuppliedParameters(ndmf.BuildContext context = null)
{
if (_component.Control == null) yield break;
if (!ParameterAssignerPass.ShouldAssignParametersToMami(_component)) yield break;
var hidden = false;
var name = _component.Control?.parameter?.name;
if (string.IsNullOrWhiteSpace(name))
{
name = $"__MA/AutoParam/{_component.gameObject.name}${_component.GetInstanceID()}";
hidden = true;
}
var type = AnimatorControllerParameterType.Bool;
if (type != AnimatorControllerParameterType.Float &&
(_component.Control.value > 1.01 || _component.Control.value < -0.01))
type = AnimatorControllerParameterType.Int;
if (Mathf.Abs(Mathf.Round(_component.Control.value) - _component.Control.value) > 0.01f)
type = AnimatorControllerParameterType.Float;
yield return new ProvidedParameter(
name,
ParameterNamespace.Animator,
_component, PluginDefinition.Instance, type)
{
WantSynced = _component.isSynced,
IsHidden = hidden,
DefaultValue = _component.isDefault ? _component.Control.value : null
};
}
}
[ParameterProviderFor(typeof(ModularAvatarParameters))]
internal class MAParametersIntrospection : IParameterProvider
{
@ -54,6 +97,7 @@ namespace nadena.dev.modular_avatar.core.editor
IsAnimatorOnly = animatorOnly,
WantSynced = !p.localOnly,
IsHidden = p.internalParameter,
DefaultValue = p.defaultValue
};
});
}
@ -76,7 +120,7 @@ namespace nadena.dev.modular_avatar.core.editor
}
else
{
remapTo = p.nameOrPrefix + "$" + GUID.Generate();
remapTo = p.nameOrPrefix + "$" + _component.GetInstanceID();
}
}
else if (string.IsNullOrEmpty(p.remapTo))
@ -88,9 +132,9 @@ namespace nadena.dev.modular_avatar.core.editor
remapTo = p.remapTo;
}
if (nameMap.TryGetKey((ns, remapTo), out var existingMapping))
if (nameMap.TryGetValue((ns, remapTo), out var existingMapping))
{
remapTo = existingMapping.Item2;
remapTo = existingMapping.ParameterName;
}
nameMap = nameMap.SetItem((ns, p.nameOrPrefix), new ParameterMapping(remapTo, p.internalParameter));

View File

@ -1,23 +0,0 @@
#region
using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
[HelpURL("https://m-a.nadena.dev/docs/intro?lang=auto")]
internal class ModularAvatarInformation : ScriptableObject
{
internal static ModularAvatarInformation _instance;
internal static ModularAvatarInformation instance
{
get
{
if (_instance == null) _instance = CreateInstance<ModularAvatarInformation>();
return _instance;
}
}
}
}

View File

@ -5,10 +5,12 @@
#Outerbox {
margin-top: 4px;
margin-bottom: 8px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-right-width: 1px;
/*border-left-width: 1px;
border-right-width: 1px;*/
border-color: black;
padding: 4px;
@ -18,6 +20,11 @@
margin-bottom: 12px;
}
#Footer {
margin-left: 15px;
margin-right: 15px;
}
#UsageBox {
height: 16px;
flex-direction: row;
@ -72,4 +79,33 @@
margin-right: -4px;
border-left-width: 4px;
border-right-width: 4px;
}
/* NO DATA display */
#Outerbox Toggle {
display: flex;
}
/* Why doesn't #Outerbox.no-data > .unity-foldout__content > * work here? */
#Outerbox.no-data #UsageBox {
display: none;
}
#Outerbox.no-data #Legend {
display: none;
}
#NoData {
display: none;
}
#Outerbox.no-data #NoData {
font-size: 150%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100%;
margin-top: 20px;
margin-bottom: 20px;
}

View File

@ -1,8 +1,12 @@
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ma="nadena.dev.modular_avatar.core.editor">
<ui:VisualElement name="root-box">
<ui:Label text="ma_info.param_usage_ui.header" class="header ndmf-tr"/>
<ma:LogoImage/>
<ui:Foldout text="ma_info.param_usage_ui.header" class="header ndmf-tr" name="Outerbox">
<ui:VisualElement name="NoData">
<ui:Label text="ma_info.param_usage_ui.no_data" class="ndmf-tr"/>
</ui:VisualElement>
<ui:VisualElement name="Outerbox">
<ui:VisualElement name="UsageBox">
<ui:VisualElement name="OtherObjects" style="background-color: #888888; flex-grow: 1;"/>
<ui:VisualElement name="UnusedSpace" style="width: auto; background-color: #eeeeee; flex-grow: 1;"/>
@ -28,8 +32,10 @@
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
</ui:Foldout>
<ma:LanguageSwitcherElement/>
<ui:VisualElement name="Footer">
<ma:LanguageSwitcherElement/>
</ui:VisualElement>
</ui:VisualElement>
</UXML>

View File

@ -1,40 +0,0 @@
using System;
using nadena.dev.modular_avatar.ui;
using UnityEditor;
using UnityEngine.Serialization;
namespace nadena.dev.modular_avatar.core.editor
{
#if UNITY_2022_3_OR_NEWER
[FilePath("modular-avatar/ParamsUsagePrefs.asset", FilePathAttribute.Location.PreferencesFolder)]
internal sealed class ParamsUsagePrefs : ScriptableSingleton<ParamsUsagePrefs>
{
public static event Action<bool> OnChange_EnableInfoMenu;
[FormerlySerializedAs("EnableInfoMenu")] public bool enableInfoMenu = true;
[MenuItem(UnityMenuItems.TopMenu_EnableInfo, false, UnityMenuItems.TopMenu_EnableInfoOrder)]
private static void Menu_EnableInfo()
{
ParamsUsagePrefs.instance.enableInfoMenu = !ParamsUsagePrefs.instance.enableInfoMenu;
Menu.SetChecked(UnityMenuItems.TopMenu_EnableInfo, ParamsUsagePrefs.instance.enableInfoMenu);
ParamsUsagePrefs.instance.Save(true);
OnChange_EnableInfoMenu?.Invoke(ParamsUsagePrefs.instance.enableInfoMenu);
}
[InitializeOnLoadMethod]
private static void Initialize()
{
Menu.SetChecked(UnityMenuItems.TopMenu_EnableInfo, ParamsUsagePrefs.instance.enableInfoMenu);
}
}
#else
internal sealed class ParamsUsagePrefs
{
public static ParamsUsagePrefs instance => new ParamsUsagePrefs();
public static event Action<bool> OnChange_EnableInfoMenu;
public bool enableInfoMenu => false;
}
#endif
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3ebf965fd4064a52896def62c36c6a90
timeCreated: 1713742583

Some files were not shown because too many files have changed in this diff Show More