mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-05-07 03:39:00 +08:00
feat: Add error reporting UI
This commit is contained in:
parent
2a8c2ec3ce
commit
c2e6bb53cd
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -61,10 +62,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
if (!layer.isDefault && layer.animatorController is AnimatorController ac && Util.IsTemporaryAsset(ac))
|
if (!layer.isDefault && layer.animatorController is AnimatorController ac && Util.IsTemporaryAsset(ac))
|
||||||
{
|
{
|
||||||
foreach (var state in Util.States(ac))
|
BuildReport.ReportingObject(ac, () =>
|
||||||
{
|
{
|
||||||
RegisterState(state);
|
foreach (var state in Util.States(ac))
|
||||||
}
|
{
|
||||||
|
RegisterState(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -77,8 +78,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
if (acp.type != param.type)
|
if (acp.type != param.type)
|
||||||
{
|
{
|
||||||
throw new Exception(
|
BuildReport.LogFatal("error.merge_animator.param_type_mismatch", param.name, acp.type,
|
||||||
$"Parameter {param.name} has different types in {basePath} and {controller.name}");
|
param.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@ -27,10 +27,13 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEditor.Build.Reporting;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
using VRC.SDKBase.Editor.BuildPipeline;
|
using VRC.SDKBase.Editor.BuildPipeline;
|
||||||
|
using BuildReport = nadena.dev.modular_avatar.editor.ErrorReporting.BuildReport;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Tests")]
|
[assembly: InternalsVisibleTo("Tests")]
|
||||||
@ -83,17 +86,25 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
savePath = basePath + " " + (++extension);
|
savePath = basePath + " " + (++extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string originalBasePath = RuntimeUtil.RelativePath(null, avatar);
|
||||||
|
avatar = Object.Instantiate(avatar);
|
||||||
|
|
||||||
|
string clonedBasePath = RuntimeUtil.RelativePath(null, avatar);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Util.OverridePath = savePath;
|
Util.OverridePath = savePath;
|
||||||
|
|
||||||
avatar = Object.Instantiate(avatar);
|
var original = avatar;
|
||||||
avatar.transform.position += Vector3.forward * 2;
|
avatar.transform.position += Vector3.forward * 2;
|
||||||
|
|
||||||
|
BuildReport.Clear();
|
||||||
|
|
||||||
ProcessAvatar(avatar);
|
ProcessAvatar(avatar);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Util.OverridePath = null;
|
Util.OverridePath = null;
|
||||||
|
BuildReport.RemapPaths(originalBasePath, clonedBasePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +125,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
BuildReport.Clear();
|
||||||
ProcessAvatar(avatarGameObject);
|
ProcessAvatar(avatarGameObject);
|
||||||
FixupAnimatorDebugData(avatarGameObject);
|
FixupAnimatorDebugData(avatarGameObject);
|
||||||
return true;
|
return true;
|
||||||
@ -129,71 +141,76 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
if (nowProcessing) return;
|
if (nowProcessing) return;
|
||||||
|
|
||||||
try
|
var vrcAvatarDescriptor = avatarGameObject.GetComponent<VRCAvatarDescriptor>();
|
||||||
|
|
||||||
|
using (BuildReport.CurrentReport.ReportingOnAvatar(vrcAvatarDescriptor))
|
||||||
{
|
{
|
||||||
AssetDatabase.StartAssetEditing();
|
try
|
||||||
nowProcessing = true;
|
|
||||||
|
|
||||||
var vrcAvatarDescriptor = avatarGameObject.GetComponent<VRCAvatarDescriptor>();
|
|
||||||
|
|
||||||
BoneDatabase.ResetBones();
|
|
||||||
PathMappings.Init(vrcAvatarDescriptor.gameObject);
|
|
||||||
ClonedMenuMappings.Clear();
|
|
||||||
|
|
||||||
// Sometimes people like to nest one avatar in another, when transplanting clothing. To avoid issues
|
|
||||||
// with inconsistently determining the avatar root, we'll go ahead and remove the extra sub-avatars
|
|
||||||
// here.
|
|
||||||
foreach (Transform directChild in avatarGameObject.transform)
|
|
||||||
{
|
{
|
||||||
foreach (var component in directChild.GetComponentsInChildren<VRCAvatarDescriptor>(true))
|
AssetDatabase.StartAssetEditing();
|
||||||
|
nowProcessing = true;
|
||||||
|
|
||||||
|
BoneDatabase.ResetBones();
|
||||||
|
PathMappings.Init(vrcAvatarDescriptor.gameObject);
|
||||||
|
ClonedMenuMappings.Clear();
|
||||||
|
|
||||||
|
// Sometimes people like to nest one avatar in another, when transplanting clothing. To avoid issues
|
||||||
|
// with inconsistently determining the avatar root, we'll go ahead and remove the extra sub-avatars
|
||||||
|
// here.
|
||||||
|
foreach (Transform directChild in avatarGameObject.transform)
|
||||||
{
|
{
|
||||||
Object.DestroyImmediate(component);
|
foreach (var component in directChild.GetComponentsInChildren<VRCAvatarDescriptor>(true))
|
||||||
|
{
|
||||||
|
Object.DestroyImmediate(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var component in directChild.GetComponentsInChildren<PipelineSaver>(true))
|
||||||
|
{
|
||||||
|
Object.DestroyImmediate(component);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var component in directChild.GetComponentsInChildren<PipelineSaver>(true))
|
var context = new BuildContext(vrcAvatarDescriptor);
|
||||||
|
|
||||||
|
new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context);
|
||||||
|
new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
||||||
|
context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor);
|
||||||
|
|
||||||
|
new MenuInstallHook().OnPreprocessAvatar(avatarGameObject, context);
|
||||||
|
new MergeArmatureHook().OnPreprocessAvatar(context, avatarGameObject);
|
||||||
|
new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject);
|
||||||
|
new VisibleHeadAccessoryProcessor(vrcAvatarDescriptor).Process(context);
|
||||||
|
new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase);
|
||||||
|
new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
||||||
|
PhysboneBlockerPass.Process(avatarGameObject);
|
||||||
|
|
||||||
|
context.AnimationDatabase.Commit();
|
||||||
|
|
||||||
|
AfterProcessing?.Invoke(avatarGameObject);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
AssetDatabase.StopAssetEditing();
|
||||||
|
|
||||||
|
nowProcessing = false;
|
||||||
|
|
||||||
|
// Ensure that we clean up AvatarTagComponents after failed processing. This ensures we don't re-enter
|
||||||
|
// processing from the Awake method on the unprocessed AvatarTagComponents
|
||||||
|
foreach (var component in avatarGameObject.GetComponentsInChildren<AvatarTagComponent>(true))
|
||||||
{
|
{
|
||||||
Object.DestroyImmediate(component);
|
UnityEngine.Object.DestroyImmediate(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var activator = avatarGameObject.GetComponent<AvatarActivator>();
|
||||||
|
if (activator != null)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.DestroyImmediate(activator);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClonedMenuMappings.Clear();
|
||||||
|
|
||||||
|
ErrorReportUI.MaybeOpenErrorReportUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = new BuildContext(vrcAvatarDescriptor);
|
|
||||||
|
|
||||||
new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context);
|
|
||||||
new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
|
||||||
context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor);
|
|
||||||
|
|
||||||
new MenuInstallHook().OnPreprocessAvatar(avatarGameObject, context);
|
|
||||||
new MergeArmatureHook().OnPreprocessAvatar(context, avatarGameObject);
|
|
||||||
new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject);
|
|
||||||
new VisibleHeadAccessoryProcessor(vrcAvatarDescriptor).Process(context);
|
|
||||||
new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase);
|
|
||||||
new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
|
||||||
PhysboneBlockerPass.Process(avatarGameObject);
|
|
||||||
|
|
||||||
context.AnimationDatabase.Commit();
|
|
||||||
|
|
||||||
AfterProcessing?.Invoke(avatarGameObject);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
AssetDatabase.StopAssetEditing();
|
|
||||||
|
|
||||||
nowProcessing = false;
|
|
||||||
|
|
||||||
// Ensure that we clean up AvatarTagComponents after failed processing. This ensures we don't re-enter
|
|
||||||
// processing from the Awake method on the unprocessed AvatarTagComponents
|
|
||||||
foreach (var component in avatarGameObject.GetComponentsInChildren<AvatarTagComponent>(true))
|
|
||||||
{
|
|
||||||
UnityEngine.Object.DestroyImmediate(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
var activator = avatarGameObject.GetComponent<AvatarActivator>();
|
|
||||||
if (activator != null)
|
|
||||||
{
|
|
||||||
UnityEngine.Object.DestroyImmediate(activator);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClonedMenuMappings.Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -78,31 +79,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var component in components)
|
foreach (var component in components)
|
||||||
{
|
{
|
||||||
var targetObj = RuntimeUtil.RelativePath(avatarDescriptor.gameObject, component.gameObject);
|
BuildReport.ReportingObject(component, () => ProcessComponent(avatarDescriptor, component));
|
||||||
|
|
||||||
foreach (var binding in component.Bindings)
|
|
||||||
{
|
|
||||||
var refObj = binding.ReferenceMesh.Get(component);
|
|
||||||
if (refObj == null) continue;
|
|
||||||
var refSmr = refObj.GetComponent<SkinnedMeshRenderer>();
|
|
||||||
if (refSmr == null) continue;
|
|
||||||
|
|
||||||
var refPath = RuntimeUtil.RelativePath(avatarDescriptor.gameObject, refObj);
|
|
||||||
|
|
||||||
var srcBinding = new SummaryBinding(refPath, binding.Blendshape);
|
|
||||||
|
|
||||||
if (!_bindingMappings.TryGetValue(srcBinding, out var dstBindings))
|
|
||||||
{
|
|
||||||
dstBindings = new List<SummaryBinding>();
|
|
||||||
_bindingMappings[srcBinding] = dstBindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetBlendshapeName = string.IsNullOrWhiteSpace(binding.LocalBlendshape)
|
|
||||||
? binding.Blendshape
|
|
||||||
: binding.LocalBlendshape;
|
|
||||||
|
|
||||||
dstBindings.Add(new SummaryBinding(targetObj, targetBlendshapeName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk and transform all clips
|
// Walk and transform all clips
|
||||||
@ -110,11 +87,41 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
if (clip.CurrentClip is AnimationClip anim)
|
if (clip.CurrentClip is AnimationClip anim)
|
||||||
{
|
{
|
||||||
clip.CurrentClip = TransformMotion(anim);
|
BuildReport.ReportingObject(clip.CurrentClip,
|
||||||
|
() => { clip.CurrentClip = TransformMotion(anim); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessComponent(VRCAvatarDescriptor avatarDescriptor, ModularAvatarBlendshapeSync component)
|
||||||
|
{
|
||||||
|
var targetObj = RuntimeUtil.RelativePath(avatarDescriptor.gameObject, component.gameObject);
|
||||||
|
|
||||||
|
foreach (var binding in component.Bindings)
|
||||||
|
{
|
||||||
|
var refObj = binding.ReferenceMesh.Get(component);
|
||||||
|
if (refObj == null) continue;
|
||||||
|
var refSmr = refObj.GetComponent<SkinnedMeshRenderer>();
|
||||||
|
if (refSmr == null) continue;
|
||||||
|
|
||||||
|
var refPath = RuntimeUtil.RelativePath(avatarDescriptor.gameObject, refObj);
|
||||||
|
|
||||||
|
var srcBinding = new SummaryBinding(refPath, binding.Blendshape);
|
||||||
|
|
||||||
|
if (!_bindingMappings.TryGetValue(srcBinding, out var dstBindings))
|
||||||
|
{
|
||||||
|
dstBindings = new List<SummaryBinding>();
|
||||||
|
_bindingMappings[srcBinding] = dstBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetBlendshapeName = string.IsNullOrWhiteSpace(binding.LocalBlendshape)
|
||||||
|
? binding.Blendshape
|
||||||
|
: binding.LocalBlendshape;
|
||||||
|
|
||||||
|
dstBindings.Add(new SummaryBinding(targetObj, targetBlendshapeName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Motion TransformMotion(Motion motion)
|
Motion TransformMotion(Motion motion)
|
||||||
{
|
{
|
||||||
if (motion == null) return null;
|
if (motion == null) return null;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
@ -41,39 +42,44 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var proxy in boneProxies)
|
foreach (var proxy in boneProxies)
|
||||||
{
|
{
|
||||||
if (proxy.target != null && ValidateTarget(avatarGameObject, proxy.target) == ValidationResult.OK)
|
BuildReport.ReportingObject(proxy, () => ProcessProxy(avatarGameObject, proxy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessProxy(GameObject avatarGameObject, ModularAvatarBoneProxy proxy)
|
||||||
|
{
|
||||||
|
if (proxy.target != null && ValidateTarget(avatarGameObject, proxy.target) == ValidationResult.OK)
|
||||||
|
{
|
||||||
|
var oldPath = RuntimeUtil.AvatarRootPath(proxy.gameObject);
|
||||||
|
Transform transform = proxy.transform;
|
||||||
|
transform.SetParent(proxy.target, true);
|
||||||
|
|
||||||
|
bool keepPos, keepRot;
|
||||||
|
switch (proxy.attachmentMode)
|
||||||
{
|
{
|
||||||
var oldPath = RuntimeUtil.AvatarRootPath(proxy.gameObject);
|
default:
|
||||||
Transform transform = proxy.transform;
|
case BoneProxyAttachmentMode.Unset:
|
||||||
transform.SetParent(proxy.target, true);
|
case BoneProxyAttachmentMode.AsChildAtRoot:
|
||||||
|
keepPos = keepRot = false;
|
||||||
bool keepPos, keepRot;
|
break;
|
||||||
switch (proxy.attachmentMode)
|
case BoneProxyAttachmentMode.AsChildKeepWorldPose:
|
||||||
{
|
keepPos = keepRot = true;
|
||||||
default:
|
break;
|
||||||
case BoneProxyAttachmentMode.Unset:
|
case BoneProxyAttachmentMode.AsChildKeepPosition:
|
||||||
case BoneProxyAttachmentMode.AsChildAtRoot:
|
keepPos = true;
|
||||||
keepPos = keepRot = false;
|
keepRot = false;
|
||||||
break;
|
break;
|
||||||
case BoneProxyAttachmentMode.AsChildKeepWorldPose:
|
case BoneProxyAttachmentMode.AsChildKeepRotation:
|
||||||
keepPos = keepRot = true;
|
keepRot = true;
|
||||||
break;
|
keepPos = false;
|
||||||
case BoneProxyAttachmentMode.AsChildKeepPosition:
|
break;
|
||||||
keepPos = true;
|
|
||||||
keepRot = false;
|
|
||||||
break;
|
|
||||||
case BoneProxyAttachmentMode.AsChildKeepRotation:
|
|
||||||
keepRot = true;
|
|
||||||
keepPos = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!keepPos) transform.localPosition = Vector3.zero;
|
|
||||||
if (!keepRot) transform.localRotation = Quaternion.identity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.DestroyImmediate(proxy);
|
if (!keepPos) transform.localPosition = Vector3.zero;
|
||||||
|
if (!keepRot) transform.localRotation = Quaternion.identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.DestroyImmediate(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ValidationResult ValidateTarget(GameObject avatarGameObject, Transform proxyTarget)
|
internal static ValidationResult ValidateTarget(GameObject avatarGameObject, Transform proxyTarget)
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0fb1980d53a548a996bdfca622d468ca
|
||||||
|
timeCreated: 1674039787
|
||||||
|
folderAsset: yes
|
@ -0,0 +1,186 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using nadena.dev.modular_avatar.core;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
||||||
|
{
|
||||||
|
internal static class ComponentValidation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the provided tag component.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagComponent"></param>
|
||||||
|
/// <returns>Null if valid, otherwise a list of configuration errors</returns>
|
||||||
|
internal static List<ErrorLog> CheckComponent(this AvatarTagComponent tagComponent)
|
||||||
|
{
|
||||||
|
switch (tagComponent)
|
||||||
|
{
|
||||||
|
case ModularAvatarBlendshapeSync bs:
|
||||||
|
return CheckInternal(bs);
|
||||||
|
case ModularAvatarBoneProxy bp:
|
||||||
|
return CheckInternal(bp);
|
||||||
|
case ModularAvatarMenuInstaller mi:
|
||||||
|
return CheckInternal(mi);
|
||||||
|
case ModularAvatarMergeAnimator obj:
|
||||||
|
return CheckInternal(obj);
|
||||||
|
case ModularAvatarMergeArmature obj:
|
||||||
|
return CheckInternal(obj);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<ErrorLog> ValidateAll(GameObject root)
|
||||||
|
{
|
||||||
|
List<ErrorLog> logs = new List<ErrorLog>();
|
||||||
|
foreach (var component in root.GetComponentsInChildren<AvatarTagComponent>(true))
|
||||||
|
{
|
||||||
|
var componentLogs = component.CheckComponent();
|
||||||
|
if (componentLogs != null)
|
||||||
|
{
|
||||||
|
logs.AddRange(componentLogs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ErrorLog> CheckInternal(ModularAvatarBlendshapeSync bs)
|
||||||
|
{
|
||||||
|
var localMesh = bs.GetComponent<SkinnedMeshRenderer>();
|
||||||
|
if (localMesh == null)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>
|
||||||
|
{new ErrorLog(ReportLevel.Validation, "validation.blendshape_sync.no_local_renderer", bs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localMesh.sharedMesh == null)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>
|
||||||
|
{new ErrorLog(ReportLevel.Validation, "validation.blendshape_sync.no_local_mesh", bs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bs.Bindings == null || bs.Bindings.Count == 0)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>
|
||||||
|
{new ErrorLog(ReportLevel.Validation, "validation.blendshape_sync.no_bindings", bs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ErrorLog> errorLogs = new List<ErrorLog>();
|
||||||
|
foreach (var binding in bs.Bindings)
|
||||||
|
{
|
||||||
|
var localShape = string.IsNullOrWhiteSpace(binding.LocalBlendshape)
|
||||||
|
? binding.Blendshape
|
||||||
|
: binding.LocalBlendshape;
|
||||||
|
|
||||||
|
if (localMesh.sharedMesh.GetBlendShapeIndex(localShape) == -1)
|
||||||
|
{
|
||||||
|
errorLogs.Add(new ErrorLog(ReportLevel.Validation, "validation.blendshape_sync.missing_local_shape",
|
||||||
|
new string[] {localShape}, bs));
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetObj = binding.ReferenceMesh.Get(bs.transform);
|
||||||
|
if (targetObj == null)
|
||||||
|
{
|
||||||
|
errorLogs.Add(new ErrorLog(ReportLevel.Validation, "validation.blendshape_sync.no_target", bs));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetRenderer = targetObj.GetComponent<SkinnedMeshRenderer>();
|
||||||
|
if (targetRenderer == null)
|
||||||
|
{
|
||||||
|
errorLogs.Add(new ErrorLog(ReportLevel.Validation,
|
||||||
|
"validation.blendshape_sync.missing_target_renderer", bs, targetRenderer));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetMesh = targetRenderer.sharedMesh;
|
||||||
|
if (targetMesh == null)
|
||||||
|
{
|
||||||
|
errorLogs.Add(new ErrorLog(ReportLevel.Validation, "validation.blendshape_sync.missing_target_mesh",
|
||||||
|
bs, targetRenderer));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetMesh.GetBlendShapeIndex(binding.Blendshape) == -1)
|
||||||
|
{
|
||||||
|
errorLogs.Add(new ErrorLog(ReportLevel.Validation,
|
||||||
|
"validation.blendshape_sync.missing_target_shape", new string[] {binding.Blendshape}, bs,
|
||||||
|
targetRenderer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorLogs.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return errorLogs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ErrorLog> CheckInternal(ModularAvatarBoneProxy bp)
|
||||||
|
{
|
||||||
|
if (bp.target == null)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>()
|
||||||
|
{
|
||||||
|
new ErrorLog(ReportLevel.Validation, "validation.bone_proxy.no_target", bp)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ErrorLog> CheckInternal(ModularAvatarMenuInstaller mi)
|
||||||
|
{
|
||||||
|
// TODO - check that target menu is in the avatar
|
||||||
|
if (mi.menuToAppend == null)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>()
|
||||||
|
{
|
||||||
|
new ErrorLog(ReportLevel.Validation, "validation.menu_installer.no_menu", mi)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ErrorLog> CheckInternal(ModularAvatarMergeAnimator ma)
|
||||||
|
{
|
||||||
|
if (ma.animator == null)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>()
|
||||||
|
{
|
||||||
|
new ErrorLog(ReportLevel.Validation, "validation.merge_animator.no_animator", ma)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ErrorLog> CheckInternal(ModularAvatarMergeArmature ma)
|
||||||
|
{
|
||||||
|
if (ma.mergeTargetObject == null)
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>()
|
||||||
|
{
|
||||||
|
new ErrorLog(ReportLevel.Validation, "validation.merge_armature.no_target", ma)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ma.mergeTargetObject == ma.gameObject || ma.mergeTargetObject.transform.IsChildOf(ma.transform))
|
||||||
|
{
|
||||||
|
return new List<ErrorLog>()
|
||||||
|
{
|
||||||
|
new ErrorLog(ReportLevel.Validation, "error.merge_armature.merge_into_self", ma,
|
||||||
|
ma.mergeTargetObject)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3b500f7714e4401aa090eef81c0bab01
|
||||||
|
timeCreated: 1675855192
|
@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
||||||
|
{
|
||||||
|
internal class ErrorElement : Box
|
||||||
|
{
|
||||||
|
private readonly ErrorLog log;
|
||||||
|
|
||||||
|
Texture2D GetIcon()
|
||||||
|
{
|
||||||
|
switch (log.reportLevel)
|
||||||
|
{
|
||||||
|
case ReportLevel.Info:
|
||||||
|
return EditorGUIUtility.FindTexture("d_console.infoicon");
|
||||||
|
case ReportLevel.Warning:
|
||||||
|
return EditorGUIUtility.FindTexture("d_console.warnicon");
|
||||||
|
default:
|
||||||
|
return EditorGUIUtility.FindTexture("d_console.erroricon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorElement(ErrorLog log, ObjectRefLookupCache cache)
|
||||||
|
{
|
||||||
|
this.log = log;
|
||||||
|
|
||||||
|
AddToClassList("ErrorElement");
|
||||||
|
var tex = GetIcon();
|
||||||
|
if (tex != null)
|
||||||
|
{
|
||||||
|
var image = new Image();
|
||||||
|
image.image = tex;
|
||||||
|
Add(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
var inner = new Box();
|
||||||
|
Add(inner);
|
||||||
|
|
||||||
|
var label = new Label(GetLabelText());
|
||||||
|
inner.Add(label);
|
||||||
|
|
||||||
|
foreach (var obj in log.referencedObjects)
|
||||||
|
{
|
||||||
|
var referenced = obj.Lookup(cache);
|
||||||
|
if (referenced != null)
|
||||||
|
{
|
||||||
|
inner.Add(new SelectionButton(obj.typeName, referenced));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(log.stacktrace))
|
||||||
|
{
|
||||||
|
var foldout = new Foldout();
|
||||||
|
foldout.text = Localization.S("error.stack_trace");
|
||||||
|
var field = new TextField();
|
||||||
|
field.value = log.stacktrace;
|
||||||
|
field.isReadOnly = true;
|
||||||
|
field.multiline = true;
|
||||||
|
foldout.Add(field);
|
||||||
|
foldout.value = false;
|
||||||
|
inner.Add(foldout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject FindObject(string path)
|
||||||
|
{
|
||||||
|
var scene = SceneManager.GetActiveScene();
|
||||||
|
foreach (var root in scene.GetRootGameObjects())
|
||||||
|
{
|
||||||
|
if (root.name == path) return root;
|
||||||
|
if (path.StartsWith(root.name + "/"))
|
||||||
|
{
|
||||||
|
return root.transform.Find(path.Substring(root.name.Length + 1))?.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLabelText()
|
||||||
|
{
|
||||||
|
var objArray = new object[log.substitutions.Length];
|
||||||
|
for (int i = 0; i < log.substitutions.Length; i++)
|
||||||
|
{
|
||||||
|
objArray[i] = log.substitutions[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return string.Format(Localization.S(log.messageCode), objArray);
|
||||||
|
}
|
||||||
|
catch (FormatException e)
|
||||||
|
{
|
||||||
|
Debug.LogError("Error formatting message code: " + log.messageCode);
|
||||||
|
Debug.LogException(e);
|
||||||
|
return log.messageCode + "\n" + string.Join("\n", objArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a534edd7151c4cd49fe07919ae526004
|
||||||
|
timeCreated: 1674132977
|
@ -0,0 +1,388 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using nadena.dev.modular_avatar.core;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
||||||
|
{
|
||||||
|
internal class AvatarReport
|
||||||
|
{
|
||||||
|
[JsonProperty] internal ObjectRef objectRef;
|
||||||
|
|
||||||
|
[JsonProperty] internal bool successful;
|
||||||
|
|
||||||
|
[JsonProperty] internal List<ErrorLog> logs = new List<ErrorLog>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ObjectRefLookupCache
|
||||||
|
{
|
||||||
|
private Dictionary<string, Dictionary<long, UnityEngine.Object>> _cache =
|
||||||
|
new Dictionary<string, Dictionary<long, Object>>();
|
||||||
|
|
||||||
|
internal UnityEngine.Object FindByGuidAndLocalId(string guid, long localId)
|
||||||
|
{
|
||||||
|
if (!_cache.TryGetValue(guid, out var fileContents))
|
||||||
|
{
|
||||||
|
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
|
||||||
|
fileContents = new Dictionary<long, Object>(assets.Length);
|
||||||
|
foreach (var asset in assets)
|
||||||
|
{
|
||||||
|
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out var _, out long detectedId))
|
||||||
|
{
|
||||||
|
fileContents[detectedId] = asset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache[guid] = fileContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileContents.TryGetValue(localId, out var obj))
|
||||||
|
{
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ObjectRef
|
||||||
|
{
|
||||||
|
[JsonProperty] internal string guid;
|
||||||
|
[JsonProperty] internal long? localId;
|
||||||
|
[JsonProperty] internal string path, name;
|
||||||
|
[JsonProperty] internal string typeName;
|
||||||
|
|
||||||
|
internal ObjectRef(Object obj)
|
||||||
|
{
|
||||||
|
this.guid = null;
|
||||||
|
localId = null;
|
||||||
|
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
this.guid = path = name = null;
|
||||||
|
localId = null;
|
||||||
|
typeName = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeName = obj.GetType().Name;
|
||||||
|
|
||||||
|
long id;
|
||||||
|
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out id))
|
||||||
|
{
|
||||||
|
this.guid = guid;
|
||||||
|
localId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Component c)
|
||||||
|
{
|
||||||
|
path = RuntimeUtil.RelativePath(null, c.gameObject);
|
||||||
|
}
|
||||||
|
else if (obj is GameObject go)
|
||||||
|
{
|
||||||
|
path = RuntimeUtil.RelativePath(null, go);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = string.IsNullOrWhiteSpace(obj.name) ? "<???>" : obj.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal UnityEngine.Object Lookup(ObjectRefLookupCache cache)
|
||||||
|
{
|
||||||
|
if (path != null)
|
||||||
|
{
|
||||||
|
return FindObject(path);
|
||||||
|
}
|
||||||
|
else if (guid != null && localId.HasValue)
|
||||||
|
{
|
||||||
|
return cache.FindByGuidAndLocalId(guid, localId.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject FindObject(string path)
|
||||||
|
{
|
||||||
|
var scene = SceneManager.GetActiveScene();
|
||||||
|
foreach (var root in scene.GetRootGameObjects())
|
||||||
|
{
|
||||||
|
if (root.name == path) return root;
|
||||||
|
if (path.StartsWith(root.name + "/"))
|
||||||
|
{
|
||||||
|
return root.transform.Find(path.Substring(root.name.Length + 1))?.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectRef Remap(string original, string cloned)
|
||||||
|
{
|
||||||
|
if (path == cloned)
|
||||||
|
{
|
||||||
|
path = original;
|
||||||
|
name = path.Substring(path.LastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
else if (path != null && path.StartsWith(cloned + "/"))
|
||||||
|
{
|
||||||
|
path = original + path.Substring(cloned.Length);
|
||||||
|
name = path.Substring(path.LastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ReportLevel
|
||||||
|
{
|
||||||
|
Validation,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
InternalError,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ErrorLog
|
||||||
|
{
|
||||||
|
[JsonProperty] internal List<ObjectRef> referencedObjects;
|
||||||
|
[JsonProperty] internal ReportLevel reportLevel;
|
||||||
|
[JsonProperty] internal string messageCode;
|
||||||
|
[JsonProperty] internal string[] substitutions;
|
||||||
|
[JsonProperty] internal string stacktrace;
|
||||||
|
|
||||||
|
internal ErrorLog(ReportLevel level, string code, string[] strings, params object[] args)
|
||||||
|
{
|
||||||
|
reportLevel = level;
|
||||||
|
|
||||||
|
substitutions = strings.Select(s => s.ToString()).ToArray();
|
||||||
|
|
||||||
|
referencedObjects = args.Where(o => o is Component || o is GameObject)
|
||||||
|
.Select(o => new ObjectRef(o is Component c ? c.gameObject : (GameObject) o))
|
||||||
|
.ToList();
|
||||||
|
referencedObjects.AddRange(BuildReport.CurrentReport.GetActiveReferences());
|
||||||
|
|
||||||
|
messageCode = code;
|
||||||
|
stacktrace = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ErrorLog(ReportLevel level, string code, params object[] args) : this(level, code,
|
||||||
|
Array.Empty<string>(), args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ErrorLog(Exception e, string additionalStackTrace = "")
|
||||||
|
{
|
||||||
|
reportLevel = ReportLevel.InternalError;
|
||||||
|
messageCode = "error.internal_error";
|
||||||
|
substitutions = new string[] {e.Message, e.TargetSite?.Name};
|
||||||
|
referencedObjects = BuildReport.CurrentReport.GetActiveReferences().ToList();
|
||||||
|
stacktrace = e.ToString() + additionalStackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString()
|
||||||
|
{
|
||||||
|
return "[" + reportLevel + "] " + messageCode + " " + "subst: " + string.Join(", ", substitutions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class BuildReport
|
||||||
|
{
|
||||||
|
private const string Path = "Library/ModularAvatarBuildReport.json";
|
||||||
|
|
||||||
|
private static BuildReport _report;
|
||||||
|
private AvatarReport _currentAvatar;
|
||||||
|
private Stack<UnityEngine.Object> _references = new Stack<Object>();
|
||||||
|
|
||||||
|
[JsonProperty] internal List<AvatarReport> Avatars = new List<AvatarReport>();
|
||||||
|
internal AvatarReport CurrentAvatar => _currentAvatar;
|
||||||
|
|
||||||
|
public static BuildReport CurrentReport
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_report == null) _report = LoadReport() ?? new BuildReport();
|
||||||
|
return _report;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static BuildReport()
|
||||||
|
{
|
||||||
|
EditorApplication.playModeStateChanged += change =>
|
||||||
|
{
|
||||||
|
switch (change)
|
||||||
|
{
|
||||||
|
case PlayModeStateChange.ExitingEditMode:
|
||||||
|
// TODO - skip if we're doing a VRCSDK build
|
||||||
|
_report = new BuildReport();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BuildReport LoadReport()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = File.ReadAllText(Path);
|
||||||
|
return JsonConvert.DeserializeObject<BuildReport>(data);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void SaveReport()
|
||||||
|
{
|
||||||
|
var report = CurrentReport;
|
||||||
|
var json = JsonConvert.SerializeObject(report);
|
||||||
|
|
||||||
|
File.WriteAllText(Path, json);
|
||||||
|
|
||||||
|
ErrorReportUI.reloadErrorReport();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AvatarReportScope : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var successful = CurrentReport._currentAvatar.successful;
|
||||||
|
CurrentReport._currentAvatar = null;
|
||||||
|
BuildReport.SaveReport();
|
||||||
|
if (!successful) throw new Exception("Avatar processing failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IDisposable ReportingOnAvatar(VRCAvatarDescriptor descriptor)
|
||||||
|
{
|
||||||
|
if (descriptor != null)
|
||||||
|
{
|
||||||
|
AvatarReport report = new AvatarReport();
|
||||||
|
report.objectRef = new ObjectRef(descriptor.gameObject);
|
||||||
|
Avatars.Add(report);
|
||||||
|
_currentAvatar = report;
|
||||||
|
_currentAvatar.successful = true;
|
||||||
|
|
||||||
|
_currentAvatar.logs.AddRange(ComponentValidation.ValidateAll(descriptor.gameObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AvatarReportScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Log(ReportLevel level, string code, params object[] objects)
|
||||||
|
{
|
||||||
|
ErrorLog errorLog = new ErrorLog(level, code, objects);
|
||||||
|
|
||||||
|
var avatarReport = CurrentReport._currentAvatar;
|
||||||
|
if (avatarReport == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Error logged when not processing an avatar: " + errorLog);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarReport.logs.Add(errorLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void LogFatal(string code, params object[] objects)
|
||||||
|
{
|
||||||
|
Log(ReportLevel.Error, code, objects);
|
||||||
|
if (CurrentReport._currentAvatar != null)
|
||||||
|
{
|
||||||
|
CurrentReport._currentAvatar.successful = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Fatal error without error reporting scope");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void LogException(Exception e, string additionalStackTrace = "")
|
||||||
|
{
|
||||||
|
var avatarReport = CurrentReport._currentAvatar;
|
||||||
|
if (avatarReport == null)
|
||||||
|
{
|
||||||
|
Debug.LogException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
avatarReport.logs.Add(new ErrorLog(e, additionalStackTrace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static T ReportingObject<T>(UnityEngine.Object obj, Func<T> action)
|
||||||
|
{
|
||||||
|
CurrentReport._references.Push(obj);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return action();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
var additionalStackTrace = string.Join("\n", Environment.StackTrace.Split('\n').Skip(1)) + "\n";
|
||||||
|
LogException(e, additionalStackTrace);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CurrentReport._references.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ReportingObject(UnityEngine.Object obj, Action action)
|
||||||
|
{
|
||||||
|
ReportingObject(obj, () =>
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IEnumerable<ObjectRef> GetActiveReferences()
|
||||||
|
{
|
||||||
|
return _references.Select(o => new ObjectRef(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Clear()
|
||||||
|
{
|
||||||
|
_report = new BuildReport();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemapPaths(string original, string cloned)
|
||||||
|
{
|
||||||
|
foreach (var av in CurrentReport.Avatars)
|
||||||
|
{
|
||||||
|
av.objectRef = av.objectRef.Remap(original, cloned);
|
||||||
|
|
||||||
|
foreach (var log in av.logs)
|
||||||
|
{
|
||||||
|
log.referencedObjects = log.referencedObjects.Select(o => o.Remap(original, cloned)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorReportUI.reloadErrorReport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 08d5f5d12365416d94e2d97970a24f5d
|
||||||
|
timeCreated: 1674039799
|
@ -0,0 +1,306 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using nadena.dev.modular_avatar.core;
|
||||||
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
||||||
|
{
|
||||||
|
internal class ErrorReportUI : EditorWindow
|
||||||
|
{
|
||||||
|
internal static Action reloadErrorReport = () => { };
|
||||||
|
|
||||||
|
[MenuItem("Tools/Modular Avatar/Show error report", false, 100)]
|
||||||
|
public static void OpenErrorReportUI()
|
||||||
|
{
|
||||||
|
GetWindow<ErrorReportUI>().Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MaybeOpenErrorReportUI()
|
||||||
|
{
|
||||||
|
if (BuildReport.CurrentReport.Avatars.Any(av => av.logs.Count > 0))
|
||||||
|
{
|
||||||
|
OpenErrorReportUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 _avatarScrollPos, _errorScrollPos;
|
||||||
|
private int _selectedAvatar = -1;
|
||||||
|
private List<Button> _avatarButtons = new List<Button>();
|
||||||
|
|
||||||
|
private Box selectAvatar;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
titleContent = new GUIContent("Error Report");
|
||||||
|
|
||||||
|
rootVisualElement.styleSheets.Add(Resources.Load<StyleSheet>("ModularAvatarErrorReport"));
|
||||||
|
RenderContent();
|
||||||
|
|
||||||
|
reloadErrorReport = RenderContent;
|
||||||
|
|
||||||
|
Selection.selectionChanged += ScheduleRender;
|
||||||
|
EditorApplication.hierarchyChanged += ScheduleRender;
|
||||||
|
AvatarTagComponent.OnChangeAction += ScheduleRender;
|
||||||
|
Localization.OnLangChange += RenderContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
reloadErrorReport = () => { };
|
||||||
|
Selection.selectionChanged -= ScheduleRender;
|
||||||
|
EditorApplication.hierarchyChanged -= ScheduleRender;
|
||||||
|
AvatarTagComponent.OnChangeAction -= ScheduleRender;
|
||||||
|
Localization.OnLangChange -= RenderContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly int RefreshDelayTime = 500;
|
||||||
|
private Stopwatch DelayTimer = new Stopwatch();
|
||||||
|
private bool RenderPending = false;
|
||||||
|
|
||||||
|
private void ScheduleRender()
|
||||||
|
{
|
||||||
|
if (RenderPending) return;
|
||||||
|
RenderPending = true;
|
||||||
|
DelayTimer.Restart();
|
||||||
|
EditorApplication.delayCall += StartRenderTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartRenderTimer()
|
||||||
|
{
|
||||||
|
while (DelayTimer.ElapsedMilliseconds < RefreshDelayTime)
|
||||||
|
{
|
||||||
|
long remaining = RefreshDelayTime - DelayTimer.ElapsedMilliseconds;
|
||||||
|
if (remaining > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay((int) remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPending = false;
|
||||||
|
RenderContent();
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderContent()
|
||||||
|
{
|
||||||
|
rootVisualElement.Clear();
|
||||||
|
|
||||||
|
var root = new Box();
|
||||||
|
root.Clear();
|
||||||
|
root.name = "Root";
|
||||||
|
rootVisualElement.Add(root);
|
||||||
|
|
||||||
|
root.Add(CreateLogo());
|
||||||
|
|
||||||
|
var box = new ScrollView();
|
||||||
|
var lookupCache = new ObjectRefLookupCache();
|
||||||
|
|
||||||
|
int reported = 0;
|
||||||
|
|
||||||
|
AvatarReport activeAvatar = null;
|
||||||
|
|
||||||
|
GameObject activeAvatarObject = null;
|
||||||
|
if (Selection.gameObjects.Length == 1)
|
||||||
|
{
|
||||||
|
activeAvatarObject = RuntimeUtil.FindAvatarInParents(Selection.activeGameObject.transform)?.gameObject;
|
||||||
|
activeAvatar = BuildReport.CurrentReport.Avatars.FirstOrDefault(av =>
|
||||||
|
av.objectRef.path == RuntimeUtil.RelativePath(null, activeAvatarObject));
|
||||||
|
|
||||||
|
if (activeAvatar == null)
|
||||||
|
{
|
||||||
|
activeAvatar = new AvatarReport();
|
||||||
|
activeAvatar.objectRef = new ObjectRef(activeAvatarObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeAvatar == null)
|
||||||
|
{
|
||||||
|
activeAvatar = BuildReport.CurrentReport.Avatars.LastOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeAvatar != null)
|
||||||
|
{
|
||||||
|
reported++;
|
||||||
|
|
||||||
|
var avBox = new Box();
|
||||||
|
avBox.AddToClassList("avatarBox");
|
||||||
|
|
||||||
|
var header = new Box();
|
||||||
|
header.Add(new Label("Error report for " + activeAvatar.objectRef.name));
|
||||||
|
header.AddToClassList("avatarHeader");
|
||||||
|
avBox.Add(header);
|
||||||
|
|
||||||
|
List<ErrorLog> errorLogs = activeAvatar.logs
|
||||||
|
.Where(l => activeAvatarObject == null || l.reportLevel != ReportLevel.Validation).ToList();
|
||||||
|
|
||||||
|
if (activeAvatarObject != null)
|
||||||
|
{
|
||||||
|
activeAvatar.logs = errorLogs;
|
||||||
|
|
||||||
|
activeAvatar.logs.AddRange(ComponentValidation.ValidateAll(activeAvatarObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ev in activeAvatar.logs)
|
||||||
|
{
|
||||||
|
avBox.Add(new ErrorElement(ev, lookupCache));
|
||||||
|
}
|
||||||
|
|
||||||
|
activeAvatar.logs.Sort((a, b) => a.reportLevel.CompareTo(b.reportLevel));
|
||||||
|
|
||||||
|
box.Add(avBox);
|
||||||
|
root.Add(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (reported == 0)
|
||||||
|
{
|
||||||
|
var container = new Box();
|
||||||
|
container.name = "no-errors";
|
||||||
|
container.Add(new Label("Nothing to report!"));
|
||||||
|
root.Add(container);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement CreateLogo()
|
||||||
|
{
|
||||||
|
var img = new Image();
|
||||||
|
img.image = LogoDisplay.LOGO_ASSET;
|
||||||
|
|
||||||
|
// I've given up trying to get USS to resize proportionally for now :|
|
||||||
|
float height = 64;
|
||||||
|
img.style.height = new StyleLength(new Length(height, LengthUnit.Pixel));
|
||||||
|
img.style.width = new StyleLength(new Length(LogoDisplay.ImageWidth(height), LengthUnit.Pixel));
|
||||||
|
|
||||||
|
var box = new Box();
|
||||||
|
box.name = "logo";
|
||||||
|
box.Add(img);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement BuildErrorBox()
|
||||||
|
{
|
||||||
|
return new Box();
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement BuildSelectAvatarBox()
|
||||||
|
{
|
||||||
|
if (selectAvatar == null) selectAvatar = new Box();
|
||||||
|
selectAvatar.Clear();
|
||||||
|
_avatarButtons.Clear();
|
||||||
|
|
||||||
|
var avatars = BuildReport.CurrentReport.Avatars;
|
||||||
|
for (int i = 0; i < avatars.Count; i++)
|
||||||
|
{
|
||||||
|
var btn = new Button(() => SelectAvatar(i));
|
||||||
|
btn.text = avatars[i].objectRef.name;
|
||||||
|
_avatarButtons.Add(btn);
|
||||||
|
selectAvatar.Add(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectAvatar(_selectedAvatar);
|
||||||
|
|
||||||
|
return selectAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectAvatar(int idx)
|
||||||
|
{
|
||||||
|
_selectedAvatar = idx;
|
||||||
|
|
||||||
|
for (int i = 0; i < _avatarButtons.Count; i++)
|
||||||
|
{
|
||||||
|
if (_selectedAvatar == i)
|
||||||
|
{
|
||||||
|
_avatarButtons[i].AddToClassList("selected");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_avatarButtons[i].RemoveFromClassList("selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI___()
|
||||||
|
{
|
||||||
|
var report = BuildReport.CurrentReport;
|
||||||
|
|
||||||
|
AvatarReport selected = null;
|
||||||
|
EditorGUILayout.BeginVertical(GUILayout.MaxHeight(150), GUILayout.Width(position.width));
|
||||||
|
if (report.Avatars.Count == 0)
|
||||||
|
{
|
||||||
|
GUILayout.Label("<no build messages>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_avatarScrollPos = EditorGUILayout.BeginScrollView(_avatarScrollPos, false, true);
|
||||||
|
|
||||||
|
for (int i = 0; i < report.Avatars.Count; i++)
|
||||||
|
{
|
||||||
|
var avatarReport = report.Avatars[i];
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
if (GUILayout.Toggle(_selectedAvatar == i, avatarReport.objectRef.name, EditorStyles.toggle))
|
||||||
|
{
|
||||||
|
_selectedAvatar = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
var rect = EditorGUILayout.BeginVertical(GUILayout.Width(position.width));
|
||||||
|
|
||||||
|
_errorScrollPos = EditorGUILayout.BeginScrollView(_errorScrollPos, false, true);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical(
|
||||||
|
GUILayout.Width(rect.width
|
||||||
|
- GUI.skin.scrollView.margin.horizontal
|
||||||
|
- GUI.skin.scrollView.padding.horizontal),
|
||||||
|
GUILayout.ExpandWidth(false));
|
||||||
|
|
||||||
|
if (_selectedAvatar >= 0 && _selectedAvatar < BuildReport.CurrentReport.Avatars.Count)
|
||||||
|
{
|
||||||
|
foreach (var logEntry in BuildReport.CurrentReport.Avatars[_selectedAvatar].logs)
|
||||||
|
{
|
||||||
|
imguiRenderLogEntry(logEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void imguiRenderLogEntry(ErrorLog logEntry)
|
||||||
|
{
|
||||||
|
MessageType ty = MessageType.Error;
|
||||||
|
switch (logEntry.reportLevel)
|
||||||
|
{
|
||||||
|
case ReportLevel.InternalError:
|
||||||
|
case ReportLevel.Error:
|
||||||
|
ty = MessageType.Error;
|
||||||
|
break;
|
||||||
|
case ReportLevel.Warning:
|
||||||
|
ty = MessageType.Warning;
|
||||||
|
break;
|
||||||
|
case ReportLevel.Info:
|
||||||
|
ty = MessageType.Info;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox(logEntry.ToString(), ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c6dd917ee1894d58a0fa63c5edd9134d
|
||||||
|
timeCreated: 1674042526
|
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c9de7a0b7769b954ea5ca239afd0d4c5
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,91 @@
|
|||||||
|
VisualElement {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarHeader {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 200%;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ErrorElement {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ErrorElement > Image {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ErrorElement Box {
|
||||||
|
flex: 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 2px;
|
||||||
|
border-width: 0 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-width: 0;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ErrorElement Box Label {
|
||||||
|
white-space: normal; /* word wrap??? */
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-button {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
border-width: 0 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-button > Image {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#no-errors {
|
||||||
|
flex-grow: 1;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#no-errors Label {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarBox {
|
||||||
|
border-width: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Root {
|
||||||
|
border-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f11231cfb768b5a4da3f8965eeef0775
|
||||||
|
ScriptedImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
disableValidation: 0
|
@ -0,0 +1,32 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.editor.ErrorReporting
|
||||||
|
{
|
||||||
|
internal class SelectionButton : Box
|
||||||
|
{
|
||||||
|
private UnityEngine.Object target;
|
||||||
|
|
||||||
|
internal SelectionButton(string typeName, UnityEngine.Object target)
|
||||||
|
{
|
||||||
|
this.target = target;
|
||||||
|
|
||||||
|
AddToClassList("selection-button");
|
||||||
|
|
||||||
|
var tex = EditorGUIUtility.FindTexture("d_Search Icon");
|
||||||
|
var icon = new Image {image = tex};
|
||||||
|
Add(icon);
|
||||||
|
|
||||||
|
var button = new Button(() =>
|
||||||
|
{
|
||||||
|
Selection.activeObject = target;
|
||||||
|
EditorGUIUtility.PingObject(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
//button.Add(new Label("[" + typeName + "] " + target.name));
|
||||||
|
button.text = "[" + typeName + "] " + target.name;
|
||||||
|
Add(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d5b79370f6c94a3eb05376006f255790
|
||||||
|
timeCreated: 1674134609
|
@ -6,9 +6,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
internal static class LogoDisplay
|
internal static class LogoDisplay
|
||||||
{
|
{
|
||||||
private static Texture2D LOGO_ASSET;
|
internal static readonly Texture2D LOGO_ASSET;
|
||||||
private static float TARGET_HEIGHT => EditorStyles.label.lineHeight * 3;
|
private static float TARGET_HEIGHT => EditorStyles.label.lineHeight * 3;
|
||||||
|
|
||||||
|
internal static float ImageWidth(float height)
|
||||||
|
{
|
||||||
|
return (height / (float) LOGO_ASSET.height) * LOGO_ASSET.width;
|
||||||
|
}
|
||||||
|
|
||||||
private static GUIStyle STYLE => new GUIStyle()
|
private static GUIStyle STYLE => new GUIStyle()
|
||||||
{
|
{
|
||||||
fixedHeight = TARGET_HEIGHT,
|
fixedHeight = TARGET_HEIGHT,
|
||||||
@ -35,7 +40,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
if (LOGO_ASSET == null) return;
|
if (LOGO_ASSET == null) return;
|
||||||
|
|
||||||
var height = TARGET_HEIGHT;
|
var height = TARGET_HEIGHT;
|
||||||
var width = (height / (float) LOGO_ASSET.height) * LOGO_ASSET.width;
|
var width = ImageWidth(height);
|
||||||
var rect = GUILayoutUtility.GetRect(width, height);
|
var rect = GUILayoutUtility.GetRect(width, height);
|
||||||
|
|
||||||
GUI.DrawTexture(rect, LOGO_ASSET, ScaleMode.ScaleToFit);
|
GUI.DrawTexture(rect, LOGO_ASSET, ScaleMode.ScaleToFit);
|
||||||
|
@ -11,6 +11,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
internal static class Localization
|
internal static class Localization
|
||||||
{
|
{
|
||||||
|
public static event Action OnLangChange;
|
||||||
private const string FallbackLanguage = "en";
|
private const string FallbackLanguage = "en";
|
||||||
|
|
||||||
private const string localizationPathGuid = "488c994003974b3ab2796371cf627bca";
|
private const string localizationPathGuid = "488c994003974b3ab2796371cf627bca";
|
||||||
@ -22,7 +23,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
.Add("ja", "日本語")
|
.Add("ja", "日本語")
|
||||||
.Add("zh-hans", "简体中文");
|
.Add("zh-hans", "简体中文");
|
||||||
|
|
||||||
private static ImmutableList<string> SupportedLanguages = new string[] {"en", "ja", "zh-hans"}.ToImmutableList();
|
private static ImmutableList<string>
|
||||||
|
SupportedLanguages = new string[] {"en", "ja", "zh-hans"}.ToImmutableList();
|
||||||
|
|
||||||
private static string[] DisplayNames = SupportedLanguages.Select(l =>
|
private static string[] DisplayNames = SupportedLanguages.Select(l =>
|
||||||
{
|
{
|
||||||
@ -38,6 +40,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
public static void Reload()
|
public static void Reload()
|
||||||
{
|
{
|
||||||
Cache.Clear();
|
Cache.Clear();
|
||||||
|
OnLangChange?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableDictionary<string, string> GetLocalization(string lang)
|
private static ImmutableDictionary<string, string> GetLocalization(string lang)
|
||||||
|
@ -60,5 +60,23 @@
|
|||||||
"boneproxy.attachment.AsChildKeepPosition": "As child; keep position",
|
"boneproxy.attachment.AsChildKeepPosition": "As child; keep position",
|
||||||
"boneproxy.attachment.AsChildKeepRotation": "As child; keep rotation",
|
"boneproxy.attachment.AsChildKeepRotation": "As child; keep rotation",
|
||||||
"pb_blocker.help": "This object will not be affected by PhysBones attached to parents.",
|
"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."
|
"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!)",
|
||||||
|
"error.merge_armature.merge_into_self": "Your Merge Armature component is referencing itself, or a child of itself, as the merge target. You should reference the avatar's armature instead. Do not put Merge Armature on the avatar's main armature.",
|
||||||
|
"error.internal_error": "An internal error has occurred: {0}\nwhen processing:",
|
||||||
|
"error.merge_animator.param_type_mismatch": "Parameter {0} has multiple types: {1} != {2}",
|
||||||
|
"error.rename_params.too_many_synced_params": "Too many synced parameters: Cost {0} > {1}",
|
||||||
|
"validation.blendshape_sync.no_local_renderer": "No renderer found on this object",
|
||||||
|
"validation.blendshape_sync.no_local_mesh": "No mesh found on the renderer on this object",
|
||||||
|
"validation.blendshape_sync.no_bindings": "No blendshape bindings found on this object",
|
||||||
|
"validation.blendshape_sync.missing_local_shape": "Missing local blendshape: {0}",
|
||||||
|
"validation.blendshape_sync.missing_target_shape": "Missing target blendshape: {0}",
|
||||||
|
"validation.blendshape_sync.no_target": "No target object specified",
|
||||||
|
"validation.blendshape_sync.missing_target_renderer": "No renderer found on the target object",
|
||||||
|
"validation.blendshape_sync.missing_target_mesh": "No mesh found on the renderer on the target object",
|
||||||
|
"validation.bone_proxy.no_target": "No target object specified (or target object not found)",
|
||||||
|
"validation.menu_installer.no_menu": "No menu to install specified",
|
||||||
|
"validation.merge_animator.no_animator": "No animator to merge specified",
|
||||||
|
"validation.merge_armature.no_target": "No merge target specified",
|
||||||
|
"validation.merge_armature.target_is_child": "Merge target cannot be a child of this object"
|
||||||
}
|
}
|
@ -58,5 +58,22 @@
|
|||||||
"boneproxy.attachment.AsChildKeepPosition": "子として・ワールド位置を維持",
|
"boneproxy.attachment.AsChildKeepPosition": "子として・ワールド位置を維持",
|
||||||
"boneproxy.attachment.AsChildKeepRotation": "子として・ワールド向きを維持",
|
"boneproxy.attachment.AsChildKeepRotation": "子として・ワールド向きを維持",
|
||||||
"pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。",
|
"pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。",
|
||||||
"hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。"
|
"hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。",
|
||||||
|
"error.stack_trace": "スタックトレース(バグを報告する時は必ず添付してください!)",
|
||||||
|
"error.merge_armature.merge_into_self": "Merge Armatureに自分自身のオブジェクト、もしくは自分の子をターゲットにしてしています。かわりにアバターのメインArmatureを指定してください。アバター自体のArmatureに追加しないでください。",
|
||||||
|
"error.internal_error": "内部エラーが発生しました:{0}\n以下のオブジェクトの処理中に発生しました:",
|
||||||
|
"error.merge_animator.param_type_mismatch": "パラメーター {0} に複数の種別が設定されています: {1} != {2}",
|
||||||
|
"error.rename_params.too_many_synced_params": "同期パラメーターが多すぎます。コスト{0}が制限値の{1}を超えています。",
|
||||||
|
"validation.blendshape_sync.no_local_renderer": "このオブジェクトにはSkinnedMeshRendererがありません。",
|
||||||
|
"validation.blendshape_sync.no_local_mesh": "このオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。",
|
||||||
|
"validation.blendshape_sync.no_bindings": "このBlendshapeSyncにはバインドが設定されていません。",
|
||||||
|
"validation.blendshape_sync.missing_local_shape": "同期先のメッシュに該当するブレンドシェープ「{0}」がありません。",
|
||||||
|
"validation.blendshape_sync.missing_target_shape": "同期元のメッシュに該当するブレンドシェープ「{0}」がありません。",
|
||||||
|
"validation.blendshape_sync.no_target": "このBlendshapeSyncには同期元が設定されていないバインドがあります。",
|
||||||
|
"validation.blendshape_sync.missing_target_renderer": "同期元のオブジェクトにはSkinnedMeshRendererがありません。",
|
||||||
|
"validation.blendshape_sync.missing_target_mesh": "同期元のオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。",
|
||||||
|
"validation.bone_proxy.no_target": "ターゲットオブジェクトが未設定、もしくは存在しません。",
|
||||||
|
"validation.menu_installer.no_menu": "インストールするメニューがありません。",
|
||||||
|
"validation.merge_animator.no_animator": "Animator Controllerがありません。",
|
||||||
|
"validation.merge_armature.no_target": "ターゲットオブジェクトが未設定、もしくは存在しません。"
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
@ -59,12 +60,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (ModularAvatarMenuInstaller installer in menuInstallers)
|
foreach (ModularAvatarMenuInstaller installer in menuInstallers)
|
||||||
{
|
{
|
||||||
_menuTree.TraverseMenuInstaller(installer);
|
BuildReport.ReportingObject(installer, () => _menuTree.TraverseMenuInstaller(installer));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null))
|
foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null))
|
||||||
{
|
{
|
||||||
InstallMenu(childElement.installer);
|
BuildReport.ReportingObject(childElement.installer, () => InstallMenu(childElement.installer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -66,44 +67,50 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var merge in toMerge)
|
foreach (var merge in toMerge)
|
||||||
{
|
{
|
||||||
if (merge.animator == null) continue;
|
BuildReport.ReportingObject(merge, () => ProcessMergeAnimator(avatarGameObject, context, merge));
|
||||||
|
|
||||||
string basePath;
|
|
||||||
if (merge.pathMode == MergeAnimatorPathMode.Relative)
|
|
||||||
{
|
|
||||||
var relativePath = RuntimeUtil.RelativePath(avatarGameObject, merge.gameObject);
|
|
||||||
basePath = relativePath != "" ? relativePath + "/" : "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
basePath = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mergeSessions.TryGetValue(merge.layerType, out var session))
|
|
||||||
{
|
|
||||||
session = new AnimatorCombiner(context);
|
|
||||||
mergeSessions[merge.layerType] = session;
|
|
||||||
if (defaultControllers_.ContainsKey(merge.layerType))
|
|
||||||
{
|
|
||||||
session.AddController("", defaultControllers_[merge.layerType], null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool? writeDefaults = merge.matchAvatarWriteDefaults ? writeDefaults_[merge.layerType] : null;
|
|
||||||
mergeSessions[merge.layerType]
|
|
||||||
.AddController(basePath, (AnimatorController) merge.animator, writeDefaults);
|
|
||||||
|
|
||||||
if (merge.deleteAttachedAnimator)
|
|
||||||
{
|
|
||||||
var animator = merge.GetComponent<Animator>();
|
|
||||||
if (animator != null) Object.DestroyImmediate(animator);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers);
|
descriptor.baseAnimationLayers = FinishSessions(descriptor.baseAnimationLayers);
|
||||||
descriptor.specialAnimationLayers = FinishSessions(descriptor.specialAnimationLayers);
|
descriptor.specialAnimationLayers = FinishSessions(descriptor.specialAnimationLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessMergeAnimator(GameObject avatarGameObject, BuildContext context,
|
||||||
|
ModularAvatarMergeAnimator merge)
|
||||||
|
{
|
||||||
|
if (merge.animator == null) return;
|
||||||
|
|
||||||
|
string basePath;
|
||||||
|
if (merge.pathMode == MergeAnimatorPathMode.Relative)
|
||||||
|
{
|
||||||
|
var relativePath = RuntimeUtil.RelativePath(avatarGameObject, merge.gameObject);
|
||||||
|
basePath = relativePath != "" ? relativePath + "/" : "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
basePath = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mergeSessions.TryGetValue(merge.layerType, out var session))
|
||||||
|
{
|
||||||
|
session = new AnimatorCombiner(context);
|
||||||
|
mergeSessions[merge.layerType] = session;
|
||||||
|
if (defaultControllers_.ContainsKey(merge.layerType))
|
||||||
|
{
|
||||||
|
session.AddController("", defaultControllers_[merge.layerType], null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? writeDefaults = merge.matchAvatarWriteDefaults ? writeDefaults_[merge.layerType] : null;
|
||||||
|
mergeSessions[merge.layerType]
|
||||||
|
.AddController(basePath, (AnimatorController) merge.animator, writeDefaults);
|
||||||
|
|
||||||
|
if (merge.deleteAttachedAnimator)
|
||||||
|
{
|
||||||
|
var animator = merge.GetComponent<Animator>();
|
||||||
|
if (animator != null) Object.DestroyImmediate(animator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private VRCAvatarDescriptor.CustomAnimLayer[] FinishSessions(
|
private VRCAvatarDescriptor.CustomAnimLayer[] FinishSessions(
|
||||||
VRCAvatarDescriptor.CustomAnimLayer[] layers
|
VRCAvatarDescriptor.CustomAnimLayer[] layers
|
||||||
)
|
)
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Animations;
|
using UnityEngine.Animations;
|
||||||
@ -49,17 +50,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var mergeArmature in mergeArmatures)
|
foreach (var mergeArmature in mergeArmatures)
|
||||||
{
|
{
|
||||||
mergedObjects.Clear();
|
BuildReport.ReportingObject(mergeArmature, () =>
|
||||||
thisPassAdded.Clear();
|
{
|
||||||
MergeArmature(mergeArmature);
|
mergedObjects.Clear();
|
||||||
PruneDuplicatePhysBones();
|
thisPassAdded.Clear();
|
||||||
UnityEngine.Object.DestroyImmediate(mergeArmature);
|
MergeArmature(mergeArmature);
|
||||||
}
|
PruneDuplicatePhysBones();
|
||||||
|
UnityEngine.Object.DestroyImmediate(mergeArmature);
|
||||||
foreach (var renderer in avatarGameObject.transform.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
});
|
||||||
{
|
|
||||||
var bones = renderer.bones;
|
|
||||||
renderer.bones = bones;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
||||||
@ -274,7 +272,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
if (src == newParent)
|
if (src == newParent)
|
||||||
{
|
{
|
||||||
throw new Exception("[ModularAvatar] Attempted to merge an armature into itself! Aborting build...");
|
// Error reported by validation framework
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zipMerge)
|
if (zipMerge)
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDKBase.Editor.BuildPipeline;
|
using VRC.SDKBase.Editor.BuildPipeline;
|
||||||
@ -85,23 +86,26 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||||
{
|
{
|
||||||
if (renderer.sharedMesh == null) continue;
|
BuildReport.ReportingObject(renderer, () =>
|
||||||
|
|
||||||
bool isRetargetable = false;
|
|
||||||
foreach (var bone in renderer.bones)
|
|
||||||
{
|
{
|
||||||
if (BoneDatabase.GetRetargetedBone(bone) != null)
|
if (renderer.sharedMesh == null) return;
|
||||||
|
|
||||||
|
bool isRetargetable = false;
|
||||||
|
foreach (var bone in renderer.bones)
|
||||||
{
|
{
|
||||||
isRetargetable = true;
|
if (BoneDatabase.GetRetargetedBone(bone) != null)
|
||||||
break;
|
{
|
||||||
|
isRetargetable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (isRetargetable)
|
if (isRetargetable)
|
||||||
{
|
{
|
||||||
var newMesh = new MeshRetargeter(renderer).Retarget();
|
var newMesh = new MeshRetargeter(renderer).Retarget();
|
||||||
_context.SaveAsset(newMesh);
|
_context.SaveAsset(newMesh);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now remove retargeted bones
|
// Now remove retargeted bones
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Dynamics.PhysBone.Components;
|
using VRC.SDK3.Dynamics.PhysBone.Components;
|
||||||
|
|
||||||
@ -43,28 +44,34 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
var avatarTransform = avatarRoot.transform;
|
var avatarTransform = avatarRoot.transform;
|
||||||
foreach (var tip in blockers)
|
foreach (var tip in blockers)
|
||||||
{
|
{
|
||||||
var node = tip.transform;
|
BuildReport.ReportingObject(tip, () =>
|
||||||
// We deliberately skip the node itself to allow for a specific PhysBone to be attached here.
|
|
||||||
while (node != null && node != avatarTransform && node.parent != null)
|
|
||||||
{
|
{
|
||||||
node = node.parent;
|
var node = tip.transform;
|
||||||
if (!physBoneRootToIgnores.TryGetValue(node, out var parent))
|
// We deliberately skip the node itself to allow for a specific PhysBone to be attached here.
|
||||||
|
while (node != null && node != avatarTransform && node.parent != null)
|
||||||
{
|
{
|
||||||
parent = new List<Transform>();
|
node = node.parent;
|
||||||
physBoneRootToIgnores.Add(node, parent);
|
if (!physBoneRootToIgnores.TryGetValue(node, out var parent))
|
||||||
}
|
{
|
||||||
|
parent = new List<Transform>();
|
||||||
|
physBoneRootToIgnores.Add(node, parent);
|
||||||
|
}
|
||||||
|
|
||||||
parent.Add(tip.transform);
|
parent.Add(tip.transform);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var pb in physBones)
|
foreach (var pb in physBones)
|
||||||
{
|
{
|
||||||
var root = pb.rootTransform != null ? pb.rootTransform : pb.transform;
|
BuildReport.ReportingObject(pb, () =>
|
||||||
if (physBoneRootToIgnores.TryGetValue(root, out var ignores))
|
|
||||||
{
|
{
|
||||||
pb.ignoreTransforms.AddRange(ignores);
|
var root = pb.rootTransform != null ? pb.rootTransform : pb.transform;
|
||||||
}
|
if (physBoneRootToIgnores.TryGetValue(root, out var ignores))
|
||||||
|
{
|
||||||
|
pb.ignoreTransforms.AddRange(ignores);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using UnityEditor;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
@ -22,10 +23,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
PathMappings.ClearCache();
|
PathMappings.ClearCache();
|
||||||
animDb.ForeachClip(clip =>
|
animDb.ForeachClip(clip =>
|
||||||
{
|
{
|
||||||
if (clip.CurrentClip is AnimationClip anim && !clip.IsProxyAnimation)
|
BuildReport.ReportingObject(clip.CurrentClip, () =>
|
||||||
{
|
{
|
||||||
clip.CurrentClip = MapMotion(anim);
|
if (clip.CurrentClip is AnimationClip anim && !clip.IsProxyAnimation)
|
||||||
}
|
{
|
||||||
|
clip.CurrentClip = MapMotion(anim);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -71,10 +72,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
expParams.parameters = parameters.ToArray();
|
expParams.parameters = parameters.ToArray();
|
||||||
if (expParams.CalcTotalCost() > VRCExpressionParameters.MAX_PARAMETER_COST)
|
if (expParams.CalcTotalCost() > VRCExpressionParameters.MAX_PARAMETER_COST)
|
||||||
{
|
{
|
||||||
throw new Exception("Too many synced parameters: " +
|
BuildReport.LogFatal("error.rename_params.too_many_synced_params", expParams.CalcTotalCost(),
|
||||||
"Cost " + expParams.CalcTotalCost() + " > "
|
VRCExpressionParameters.MAX_PARAMETER_COST);
|
||||||
+ VRCExpressionParameters.MAX_PARAMETER_COST
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
avatar.expressionParameters = expParams;
|
avatar.expressionParameters = expParams;
|
||||||
@ -89,7 +88,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
var p = obj.GetComponent<ModularAvatarParameters>();
|
var p = obj.GetComponent<ModularAvatarParameters>();
|
||||||
if (p != null)
|
if (p != null)
|
||||||
{
|
{
|
||||||
ApplyRemappings(p, ref remaps, ref prefixRemaps);
|
BuildReport.ReportingObject(p, () => ApplyRemappings(p, ref remaps, ref prefixRemaps));
|
||||||
}
|
}
|
||||||
|
|
||||||
var willPurgeAnimators = false;
|
var willPurgeAnimators = false;
|
||||||
@ -104,76 +103,79 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var component in obj.GetComponents<Component>())
|
foreach (var component in obj.GetComponents<Component>())
|
||||||
{
|
{
|
||||||
switch (component)
|
BuildReport.ReportingObject(component, () =>
|
||||||
{
|
{
|
||||||
case VRCPhysBone bone:
|
switch (component)
|
||||||
{
|
{
|
||||||
if (bone.parameter != null && prefixRemaps.TryGetValue(bone.parameter, out var newVal))
|
case VRCPhysBone bone:
|
||||||
{
|
{
|
||||||
bone.parameter = newVal;
|
if (bone.parameter != null && prefixRemaps.TryGetValue(bone.parameter, out var newVal))
|
||||||
|
{
|
||||||
|
bone.parameter = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
case VRCContactReceiver contact:
|
||||||
|
{
|
||||||
|
if (contact.parameter != null && remaps.TryGetValue(contact.parameter, out var newVal))
|
||||||
|
{
|
||||||
|
contact.parameter = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Animator anim:
|
||||||
|
{
|
||||||
|
if (willPurgeAnimators) break; // animator will be deleted in subsequent processing
|
||||||
|
|
||||||
|
// RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController
|
||||||
|
if (anim.runtimeAnimatorController is AnimatorOverrideController overrideController)
|
||||||
|
{
|
||||||
|
anim.runtimeAnimatorController = _context.ConvertAnimatorController(overrideController);
|
||||||
|
}
|
||||||
|
|
||||||
|
var controller = anim.runtimeAnimatorController as AnimatorController;
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
ProcessAnimator(ref controller, remaps);
|
||||||
|
anim.runtimeAnimatorController = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ModularAvatarMergeAnimator merger:
|
||||||
|
{
|
||||||
|
// RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController
|
||||||
|
if (merger.animator is AnimatorOverrideController overrideController)
|
||||||
|
{
|
||||||
|
merger.animator = _context.ConvertAnimatorController(overrideController);
|
||||||
|
}
|
||||||
|
|
||||||
|
var controller = merger.animator as AnimatorController;
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
ProcessAnimator(ref controller, remaps);
|
||||||
|
merger.animator = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ModularAvatarMenuInstaller installer:
|
||||||
|
{
|
||||||
|
if (installer.menuToAppend != null && installer.enabled)
|
||||||
|
{
|
||||||
|
ProcessMenu(ref installer.menuToAppend, remaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
case VRCContactReceiver contact:
|
|
||||||
{
|
|
||||||
if (contact.parameter != null && remaps.TryGetValue(contact.parameter, out var newVal))
|
|
||||||
{
|
|
||||||
contact.parameter = newVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Animator anim:
|
|
||||||
{
|
|
||||||
if (willPurgeAnimators) break; // animator will be deleted in subsequent processing
|
|
||||||
|
|
||||||
// RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController
|
|
||||||
if (anim.runtimeAnimatorController is AnimatorOverrideController overrideController)
|
|
||||||
{
|
|
||||||
anim.runtimeAnimatorController = _context.ConvertAnimatorController(overrideController);
|
|
||||||
}
|
|
||||||
|
|
||||||
var controller = anim.runtimeAnimatorController as AnimatorController;
|
|
||||||
if (controller != null)
|
|
||||||
{
|
|
||||||
ProcessAnimator(ref controller, remaps);
|
|
||||||
anim.runtimeAnimatorController = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ModularAvatarMergeAnimator merger:
|
|
||||||
{
|
|
||||||
// RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController
|
|
||||||
if (merger.animator is AnimatorOverrideController overrideController)
|
|
||||||
{
|
|
||||||
merger.animator = _context.ConvertAnimatorController(overrideController);
|
|
||||||
}
|
|
||||||
|
|
||||||
var controller = merger.animator as AnimatorController;
|
|
||||||
if (controller != null)
|
|
||||||
{
|
|
||||||
ProcessAnimator(ref controller, remaps);
|
|
||||||
merger.animator = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ModularAvatarMenuInstaller installer:
|
|
||||||
{
|
|
||||||
if (installer.menuToAppend != null && installer.enabled)
|
|
||||||
{
|
|
||||||
ProcessMenu(ref installer.menuToAppend, remaps);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Transform child in obj.transform)
|
foreach (Transform child in obj.transform)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Animations;
|
using UnityEngine.Animations;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
@ -61,7 +62,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var target in _avatar.GetComponentsInChildren<ModularAvatarVisibleHeadAccessory>(true))
|
foreach (var target in _avatar.GetComponentsInChildren<ModularAvatarVisibleHeadAccessory>(true))
|
||||||
{
|
{
|
||||||
var w = Process(target);
|
var w = BuildReport.ReportingObject(target, () => Process(target));
|
||||||
didWork = didWork || w;
|
didWork = didWork || w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +71,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
// Process meshes
|
// Process meshes
|
||||||
foreach (var smr in _avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
foreach (var smr in _avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||||
{
|
{
|
||||||
new VisibleHeadAccessoryMeshProcessor(smr, _visibleBones, _proxyHead).Retarget(context);
|
BuildReport.ReportingObject(smr,
|
||||||
|
() => new VisibleHeadAccessoryMeshProcessor(smr, _visibleBones, _proxyHead).Retarget(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
@ -32,6 +33,8 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
[DefaultExecutionOrder(-9999)] // run before av3emu
|
[DefaultExecutionOrder(-9999)] // run before av3emu
|
||||||
public abstract class AvatarTagComponent : MonoBehaviour
|
public abstract class AvatarTagComponent : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
internal static event Action OnChangeAction;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
if (!RuntimeUtil.isPlaying || this == null) return;
|
if (!RuntimeUtil.isPlaying || this == null) return;
|
||||||
@ -44,7 +47,7 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
RuntimeUtil.OnDemandProcessAvatar(RuntimeUtil.OnDemandSource.Start, this);
|
RuntimeUtil.OnDemandProcessAvatar(RuntimeUtil.OnDemandSource.Start, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValidate()
|
protected virtual void OnValidate()
|
||||||
{
|
{
|
||||||
if (RuntimeUtil.isPlaying) return;
|
if (RuntimeUtil.isPlaying) return;
|
||||||
|
|
||||||
@ -55,6 +58,13 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
Activator.CreateIfNotPresent(gameObject.scene);
|
Activator.CreateIfNotPresent(gameObject.scene);
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
|
||||||
|
OnChangeAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnDestroy()
|
||||||
|
{
|
||||||
|
OnChangeAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,8 +48,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
private List<EditorBlendshapeBinding> _editorBindings;
|
private List<EditorBlendshapeBinding> _editorBindings;
|
||||||
|
|
||||||
private void OnValidate()
|
protected override void OnValidate()
|
||||||
{
|
{
|
||||||
|
base.OnValidate();
|
||||||
|
|
||||||
if (RuntimeUtil.isPlaying) return;
|
if (RuntimeUtil.isPlaying) return;
|
||||||
RuntimeUtil.delayCall(Rebind);
|
RuntimeUtil.delayCall(Rebind);
|
||||||
RuntimeUtil.OnHierarchyChanged -= Rebind;
|
RuntimeUtil.OnHierarchyChanged -= Rebind;
|
||||||
|
@ -58,8 +58,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
private List<BoneBinding> lockedBones;
|
private List<BoneBinding> lockedBones;
|
||||||
|
|
||||||
void OnValidate()
|
protected override void OnValidate()
|
||||||
{
|
{
|
||||||
|
base.OnValidate();
|
||||||
|
|
||||||
RuntimeUtil.delayCall(() =>
|
RuntimeUtil.delayCall(() =>
|
||||||
{
|
{
|
||||||
if (this == null) return;
|
if (this == null) return;
|
||||||
|
@ -61,7 +61,7 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
child = child.transform.parent?.gameObject;
|
child = child.transform.parent?.gameObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child == null) return null;
|
if (child == null && root != null) return null;
|
||||||
|
|
||||||
pathSegments.Reverse();
|
pathSegments.Reverse();
|
||||||
return String.Join("/", pathSegments);
|
return String.Join("/", pathSegments);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user