Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Return Failure Information to Command?

8 REPLIES 8
SOLVED
Reply
Message 1 of 9
mastjaso
2277 Views, 8 Replies

Return Failure Information to Command?

Hi all, is there a way to return failure information to your main command when creating a transaction? My program is doing some error logging and creating an error report at the end of it, and I'd like to include some information from specific types of Revit failures (i.e. include if multiple instances were created in the same place).

I've looked over the failure SDK samples and forum / blog posts but have not yet come across, nor can think of a way to accomplish this. Implementing a preprocessor and passing it to the transaction is simple enough, but how do I get that information back to my main program? Since I'm not passing the preprocessor in using ref I can't save it to a property on the preprocessor for accessing after and the transaction object itself doesn't appear to contain any information about failures that occur.

Do I have to write to a file on disk with my preprocessor and then read that back with the tail end of my main program?

8 REPLIES 8
Message 2 of 9
jeremytammik
in reply to: mastjaso

Dear Mastjaso,

 

Thank you for your query.

 

You have an infinite number of options for passing information back and forth within your .NET application.

 

Writing a file to disk is definitely not an optimal solution, nor is it any easier than other, more efficient methods.

 

I would suggest using one of the following approaches:

 

  • Return value from function including error information
  • Return Boolean from function signalling failure or success and add a return argument for passing back error information
  • Implement a global error handling and logging class
  • Use .NET exceptions

 

You could use exceptions to signal error conditions back and forth within your add-in.

 

The Revit API uses this method to inform an add-in that a user has cancelled a prompt to select an element, for instance.

 

However, this is bad practice.

 

Exceptions should be exceptional:

 

 

Every exception handler is resource intensive and will significantly slow down execution and consume resources. An exception handler should be designed to handle unexpected, exceptional cases only. Exceptions should be exceptional. Therefore, it is normally sufficient to implement a single top-level handler at the level of the application mainline, e.g. in the external command Execute method.

 

On the other hand, Microsoft does recommend using exceptions for error handling after all, saying, 'The exception mechanism has a very minimal performance cost if no exception is thrown. If an exception is thrown, the cost of the stack traversal and unwinding is roughly comparable to the cost of a function call':

 

https://docs.microsoft.com/en-us/cpp/cpp/errors-and-exception-handling-modern-cpp

 

Here is a discussion comparing the two approaches:

 

https://stackoverflow.com/questions/1272342/error-handling-without-exceptions

 

However, as long as all the classes you are dealing with live within one single add-in and in the same namespace, you can also easily declare static class variables and methods to access and control errors and pass the information back and forth between one class and another. Such variables can be used to control and handle error conditions. They can be encapsulated in an error handling and logging class, if you like. Many libraries doing this are available.

 

Or, as said, implement return values from each function.

  

That provides all you need.

 

I hope this helps.

 

Best regards,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 3 of 9
mastjaso
in reply to: jeremytammik

Hi Jeremy, thank you very much for the very thorough answer, however, I believe you may not have quite understood my question (or maybe I'm misunderstanding your answer!).

I wasn't confused about how to pass data around or return data in general, but specifically to the Failure Processing stage of a Revit transaction.

As far as I can tell the general stage for this process is as follow:
      * Addin creates a transaction
      * Addin calls Transaction.Commit() to end the transaction
      * The failure preprocessor is executed.
      * Failure processing event is flagged and all failure processing event handlers are run
      * Failure processor is run
      * The transaction is now finally either committed or rollbacked by revit.
      * The addin will now execute the line of code that comes after Transaction.Commit().

My issue is that I want Revit to continue with its standard failure processing, but 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.

Message 4 of 9
RPTHOMAS108
in reply to: mastjaso

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

 

Message 5 of 9
mastjaso
in reply to: RPTHOMAS108


@RPTHOMAS108 wrote:

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.

 



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.

Message 6 of 9
RPTHOMAS108
in reply to: mastjaso

They'll continue to a further stage of the failure framework but the point of the preprosessor 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?

 

Message 7 of 9
RPTHOMAS108
in reply to: RPTHOMAS108

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 Failures 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

 

 

Message 8 of 9
mastjaso
in reply to: RPTHOMAS108

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:

class MessageReturnPreProcessor : IFailuresPreprocessor
{
    private List<string> FailureList { get; set; }

    public MessageReturnPreProcessor()
    {
        FailureList = new List<string>();
    }
    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()
    {
        StringBuilder sb = new StringBuilder();
        foreach (string s in FailureList)
            sb.AppendLine(s);
        TaskDialog.Show("Post Processing Failures:", sb.ToString());
    }
} [TransactionAttribute(TransactionMode.Manual)] [RegenerationAttribute(RegenerationOption.Manual)] public class FailureMessageGathering : IExternalCommand { public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { UIApplication uiApp = commandData.Application; Document activeDoc = uiApp.ActiveUIDocument.Document; RR_AddInShell.MessageReturnPreProcessor pp = new RR_AddInShell.MessageReturnPreProcessor(); using(Transaction t = new Transaction(activeDoc, "Marks")) { FailureHandlingOptions ops = t.GetFailureHandlingOptions(); ops.SetFailuresPreprocessor(pp); t.SetFailureHandlingOptions(ops); t.Start(); List<Element> specEqu = new FilteredElementCollector(activeDoc).OfCategory(BuiltInCategory.OST_SpecialityEquipment).WhereElementIsNotElementType().ToElements().ToList(); 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; } }

 

Message 9 of 9
jeremytammik
in reply to: mastjaso

Thanks, guys, for the interesting discussion and sharing.

 

Published for posterity here:

 

http://thebuildingcoder.typepad.com/blog/2018/01/gathering-and-returning-failure-information.html

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community