mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
feat: NDMF integration
This commit is contained in:
parent
b155202714
commit
99386fc756
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Packages/nadena.dev.ndmf"]
|
||||||
|
path = Packages/nadena.dev.ndmf
|
||||||
|
url = https://github.com/bdunderscore/ndmf.git
|
@ -29,15 +29,15 @@ namespace modular_avatar_tests
|
|||||||
|
|
||||||
var boneProxy = reference.AddComponent<ModularAvatarBoneProxy>();
|
var boneProxy = reference.AddComponent<ModularAvatarBoneProxy>();
|
||||||
boneProxy.target = root.transform;
|
boneProxy.target = root.transform;
|
||||||
boneProxy.ClearCache();
|
boneProxy.ClearCache(true);
|
||||||
Assert.AreEqual(root.transform, boneProxy.target);
|
Assert.AreEqual(root.transform, boneProxy.target);
|
||||||
|
|
||||||
boneProxy.target = target.transform;
|
boneProxy.target = target.transform;
|
||||||
boneProxy.ClearCache();
|
boneProxy.ClearCache(true);
|
||||||
Assert.AreEqual(target.transform, boneProxy.target);
|
Assert.AreEqual(target.transform, boneProxy.target);
|
||||||
|
|
||||||
target.name = "target2";
|
target.name = "target2";
|
||||||
boneProxy.ClearCache();
|
boneProxy.ClearCache(true);
|
||||||
Assert.IsNull(boneProxy.target);
|
Assert.IsNull(boneProxy.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ namespace modular_avatar_tests
|
|||||||
Texture2D _iconTexture;
|
Texture2D _iconTexture;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public virtual void Setup()
|
public override void Setup()
|
||||||
{
|
{
|
||||||
base.Setup();
|
base.Setup();
|
||||||
_gameObject = new GameObject();
|
_gameObject = new GameObject();
|
||||||
@ -40,10 +40,10 @@ namespace modular_avatar_tests
|
|||||||
if (type == typeof(TestComponent)) return;
|
if (type == typeof(TestComponent)) return;
|
||||||
|
|
||||||
// get icon
|
// get icon
|
||||||
var component = (MonoBehaviour)_gameObject.AddComponent(type);
|
var component = (MonoBehaviour) _gameObject.AddComponent(type);
|
||||||
var monoScript = MonoScript.FromMonoBehaviour(component);
|
var monoScript = MonoScript.FromMonoBehaviour(component);
|
||||||
var scriptPath = AssetDatabase.GetAssetPath(monoScript);
|
var scriptPath = AssetDatabase.GetAssetPath(monoScript);
|
||||||
var monoImporter = (MonoImporter)AssetImporter.GetAtPath(scriptPath);
|
var monoImporter = (MonoImporter) AssetImporter.GetAtPath(scriptPath);
|
||||||
// in Unity 2021.2, we can use monoImporter.GetIcon()
|
// in Unity 2021.2, we can use monoImporter.GetIcon()
|
||||||
// but it's not available in unity 2019 so use SerializedObject
|
// but it's not available in unity 2019 so use SerializedObject
|
||||||
var serializedImporter = new SerializedObject(monoImporter);
|
var serializedImporter = new SerializedObject(monoImporter);
|
||||||
|
@ -6,6 +6,7 @@ namespace _ModularAvatar.EditModeTests
|
|||||||
{
|
{
|
||||||
public class DuplicateObjectNameTest : TestBase
|
public class DuplicateObjectNameTest : TestBase
|
||||||
{
|
{
|
||||||
|
/* TODO - move to build framework
|
||||||
[Test]
|
[Test]
|
||||||
public void test_duplicate_object_names()
|
public void test_duplicate_object_names()
|
||||||
{
|
{
|
||||||
@ -17,5 +18,6 @@ namespace _ModularAvatar.EditModeTests
|
|||||||
c2.gameObject.name = "child2";
|
c2.gameObject.name = "child2";
|
||||||
Assert.AreEqual(PathMappings.MapPath("child"), "child");
|
Assert.AreEqual(PathMappings.MapPath("child"), "child");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using nadena.dev.ndmf.animation;
|
||||||
using nadena.dev.modular_avatar.core;
|
using nadena.dev.modular_avatar.core;
|
||||||
using nadena.dev.modular_avatar.core.editor;
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -51,7 +52,10 @@ namespace modular_avatar_tests.MergeArmatureTests
|
|||||||
m1_leaf2.AddComponent<TestComponentA>();
|
m1_leaf2.AddComponent<TestComponentA>();
|
||||||
m2_leaf3.AddComponent<TestComponentB>();
|
m2_leaf3.AddComponent<TestComponentB>();
|
||||||
|
|
||||||
BuildContext context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
nadena.dev.ndmf.BuildContext context =
|
||||||
|
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||||
|
context.ActivateExtensionContext<ModularAvatarContext>();
|
||||||
|
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext));
|
||||||
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||||
|
|
||||||
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
|
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
|
||||||
@ -76,7 +80,10 @@ namespace modular_avatar_tests.MergeArmatureTests
|
|||||||
ma.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
ma.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
||||||
ma.mangleNames = false;
|
ma.mangleNames = false;
|
||||||
|
|
||||||
BuildContext context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
nadena.dev.ndmf.BuildContext context =
|
||||||
|
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||||
|
context.ActivateExtensionContext<ModularAvatarContext>();
|
||||||
|
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext));
|
||||||
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||||
|
|
||||||
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
||||||
@ -97,7 +104,10 @@ namespace modular_avatar_tests.MergeArmatureTests
|
|||||||
var ma = merge.AddComponent<ModularAvatarMergeArmature>();
|
var ma = merge.AddComponent<ModularAvatarMergeArmature>();
|
||||||
ma.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
ma.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
||||||
|
|
||||||
BuildContext context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
nadena.dev.ndmf.BuildContext context =
|
||||||
|
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||||
|
context.ActivateExtensionContext<ModularAvatarContext>();
|
||||||
|
context.ActivateExtensionContext(typeof(TrackObjectRenamesContext));
|
||||||
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||||
|
|
||||||
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
using nadena.dev.modular_avatar.core.editor;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace modular_avatar_tests
|
|
||||||
{
|
|
||||||
public class PathMappingTest : TestBase
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void TracksSimpleRenames()
|
|
||||||
{
|
|
||||||
var root = CreateRoot("root");
|
|
||||||
var a = CreateChild(root, "a");
|
|
||||||
|
|
||||||
PathMappings.Init(root);
|
|
||||||
Assert.AreEqual("a", PathMappings.MapPath("a"));
|
|
||||||
a.name = "b";
|
|
||||||
PathMappings.ClearCache();
|
|
||||||
Assert.AreEqual("b", PathMappings.MapPath("a"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TracksObjectMoves()
|
|
||||||
{
|
|
||||||
var root = CreateRoot("root");
|
|
||||||
var a = CreateChild(root, "a");
|
|
||||||
var b = CreateChild(root, "b");
|
|
||||||
|
|
||||||
PathMappings.Init(root);
|
|
||||||
Assert.AreEqual("a", PathMappings.MapPath("a"));
|
|
||||||
a.transform.parent = b.transform;
|
|
||||||
PathMappings.ClearCache();
|
|
||||||
Assert.AreEqual("b/a", PathMappings.MapPath("a"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TracksCollapses()
|
|
||||||
{
|
|
||||||
var root = CreateRoot("root");
|
|
||||||
var a = CreateChild(root, "a");
|
|
||||||
var b = CreateChild(a, "b");
|
|
||||||
var c = CreateChild(b, "c");
|
|
||||||
|
|
||||||
PathMappings.Init(root);
|
|
||||||
PathMappings.MarkRemoved(b);
|
|
||||||
c.transform.parent = a.transform;
|
|
||||||
Object.DestroyImmediate(b);
|
|
||||||
|
|
||||||
Assert.AreEqual("a/c", PathMappings.MapPath("a/b/c"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TransformLookthrough()
|
|
||||||
{
|
|
||||||
var root = CreateRoot("root");
|
|
||||||
var a = CreateChild(root, "a");
|
|
||||||
var b = CreateChild(a, "b");
|
|
||||||
var c = CreateChild(b, "c");
|
|
||||||
var d = CreateChild(c, "d");
|
|
||||||
|
|
||||||
PathMappings.Init(root);
|
|
||||||
PathMappings.MarkTransformLookthrough(b);
|
|
||||||
PathMappings.MarkTransformLookthrough(c);
|
|
||||||
Assert.AreEqual("a/b/c", PathMappings.MapPath("a/b/c"));
|
|
||||||
Assert.AreEqual("a", PathMappings.MapPath("a/b/c", true));
|
|
||||||
Assert.AreEqual("a/b/c/d", PathMappings.MapPath("a/b/c/d", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fc087947fd98b2b43a853f93161cfe13
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using nadena.dev.ndmf.animation;
|
||||||
using nadena.dev.modular_avatar.core;
|
using nadena.dev.modular_avatar.core;
|
||||||
using nadena.dev.modular_avatar.core.editor;
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
@ -10,10 +11,14 @@ namespace modular_avatar_tests.ReplaceObject
|
|||||||
{
|
{
|
||||||
public class ReplaceObjectTests : TestBase
|
public class ReplaceObjectTests : TestBase
|
||||||
{
|
{
|
||||||
|
private TrackObjectRenamesContext pathMappings;
|
||||||
|
|
||||||
void Process(GameObject root)
|
void Process(GameObject root)
|
||||||
{
|
{
|
||||||
var avDesc = root.GetComponent<VRCAvatarDescriptor>();
|
var avDesc = root.GetComponent<VRCAvatarDescriptor>();
|
||||||
new ReplaceObjectPass(new BuildContext(avDesc)).Process();
|
var buildContext = new nadena.dev.ndmf.BuildContext(avDesc, null);
|
||||||
|
pathMappings = buildContext.ActivateExtensionContext<TrackObjectRenamesContext>();
|
||||||
|
new ReplaceObjectPass(buildContext).Process();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -159,10 +164,9 @@ namespace modular_avatar_tests.ReplaceObject
|
|||||||
var replaceObject = replacement.AddComponent<ModularAvatarReplaceObject>();
|
var replaceObject = replacement.AddComponent<ModularAvatarReplaceObject>();
|
||||||
replaceObject.targetObject.Set(replacee);
|
replaceObject.targetObject.Set(replacee);
|
||||||
|
|
||||||
PathMappings.Init(root);
|
|
||||||
Process(root);
|
Process(root);
|
||||||
|
|
||||||
Assert.AreEqual("replacement", PathMappings.MapPath("replacee"));
|
Assert.AreEqual("replacement", pathMappings.MapPath("replacee"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using nadena.dev.modular_avatar.core.editor;
|
using nadena.dev.ndmf.animation;
|
||||||
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
using VRC.SDK3.Avatars.Components;
|
||||||
@ -20,13 +21,19 @@ namespace modular_avatar_tests
|
|||||||
skinnedMeshRenderer.rootBone = b.transform;
|
skinnedMeshRenderer.rootBone = b.transform;
|
||||||
Debug.Assert(skinnedMeshRenderer.bones.Length == 0);
|
Debug.Assert(skinnedMeshRenderer.bones.Length == 0);
|
||||||
|
|
||||||
BoneDatabase.AddMergedBone(b.transform);
|
var build_context =
|
||||||
var context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||||
new RetargetMeshes().OnPreprocessAvatar(root, context);
|
var torc = new TrackObjectRenamesContext();
|
||||||
|
torc.OnActivate(build_context);
|
||||||
|
|
||||||
|
var bonedb = new BoneDatabase();
|
||||||
|
bonedb.AddMergedBone(b.transform);
|
||||||
|
|
||||||
|
new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc);
|
||||||
|
|
||||||
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
|
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void NoMeshRootBoneOnly()
|
public void NoMeshRootBoneOnly()
|
||||||
{
|
{
|
||||||
@ -41,13 +48,19 @@ namespace modular_avatar_tests
|
|||||||
skinnedMeshRenderer.rootBone = b.transform;
|
skinnedMeshRenderer.rootBone = b.transform;
|
||||||
Debug.Assert(skinnedMeshRenderer.bones.Length == 0);
|
Debug.Assert(skinnedMeshRenderer.bones.Length == 0);
|
||||||
|
|
||||||
BoneDatabase.AddMergedBone(b.transform);
|
var build_context =
|
||||||
var context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
new nadena.dev.ndmf.BuildContext(root.GetComponent<VRCAvatarDescriptor>(), null);
|
||||||
new RetargetMeshes().OnPreprocessAvatar(root, context);
|
var torc = new TrackObjectRenamesContext();
|
||||||
|
torc.OnActivate(build_context);
|
||||||
|
|
||||||
|
var bonedb = new BoneDatabase();
|
||||||
|
bonedb.AddMergedBone(b.transform);
|
||||||
|
|
||||||
|
new RetargetMeshes().OnPreprocessAvatar(root, bonedb, torc);
|
||||||
|
|
||||||
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
|
Assert.AreEqual(a.transform, skinnedMeshRenderer.rootBone);
|
||||||
Assert.AreEqual(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)),
|
Assert.AreEqual(new Bounds(new Vector3(0, 0, 0), new Vector3(2, 2, 2)),
|
||||||
skinnedMeshRenderer.localBounds);
|
skinnedMeshRenderer.localBounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,56 +0,0 @@
|
|||||||
using nadena.dev.modular_avatar.core.editor;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
using VRC.SDK3.Avatars.Components;
|
|
||||||
|
|
||||||
namespace modular_avatar_tests.serialization
|
|
||||||
{
|
|
||||||
class TestComponent : MonoBehaviour
|
|
||||||
{
|
|
||||||
public UnityEngine.Object ref1, ref2;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestScriptable : ScriptableObject
|
|
||||||
{
|
|
||||||
public UnityEngine.Object ref1, ref2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SerializationSweepTest : TestBase
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void testSerialization()
|
|
||||||
{
|
|
||||||
var root = CreateRoot("root");
|
|
||||||
var child = CreateChild(root, "child");
|
|
||||||
var testComponent = child.AddComponent<TestComponent>();
|
|
||||||
var testScriptable1 = ScriptableObject.CreateInstance<TestScriptable>();
|
|
||||||
var testScriptable2 = ScriptableObject.CreateInstance<TestScriptable>();
|
|
||||||
var testScriptable3 = ScriptableObject.CreateInstance<TestScriptable>();
|
|
||||||
var testScriptable4 = ScriptableObject.CreateInstance<TestScriptable>();
|
|
||||||
|
|
||||||
testComponent.ref1 = testScriptable1;
|
|
||||||
testComponent.ref2 = root;
|
|
||||||
|
|
||||||
testScriptable1.ref1 = testScriptable2;
|
|
||||||
testScriptable2.ref1 = testScriptable3;
|
|
||||||
|
|
||||||
testScriptable1.ref2 = testScriptable4;
|
|
||||||
testScriptable2.ref2 = testScriptable4;
|
|
||||||
testScriptable3.ref2 = testScriptable4;
|
|
||||||
|
|
||||||
BuildContext bc = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
|
||||||
bc.CommitReferencedAssets();
|
|
||||||
|
|
||||||
var path = AssetDatabase.GetAssetPath(testScriptable1);
|
|
||||||
Assert.IsFalse(string.IsNullOrEmpty(path));
|
|
||||||
Assert.AreEqual(path, AssetDatabase.GetAssetPath(testScriptable2));
|
|
||||||
Assert.AreEqual(path, AssetDatabase.GetAssetPath(testScriptable3));
|
|
||||||
Assert.AreEqual(path, AssetDatabase.GetAssetPath(testScriptable4));
|
|
||||||
|
|
||||||
Assert.IsTrue(string.IsNullOrEmpty(AssetDatabase.GetAssetPath(testComponent)));
|
|
||||||
Assert.IsTrue(string.IsNullOrEmpty(AssetDatabase.GetAssetPath(root)));
|
|
||||||
Assert.IsTrue(string.IsNullOrEmpty(AssetDatabase.GetAssetPath(child)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a8035784f3364865a84cc938682be7a0
|
|
||||||
timeCreated: 1690804771
|
|
@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using modular_avatar_tests;
|
using modular_avatar_tests;
|
||||||
|
using nadena.dev.ndmf.runtime;
|
||||||
using nadena.dev.modular_avatar.core;
|
using nadena.dev.modular_avatar.core;
|
||||||
using nadena.dev.modular_avatar.core.editor;
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -60,7 +61,7 @@ namespace _ModularAvatar.EditModeTests.SerializationTests
|
|||||||
Assert.False(string.IsNullOrEmpty(path));
|
Assert.False(string.IsNullOrEmpty(path));
|
||||||
|
|
||||||
var mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
|
var mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
|
||||||
Assert.IsInstanceOf<MAAssetBundle>(mainAsset);
|
Assert.IsInstanceOf<GeneratedAssets>(mainAsset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,9 @@
|
|||||||
"GUID:5718fb738711cd34ea54e9553040911d",
|
"GUID:5718fb738711cd34ea54e9553040911d",
|
||||||
"GUID:b906909fcc54f634db50f2cad0f988d9",
|
"GUID:b906909fcc54f634db50f2cad0f988d9",
|
||||||
"GUID:3456780c4fb2d324ab9c633d6f1b0ddb",
|
"GUID:3456780c4fb2d324ab9c633d6f1b0ddb",
|
||||||
"GUID:e9745f6a32442194c8dc5a43e9ab86f9"
|
"GUID:e9745f6a32442194c8dc5a43e9ab86f9",
|
||||||
|
"GUID:62ced99b048af7f4d8dfe4bed8373d76",
|
||||||
|
"GUID:fe747755f7b44e048820525b07f9b956"
|
||||||
],
|
],
|
||||||
"includePlatforms": [
|
"includePlatforms": [
|
||||||
"Editor"
|
"Editor"
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022 bd_
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
|
||||||
{
|
|
||||||
[InitializeOnLoad]
|
|
||||||
internal static class ApplyOnPlay
|
|
||||||
{
|
|
||||||
private const string MENU_NAME = "Tools/Modular Avatar/Apply on Play";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We need to process avatars before lyuma's av3 emulator wakes up and processes avatars; it does this in Awake,
|
|
||||||
* so we have to do our processing in Awake as well. This seems to work fine when first entering play mode, but
|
|
||||||
* if you subsequently enable an initially-disabled avatar, processing from within Awake causes an editor crash.
|
|
||||||
*
|
|
||||||
* To workaround this, we initially process in awake; then, after OnPlayModeStateChanged is invoked (ie, after
|
|
||||||
* all initially-enabled components have Awake called), we switch to processing from Start instead.
|
|
||||||
*/
|
|
||||||
private static RuntimeUtil.OnDemandSource armedSource = RuntimeUtil.OnDemandSource.Awake;
|
|
||||||
|
|
||||||
static ApplyOnPlay()
|
|
||||||
{
|
|
||||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
|
||||||
RuntimeUtil.OnDemandProcessAvatar = MaybeProcessAvatar;
|
|
||||||
EditorApplication.delayCall += () => Menu.SetChecked(MENU_NAME, ModularAvatarSettings.applyOnPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void MaybeProcessAvatar(RuntimeUtil.OnDemandSource source, MonoBehaviour component)
|
|
||||||
{
|
|
||||||
if (ModularAvatarSettings.applyOnPlay && source == armedSource && component != null)
|
|
||||||
{
|
|
||||||
var avatar = RuntimeUtil.FindAvatarInParents(component.transform);
|
|
||||||
if (avatar == null) return;
|
|
||||||
AvatarProcessor.ProcessAvatar(avatar.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MenuItem(MENU_NAME)]
|
|
||||||
private static void ToggleApplyOnPlay()
|
|
||||||
{
|
|
||||||
ModularAvatarSettings.applyOnPlay = !ModularAvatarSettings.applyOnPlay;
|
|
||||||
Menu.SetChecked(MENU_NAME, ModularAvatarSettings.applyOnPlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnPlayModeStateChanged(PlayModeStateChange obj)
|
|
||||||
{
|
|
||||||
if (obj == PlayModeStateChange.EnteredPlayMode)
|
|
||||||
{
|
|
||||||
armedSource = RuntimeUtil.OnDemandSource.Start;
|
|
||||||
}
|
|
||||||
else if (obj == PlayModeStateChange.EnteredEditMode)
|
|
||||||
{
|
|
||||||
armedSource = RuntimeUtil.OnDemandSource.Awake;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 246c8f6cb27c4758972ceac5e8700add
|
|
||||||
timeCreated: 1661822272
|
|
@ -22,51 +22,16 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using VRC.SDK3.Avatars.Components;
|
|
||||||
using VRC.SDKBase.Editor.BuildPipeline;
|
|
||||||
using BuildReport = nadena.dev.modular_avatar.editor.ErrorReporting.BuildReport;
|
|
||||||
using Debug = UnityEngine.Debug;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Tests")]
|
[assembly: InternalsVisibleTo("Tests")]
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
{
|
{
|
||||||
[InitializeOnLoad]
|
public class AvatarProcessor
|
||||||
public class AvatarProcessor : IVRCSDKPreprocessAvatarCallback, IVRCSDKPostprocessAvatarCallback
|
|
||||||
{
|
{
|
||||||
// Place after EditorOnly processing (which runs at -1024) but hopefully before most other user callbacks
|
|
||||||
public int callbackOrder => -25;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Avoid recursive activation of avatar processing by suppressing starting processing while processing is
|
|
||||||
/// already in progress.
|
|
||||||
/// </summary>
|
|
||||||
private static bool nowProcessing = false;
|
|
||||||
|
|
||||||
internal delegate void AvatarProcessorCallback(GameObject obj, BuildContext context);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API is NOT stable. Do not use it yet.
|
|
||||||
/// </summary>
|
|
||||||
internal static event AvatarProcessorCallback AfterProcessing;
|
|
||||||
|
|
||||||
static AvatarProcessor()
|
|
||||||
{
|
|
||||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MenuItem("GameObject/ModularAvatar/Manual bake avatar", true, 100)]
|
[MenuItem("GameObject/ModularAvatar/Manual bake avatar", true, 100)]
|
||||||
static bool ValidateApplyToCurrentAvatarGameobject()
|
static bool ValidateApplyToCurrentAvatarGameobject()
|
||||||
{
|
{
|
||||||
@ -82,311 +47,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
[MenuItem("Tools/Modular Avatar/Manual bake avatar", true)]
|
[MenuItem("Tools/Modular Avatar/Manual bake avatar", true)]
|
||||||
private static bool ValidateApplyToCurrentAvatar()
|
private static bool ValidateApplyToCurrentAvatar()
|
||||||
{
|
{
|
||||||
var avatar = Selection.activeGameObject;
|
return ndmf.AvatarProcessor.CanProcessObject(Selection.activeGameObject);
|
||||||
return (avatar != null && avatar.GetComponent<VRCAvatarDescriptor>() != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MenuItem("Tools/Modular Avatar/Manual bake avatar", false)]
|
[MenuItem("Tools/Modular Avatar/Manual bake avatar", false)]
|
||||||
private static void ApplyToCurrentAvatar()
|
private static void ApplyToCurrentAvatar()
|
||||||
{
|
{
|
||||||
var avatar = Selection.activeGameObject;
|
ndmf.AvatarProcessor.ProcessAvatarUI(Selection.activeGameObject);
|
||||||
if (avatar == null || avatar.GetComponent<VRCAvatarDescriptor>() == null) return;
|
|
||||||
var basePath = "Assets/ModularAvatarOutput/" + avatar.name;
|
|
||||||
var savePath = basePath;
|
|
||||||
|
|
||||||
int extension = 0;
|
|
||||||
|
|
||||||
while (File.Exists(savePath) || Directory.Exists(savePath))
|
|
||||||
{
|
|
||||||
savePath = basePath + " " + (++extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
string originalBasePath = RuntimeUtil.RelativePath(null, avatar);
|
|
||||||
avatar = Object.Instantiate(avatar);
|
|
||||||
|
|
||||||
string clonedBasePath = RuntimeUtil.RelativePath(null, avatar);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Util.OverridePath = savePath;
|
|
||||||
|
|
||||||
var original = avatar;
|
|
||||||
avatar.transform.position += Vector3.forward * 2;
|
|
||||||
|
|
||||||
BuildReport.Clear();
|
|
||||||
|
|
||||||
ProcessAvatar(avatar);
|
|
||||||
Selection.objects = new Object[] {avatar};
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Util.OverridePath = null;
|
|
||||||
BuildReport.RemapPaths(originalBasePath, clonedBasePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnPlayModeStateChanged(PlayModeStateChange obj)
|
|
||||||
{
|
|
||||||
if (obj == PlayModeStateChange.EnteredEditMode)
|
|
||||||
{
|
|
||||||
Util.DeleteTemporaryAssets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPostprocessAvatar()
|
|
||||||
{
|
|
||||||
Util.DeleteTemporaryAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
BuildReport.Clear();
|
|
||||||
ProcessAvatar(avatarGameObject);
|
|
||||||
FixupAnimatorDebugData(avatarGameObject);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogError(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ProcessAvatar(GameObject avatarGameObject)
|
public static void ProcessAvatar(GameObject avatarGameObject)
|
||||||
{
|
{
|
||||||
if (nowProcessing) return;
|
ndmf.AvatarProcessor.ProcessAvatar(avatarGameObject);
|
||||||
|
|
||||||
var vrcAvatarDescriptor = avatarGameObject.GetComponent<VRCAvatarDescriptor>();
|
|
||||||
|
|
||||||
Stopwatch sw = new Stopwatch();
|
|
||||||
sw.Start();
|
|
||||||
|
|
||||||
using (BuildReport.CurrentReport.ReportingOnAvatar(vrcAvatarDescriptor))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AssetDatabase.StartAssetEditing();
|
|
||||||
nowProcessing = true;
|
|
||||||
|
|
||||||
RemoveMissingScriptComponents(avatarGameObject);
|
|
||||||
|
|
||||||
ClearEditorOnlyTagComponents(avatarGameObject.transform);
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
Object.DestroyImmediate(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable deprecation warning for reference to PipelineSaver
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
foreach (var component in directChild.GetComponentsInChildren<PipelineSaver>(true))
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
{
|
|
||||||
Object.DestroyImmediate(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = new BuildContext(vrcAvatarDescriptor);
|
|
||||||
|
|
||||||
new MeshSettingsPass(context).OnPreprocessAvatar();
|
|
||||||
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 ReplaceObjectPass(context).Process();
|
|
||||||
new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase);
|
|
||||||
new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context);
|
|
||||||
PhysboneBlockerPass.Process(avatarGameObject);
|
|
||||||
|
|
||||||
context.CommitReferencedAssets();
|
|
||||||
|
|
||||||
AfterProcessing?.Invoke(avatarGameObject, context);
|
|
||||||
|
|
||||||
context.AnimationDatabase.Commit();
|
|
||||||
|
|
||||||
new GCGameObjectsPass(context, avatarGameObject).OnPreprocessAvatar();
|
|
||||||
|
|
||||||
context.CommitReferencedAssets();
|
|
||||||
}
|
|
||||||
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
|
|
||||||
var toDestroy = avatarGameObject.GetComponentsInChildren<AvatarTagComponent>(true).ToList();
|
|
||||||
var retryDestroy = new List<AvatarTagComponent>();
|
|
||||||
|
|
||||||
// Sometimes AvatarTagComponents have interdependencies and need to be deleted in the right order;
|
|
||||||
// retry until we purge them all.
|
|
||||||
bool madeProgress = true;
|
|
||||||
while (toDestroy.Count > 0)
|
|
||||||
{
|
|
||||||
if (!madeProgress)
|
|
||||||
{
|
|
||||||
throw new Exception("One or more components failed to destroy." +
|
|
||||||
RuntimeUtil.AvatarRootPath(toDestroy[0].gameObject));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var component in toDestroy)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (component != null)
|
|
||||||
{
|
|
||||||
UnityEngine.Object.DestroyImmediate(component);
|
|
||||||
madeProgress = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
retryDestroy.Add(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toDestroy = retryDestroy;
|
|
||||||
retryDestroy = new List<AvatarTagComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var activator = avatarGameObject.GetComponent<AvatarActivator>();
|
|
||||||
if (activator != null)
|
|
||||||
{
|
|
||||||
UnityEngine.Object.DestroyImmediate(activator);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClonedMenuMappings.Clear();
|
|
||||||
|
|
||||||
AssetDatabase.SaveAssets();
|
|
||||||
|
|
||||||
Resources.UnloadUnusedAssets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
BuildReport.LogException(e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ErrorReportUI.MaybeOpenErrorReportUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BuildReport.CurrentReport.CurrentAvatar.successful)
|
|
||||||
{
|
|
||||||
throw new Exception("Fatal error reported during avatar processing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Log($"Processed avatar " + avatarGameObject.name + " in " + sw.ElapsedMilliseconds + "ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RemoveMissingScriptComponents(GameObject avatarGameObject)
|
|
||||||
{
|
|
||||||
foreach (var child in avatarGameObject.GetComponentsInChildren<Transform>(true))
|
|
||||||
GameObjectUtility.RemoveMonoBehavioursWithMissingScript(child.gameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ClearEditorOnlyTagComponents(Transform obj)
|
|
||||||
{
|
|
||||||
// EditorOnly objects can be used for multiple purposes - users might want a camera rig to be available in
|
|
||||||
// play mode, for example. For now, we'll prune MA components from EditorOnly objects, but otherwise leave
|
|
||||||
// them in place when in play mode.
|
|
||||||
|
|
||||||
if (obj.CompareTag("EditorOnly"))
|
|
||||||
{
|
|
||||||
foreach (var component in obj.GetComponentsInChildren<AvatarTagComponent>(true))
|
|
||||||
{
|
|
||||||
UnityEngine.Object.DestroyImmediate(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (Transform transform in obj)
|
|
||||||
{
|
|
||||||
ClearEditorOnlyTagComponents(transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
|
|
||||||
private static void FixupAnimatorDebugData(GameObject avatarGameObject)
|
|
||||||
{
|
|
||||||
Object tempControlPanel = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// The VRCSDK captures some debug information about animators as part of the build process, prior to invoking
|
|
||||||
// hooks. For some reason this happens in the ValidateFeatures call on the SDK builder. Reinvoke it to
|
|
||||||
// refresh this debug info.
|
|
||||||
//
|
|
||||||
// All of these methods are public, but for compatibility with unitypackage-based SDKs, we need to use
|
|
||||||
// reflection to invoke everything here, as the asmdef structure is different between the two SDK variants.
|
|
||||||
// Bleh.
|
|
||||||
//
|
|
||||||
// Canny filed requesting that this processing move after build hooks:
|
|
||||||
// https://feedback.vrchat.com/sdk-bug-reports/p/animator-debug-information-needs-to-be-captured-after-invoking-preprocess-avatar
|
|
||||||
var ty_VRCSdkControlPanelAvatarBuilder3A = Util.FindType(
|
|
||||||
"VRC.SDK3.Editor.VRCSdkControlPanelAvatarBuilder3A"
|
|
||||||
);
|
|
||||||
var ty_AvatarPerformanceStats = Util.FindType(
|
|
||||||
"VRC.SDKBase.Validation.Performance.Stats.AvatarPerformanceStats"
|
|
||||||
);
|
|
||||||
var ty_VRCSdkControlPanel = Util.FindType("VRCSdkControlPanel");
|
|
||||||
|
|
||||||
if (ty_VRCSdkControlPanelAvatarBuilder3A == null || ty_AvatarPerformanceStats == null ||
|
|
||||||
ty_VRCSdkControlPanel == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var avatar = avatarGameObject.GetComponent<VRCAvatarDescriptor>();
|
|
||||||
var animator = avatarGameObject.GetComponent<Animator>();
|
|
||||||
var builder = ty_VRCSdkControlPanelAvatarBuilder3A.GetConstructor(Type.EmptyTypes)
|
|
||||||
?.Invoke(Array.Empty<object>());
|
|
||||||
var perfStats = ty_AvatarPerformanceStats.GetConstructor(new[] {typeof(bool)})
|
|
||||||
?.Invoke(new object[] {false});
|
|
||||||
|
|
||||||
if (builder == null || perfStats == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tempControlPanel = ScriptableObject.CreateInstance(ty_VRCSdkControlPanel) as Object;
|
|
||||||
|
|
||||||
ty_VRCSdkControlPanelAvatarBuilder3A
|
|
||||||
.GetMethod("RegisterBuilder", BindingFlags.Public | BindingFlags.Instance)
|
|
||||||
.Invoke(builder, new object[] {tempControlPanel});
|
|
||||||
ty_VRCSdkControlPanelAvatarBuilder3A.GetMethod("ValidateFeatures").Invoke(
|
|
||||||
builder, new object[] {avatar, animator, perfStats}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogWarning(
|
|
||||||
"[ModularAvatar] Incompatible VRCSDK version; failed to regenerate animator debug data");
|
|
||||||
Debug.LogException(e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (tempControlPanel != null) Object.DestroyImmediate(tempControlPanel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -12,9 +11,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
internal class BuildContext
|
internal class BuildContext
|
||||||
{
|
{
|
||||||
internal readonly VRCAvatarDescriptor AvatarDescriptor;
|
internal readonly nadena.dev.ndmf.BuildContext PluginBuildContext;
|
||||||
|
|
||||||
|
internal VRCAvatarDescriptor AvatarDescriptor => PluginBuildContext.AvatarDescriptor;
|
||||||
internal readonly AnimationDatabase AnimationDatabase = new AnimationDatabase();
|
internal readonly AnimationDatabase AnimationDatabase = new AnimationDatabase();
|
||||||
internal readonly UnityEngine.Object AssetContainer;
|
internal UnityEngine.Object AssetContainer => PluginBuildContext.AssetContainer;
|
||||||
|
|
||||||
private bool SaveImmediate = false;
|
private bool SaveImmediate = false;
|
||||||
|
|
||||||
@ -29,16 +30,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
internal readonly Dictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>> PostProcessControls
|
internal readonly Dictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>> PostProcessControls
|
||||||
= new Dictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>>();
|
= new Dictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>>();
|
||||||
|
|
||||||
public BuildContext(VRCAvatarDescriptor avatarDescriptor)
|
public BuildContext(nadena.dev.ndmf.BuildContext PluginBuildContext)
|
||||||
{
|
{
|
||||||
AvatarDescriptor = avatarDescriptor;
|
this.PluginBuildContext = PluginBuildContext;
|
||||||
|
}
|
||||||
|
|
||||||
// AssetDatabase.CreateAsset is super slow - so only do it once, and add everything else as sub-assets.
|
public BuildContext(VRCAvatarDescriptor avatarDescriptor)
|
||||||
// This scriptable object exists for the sole purpose of providing a placeholder to dump everything we
|
: this(new ndmf.BuildContext(avatarDescriptor, null))
|
||||||
// generate into. Note that we use a custom component here to force binary serialization; this saves both
|
{
|
||||||
// time as well as disk space (if you're using manual bake).
|
|
||||||
AssetContainer = ScriptableObject.CreateInstance<MAAssetBundle>();
|
|
||||||
AssetDatabase.CreateAsset(AssetContainer, Util.GenerateAssetPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveAsset(Object obj)
|
public void SaveAsset(Object obj)
|
||||||
@ -124,95 +123,5 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
return newMenu;
|
return newMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CommitReferencedAssets()
|
|
||||||
{
|
|
||||||
HashSet<UnityEngine.Object> referencedAssets = new HashSet<UnityEngine.Object>();
|
|
||||||
HashSet<UnityEngine.Object> sceneAssets = new HashSet<UnityEngine.Object>();
|
|
||||||
|
|
||||||
Walk(AvatarDescriptor.gameObject);
|
|
||||||
|
|
||||||
referencedAssets.RemoveWhere(sceneAssets.Contains);
|
|
||||||
referencedAssets.RemoveWhere(a => a is GameObject || a is Component);
|
|
||||||
referencedAssets.RemoveWhere(o => !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(o)));
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
foreach (var asset in referencedAssets)
|
|
||||||
{
|
|
||||||
if (asset.name == "")
|
|
||||||
{
|
|
||||||
asset.name = "Asset " + index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetDatabase.AddObjectToAsset(asset, AssetContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveImmediate = true;
|
|
||||||
|
|
||||||
void Walk(GameObject root)
|
|
||||||
{
|
|
||||||
var components = AvatarDescriptor.gameObject.GetComponentsInChildren<Component>(true);
|
|
||||||
Queue<UnityEngine.Object> visitQueue = new Queue<UnityEngine.Object>(
|
|
||||||
components.Where(t => (!(t is Transform)))
|
|
||||||
);
|
|
||||||
|
|
||||||
while (visitQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var current = visitQueue.Dequeue();
|
|
||||||
if (referencedAssets.Contains(current)) continue;
|
|
||||||
referencedAssets.Add(current);
|
|
||||||
|
|
||||||
// These assets have large internal arrays we don't want to walk through...
|
|
||||||
if (current is Mesh || current is AnimationClip || current is Texture) continue;
|
|
||||||
|
|
||||||
var so = new SerializedObject(current);
|
|
||||||
var sp = so.GetIterator();
|
|
||||||
bool enterChildren = true;
|
|
||||||
|
|
||||||
while (sp.Next(enterChildren))
|
|
||||||
{
|
|
||||||
enterChildren = true;
|
|
||||||
if (sp.name == "m_GameObject") continue;
|
|
||||||
if (sp.propertyType == SerializedPropertyType.String)
|
|
||||||
{
|
|
||||||
enterChildren = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sp.isArray && IsPrimitiveArray(sp))
|
|
||||||
{
|
|
||||||
enterChildren = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sp.propertyType != SerializedPropertyType.ObjectReference)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = sp.objectReferenceValue;
|
|
||||||
if (obj != null && !referencedAssets.Contains(obj) && !(obj is Transform) &&
|
|
||||||
!(obj is GameObject))
|
|
||||||
{
|
|
||||||
visitQueue.Enqueue(sp.objectReferenceValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsPrimitiveArray(SerializedProperty prop)
|
|
||||||
{
|
|
||||||
if (prop.arraySize == 0) return false;
|
|
||||||
var propertyType = prop.GetArrayElementAtIndex(0).propertyType;
|
|
||||||
switch (propertyType)
|
|
||||||
{
|
|
||||||
case SerializedPropertyType.Generic:
|
|
||||||
case SerializedPropertyType.ObjectReference:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,51 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
|
||||||
{
|
|
||||||
internal static class ClonedMenuMappings
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Map to link the cloned menu from the clone source.
|
|
||||||
/// If one menu is specified for multiple installers, they are replicated separately, so there is a one-to-many relationship.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Dictionary<VRCExpressionsMenu, ImmutableList<VRCExpressionsMenu>> ClonedMappings =
|
|
||||||
new Dictionary<VRCExpressionsMenu, ImmutableList<VRCExpressionsMenu>>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Map to link the clone source from the cloned menu.
|
|
||||||
/// Map is the opposite of ClonedMappings.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> OriginalMapping =
|
|
||||||
new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
|
|
||||||
|
|
||||||
public static void Clear()
|
|
||||||
{
|
|
||||||
ClonedMappings.Clear();
|
|
||||||
OriginalMapping.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Add(VRCExpressionsMenu original, VRCExpressionsMenu clonedMenu)
|
|
||||||
{
|
|
||||||
if (!ClonedMappings.TryGetValue(original, out ImmutableList<VRCExpressionsMenu> clonedMenus))
|
|
||||||
{
|
|
||||||
clonedMenus = ImmutableList<VRCExpressionsMenu>.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClonedMappings[original] = clonedMenus.Add(clonedMenu);
|
|
||||||
OriginalMapping[clonedMenu] = original;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryGetClonedMenus(VRCExpressionsMenu original,
|
|
||||||
out ImmutableList<VRCExpressionsMenu> clonedMenus)
|
|
||||||
{
|
|
||||||
return ClonedMappings.TryGetValue(original, out clonedMenus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VRCExpressionsMenu GetOriginal(VRCExpressionsMenu cloned)
|
|
||||||
{
|
|
||||||
return OriginalMapping.TryGetValue(cloned, out VRCExpressionsMenu original) ? original : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: aeaeff9c3af44683bb2f8f5fe6c5791d
|
|
||||||
timeCreated: 1671016064
|
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -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.ndmf.animation;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -38,13 +39,19 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
internal class MergeArmatureHook
|
internal class MergeArmatureHook
|
||||||
{
|
{
|
||||||
|
private ndmf.BuildContext frameworkContext;
|
||||||
private BuildContext context;
|
private BuildContext context;
|
||||||
|
private BoneDatabase BoneDatabase = new BoneDatabase();
|
||||||
|
|
||||||
|
private TrackObjectRenamesContext PathMappings => frameworkContext.Extension<TrackObjectRenamesContext>();
|
||||||
|
|
||||||
private HashSet<Transform> mergedObjects = new HashSet<Transform>();
|
private HashSet<Transform> mergedObjects = new HashSet<Transform>();
|
||||||
private HashSet<Transform> thisPassAdded = new HashSet<Transform>();
|
private HashSet<Transform> thisPassAdded = new HashSet<Transform>();
|
||||||
|
|
||||||
internal void OnPreprocessAvatar(BuildContext context, GameObject avatarGameObject)
|
internal void OnPreprocessAvatar(ndmf.BuildContext context, GameObject avatarGameObject)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.frameworkContext = context;
|
||||||
|
this.context = context.Extension<ModularAvatarContext>().BuildContext;
|
||||||
|
|
||||||
var mergeArmatures =
|
var mergeArmatures =
|
||||||
avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
||||||
@ -74,7 +81,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
RetainBoneReferences(c as Component);
|
RetainBoneReferences(c as Component);
|
||||||
}
|
}
|
||||||
|
|
||||||
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, context);
|
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, BoneDatabase, PathMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures)
|
private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures)
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -25,37 +25,38 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using nadena.dev.ndmf.animation;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
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
|
||||||
{
|
{
|
||||||
internal static class BoneDatabase
|
internal class BoneDatabase
|
||||||
{
|
{
|
||||||
private static Dictionary<Transform, bool> m_IsRetargetable = new Dictionary<Transform, bool>();
|
private Dictionary<Transform, bool> m_IsRetargetable = new Dictionary<Transform, bool>();
|
||||||
|
|
||||||
internal static void ResetBones()
|
internal void ResetBones()
|
||||||
{
|
{
|
||||||
m_IsRetargetable.Clear();
|
m_IsRetargetable.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsRetargetable(Transform t)
|
internal bool IsRetargetable(Transform t)
|
||||||
{
|
{
|
||||||
return m_IsRetargetable.TryGetValue(t, out var result) && result;
|
return m_IsRetargetable.TryGetValue(t, out var result) && result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void AddMergedBone(Transform bone)
|
internal void AddMergedBone(Transform bone)
|
||||||
{
|
{
|
||||||
m_IsRetargetable[bone] = true;
|
m_IsRetargetable[bone] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RetainMergedBone(Transform bone)
|
internal void RetainMergedBone(Transform bone)
|
||||||
{
|
{
|
||||||
if (bone == null) return;
|
if (bone == null) return;
|
||||||
if (m_IsRetargetable.ContainsKey(bone)) m_IsRetargetable[bone] = false;
|
if (m_IsRetargetable.ContainsKey(bone)) m_IsRetargetable[bone] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Transform GetRetargetedBone(Transform bone)
|
internal Transform GetRetargetedBone(Transform bone)
|
||||||
{
|
{
|
||||||
if (bone == null || !m_IsRetargetable.ContainsKey(bone)) return null;
|
if (bone == null || !m_IsRetargetable.ContainsKey(bone)) return null;
|
||||||
|
|
||||||
@ -65,14 +66,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
return bone;
|
return bone;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IEnumerable<KeyValuePair<Transform, Transform>> GetRetargetedBones()
|
internal IEnumerable<KeyValuePair<Transform, Transform>> GetRetargetedBones()
|
||||||
{
|
{
|
||||||
return m_IsRetargetable.Where((kvp) => kvp.Value)
|
return m_IsRetargetable.Where((kvp) => kvp.Value)
|
||||||
.Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
|
.Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
|
||||||
.Where(kvp => kvp.Value != null);
|
.Where(kvp => kvp.Value != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Transform GetRetargetedBone(Transform bone, bool fallbackToOriginal)
|
public Transform GetRetargetedBone(Transform bone, bool fallbackToOriginal)
|
||||||
{
|
{
|
||||||
Transform retargeted = GetRetargetedBone(bone);
|
Transform retargeted = GetRetargetedBone(bone);
|
||||||
|
|
||||||
@ -82,11 +83,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
internal class RetargetMeshes
|
internal class RetargetMeshes
|
||||||
{
|
{
|
||||||
private BuildContext _context;
|
private BoneDatabase _boneDatabase;
|
||||||
|
private TrackObjectRenamesContext _pathTracker;
|
||||||
|
|
||||||
internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context)
|
internal void OnPreprocessAvatar(GameObject avatarGameObject, BoneDatabase boneDatabase,
|
||||||
|
TrackObjectRenamesContext pathMappings)
|
||||||
{
|
{
|
||||||
_context = context;
|
this._boneDatabase = boneDatabase;
|
||||||
|
this._pathTracker = pathMappings;
|
||||||
|
|
||||||
foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||||
{
|
{
|
||||||
@ -95,19 +99,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
bool isRetargetable = false;
|
bool isRetargetable = false;
|
||||||
foreach (var bone in renderer.bones)
|
foreach (var bone in renderer.bones)
|
||||||
{
|
{
|
||||||
if (BoneDatabase.GetRetargetedBone(bone) != null)
|
if (_boneDatabase.GetRetargetedBone(bone) != null)
|
||||||
{
|
{
|
||||||
isRetargetable = true;
|
isRetargetable = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRetargetable |= BoneDatabase.GetRetargetedBone(renderer.rootBone);
|
isRetargetable |= _boneDatabase.GetRetargetedBone(renderer.rootBone);
|
||||||
|
|
||||||
if (isRetargetable)
|
if (isRetargetable)
|
||||||
{
|
{
|
||||||
var newMesh = new MeshRetargeter(renderer).Retarget();
|
new MeshRetargeter(renderer, _boneDatabase).Retarget();
|
||||||
if (newMesh) _context.SaveAsset(newMesh);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -115,9 +118,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
// Now remove retargeted bones
|
// Now remove retargeted bones
|
||||||
if (true)
|
if (true)
|
||||||
{
|
{
|
||||||
foreach (var bonePair in BoneDatabase.GetRetargetedBones())
|
foreach (var bonePair in _boneDatabase.GetRetargetedBones())
|
||||||
{
|
{
|
||||||
if (BoneDatabase.GetRetargetedBone(bonePair.Key) == null) continue;
|
if (_boneDatabase.GetRetargetedBone(bonePair.Key) == null) continue;
|
||||||
|
|
||||||
var sourceBone = bonePair.Key;
|
var sourceBone = bonePair.Key;
|
||||||
var destBone = bonePair.Value;
|
var destBone = bonePair.Value;
|
||||||
@ -150,7 +153,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
child.SetParent(destBone, true);
|
child.SetParent(destBone, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
PathMappings.MarkRemoved(sourceBone.gameObject);
|
_pathTracker.MarkRemoved(sourceBone.gameObject);
|
||||||
UnityEngine.Object.DestroyImmediate(sourceBone.gameObject);
|
UnityEngine.Object.DestroyImmediate(sourceBone.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,11 +167,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
internal class MeshRetargeter
|
internal class MeshRetargeter
|
||||||
{
|
{
|
||||||
private readonly SkinnedMeshRenderer renderer;
|
private readonly SkinnedMeshRenderer renderer;
|
||||||
|
private readonly BoneDatabase _boneDatabase;
|
||||||
|
|
||||||
[CanBeNull] private Mesh src, dst;
|
[CanBeNull] private Mesh src, dst;
|
||||||
|
|
||||||
public MeshRetargeter(SkinnedMeshRenderer renderer)
|
public MeshRetargeter(SkinnedMeshRenderer renderer, BoneDatabase boneDatabase)
|
||||||
{
|
{
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
|
this._boneDatabase = boneDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
@ -218,7 +224,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
for (int i = 0; i < originalBones.Length; i++)
|
for (int i = 0; i < originalBones.Length; i++)
|
||||||
{
|
{
|
||||||
Transform newBindTarget = BoneDatabase.GetRetargetedBone(originalBones[i]);
|
Transform newBindTarget = _boneDatabase.GetRetargetedBone(originalBones[i]);
|
||||||
if (newBindTarget == null) continue;
|
if (newBindTarget == null) continue;
|
||||||
newBones[i] = newBindTarget;
|
newBones[i] = newBindTarget;
|
||||||
|
|
||||||
@ -247,8 +253,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
renderer.sharedMesh = dst;
|
renderer.sharedMesh = dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRootBone = BoneDatabase.GetRetargetedBone(rootBone, true);
|
var newRootBone = _boneDatabase.GetRetargetedBone(rootBone, true);
|
||||||
var newScaleBone = BoneDatabase.GetRetargetedBone(scaleBone, true);
|
var newScaleBone = _boneDatabase.GetRetargetedBone(scaleBone, true);
|
||||||
|
|
||||||
var oldLossyScale = scaleBone.transform.lossyScale;
|
var oldLossyScale = scaleBone.transform.lossyScale;
|
||||||
var newLossyScale = newScaleBone.transform.lossyScale;
|
var newLossyScale = newScaleBone.transform.lossyScale;
|
||||||
@ -267,7 +273,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
renderer.localBounds = bounds;
|
renderer.localBounds = bounds;
|
||||||
|
|
||||||
renderer.rootBone = newRootBone;
|
renderer.rootBone = newRootBone;
|
||||||
renderer.probeAnchor = BoneDatabase.GetRetargetedBone(renderer.probeAnchor, true);
|
renderer.probeAnchor = _boneDatabase.GetRetargetedBone(renderer.probeAnchor, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2022 bd_
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
|
||||||
{
|
|
||||||
internal static class PathMappings
|
|
||||||
{
|
|
||||||
private static Dictionary<GameObject, List<string>> _objectToOriginalPaths =
|
|
||||||
new Dictionary<GameObject, List<string>>();
|
|
||||||
|
|
||||||
private static ImmutableDictionary<string, string> _originalPathToMappedPath = null;
|
|
||||||
private static ImmutableDictionary<string, string> _transformOriginalPathToMappedPath = null;
|
|
||||||
|
|
||||||
private static HashSet<GameObject> _transformLookthroughObjects = new HashSet<GameObject>();
|
|
||||||
|
|
||||||
internal static void Init(GameObject root)
|
|
||||||
{
|
|
||||||
_objectToOriginalPaths.Clear();
|
|
||||||
_originalPathToMappedPath = null;
|
|
||||||
_transformLookthroughObjects.Clear();
|
|
||||||
|
|
||||||
foreach (var xform in root.GetComponentsInChildren<Transform>(true))
|
|
||||||
{
|
|
||||||
var path = RuntimeUtil.RelativePath(root, xform.gameObject);
|
|
||||||
_objectToOriginalPaths.Add(xform.gameObject, new List<string> {path});
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void ClearCache()
|
|
||||||
{
|
|
||||||
_originalPathToMappedPath = _transformOriginalPathToMappedPath = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a path identifying a given object. This can include objects not originally present; in this case,
|
|
||||||
/// they will be assigned a randomly-generated internal ID which will be replaced during path remapping with
|
|
||||||
/// the true path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">Object to map</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal static string GetObjectIdentifier(GameObject obj)
|
|
||||||
{
|
|
||||||
if (_objectToOriginalPaths.TryGetValue(obj, out var paths))
|
|
||||||
{
|
|
||||||
return paths[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var internalPath = "_ModularAvatarInternal/" + GUID.Generate();
|
|
||||||
_objectToOriginalPaths.Add(obj, new List<string> {internalPath});
|
|
||||||
return internalPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When animating a transform component on a merged bone, we want to make sure we manipulate the original
|
|
||||||
/// avatar's bone, not a stub bone attached underneath. By making an object as transform lookthrough, any
|
|
||||||
/// queries for mapped paths on the transform component will walk up the tree to the next parent.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The object to mark transform lookthrough</param>
|
|
||||||
internal static void MarkTransformLookthrough(GameObject obj)
|
|
||||||
{
|
|
||||||
ClearCache();
|
|
||||||
_transformLookthroughObjects.Add(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks an object as having been removed. Its paths will be remapped to its parent.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
internal static void MarkRemoved(GameObject obj)
|
|
||||||
{
|
|
||||||
ClearCache();
|
|
||||||
if (_objectToOriginalPaths.TryGetValue(obj, out var paths))
|
|
||||||
{
|
|
||||||
var parent = obj.transform.parent.gameObject;
|
|
||||||
if (_objectToOriginalPaths.TryGetValue(parent, out var parentPaths))
|
|
||||||
{
|
|
||||||
parentPaths.AddRange(paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
_objectToOriginalPaths.Remove(obj);
|
|
||||||
_transformLookthroughObjects.Remove(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks an object as having been replaced by another object. All references to the old object will be replaced
|
|
||||||
/// by the new object. References originally to the new object will continue to point to the new object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="old"></param>
|
|
||||||
/// <param name="newObject"></param>
|
|
||||||
public static void ReplaceObject(GameObject old, GameObject newObject)
|
|
||||||
{
|
|
||||||
ClearCache();
|
|
||||||
|
|
||||||
if (_objectToOriginalPaths.TryGetValue(old, out var paths))
|
|
||||||
{
|
|
||||||
if (!_objectToOriginalPaths.TryGetValue(newObject, out var newObjectPaths))
|
|
||||||
{
|
|
||||||
newObjectPaths = new List<string>();
|
|
||||||
_objectToOriginalPaths.Add(newObject, newObjectPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
newObjectPaths.AddRange(paths);
|
|
||||||
|
|
||||||
_objectToOriginalPaths.Remove(old);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_transformLookthroughObjects.Contains(old))
|
|
||||||
{
|
|
||||||
_transformLookthroughObjects.Remove(old);
|
|
||||||
_transformLookthroughObjects.Add(newObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableDictionary<string, string> BuildMapping(ref ImmutableDictionary<string, string> cache,
|
|
||||||
bool transformLookup)
|
|
||||||
{
|
|
||||||
if (cache != null) return cache;
|
|
||||||
|
|
||||||
ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;
|
|
||||||
|
|
||||||
foreach (var kvp in _objectToOriginalPaths)
|
|
||||||
{
|
|
||||||
var obj = kvp.Key;
|
|
||||||
var paths = kvp.Value;
|
|
||||||
|
|
||||||
if (transformLookup)
|
|
||||||
{
|
|
||||||
while (_transformLookthroughObjects.Contains(obj))
|
|
||||||
{
|
|
||||||
obj = obj.transform.parent.gameObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPath = RuntimeUtil.AvatarRootPath(obj);
|
|
||||||
foreach (var origPath in paths)
|
|
||||||
{
|
|
||||||
if (!dict.ContainsKey(origPath))
|
|
||||||
{
|
|
||||||
dict = dict.Add(origPath, newPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache = dict;
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string MapPath(string path, bool isTransformMapping = false)
|
|
||||||
{
|
|
||||||
ImmutableDictionary<string, string> mappings;
|
|
||||||
|
|
||||||
if (isTransformMapping)
|
|
||||||
{
|
|
||||||
mappings = BuildMapping(ref _originalPathToMappedPath, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mappings = BuildMapping(ref _transformOriginalPathToMappedPath, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappings.TryGetValue(path, out var mappedPath))
|
|
||||||
{
|
|
||||||
return mappedPath;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 6a5a2ea7723848d1bfe793debcf298cc
|
|
||||||
timeCreated: 1661649007
|
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 06c2c2893c2741c0895e3b90ee8e33c0
|
||||||
|
timeCreated: 1691213499
|
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using nadena.dev.ndmf;
|
||||||
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
internal class ModularAvatarContext : IExtensionContext
|
||||||
|
{
|
||||||
|
private IDisposable toDispose;
|
||||||
|
internal BuildContext BuildContext { get; private set; }
|
||||||
|
|
||||||
|
public void OnActivate(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
if (BuildContext == null)
|
||||||
|
{
|
||||||
|
BuildContext = new BuildContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
toDispose = BuildReport.CurrentReport.ReportingOnAvatar(context.AvatarDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDeactivate(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
toDispose?.Dispose();
|
||||||
|
toDispose = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2be0ae3b99ac44c0a35522d7fd0c6f10
|
||||||
|
timeCreated: 1692614275
|
@ -0,0 +1,206 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using nadena.dev.ndmf;
|
||||||
|
using nadena.dev.ndmf.animation;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[assembly: ExportsPlugin(
|
||||||
|
typeof(nadena.dev.modular_avatar.core.editor.plugin.PluginDefinition)
|
||||||
|
)]
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core.editor.plugin
|
||||||
|
{
|
||||||
|
class PluginDefinition : Plugin
|
||||||
|
{
|
||||||
|
public override string QualifiedName => "nadena.dev.modular-avatar";
|
||||||
|
|
||||||
|
public override ImmutableList<PluginPass> Passes => (new List<PluginPass>()
|
||||||
|
{
|
||||||
|
new ResolveObjectReferences(),
|
||||||
|
new ClearEditorOnlyTags(),
|
||||||
|
new MeshSettingsPluginPass(),
|
||||||
|
new RenameParametersPluginPass(),
|
||||||
|
new MergeAnimatorPluginPass(),
|
||||||
|
new MenuInstallPluginPass(),
|
||||||
|
new MergeArmaturePluginPass(),
|
||||||
|
new BoneProxyPluginPass(),
|
||||||
|
new VisibleHeadAccessoryPluginPass(),
|
||||||
|
new ReplaceObjectPluginPass(),
|
||||||
|
new BlendshapeSyncAnimationPluginPass(),
|
||||||
|
new PhysbonesBlockerPluginPass(),
|
||||||
|
new GCGameObjectsPluginPass(),
|
||||||
|
}).ToImmutableList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This plugin runs very early in order to resolve all AvatarObjectReferences to their
|
||||||
|
/// referent before any other plugins perform heirarchy manipulations.
|
||||||
|
/// </summary>
|
||||||
|
internal class ResolveObjectReferences : PluginPass
|
||||||
|
{
|
||||||
|
public override BuiltInPhase ExecutionPhase => BuiltInPhase.Resolving;
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
foreach (var obj in context.AvatarRootObject.GetComponentsInChildren<AvatarTagComponent>())
|
||||||
|
{
|
||||||
|
obj.ResolveReferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class MAPass : PluginPass
|
||||||
|
{
|
||||||
|
public override IImmutableSet<Type> RequiredContexts =>
|
||||||
|
ImmutableHashSet<Type>.Empty.Add(typeof(ModularAvatarContext));
|
||||||
|
|
||||||
|
public override IImmutableSet<object> CompatibleContexts =>
|
||||||
|
ImmutableHashSet<object>.Empty.Add(typeof(TrackObjectRenamesContext));
|
||||||
|
|
||||||
|
protected BuildContext MAContext(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
return context.Extension<ModularAvatarContext>().BuildContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClearEditorOnlyTags : MAPass
|
||||||
|
{
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
Traverse(context.AvatarRootTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Traverse(Transform obj)
|
||||||
|
{
|
||||||
|
// EditorOnly objects can be used for multiple purposes - users might want a camera rig to be available in
|
||||||
|
// play mode, for example. For now, we'll prune MA components from EditorOnly objects, but otherwise leave
|
||||||
|
// them in place when in play mode.
|
||||||
|
|
||||||
|
if (obj.CompareTag("EditorOnly"))
|
||||||
|
{
|
||||||
|
foreach (var component in obj.GetComponentsInChildren<AvatarTagComponent>(true))
|
||||||
|
{
|
||||||
|
UnityEngine.Object.DestroyImmediate(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (Transform transform in obj)
|
||||||
|
{
|
||||||
|
Traverse(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeshSettingsPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new MeshSettingsPass(MAContext(context)).OnPreprocessAvatar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenameParametersPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new RenameParametersHook().OnPreprocessAvatar(context.AvatarRootObject, MAContext(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MergeAnimatorPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new MergeAnimatorProcessor().OnPreprocessAvatar(context.AvatarRootObject, MAContext(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuInstallPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new MenuInstallHook().OnPreprocessAvatar(context.AvatarRootObject, MAContext(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MergeArmaturePluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override IImmutableSet<Type> RequiredContexts =>
|
||||||
|
base.RequiredContexts.Add(typeof(TrackObjectRenamesContext));
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
// The animation database is currently only used by the merge armature hook; it should probably become
|
||||||
|
// an extension context instead.
|
||||||
|
MAContext(context).AnimationDatabase.Bootstrap(context.AvatarDescriptor);
|
||||||
|
new MergeArmatureHook().OnPreprocessAvatar(context, context.AvatarRootObject);
|
||||||
|
MAContext(context).AnimationDatabase.Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoneProxyPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override IImmutableSet<Type> RequiredContexts =>
|
||||||
|
base.RequiredContexts.Add(typeof(TrackObjectRenamesContext));
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new BoneProxyProcessor().OnPreprocessAvatar(context.AvatarRootObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VisibleHeadAccessoryPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override IImmutableSet<Type> RequiredContexts =>
|
||||||
|
base.RequiredContexts.Add(typeof(TrackObjectRenamesContext));
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new VisibleHeadAccessoryProcessor(context.AvatarDescriptor).Process(MAContext(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplaceObjectPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override IImmutableSet<Type> RequiredContexts =>
|
||||||
|
base.RequiredContexts.Add(typeof(TrackObjectRenamesContext));
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new ReplaceObjectPass(context).Process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlendshapeSyncAnimationPluginPass : MAPass
|
||||||
|
{
|
||||||
|
// Flush animation path remappings, since we need an up-to-date path name while adjusting blendshape animations
|
||||||
|
public override IImmutableSet<object> CompatibleContexts =>
|
||||||
|
ImmutableHashSet<object>.Empty;
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(context.AvatarRootObject, MAContext(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhysbonesBlockerPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
PhysboneBlockerPass.Process(context.AvatarRootObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GCGameObjectsPluginPass : MAPass
|
||||||
|
{
|
||||||
|
public override BuiltInPhase ExecutionPhase => BuiltInPhase.Optimization;
|
||||||
|
|
||||||
|
public override void Process(ndmf.BuildContext context)
|
||||||
|
{
|
||||||
|
new GCGameObjectsPass(MAContext(context), context.AvatarRootObject).OnPreprocessAvatar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aed34bf4ee4045ff97b749ab1c36d845
|
||||||
|
timeCreated: 1691213504
|
@ -1,77 +0,0 @@
|
|||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
using VRC.SDK3.Avatars.Components;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Remaps all animation path references based on PathMappings data.
|
|
||||||
/// </summary>
|
|
||||||
internal class RemapAnimationPass
|
|
||||||
{
|
|
||||||
private readonly VRCAvatarDescriptor _avatarDescriptor;
|
|
||||||
|
|
||||||
public RemapAnimationPass(VRCAvatarDescriptor avatarDescriptor)
|
|
||||||
{
|
|
||||||
_avatarDescriptor = avatarDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Process(AnimationDatabase animDb)
|
|
||||||
{
|
|
||||||
PathMappings.ClearCache();
|
|
||||||
animDb.ForeachClip(clip =>
|
|
||||||
{
|
|
||||||
BuildReport.ReportingObject(clip.CurrentClip, () =>
|
|
||||||
{
|
|
||||||
if (clip.CurrentClip is AnimationClip anim && !clip.IsProxyAnimation)
|
|
||||||
{
|
|
||||||
clip.CurrentClip = MapMotion(anim);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string MapPath(EditorCurveBinding binding)
|
|
||||||
{
|
|
||||||
if (binding.type == typeof(Animator) && binding.path == "")
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return PathMappings.MapPath(binding.path, binding.type == typeof(Transform));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AnimationClip MapMotion(AnimationClip clip)
|
|
||||||
{
|
|
||||||
AnimationClip newClip = new AnimationClip();
|
|
||||||
newClip.name = "remapped " + clip.name;
|
|
||||||
|
|
||||||
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
|
|
||||||
{
|
|
||||||
var newBinding = binding;
|
|
||||||
newBinding.path = MapPath(binding);
|
|
||||||
newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName,
|
|
||||||
AnimationUtility.GetEditorCurve(clip, binding));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var objBinding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
|
|
||||||
{
|
|
||||||
var newBinding = objBinding;
|
|
||||||
newBinding.path = MapPath(objBinding);
|
|
||||||
AnimationUtility.SetObjectReferenceCurve(newClip, newBinding,
|
|
||||||
AnimationUtility.GetObjectReferenceCurve(clip, objBinding));
|
|
||||||
}
|
|
||||||
|
|
||||||
newClip.wrapMode = clip.wrapMode;
|
|
||||||
newClip.legacy = clip.legacy;
|
|
||||||
newClip.frameRate = clip.frameRate;
|
|
||||||
newClip.localBounds = clip.localBounds;
|
|
||||||
AnimationUtility.SetAnimationClipSettings(newClip, AnimationUtility.GetAnimationClipSettings(clip));
|
|
||||||
|
|
||||||
return newClip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 13e4085d9ace44a680228680e5e1172e
|
|
||||||
timeCreated: 1671618477
|
|
@ -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.ndmf.animation;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -13,9 +14,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
internal class ReplaceObjectPass
|
internal class ReplaceObjectPass
|
||||||
{
|
{
|
||||||
private readonly BuildContext _buildContext;
|
private readonly ndmf.BuildContext _buildContext;
|
||||||
|
|
||||||
public ReplaceObjectPass(BuildContext context)
|
public ReplaceObjectPass(ndmf.BuildContext context)
|
||||||
{
|
{
|
||||||
_buildContext = context;
|
_buildContext = context;
|
||||||
}
|
}
|
||||||
@ -122,7 +123,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PathMappings.ReplaceObject(original, replacement);
|
_buildContext.Extension<TrackObjectRenamesContext>()
|
||||||
|
.ReplaceObject(original, replacement);
|
||||||
|
|
||||||
// Destroy original
|
// Destroy original
|
||||||
UnityObject.DestroyImmediate(original);
|
UnityObject.DestroyImmediate(original);
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -193,8 +193,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
public static bool IsTemporaryAsset(Object obj)
|
public static bool IsTemporaryAsset(Object obj)
|
||||||
{
|
{
|
||||||
var path = AssetDatabase.GetAssetPath(obj);
|
var path = AssetDatabase.GetAssetPath(obj);
|
||||||
|
var generatedAssetsFolder = OverridePath ?? generatedAssetsPath;
|
||||||
|
|
||||||
return string.IsNullOrEmpty(path) || path.StartsWith(GetGeneratedAssetsFolder() + "/");
|
return !EditorUtility.IsPersistent(obj) || string.IsNullOrEmpty(path) ||
|
||||||
|
path.StartsWith(generatedAssetsFolder + "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type FindType(string typeName)
|
public static Type FindType(string typeName)
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
"references": [
|
"references": [
|
||||||
"GUID:fc900867c0f47cd49b6e2ae4ef907300",
|
"GUID:fc900867c0f47cd49b6e2ae4ef907300",
|
||||||
"GUID:5718fb738711cd34ea54e9553040911d",
|
"GUID:5718fb738711cd34ea54e9553040911d",
|
||||||
"GUID:3456780c4fb2d324ab9c633d6f1b0ddb"
|
"GUID:3456780c4fb2d324ab9c633d6f1b0ddb",
|
||||||
|
"GUID:62ced99b048af7f4d8dfe4bed8373d76"
|
||||||
],
|
],
|
||||||
"includePlatforms": [
|
"includePlatforms": [
|
||||||
"Editor"
|
"Editor"
|
||||||
@ -27,6 +28,12 @@
|
|||||||
],
|
],
|
||||||
"autoReferenced": false,
|
"autoReferenced": false,
|
||||||
"defineConstraints": [],
|
"defineConstraints": [],
|
||||||
"versionDefines": [],
|
"versionDefines": [
|
||||||
|
{
|
||||||
|
"name": "nadena.dev.ndmf",
|
||||||
|
"expression": "[0.0.1,999]",
|
||||||
|
"define": "AV3_BUILD_FRAMEWORK_PRESENT"
|
||||||
|
}
|
||||||
|
],
|
||||||
"noEngineReferences": false
|
"noEngineReferences": false
|
||||||
}
|
}
|
@ -1,15 +1,13 @@
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.SceneManagement;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using VRC.SDKBase;
|
||||||
using VRC.SDK3.Avatars.Components;
|
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This component is used to trigger MA processing upon entering play mode (prior to Av3Emu running).
|
/// This component was previously used to trigger avatar processing when entering play mode. This functionality has
|
||||||
|
/// moved to NDMF, so we leave here a stub to clean up detritus left behind from older versions of MA.
|
||||||
/// We create it on a hidden object via AvatarTagObject's OnValidate, and it will proceed to add MAAvatarActivator
|
/// We create it on a hidden object via AvatarTagObject's OnValidate, and it will proceed to add MAAvatarActivator
|
||||||
/// components to all avatar roots which contain MA components. This MAAvatarActivator component then performs MA
|
/// components to all avatar roots which contain MA components. This MAAvatarActivator component then performs MA
|
||||||
/// processing on Awake.
|
/// processing on Awake.
|
||||||
@ -21,108 +19,19 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
[AddComponentMenu("")]
|
[AddComponentMenu("")]
|
||||||
[ExecuteInEditMode]
|
[ExecuteInEditMode]
|
||||||
[DefaultExecutionOrder(-9998)]
|
[DefaultExecutionOrder(-9998)]
|
||||||
public class Activator : MonoBehaviour
|
public class Activator : MonoBehaviour, IEditorOnly
|
||||||
{
|
{
|
||||||
private const string TAG_OBJECT_NAME = "ModularAvatarInternal_Activator";
|
private void Update()
|
||||||
|
|
||||||
private void Awake()
|
|
||||||
{
|
{
|
||||||
if (!RuntimeUtil.isPlaying || this == null) return;
|
UnityEngine.Object.DestroyImmediate(gameObject);
|
||||||
|
|
||||||
var scene = gameObject.scene;
|
|
||||||
foreach (var root in scene.GetRootGameObjects())
|
|
||||||
{
|
|
||||||
foreach (var avatar in root.GetComponentsInChildren<VRCAvatarDescriptor>())
|
|
||||||
{
|
|
||||||
if (avatar.GetComponentInChildren<AvatarTagComponent>(true) != null)
|
|
||||||
{
|
|
||||||
avatar.gameObject.GetOrAddComponent<AvatarActivator>().hideFlags = HideFlags.HideInInspector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasMAComponentsInScene()
|
|
||||||
{
|
|
||||||
var scene = gameObject.scene;
|
|
||||||
foreach (var root in scene.GetRootGameObjects())
|
|
||||||
{
|
|
||||||
if (root.GetComponentInChildren<AvatarTagComponent>(true) != null) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnValidate()
|
|
||||||
{
|
|
||||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
|
||||||
|
|
||||||
EditorApplication.delayCall += () =>
|
|
||||||
{
|
|
||||||
if (this == null) return;
|
|
||||||
|
|
||||||
gameObject.hideFlags = HIDE_FLAGS;
|
|
||||||
if (!HasMAComponentsInScene())
|
|
||||||
{
|
|
||||||
var scene = gameObject.scene;
|
|
||||||
DestroyImmediate(gameObject);
|
|
||||||
EditorSceneManager.MarkSceneDirty(scene);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void CreateIfNotPresent(Scene scene)
|
|
||||||
{
|
|
||||||
if (!scene.IsValid() || EditorSceneManager.IsPreviewScene(scene)) return;
|
|
||||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
|
||||||
|
|
||||||
bool rootPresent = false;
|
|
||||||
foreach (var root in scene.GetRootGameObjects())
|
|
||||||
{
|
|
||||||
if (root.GetComponent<Activator>() != null)
|
|
||||||
{
|
|
||||||
root.hideFlags = HIDE_FLAGS;
|
|
||||||
if (rootPresent) DestroyImmediate(root);
|
|
||||||
rootPresent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rootPresent) return;
|
|
||||||
|
|
||||||
var oldActiveScene = SceneManager.GetActiveScene();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SceneManager.SetActiveScene(scene);
|
|
||||||
var gameObject = new GameObject(TAG_OBJECT_NAME);
|
|
||||||
gameObject.AddComponent<Activator>();
|
|
||||||
gameObject.hideFlags = HIDE_FLAGS;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
SceneManager.SetActiveScene(oldActiveScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const HideFlags HIDE_FLAGS = HideFlags.HideInHierarchy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AddComponentMenu("")]
|
[AddComponentMenu("")]
|
||||||
[ExecuteInEditMode]
|
[ExecuteInEditMode]
|
||||||
[DefaultExecutionOrder(-9997)]
|
[DefaultExecutionOrder(-9997)]
|
||||||
public class AvatarActivator : MonoBehaviour
|
public class AvatarActivator : MonoBehaviour, IEditorOnly
|
||||||
{
|
{
|
||||||
private void Awake()
|
|
||||||
{
|
|
||||||
if (!RuntimeUtil.isPlaying || this == null) return;
|
|
||||||
RuntimeUtil.OnDemandProcessAvatar(RuntimeUtil.OnDemandSource.Awake, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
if (!RuntimeUtil.isPlaying || this == null) return;
|
|
||||||
RuntimeUtil.OnDemandProcessAvatar(RuntimeUtil.OnDemandSource.Start, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
DestroyImmediate(this);
|
DestroyImmediate(this);
|
||||||
|
@ -7,6 +7,8 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class AvatarObjectReference
|
public class AvatarObjectReference
|
||||||
{
|
{
|
||||||
|
private long ReferencesLockedAtFrame = long.MinValue;
|
||||||
|
|
||||||
public static string AVATAR_ROOT = "$$$AVATAR_ROOT$$$";
|
public static string AVATAR_ROOT = "$$$AVATAR_ROOT$$$";
|
||||||
public string referencePath;
|
public string referencePath;
|
||||||
|
|
||||||
@ -16,7 +18,9 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
public GameObject Get(Component container)
|
public GameObject Get(Component container)
|
||||||
{
|
{
|
||||||
if (_cacheValid && _cachedPath == referencePath && _cachedReference != null) return _cachedReference;
|
bool cacheValid = _cacheValid || ReferencesLockedAtFrame == Time.frameCount;
|
||||||
|
|
||||||
|
if (cacheValid && _cachedPath == referencePath && _cachedReference != null) return _cachedReference;
|
||||||
|
|
||||||
_cacheValid = true;
|
_cacheValid = true;
|
||||||
_cachedPath = referencePath;
|
_cachedPath = referencePath;
|
||||||
@ -57,7 +61,8 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
referencePath = RuntimeUtil.AvatarRootPath(target);
|
referencePath = RuntimeUtil.AvatarRootPath(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheValid = false;
|
_cachedReference = target;
|
||||||
|
_cacheValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InvalidateCache()
|
private void InvalidateCache()
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -52,14 +52,6 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
{
|
{
|
||||||
if (RuntimeUtil.isPlaying) return;
|
if (RuntimeUtil.isPlaying) return;
|
||||||
|
|
||||||
RuntimeUtil.delayCall(() =>
|
|
||||||
{
|
|
||||||
if (this == null) return;
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
Activator.CreateIfNotPresent(gameObject.scene);
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
|
|
||||||
OnChangeAction?.Invoke();
|
OnChangeAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,5 +59,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
{
|
{
|
||||||
OnChangeAction?.Invoke();
|
OnChangeAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eagerly resolve all AvatarTagReferences to their destinations.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract void ResolveReferences();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,5 +12,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
{
|
{
|
||||||
context.PushNode(new MenuNodesUnder(targetObject != null ? targetObject : gameObject));
|
context.PushNode(new MenuNodesUnder(targetObject != null ? targetObject : gameObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,5 +22,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
RuntimeUtil.InvalidateMenu();
|
RuntimeUtil.InvalidateMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -64,6 +64,11 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
RuntimeUtil.OnHierarchyChanged -= Rebind;
|
RuntimeUtil.OnHierarchyChanged -= Rebind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
private void Rebind()
|
private void Rebind()
|
||||||
{
|
{
|
||||||
if (this == null) return;
|
if (this == null) return;
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -95,6 +95,11 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public string subPath;
|
public string subPath;
|
||||||
public BoneProxyAttachmentMode attachmentMode = BoneProxyAttachmentMode.Unset;
|
public BoneProxyAttachmentMode attachmentMode = BoneProxyAttachmentMode.Unset;
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
_targetCache = UpdateDynamicMapping();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnValidate()
|
protected override void OnValidate()
|
||||||
{
|
{
|
||||||
base.OnValidate();
|
base.OnValidate();
|
||||||
@ -103,7 +108,19 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
internal void ClearCache()
|
internal void ClearCache()
|
||||||
{
|
{
|
||||||
_targetCache = null;
|
ClearCache(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearCache(bool immediate)
|
||||||
|
{
|
||||||
|
if (immediate)
|
||||||
|
{
|
||||||
|
_targetCache = null;
|
||||||
|
} else if (_targetCache != null)
|
||||||
|
{
|
||||||
|
RuntimeUtil.delayCall(() => { _targetCache = null; });
|
||||||
|
}
|
||||||
|
|
||||||
RuntimeUtil.OnHierarchyChanged -= ClearCache;
|
RuntimeUtil.OnHierarchyChanged -= ClearCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,5 +22,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
{
|
{
|
||||||
context.PushNode(installer);
|
context.PushNode(installer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,6 +37,11 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
public void Visit(NodeContext context)
|
public void Visit(NodeContext context)
|
||||||
{
|
{
|
||||||
if (Control == null)
|
if (Control == null)
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -41,5 +41,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public bool deleteAttachedAnimator;
|
public bool deleteAttachedAnimator;
|
||||||
public MergeAnimatorPathMode pathMode = MergeAnimatorPathMode.Relative;
|
public MergeAnimatorPathMode pathMode = MergeAnimatorPathMode.Relative;
|
||||||
public bool matchAvatarWriteDefaults;
|
public bool matchAvatarWriteDefaults;
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -89,6 +89,11 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
mergeTarget?.Get(this);
|
||||||
|
}
|
||||||
|
|
||||||
void EditorUpdate()
|
void EditorUpdate()
|
||||||
{
|
{
|
||||||
if (this == null)
|
if (this == null)
|
||||||
|
@ -25,5 +25,11 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public InheritMode InheritBounds = InheritMode.Inherit;
|
public InheritMode InheritBounds = InheritMode.Inherit;
|
||||||
public AvatarObjectReference RootBone;
|
public AvatarObjectReference RootBone;
|
||||||
public Bounds Bounds = DEFAULT_BOUNDS;
|
public Bounds Bounds = DEFAULT_BOUNDS;
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
ProbeAnchor?.Get(this);
|
||||||
|
RootBone?.Get(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022 bd_
|
* Copyright (c) 2022 bd_
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
*
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
* copies or substantial portions of the Software.
|
* copies or substantial portions of the Software.
|
||||||
*
|
*
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
@ -30,5 +30,9 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
[AddComponentMenu("Modular Avatar/MA PhysBone Blocker")]
|
[AddComponentMenu("Modular Avatar/MA PhysBone Blocker")]
|
||||||
public class ModularAvatarPBBlocker : AvatarTagComponent
|
public class ModularAvatarPBBlocker : AvatarTagComponent
|
||||||
{
|
{
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -33,5 +33,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public class ModularAvatarParameters : AvatarTagComponent
|
public class ModularAvatarParameters : AvatarTagComponent
|
||||||
{
|
{
|
||||||
public List<ParameterConfig> parameters = new List<ParameterConfig>();
|
public List<ParameterConfig> parameters = new List<ParameterConfig>();
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,5 +7,10 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public class ModularAvatarReplaceObject : AvatarTagComponent
|
public class ModularAvatarReplaceObject : AvatarTagComponent
|
||||||
{
|
{
|
||||||
public AvatarObjectReference targetObject = new AvatarObjectReference();
|
public AvatarObjectReference targetObject = new AvatarObjectReference();
|
||||||
|
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
targetObject?.Get(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,5 +7,9 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
public class ModularAvatarVisibleHeadAccessory : AvatarTagComponent
|
public class ModularAvatarVisibleHeadAccessory : AvatarTagComponent
|
||||||
{
|
{
|
||||||
// no configuration needed
|
// no configuration needed
|
||||||
|
internal override void ResolveReferences()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,6 +15,7 @@
|
|||||||
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
||||||
},
|
},
|
||||||
"vpmDependencies": {
|
"vpmDependencies": {
|
||||||
"com.vrchat.avatars": ">=3.2.0"
|
"com.vrchat.avatars": ">=3.2.0",
|
||||||
|
"nadena.dev.ndmf": "=0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
Packages/nadena.dev.ndmf
Submodule
1
Packages/nadena.dev.ndmf
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 3a5f80dfabd90c27c1b5a9e216f1a9093c28e092
|
@ -196,6 +196,12 @@
|
|||||||
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
"com.unity.nuget.newtonsoft-json": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nadena.dev.ndmf": {
|
||||||
|
"version": "file:nadena.dev.ndmf",
|
||||||
|
"depth": 0,
|
||||||
|
"source": "embedded",
|
||||||
|
"dependencies": {}
|
||||||
|
},
|
||||||
"vrchat.blackstartx.gesture-manager": {
|
"vrchat.blackstartx.gesture-manager": {
|
||||||
"version": "file:vrchat.blackstartx.gesture-manager",
|
"version": "file:vrchat.blackstartx.gesture-manager",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user