32动态模型更新
动态模型更新
动态模型更新为Revit API应用程序提供了修改Revit模型的能力,作为对模型中发生的更改(当这些更改即将在事务结束时提交时)的反应。Revit API应用程序可以通过实现IUpdater接口并将其注册到UpdaterRegistry类来创建更新程序。注册包括指定模型中的哪些更改应该触发更新程序。
Pages in this section 本节中的页面
- 实现IUpdater
- Execute方法
- 注册更新程序
- Exposure to End-User
实现IUpdater
IUpdater接口要求实现以下5个方法:
- GetUpdaterId()-此方法应为更新程序返回一个全局唯一的ID,包括应用程序ID和此更新程序的ID。此方法在更新程序注册期间调用一次。
- GetUpdateName()-如果在运行时更新程序有问题,则返回一个名称,通过该名称可以向用户标识更新程序。
- GetAdditionalInformation()-此方法应返回辅助文本,Revit将使用该文本在未加载更新程序时通知最终用户。
- GetChangePriority()-此方法标识更新程序将执行的更改的性质。它用于标识更新程序的执行顺序。此方法在更新程序注册期间调用一次。
- Execute()-这是Revit将调用以执行更新的方法。有关Execute()方法的更多信息,请参见下一节。
如果一个文档被一个Updater修改了,那么这个文档将存储Updater的唯一ID。如果用户稍后打开文档,但Updater不存在,则Revit将警告用户,以前编辑文档的第三方Updater不可用,除非将Updater标记为可选。默认情况下,Updater是非可选的,只有在必要时才应使用可选Updater。
下面的代码是实现IUpdater接口(为新添加的墙更改WallType)并在OnStartup()方法中注册更新程序的简单示例。它演示了创建和使用Updater的所有关键方面。
代码区域25-1:实现IUpdater的示例
1 | public class WallUpdaterApplication : Autodesk.Revit.UI.IExternalApplication |
Execute方法
Execute()方法的目的是允许你的Updater对文档的更改做出反应,并做出适当的关联。此方法由Revit在文档事务结束时调用,其中添加、更改或删除了与此Updater的UpdateTrigger匹配的元素。由于其他Updater所做的更改,该方法可能会为同一事务调用多次。更新程序在DocumentChanged事件之前调用,因此此事件将包含所有Updater所做的更改。
在调用此方法期间对文档所做的所有更改都将成为调用事务的一部分,并为撤消和重做操作进行维护。在实现此方法时,您可能不会打开任何新事务(将抛出异常),但可以根据需要使用子事务。
虽然它也可以用于更新文档外部的数据,但这些更改不会成为原始事务的一部分,并且在原始事务撤消或重做时不会受到撤消或重做的影响。如果确实使用此方法修改文档外部的数据,则还应订阅DocumentChanged事件,以便在撤消或重做原始事务时更新数据。
变更范围
Execute()方法有一个UpdaterData参数,它提供执行更新所需的所有必要数据,包括文档和有关触发更新的更改的信息。三个基本方法(GetAddedElementIds()、GetDeletedElementIds()和GetModifiedElementIds())标识触发更新的元素。Updater还可以使用IsChangeTriggered()方法来检查特定的更改是否触发了更新。
禁止和警告性变更
执行Updater时不能调用以下方法,因为它们会在元素之间引入交叉引用。(当这些更改与工作集操作结合使用时,可能导致文档损坏)。当更新程序尝试调用以下任何方法时,将引发ForbiddenForDynamicUpdateException:
- Autodesk.Revit.DB.ViewSheet.AddView()
- Autodesk.Revit.DB.Document.LoadFamily(Autodesk.Revit.DB.Document、Autodesk.Revit.DB.IFamilyLoadOptions)
- AreaReinforcement.Create()
- PathReinforcement.Create()
除了上面列出的禁用方法外,其他要求文档处于无事务状态的API方法也不能被调用。这些方法包括但不限于Save()、SaveAs()、Close()、LoadFamily()等。有关详细信息,请参阅相应方法的文档。
还禁止从更新程序的Execute()方法内调用UpdaterRegistry类,例如RegistryUpdater()或AddTrigger()。调用任何UpdaterRegistry方法都将引发异常。此规则的一个例外是UpdaterRegistry.UnregisterUpdater()方法,只要要注销的更新程序不是当前正在执行的更新程序,就可以在执行更新程序期间调用该方法。
尽管在执行Updater期间允许使用以下方法,但当调用的结果是建立元素之间的交叉引用时,它们也可以抛出ForbiddenForDynamicUpdateException。其中一个例子是创建一个与现有面墙相交的面墙,因此这两个面墙必定连接在一起。从Updater调用这些方法时要小心:
- Autodesk.Revit.Creation.ItemFactoryBase.NewFamilyInstances2()
- Autodesk.Revit.Creation.ItemFactoryBase.NewFamilyInstance(Autodesk.Revit.DB.XYZ, Autodesk.Revit.DB.FamilySymbol, Autodesk.Revit.DB.Element,Autodesk.Revit.DB.Structure.StructuralType)
- Autodesk.Revit.Creation.Document.NewFamilyInstance(Autodesk.Revit.DB.XYZ, Autodesk.Revit.DB.FamilySymbol, Autodesk.Revit.DB.Element, Autodesk.Revit.DB.Level, Autodesk.Revit.DB.Structure.StructuralType)
- Autodesk.Revit.DB.FaceWall.Create()
因此,应该注意的是,如果修改它们将是有效的,那么删除和重新创造现有元素应该是有效的。如果删除元素可能是一个简单的解决方案,它不仅会影响Revit的性能,但它将销毁任何引用以“重新创建”其他元素的对象。用户可能会失去工作,因为他们已经完成了限制并宣布了问题中的元素。
管理变更
更新程序需要能够处理使用它们时可能出现的复杂问题,可能需要协调对元素的后续更改。由更新器修改的元素可能在更新器下一次被调用时改变,并且那些改变可能影响由更新器修改的信息。例如,元素可以由用户显式编辑,或者由于再生触发的传播更改而隐式编辑。
同一个元素也可能被另一个更新器修改,甚至可能在同一个事务中。虽然对完全相同的数据进行显式更改会被跟踪和禁止,但间接或传播的更改仍然是可能的。也许最复杂的情况是,用户和/或同一更新程序可以在文件的不同版本中更改元素。在用户重新加载最新图元或将其保存到中心图元后,将从其他文件中引入修改后的目标图元,更新程序将需要协调更改。
同样重要的是要认识到,当文档与中心文件同步时,元素的ElementId可能会受到影响。如果新元素已添加到同一文件的两个版本中,并且在两个位置使用相同的ElementId,则在将文件同步到中央数据库时,将进行协调。出于这个原因,当使用更新器交叉引用另一个元素中的一个元素时,它们应该使用Element.UniqueId,这可以保证是唯一的。
另一个需要考虑的问题是,如果更新器将某些数据(即作为参数)附加到元素,则它不仅必须确保在添加该数据的元素中维护该信息,而且还必须在该元素通过复制/粘贴或组传播复制时协调数据。例如,如果更新程序将参数“钢筋的总重量”添加到钢筋主体,则该参数及其值将复制到复制的钢筋主体,即使钢筋本身可能未与主体一起复制。在这种情况下,更新程序需要确保在新复制的钢筋主体中重置参数值。
注册Updater
Updater必须注册,以便在模型更改时得到通知。应用程序级别的UpdaterRegistry类提供了注册/取消注册以及操作为Updater设置的选项的能力。Updater可以从任何API回调中注册,并且可以注册为应用程序范围或特定于文档,这意味着它们将仅由对指定文档的更改触发。为了使用UpdaterRegistry功能,Revit附加模块必须在清单文件中注册,并且UpdaterId返回的ID。任何更新程序的GetAddInId()(从GetUpdaterId()获得)必须与附加模块清单文件中的AddInId字段匹配。外接程序不能添加、删除或修改不属于它的更新程序。
触发
除了调用UpdaterRegistry.RegisterUpdater()方法之外,Updater还应该通过AddTrigger()方法添加一个或多个更新触发器。这些触发器向UpdaterRegistry指示哪些事件应该触发Updaters Execute()方法运行。它们可以在应用程序范围内设置,也可以应用于特定文档中所做的更改。更新触发器是通过将更改范围和更改类型配对来指定的。
变更范围是这两种之一:
- 文档中元素ID的显式列表-只有对这些元素的更改才会触发Updater
- 通过ElementFilter传递的元素的隐式列表-每个更改的元素都将针对过滤器运行,如果有任何通过,则会触发Updater
有几个选项可用于更改类型。ChangeTypes从Element类的静态方法中获得。
- Element addition - 通过Element.GetChangeTypeElementAddition()
- Element deletion - 通过Element.GetChangeTypeElementDeletion()
- Change of element geometry (shape or position) - 通过Element.GetChangeTypeGeometry()
- Changing value of a specific parameter - 通过Element.GetChangeTypeParameter()
- Any change of element - 通过Element.GetChangeTypeAny()。
请注意,几何图形更改可能由于多种原因而触发,例如图元类型更改、属性和参数修改、移动和旋转,或者在再生期间从其他修改的图元对图元施加的更改。
还要注意的是,最后一个选项,任何元素的更改,只会触发Updater修改预先存在的元素,而不会触发Updater修改新添加或删除的元素。此外,当对实例使用此触发器时,只有对其类型的某些修改才会触发Updater。影响实例本身的更改(如修改实例的几何体)将触发Updater。但是,不直接修改实例并且不会导致对实例进行任何可识别的更改的更改(例如对文本参数的更改)将不会触发实例的Updater。要根据这些更改触发,Type也必须包含在触发器的更改范围中。
执行顺序
Revit对多个Updater进行排序以按正确顺序执行的主要方法是查看给定Updater返回的ChangePriority。一个Updater报告一组更基本的元素(例如GridsLevelsReferencePlanes)的优先级,将在Updater报告由这些基本元素(例如Annotations)驱动的元素的优先级之前执行。为Updater修改的图元报告适当的更改优先级将使应用程序的用户受益:Revit不太可能由于其他Updater所做的更改而不得不再次执行Updater。
对于报告相同更改优先级的Updater,执行顺序基于UpdaterId的排序。方法UpdaterRegistry.SetExecutionOrder()允许您在任何两个注册的Updater(甚至是由其他API外接程序注册的Updater)之间设置执行顺序,只要您的代码知道这两个Updater的ID。
Exposure to End-User
当更新程序正常工作时,它们对用户是透明的。但在某些特殊情况下,Revit将向用户显示有关第三方更新程序的警告。这些消息将使用GetUpdaterName()方法的值来引用更新程序。
未安装Updater
如果文档由非可选Updater修改,然后在未安装该Updater时加载,则会显示类似于以下内容的任务对话框:
图135:缺少第三方Updater警告
更新程序执行无效操作
如果更新程序出现错误(如未处理的异常),则会显示类似于以下内容的消息,为用户提供禁用更新程序的选项:
图136:更新程序执行了无效操作
如果用户选择“取消”,则整个事务将回滚。在本章前面的墙更新程序示例中,新添加的墙被删除。如果用户选择DisableUpdater,则不再调用更新程序,但事务不会回滚。
无限循环
如果Updater陷入无限循环,Revit将通知用户并在Revit任务期间禁用更新程序。
两个Updater尝试编辑同一元素
如果Updater尝试编辑由同一事务中的另一Updater更新的元素的相同参数,或者如果Updater尝试以与另一Updater所做的更改冲突的方式编辑元素的几何图形,则Updater被取消,显示错误消息,并且用户可以选择禁用更新器。
本地不存在Updater修改的中心文档
如果用户重新加载最新版本或使用未在本地安装的Updater修改的中心文件保存到中心,则会显示一个任务对话框,为用户提供继续或取消同步的选项。此警告表示,以后将中心模型与第三方Updater一起使用时,继续操作可能会导致中心模型出现问题。