故障发布和处理

Revit API提供了在发生用户可见的问题时发布故障以及响应由Revit或Revit附加模块发布的故障的功能。

本节中的页面

  • 发布故障
  • 处理故障

发布故障

要使用故障发布机制报告问题,需要执行以下步骤:

  1. 在ExternalApplication的OnStartup()调用过程中,必须在FailureValidationRegistry中定义并注册Revit中尚未定义的新故障。
  2. 从BuiltInFailures类或使用与FailureDefinition相关的类从预注册的自定义故障中查找故障定义ID。
  3. 使用与FailureMessage相关的类设置与失败相关的选项和详细信息,将失败发布到有问题的文档。

定义和注册故障

在Revit应用程序启动期间,必须通过创建一个FailureDefinition对象来定义和注册Revit中的每个可能的故障,该对象包含有关故障的一些永久性信息,例如标识、严重性、基本描述、解决方法类型和默认解决方法。

下面的示例创建了两个新的故障,一个警告和一个错误,可用于太高的墙。在这个例子中,它们与一个更新程序一起使用,后者将执行失败发布(在本章后续的代码示例中)。FailureFailitionIds保存在Updater类中,因为在发布失败时需要它们。以下各节将更详细地解释FailureDefinition. conf FailureDefinition()方法参数。

代码区域26-1:定义和注册故障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
WallWarnUpdater wallUpdater = new WallWarnUpdater();
UpdaterRegistry.RegisterUpdater(wallUpdater);
ElementClassFilter filter = new ElementClassFilter(typeof(Wall));
UpdaterRegistry.AddTrigger(wallUpdater.GetUpdaterId(), filter, Element.GetChangeTypeGeometry());

// define a new failure id for a warning about walls
FailureDefinitionId warnId = new FailureDefinitionId(new Guid("FB4F5AF3-42BB-4371-B559-FB1648D5B4D1"));

// register the new warning using FailureDefinition
FailureDefinition failDef = FailureDefinition.CreateFailureDefinition(warnId, FailureSeverity.Warning, "Wall is too big (>100'). Performance problems may result.");

FailureDefinitionId failId = new FailureDefinitionId(new Guid("691E5825-93DC-4f5c-9290-8072A4B631BC"));

FailureDefinition failDefError = FailureDefinition.CreateFailureDefinition(failId, FailureSeverity.Error, "Wall is WAY too big (>200'). Performance problems may result.");
// save ids for later reference
wallUpdater.WarnId = warnId;
wallUpdater.FailureId = failId;

故障排除Id

必须使用唯一的FailureDefinition Id作为注册FailureDefinition的键。应使用故障诊断生成工具创建每个唯一的故障诊断ID。稍后,可以使用FailureDefinition Id在FailureDefinition Registry中查找FailureDefinition,并创建和发布FailureMessages。

严重程度

注册新故障时,将指定严重性,沿着指定FailureValidationId和可向用户显示的故障文本描述。严重性决定了文档中允许的操作以及是否可以提交事务。严重性选项包括:

  • Warning - 最终用户可以忽略的故障。此严重性的故障不会阻止提交事务。当Revit需要向用户传达问题,但该问题不会阻止用户继续处理文档时,应使用此严重性
  • Error - 无法忽略的故障。如果发布了此严重性的FailureMeassage,则无法提交当前事务,除非通过适当的FailureResolution解决故障。如果问题未解决,则文档上的工作无法继续,则应使用此严重性。如果故障没有可用的预定义解决方案或这些解决方案无法解决问题,则必须中止事务以继续处理文档。强烈建议在此严重性的每个故障中至少有一个解决方案。
  • DocumentCorruption - 由于已知的文档损坏而强制事务尽快回滚的失败。当发布此严重性的故障时,不允许从文档中阅读信息。必须先回滚当前事务才能处理文档。仅当文档中存在已知的数据损坏时才使用此严重性。通常应避免这种类型的故障,除非无法防止损坏或在本地进行恢复。

定义新的FailureDefinition时,不能指定第四个严重性None。

故障解决方案

当故障可以解决时,所有可能的解决方案都应该在FailureDefinition类中预定义。这将通知Revit针对给定故障可能使用的故障解决方案。FailureDefinition包含适用于故障的解决方案类型的完整列表,包括用户可见的解决方案标题。

解决方案的数量不受限制,但从2011年Revit API开始,唯一公开的故障解决方案是DeleteElements。当指定多个分辨率时,除非使用SetDefaultResolutionType()方法显式更改,否则添加的第一个分辨率将成为默认分辨率。默认分辨率由Revit故障处理机制用于在适用时自动解决故障。Revit UI仅使用默认分辨率,但Revit附加模块可以通过Revit API使用任何适用的分辨率,并可以提供用于执行此操作的替代UI(如本章后面的“处理故障”部分所述)。

在严重性为DocumentCorruption的故障的情况下,在故障解决可能发生时,事务已经中止,因此没有什么需要解决的。因此,不应将FailureResolutions添加到严重性为DocumentCorruption的API定义的故障中。

发布故障

PostFailure()方法用于将问题通知给文档。失败将被验证,并可能在事务结束时解决。通过此方法发布的错误在解决后将不会存储在文档中。失败过帐用于处理文档的状态,该状态可能在事务结束之前发生更改,或者在有必要将解决方案推迟到事务结束时发生更改。并非外部命令遇到的所有失败都应该发布失败。如果失败与文档无关,则应使用任务对话框。例如,如果Revit UI处于无效状态,则无法执行外部命令。

若要发布故障,请使用定义自定义故障时的FailureFailitionId创建新的FailureMessage,或使用Revit API提供的BuiltInFailure。在FailureMessage对象中设置任何其他信息,如失败元素,然后调用Document.PostFailure(),传入新的FailureMessage。请注意,文档必须是可修改的,才能发布失败。

PostFailure()返回的唯一FailureMessageKey可以在事务的生命周期内存储,并用于删除不再相关的失败消息。如果相同的FailureMessage被发布两次或更多次,则返回相同的FailureMessageKey。如果发布的故障的严重性为DocumentCorruption,则返回无效的FailureMessageKey。这是因为无法取消发布DocumentCorruption失败。

下面的示例显示一个IUpdate类(在上面的“定义和注册故障”代码区域中引用),该类根据Execute()方法中接收到的信息发布新故障。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class WallWarnUpdater : IUpdater
{
static AddInId m_appId;
UpdaterId m_updaterId;
FailureDefinitionId m_failureId = null;
FailureDefinitionId m_warnId = null;

// constructor takes the AddInId for the add-in associated with this updater
public WallWarnUpdater(AddInId id)
{
m_appId = id;
m_updaterId = new UpdaterId(m_appId,
new Guid("69797663-7BCB-44f9-B756-E4189FE0DED8"));
}

public void Execute(UpdaterData data)
{
Document doc = data.GetDocument();
Autodesk.Revit.ApplicationServices.Application app = doc.Application;
foreach (ElementId id in data.GetModifiedElementIds())
{
Wall wall = doc.GetElement(id) as Wall;
Autodesk.Revit.DB.Parameter p = wall.LookupParameter("Unconnected Height");
if (p != null)
{
if (p.AsDouble() > 200)
{
FailureMessage failMessage = new FailureMessage(FailureId);
failMessage.SetFailingElement(id);
doc.PostFailure(failMessage);
}
else if (p.AsDouble() > 100)
{
FailureMessage failMessage = new FailureMessage(WarnId);
failMessage.SetFailingElement(id);
doc.PostFailure(failMessage);
}
}
}
}

public FailureDefinitionId FailureId
{
get { return m_failureId; }
set { m_failureId = value; }
}

public FailureDefinitionId WarnId
{
get { return m_warnId; }
set { m_warnId = value; }
}

public string GetAdditionalInformation()
{
return "Give warning and error if wall is too tall";
}

public ChangePriority GetChangePriority()
{
return ChangePriority.FloorsRoofsStructuralWalls;
}

public UpdaterId GetUpdaterId()
{
return m_updaterId;
}

public string GetUpdaterName()
{
return "Wall Height Check";
}
}

删除已发布的故障

由于在同一事务中可能存在对文档的多次更改和多次重新生成,因此可能有些故障不再相关,并且可能需要将其删除以防止“假警报”。通过调用Document.UnpostFailure()方法并传入在调用PostFailure()时获得的FailureMessageKey,可以取消发送特定的消息。如果失败的严重性是DocumentCorruption,UnpostFailure()将抛出异常。

当事务即将回滚时,还可以使用Transaction.SetFailureHandlingOptions()方法自动删除所有已发布的失败(以便用户不必点击取消)。

处理故障

通常,在事务结束时(特别是在调用Transaction.Commit()或Transaction.Rollback()时),由Revit的标准故障解决UI处理发布的故障。向用户呈现处理故障的信息和选项。

如果文档上的某个操作(或一组操作)需要Revit附加模块对某些错误进行特殊处理,则可以自定义故障处理以执行此解决方案。可提供自定义故障处理:

  • 对于使用接口IFailuresPreprocessor的给定事务。
  • 对于使用FailuresProcessing事件的所有可能的错误。

最后,该API提供了使用接口IFailuresProcessor完全替换标准故障处理用户界面的能力。虽然前两种处理故障的方法在大多数情况下应该足够了,但最后一个选项可以用于特殊情况,例如提供更好的故障处理UI或将应用程序用作Revit上的前端时。

故障处理概述

重要的是要记住,在调用Transaction.Commit()和实际处理失败之间会发生许多事情。自动连接、重叠检查、组检查和工作集可编辑性检查仅举几例。这些检查和更改可能会使某些故障消失,或者更有可能会发布新的故障。因此,无法得出调用Transaction.Commit()时要处理的故障状态的结论。为了正确处理故障,有必要连接到实际的故障处理机制。

当失败处理开始时,对应该在事务中进行的文档的所有更改都将被执行,并且所有失败都将被发布。因此,在故障处理期间,不允许对文档进行不受控的更改。通过FailuresAccessor提供的受限接口解决故障的能力有限。如果发生这种情况,则必须重复所有事务结束检查和失败处理。因此,在一个事务结束时可能会有几个故障解决周期。

每个故障处理周期包括3个步骤:

  1. 故障预处理(FailuresPreprocessor)
  2. 广播故障处理事件(FailuresProcessing事件)
  3. 最终处理(FailuresProcessor)

这3个步骤中的每一个都可以通过返回不同的FailureProcessingResults来控制接下来发生的事情。这些选项包括:

  • Continue - 对执行流没有影响。如果FailuresProcessor返回“Continue”(继续),但未解决故障,则Revit将按照返回“ProceedWithRollBack”的方式进行操作。
  • ProceedWithCommit - 中断故障处理,并立即触发另一个事务结束检查循环,然后进行另一个故障处理。在尝试解决故障后应返回。如果返回时没有任何成功的故障解决方案,则很容易导致无限循环。如果事务已经在回滚,则无法返回,在这种情况下将被视为“ProceedWithRollBack”。
  • ProceedWithRollback -继续执行失败处理,但强制回滚事务,即使最初请求提交事务也是如此。如果在返回ProceedWithRollBack之前,FailureHandlingOptions被设置为在回滚后清除错误,则不会进行进一步的错误处理,所有失败都将被删除,事务将以静默方式回滚。否则,默认的失败处理将继续,失败可能会传递给用户,但保证事务被回滚。
  • WaitForUserInput -只有当FailuresProcessor正在等待外部事件(通常是用户输入)来完成故障处理时,才能返回。

根据事务中发布的故障的严重性以及事务是正在提交还是正在回滚,这3个步骤中的每一个都可能具有某些解决错误的选项。有关文档中发布的故障的所有信息、有关执行某些操作以解决故障的能力的信息以及执行此类操作的API都通过FailuresAccessor类提供。文档可用于获取其他信息,但只能通过FailuresAccessor进行更改。

故障访问器

FailuresAccessor对象作为参数传递给每个失败处理步骤,并且是获取文档中有关失败的信息的唯一可用接口。虽然允许在故障处理期间阅读文档,但在故障解决期间修改文档的唯一方法是通过此类提供的方法。从失败处理返回后,类的实例被停用,不能再使用。

故障访问器提供的信息

FailuresAccessor对象提供了一些通用信息,例如:

  • 正在处理或预处理失败的文档
  • 文档中公布的最高失效严重度
  • 正在完成的事务的事务名称和失败处理选项
  • 是否请求提交或回滚事务。

FailuresAccessor对象还通过GetFailuresMessages()方法提供有关特定故障的信息。

解决故障的选项

FailuresAccessor对象提供了几种解决故障的方法:

  • 可以使用DeleteWarning()或DeleteAllWarning()方法删除严重性为Warning的故障消息。
  • ResolveFailure()或ResolveFailures()方法可用于使用为每个故障设置的最后一个故障解决类型来解决一个或多个故障。
  • DeleteElements()可以通过删除与失败相关的元素来解决失败。
  • 或者使用ReplaceFailures()方法删除所有失败消息并将其替换为一个“一般”失败。

IFailuresPreprocessor

IFailuresPreprocessor接口可用于仅为特定事务提供自定义故障处理。IFailuresPreprocessor是一个接口,可用于执行预处理步骤,以过滤掉预期的事务失败或发布新的失败。故障可以通过以下方式“过滤掉”:

  • 静默地移除已知为事务发布的并且在特定事务的上下文中被认为与用户无关的警告
  • 静默地解决已知为事务发布的某些故障,并且这些故障应始终在给定事务的上下文中解决
  • 对于给定的工作流,在不应提交“不完美”事务的情况下静默地中止事务或者中止事务比用户交互更可取。

IFailuresPreprocessor接口在故障解决过程中首先获得控制权。它几乎等同于在完成事务之前检查和解决故障,除了IFailuresPreprocessor在正确的时间获得控制权,在所有故障保证被发布和/或所有不相关的被删除之后。

每个事务只能有一个IFailuresPreprocessor,并且没有默认的故障预处理器。如果没有附加到事务(通过故障处理选项),则简单地省略故障解决的第一步。

代码区域26-3:处理来自IFailuresPreprocessor的故障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class SwallowTransactionWarning : IExternalCommand
{
public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
Autodesk.Revit.ApplicationServices.Application app =
commandData.Application.Application;
Document doc = commandData.Application.ActiveUIDocument.Document;
UIDocument uidoc = commandData.Application.ActiveUIDocument;

FilteredElementCollector collector = new FilteredElementCollector(doc);
ICollection elementCollection =
collector.OfClass(typeof(Level)).ToElements();
Level level = elementCollection.Cast().ElementAt(0);

Transaction t = new Transaction(doc);
t.Start("room");
FailureHandlingOptions failOpt = t.GetFailureHandlingOptions();
failOpt.SetFailuresPreprocessor(new RoomWarningSwallower());
t.SetFailureHandlingOptions(failOpt);

doc.Create.NewRoom(level, new UV(0, 0));
t.Commit();

return Autodesk.Revit.UI.Result.Succeeded;
}
}
public class RoomWarningSwallower : IFailuresPreprocessor
{
public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
{
IList failList = new List();
// Inside event handler, get all warnings
failList = failuresAccessor.GetFailureMessages(); foreach (FailureMessageAccessor failure in failList)
{
// check FailureDefinitionIds against ones that you want to dismiss, FailureDefinitionId failID = failure.GetFailureDefinitionId();
// prevent Revit from showing Unenclosed room warnings
if (failID == BuiltInFailures.RoomFailures.RoomNotEnclosed)
{
failuresAccessor.DeleteWarning(failure);
}
}

return FailureProcessingResult.Continue;
}
}

故障处理事件

FailuresProcessing事件最适合于希望在没有用户界面的情况下为整个会话或许多不相关的事务提供自定义故障处理的应用程序。通过此事件处理故障的一些用例包括:

  • 自动删除某些警告和/或基于办公室标准(或其他准则)自动解决某些错误
  • 自定义故障记录

FailuresProcessing事件在IFailuresPreprocessor(如果有)完成后引发。它可以有任意数量的处理程序,并且所有的处理程序都将被调用。由于事件处理程序无法返回值,因此应该使用事件参数上的SetProcessingResult()来传递状态。只能设置Continue、ProceedWithRollback或ProceedWithCommit。

下面的示例演示FailuresProcessing事件的事件处理程序。

代码区域26-4:处理FailuresProcessing事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void CheckWarnings(object sender, FailuresProcessingEventArgs e)
{
FailuresAccessor fa = e.GetFailuresAccessor();
IList failList = new List();
failList = fa.GetFailureMessages(); // Inside event handler, get all warnings
foreach (FailureMessageAccessor failure in failList)
{

// check FailureDefinitionIds against ones that you want to dismiss, FailureDefinitionId failID = failure.GetFailureDefinitionId();
// prevent Revit from showing Unenclosed room warnings
if (failID == BuiltInFailures.RoomFailures.RoomNotEnclosed)
{
fa.DeleteWarning(failure);
}
}
}

故障处理器

在处理FailuresProcessing事件之后,IFailuresProcessor接口最后获得控制权。Revit任务中只有一个活动IFailuresProcessor。要注册故障处理器,从IFailuresProcessor派生一个类,并使用Application.RegisterFailuresProcessor()方法注册它。如果先前注册的处理器出现故障,则将其丢弃。如果Revit附加模块选择为Revit注册故障处理器,则该处理器将成为任务中所有Revit错误的默认错误处理程序,并且不会显示标准的Revit错误对话框。如果未设置故障处理器,则Revit UI中有一个默认的故障处理器,用于调用所有常规的Revit错误对话框。只有在使用自定义故障解决处理程序(可以是交互式的,也可以没有用户界面)替换现有Revit故障UI时,才应覆盖FailuresProcessor。

如果传递给RegisterFailuresProcessor()方法NULL,则任何失败的事务都将被静默中止(除非通过前两个失败处理步骤解决了失败)。

允许IFailuresProcessor.ProcessFailures()方法返回WaitForUserInput,这将使事务挂起。在这种情况下,预计FailuresProcessor会在屏幕上留下一些UI,这些UI最终将提交或回滚挂起的事务-否则挂起状态将无限期地持续下去,基本上会冻结文档。

以下实现IFailuresProcessor的示例检查故障,删除失败的元素并为用户设置适当的消息。

代码区域26-5:IFailuresProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]public class MyFailuresUI : IExternalApplication
{
static AddInId m_appId = new AddInId(new Guid("9F179363-B349-4541-823F-A2DDB2B86AF3"));
public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication uiControlledApplication)
{
IFailuresProcessor myFailUI = new FailUI();
Autodesk.Revit.ApplicationServices.Application.RegisterFailuresProcessor(myFailUI);
return Result.Succeeded;
}

public Autodesk.Revit.UI.Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}

public class FailUI : IFailuresProcessor
{
public void Dismiss(Document document)
{
// This method is being called in case of exception or document destruction to
// dismiss any possible pending failure UI that may have left on the screen
}

public FailureProcessingResult ProcessFailures(FailuresAccessor failuresAccessor)
{
IList resolutionTypeList =
new List();
IList failList = new List();
// Inside event handler, get all warnings
failList = failuresAccessor.GetFailureMessages();
string errorString = "";
bool hasFailures = false;
foreach (FailureMessageAccessor failure in failList)
{

// check how many resolutions types were attempted to try to prevent
// entering infinite loop
resolutionTypeList =
failuresAccessor.GetAttemptedResolutionTypes(failure);
if (resolutionTypeList.Count >= 3)
{
TaskDialog.Show("Error", "Cannot resolve failures - transaction will be rolled back.");
return FailureProcessingResult.ProceedWithRollBack;
}

errorString += "IDs ";
foreach (ElementId id in failure.GetFailingElementIds())
{
errorString += id + ", ";
hasFailures = true;
}
errorString += "\nWill be deleted because: " + failure.GetDescriptionText() + "\n";
failuresAccessor.DeleteElements(
failure.GetFailingElementIds() as IList);
}
if (hasFailures)
{
TaskDialog.Show("Error", errorString);
return FailureProcessingResult.ProceedWithCommit;
}

return FailureProcessingResult.Continue;
}
}
}

注:翻译自Revit API Developers Guide