First up: a quick note: These APIs were added in 2023, and updated in 2024. As a result it won’t work for those of us who haven’t updated yet. So for anyone who wants the new toys, make sure you upgrade first.
Now then… to get the changes between versions of a thing you need a basis of comparison. This is what the versionGUID input is for. However that version GUID can’t be pulled from the current document version, you need an old one. So functionally you’ll likely want to provide a path to the previous version of the document. With that you can either get the BasicFileInfo and pull the DocumentVersion from there, or open the file in the background and get the info from the background opened document. Usually I don’t recommend keeping documents open in the background, but it could be useful here… More on that in a few.
Once you have the document version, you need to pull the VersionGUID as that’s how Revit keeps track of everything for comparison. From there the doc.GetChangedElements(oldDocumentVersionGUID) method should work. The resulting object is a DocumentDifference object, which has methods to pull the modified, added, and deleted element ids. From there you can get the elements from the model fairly simply using the doc.GetElement(id) method.
However, this won’t work on the deleted elements, as they no longer exist in the active document. Instead you’d have to pull them from the background opened document (which is why I thought keeping it in memory might be a good thing here). With the old document open in memory you can do soemthing like this: deletedElements = [oldDoc.GetElements(id) for id in deletedElementIds] which will give you full access to the elements with anything they may be able to do with them in the Python environment, and in some cases beyond as I am showing here (Revit 2024.1 with Dynamo 2.18):
import clr #import the common language runtime to the python environment
clr.AddReference("RevitServices") #add the revit services library to the CLR
import RevitServices #import the Revit Services namespace to the Python environment
from RevitServices.Persistence import DocumentManager #import the document manager class to the Python environment
from RevitServices.Transactions import TransactionManager #import the transaction manager class to the Python environment
clr.AddReference("RevitAPI") #add the Revit API to the CLR
import Autodesk #import the Autodesk namespace
from Autodesk.Revit.DB import OpenOptions, ModelPathUtils, WorksetConfiguration, WorksetConfigurationOption #import the relevant sections of the Revit API
doc = DocumentManager.Instance.CurrentDBDocument #get the active document
app = DocumentManager.Instance.CurrentUIApplication.Application #get the applicaton
oldModelPath = IN[0] #path for the original model from the Dyanmo environment
openOpts = OpenOptions() #create an open options object
worksets = WorksetConfiguration(WorksetConfigurationOption.OpenAllWorksets) #create a workset configuration to open all worksets
openOpts.SetOpenWorksetsConfiguration(worksets) #set the open options workset configuration to open all workset
oldModelPath = ModelPathUtils.ConvertUserVisiblePathToModelPath(oldModelPath) #get the old model path as a Revit ,model path object
oldDoc = app.OpenDocumentFile(oldModelPath,openOpts) #open the old document
oldDocVer = oldDoc.GetDocumentVersion() #get the document version
oldDocGUID = oldDocVer.VersionGUID #get the document version GUID
oldDocNumb = oldDocVer.NumberOfSaves #get the document version number
oldDocCreationGUID = oldDoc.CreationGUID #get the document creation GUID
docVer = doc.GetDocumentVersion() #get the active document version
docNumb = docVer.NumberOfSaves #get the active docuemnt version number
docGUID = docVer.VersionGUID #get the active document version GUID
docCreationGUID = doc.CreationGUID #get the active document creation GUID
if not docCreationGUID == oldDocCreationGUID: #if the active document creation GUID is not the same as the old document creation GUID
OUT = "The two documents do not share a common history, so document comparison will not work." #inform the user the models dont' have a common history
oldDoc.Close(False) #close the old document
elif not docNumb > oldDocNumb: #if the active document version number is not greater than the old document version number
OUT = "The comparison model is newer than the active document so changes cannot be compared." #inform the user that the comparison document is newer than the active document
oldDoc.Close(False) #close the old document
else: #otherwise
docDiff = doc.GetChangedElements(oldDocGUID) #get the document differential
newElems = [doc.GetElement(id) for id in docDiff.GetCreatedElementIds()] #get the new elements
modElems = [doc.GetElement(id) for id in docDiff.GetModifiedElementIds()] #get the modified elements
delElems = [oldDoc.GetElement(id) for id in docDiff.GetDeletedElementIds()] #get the deleted elements
OUT = {"New Elements": newElems, "Modified Elements": modElems, "Deleted Elements": delElems} #return the modified elements to the Revit DB
Hopefully this clears up any confusion on how to use this new set of APIs.
Works also with linked models (I admit that this feature gave me a hard time)
Code
import clr #import the common language runtime to the python environment
clr.AddReference("RevitServices") #add the revit services library to the CLR
import RevitServices #import the Revit Services namespace to the Python environment
from RevitServices.Persistence import DocumentManager #import the document manager class to the Python environment
from RevitServices.Transactions import TransactionManager #import the transaction manager class to the Python environment
clr.AddReference("RevitAPI") #add the Revit API to the CLR
import Autodesk #import the Autodesk namespace
from Autodesk.Revit.DB import OpenOptions, ModelPathUtils, WorksetConfiguration, WorksetConfigurationOption, Document #import the relevant sections of the Revit API
doc = DocumentManager.Instance.CurrentDBDocument #get the active document
app = DocumentManager.Instance.CurrentUIApplication.Application #get the applicaton
oldlinkInstance = UnwrapElement(IN[0]) #path for the original model from the Dyanmo environment
oldDoc = oldlinkInstance.GetLinkDocument()
oldDocVer = Document.GetDocumentVersion(oldDoc) #get the link document version
oldDocGUID = oldDocVer.VersionGUID #get the document version GUID
docVer = Document.GetDocumentVersion(doc) #get the active document version
docGUID = docVer.VersionGUID #get the active document version GUID
try: #otherwise
docDiff = doc.GetChangedElements(oldDocGUID) #get the document differential
except Exception as ex:
OUT = ex.message
else:
newElems = [doc.GetElement(id) for id in docDiff.GetCreatedElementIds()] #get the new elements
modElems = [doc.GetElement(id) for id in docDiff.GetModifiedElementIds()] #get the modified elements
delElems = [oldDoc.GetElement(id) for id in docDiff.GetDeletedElementIds()] #get the deleted elements
OUT = {"New Elements": newElems, "Modified Elements": modElems, "Deleted Elements": delElems} #return the modified elements to the Revit DB
I actually thought about going that way, but I thought it might be rather rare to link a model into itself due to stuff like overlapping elements causing issues and whatnot - hence the path. Glad to see it works.
I haven’t tried, but if I was already in the cloud I’d likely utilize the document comparison feature, which is a little bit more robust at the moment due to working with 2020 and 2021 Revit versions.
That said, I can’t see why it wouldn’t work - just have to have the model cached before you open it.
Hey Jacob, in continuation with the DocumentDiffernce topic and opening files, I’ve got a question in regards of opening and closing the file in the background. When running the script, do you always need to put in a close command or does it do it automatically at the end?
I see in this case that you put in a close command inside the if not condition as but not at the end of the script. Is it necessary that the document be passed on and verify that it is closed when finished inside and at the end of the dynamo script?
Yes, if you leave it open it continues to be there in Revit using resources even if you can’t see it. Should you try to open it again and don’t close it before you’ll also get an error.
Is the error received if tried to be open in the same script run? I’m asking because I opened the file without closing and when I have rerun the script, I didn’t get any error. Does that mean that it closes automatically?