Failure definition registry revit api
My issue is that I want Revit to continue with its standard failure processing, and I just want the information about those failures. From the perspective of my add-in, I can call a transaction and pass it a custom preprocessor class; however, this doesn’t appear to be a real object, just a method that gets run with a set return value. I could also hook up a preprocessor event handler, but again, there’s no simple way to get information from my event handler, back into my main program.
Here’s the preprocessor example from the ErrorHandling SDK sample:
Transaction transaction = new Transaction( m_doc, Warning_FailurePreproccessor_OverlappedWall ); FailureHandlingOptions options = transaction .GetFailureHandlingOptions(); FailurePreproccessor preproccessor = new FailurePreproccessor(); options.SetFailuresPreprocessor( preproccessor ); transaction.SetFailureHandlingOptions( options ); transaction.Start(); Line line = Line.CreateBound( new XYZ( -10, 0, 0 ), new XYZ( -20, 0, 0 ) ); Wall wall1 = Wall.Create( m_doc, line, level1.Id, false ); Wall wall2 = Wall.Create( m_doc, line, level1.Id, false ); m_doc.Regenerate(); transaction.Commit();
So, I can pass in a preprocessor object to handle the preprocessing where I can access the failure errors.
However, the preprocessor object just looks like this:
/// summary> /// Implements the interface IFailuresPreprocessor /// summary> public class FailurePreproccessor : IFailuresPreprocessor < /// summary> /// This method is called when there have been /// failures found at the end of a transaction /// and Revit is about to start processing them. /// summary> /// param name="failuresAccessor">The Interface class /// that provides access to the failure information. param> /// returns>returns> public FailureProcessingResult PreprocessFailures( FailuresAccessor failuresAccessor ) < >>
With just a single method ( PreprocessFailures ) that presumably gets called by Revit, with a set return value to Revit. So, my issue is here, how do I get information from this failure preprocessing method, back into my main program? The preprocessor’s return value gets returned to Revit, not my addin, and if I want to implement the IFailuresPreprocessor interface I can’t pass in a ref or out variable to the preprocessor to write to, as far as I can tell.
Answer: The implementation of the PreprocessFailures method has an argument failuresAccessor which is used for reviewing and dealing with failure messages.
The below is an example implementation. Failure messages have to be dealt with before you can commit a transaction either by resolving them or deleting them so there are generally no messages left after transaction is committed. You can get information out, but you would need to create your own class to store the information. IFailurePreprocessor is more for resolving the messages beforehand.
If you were passing API objects ByRef or holding onto them you’d likely get errors due to those objects being out of context.
Private Class FailurePP Implements IFailuresPreprocessor Private FailureList As New List(Of String) Public Function PreprocessFailures(failuresAccessor As FailuresAccessor) As FailureProcessingResult Implements IFailuresPreprocessor.PreprocessFailures For Each item As FailureMessageAccessor In failuresAccessor.GetFailureMessages FailureList.Add(item.GetDescriptionText) Dim FailDefID As FailureDefinitionId = item.GetFailureDefinitionId If FailDefID = BuiltInFailures.GeneralFailures.DuplicateValue Then failuresAccessor.DeleteWarning(item) End If Next Return FailureProcessingResult.ProceedWithCommit End Function Public Sub ShowDialogue() Dim SB As New Text.StringBuilder For Each item As String In FailureList SB.AppendLine(item) Next TaskDialog.Show("Some info", SB.ToString) End Sub End Class Private Function FailureEx(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) If commandData.Application.ActiveUIDocument Is Nothing Then Return Result.Succeeded Else Dim IntUIDoc As UIDocument = commandData.Application.ActiveUIDocument Dim IntDoc As Document = IntUIDoc.Document Dim F_PP As New FailurePP Using tx As New Transaction(IntDoc, "Marks") Dim Ops As FailureHandlingOptions = tx.GetFailureHandlingOptions Ops.SetFailuresPreprocessor(F_PP) tx.SetFailureHandlingOptions(Ops) If tx.Start = TransactionStatus.Started Then Dim FEC As New FilteredElementCollector(IntDoc) Dim ECF As New ElementCategoryFilter(BuiltInCategory.OST_StructuralColumns) Dim Jx As List(Of Element) = FEC.WherePasses(ECF).WhereElementIsNotElementType.ToElements If Jx.Count >= 2 Then For I = 0 To 1 Jx(I).Parameter(BuiltInParameter.ALL_MODEL_MARK).Set("Duplicate Mark") Next End If tx.Commit() End If End Using F_PP.ShowDialogue() Return Result.Succeeded End Function
Response: The preprocessor doesn’t necessarily need to resolve or delete them, though, since if it leaves them they’ll just be handled by the failure processor, which I believe by default is those Revit popup boxes. I was looking at failuresAccessor to get the failure messages, but how do I get that information back into my program?
You mention that I would need to create my own class to store the information, but I’m confused as to how the method or object I pass would ever be able to get information back out into my main command.
Answer: They’ll continue to a further stage of the failure framework but the point of the preprocessor is to deal with them so they don’t arrive at that stage.
In the code above, I got the failure message out as a string, I could have also obtained the failure ID, the name of the transaction amongst other things. I’m not sure what information you are after?
The code below doesn’t require you to cancel or delete warnings, but this depends on severity of warnings. The old Revit 2012 document states that returning FailureProcessingResult.Continue rolls back the transaction, but I find for the example below that not to be the case (since parameters have been changed). Probably this depends on failure severity. The warnings of duplicate mark still exist in the document, but the API objects are no more.
The API objects are not directly accessible after transaction commit, so you have to mirror the information within them in your own class.
The other two parts of the Failure API framework are probably not suitable for what you describe since they have no direct relation to your IExternalcommand , i.e., the IFailuresProcessingEvent is for everything called for all transactions, and the IFailuresProcessor is to replace the dialogue for the whole Revit session.
Private Class FailurePP Implements IFailuresPreprocessor Private FailureList As New List(Of String) Public Function PreprocessFailures(failuresAccessor As FailuresAccessor) As FailureProcessingResult Implements IFailuresPreprocessor.PreprocessFailures For Each item As FailureMessageAccessor In failuresAccessor.GetFailureMessages FailureList.Add(item.GetDescriptionText) Dim FailDefID As FailureDefinitionId = item.GetFailureDefinitionId If FailDefID = BuiltInFailures.GeneralFailures.DuplicateValue Then 'failuresAccessor.DeleteWarning(item) End If Next Return FailureProcessingResult.Continue End Function Public Sub ShowDialogue() Dim SB As New Text.StringBuilder For Each item As String In FailureList SB.AppendLine(item) Next TaskDialog.Show("Some info", SB.ToString) End Sub End Class
Response: Thanks for your replies! The code does indeed work perfectly. I had a fundamental misunderstanding of how objects behaved in C# and had missed that you were calling ShowDialogue from outside the PreprocessFailures method.
In case anyone happens to come across this, I ported the code to C# and figure I may as well share it:
/// summary> /// Collect all failure message description strings. /// summary> class MessageDescriptionGatheringPreprocessor : IFailuresPreprocessor < Liststring> FailureList < get; set; > public MessageDescriptionGatheringPreprocessor() < FailureList = new Liststring>(); > public FailureProcessingResult PreprocessFailures( FailuresAccessor failuresAccessor ) < foreach( FailureMessageAccessor fMA in failuresAccessor.GetFailureMessages() ) < FailureList.Add( fMA.GetDescriptionText() ); FailureDefinitionId FailDefID = fMA.GetFailureDefinitionId(); //if (FailDefID == BuiltInFailures // .GeneralFailures.DuplicateValue) // failuresAccessor.DeleteWarning(fMA); > return FailureProcessingResult.Continue; > public void ShowDialogue() < string s = string.Join( "\r\n", FailureList ); TaskDialog.Show( "Post Processing Failures:", s ); > > [Transaction( TransactionMode.Manual )] class CmdFailureGatherer < public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) < UIApplication uiApp = commandData.Application; Document activeDoc = uiApp.ActiveUIDocument.Document; MessageDescriptionGatheringPreprocessor pp = new MessageDescriptionGatheringPreprocessor(); using( Transaction t = new Transaction( activeDoc ) ) < FailureHandlingOptions ops = t.GetFailureHandlingOptions(); ops.SetFailuresPreprocessor( pp ); t.SetFailureHandlingOptions( ops ); t.Start( "Marks" ); IListElement> specEqu = new FilteredElementCollector( activeDoc ) .OfCategory( BuiltInCategory.OST_SpecialityEquipment ) .WhereElementIsNotElementType() .ToElements(); if( specEqu.Count >= 2 ) < for( int i = 0; i < 2; i++ ) specEqu[i].get_Parameter( BuiltInParameter.ALL_MODEL_MARK ).Set( "Duplicate Mark" ); > t.Commit(); > pp.ShowDialogue(); return Result.Succeeded; > >
Many thanks to Mastjaso and Rpthomas108 for putting this together!
In that command, I generate a warning by creating two overlapping walls:
// Generate an 'duplicate wall' warning message: Element level = new FilteredElementCollector( doc ) .OfClass( typeof( Level ) ) .FirstElement(); Line line = Line.CreateBound( XYZ.Zero, 10 * XYZ.BasisX ); Wall wall1 = Wall.Create( doc, line, level.Id, false ); Wall wall2 = Wall.Create( doc, line, level.Id, false );
During the command execution, this displays the standard Revit warning message, which is not suppressed:
At the end of the command, the failure message descriptions have all been collected and are reported in a separate task dialogue:
Remarks
Each failure that can be potentially posted in Revit must be based on a FailureDefinition object that contains some persistent information about failure such as identity, severity, basic description text, types of resolution and default resolution. Each FailureMessage, which contains variable part of the information for a specific failure when it occurs, is created with a reference to a registered FailureDefinition. In order to be able to post a failure, one must define and register it via FailureDefinition object during Revit Application startup.
Inheritance Hierarchy
System Object
Autodesk.Revit.DB FailureDefinition
See Also
Online Documentation for the Revit API | Gui Talarico
Please disable your ad-blocker to support this project. .
Syntax
id Type: Autodesk.Revit.DB FailureDefinitionId
Unique identifier of the failure. severity Type: Autodesk.Revit.DB FailureSeverity
The severity of the failure. Cannot be FailureSeverity::None. messageString Type: System String
A user-visible string describing the failure.
Return Value
The created FailureDefinition instance.
Remarks
The newly created FailureDefinition will be added to the FailureDefinitionRegistry. Because FailureDefinition could only be registered when Revit starting up, this function cannot be used after Revit has already started. Throws InvalidOperationException if invoked after Revit start-up is completed.
Examples
// 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.");
Copy VB.NET
' define a new failure id for a warning about walls Dim warnId As New FailureDefinitionId(New Guid("FB4F5AF3-42BB-4371-B559-FB1648D5B4D1")) ' register the new warning using FailureDefinition Dim failDef As FailureDefinition = FailureDefinition.CreateFailureDefinition(warnId, FailureSeverity.Warning, "Wall is too big (>100'). Performance problems may result.")
Exceptions
Exception | Condition |
---|---|
Autodesk.Revit.Exceptions ArgumentException | The id of failure definition is not valid. -or- The id of failure definition is already used to register another FailureDefinition. -or- The severity of failures cannot be FailureSeverity::None. -or- Message string is empty or contains invalid characters. |
Autodesk.Revit.Exceptions ArgumentNullException | A non-optional argument was NULL |
Autodesk.Revit.Exceptions ArgumentOutOfRangeException | A value passed for an enumeration argument is not a member of that enumeration |
Failure definition registry revit api
Before I get into today’s topic, let me lament a bit about my Wednesday morning.
Travel Woes
I am flying to Las Vegas today in order to have time for last-minute AU preparations and acclimatise properly there. For reasons unknown to me, my travel agent (who shall remain unnamed here in public) booked me a flight leaving at seven in the morning from Switzerland to London, and a continuation from London to Las Vegas with an eight hour wait in between. I have a brother in London, so we arranged to meet, making the wait quite a tolerable affair.
Be that as it may, I tried to check in online to my British Airways flight yesterday morning. The system asked me to enter all my data, which took ten or fifteen minutes the first time around, then went ahead and processed my entry for a while, only to return with an error message saying something went wrong, and please to try later. So I did. Three times, in fact, with a couple of hours in between each attempt, to no avail. Rather frustrating, of course, since I had to re-enter all my data for each attempt. Snafu.
This morning at the airport, I checked in, everything seemed in order, but there was a problem in the very last step, printing the boarding pass. It turned out to be the US ESTA. Since i visited the US in June and the ESTA is valid for two years, I assumed that would not be a problem. I updated my passport in the meantime, though, and now I apparently need a new ESTA. Snafu.
They asked me to go to an Internet point next door and reapply for the new ESTA, which I did. The machine accepted coins only; I bought ten minutes of time, filled in the form, everything complete, and reached the validation step just before the ten minutes were up. The machine would not accept my next coin. Result: start again from scratch. Snafu.
The second time around I was much faster, completed the form in five minutes, and was told by the system that I could either pay immediately or within seven days’ time. I was in deep stress by now, afraid of missing my flight, so I thought «ok, I’ll do it when I arrive, within seven days», and went back to check in. Same problem. Apparently, payment is required within seven days, but also before departure. And the latter was not mentioned. By now, of course, the flight was closed. Snafu.
The next step in the list of bungles is the fact that my unnamed travel agent had booked me on this early flight at seven in the morning, with the continuation three in the afternoon. I wonder why they did that? Anyway, it turns out there is another flight at lunch time that serves just as well, and I am now rebooked for that. And successfully checked in as well, I am pleased to say.
BA was kind enough to let me enter the lounge, which provides Internet access, so I can went my frustration here. Thank you for that, at least.
- The British Airways online check-in system should have told me what the problem was.
- I should have thought of checking my ESTA validity.
- The Internet machine should have accepted my second coin.
- The ESTA processing form should have said payment is required before departure.
- The travel agent should have booked the later flight; really, I wonder why they did not?
- Thank God they didn’t!
Anyway, I think I can leave that topic now, to return to the Revit API. I already gave a brief introduction to the Revit 2011 Failure API and the ErrorHandling SDK Sample which demonstrates its use.
By the way, maybe the story above just illustrates my real-world failure API taking control of the snafu?
One of the most common uses of this API is to disable undesired warning and error messages. Previously, one way to achieve this was to use an older and simpler approach based on the DialogBoxShowing event. The Failure API provides a much more powerful and complete solution, as we showed in the discussions on editing elements inside groups and suppressing an unwanted dialogue.
Now Joe Ye of Autodesk Beijing wrote the following more extensive introduction to the Failure API, including several interesting examples:
Failure Posting and Handling API
Revit posts failure messages which may report either a warning or an error when there are issues in the model. For example, when two walls partially overlap, Revit displays the following warning message:
Revit 2011 exposes the Failure API for posting and handling these kinds of failures. Failure posting APIs can be used to define and register new custom failures. Custom failures can be posted in your own plug-ins. Failure handling APIs can delete or resolve Revit failures when an operation on the model has been submitted. You can do something to the model during the handling process. If failures are handled by the failure handling API, users might not see the warning or error dialogs at all as if everything goes well. In fact, these failures are handled by your application.
Failure Posting
The failure posting API is easy to use. A new failure definition can be registered in the OnStartup method of an external application, and then the failure severity and resolution type can be set. Here is the code showing the registration of a warning. Once custom failings have been registered, they can be posted in your program. Here is a code fragment to register a custom failure:
public Result OnStartup( UIControlledApplication a ) < // Create failure definition Ids m_idWarning = new FailureDefinitionId( new Guid( "0C3F66B5-3E26-4d24-A228-7A8358C76D39" ) ); // Create failure definition and add resolution m_fdWarning = FailureDefinition.CreateFailureDefinition( m_idWarning, FailureSeverity.Warning, "I am the warning." ); m_fdWarning.AddResolutionType( FailureResolutionType.MoveElements, "MoveElements", typeof( DeleteElements ) ); return Result.Succeeded; >
The Document.PostFailure method is used to notify the document of a problem.
Here is a code fragment to post a custom failure:
transaction.Start(); FailureMessage fm = new FailureMessage( m_idWarning ); m_doc.PostFailure( fm ); transaction.Commit();
Failures will be validated and possibly resolved at the end of the transaction.
Failure Handling
Normally posted failures are processed by Revit’s standard failure resolution UI at the end of transaction. The user is presented information and options in the UI to deal with the failures. Besides, if operations on the document require some special treatment for certain errors (or even all possible errors), you can customize automatic failure resolution.
- Failure pre-processing (IFailuresProcessor)
- Failure processing event (FailureProcessing event)
- Final processing (IFailureProcessor)
These steps are called one by one in a cycle. Each of these three steps can control what happens and whether to continue to the next step by returning different FailureProcessingResults. Corresponding with these three steps, there are two interfaces and an event to implement failure handling. If you know the transaction, IFailuresPreprocessor can be used. To handle all possible errors, you can use the FailuresProcessing event. Finally, the IFailuresProcessor interface offers the ability to completely replace the standard failure processing.
Implement IFailuresPreprocessor interface and its method PreprocessFailures to handle failures. Register and define an event handler to handle all failures. Implement IFailureProcessor interface and its ProcessFailures method to implement final failure handling.
In each step, we can delete elements, delete a warning, and resolve or post a failure. All actions that can be taken are wrapped in the FailuresAccessor class.
Handling a Warning
Here is a sample showing how to handle a warning posted by deleting the last instance of a linked Revit model. The following warning message is displayed to the user if no failure handling mechanism has been added:
Revit users can manually click one of the three buttons to respond to the warning and dismiss the dialog. Instead, we will use failure handling APIs to mimic the manual click. First we use an event to handle the warning.
You can define the event handler and register to the FailuresProcessing event in an external command like this:
public class RegisterFailureEvent : IExternalCommand < public Result Execute( ExternalCommandData commandData, ref string messages, ElementSet elements ) < UIApplication app = commandData.Application; Document doc = app.ActiveUIDocument.Document; app.Application.FailuresProcessing += new EventHandlerFailuresProcessingEventArgs>( OnFailuresProcessing ); return Result.Succeeded; > /// /// Define event handler. /// private void OnFailuresProcessing( object sender, FailuresProcessingEventArgs e ) < FailuresAccessor failuresAccessor = e.GetFailuresAccessor(); String transactionName = failuresAccessor.GetTransactionName(); IListFailureMessageAccessor> fmas = failuresAccessor.GetFailureMessages(); if( fmas.Count == 0 ) < // FailureProcessingResult.Continue is to let // the failure cycle continue next step. e.SetProcessingResult( FailureProcessingResult.Continue ); return; > // If manually delete an element, the // transaction name is 'Delete Selection' // if the failure is caused by deleting element. if( transactionName.Equals( "Delete Selection" ) ) < foreach( FailureMessageAccessor fma in fmas ) < // the following line mimics clicking the // 'Remove Link' button to resolve // the failure. failuresAccessor.ResolveFailure( fma ); // the following line mimics clicking the // 'Ok' button by just deleting the warning. //failuresAccessor.DeleteWarning(fma); > e.SetProcessingResult( FailureProcessingResult.ProceedWithCommit ); return; > e.SetProcessingResult( FailureProcessingResult.Continue ); > >
The code in the event handler above shows two options to handle the warning. The uncommented one resolves the warning, so that both the linked model and the linked file are removed. If you comment the line saying ‘failuresAccessor.ResolveFailure(fma)’ and uncomment the ‘failuresAccessor.DeleteWarning(fma)’ one, the linked model is removed and the linked file remains.
- Build the code in a project, and create an add-in manifest file to load it into Revit.
- Start Revit and create a new document.
- Run the external command to register the event handler.
- In the current Revit document, import a Revit model.
- Delete the linked model using Revit’s ‘Delete’ command.
- You will see the expected result.
Pre-processing a Warning
Now we will talk about how to pre-process warning. To use the IFailuresPreprocessor interface to handle failures, we need to register the interface derived class to transaction. Then the derived interface class will be triggered when the failure is posted.
[TransactionAttribute( TransactionMode.Manual )] [RegenerationAttribute( RegenerationOption.Manual )] public class FailureHandle : IExternalCommand < public Result Execute( ExternalCommandData commandData, ref string messages, ElementSet elements ) < UIApplication app = commandData.Application; Document doc = app.ActiveUIDocument.Document; Transaction trans = new Transaction( doc, "DeleteLinkedModel" ); trans.Start(); FailureHandlingOptions options = trans.GetFailureHandlingOptions(); MyPreProcessor preproccessor = new MyPreProcessor(); options.SetFailuresPreprocessor( preproccessor ); trans.SetFailureHandlingOptions( options ); Selection sel = app.ActiveUIDocument.Selection; Reference ref1 = sel.PickObject( ObjectType.Element, "Please pick a linked model instance" ); Element elem = ref1.Element; doc.Delete( elem ); trans.Commit(); return Result.Succeeded; > > /// /// Define a failure preprossor to handle failure /// public class MyPreProcessor : IFailuresPreprocessor < FailureProcessingResult IFailuresPreprocessor.PreprocessFailures( FailuresAccessor failuresAccessor ) < String transactionName = failuresAccessor.GetTransactionName(); IListFailureMessageAccessor> fmas = failuresAccessor.GetFailureMessages(); if( fmas.Count == 0 ) < return FailureProcessingResult.Continue; > // We already know the transaction name. if( transactionName.Equals( "DeleteLinkedModel" ) ) < foreach( FailureMessageAccessor fma in fmas ) < // ResolveFailure mimics clicking // 'Remove Link' button . failuresAccessor.ResolveFailure( fma ); // DeleteWarning mimics clicking 'Ok' button. //failuresAccessor.DeleteWarning( fma ); > return FailureProcessingResult .ProceedWithCommit; > return FailureProcessingResult.Continue; > >
Failure pre-processing can only be used for the failures caused by actions in your transaction. In the sample, we delete the linked model by picking it and then delete it in our transaction. The core code to handle the failure is the same as the event method above.
Further Reading
This article is a general introduction to the failure posting and handing mechanism. More resources are provided in the Revit 2011 SDK. The sample project ErrorHandling shows more Failure API functionality. A detailed description of the Failure API is provided in Chapter 26 of the Developer Guide in the Revit 2011 SDK. For a reference to all relevant classes and methods, please refer to the Revit API help file RevitAPI.chm.
Many thanks to Joe for the useful overview and samples!