feat: add UI to show parameter usage (#773)

This commit is contained in:
bd_ 2024-03-14 21:33:44 +09:00 committed by GitHub
parent 532e3bc250
commit 654aec1aab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 926 additions and 23 deletions

View File

@ -4,7 +4,7 @@
"version": "3.4.2" "version": "3.4.2"
}, },
"nadena.dev.ndmf": { "nadena.dev.ndmf": {
"version": "1.3.6" "version": "1.4.0-rc.0"
} }
}, },
"locked": { "locked": {
@ -19,7 +19,7 @@
"dependencies": {} "dependencies": {}
}, },
"nadena.dev.ndmf": { "nadena.dev.ndmf": {
"version": "1.3.6" "version": "1.4.0-rc.0"
} }
} }
} }

View File

@ -4,7 +4,7 @@
"version": "3.5.0" "version": "3.5.0"
}, },
"nadena.dev.ndmf": { "nadena.dev.ndmf": {
"version": "1.3.6" "version": "1.4.0-rc.0"
} }
}, },
"locked": { "locked": {
@ -19,7 +19,7 @@
"dependencies": {} "dependencies": {}
}, },
"nadena.dev.ndmf": { "nadena.dev.ndmf": {
"version": "1.3.6" "version": "1.4.0-rc.0"
} }
} }
} }

View File

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

View File

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

View File

@ -17,6 +17,7 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
#if UNITY_2022_3_OR_NEWER #if UNITY_2022_3_OR_NEWER
HandleUtilityPatches.Patch_FilterInstanceIDs, HandleUtilityPatches.Patch_FilterInstanceIDs,
PickingObjectPatch.Patch, PickingObjectPatch.Patch,
InjectParamsUsageUI.Patch,
#endif #endif
}; };
@ -36,6 +37,8 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches
Debug.LogException(e); Debug.LogException(e);
} }
} }
AssemblyReloadEvents.beforeAssemblyReload += () => { harmony.UnpatchAll(); };
} }
} }
} }

View File

@ -1,9 +1,11 @@
{ {
"name": "nadena.dev.modular-avatar.harmony-patches", "name": "nadena.dev.modular-avatar.harmony-patches",
"rootNamespace": "",
"references": [ "references": [
"nadena.dev.modular-avatar.core", "nadena.dev.modular-avatar.core",
"nadena.dev.modular-avatar.core.editor", "nadena.dev.modular-avatar.core.editor",
"VRC.SDKBase.Editor" "VRC.SDKBase.Editor",
"nadena.dev.modular-avatar.param-introspection"
], ],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"

View File

@ -22,7 +22,12 @@
* SOFTWARE. * SOFTWARE.
*/ */
#region
using UnityEditor; using UnityEditor;
using UnityEngine;
#endregion
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
@ -30,6 +35,11 @@ namespace nadena.dev.modular_avatar.core.editor
[CanEditMultipleObjects] [CanEditMultipleObjects]
internal class PBBlockerEditor : MAEditorBase internal class PBBlockerEditor : MAEditorBase
{ {
public PBBlockerEditor()
{
Debug.Log("ctor");
}
protected override void OnInnerInspectorGUI() protected override void OnInnerInspectorGUI()
{ {
EditorGUILayout.HelpBox(Localization.S("pb_blocker.help"), MessageType.Info); EditorGUILayout.HelpBox(Localization.S("pb_blocker.help"), MessageType.Info);

View File

@ -1,9 +1,13 @@
using System; #region
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using nadena.dev.ndmf.localization; using nadena.dev.ndmf.localization;
using UnityEngine.UIElements; using UnityEngine.UIElements;
#endregion
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class UIElementLocalizer internal class UIElementLocalizer
@ -21,6 +25,7 @@ namespace nadena.dev.modular_avatar.core.editor
internal void Localize(VisualElement elem) internal void Localize(VisualElement elem)
{ {
WalkTree(elem); WalkTree(elem);
LanguagePrefs.ApplyFontPreferences(elem);
} }
private void WalkTree(VisualElement elem) private void WalkTree(VisualElement elem)

View File

@ -240,5 +240,9 @@
"scale_adjuster.scale": "Scale adjustment", "scale_adjuster.scale": "Scale adjustment",
"scale_adjuster.adjust_children": "Adjust position of child objects", "scale_adjuster.adjust_children": "Adjust position of child objects",
"world_fixed_object.err.unsupported_platform": "World Fixed Object is not supported on this platform", "world_fixed_object.err.unsupported_platform": "World Fixed Object is not supported on this platform",
"world_fixed_object.err.unsupported_platform:description": "World Fixed Object is not supported on Android builds and will be ignored." "world_fixed_object.err.unsupported_platform:description": "World Fixed Object is not supported on Android builds and will be ignored.",
"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)"
} }

View File

@ -236,5 +236,9 @@
"scale_adjuster.scale": "Scale調整値", "scale_adjuster.scale": "Scale調整値",
"scale_adjuster.adjust_children": "子オブジェクトの位置を調整", "scale_adjuster.adjust_children": "子オブジェクトの位置を調整",
"world_fixed_object.err.unsupported_platform": "World Fixed Objectがこのプラットフォームに対応していません", "world_fixed_object.err.unsupported_platform": "World Fixed Objectがこのプラットフォームに対応していません",
"world_fixed_object.err.unsupported_platform:description": "World Fixed ObjectはAndroid向けビルドには対応していないため、動作しません。" "world_fixed_object.err.unsupported_platform:description": "World Fixed ObjectはAndroid向けビルドには対応していないため、動作しません。",
"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} 個のビットを使用中)"
} }

3
Editor/ParamsUsage.meta Normal file
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9914a6ac6399437dbcaa252282d02beb
timeCreated: 1710222101

View File

@ -0,0 +1,100 @@
#region
using System.Collections.Generic;
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(ModularAvatarParameters))]
internal class MAParametersIntrospection : IParameterProvider
{
private readonly ModularAvatarParameters _component;
public MAParametersIntrospection(ModularAvatarParameters parameters)
{
_component = parameters;
}
public IEnumerable<ProvidedParameter> GetSuppliedParameters(ndmf.BuildContext context = null)
{
return _component.parameters.Select(p =>
{
AnimatorControllerParameterType paramType;
bool animatorOnly = false;
switch (p.syncType)
{
case ParameterSyncType.Bool:
paramType = AnimatorControllerParameterType.Bool;
break;
case ParameterSyncType.Float:
paramType = AnimatorControllerParameterType.Float;
break;
case ParameterSyncType.Int:
paramType = AnimatorControllerParameterType.Int;
break;
default:
paramType = AnimatorControllerParameterType.Float;
animatorOnly = true;
break;
}
return new ProvidedParameter(
p.nameOrPrefix,
p.isPrefix ? ParameterNamespace.PhysBonesPrefix : ParameterNamespace.Animator,
_component, PluginDefinition.Instance, paramType)
{
IsAnimatorOnly = animatorOnly,
WantSynced = !p.localOnly,
IsHidden = p.internalParameter,
};
});
}
public void RemapParameters(ref ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> nameMap,
ndmf.BuildContext context = null)
{
var remappings = context != null ? ParameterRenameMappings.Get(context) : null;
// TODO - internal parameter handling
foreach (var p in _component.parameters)
{
ParameterNamespace ns = p.isPrefix ? ParameterNamespace.PhysBonesPrefix : ParameterNamespace.Animator;
string remapTo = null;
if (p.internalParameter)
{
if (remappings != null)
{
remapTo = remappings.Remap(_component, ns, p.nameOrPrefix);
}
else
{
remapTo = p.nameOrPrefix + "$" + GUID.Generate();
}
}
else if (string.IsNullOrEmpty(p.remapTo))
{
continue;
}
else
{
remapTo = p.remapTo;
}
if (nameMap.TryGetKey((ns, remapTo), out var existingMapping))
{
remapTo = existingMapping.Item2;
}
nameMap = nameMap.SetItem((ns, p.nameOrPrefix), new ParameterMapping(remapTo, p.internalParameter));
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eadfd1e62f714d06a8b9f693dec21940
timeCreated: 1710229132

View File

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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f902feee12ad4fcbb8a975bbea565ab1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,75 @@
Label.header {
-unity-font-style: bold;
margin-top: 10px;
}
#Outerbox {
margin-top: 4px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-right-width: 1px;
border-color: black;
padding: 4px;
}
#root-box {
margin-bottom: 12px;
}
#UsageBox {
height: 16px;
flex-direction: row;
margin-bottom: 8px;
}
#UsageBox VisualElement {
flex-grow: 0;
}
#UsageBox VisualElement.Hovering {
border-top-width: 4px;
border-bottom-width: 4px;
/*border-left-width: 4px;
border-right-width: 4px;
margin: -4px;
*/
margin-top: -4px;
margin-bottom: -4px;
border-color: black;
}
.Entry {
flex-direction: row;
}
.IconOuter {
border-top-width: 3px;
border-bottom-width: 3px;
border-left-width: 3px;
border-right-width: 3px;
border-color: grey;
padding: 1px;
margin-right: 4px;
align-items: center;
justify-content: center;
height: 16px;
width: 16px;
}
.IconInner {
height: 100%;
width: 100%;
}
.Entry.Hovering {
margin-left: -4px;
margin-right: -4px;
border-left-width: 4px;
border-right-width: 4px;
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6813d571517475dbf36efb2d266003a
timeCreated: 1710399136

View File

@ -0,0 +1,35 @@
<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"/>
<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;"/>
</ui:VisualElement>
<ui:VisualElement name="Legend">
<ui:VisualElement class="retained">
<ui:VisualElement class="Entry" name="OtherObjects" style="display: none">
<ui:VisualElement class="IconOuter">
<ui:VisualElement class="IconInner" style="background-color: #888888"/>
</ui:VisualElement>
<ui:Label class="description" text="Other objects on this avatar (xx)"/>
</ui:VisualElement>
<ui:VisualElement class="Entry" name="UnusedSpace" style="display: none">
<ui:VisualElement class="IconOuter">
<ui:VisualElement class="IconInner" style="background-color: #eeeeee"/>
</ui:VisualElement>
<ui:Label class="description" text="Free parameter space (xx)"/>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ma:LanguageSwitcherElement/>
</ui:VisualElement>
</UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d51876a8e634298aa6d0271bb820189
timeCreated: 1710399118

View File

@ -0,0 +1,203 @@
#region
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.Avatars.ScriptableObjects;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
internal class ParamsUsageEditor : MAEditorBase
{
[SerializeField] private StyleSheet uss;
[SerializeField] private VisualTreeAsset uxml;
private VisualElement _root;
private VisualElement _entryTemplate;
private VisualElement _usageBoxContainer;
private VisualElement _legendContainer;
private bool _visible = false;
public bool Visible
{
get => _visible;
set
{
if (_visible == value) return;
_visible = value;
if (_visible) Recalculate();
}
}
private void OnEnable()
{
#if UNITY_2022_1_OR_NEWER
ObjectChangeEvents.changesPublished += OnChangesPublished;
#endif
Recalculate();
}
#if UNITY_2022_1_OR_NEWER
private void OnChangesPublished(ref ObjectChangeEventStream stream)
{
Recalculate();
}
private void OnDisable()
{
ObjectChangeEvents.changesPublished -= OnChangesPublished;
}
#endif
protected override VisualElement CreateInnerInspectorGUI()
{
_root = uxml.CloneTree();
_root.styleSheets.Add(uss);
Localization.L.LocalizeUIElements(_root);
_legendContainer = _root.Q<VisualElement>("Legend");
_usageBoxContainer = _root.Q<VisualElement>("UsageBox");
Recalculate();
return _root;
}
protected override void OnInnerInspectorGUI()
{
// no-op
}
private static IEnumerable<Color> Colors()
{
// Spiral inwards on an HSV scale
float h_step = 0.33f;
float h_step_mult = 0.8f;
float h_step_min = 0.05f;
float v_mult = 0.98f;
float h = 0;
float s = 1;
float v = 0.9f;
while (true)
{
yield return Color.HSVToRGB(h, s, v);
h = (h + h_step) % 1;
h_step = h_step_min + ((h_step - h_step_min) * h_step_mult);
v *= v_mult;
}
}
private void Recalculate()
{
if (_root == null || !_visible) return;
var ctx = serializedObject.context as GameObject;
var avatarRoot = RuntimeUtil.FindAvatarTransformInParents(ctx.transform)?.gameObject;
if (ctx == null || avatarRoot == null) return;
var orderedPlugins = ParameterInfo.ForUI.GetParametersForObject(ctx)
.GroupBy(p => p.Plugin)
.Select(group => (group.Key, group.Sum(p => p.BitUsage)))
.OrderBy(group => group.Key.DisplayName)
.ToList();
var byPlugin = orderedPlugins
.Zip(Colors(), (kv, color) => (kv.Key.DisplayName, kv.Item2, kv.Key.ThemeColor ?? color))
.ToList();
int totalUsage = byPlugin.Sum(kv => kv.Item2);
int avatarTotalUsage =
ParameterInfo.ForUI.GetParametersForObject(avatarRoot).Sum(p => p.BitUsage);
int freeSpace = VRCExpressionParameters.MAX_PARAMETER_COST - avatarTotalUsage;
float avatarTotalPerc = avatarTotalUsage / (float)VRCExpressionParameters.MAX_PARAMETER_COST;
float freeSpacePerc = freeSpace / (float)VRCExpressionParameters.MAX_PARAMETER_COST;
if (avatarTotalUsage > totalUsage)
{
byPlugin.Add((Localization.S("ma_info.param_usage_ui.other_objects"), avatarTotalUsage - totalUsage,
Color.gray));
}
var bits_template = Localization.S("ma_info.param_usage_ui.bits_template");
byPlugin = byPlugin.Select((tuple, _) =>
(string.Format(bits_template, tuple.Item1, tuple.Item2), tuple.Item2, tuple.Item3)).ToList();
if (freeSpace > 0)
{
var free_space_label = Localization.S("ma_info.param_usage_ui.free_space");
byPlugin.Add((string.Format(free_space_label, freeSpace), freeSpace, Color.white));
}
foreach (var child in _legendContainer.Children().ToList())
{
child.RemoveFromHierarchy();
}
foreach (var child in _usageBoxContainer.Children().ToList())
{
child.RemoveFromHierarchy();
}
foreach (var (label, usage, color) in byPlugin)
{
var colorBar = new VisualElement();
colorBar.style.backgroundColor = color;
colorBar.style.width =
new StyleLength(new Length(100.0f * usage / (float)VRCExpressionParameters.MAX_PARAMETER_COST,
LengthUnit.Percent));
_usageBoxContainer.Add(colorBar);
var entry = new VisualElement();
_legendContainer.Add(entry);
entry.AddToClassList("Entry");
var icon_outer = new VisualElement();
icon_outer.AddToClassList("IconOuter");
entry.Add(icon_outer);
var icon_inner = new VisualElement();
icon_inner.AddToClassList("IconInner");
icon_outer.Add(icon_inner);
icon_inner.style.backgroundColor = color;
var pluginLabel = new Label(label);
entry.Add(pluginLabel);
entry.style.borderBottomColor = color;
entry.style.borderTopColor = color;
entry.style.borderLeftColor = color;
entry.style.borderRightColor = color;
colorBar.style.borderBottomColor = color;
colorBar.style.borderTopColor = color;
colorBar.style.borderLeftColor = color;
colorBar.style.borderRightColor = color;
SetMouseHover(entry, colorBar);
SetMouseHover(colorBar, entry);
}
}
private void SetMouseHover(VisualElement src, VisualElement other)
{
src.RegisterCallback<MouseEnterEvent>(ev => { other.AddToClassList("Hovering"); });
src.RegisterCallback<MouseLeaveEvent>(ev => { other.RemoveFromClassList("Hovering"); });
}
}
}

View File

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

View File

@ -0,0 +1,202 @@
#region
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using HarmonyLib;
using nadena.dev.ndmf.localization;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor
{
internal class ParamsUsageUI : VisualElement
{
private static readonly Type editorElem = AccessTools.TypeByName("UnityEditor.UIElements.EditorElement");
private static readonly PropertyInfo editorElem_editor = AccessTools.Property(editorElem, "editor");
private class FoldoutState
{
public bool Visible;
}
private static ConditionalWeakTable<VisualElement, FoldoutState> FoldoutStateHolder =
new ConditionalWeakTable<VisualElement, FoldoutState>();
private VisualElement _gameObjectEditorElement;
private Editor _parentEditor;
private Object _rawTarget;
private GameObject _target;
private ParamsUsageEditor _editor;
private FoldoutState _foldoutState;
private bool _recursing = false;
public ParamsUsageUI()
{
RegisterCallback<AttachToPanelEvent>(OnAttach);
RegisterCallback<DetachFromPanelEvent>(OnDetach);
LanguagePrefs.RegisterLanguageChangeCallback(this,
(self) => self.OnLanguageChangedCallback());
}
private void OnLanguageChangedCallback()
{
if (_editor != null)
{
BuildContent();
}
}
private void OnDetach(DetachFromPanelEvent evt)
{
if (_recursing) return;
Clear();
if (_editor != null)
{
Object.DestroyImmediate(_editor);
_editor = null;
}
}
private void OnAttach(AttachToPanelEvent evt)
{
if (_recursing) return;
Rebuild();
}
private void Rebuild()
{
if (parent == null) return;
SetRedrawSensor();
if (_gameObjectEditorElement?.parent != parent)
{
_gameObjectEditorElement = null;
var kv = FindEditorElement();
if (kv != null)
{
var elem = kv.Value.Item1;
var index = kv.Value.Item2;
if (index != parent.Children().ToList().IndexOf(this))
{
_recursing = true;
var p = parent;
RemoveFromHierarchy();
p.Insert(index + 1, this);
_recursing = false;
}
_gameObjectEditorElement = elem;
}
}
if (_gameObjectEditorElement == null) return;
_parentEditor = editorElem_editor.GetValue(_gameObjectEditorElement) as Editor;
if (_parentEditor == null) return;
_rawTarget = _parentEditor.target;
_target = _rawTarget as GameObject;
if (_target == null) return;
Clear();
_redrawSensorActive = false;
BuildContent();
}
private (VisualElement, int)? FindEditorElement()
{
foreach (var (elem, index) in parent.Children().Select((e, i) => (e, i)))
{
if (elem.ClassListContains("game-object-inspector"))
{
return (elem, index);
}
}
return null;
}
private bool _redrawSensorActive = false;
private void SetRedrawSensor()
{
if (_redrawSensorActive) return;
Clear();
_redrawSensorActive = true;
Add(new IMGUIContainer(() => EditorApplication.delayCall += Rebuild));
}
private void BuildContent()
{
Clear();
if (!FoldoutStateHolder.TryGetValue(parent, out _foldoutState))
{
_foldoutState = new FoldoutState();
FoldoutStateHolder.Add(parent, _foldoutState);
}
if (RuntimeUtil.FindAvatarTransformInParents(_target.transform) == null)
{
return;
}
_editor = Editor.CreateEditorWithContext(new Object[] { ModularAvatarInformation.instance }, _target,
typeof(ParamsUsageEditor))
as ParamsUsageEditor;
if (_editor == null) return;
var inspectorElement = new InspectorElement(_editor);
Add(new IMGUIContainer(() =>
{
if (_gameObjectEditorElement?.parent != parent || _parentEditor == null ||
_parentEditor.target != _rawTarget)
{
EditorApplication.delayCall += Rebuild;
return;
}
switch (Event.current.rawType)
{
case EventType.Repaint:
case EventType.MouseMove:
case EventType.Layout:
break;
case EventType.MouseDrag:
case EventType.DragUpdated:
case EventType.DragPerform:
case EventType.DragExited:
return;
default:
break;
}
_foldoutState.Visible = EditorGUILayout.InspectorTitlebar(_foldoutState.Visible, _editor);
inspectorElement.style.display = _foldoutState.Visible ? DisplayStyle.Flex : DisplayStyle.None;
_editor.Visible = _foldoutState.Visible;
}));
_editor.Visible = _foldoutState.Visible;
Add(inspectorElement);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 18364f2754ed43c3baba0f1e18ac03cd
timeCreated: 1710226452

View File

@ -0,0 +1,7 @@
#region
using System.Runtime.CompilerServices;
#endregion
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")]

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6899d9fe550c492d887ce9e02d2a758b
timeCreated: 1710404112

View File

@ -0,0 +1,31 @@
{
"name": "nadena.dev.modular-avatar.param-introspection",
"rootNamespace": "",
"references": [
"GUID:fc900867c0f47cd49b6e2ae4ef907300",
"GUID:5ce33783346c3124990afbe7b0390a06",
"GUID:62ced99b048af7f4d8dfe4bed8373d76",
"GUID:5718fb738711cd34ea54e9553040911d",
"GUID:b906909fcc54f634db50f2cad0f988d9",
"GUID:901e56b065a857d4483a77f8cae73588"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [
"MODULAR_AVATAR_VRCSDK_AVATAR"
],
"versionDefines": [
{
"name": "com.vrchat.avatars",
"expression": "(0,999)",
"define": "MODULAR_AVATAR_VRCSDK_AVATAR"
}
],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f906ad1132cf10c48a65d14ae0809457
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,12 +1,19 @@
using System; #region
using System;
using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.animation;
using nadena.dev.modular_avatar.core.ArmatureAwase;
using nadena.dev.modular_avatar.core.editor.plugin;
using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.modular_avatar.editor.ErrorReporting;
using nadena.dev.ndmf; using nadena.dev.ndmf;
using nadena.dev.ndmf.fluent; using nadena.dev.ndmf.fluent;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object;
#endregion
[assembly: ExportsPlugin( [assembly: ExportsPlugin(
typeof(nadena.dev.modular_avatar.core.editor.plugin.PluginDefinition) typeof(PluginDefinition)
)] )]
namespace nadena.dev.modular_avatar.core.editor.plugin namespace nadena.dev.modular_avatar.core.editor.plugin
@ -17,6 +24,9 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
public override string DisplayName => "Modular Avatar"; public override string DisplayName => "Modular Avatar";
public override Texture2D LogoTexture => LogoDisplay.LOGO_ASSET; public override Texture2D LogoTexture => LogoDisplay.LOGO_ASSET;
// 00a0e9
public override Color? ThemeColor => new Color(0x00 / 255f, 0xa0 / 255f, 0xe9 / 255f, 1);
protected override void OnUnhandledException(Exception e) protected override void OnUnhandledException(Exception e)
{ {
BuildReport.LogException(e); BuildReport.LogException(e);
@ -82,11 +92,11 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
{ {
foreach (var component in ctx.AvatarRootTransform.GetComponentsInChildren<AvatarTagComponent>(true)) foreach (var component in ctx.AvatarRootTransform.GetComponentsInChildren<AvatarTagComponent>(true))
{ {
UnityEngine.Object.DestroyImmediate(component); Object.DestroyImmediate(component);
} }
foreach (var component in ctx.AvatarRootTransform.GetComponentsInChildren<ArmatureAwase.MAMoveIndependently>(true)) foreach (var component in ctx.AvatarRootTransform.GetComponentsInChildren<MAMoveIndependently>(true))
{ {
UnityEngine.Object.DestroyImmediate(component); Object.DestroyImmediate(component);
} }
}); });
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
@ -140,7 +150,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
{ {
foreach (var component in obj.GetComponentsInChildren<AvatarTagComponent>(true)) foreach (var component in obj.GetComponentsInChildren<AvatarTagComponent>(true))
{ {
UnityEngine.Object.DestroyImmediate(component); Object.DestroyImmediate(component);
} }
} }
else else

View File

@ -1,5 +1,7 @@
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
#region
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -18,8 +20,33 @@ using Object = UnityEngine.Object;
using UnityObject = UnityEngine.Object; using UnityObject = UnityEngine.Object;
#endregion
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class ParameterRenameMappings
{
public static ParameterRenameMappings Get(ndmf.BuildContext ctx)
{
return ctx.GetState<ParameterRenameMappings>();
}
public Dictionary<(ModularAvatarParameters, ParameterNamespace, string), string> Remappings =
new Dictionary<(ModularAvatarParameters, ParameterNamespace, string), string>();
private int internalParamIndex;
public string Remap(ModularAvatarParameters p, ParameterNamespace ns, string s)
{
var tuple = (p, ns, s);
if (Remappings.TryGetValue(tuple, out var mapping)) return mapping;
return s + "$$Internal_" + internalParamIndex++;
}
}
internal class DefaultValues internal class DefaultValues
{ {
public ImmutableDictionary<string, float> InitialValueOverrides; public ImmutableDictionary<string, float> InitialValueOverrides;
@ -609,6 +636,8 @@ namespace nadena.dev.modular_avatar.core.editor
ref ImmutableDictionary<string, string> prefixRemaps ref ImmutableDictionary<string, string> prefixRemaps
) )
{ {
var remapper = ParameterRenameMappings.Get(_context.PluginBuildContext);
ImmutableDictionary<string, ParameterInfo> parameterInfos = ImmutableDictionary<string, ParameterInfo>.Empty; ImmutableDictionary<string, ParameterInfo> parameterInfos = ImmutableDictionary<string, ParameterInfo>.Empty;
foreach (var param in p.parameters) foreach (var param in p.parameters)
@ -618,7 +647,9 @@ namespace nadena.dev.modular_avatar.core.editor
var remapTo = param.remapTo; var remapTo = param.remapTo;
if (param.internalParameter) if (param.internalParameter)
{ {
remapTo = param.nameOrPrefix + "$$Internal_" + internalParamIndex++; remapTo = remapper.Remap(p,
param.isPrefix ? ParameterNamespace.PhysBonesPrefix : ParameterNamespace.Animator,
param.nameOrPrefix);
} }
else if (string.IsNullOrWhiteSpace(remapTo)) else if (string.IsNullOrWhiteSpace(remapTo))
{ {

View File

@ -7,3 +7,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("net.fushizen.xdress")] [assembly: InternalsVisibleTo("net.fushizen.xdress")]
[assembly: InternalsVisibleTo("net.fushizen.xdress.editor")] [assembly: InternalsVisibleTo("net.fushizen.xdress.editor")]
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")] [assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")]
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.param-introspection")]

View File

@ -1,10 +1,12 @@
{ {
"name": "nadena.dev.modular-avatar.core.editor", "name": "nadena.dev.modular-avatar.core.editor",
"rootNamespace": "",
"references": [ "references": [
"nadena.dev.modular-avatar.core", "nadena.dev.modular-avatar.core",
"VRC.SDK3A", "VRC.SDK3A",
"VRC.SDKBase", "VRC.SDKBase",
"nadena.dev.ndmf" "nadena.dev.ndmf",
"nadena.dev.ndmf.vrchat"
], ],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"

View File

@ -9,3 +9,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("net.fushizen.xdress.editor")] [assembly: InternalsVisibleTo("net.fushizen.xdress.editor")]
[assembly: InternalsVisibleTo("Tests")] [assembly: InternalsVisibleTo("Tests")]
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")] [assembly: InternalsVisibleTo("nadena.dev.modular-avatar.harmony-patches")]
[assembly: InternalsVisibleTo("nadena.dev.modular-avatar.param-introspection")]

View File

@ -16,6 +16,6 @@
}, },
"vpmDependencies": { "vpmDependencies": {
"com.vrchat.avatars": ">=3.4.0", "com.vrchat.avatars": ">=3.4.0",
"nadena.dev.ndmf": ">=1.3.6 <2.0.0-a" "nadena.dev.ndmf": ">=1.4.0-rc.0 <2.0.0-a"
} }
} }