By Aaron Lu
We know Revit's parametric modeling feature allows us to change an element and all related elements will be changed automatically to keep the model consistent, e.g. move a wall, the window or door on the wall will move accordingly.
But sometimes we want to add more association relations between elements, so that we have some customized propagation behavior, e.g. when extend a wall, another wall will become shorter, or when a component in link document is moved, another component in current document will also be moved. In this kind of situation, we can use DMU (Dynamic Model Update).
What is DMU? to be simple, it is a kind of event, or we can say it is an implementation of Sub-Pub design pattern, i.e. we register a callback function and when some event happens, the callback will be invoked. In DMU, the callback function is encapsulated in an interface named IUpdater, an updater is an instance of class which implements IUpdater.
Register
First let's see the method(s) on how to register an updater, the signatures are:
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); }
Arguments and their meanings are:
Argument | Meaning |
---|---|
IUpdater updater | The instance with a callback function which will be invoked when something happens. |
bool isOptional | Whether it is optional or not, true means Revit won't care if the updater is registered or not, false means the updater is very important, when it is missing, Revit will pop up warning dialog. |
Document document | Related docuument, if specify, then the updater will only apply to this document, otherwise, it will apply to the whole Revit application. |
Implement IUpdater
Looking at the arguments of the above methods, we know that we should first create an instance of IUpdater.
How to implement IUpdater? Below is an code example:
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"; } }
Note that:
- Creation of UpdaterId: the first argument is a Guid, which is guid of the addon/plugin registering the updater, which should be the same as the ClientId or AddinId defined in .addin file. e.g. below is the addin file of an ExternalCommand, so the first argument should be c1f5f009-8ba9-4f1d-b0fb-ba41a0f69942. The second argument is the guid of the updater itself, we can create one using the built-in guid generation tool of visual studio.
<?xml version="1.0" encoding="utf-8" standalone="no"?> <RevitAddIns> <AddIn Type="Command"> <Name>CommandB</Name> <ClientId>c1f5f009-8ba9-4f1d-b0fb-ba41a0f69942</ClientId> <Assembly>D:\ADN\Test\bin\Debug\CommandB.dll</Assembly> <FullClassName>ApplicationB.CommandB</FullClassName> <VendorId>ADSK</VendorId> </AddIn> </RevitAddIns>
- Execute function is the callback function, it will be invoked when something happens, we can get enough information from its argument UpdaterData, e.g. UpdaterData.GetDocument() returns the related Document object, GetAddedElementIds() returns the ids of added elements etc.. We can do a lot of things inside the callback function, for example, move or create an element, it is totally up to us.
- We can't use Transaction inside Execute method, because Execute is already in a transaction.
ParameterUpdater _updater = new ParameterUpdater(new Guid("{E305C880-2918-4FB0-8062-EE1FA70FABD6}")); UpdaterRegistry.RegisterUpdater(_updater, true);Here, the guid is created via builtin tool of VS
Trigger
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); }Arguments and meanings:
Argument | Meaning |
---|---|
UpdaterId id | id of updater |
ElementFilter filter | ElementFilter defines a set of elements, the updater will only be triggered when something happens to those elements |
ICollection<ElementId> elements | The updater will only be triggered when something happens to specific elements by designating the ids of them |
Document document | Only apply to a document |
ChangeType change | Specify the trigger condition, e.g. when parameter changed (Element.GetChangeTypeParameter) or when geometry changed (Element.GetChangeTypeGeometry) or any changes (Element.GetChangeTypeAny) etc. |
If we want to trigger an updater when Area of an element is changed, we can write code like this:
var parameter = element.get_Parameter(BuiltInParameter.ROOM_AREA); UpdaterRegistry.AddTrigger(_updater.GetUpdaterId(), doc,
new List<ElementId>() { new ElementId(197280)},
Element.GetChangeTypeParameter(parameter));
This is the whole DMU api usage workflow, when we change Area of element 197280, the Execute method will be invoked. Of course, we can do anything we want, but make sure there is no dead loop :-)
Note: we can use UpdaterRegistry.RemoveAllTriggers or UpdaterRegistry.RemoveDocumentTriggers to remove triggers and UpdaterRegistry.UnregisterUpdater to unregister updaters