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
Plugin class name: PluginLoader
Command class name: LoadPlugin
(this command name doesn´t exist in Rhinoceros)
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:
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).
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
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();
string plugin_name = gs.StringResult().Trim();
if (string.IsNullOrEmpty(plugin_name))
{
Rhino.RhinoApp.WriteLine("Plugin name cannot be blank.");
return Rhino.Commands.Result.Cancel;
}
if (!System.IO.File.Exists(plugin_name))
{
Rhino.RhinoApp.WriteLine("File not exist !!!");
return Rhino.Commands.Result.Cancel;
}
var catalog = new AggregateCatalog
(new AssemblyCatalog(Assembly.Load(System.IO.File.ReadAllBytes(plugin_name))));
CompositionContainer container = new CompositionContainer(catalog);
try
{
container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
RhinoApp.WriteLine(compositionException.ToString());
return Rhino.Commands.Result.Cancel;
}
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.
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
.
namespace RhinoPluginMakeLine
{
[Export(typeof(IPlugin))]
public class CodeEjecutor : IPlugin
{
public Result Run(RhinoDoc doc, RunMode mode)
{
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
.
using Rhino;
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]
Tip: When working in a complicated plugin, to avoid writing the DLL name, uncomment the line of RunCommand
:
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:
- [F6] Build Solution in Visual Studio.
- Click on Rhino.
- [ENTER] and the last command will be reloaded.
- 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:
RhinoApp.WriteLine("Is valid:{0}", myCurve.IsValid);
I hope you find it useful, for me it was very useful.