Rhino插件开发:使用MEF提高开发效率

在进行Rhino插件开发时,经常会遇到需要频繁重启Rhino以加载新编译的插件的问题。这不仅耗时,而且降低了开发效率。为了解决这个问题,可以利用Managed Extensibility Framework (MEF) 来创建一个插件加载器,从而实现插件的热加载,即在不重启Rhino的情况下加载新的插件。本文将详细介绍如何通过创建RhinoCommon插件和MEF插件来实现这一目标。

主要目标

本文的主要目标是介绍一种方法,通过这种方法,可以:

  • 避免每次重新编译插件时都需要重启Rhino。
  • 加快代码开发和测试过程。
  • 展示一个简单的MEF插件技术。

简介

刚开始学习使用C#进行RhinoCommon插件开发。在参考了Rhino官方的插件开发指南后,开始编写自己的插件,涉及到更复杂的几何操作。然而,在开发过程中,遇到了一个问题:每次重新编译并尝试运行时,都需要启动Rhino并重新加载插件。由于代码不够精细,这个过程涉及到许多尝试和错误,浪费了宝贵的时间和耐心。

为了加快开发过程,想到了一个主意:创建一个RhinoCommon插件,它可以加载一个MEF插件。这样,需要编写:

  • 一个Rhino插件。
  • 一个MEF插件,用于执行绘图工作。
  • 一个契约接口DLL,由RhinoCommon插件和MEF插件引用,作为它们之间的管道。

Visual Studio项目

需要编写三个项目:

项目名称 引用
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。

RhinoPluginLoader项目

在开始之前,请确保理解了Rhino官方的插件开发指南。现在,开始第一个项目,在Visual Studio中创建一个新项目,并选择已安装的模板:Rhino->RhinoCommon Plug-In。

设置以下名称:

  • 项目名称:RhinoPluginLoader
  • 插件类名称:PluginLoader
  • 命令类名称:LoadPlugin(这个命令名称在Rhino中不存在)

向导结束后,将得到以下文件:

  • PluginLoader.cs - 继承自Rhino.PlugIns.PlugIn
  • LoadPlugin.cs - 继承自Rhino.Commands.Command

现在编辑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插件

构建解决方案后,在Rhino中安装插件,操作如下:Tools->Options->Rhino Options,[Install...],然后选择RhinoPluginLoader.rhp文件。或者最简单的方法:将RhinoPluginLoader.rhp文件从文件资源管理器拖放到Rhino中。

运行Rhino插件

将来在Rhino命令中,输入:

LoadPlugin [ENTER] File name of plugin to run: c:\OurMefDrawingPlugin.dll [ENTER]

但现在还不要尝试,因为还需要添加两个项目!

MEF绘图插件

这是执行所有绘图工作的地方。创建一个新项目,选择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永久打开,编译和尝试非常快,减少到:

  • [F6] 在Visual Studio中构建解决方案。
  • 点击Rhino。
  • [ENTER],最后一个命令将被重新加载。
  • 看看发生了什么...,更改代码...,[F6]然后再次开始。

如果失去了正常的调试方式,那么有两个选择:

  • Debug->Attach to a running process(在VS Express版本中不可用)。
  • 或者必须在Rhino控制台打印许多调试消息:
RhinoApp.WriteLine("Is valid:{0}", myCurve.IsValid);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485