Revit二次开发之 错误
一、报告错误
重点:错误发送方法:PostFailure
消息主体:FailureMessage
错误的严重程度:FailureSeverity
Revit开发中,不可避免会出现错误、警告等信息,如何友好的处理错误,是写好插件的关键,我们在操作revit过程中,经常会碰到以下对话框:
他将所程序处理过程中,遇到的问题,都呈现出来,并且指定用户如何应对错误,我们在程序中,如何实现此功能呢,需要用到一个标准的文档接口PostFailure来通告错误
public class Test : IExternalCommand { /// <summary> /// 软件执行 /// </summary> /// <param name="commandData"></param> /// <param name="message"></param> /// <param name="elements"></param> /// <returns></returns> public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { var m_idWarning = new FailureDefinitionId(new Guid("15060356-048c-4298-8ad8-a34c1637d45f"));
using (Transaction tran = new Transaction(commandData.Application.ActiveUIDocument.Document, "报错")) { tran.Start(); commandData.Application.ActiveUIDocument.Document.PostFailure(new FailureMessage(m_idWarning)); tran.Commit(); } return Result.Succeeded; } }
二、错误定义与错误源
重点:错误定义类:FailureDefinition
消息主体携带错误源:SetAdditionalElement
上面有一个有一句代码是不是很奇怪: var m_idWarning = new FailureDefinitionId(new Guid("15060356-048c-4298-8ad8-a34c1637d45f"));他是何方神圣,其实在revit的机制中,错误是需要提前定义的,就是需要先领个证,不领证就想上车,是不被允许的。所以错误的定义需要在插件一开始的时候就定义好,程序在运行过程中定义是无效的,故在插件启动的时候,就需要把对应的错误定义完整,其代码如下:
using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace RevitTest { [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] public class Test : IExternalCommand, IExternalApplication { /// <summary> /// 启动程序 /// </summary> /// <param name="application"></param> /// <returns></returns> public Result OnStartup(UIControlledApplication application) { try { var m_idWarning = new FailureDefinitionId(new Guid("15060356-048c-4298-8ad8-a34c1637d45f")); var m_fdWarning = FailureDefinition.CreateFailureDefinition(m_idWarning, FailureSeverity.Warning, "我的错误我负责."); return Result.Succeeded; } catch (System.Exception) { return Result.Failed; } } public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } /// <summary> /// 软件执行 /// </summary> /// <param name="commandData"></param> /// <param name="message"></param> /// <param name="elements"></param> /// <returns></returns> public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { var m_idWarning = new FailureDefinitionId(new Guid("15060356-048c-4298-8ad8-a34c1637d45f")); using (Transaction tran = new Transaction(commandData.Application.ActiveUIDocument.Document, "报错")) { tran.Start(); commandData.Application.ActiveUIDocument.Document.PostFailure(new FailureMessage(m_idWarning)); tran.Commit(); } return Result.Succeeded; } } }
通过以上的过程,我们就将系统的错误和报错的时机都定义完成了,我们就可以在错误的海洋里遨游了吗,显然是不够的。我们都不知道谁产生的错误,错误的源头是什么,
上图可以看到,删除错误项无法使用,没有错误源头,那么在报告错误的时候,需要指定错误源头,其代码定义如下:
XYZ start = new XYZ(); XYZ end = new XYZ(3.4, 0, 0); Line wallLine = Line.CreateBound(start, end); Level level = commandData.Application.ActiveUIDocument.Document.GetElement(new ElementId(339)) as Level; Wall wall = Wall.Create(commandData.Application.ActiveUIDocument.Document, wallLine, level.Id, true); var msg = new FailureMessage(m_idWarning); msg.SetAdditionalElement(wall.Id); commandData.Application.ActiveUIDocument.Document.PostFailure(msg);
错误就有源头了,表明是什么元素引发的异常。
三、错误的处理
重点:IFailuresProcessor 错误分发接口,我们称之为错误分发器
FailureResolutionType 错误处理类型
遇到错误不可怕,就怕错误不及时处理,造成不可估计的问题,在一个复杂的处理事务中,错误一般会定义三种处理方式:
1、忽略,根本不算错误;
2、解决错误;
3、报告异常,执行失败
什么时候处理错误呢,需要回到事务中,事务是异常的管理者,当事务接受到错误的时候,会调用错误处理机制IFailuresProcessor接口,
第一种情况的代码,不报告错误,其代码可以定义如下:
using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace RevitTest { [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] public class Test : IExternalCommand, IExternalApplication { internal static FailureDefinitionId m_idWarning = new FailureDefinitionId(new Guid("15060356-048c-4298-8ad8-a34c1637d45f")); /// <summary> /// 启动程序 /// </summary> /// <param name="application"></param> /// <returns></returns> public Result OnStartup(UIControlledApplication application) { try { var m_fdWarning = FailureDefinition.CreateFailureDefinition(m_idWarning, FailureSeverity.Warning, "我的错误我负责."); return Result.Succeeded; } catch (System.Exception) { return Result.Failed; } } public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } /// <summary> /// 软件执行 /// </summary> /// <param name="commandData"></param> /// <param name="message"></param> /// <param name="elements"></param> /// <returns></returns> public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { using (Transaction tran = new Transaction(commandData.Application.ActiveUIDocument.Document, "Warning_FailureProccessor")) { FailureHandlingOptions options = tran.GetFailureHandlingOptions(); FailurePreproccessor preproccessor = new FailurePreproccessor(); options.SetFailuresPreprocessor(preproccessor); tran.SetFailureHandlingOptions(options); tran.Start(); XYZ start = new XYZ(); XYZ end = new XYZ(3.4, 0, 0); Line wallLine = Line.CreateBound(start, end); Level level = commandData.Application.ActiveUIDocument.Document.GetElement(new ElementId(339)) as Level; Wall wall = Wall.Create(commandData.Application.ActiveUIDocument.Document, wallLine, level.Id, true); var msg = new FailureMessage(m_idWarning); msg.SetAdditionalElement(wall.Id); commandData.Application.ActiveUIDocument.Document.PostFailure(msg); tran.Commit(); } return Result.Succeeded; } public class FailurePreproccessor : IFailuresPreprocessor { /// <summary> /// 定义当前的吹进程 /// </summary> /// <param name="failuresAccessor"></param> /// <returns></returns> public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) { //获取所有的错误消息 IList<FailureMessageAccessor> fmas = failuresAccessor.GetFailureMessages(); //如果没有错误,则直接继续 if (fmas.Count == 0) { return FailureProcessingResult.Continue; } //获取当前事务的名称 String transactionName = failuresAccessor.GetTransactionName(); //假如是预处理事务 if (transactionName.Equals("Warning_FailureProccessor")) { foreach (FailureMessageAccessor fma in fmas) { FailureDefinitionId id = fma.GetFailureDefinitionId(); //可以删除当前的错误,则系统不报错 if (id == Test.m_idWarning) { failuresAccessor.DeleteWarning(fma); } } //处理和提交 return FailureProcessingResult.ProceedWithCommit; } else { //不处理,继续报错 return FailureProcessingResult.Continue; } } } } }
通过以上代码,虽然报告的错误,但是错误被从事务队列中删除,所以界面不再报告错误。
第二种情况的代码,报告调用处理器,进行错误处理,Revit的错误处理器的类型其实早都定义好了,主要看FailureResolutionType枚举的说明:
Invalid | 特殊(保留)类型。这应该是FailureResolutionType的任何变量的未初始化值,最终用户不应该看到这个值,API程序员可以在初始化和比较中使用这个值。 |
Default | 特殊(保留)类型。它不能用作定义解析时的类型,但可以用作从FailureMessage或FailureDefinition查询默认解析的键。 |
CreateElements |
定义一个失败的解决方案,其中Revit将添加新元素来代替缺少的元素。
|
DeleteElements | 通过删除导致失败的元素来定义故障的解决方案。 |
SkipElements |
通过跳过操作失败的元素并继续处理其余元素来定义故障的解决方案。
|
MoveElements | 通过将元素移动到适当的位置来定义故障的解决方案。 |
FixElements | 通过更改失败元素定义故障解决方案-特定更改可能因故障类型和元素类型而异。 |
DetachElements | 通过删除图元(例如连接的墙和屋顶、成组图元和关联曲线)之间的无效关系来定义故障解决方案。 |
QuitEditMode |
通过退出编辑模式而不是提交更改来定义故障解决方案。在编辑模式下所做的更改将被回滚。
|
UnlockConstraints | 通过无约束元素定义故障的解决方案。 |
SetValue | 通过设置适当的值代替无效值来定义故障的解决方案。 |
SaveDocument | 保存当前文档以避免在发生严重情况时丢失更多文档。 |
ShowElements | 标准的“Show”在某些情况下可能会失败,在这种情况下可以添加特殊的解析来显示失败的元素。 |
Others | 解析类型与以上任何一个都不匹配。 |
所以,我可以错误定义的时候,指定错误处理器如下:
m_fdWarning.AddResolutionType(FailureResolutionType.DeleteElements, "DeleteElements", typeof(DeleteElements)); m_fdWarning.SetDefaultResolutionType(FailureResolutionType.DeleteElements);
通过以上代码,我们定义好了错误处理器,则我们在错误分发器中,需要如下实现:
foreach (FailureMessageAccessor fma in fmas) { FailureDefinitionId id = fma.GetFailureDefinitionId(); //可以删除当前的错误,则系统不报错 if (id == Test.m_idWarning) { failuresAccessor.ResolveFailure(fma); } }
我们发现,创建的的墙体,由于错误,通过DeleteElements 处理器被删除。但我们经常在处理错误的时候,希望不同的情况,调用不同的处理器,不希望一开始就给错误定义定义好,则我们可以通过FailureMessage 指定一个定义好的处理器,其代码如下:
using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace RevitTest { [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] public class Test : IExternalCommand, IExternalApplication { internal static FailureDefinitionId m_idWarning = new FailureDefinitionId(new Guid("15060356-048c-4298-8ad8-a34c1637d45f")); /// <summary> /// 启动程序 /// </summary> /// <param name="application"></param> /// <returns></returns> public Result OnStartup(UIControlledApplication application) { try { var m_fdWarning = FailureDefinition.CreateFailureDefinition(m_idWarning, FailureSeverity.Warning, "我的错误我负责."); m_fdWarning.AddResolutionType(FailureResolutionType.MoveElements, "MoveElements", typeof(DeleteElements)); m_fdWarning.AddResolutionType(FailureResolutionType.DeleteElements, "DeleteElements", typeof(DeleteElements)); m_fdWarning.SetDefaultResolutionType(FailureResolutionType.DeleteElements); return Result.Succeeded; } catch (System.Exception) { return Result.Failed; } } public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; } /// <summary> /// 软件执行 /// </summary> /// <param name="commandData"></param> /// <param name="message"></param> /// <param name="elements"></param> /// <returns></returns> public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { using (Transaction tran = new Transaction(commandData.Application.ActiveUIDocument.Document, "Warning_FailureProccessor")) { FailureHandlingOptions options = tran.GetFailureHandlingOptions(); FailurePreproccessor preproccessor = new FailurePreproccessor(); options.SetFailuresPreprocessor(preproccessor); tran.SetFailureHandlingOptions(options); tran.Start(); XYZ start = new XYZ(); XYZ end = new XYZ(3.4, 0, 0); Line wallLine = Line.CreateBound(start, end); Level level = commandData.Application.ActiveUIDocument.Document.GetElement(new ElementId(339)) as Level; Wall wall = Wall.Create(commandData.Application.ActiveUIDocument.Document, wallLine, level.Id, true); var msg = new FailureMessage(m_idWarning); //创建一个错误处理器 FailureResolution fr = DeleteElements.Create(commandData.Application.ActiveUIDocument.Document, wall.Id); //添加到消息中 msg.AddResolution(FailureResolutionType.ShowElements, fr); msg.SetAdditionalElement(wall.Id); commandData.Application.ActiveUIDocument.Document.PostFailure(msg); tran.Commit(); } return Result.Succeeded; } public class FailurePreproccessor : IFailuresPreprocessor { /// <summary> /// 定义当前的吹进程 /// </summary> /// <param name="failuresAccessor"></param> /// <returns></returns> public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) { //获取所有的错误消息 IList<FailureMessageAccessor> fmas = failuresAccessor.GetFailureMessages(); //如果没有错误,则直接继续 if (fmas.Count == 0) { return FailureProcessingResult.Continue; } //获取当前事务的名称 String transactionName = failuresAccessor.GetTransactionName(); //假如是预处理事务 if (transactionName.Equals("Warning_FailureProccessor")) { foreach (FailureMessageAccessor fma in fmas) { FailureDefinitionId id = fma.GetFailureDefinitionId(); //可以删除当前的错误,则系统不报错 if (id == Test.m_idWarning) { failuresAccessor.ResolveFailure(fma); } } //处理和提交 return FailureProcessingResult.ProceedWithCommit; } else { //不处理,继续报错 return FailureProcessingResult.Continue; } } } } }