Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Managed Extensibility Framework (MEF) Plugin for Rhinoceros RhinoCommon

0.00/5 (No votes)
8 Apr 2016CPOL4 min read 13.5K   283  
Load a Rhinoceros plugin through MEF

Visual Studio 2010 Projects

Main Objectives

  • Avoid restarting Rhino every time you recompile a plugin
  • Speed up code and try a development process
  • Show a simple Mef plugin technique

Introduction

I have just started learning RhinoCommon using C#. So, I started writing my first plugin, taking this page for reference: Rhinoceros first Plugin for Windows

After that, I began to write my own plugin, involving more complex geometry work. Then I encountered a problem, everytime that recompiled and made a run attempt, the Rhinoceros had to be started, and the plugin had to be reloaded.

As my code is not very punctilious, the process involves many try and error attempts, wasting a lot of my precious time and my short patience.

Looking for a way to speed up the development process, I had the next idea:

Create a RhinoCommon plugin that would load a Managed Extensibility Framework(MEF) plugin.

So you must to write:

  • A Rhinoceros plugin
  • An MEF Plugin that will make our drawing work
  • A contract interface DLL, referenced by both, that serves as a pipeline between the Rhinocommon plugin and our MEF Plugin

The Visual Studio Projects

Must Write Three Projects

Project Name Reference to
RhinoPluginLoader RhinoCommon.dll, RhinoPluginContract.dll, System.ComponentModel.Composition;
RhinoPluginMakeLine RhinoCommon.dll, RhinoPluginContract.dll, System.ComponentModel.Composition;
RhinoPluginContract RhinoCommon.dll

The RhinoCommon.dll must be referenced wit Copy local = false. The RhinoPluginContract.dll must be referenced with Copy local = true.

RhinoPluginLoader Project

The Load Plugin Command

Before you proceed, be sure to understand the Rhinoceros first Plugin for Windows guide. Now begin with our first project, in Visual Studio, make a new project and select installed template:
Rhinoceros->RhinoCommon Plug-In, (If you don´t have the template, install it from: RhinoCommon templates for v5).

Set the next names:

Project Name: RhinoPluginLoader

Image 1

Plugin class name: PluginLoader
Command class name: LoadPlugin (this command name doesn´t exist in Rhinoceros)

Image 2

When the wizard ends, we have the next files:

Files: Define: Derived From:
PluginLoader.cs Rhinoceros Plugin definitions derived from Rhino.PlugIns.PlugIn
LoadPlugin.cs Rhinoceros Command definitions derived from Rhino.Commands.Command

The Code

Now edit the LoadPlugin.cs file:

First, create an MEF port where the pipeline interface will be connected:

C#
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

public class LoadPlugin : Command
    {
        [Import(typeof(IPlugin))]
        public IPlugin Plugin;
		
	.......

Then the code to load our plugin in a Catalog:
(Change the RunCommand template generated code by next).

C#
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
      {
          // Prompt the user to enter a Plugin name
          Rhino.Input.Custom.GetString gs = new Rhino.Input.Custom.GetString();
          gs.SetCommandPrompt("File name of plugin to run:");
          gs.AcceptNothing(true);
          gs.Get();
          if (gs.CommandResult() != Rhino.Commands.Result.Success)
              return gs.CommandResult();

          // Was a plugin named entered?
          string plugin_name = gs.StringResult().Trim();
          if (string.IsNullOrEmpty(plugin_name))
          {
              Rhino.RhinoApp.WriteLine("Plugin name cannot be blank.");
              return Rhino.Commands.Result.Cancel;
              //Uncomment to load a default dll name
              // plugin_name = @"D:\the full path\PluginMakeLine.dll";
          }
          // Check if file exist?
          if (!System.IO.File.Exists(plugin_name))
          {
              Rhino.RhinoApp.WriteLine("File not exist !!!");
              return Rhino.Commands.Result.Cancel;
          }

          //--------------------------------------
          // MEF - Managed Extensibility Framework
          //--------------------------------------
          var catalog = new AggregateCatalog
          (new AssemblyCatalog(Assembly.Load(System.IO.File.ReadAllBytes(plugin_name))));
          // To avoid dll locked file->System.IO.File.ReadAllBytes(plugin_name)

          // Create a composition container
          CompositionContainer  container = new CompositionContainer(catalog);

          try
          {
              container.ComposeParts(this);
          }
          catch (CompositionException compositionException)
          {
              RhinoApp.WriteLine(compositionException.ToString());
              return Rhino.Commands.Result.Cancel;
          }

          // Execute the Run method of Plugin interface
          Result result = Plugin.Run(doc, mode);

          catalog.Dispose();
          container.Dispose();
          return result;
      }

Set the references that show the above table, and build the solution[F6].

Install the Rhino Plugin

After build solution, install the plugin in Rhinoceros, do: Tools->Options->Rhino Options,[Install...], and select the RhinoPluginLoader.rhp file. Or the most easy way: drag the RhinoPluginLoader.rhp from the file explorer and drop in Rhino.

Run the Rhino Plugin

In the future from the Rhino command, write: LoadPlugin [ENTER]
File name of plugin to run: c:\OurMefDrawingPlugin.dll [ENTER]

Still do not try, because we need two more projects to add!

Our MEF Drawing Plugin

This is the place where we perform all the drawing work. Create a new project choice Class Library template, set the project name to: RhinoPluginMakeLine.

In this case, only four demonstration purposes will wear the code of the template. But in my case, I have a code to generate the fusselage of a drone in a parametrical way without any click in the Rhino draw area.

Image 3

Here, it is important to highlight the MEF export clause to indicate that this class will be exported to the composition model using the contract interface type IPlugin.

C#
namespace RhinoPluginMakeLine
{
    [Export(typeof(IPlugin))]

    public class CodeEjecutor : IPlugin
    {
        public Result Run(RhinoDoc doc, RunMode mode)
        {
            // TODO: start here modifying the behaviour of your command.
            // ---          
            Point3d pt0;
            using (GetPoint getPointAction = new GetPoint())
            {
                getPointAction.SetCommandPrompt
                ("Please select the start point for a line: 1");
                if (getPointAction.Get() != GetResult.Point)
                {
                    RhinoApp.WriteLine("No start point was selected.");
                    return getPointAction.CommandResult();
                }
                pt0 = getPointAction.Point();
            }

            Point3d pt1;
            using (GetPoint getPointAction = new GetPoint())
            {
                getPointAction.SetCommandPrompt
                ("Please select the end point");
                getPointAction.SetBasePoint(pt0, true);
                getPointAction.DynamicDraw +=
                  (sender, e) => e.Display.DrawLine
                  (pt0, e.CurrentPoint, System.Drawing.Color.DarkRed);
                if (getPointAction.Get() != GetResult.Point)
                {
                    RhinoApp.WriteLine("No end point was selected.");
                    return getPointAction.CommandResult();
                }
                pt1 = getPointAction.Point();
            }
            pt0 = null;
            doc.Objects.AddLine(pt0, pt1);
            doc.Views.Redraw();                       

            return Result.Success;
        }
    }
}

Something strange in the MEF is that the name of our class is irrelevant, but it must derive form IPlugin. We never use CodeEjecutor class in a direct way, only use through the interface.

The Contract Interface

To connect the two projects, we need a pipeline, in the MEF jargon is named a Contract. The imported and exported parts need to talk between themselves using this contract. The contract can be an Interface or a predefined data type like string.

Create a new project, choice Class Library template, set the project name to: RhinoPluginContract.

C#
using Rhino; //remember add a reference to: rhinocommon.dll with "Copy Local" = False
using Rhino.Commands;

namespace RhinoPluginContractInterface
{
    public interface IPlugin
    {
        Result Run(Rhino.RhinoDoc doc, Rhino.Commands.RunMode mode);
    }

}

Set the references that show the above table, and Build the solution[F6].

Testing All

Now that we have the three projects, it is time to run all:

After installing the Rhp file, from the Rhino command, write: LoadPlugin [ENTER]
File name of plugin to run: c:\ all the path\RhinoPluginMakeLine.dll [ENTER]

Image 4

Tip: When working in a complicated plugin, to avoid writing the DLL name, uncomment the line of RunCommand:

C#
//Uncomment to load a default dll name //
plugin_name = @"D:\the full path\RhinoPluginMakeLine.dll";

Points of Interest

As the Rhinoceros is permanently open, the compile and try is very fast, is reduced to:

  1. [F6] Build Solution in Visual Studio.
  2. Click on Rhino.
  3. [ENTER] and the last command will be reloaded.
  4. See what happens..., Change the code..., [F6] and begin again.

If you lost the normal way of debug, then you have two alternatives:

Debug->Attach to a running process (is NOT disponible in VS Express editions).
Or you must print many debug messages to the Rhino console:
Example:

C#
RhinoApp.WriteLine("Is valid:{0}", myCurve.IsValid);

I hope you find it useful, for me it was very useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)