外部服务框架(ExternalService Framework)主要是Revit用来把一部分功能委托给外部程序来完成。方式是定义一个自定的Server接口,让外部程序实现并注册。
下面是主要的类图:
这里我们主要关注的是IExternalServer接口,即如何去实现它,并注册它的实例。
一个典型的外部服务使用的步骤大概如下:
- 定义一个类,实现服务器(External Server)接口(里面包含回调函数)。
- 创建这个类的实例,并把这个实例注册到Revit里面。
- 当我们调用Revit相关的功能的时候,这个实例的回调函数就会被执行。
这是不是有点似曾相识?没错,跟IExternalCommand的机制一样:
- 定义一个类,实现IExternalCommand接口。
- 使用.Addin配置文件注册这个类,Revit会为我们创建一个菜单(即实例化并注册)。
- 点击这个菜单,接口类的Execute函数就会被执行(即调用相关功能)。
下面是一些它和IExternalCommand的区别:
- 外部服务分为SingleServer和MultiServer。SingleServer服务一次只执行一个Server,MultiServer服务一次可以按顺序执行一个到多个Server。显然,IExternalCommand相当于SingleServer。
- 外部服务可以有多个Server,不管是SingleServer还是MultiServer,而出于激活状态的Server才能在服务执行的时候被执行。对于SingleServer服务来讲,只有一个Server可以处于激活状态,对于MultiServer服务来讲,可以有多个Server被激活。一般来讲,激活可以通过Revit界面或者通过程序来完成。
这与IExternalCommand的点击菜单的行为是不一样的,点击菜单相当于这里的运行了一次服务,服务再会调用相应的IExternalCommand。
所以,IExternalCommand相当于只有一个Server的SingleServer服务,而这个Server是默认激活的。
使用外部服务的整个流程是这样的:
- 首先创建一个类实现IExternalApplication
- 定义一个类,实现我们的Server接口
- 在IExternalApplication的OnStartup方法里面,创建我们Server的实例
- 然后获取对应的Service
- 注册我们创建的自定义Server
- 把我们的IExternalApplication注册到Revit
我们以PipePressureDropService 为例,看看如何实现对应的Server,这里IPipePressureDropServer是它对应的Server的接口。
实现自定义Server接口:
public class MyServer : IPipePressureDropServer
{
readonly Guid m_guid = new Guid("{DFC9FFDB-7AC8-40EE-A884-9F39094367DA}");
#region IPipePressureDropServer Members
public void Calculate(PipePressureDropData data)
{
data.Coefficient = 1.111;
data.FlowState = PipeFlowState.TransitionState;
}
public string GetHtmlDescription()
{
return "<h1>My Title</h1>";
}
public string GetInformationLink()
{
return "http://www.abc.com";
}
#endregion
#region IExternalServer Members
public string GetDescription()
{
return "My Server description";
}
public string GetName()
{
return "My Server name";
}
public System.Guid GetServerId()
{
return m_guid;
}
public ExternalServiceId GetServiceId()
{
return ExternalServices.BuiltInExternalServices.PipePressureDropService;
}
public string GetVendorId()
{
return "MyCompanyName";
}
#endregion
}
其中前半部分的是IpipePressureDropServer特有的接口函数,比如Calculate等,后面是IExternalServer用来标识这个Server的一些公共接口。
注意:GetServiceId()返回值必须和我们实现的Server对应的Service的Id是一样的。
比如我们这里GetServiceId()返回的是PipePressureDropService,那么实现的接口就是IPipePressureDropServer, 而不能是IDuctPressureDropServer,否则就会有异常抛出,像这样:Autodesk.Revit.Exceptions.ArgumentException: The server does not represent a server of avalid type to be used with the service.
注册自定义Server:
// get service id
var serviceId = ExternalServices.BuiltInExternalServices.PipePressureDropService;
// get service
ExternalService pipePressureDropService = ExternalServiceRegistry.GetService(serviceId);
var server = new MyServer();
// check if the server is already registered
if (pipePressureDropService.IsRegisteredServerId(server.GetServerId()))
{
Autodesk.Revit.UI.TaskDialog.Show("Confirmation",
"The server with the same id has already been registered for PipePressureDropService");
return;
}
// register the server
pipePressureDropService.AddServer(server);
激活自定义Server:
我们可以通过Revit界面来激活,也可以通过程序来激活。
1. 通过Revit界面:本例中,点击“管理”>“MEP设置”>“机械设置”>选择“管道设置”里的“计算”,在“计算方法”的下拉菜单里面可以看到我们添加的Server,通过选择它来激活,如下图所示:
2. 通过程序来激活,一般来讲,我们可以调用SingleServerService的SetActiveServer或者MultiServerService的SetActiveServers,然而,各个服务的情况可能是不一样的,有可能不允许激活,也可能有另外的激活方式。本例中,上述方法是不起作用的,而需要调用相关的特殊方法,如下所示:
using (Transaction transaction = new Transaction(RevitDoc))
{
transaction.Start("Activate Custom Server");
var ductSettings = PipeSettings.GetPipeSettings(RevitDoc);
var serverInfo = new MEPCalculationServerInfo(server);
ductSettings.SetPressLossCalculationServerInfo(serverInfo);
transaction.Commit();
}
这里我们使用DuctSettings的SetPressLossCalculationServerInfo方法来激活的,激活之后,在上面的界面上就可以看到自定义的Server现在已经是默认的了。
注意:某些激活操作可能需要在事务(Transaction)里面进行,比如本例。
调用注册程序的地方:
我们该在什么地方放置上述代码呢?我想你已经猜到了,对,我们可以放在IExternalCommand的Execute函数里,当然也可以在IExternalApplication的OnStartup里。
主动执行Server:
Revit的服务大多是由它自己在特定的情况下执行的,所以一般而言,没有接口供我们直接执行一个服务,所以主动执行Server也无从谈起,至少现在不支持。