I don’t where you ended up this, but I came back around to this post as I was thinking about adding UserData to InstanceDefinitions.
The way we’ve been successful with UserData in unit tests is by declaring callback functions for get/add/remove operations as well as the interface for your userdata in a shared library. Then you implement those callbacks as well as the UserData class in the plugin project. In your plugin’s constructor you then assign the implementation of get/add/remove callbacks to the delegates in your shared library. Finally you need to make sure that your plugin is loaded as a part of the unit tests. This makes is possible to invoke the callback functions from your unit testing code without needed a direct dependency on the plugin *.rhp. I leveraged the Rhino.Testing library to do this. To expand upon my example above:
#In Shared.Dll
public static class UserDataExtensions
{
public delegate bool TryGetUserDataDelegate(RhinoObject ro,
[MaybeNullWhen(false)] out ICustomUserData userData);
public Func<ICustomUserData, RhinoObject>? AddUserDataFunc {get; set;}
public Func<bool, RhinoObject>? RemoveUserDataFunc {get; set;}
public TryGetUserDataDelegate? TryGetUserDataFunc {get; set;}
public ICustomUserData AddUserData(this RhinoObject ro)
{
return AddUserDataFunc?.Invoke(ro);
}
public bool TryGetUserData(this RhinoObject ro,
[MaybeNullWhen(false)] out ICustomUserData userData)
{
return TryGetUserDataFunc?.Invoke(ro, out userData) ?? false;
}
public bool RemoveUserData(this RhinoObject ro)
{
return RemoveUserDataFunc?.Invoke(ro) ?? false;
}
}
#in CustomUserDataImpl.cs
public static ICustomUserData AddUserDataImpl(RhinoObject ro)
{
if(TryGetUserDataImpl(ro, out var data) return data;
var attributes = ro.Attributes.Duplicate();
var data = new CustomUserDataImpl();
attributes.UserData.Add(data);
ro.Document.Objects.ModifyAttributes(ro.Id, attributes, false);
}
public static bool TryGetUserDataImpl (RhinoObject ro, out ICustomUserData rhinoData)
{
var cdata = ro.attributes.UserData.Find(typeof(CustomUserDataImpl));
rhinoData = (ICustomUserData)cdata;
return rhinoData is not null;
}
public static bool RemoveUserDataImpl(RhinoObject ro)
{
var userObject = ro.attributes.UserData.Find(typeof(CustomUserDataImpl));
if (userObject is null) return false;
var attributes = ro.Attributes.Duplicate();
attributes.UserData.Remove(userObject);
ro.Document.Objects.ModifyAttributes(ro.Id, attributes, false);
return true;
}
#in Plugin.cs
public class CustomPlugIn : Rhino.PlugIns.PlugIn
{
protected override LoadReturnCode OnLoad(ref string errorMessage)
{
UserDataExtensions.AddUserDataFunc = AddUserDataImpl;
UserDataExtensions.TryGetUserDataFunc = TryGetUserDataImpl;
UserDataExtensions.RemoveUserDataFunc = RemoveUserDataImpl;
// Any other setup implementation for your plugin
}
}
All of that said, I’ve also done the UserDictionary approach and it’s probably good enough for most situations. I moved away from using ArchivableDictionaries because I wanted to have more control over serialization and copy logic
There used to be some gotcha’s around AppDomains with the Visual Studio/JetBrains Tests runners, but those don’t appear to be an issue anymore with .Net8 and Rhino8. Hope you’ve settled your own approach to handle this problem in the meantime.