Get Added Elements from Event handler

I have a script that prompts the user to place a certain detail family using this method:

uiDoc.PostRequestForElementTypePlacement(desired_symbol)

I then want to assign some parameter values of this newly placed family instance. The trouble I have found is that the PostRequestForElementTypePlacement method creates its own transaction. So what I have been trying in order to get the newly created elements is using an event handler for the DocumentCHanged event:

            __revit__.Application.DocumentChanged += EventHandler[DocumentChangedEventArgs](docChanged)
            uiDoc.PostRequestForElementTypePlacement(desired_symbol)

and the docChanged Function looks like this:

def docChanged(sender, args):
    newEls = args.GetAddedElementIds()
    el = revit.doc.GetElement(newEls[0])
    print el

The function is working in that it does report back an element ID of the new element (newEls[0]). However, I can’t seem to grab that element from the document. I think I’m close, but getting stuck here. Any help would be greatly appreciated!

Hi Dennis,
Untested but from this sample RevitSdkSamples/SDK/Samples/DocumentChanged/CS/ChangesMonitor.cs at master · jeremytammik/RevitSdkSamples · GitHub I think you need to change the definition like so:

def docChanged(sender, args):
    newEls = args.GetAddedElementIds()
    doc = args.GetDocument()
    el = doc.GetElement(newEls[0])
    print el

Hi Jean-Marc,

That did allow me to get a hold of the elements! Now that i have that, my new problem is finding a way to set some instance parameter values for the newly placed elements. From what I understand, the “DocumentChanged” event is read only, so I can’t modify elements inside of this handler. I’m trying to push the element Ids out to a global list so that I can grab and edit them outside of the event handler in a separate transaction, but it doesn’t seem to be working:

addedElements = []
def docChanged(sender, args):
    newEls = args.GetAddedElementIds()
    addedElements.extend(newEls)

__revit__.Application.DocumentChanged += EventHandler[DocumentChangedEventArgs](docChanged)
uiDoc.PostRequestForElementTypePlacement(desired_symbol)
print addedElements

In the above code, the “print addedElements” always returns the empty list

Hi Dennis,

I’m not sure about that issue. I believe the DocumentChanged event will actually get called AFTER your script is ran. Thus, what you are trying to do might not be possible.

I see two potential options that may work for you. Anyone with more knowledge please correct if I am mistaken.

  1. Create an ExternalEvent that will be raised inside the DocumentChanged event handler. You can then use the ExternalEvent to create a new Transaction and modify the new symbol that you added.

  2. Create an IUpdater class that will listen for element additions of your desired symbol. An IUpdater will be called inside the Transaction of the Symbols placement. Meaning you can modify it’s parameters however you need.

Edit:
One more option which is the most obvious one. You could try creating the Symbol manually using NewFamilyInstance. However, I’m not familiar with the symbol your trying to place, so it may be quite tedious to replicate Revit’s default placement.

Hi Nicholas,
I don’t think that the NewFamilyInstance method will work for me. The family that I am placing is a line based detail family, so I want the user to have that manual placement ability. Document changed is definitely running during the Placing of each family instance, because I can set it to print the element ID of each placed element. I believe this is because PostRequestForElementTypePlacement Method creates its own transaction.

In terms of the IUpdater, I am not familiar with that workflow. Do you know of any python examples of this? The only examples I have found so far are C#

I tried your code out. It appears that the placement happens AFTER the script is completed, thus the documentchanged event will also happen outside of the scripts runtime.

I don’t know of any example of IUpdaters in python off the top of my head. But I use IUpdaters in my code, so I can give you example of how you might do it.

This is how the IUpdater would probably be structured. One thing you will need to keep in mind is that this will be executed ANY time the particular symbol you have defined is added to the document as long as the updater is registered and enabled. If that is not desired then you will need to unregister or disable the IUpdater after the symbol is placed. I have included potential options to achieve that in this example.

from Autodesk.Revit.DB import Element, ElementId, FamilyInstanceFilter, IUpdater, UpdaterId, UpdaterRegistry, ChangePriority


class SymbolUpdater(IUpdater):
    @classmethod
    def uuid(cls):
        # this generates a GUID unique to the class name
        import hashlib
        from System import Guid

        m = hashlib.md5()
        m.update(cls.__name__.encode('utf-8'))
        return Guid(m.hexdigest())

    def __init__(self, addin_id):
        self._updater_id = UpdaterId(addin_id, self.uuid())

    def GetUpdaterId(self):
        return self._updater_id

    def GetAdditionalInformation(self):
        return 'Not Implemented'

    def GetUpdaterName(self):
        return self.__class__.__name__

    def GetChangePriority(self):
        return ChangePriority.DetailComponents

    def register_updater(self, doc, symbol):
        if not UpdaterRegistry.IsUpdaterRegistered(self._updater_id):
            UpdaterRegistry.RegisterUpdater(self, True)
            change_type = Element.GetChangeTypeElementAddition()
            symbol_filter = FamilyInstanceFilter(doc, symbol.Id)  # not sure about this filter
            UpdaterRegistry.AddTrigger(self._updater_id, symbol_filter, change_type)

    def Execute(self, data):
        doc = data.GetDocument()
        added_elements = [doc.GetElement(e_id) for e_id in data.GetAddedElementIds()]
        # modify added element as needed here

        # do this if you only want this ran one time for the symbol you add
        # UpdaterRegistry.DisableUpdater(self._updater_id)


UIAPP = __revit__
UIDOC = UIAPP.ActiveUIDocument
DOC = UIDOC.Document

desired_symbol = DOC.GetElement(ElementId(5572516))  # replace with your symbol

updater_id = UpdaterId(UIAPP.ActiveAddInId, SymbolUpdater.uuid())
if UpdaterRegistry.IsUpdaterRegistered(updater_id):
    if not UpdaterRegistry.IsUpdaterEnabled(updater_id):
        UpdaterRegistry.EnableUpdater(updater_id)
else:
    updater = SymbolUpdater(UIAPP.ActiveAddInId)
    updater.register_updater(DOC, desired_symbol)

UIDOC.PostRequestForElementTypePlacement(desired_symbol)

Thanks Nicholas for this, it’s super helpful! I tried implementing this into a button click event on one of my XAML forms, and it doesn’t seem to do anything. Here is the click function in my form:

    def placeMarker(self, sender, args):
        if self.link and self.viewType and self.linkedView:
            UIAPP = __revit__
            UIDOC = UIAPP.ActiveUIDocument
            DOC = UIDOC.Document
            
            updater_id = UpdaterId(UIAPP.ActiveAddInId, SymbolUpdater.uuid())
            if UpdaterRegistry.IsUpdaterRegistered(updater_id):
                if not UpdaterRegistry.IsUpdaterEnabled(updater_id):
                    UpdaterRegistry.EnableUpdater(updater_id)
            else:
                updater = SymbolUpdater(UIAPP.ActiveAddInId)
                updater.register_updater(DOC, desired_symbol)
            self.Hide()
            uiDoc.PostRequestForElementTypePlacement(desired_symbol)

I didn’t alter your updater class at all, aside from putting a print function insider the “Execute” function to see if it’s happening. When I do the button click, and place some elements, I don’t get that print out, so it doesn’t seem to be working

    def Execute(self, data):
        doc = data.GetDocument()
        added_elements = [doc.GetElement(e_id) for e_id in data.GetAddedElementIds()]
        print "I WORKED"

@DennisGoff

Is your form modal or modeless?

I tested it again and noticed you can’t disable the updater in the Execute function, because the updater is not executed in the same runtime as the original script and no longer has UpdaterRegistry in the scope. We can fix that by adding an attribute to the updater class that references the UpdaterRegistry.
I didn’t notice this initially because I had it commented out for testing.

That may not be the only issue, especially if you are executing this in a modeless window.

Also if you want to modify the parameters for ANY placement of the symbol you shouldn’t bother with disabling the updater.

Here’s the updated version with what I mentioned.

class SymbolUpdater(IUpdater):
    @classmethod
    def uuid(cls):
        # this generates a GUID unique to the class name
        import hashlib
        from System import Guid

        m = hashlib.md5()
        m.update(cls.__name__.encode('utf-8'))
        return Guid(m.hexdigest())

    def __init__(self, addin_id):
        self._updater_id = UpdaterId(addin_id, self.uuid())
        self.updater_registry = UpdaterRegistry

    def GetUpdaterId(self):
        return self._updater_id

    def GetAdditionalInformation(self):
        return 'Not Implemented'

    def GetUpdaterName(self):
        return self.__class__.__name__

    def GetChangePriority(self):
        return ChangePriority.DetailComponents

    def register_updater(self, doc, symbol):
        if not UpdaterRegistry.IsUpdaterRegistered(self._updater_id):
            UpdaterRegistry.RegisterUpdater(self, True)
            change_type = Element.GetChangeTypeElementAddition()

            # not sure about this filter
            # your symbol needs to pass this filter for the updater to be called
            symbol_filter = FamilyInstanceFilter(doc, symbol.Id)
            UpdaterRegistry.AddTrigger(self._updater_id, symbol_filter, change_type)

    def Execute(self, data):
        try:
            doc = data.GetDocument()
            added_elements = [doc.GetElement(e_id) for e_id in data.GetAddedElementIds()]
            print("Worked")
            if added_elements:
                print(added_elements)

            # modify added element as needed here

            # do this if you only want this ran one time for the symbol you add
            self.updater_registry.DisableUpdater(self._updater_id)
        except Exception as ex:
            print(ex)

Thanks Nicholas, I’ll see if this changes things. My window is modal, so when I click the button, it hides the window and then starts placing the elements. Does Modal vs Modeless have an impact on how this would work?

I don’t think it should have a major impact if it’s modal. I was just making sure that was not an issue.
A modeless window just means the window is ran outside of of the Revit API context. Meaning anything that requires a Revit API context will need to use external events or event handlers.

After a bit of tinkering, it is now working! So onto my next challenge, which is how to pass data into the updater? I have a variable that is created by my form when all of the inputs have been selected ( self.sheetNumber ). I have passed this into the updater with a function “updater.GetViewInfo” with the attention of assigning that value to a parameter of the newly placed elements. This works as it does pass the value through. However, it doesn’t update the next time I run the script, and I assume this is because the updater is registered once, and then it just exists, so it doesn’t get new data. Here is the snippet where I give it the values:

    def placeMarker(self, sender, args):
        if self.link and self.viewType and self.linkedView:
            self.Hide()
            UIAPP = __revit__
            UIDOC = UIAPP.ActiveUIDocument
            DOC = UIDOC.Document

            updater_id = UpdaterId(UIAPP.ActiveAddInId, SymbolUpdater.uuid())
            if UpdaterRegistry.IsUpdaterRegistered(updater_id):
                if not UpdaterRegistry.IsUpdaterEnabled(updater_id):
                    UpdaterRegistry.EnableUpdater(updater_id)
                    #SymbolUpdater.GetViewInfo(self.sheetNumber, self.detailNumber)
            else:
                updater = SymbolUpdater(UIAPP.ActiveAddInId)
                updater.register_updater(DOC, desired_symbol)
                updater.GetViewInfo(self.sheetNumber, self.detailNumber)
            uiDoc.PostRequestForElementTypePlacement(desired_symbol)

and the Execute portion of the IUpdater Class:

   def Execute(self, data):
        try:
            doc = data.GetDocument()
            added_elements = [doc.GetElement(e_id) for e_id in data.GetAddedElementIds()]
            if added_elements:
                #print added_elements
                for el in added_elements:
                    elParams = el.Parameters
                    for p in elParams:
                        if p.Definition.Name == "Sheet Number":
                            p.Set(self.sheetNumber)

Just a note, the “self.sheetNumber” in Execute, is an attribute I made of the updater class, not to be confused with the self.sheetNumber that is defined in my form. I know that’s bad practice, and I’ll fix that once I get this all sorted