Dynamically Generate Plugin Commands

reflection
dynamic
rhinocommon
command

#1

Is it possible to hook into any part of the Rhino Startup Process to all a Plugin to dynamically generate the Rhino Commands it exposes to Rhino?

We are trying to create an [Attribute] that we decorate our Rhino Logic methods that can be reflected upon and used to dynamically generate the Rhino Commands.

I have hooked into the OnLoad() of our Rhino Plugin and using the CSharpCodeProvider() generated a series of Rhino Commands from our plugin.

These commands are compiled into an assembly and dynamically loaded into the Rhino App Domain.

The Commands however are not found by Rhino. I assume because of how the don’t yet exist when the plugin is found and loaded by Rhino.

Do you guys have any idea if this is possible?

If not, do you guys have any Interface to hook into the Command Prompt that I can use as a command resolver of sorts to offer our commands to users?

Thanks,

Jake


(Steve Baer) #2

Hey Jake,
The PlugIn class has a virtual CreateCommands function that you can override. Inside of that function you can call the protected RegisterCommand function for each of your command instances.


#3

Excellent! I’ll give it a try. Thank you!


#4

This worked great! Thank you.


(Caglar Aydin) #5

Hello @jstevenson72 @stevebaer ,

Could you provide a sample code for how to use the ‘CSharpCodeProvider()’ inside a plugin?

I’m trying to get an user input text which I want to include it as methods in my main class inside a plugin. Or can I use that input as a new command?

Thanks,
-Caglar


#6

Caglar,

We are dynamically generating methods at runtime, that become Rhino Commands within our Plugin. We use a custom attribute that we apply to methods within our code, then this logic finds those methods using reflection and creates the commands for each one at runtime. If you are wanting something similar, maybe these code snippets will help.

This method is within our Plugin, and overrides the normal CreateCommands() logic:

    protected override void CreateCommands()
    {
        RhinoCommandHelper.GetCommandItems();

        var assembly = RhinoCommandHelper.CreateRhinoCommandsAssembly();

        var commands = assembly.GetTypes().Where(_ => _.IsClass && !_.IsAbstract && _.IsSubclassOf(typeof(Command))).ToList();

        commands.ForEach(_ =>
        {
            RegisterCommand((Command)Activator.CreateInstance(_));
        });

        base.CreateCommands();
    }

This method is within a helper class RhinoCommandHelper, that generates a c# assembly:

    public static Assembly CreateRhinoCommandsAssembly()
    {
        ErrorLog.WriteMethod();

        var builder = new StringBuilder();

        var provider = new CSharpCodeProvider(new Dictionary<string, string> { { @"CompilerVersion", @"v4.0" } });

        var compilerParameters = new CompilerParameters();
        compilerParameters.GenerateExecutable = false;
        compilerParameters.GenerateInMemory = true;

        // Add Referenced Assemblies

        var path = Assembly.GetAssembly(typeof(Log)).Location;
        compilerParameters.ReferencedAssemblies.Add(path);

        path = Assembly.GetAssembly(typeof(Command)).Location;
        compilerParameters.ReferencedAssemblies.Add(path);

        path = Assembly.GetAssembly(typeof(INotifyPropertyChanged)).Location;
        compilerParameters.ReferencedAssemblies.Add(path);

        path = Assembly.GetAssembly(typeof(RhinoCommandHelper)).Location;
        compilerParameters.ReferencedAssemblies.Add(path);

        // Add Using Statements and Namespace
        builder.AppendLine("using System;");
        builder.AppendLine("using System.Runtime.InteropServices;");
        builder.AppendLine("using Common.Helpers;");
        builder.AppendLine("using Rhino;");
        builder.AppendLine("using Rhino.Commands;");
        builder.AppendLine("using Rhino.UI;");
        builder.AppendLine("namespace YourPluginName.Commands");
        builder.AppendLine($"{{");

        // Add Each Rhino Command from the CommandCache
        foreach (var methodInfo in _commandCache)
        {
            CreateRhinoCommandInCSharpCode(methodInfo.Key, methodInfo.Value, ref builder);
        }

        builder.AppendLine($"}}");

        // Compile the Assembly
        var compileResult = provider.CompileAssemblyFromSource(compilerParameters, builder.ToString());
        if (compileResult.Errors.HasErrors)
        {
            var errors = new StringBuilder("Compiler Errors :\r\n");
            foreach (CompilerError error in compileResult.Errors)
            {
                errors.AppendFormat("Line {0},{1}\t: {2}\n", error.Line, error.Column, error.ErrorText);
            }

            ErrorLog.WriteInfoToEventLog($"Compile Result: \n{errors}");
        }

        return compileResult.CompiledAssembly;
    }

    public static void CreateRhinoCommandInCSharpCode(Guid id, CommandInformation information, ref StringBuilder builder)
    {
        var methodInfo = information.Method;
        // Create a resource manager to retrieve resources.
        string english = information.Method.Name.ToString();//Resources.Strings.ResourceManager.GetString(information.CommandIt.LocalizedCommand, CultureInfo.CreateSpecificCulture("en"));
        string localized = information.Method.Name.ToString() + "ES"; //Resources.Strings.ResourceManager.GetString(information.CommandIt.LocalizedCommand, CultureInfo.CreateSpecificCulture("es"));

        var commandPrefix = @"mg";

        builder.AppendLine($"\t[Guid(\"{id}\")]");
        builder.AppendLine($"\tpublic class MG{methodInfo.Name}Command : Command");
        builder.AppendLine($"\t{{");
        builder.AppendLine($"\tstatic MG{methodInfo.Name}Command _instance;");
        builder.AppendLine($"\tpublic MG{methodInfo.Name}Command()");
        builder.AppendLine($"\t{{");
        builder.AppendLine($"\t_instance = this;");
        builder.AppendLine($"\t}}");
        builder.AppendLine($"\tpublic static MG{methodInfo.Name}Command Instance");
        builder.AppendLine($"\t{{");
        builder.AppendLine($"\t    get {{ return _instance; }}");
        builder.AppendLine($"\t}}");
        builder.AppendLine($"\tpublic override string EnglishName");
        builder.AppendLine($"\t{{");
        builder.AppendLine($"\tget {{ return \"{commandPrefix}{english}\"; }}");
        builder.AppendLine($"\t}}");
        builder.AppendLine($"\tpublic override string LocalName");
        builder.AppendLine($"\t{{");
        builder.AppendLine($"\tget {{ return \"{commandPrefix}{localized}\"; }}");
        builder.AppendLine($"\t}}");
        builder.AppendLine($"\tprotected override Result RunCommand(RhinoDoc doc, RunMode mode)");
        builder.AppendLine($"\t{{");
        builder.AppendLine($"\tvar id = new Guid(\"{id}\");");
        builder.AppendLine($"\tYourPluginName.Helpers.RhinoCommandHelper.InvokeCommand(id);");
        builder.AppendLine($"\treturn Result.Success;");
        builder.AppendLine($"\t}}");
        builder.AppendLine($"}}");
    }

Hope this helps, let me know if you need anything explained.

– Jason


(Caglar Aydin) #7

Thank you @jason_stevenson!