"Attempt to modify the model outside of transaction": how to fix it
The error message everyone sees at some point. What it means, how to fix it in three lines, and the less obvious variants.
Short answer: your script changes something in the model (for example with Set) without an open transaction. Put the change in a with revit.Transaction("name"): block, or start a Transaction yourself and finish it with Commit(). Reading is allowed without a transaction; changing never is.
The error message
In pyRevit the output window looks roughly like this. The exact layout differs per version and engine; the last line is always the same:
IronPython Traceback:
Traceback (most recent call last):
File "C:\...\MyButton.pushbutton\script.py", line 9, in <module>
Exception: Attempt to modify the model outside of transaction.
Script Executor Traceback:
Autodesk.Revit.Exceptions.ModificationOutsideTransactionException: Attempt to modify the model outside of transaction.What it means
Revit wants every change to a model to happen inside a transaction: a defined step with a name. You can undo that step with Ctrl+Z, and if something goes wrong halfway, Revit rolls back the whole step. That keeps your model intact. Reading changes nothing and is allowed without a transaction; every change, even a single parameter, is not.
This is the wrong version. It puts a comment on a pipe, but without a transaction:
from Autodesk.Revit.DB import *
doc = __revit__.ActiveUIDocument.Document
pipe = (FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_PipeCurves)
.WhereElementIsNotElementType()
.FirstElement())
p = pipe.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
p.Set("checked")Autodesk.Revit.Exceptions.ModificationOutsideTransactionException: Attempt to modify the model outside of transaction.The fix in three lines
With pyRevit you put the change in a with block. It starts the transaction, runs the change and closes it properly:
from pyrevit import revit, DB
pipe = (DB.FilteredElementCollector(revit.doc)
.OfCategory(DB.BuiltInCategory.OST_PipeCurves)
.WhereElementIsNotElementType()
.FirstElement())
p = pipe.get_Parameter(DB.BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
with revit.Transaction("Fill in comment"):
p.Set("checked")
print(p.AsString())checkedWithout pyRevit, use Transaction(doc, "name") with Start(), Commit() and, on an error, RollBack(). A full example is in Read and set Revit parameters.
Variants you will run into
The change sits just outside the block
Python looks at indentation. If the line with Set is not indented under the with, the transaction is already closed when it runs:
from pyrevit import revit, DB
pipes = (DB.FilteredElementCollector(revit.doc)
.OfCategory(DB.BuiltInCategory.OST_PipeCurves)
.WhereElementIsNotElementType()
.ToElements())
with revit.Transaction("Fill in comment"):
first = pipes[0]
first.get_Parameter(DB.BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS).Set("done")Autodesk.Revit.Exceptions.ModificationOutsideTransactionException: Attempt to modify the model outside of transaction.A function that is called later
You write a helper function that sets a parameter and call it from somewhere without a transaction. The error then points at the line inside the function, not at the call. So agree on who opens the transaction: either the function itself, or always the code that calls it. In your own library "the caller" is usually handiest, so you can bundle several changes into one step.
A transaction on the wrong document
A transaction belongs to one document. If you work with several models at once, the transaction must be open on the document you are changing.
One transaction at a time
Only one transaction can be open per document. If you open another one with the pyRevit helper inside a revit.Transaction, it works within the first. For more complex cases the Revit API also has SubTransaction (a step within a transaction) and TransactionGroup (several transactions as one undo step). You rarely need those for ordinary buttons.
And if something goes wrong inside the block?
Then pyRevit rolls everything back, writes a line to the log and still shows you the error. Your model stays intact:
ERROR [pyrevit.revit.db.transaction] Error in Transaction Context. Rolling back changes. | ZeroDivisionError:division by zero