Get a list of changed Elements ? how

Hello,

i test in revit 2023 with ironpyhton2 getting changed Elements…

… so how is here the correct syntex ?

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import *

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

current = doc.GetDocumentVersion(doc)

changed = doc.GetChangedElements()

OUT = current, changed

Get document version works well, but not the changed Elements.

KR

Andreas

The GetChangedElements requires a document version GUID as an input, which is a property of the document version.

2 Likes

@jacob.small

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import *

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

current = doc.GetDocumentVersion(doc)

#g = Guid.doc

changed = doc.GetChangedElements(VersionGUID)

OUT = changed


grafik

it seems to be correct, BUT it can`t find it.

KR

Andreas

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. :slight_smile:

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):

All in the code could look something like this:

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.

2 Likes

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
6 Likes

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. :slight_smile:

1 Like

I’m curious if it works with BIM 360 collaborate (by retrieving the GUIDs of the cloud models) :thinking:

if anyone can try

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. :slight_smile:

That said, I can’t see why it wouldn’t work - just have to have the model cached before you open it.

1 Like

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?

Should only close if you add that line to your script.