我们知道Revit的一大特色功能就是关联修改,即,一处修改处处修改,比如,在三维视图修改了墙的位置,二维视图上墙的位置也跟着变化了,同时,墙上的门窗也会跟着移动。
但有时候我们有自己的特殊需求,也想做到自定义的联动关系,比如我希望两面墙的总长度是固定的,增加一面墙长度之后,另外一面墙会跟着缩减。又或者,链接文档的某个构件移动了,希望主文件的某个不相关的构件也移动。这个时候,Revit本身的关系已经不足以满足我们的需求,我们就可以使用DMU(Dynamic Model Update)了。
什么是DMU呢?简单的说,是一种事件,或者叫发布-订阅(Sub-Pub)模式,即我们在Revit里面注册一个回调函数,告诉Revit当某种类型的变动发生的时候,就调用该回调函数。
注册
先来看怎么注册。是通过UpdaterRegistry.RegisterUpdater函数,它的签名如下:
public class UpdaterRegistry : IDisposable
{
public static void RegisterUpdater(IUpdater updater);
public static void RegisterUpdater(IUpdater updater, bool isOptional);
public static void RegisterUpdater(IUpdater updater, Document document);
public static void RegisterUpdater(IUpdater updater, Document document, bool isOptional);
}
它的几个参数的含义见下表:
参数 | 含义 |
---|---|
IUpdater updater | 处理事件的对象,需要实现一系列的函数 |
bool isOptional | 是否为可选,如果设为true则表示对Revit来讲可有可无,false则表示很重要,一定需要它,如果Revit启动的时候,或者打开某个文档的时候,没有找到需要的updater,Revit会弹出警告框 |
Document document | 对应的文档,如果不指定,则表示该updater在应用级别起作用,和文档无关。如果指定了,则表示只在该文档起作用,其他文档都和它无关 |
实现IUpdater接口
从上面的参数中可以看到,我们需要创建一个IUpdater接口的对象,那么再来看怎么实现IUpdater接口,下面是一个例子程序:
public class ParameterUpdater : IUpdater
{
UpdaterId _uid;
public ParameterUpdater(Guid guid)
{
_uid = new UpdaterId(new AddInId(
new Guid("c1f5f009-8ba9-4f1d-b0fb-ba41a0f69942")), // addin id
guid); // updater id
}
public void Execute(UpdaterData data)
{
Func<ICollection<ElementId>, string> toString = ids => ids.Aggregate("", (ss, id) => ss + "," + id).TrimStart(',');
var sb = new StringBuilder();
sb.AppendLine("added:" + toString(data.GetAddedElementIds()));
sb.AppendLine("modified:" + toString(data.GetModifiedElementIds()));
sb.AppendLine("deleted:" + toString(data.GetDeletedElementIds()));
TaskDialog.Show("Changes", sb.ToString());
}
public string GetAdditionalInformation()
{
return "N/A";
}
public ChangePriority GetChangePriority()
{
return ChangePriority.FreeStandingComponents;
}
public UpdaterId GetUpdaterId()
{
return _uid;
}
public string GetUpdaterName()
{
return "ParameterUpdater";
}
}
- UpdaterId的创建,第一个参数是一个Guid,该Guid是注册updater的插件的guid,必须和.addin文件里面的ClientId或AddinId一致,可能是Command也可能是Application,如下面就是该Updater的Command入口,所以第一个参数应该是c1f5f009-8ba9-4f1d-b0fb-ba41a0f69942,第二个参数是该Updater的Guid,可以使用Visual Stuido自带的工具创建一个新的。
<?xml version="1.0" encoding="utf-8" standalone="no"?> <RevitAddIns> <AddIn Type="Command"> <Name>CommandB</Name> <ClientId><span style="font-family: Arial, Helvetica, sans-serif;">c1f5f009-8ba9-4f1d-b0fb-ba41a0f69942</span><span style="font-family: Arial, Helvetica, sans-serif;"></ClientId></span> <Assembly>D:\ADN\Test\bin\Debug\CommandB.dll</Assembly> <FullClassName>ApplicationB.CommandB</FullClassName> <VendorId>ADSK</VendorId> </AddIn> </RevitAddIns>
- Execute函数是主要的回调函数,它会在DMU触发的时候被调用,通过该函数的参数UpdaterData,我们可以获取很多内容,比如UpdaterData..GetDocument()可以获取Document, GetAddedElementIds()可以获取被添加的元素id。我们可以在此函数里面做很多事情,例如,修改其他元素实现关联修改。这完全可以取决于我们自己。
- 在Execute函数里面不能使用Transaction,因为它本身就在Transaction里面
ParameterUpdater _updater = new ParameterUpdater(new Guid("{E305C880-2918-4FB0-8062-EE1FA70FABD6}"));
UpdaterRegistry.RegisterUpdater(_updater, true);
这里的Guid是使用VS自带的工具创建的
指定触发条件
public class UpdaterRegistry : IDisposable
{
public static void AddTrigger(UpdaterId id, ElementFilter filter, ChangeType change);
public static void AddTrigger(UpdaterId id, Document document, ElementFilter filter, ChangeType change);
public static void AddTrigger(UpdaterId id, Document document, ICollection<ElementId> elements, ChangeType change);
}
下表是它各个参数的意义:
参数 | 含义 |
---|---|
UpdaterId id | Updater的id |
ElementFilter filter | 针对特定的元素,这些元素满足该filter的条件 |
ICollection<ElementId> elements | 只针对这些特定的元素 |
Document document | 只针对某个文档 |
ChangeType change | 某种特定的修改才会触发,例如,当参数变化时(Element.GetChangeTypeParameter),或者当几何变化时(Element.GetChangeTypeGeometry),或任何变化(Element.GetChangeTypeAny)等等 |
如果我们想要某个Area的面积改变时触发,可以这样来写:
var parameter = element.get_Parameter(BuiltInParameter.ROOM_AREA);
UpdaterRegistry.AddTrigger(_updater.GetUpdaterId(), doc, new List<ElementId>() { new ElementId(197280)}, Element.GetChangeTypeParameter(parameter));
至此,整个DMU的流程代码都做好了,当我们改变id为197280的Area的面积的时候,Area的参数“Area"(面积)就会改变,Execute函数就会被触发。我们就可以在触发函数里面做任何我们想做的事情了。当然,还要注意,不要造成死循环了 :)
注:如果希望卸载Trigger,可以调用UpdaterRegistry.RemoveAllTriggers或者UpdaterRegistry.RemoveDocumentTriggers,卸载updater可以调用UpdaterRegistry.UnregisterUpdater