### Basic NDM Plugin Example Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/index.md This C# snippet demonstrates the basic structure of a non-destructive plugin for the NDM Framework. It shows how to export the plugin and define a build phase task. ```csharp [assembly: ExportsPlugin(typeof(MyPlugin))] public class MyPlugin : Plugin { public override string DisplayName => "Baby's first plugin"; protected override void Configure() { InPhase(BuildPhase.Transforming).Run("Do the thing", ctx => { Debug.Log("Hello world!"); }); } } ``` -------------------------------- ### Example NDMF Unit Test Structure Source: https://github.com/bdunderscore/ndmf/blob/main/.copilot-instructions.md Demonstrates a typical unit test pattern for NDMF plugins, including setup, execution, and assertion phases. Includes conditional compilation for VRCSDK-specific tests. ```csharp [Test] public void TestPluginExecution() { // Arrange var context = CreateTestBuildContext(); // Act RunPlugin(context); // Assert Assert.AreEqual(expectedValue, context.GetState().Value); } #if NDMF_VRCSDK3_AVATARS [Test] public void TestVRCSDKIntegration() { // VRCSDK-specific test code } #endif ``` -------------------------------- ### Define a Plugin with a Sequence and Passes Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/execution-model.md Example of defining a custom plugin with a sequence that runs in the Transforming phase, including constraints like AfterPass and BeforePass. ```csharp public class MyPlugin : Plugin { public override string DisplayName => "Baby's first plugin"; protected override void Configure() { Sequence seq = InPhase(BuildPhase.Transforming); seq .AfterPass(typeof(SomePriorPass)) .Run(typeof(Pass1)) .BeforePass(typeof(SomeOtherPass)) .Then.Run(typeof(Pass2)); } } ``` -------------------------------- ### NDMF Pass Dependency Declaration in C# Source: https://github.com/bdunderscore/ndmf/blob/main/README.md Example of declaring dependency constraints for passes within a plugin's configuration. Supports ordering relative to other plugins and passes, with options for 'After' and 'WaitFor'. ```csharp protected override void Configure() { InPhase(BuildPhase.Transforming) .AfterPlugin("com.example.some-plugin") .BeforePlugin(typeof(SomeMandatoryPlugin)) .AfterPass(typeof(SomePass)) .WaitFor(typeof(RunsJustBeforePass)) .Run(...) .BeforePass(typeof(SomeSpecificPass)); } ``` -------------------------------- ### Dependency Declaration Patterns Source: https://github.com/bdunderscore/ndmf/blob/main/.copilot-instructions.md Illustrates how to declare plugin and pass dependencies using a fluent API. This allows for precise ordering of execution within build phases. ```csharp InPhase(BuildPhase.Transforming) .AfterPlugin("com.example.some-plugin") .BeforePlugin(typeof(SomeMandatoryPlugin)) .AfterPass(typeof(SomePass)) .WaitFor(typeof(RunsJustBeforePass)) .Run("My pass", ctx => { /* ... */ }); ``` -------------------------------- ### Run Unity Tests via Command Line Source: https://github.com/bdunderscore/ndmf/blob/main/.copilot-instructions.md Execute Unity tests using command-line parameters, commonly used in CI environments. Specify the test mode, graphics settings, and assembly names for targeted testing. ```bash Unity -testMode EditMode -nographics -assemblyNames nadena.dev.ndmf.UnitTests ``` -------------------------------- ### Sequence Constraints: Before/AfterPlugin Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/execution-model.md Demonstrates how to enforce that a sequence runs entirely before or after all processing of another plugin using string names or types. ```csharp sequence.BeforePlugin("other.plugin.name"); sequence.AfterPlugin(typeof(OtherPlugin)); ``` -------------------------------- ### Manage Build State with BuildContext Source: https://context7.com/bdunderscore/ndmf/llms.txt The `BuildContext` provides access to the avatar root, asset management, state bags, and extensions. `GetState()` attaches arbitrary typed state to the build, surviving across passes. ```csharp // Attach plugin-specific state to the build class MyBuildState { public List MovedBones { get; } = new List(); } class TrackBonesPass : Pass { protected override void Execute(BuildContext ctx) { var state = ctx.GetState(); // created on first access foreach (var t in ctx.AvatarRootObject.GetComponentsInChildren()) { if (t.hasChanged) state.MovedBones.Add(t); } } } class ApplyBoneResultsPass : Pass { protected override void Execute(BuildContext ctx) { var state = ctx.GetState(); // same instance as above Debug.Log($"Moved {state.MovedBones.Count} bones"); // Access an active extension context var animSvc = ctx.Extension(); animSvc.ObjectPathRemapper.RecordObjectTree(ctx.AvatarRootTransform); // Save a generated asset to the build's asset container var mesh = new Mesh { name = "Generated" }; ctx.AssetSaver.SaveAsset(mesh); // Check if the build has reported any errors if (!ctx.Successful) Debug.LogWarning("Build has errors!"); } } ``` -------------------------------- ### Sequence Constraints: Before/AfterPass Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/execution-model.md Shows how to specify that a single pass within a sequence must run before or after another specific pass. The declaration order reflects execution order. ```csharp sequence.AfterPass(typeof(OtherPass)) .Run(typeof(MyPass)) .BeforePass(typeof(SomeOtherPass)); ``` -------------------------------- ### Define and Use Custom Extension Context Source: https://context7.com/bdunderscore/ndmf/llms.txt Implement IExtensionContext to create custom contexts activated around groups of passes. Use WithRequiredExtension or WithCompatibleExtension to declare context usage in sequences. ```csharp // Define a custom extension context public class MeshCacheContext : IExtensionContext { public Dictionary Cache { get; } = new(); public void OnActivate(BuildContext ctx) { // Build an index of all meshes on the avatar once, shared across passes foreach (var smr in ctx.AvatarRootObject.GetComponentsInChildren()) Cache[smr.name] = smr.sharedMesh; } public void OnDeactivate(BuildContext ctx) { Cache.Clear(); } } // Declare passes that use the context public class MyPlugin : Plugin { public override string DisplayName => "Mesh Processing Plugin"; protected override void Configure() { var seq = InPhase(BuildPhase.Optimizing); // All passes inside this block will activate MeshCacheContext before executing, // and deactivate it only when a non-compatible pass is about to run. seq.WithRequiredExtension(typeof(MeshCacheContext), s => { s.Run("Use mesh cache", ctx => { var cache = ctx.Extension(); foreach (var (name, mesh) in cache.Cache) Debug.Log($"Cached mesh: {name}, verts={mesh.vertexCount}"); }); }); // This pass won't deactivate the context if it happens to still be active seq.WithCompatibleExtension(typeof(MeshCacheContext), s => { s.Run("Compatible pass", ctx => { /* won't cause deactivation */ }); }); } } ``` -------------------------------- ### Configure Plugin Pass Dependencies and Execution Order Source: https://github.com/bdunderscore/ndmf/blob/main/design-docs/SEQUENCING.md Configure the execution order of passes within a plugin using methods like `InPhase`, `AfterPlugin`, `Run`, `WaitFor`, and `BeforePlugin`. This allows for fine-grained control over pass scheduling. ```csharp class MyPlugin : Plugin { public override string QualifiedName => "my.plugin"; protected override void Configure() { InPhase(Phases.Generating) .AfterPlugin("nadena.dev.modular-avatar") .Run() .Run() // Inline passes are supported, but cannot be named in a dependency reference .Run("my inline pass", context => { /* ... */ }) .WaitFor(SomeOtherPlugin.ExposedPass) .Run() // runs ASAP after ExposedPass .BeforePlugin("some.other.plugin"); // This is a separate _sequence_ of passes. No successor edge is generated, so this sequence might end up being // scheduled before the other sequence. InPhase(Phases.Generating) .Run(MyIndependentPass.Instance); } } ``` -------------------------------- ### Conditional Compilation for VRCSDK Source: https://github.com/bdunderscore/ndmf/blob/main/.copilot-instructions.md Demonstrates how to conditionally compile code that depends on the VRCSDK. This ensures compatibility when the SDK is not present. ```csharp #if VRCSDK3_AVATARS // VRCSDK-dependent code here #endif ``` -------------------------------- ### Define Plugin Passes Across Build Phases Source: https://context7.com/bdunderscore/ndmf/llms.txt Use `BuildPhase` to specify when your plugin's passes should execute. Different phases are suitable for different tasks like early resolution, generation, transformation, or optimization. ```csharp public class MyPlugin : Plugin { public override string DisplayName => "Phase Demo Plugin"; protected override void Configure() { // Early: resolve serialized string paths to GameObjects before anything moves InPhase(BuildPhase.Resolving) .Run("Resolve path references", ctx => { // safe to query avatar state before other plugins run Debug.Log("Avatar root: " + ctx.AvatarRootObject.name); }); // Middle: generate components for other plugins to consume InPhase(BuildPhase.Generating) .Run("Inject generated components", ctx => { /* ... */ }); // General: transform the avatar InPhase(BuildPhase.Transforming) .Run("Main transform pass", ctx => { /* ... */ }); // Late: run-only optimizations InPhase(BuildPhase.Optimizing) .Run("Merge identical meshes", ctx => { /* ... */ }); } } ``` -------------------------------- ### Declare Extension Context Compatibility and Requirements Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/extension-context.md Use this syntax to declare that a pass is compatible with an extension context and to specify required extensions for subsequent passes. ```csharp sequence.WithCompatibleExtension("foo.bar.ExtensionClass", seq2 => { seq2.WithRequiredExtension(typeof(OtherExtensionClass), seq3 => { seq3.Run(typeof(MyPass)); }); }); ``` -------------------------------- ### Declare and Register an NDMF Plugin Source: https://context7.com/bdunderscore/ndmf/llms.txt Use `Plugin` as the base class and `[assembly: ExportsPlugin]` to register your plugin. Override `Configure()` to define passes using `InPhase()`. ```csharp // MyPlugin.cs — declare and register a plugin [assembly: ExportsPlugin(typeof(MyAvatarPlugin))] namespace com.example.myplugin { public class MyAvatarPlugin : Plugin { public override string DisplayName => "My Avatar Plugin"; // QualifiedName defaults to "com.example.myplugin.MyAvatarPlugin" protected override void Configure() { // Register a named inline pass in the Transforming phase InPhase(BuildPhase.Transforming) .Run("Remove extra bones", ctx => { var root = ctx.AvatarRootObject; foreach (Transform t in root.GetComponentsInChildren(true)) { if (t.name.StartsWith("_extra_")) Object.DestroyImmediate(t.gameObject); } }); } } } ``` -------------------------------- ### Control Execution Order with Sequence API Source: https://context7.com/bdunderscore/ndmf/llms.txt Use the `Sequence` fluent API to control execution order relative to other plugins and passes. `AfterPass`/`BeforePass` are 'weak', while `WaitFor` schedules the pass as soon as possible after the target. ```csharp public class DependentPlugin : Plugin { public override string DisplayName => "Dependent Plugin"; protected override void Configure() { // Entire sequence runs after all of ModularAvatarPlugin and before all of OptimizerPlugin var seq = InPhase(BuildPhase.Transforming) .AfterPlugin("nadena.dev.modular_avatar.core.ModularAvatarPlugin") .BeforePlugin(); seq // This pass runs after a specific pass in another plugin .AfterPass(typeof(SomePriorPass)) .Run(typeof(MyFirstPass)) // This pass runs before a specific pass in another plugin .BeforePass(typeof(SomeLaterPass)) .Then // WaitFor: schedule as close as possible after the target pass .WaitFor(typeof(InsertImmediatelyAfterThisPass)) .Run("Second step", ctx => { /* ... */ }); } } ``` -------------------------------- ### Sequence Constraints: WaitFor Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/execution-model.md Illustrates the WaitFor constraint, which attempts to run a pass as soon as possible after a specified pass, useful for inserting processing between two passes. ```csharp sequence.WaitFor(typeof(OtherPass)) .Run(typeof(MyPass)); ``` -------------------------------- ### Basic Plugin Structure Pattern Source: https://github.com/bdunderscore/ndmf/blob/main/.copilot-instructions.md Defines the fundamental structure for creating a plugin within the NDMF framework. Ensure the plugin is exported and implements the necessary base class and methods. ```csharp [assembly: ExportsPlugin(typeof(MyPlugin))] public class MyPlugin : Plugin { public override string QualifiedName => "com.example.my-plugin"; public override string DisplayName => "My Plugin Name"; protected override void Configure() { InPhase(BuildPhase.Transforming) .Run("Pass name", ctx => { // Pass implementation }); } } ``` -------------------------------- ### Declare Required and Compatible Contexts in a Pass Source: https://github.com/bdunderscore/ndmf/blob/main/README.md Define which ExtensionContexts a pass requires and which it is compatible with. Required contexts are activated before the pass, and compatible contexts can be accessed via BuildContext.Extension(). ```csharp abstract class MAPass : PluginPass { public override IImmutableSet RequiredContexts => ImmutableHashSet.Empty.Add(typeof(ModularAvatarContext)); public override IImmutableSet CompatibleContexts => ImmutableHashSet.Empty.Add(typeof(TrackObjectRenamesContext)); protected BuildContext MAContext(build_framework.BuildContext context) { return context.Extension().BuildContext; } } ``` -------------------------------- ### Declare Plugin with Extension Requirements Source: https://github.com/bdunderscore/ndmf/blob/main/design-docs/SEQUENCING.md Define a plugin that requires specific extensions using attributes like `RequiresExtension` and `OptionalExtension`. This ensures necessary components are available. ```csharp [RequiresExtension(typeof(SomeExtension))] [OptionalExtension("com.example.SomeOtherExtension"))] class SomeOtherPlugin : Plugin { /// ... public static Type ExposedPass => typeof(SomeInternalPass); } ``` -------------------------------- ### Avoid Passing Data Between Phases with Lambda Captures Source: https://github.com/bdunderscore/ndmf/blob/main/docfx~/best-practices.md Demonstrates the incorrect use of lambda captures for passing data between phases and the correct approach using BuildContext.GetState. Avoid capturing local variables in inline passes as they may be shared across avatar builds. ```csharp public class MyPlugin : Plugin { protected override void Configure() { GameObject generatedObject = null; InPhase(BuildPhase.Generating) .Run("Pass", ctx => { generatedObject = new GameObject("Generated"); }); } } ``` ```csharp public class MyPlugin : Plugin { public class MyState { public GameObject GeneratedObject; } protected override void Configure() { InPhase(BuildPhase.Generating) .Run("Pass", ctx => { ctx.GetState().GeneratedObject = new GameObject("Generated"); }); } } ``` -------------------------------- ### Perform Bulk Animation Operations with AnimationIndex Source: https://context7.com/bdunderscore/ndmf/llms.txt Leverage AnimationIndex for efficient bulk operations on multiple clips, including finding clips by path or binding, editing curves, and rewriting paths. Requires AnimatorServicesContext. ```csharp seq.Run("Bulk animation operations", ctx => { var index = ctx.Extension().AnimationIndex; // Find all clips that animate a given path foreach (var clip in index.GetClipsForObjectPath("Armature/Hips/Spine")) Debug.Log($"Path animated by: {clip.Name}"); // Find all clips with a specific binding var bind = EditorCurveBinding.FloatCurve("Body", typeof(SkinnedMeshRenderer), "blendShape.Blink_L"); foreach (var clip in index.GetClipsForBinding(bind)) Debug.Log($"Blink_L animated in: {clip.Name}"); // Efficient bulk edit: process only clips that reference certain bindings index.EditClipsByBinding(new[] { bind }, clip => { var curve = clip.GetFloatCurve(bind); if (curve == null) return; for (int i = 0; i < curve.length; i++) { var key = curve[i]; key.value *= 0.5f; // halve all blendshape values curve.MoveKey(i, key); } clip.SetFloatCurve(bind, curve); }); // Rewrite all paths via a dictionary (null value = delete the binding) index.RewritePaths(new Dictionary { ["Armature/Hips/OldBone"] = "Armature/Hips/NewBone", ["Armature/DeletedBone"] = null, // removes all curves for this path }); // Remap all PPtr (object reference) curves — e.g. remapping material references index.RewriteObjectCurves((ecb, obj) => obj is Material mat && mat.name == "OldMat" ? newMaterial : obj); }); ``` -------------------------------- ### ObserveTransform Source: https://github.com/bdunderscore/ndmf/blob/main/Editor/ChangeStream/memo.txt Observes the transform of a game object. ```APIDOC ## ObserveTransform ### Description Observes and returns the transform component of a given game object. ### Method Signature `Transform ObserveTransform(GameObject object);` ### Parameters #### Path Parameters - **object** (GameObject) - The game object whose transform is to be observed. ### Return Value - **Transform** - The transform component of the game object. ``` -------------------------------- ### Restrict Passes to Specific Platforms Source: https://context7.com/bdunderscore/ndmf/llms.txt Use OnPlatforms or OnAllPlatforms methods on sequences, or attributes like [RunsOnPlatforms]/[RunsOnAllPlatforms] on passes and plugins to control platform execution. Pass-level attributes have the highest precedence. ```csharp // Plugin-level: all passes default to VRChat only (default behavior for backwards compat) [RunsOnPlatforms(WellKnownPlatforms.VRChatAvatar30)] public class VRChatOnlyPlugin : Plugin { public override string DisplayName => "VRChat-Only Plugin"; protected override void Configure() { var seq = InPhase(BuildPhase.Transforming); // Override at sequence level: these passes run on VRChat AND Resonite seq.OnPlatforms(new[] { WellKnownPlatforms.VRChatAvatar30, WellKnownPlatforms.Resonite }, s => { s.Run("Shared processing", ctx => { /* runs on both platforms */ }); }); // Override at sequence level: these passes run on all platforms seq.OnAllPlatforms(s => { s.Run("Universal pass", ctx => { /* runs on any platform */ }); }); } } // Pass-level attribute overrides everything above it in the hierarchy [RunsOnAllPlatforms] public class AlwaysRunPass : Pass { protected override void Execute(BuildContext ctx) { Debug.Log("Running on platform: " + ctx.PlatformProvider); } } ``` -------------------------------- ### Minimal Plugin Definition in C# Source: https://github.com/bdunderscore/ndmf/blob/main/README.md A basic plugin definition using NDMF. Requires the 'ExportsPlugin' attribute and implements the 'Plugin' base class. Configure plugin behavior within the 'Configure' method. ```csharp [assembly: ExportsPlugin(typeof(MyPlugin))] namespace nadena.dev.ndmf.sample { public class MyPlugin : Plugin { protected override void Configure() { InPhase(BuildPhase.Transforming).Run("Do something", ctx => { /* ... */ }); } } } ``` -------------------------------- ### Define a Named Pass and Use in Plugin Source: https://context7.com/bdunderscore/ndmf/llms.txt Create a named, referenceable pass by inheriting from `Pass`. Anonymous inline passes are convenient for simple operations but cannot be referenced by other plugins. ```csharp public class NormalizeBonesPass : Pass { public override string DisplayName => "Normalize Bone Names"; protected override void Execute(BuildContext ctx) { foreach (var t in ctx.AvatarRootObject.GetComponentsInChildren(true)) { t.name = t.name.Replace(" ", "_").ToLowerInvariant(); } } } public class MyPlugin : Plugin { public override string DisplayName => "My Plugin"; protected override void Configure() { InPhase(BuildPhase.Transforming) .Run(NormalizeBonesPass.Instance) // named pass — other plugins can WaitFor/BeforePass this .Then.Run("Cleanup", ctx => { // inline pass for simple operations // ... }); } } ``` -------------------------------- ### Define a Custom Pass in C# Source: https://github.com/bdunderscore/ndmf/blob/main/design-docs/SEQUENCING.md Declare a custom pass by inheriting from `Pass` and implementing the `Execute` method. This is the basic structure for creating a new pass. ```csharp class MyPass : Pass { protected override void Execute(BuildContext context) { // Do stuff } } ``` -------------------------------- ### Virtualize and Modify Animator Controllers Source: https://context7.com/bdunderscore/ndmf/llms.txt Use VirtualControllerContext to clone and modify animator controllers without altering original assets. Changes are committed on deactivation. Requires BuildPhase.Transforming. ```csharp public class MergeControllersPlugin : Plugin { public override string DisplayName => "Controller Merger"; protected override void Configure() { InPhase(BuildPhase.Transforming) .WithRequiredExtension(typeof(VirtualControllerContext), seq => { seq.Run("Merge FX layers", ctx => { var vcc = ctx.Extension(); // On VRChat: key is VRCAvatarDescriptor.AnimLayerType.FX foreach (var (key, controller) in vcc.Controllers) { Debug.Log($ ``` ```csharp Controller key={key}, layers={controller.Layers.Count()}); // Add a new layer at default priority var newLayer = controller.AddLayer(LayerPriority.Default, "MyNewLayer"); newLayer.DefaultWeight = 1.0f; // Add a parameter var param = new AnimatorControllerParameter { name = "MyParam", type = AnimatorControllerParameterType.Float, defaultFloat = 0f }; controller.Parameters = controller.Parameters.Add("MyParam", param); } // Clone a controller from a component and add it to the context var ac = SomeComponent.GetComponent().runtimeAnimatorController; if (ac != null) { var cloned = vcc.Clone(ac); vcc.Controllers["my_extra_key"] = cloned; } }); }); } } ``` -------------------------------- ### Track Object Identity with ObjectRegistry Source: https://context7.com/bdunderscore/ndmf/llms.txt Use ObjectRegistry to maintain object identity across transformations by registering cloned or replaced objects. This ensures accurate error reporting. Use `RegisterReplacedObject` or `TryRegisterReplacedObject`. ```csharp class CloneAndTrackPass : Pass { protected override void Execute(BuildContext ctx) { var go = ctx.AvatarRootObject.transform.Find("OriginalBone")?.gameObject; if (go == null) return; // Clone the object var clone = Object.Instantiate(go, go.transform.parent); clone.name = go.name + "_Clone"; // Register that the clone is a replacement for the original // This allows NDMF error reports to identify "OriginalBone" as the source ObjectRegistry.RegisterReplacedObject(go, clone); // Alternatively use TryRegisterReplacedObject for non-throwing variant var @ref = ObjectRegistry.GetReference(go); bool ok = ObjectRegistry.TryRegisterReplacedObject(@ref, clone); Debug.Log($"Registration succeeded: {ok}"); } } ``` -------------------------------- ### Edit Animation Clips with VirtualClip Source: https://context7.com/bdunderscore/ndmf/llms.txt Use VirtualClip to add, modify, or rewrite curves and paths in Unity AnimationClips. Marker clips are immutable. Ensure necessary contexts are available. ```csharp seq.Run("Add enable curve to all clips", ctx => { var vcc = ctx.Extension(); var animSvc = ctx.Extension(); // Find all clips that animate a specific object var binding = EditorCurveBinding.FloatCurve("Body", typeof(GameObject), "m_IsActive"); foreach (var clip in animSvc.AnimationIndex.GetClipsForBinding(binding)) { if (clip.IsMarkerClip) continue; // skip proxy animations // Read existing curve var existing = clip.GetFloatCurve(binding); Debug.Log($ ``` -------------------------------- ### ObserveState Source: https://github.com/bdunderscore/ndmf/blob/main/Editor/ChangeStream/memo.txt Observes the state of an object and returns a transformed value. ```APIDOC ## ObserveState ### Description Observes the state of a given object using a provided function and returns the transformed state. ### Method Signature `T ObserveState(Object obj, Func func);` ### Parameters #### Path Parameters - **obj** (Object) - The object whose state is to be observed. - **func** (Func) - A function that transforms the observed state. ### Return Value - **T** - The transformed state of the object. ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.