在进行Rhino插件开发时,经常会遇到需要频繁重启Rhino以加载新编译的插件的问题。这不仅耗时,而且降低了开发效率。为了解决这个问题,可以利用Managed Extensibility Framework (MEF) 来创建一个插件加载器,从而实现插件的热加载,即在不重启Rhino的情况下加载新的插件。本文将详细介绍如何通过创建RhinoCommon插件和MEF插件来实现这一目标。
本文的主要目标是介绍一种方法,通过这种方法,可以:
刚开始学习使用C#进行RhinoCommon插件开发。在参考了Rhino官方的插件开发指南后,开始编写自己的插件,涉及到更复杂的几何操作。然而,在开发过程中,遇到了一个问题:每次重新编译并尝试运行时,都需要启动Rhino并重新加载插件。由于代码不够精细,这个过程涉及到许多尝试和错误,浪费了宝贵的时间和耐心。
为了加快开发过程,想到了一个主意:创建一个RhinoCommon插件,它可以加载一个MEF插件。这样,需要编写:
需要编写三个项目:
项目名称 | 引用 |
---|---|
RhinoPluginLoader | RhinoCommon.dll, RhinoPluginContract.dll, System.ComponentModel.Composition |
RhinoPluginMakeLine | RhinoCommon.dll, RhinoPluginContract.dll, System.ComponentModel.Composition |
RhinoPluginContract | RhinoCommon.dll |
注意:RhinoCommon.dll的引用需要设置为Copy local = false,而RhinoPluginContract.dll的引用需要设置为Copy local = true。
在开始之前,请确保理解了Rhino官方的插件开发指南。现在,开始第一个项目,在Visual Studio中创建一个新项目,并选择已安装的模板:Rhino->RhinoCommon Plug-In。
设置以下名称:
向导结束后,将得到以下文件:
现在编辑LoadPlugin.cs文件:
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
public class LoadPlugin : Command
{
[Import(typeof(IPlugin))]
public IPlugin Plugin;
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;
}
// MEF - Managed Extensibility Framework
var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.Load(System.IO.File.ReadAllBytes(plugin_name))));
// 为了避免dll锁定文件->System.IO.File.ReadAllBytes(plugin_name)
using (CompositionContainer container = new CompositionContainer(catalog))
{
try
{
container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
RhinoApp.WriteLine(compositionException.ToString());
return Rhino.Commands.Result.Cancel;
}
// 执行插件接口的Run方法
Result result = Plugin.Run(doc, mode);
return result;
}
}
}
设置上述表格中显示的引用,并构建解决方案[F6]。
构建解决方案后,在Rhino中安装插件,操作如下:Tools->Options->Rhino Options,[Install...],然后选择RhinoPluginLoader.rhp文件。或者最简单的方法:将RhinoPluginLoader.rhp文件从文件资源管理器拖放到Rhino中。
将来在Rhino命令中,输入:
LoadPlugin [ENTER] File name of plugin to run: c:\OurMefDrawingPlugin.dll [ENTER]
但现在还不要尝试,因为还需要添加两个项目!
这是执行所有绘图工作的地方。创建一个新项目,选择Class Library模板,将项目名称设置为:
RhinoPluginMakeLine
在这种情况下,只为演示目的而使用模板代码。但在情况下,有一段代码可以以参数化的方式生成无人机的机身,而不需要在Rhino绘图区域中点击。
这里需要强调的是MEF导出子句,以指示这个类将被导出到组合模型中,使用契约接口类型IPlugin。
namespace RhinoPluginMakeLine
{
[Export(typeof(IPlugin))]
public class CodeEjecutor : IPlugin
{
public Result Run(RhinoDoc doc, RunMode mode)
{
// TODO: 从这里开始修改命令行为。
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;
}
}
}
在MEF中,类名无关紧要,但它必须派生自IPlugin。从未直接使用CodeEjecutor类,只通过接口使用。
要连接这两个项目,需要一个管道,在MEF术语中称为契约。导入和导出的部分需要使用这个契约进行相互通信。契约可以是一个接口或一个预定义的数据类型,如string。
创建一个新项目,选择Class Library模板,将项目名称设置为:
RhinoPluginContract
using Rhino;
// 记得添加对:rhinocommon.dll的引用,设置"Copy Local" = False
using Rhino.Commands;
namespace RhinoPluginContractInterface
{
public interface IPlugin
{
Result Run(Rhino.RhinoDoc doc, Rhino.Commands.RunMode mode);
}
}
设置上述表格中显示的引用,并构建解决方案[F6]。
现在有了三个项目,是时候运行它们了:
安装Rhp文件后,在Rhino命令中输入:
LoadPlugin [ENTER] File name of plugin to run: c:\ all the path\RhinoPluginMakeLine.dll [ENTER]
提示:在开发复杂的插件时,为了避免编写DLL名称,可以取消注释RunCommand中的以下行:
//
Uncomment to load a default dll name //
plugin_name = @"D:\the full path\RhinoPluginMakeLine.dll";
;
由于Rhino永久打开,编译和尝试非常快,减少到:
如果失去了正常的调试方式,那么有两个选择:
RhinoApp.WriteLine("Is valid:{0}", myCurve.IsValid);