Transaction Failure in Scripted C# Component

Hi, I have been working with the C# node to drive Rhino.Inside, specifically trying to use Rhino.Inside to create railings. I am trying to avoid using the EnqueueAction method, because I want to return a list of IDs for the created objects.
I have a C# node that works to open a new transaction and create a wall using the DB.Wall.Create method. It commits successfully. However, when I try to swap out the Wall.Create with a DB.Architecture.Railing.Create method, it triggers some Revit errors. These errors pop up one at a time, each with their own little window which needs to be dismissed, one per component iteration (see attached screenshot). The errors don’t provide any information about what went wrong, and the exception from the C# component also does not provide much info.
I’ve included a working version of the script that uses the EnqueueAction method to generate valid railings (with the same inputs) so I don’t think all of the arguments in the Railing.Create method should be working.

I’m wondering if anyone has tips on:

  • Getting a better (ie more descriptive) error message/ exception message
  • Grouping the error messages together ideally or hiding them in Revit and instead displaying them on the GH component, so it runs without the user having to click through each popup.
  • How to make the Railing.Create method work inside a transaction and/or advice on why it might not!

GH file is attached, with the C# nodes. I’m using RIR with Revit 2020, and obviously the .dll files need to be added through the Manage Assemblies feature.

Transaction Error|689x293

Railing Maker Approaches - revit 2020.gh (16.4 KB)

I’m back with some updates! Sharing here in case it helps others…

The script now accomplishes a few of the previously stated goals:
1- I found the right settings to capture the warnings and errors, and output them on the component, (then deletes them so they don’t need to be dismissed individually one at a time by the user).
2- It casts the exception to its more specific type, and extracts more info.

However, in the exception, the HR type is HResult: -2146233088… aka 0x80004005 aka E_FAIL, which is just a generic failure.

Wondering if anyone has any thoughts on this. The function succeeds with the same inputs if I use the RIR built in EnqueueAction (but then I can’t get IDs for the created objects…). Am I missing something?

Railing Maker Approaches - revit 2020.gh (23.4 KB)

using System;
using System.Collections;
using System.Collections.Generic;
using Rhino;
using Rhino.Geometry;
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using System.Linq;
using RIR = RhinoInside.Revit;
using AR = Autodesk.Revit;
using DB = Autodesk.Revit.DB;
using UI = Autodesk.Revit.UI;


public class Script_Instance : GH_ScriptInstance
{
  private void RunScript(Curve curve, int wallType, int level, bool go, ref object A, ref object B, ref object C)
  {

    if(!go){return;}

    string outbuff = "";

    var doc = RIR.Revit.ActiveDBDocument;
    DB.CurveLoop myCurveLoop = RIR.Convert.Geometry.GeometryEncoder.ToCurveLoop(curve);
    DB.ElementId railTypeId = new Autodesk.Revit.DB.ElementId(wallType);
    DB.ElementId levelId = new Autodesk.Revit.DB.ElementId(level);

    CustomFailuresPreprocessor pp = new CustomFailuresPreprocessor();

    try{
      using (var transaction = new DB.Transaction(doc, "CSharpWall"))
      {
        transaction.Start();
        this.Component.Message = "Transaction Status: " + transaction.GetStatus().ToString();

        var options = transaction.GetFailureHandlingOptions();
        options = options.SetDelayedMiniWarnings(true);
        options = options.SetForcedModalHandling(true);
        options = options.SetClearAfterRollback(true);
        options = options.SetFailuresPreprocessor(pp);

        //This is not working
        DB.Architecture.Railing.Create(doc, myCurveLoop, railTypeId, levelId);

        transaction.Commit(options);

        //The using statement will automatically rollback the changes made, if there is an exception before error
      }
    }
    catch (Exception txnErr) {
      // if any errors happen while changing the document, an exception is thrown
      // Try to ascertain the specific type and print exception info for debugging

      if(txnErr is AR.Exceptions.ArgumentException){
        AR.Exceptions.ArgumentException ex = (AR.Exceptions.ArgumentException) txnErr;
        Print(ex.Message);
        Print(ex.TargetSite.ToString());
        Print(ex.Source);
        Print(ex.ParamName);
        Print(ex.Message);
        Print(ex.InnerException.ToString());
        Print(ex.HResult.ToString());
        Print(ex.HelpLink);
        Print(ex.FunctionId.ToString());
        Print(ex.Data.ToString());
        Print(ex.ToString());
      }

      else if(txnErr is AR.Exceptions.ArgumentNullException){
        outbuff += "2\n";
      }
      else if(txnErr is AR.Exceptions.ModificationForbiddenException){
        outbuff += "3\n";
      }
      else if(txnErr is AR.Exceptions.ModificationOutsideTransactionException){
        outbuff += "4\n";
      }
      else if (txnErr is AR.Exceptions.InvalidOperationException){
        outbuff += "[5] Message: " + txnErr.Message + "\n";
        outbuff += "[5] Source: " + txnErr.Source.ToString() + "\n";
      }
      else if (txnErr is AR.Exceptions.InternalException ){
        //Seems like it is an "Internal Exception" type
        AR.Exceptions.InternalException ex = (AR.Exceptions.InternalException) txnErr;
        outbuff += "[5] Exception: " + ex.GetType() + "\n";
        outbuff += "[5] Data: " + ex.Data + "\n";
        outbuff += "[5] FunctionId: " + ex.FunctionId + "\n";
        outbuff += "[5] HelpLink: " + ex.HelpLink + "\n";
        outbuff += "[5] HResult: " + ex.HResult + "\n";
        outbuff += "[5] InnerException: " + ex.InnerException + "\n";
        outbuff += "[5] Message: " + ex.Message + "\n";
        outbuff += "[5] Source: " + ex.Source.ToString() + "\n";
        outbuff += "[5] StackTrace: " + ex.StackTrace + "\n";
        outbuff += "[5] TargetSite: " + ex.TargetSite + "\n";
      }
      else {
        outbuff += "[5] Exception: " + txnErr.GetType() + "\n";
        outbuff += "[5] Message: " + txnErr.Message + "\n";
        outbuff += "[5] Source: " + txnErr.Source.ToString() + "\n";
      }
    }

    A = outbuff;
    B = pp.FailureList;

  }

  // <Custom additional code> 
  public class CustomFailuresPreprocessor : DB.IFailuresPreprocessor
  {

    public List<string> FailureList = new List<string>();

    public DB.FailureProcessingResult PreprocessFailures(DB.FailuresAccessor failuresAccessor)
    {
      FailureList.Add("This is a list of your personal failures: ");

      var severity = failuresAccessor.GetSeverity();
      if (severity == DB.FailureSeverity.Warning)
      {
        foreach (var warning in failuresAccessor.GetFailureMessages(DB.FailureSeverity.Warning))
          FailureList.Add("Warning: " + warning.GetDescriptionText());

        failuresAccessor.DeleteAllWarnings();
        return DB.FailureProcessingResult.ProceedWithCommit;
      }
      if (severity == DB.FailureSeverity.Error)
      {
        foreach (var error in failuresAccessor.GetFailureMessages(DB.FailureSeverity.Error)){
          FailureList.Add("Error: " + error.ToString());
          FailureList.Add(" - Description text: " + error.GetDescriptionText());
          FailureList.Add(" - Number of Resolution options: " + error.GetNumberOfResolutions());
          FailureList.Add(" - Default Resolution caption: " + error.GetDefaultResolutionCaption());
          //FailureList.Add(" - Default Resolution type: " + error.GetDefaultResolutionType().ToString());
          FailureList.Add(" - Current Resolution type: " + error.GetCurrentResolutionType().ToString());
        }

        foreach (var warning in failuresAccessor.GetFailureMessages(DB.FailureSeverity.Warning))
          FailureList.Add("Warning: " + warning.GetDescriptionText());
      }

      return DB.FailureProcessingResult.ProceedWithRollBack;
    }
  }

  // </Custom additional code> 
}

Hi,

There is a conflict with Revit MFC context and Rhino MFC context.
We are working to fix this in Rhino.

But meanwhile there is a method called RhinoInside.Revit.Rhinoceros.InvokeInHostContext that should help here.
As EnqueueAction InvokeInHostContext is a temporary workaround.
EnqueAction was there when RiR was unable to keep the Revit API context, now is no more needed and will be removed. Same will happen to InvokeInHostContext once we address this.

public class Script_Instance : GH_ScriptInstance
{
  private void RunScript(Curve Curve, object Type, object Level, ref object Railing)
  {
    if(Curve == null || Type == null || Level == null)
      return;

    Railing = RhinoInside.Revit.Rhinoceros.InvokeInHostContext
    (
      () => CreateRailing(Revit.ActiveDBDocument, Curve.ToCurveLoop(), Type as DB.ElementType, Level as DB.Level)
    );
  }

  private DB.Architecture.Railing CreateRailing(DB.Document doc, DB.CurveLoop curveLoop, DB.ElementType type, DB.Level level)
  {
    DB.Architecture.Railing result = null;
    using(var transaction = new DB.Transaction(doc, Component.Name))
    {
      transaction.Start();

      result = DB.Architecture.Railing.Create(doc, curveLoop, type.Id, level.Id);

      transaction.Commit();
    }

    return result;
  }
}

Here RiR-CreateRailing.gh (26.4 KB) the Grasshopper definition with a sample on using this method.

Thanks Kike, this is awesome! After posting yesterday, I was experimenting with implementing a Func Queue, sort of a similar direction.

public static Queue<Func<object,int>> docFuncActions = new Queue<Func<object,int>>();

Really appreciate you pointing me the right direction, thanks

RH-56980 is fixed in the latest WIP