This is a general question, but I’m trying to solve a specific problem. I’m currently working on a graph whose end result is to move air terminals by a vector in order to align them with the ceiling grid. The graph works fine, except in some cases where the movement causes Revit to throw an error. Most likely error being “The duct/pipe has been modified to be in the opposite direction causing the connections to be invalid.” So my goal is create a python script that “tries” to move the air terminal unless Revit throws an error. If Revit throws an error, I want to hide the warning popup from the user (but log the error and relevant ID’s for after the graph), roll back the transaction, and then move on to the next action. I’m not trying to solve the error or ignore the error, just cancel the one action that caused it.
I’m a python novice so I’ve been stumbling through other examples that do something close to what I’m trying to do (though some of them are in c# and vb.net). The code moves the air terminals as it’s supposed to and I think I’m close to having what I want. But the node still isn’t suppressing the warning or rolling back the transactions which is my entire reason for doing this in Python and not using the out of the box node. I wish I could ask something more specific, but why isn’t my code doing what I think I’m asking it to do? Where am I going wrong? I’ve included the whole code below and it’s full of comments that explain what I think the code is doing. Sorry if the comments are over-kill but it helps me understand what I think I’m doing.
# TRANSLATION SCRIPT. The intent of this script is to move air terminals by a vector unless the movement causes an error in Revit. #If the movemnet causes an error, the intent is to "skip" that action, provide feedback to the user, and move on to the next air terminal. import sys import clr clr.AddReference('ProtoGeometry') from Autodesk.DesignScript.Geometry import * clr.AddReference('RevitServices') import RevitServices from RevitServices.Persistence import DocumentManager from RevitServices.Transactions import TransactionManager clr.AddReference("RevitNodes") import Revit clr.ImportExtensions(Revit.Elements) clr.ImportExtensions(Revit.GeometryConversion) clr.AddReference("RevitAPI") import Autodesk from Autodesk.Revit.DB import * clr.AddReference("System") from System.Collections.Generic import * #First we create a new class. A class is a means of grouping data and functionality. #So we are creating a new type of object which will allow us to make new instances of this class later #The name of this class is WarningSkipper and it is the new base class being created. #The () is a means of allowing the class to inhereit attributes of an existing base class, in this case, the "IFailuresPreprocessor" class from the Revit API. #In doing so, we can quickly add a lot of functionality to our new class and add a few new attributes as needed. #Here, we are only going to create one new function (method) called Preproccess Failures. #there are two arguments of the new method: self, and FailuresAccessor. self represents the current instance of the class calling the method. FailureAccessor is a class from the Revit API that provides access to failure information. class WarningSkipper (IFailuresPreprocessor): #create a new attribute called feedback def __init__(self): self.feedback="" #Name a new method & define it's arguements. def PreproccessFailures(self,FailuresAccesor): #I think this gets a list of the all errors and assigns them to an index? fail_list=List[FailureMessageAccessor]() fail_message_list=failuresAccessor.GetFailureMessages().GetEnumerator() #I meaning to say of there are no errors, then continue. Not sure if I need this because this class #might only be called in the event of an error. if fail_message.Count==0: return FailureProcessingResult.Continue #But if there is an error, I want to roll back the transaction, delete the warnings, and provide feedback about what the error messages were. else: for failure in fail_message_list: failureDescrition=failure.GetDescriptionText() failingElements=failure.GetFailingElements() self.feedback="No action taken because Revit said '"+failureDescription+"' about element(s):"+failingElements failuresAccessor.DeleteAllWarnings() return FailureProcessingResult.ProceedWithRollBack #end of class definition # The inputs to this node will be stored as a list in the IN variables. airTerminals = UnwrapElement(IN) #I think we have to unwrap because when the elements are in dynamo they are in some wrapping until the end of the script vectorList = IN #this list structure has match with the air terminal list #define variables used in the for loop below. doc = DocumentManager.Instance.CurrentDBDocument i = 0 failureOptions= #this one is only for troubleshooting failuresMessages= #I want to provide feedback to the user # for each air terminal in the list (each comment line below corresponds to 1 line of the for loop): #get vector of current index and turn Dynamo vectors in Revit vectors #get elementID object from air terminal ID string #get new transaction instance (which I think is the exchange of data/action with Revit) #Get the failure handling options for the current transaction. These are options to control how failures (if any) should be hanndled at the end of transaction. #create a new instance of the warningskipper class that was defined above #sets the callback function to be invoked if failure processing occurs during the transaction. So if error occurs, set preprocess failure as defined in warningSkipper for eachTerminal in airTerminals: vector = vectorList[i].ToRevitType() terminalID=ElementId(eachTerminal) transaction=Transaction(doc,"move air terminal") failureHandlingOptions=transaction.GetFailureHandlingOptions() warningSkipper=WarningSkipper() failureHandlingOptions.SetFailuresPreprocessor(warningSkipper) #begin the transaction with Revit transaction.Start() #Move the element (air terminal) by the vector. If this causes an error, the code callsback to the revised failure handling option ElementTransformUtils.MoveElement(doc,terminalID,vector) failureOptions.append(transaction.GetFailureHandlingOptions()) #This is just here for troubleshooting #I'm trying to say if there is feedback other than blank, then append the message to the failuresMessage list if warningSkipper.feedback!="": failureMessages.append(warningSkipper.feedback) #Complete the transaction with the appropriate failurehandling option transaction.Commit(); i+=1 # Assign outputs to the OUT variable. The failure options list is just for troubleshooting. OUT = [airTerminals,failuresMessages,failureOptions]
I’m working on this in a small isolated testing script where I have a list of vectors that will intentionally cause an error. When I run this, the errors still appear and I’m not seeing a log of the errors I was hoping to see in list 1 (see snip below)