mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-14 08:28:59 +08:00
Merge branch 'main' into vrm
This commit is contained in:
commit
c32bde7d9b
10
.github/ProjectRoot/vpm-manifest-2022.json
vendored
10
.github/ProjectRoot/vpm-manifest-2022.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
5
.github/workflows/build-release.yml
vendored
5
.github/workflows/build-release.yml
vendored
@ -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
|
||||
|
2
.github/workflows/deploy-pages.yml
vendored
2
.github/workflows/deploy-pages.yml
vendored
@ -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 }}
|
||||
|
2
.github/workflows/gameci.yml
vendored
2
.github/workflows/gameci.yml
vendored
@ -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
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
UnitTests/
|
||||
UnitTests
|
||||
UnitTests.meta
|
||||
/UnitTests/
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
18
Editor/Animation/EditorCurveBindingComparer.cs
Normal file
18
Editor/Animation/EditorCurveBindingComparer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Animation/EditorCurveBindingComparer.cs.meta
Normal file
3
Editor/Animation/EditorCurveBindingComparer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e751f7889323485bbe202285a47cb0d4
|
||||
timeCreated: 1719196767
|
75
Editor/Animation/GameObjectDisableDelayPass.cs
Normal file
75
Editor/Animation/GameObjectDisableDelayPass.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Animation/GameObjectDisableDelayPass.cs.meta
Normal file
3
Editor/Animation/GameObjectDisableDelayPass.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b3eb561f76b459fbfbcf29fc4484261
|
||||
timeCreated: 1722222066
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
Editor/Animation/ReadableProperty.cs
Normal file
147
Editor/Animation/ReadableProperty.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Animation/ReadableProperty.cs.meta
Normal file
3
Editor/Animation/ReadableProperty.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1074339e2a59465ba585cb8cbbc4a88c
|
||||
timeCreated: 1719195449
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 807736f252df4b1b8402827257dcbea3
|
||||
timeCreated: 1709354699
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42f70698a5df48c0908400c425a2f6ee
|
||||
timeCreated: 1709356304
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d62a8f41641443ea8bffdc0429e0ad1
|
||||
timeCreated: 1710223876
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf06818f1c0c436fbae7f755d7110aba
|
||||
timeCreated: 1709359553
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
54
Editor/Inspector/Common/LogoImage.cs
Normal file
54
Editor/Inspector/Common/LogoImage.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Inspector/Common/LogoImage.cs.meta
Normal file
3
Editor/Inspector/Common/LogoImage.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d12c8166c3f14b78a76dbef22b07fad1
|
||||
timeCreated: 1715480586
|
@ -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);
|
||||
|
14
Editor/Inspector/MAConvertConstraintsEditor.cs
Normal file
14
Editor/Inspector/MAConvertConstraintsEditor.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Inspector/MAConvertConstraintsEditor.cs.meta
Normal file
3
Editor/Inspector/MAConvertConstraintsEditor.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 484ea04548b945ce9cf5fd6d49b50244
|
||||
timeCreated: 1723778102
|
3
Editor/Inspector/MaterialSetter.meta
Normal file
3
Editor/Inspector/MaterialSetter.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 131d9706ddc04331bd09cf13b863c537
|
||||
timeCreated: 1723334567
|
25
Editor/Inspector/MaterialSetter/MaterialSetter.uxml
Normal file
25
Editor/Inspector/MaterialSetter/MaterialSetter.uxml
Normal 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>
|
3
Editor/Inspector/MaterialSetter/MaterialSetter.uxml.meta
Normal file
3
Editor/Inspector/MaterialSetter/MaterialSetter.uxml.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd5c518316b2435d8a666911d4131903
|
||||
timeCreated: 1723334567
|
43
Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs
Normal file
43
Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
13
Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs.meta
Normal file
13
Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs.meta
Normal 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:
|
56
Editor/Inspector/MaterialSetter/MaterialSetterStyles.uss
Normal file
56
Editor/Inspector/MaterialSetter/MaterialSetterStyles.uss
Normal 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;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fce9f3fe74434b718abac5ea66775acb
|
||||
timeCreated: 1723334567
|
158
Editor/Inspector/MaterialSetter/MaterialSwitchObjectEditor.cs
Normal file
158
Editor/Inspector/MaterialSetter/MaterialSwitchObjectEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6361a17f884644988ef3ece7fbe73ab7
|
||||
timeCreated: 1723334567
|
@ -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>
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55b5e53f6c364089a1871b68e0de17c6
|
||||
timeCreated: 1723334567
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
62
Editor/Inspector/Menu/ToggleCreatorShortcut.cs
Normal file
62
Editor/Inspector/Menu/ToggleCreatorShortcut.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Inspector/Menu/ToggleCreatorShortcut.cs.meta
Normal file
3
Editor/Inspector/Menu/ToggleCreatorShortcut.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e15fef260544783af5ff1fd5f13acd3
|
||||
timeCreated: 1723341065
|
@ -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"));
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
3
Editor/Inspector/ObjectToggle.meta
Normal file
3
Editor/Inspector/ObjectToggle.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61045dcdc7f24658a5b47fb0b67ab9fe
|
||||
timeCreated: 1722736548
|
25
Editor/Inspector/ObjectToggle/ObjectSwitcher.uxml
Normal file
25
Editor/Inspector/ObjectToggle/ObjectSwitcher.uxml
Normal 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>
|
3
Editor/Inspector/ObjectToggle/ObjectSwitcher.uxml.meta
Normal file
3
Editor/Inspector/ObjectToggle/ObjectSwitcher.uxml.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02f9cb4b3be34457870f111d73e2fd2f
|
||||
timeCreated: 1722736548
|
43
Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs
Normal file
43
Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
13
Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs.meta
Normal file
13
Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs.meta
Normal 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:
|
114
Editor/Inspector/ObjectToggle/ObjectSwitcherStyles.uss
Normal file
114
Editor/Inspector/ObjectToggle/ObjectSwitcherStyles.uss
Normal 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;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7559b81cea245b68c66602ea0cbbbcf
|
||||
timeCreated: 1722736548
|
30
Editor/Inspector/ObjectToggle/ToggledObjectEditor.cs
Normal file
30
Editor/Inspector/ObjectToggle/ToggledObjectEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d18528c5f704d3daf1160d9672bd09e
|
||||
timeCreated: 1722736548
|
6
Editor/Inspector/ObjectToggle/ToggledObjectEditor.uxml
Normal file
6
Editor/Inspector/ObjectToggle/ToggledObjectEditor.uxml
Normal 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>
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 565803cb95a04d1f98f7050c18234cdd
|
||||
timeCreated: 1722736548
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
@ -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())
|
||||
|
3
Editor/Inspector/ShapeChanger.meta
Normal file
3
Editor/Inspector/ShapeChanger.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2400605596824043a59b55dcb2a8e89a
|
||||
timeCreated: 1717196942
|
80
Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs
Normal file
80
Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs.meta
Normal file
3
Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0778099a393468c9775fd9a99b321af
|
||||
timeCreated: 1717198244
|
13
Editor/Inspector/ShapeChanger/ChangedShapeEditor.uxml
Normal file
13
Editor/Inspector/ShapeChanger/ChangedShapeEditor.uxml
Normal 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>
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8131b91fff04f9e94b71d70105ae05b
|
||||
timeCreated: 1717198707
|
25
Editor/Inspector/ShapeChanger/ShapeChanger.uxml
Normal file
25
Editor/Inspector/ShapeChanger/ShapeChanger.uxml
Normal 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>
|
3
Editor/Inspector/ShapeChanger/ShapeChanger.uxml.meta
Normal file
3
Editor/Inspector/ShapeChanger/ShapeChanger.uxml.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8267295b2dc4d90b92dcc289f6d31c4
|
||||
timeCreated: 1717197307
|
94
Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs
Normal file
94
Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
13
Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs.meta
Normal file
13
Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs.meta
Normal 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:
|
117
Editor/Inspector/ShapeChanger/ShapeChangerStyles.uss
Normal file
117
Editor/Inspector/ShapeChanger/ShapeChangerStyles.uss
Normal 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;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b52b9cfc8514c55af39aeb11de7f279
|
||||
timeCreated: 1717197307
|
@ -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"
|
||||
}
|
@ -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": "条件"
|
||||
}
|
@ -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": "파라미터 상세설정",
|
||||
|
@ -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": "添加菜单项",
|
||||
|
@ -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": "材質"
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
125
Editor/OptimizationPasses/ConstraintConverterPass.cs
Normal file
125
Editor/OptimizationPasses/ConstraintConverterPass.cs
Normal 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
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c172d4eac3d4902826a96656cf1ce34
|
||||
timeCreated: 1723776385
|
@ -134,7 +134,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingComponentException e)
|
||||
catch (MissingComponentException _)
|
||||
{
|
||||
// No animator? weird. Move on.
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
@ -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
|
||||
}
|
@ -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
Loading…
x
Reference in New Issue
Block a user