IUpdater Interface / Dynamic Model Updater

That seemed to do the trick! :mage: I’m wrapping up my code and will test and report back here. Thanks again for all your help! :+1:

1 Like

I hate to take up your time like this! :pleading_face: so sorry! I am getting a strange error :thinking:

This is how I’m hoping to use this updater by the way, nothing too complicated, right? It doesn’t find the exact width and length of rooms that are off-axis or funny shapes, but for a majority of rooms it should work.

from pyrevit import EXEC_PARAMS
from pyrevit import revit, DB

def round_to_nearest_inch(n):
    return round(n * 12) / 12

doc = EXEC_PARAMS.event_args.GetDocument()

for room in revit.query.get_elements_by_class(DB.Room, doc=doc):
    w = room.LookupParameter("Width")
    l = room.LookupParameter("Length")

    rBoundingBox = room.get_BoundingBox( None )
    
    width = rBoundingBox.Max.X - rBoundingBox.Min.X
    length = rBoundingBox.Max.Y - rBoundingBox.Min.Y
    
    w.Set(round_to_nearest_inch(width))
    l.Set(round_to_nearest_inch(length))

Yeah the script looks good. I gotta debug and see why the import pyrevit fails on detecting the running app. Will do that this weekend.

Any progress?
I’m also into the IUpdater implementation and it seems that “doc-updater” is detected but I also got the error that NoneType object has no attribute 'VersionNumber". So it is not detecting the application?

1 Like

It’s been a couple of years, any updates on this?

I just have tried the doc-updater hook and it worked well, Thanks to @ErikFrits for the great tutorial he did in you tube, here you have the code he uses for testing:

# Imports
import clr
clr.AddReference('System')
from datetime import datetime
from Autodesk.Revit.DB import (BuiltInCategory, 
                              BuiltInParameter, 
                              ElementId, 
                              WorksharingUtils,
                              )

# Variables
sender = __eventsender__
args = __eventargs__

doc = args.GetDocument()

# Get element ids for modified, deleted and new
modified_el_ids = args.GetModifiedElementIds()
deleted_el_ids = args.GetDeletedElementIds()
new_el_ids = args.GetAddedElementIds()

modified_el = [doc.GetElement(e_id) for e_id in modified_el_ids]

# IUpdater aiming modified elements
allowed_cats = [ElementId(BuiltInCategory.OST_Windows), ElementId(BuiltInCategory.OST_Doors), ElementId(BuiltInCategory.OST_MechanicalEquipment)]
for el in modified_el:
    if el.Category.Id in allowed_cats:
        # Update XYZ Coordinates
        p_com = el.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
        pt = el.Location.Point
        x = pt.X
        y = pt.Y
        z = pt.Z
        coord = "{}, {}, {}".format(x, y, z)
        p_com.Set(coord)

        # Add Timestamp to edited elements
        timestamp = datetime.now()
        f_timestamp = timestamp.strftime("%Y-%m-%d %H-%M-%S")
        wti = WorksharingUtils.GetWorksharingTooltipInfo(doc, el.Id)
        last = wti.LastChangedBy

        value = "{} (at {})".format(last, f_timestamp)
        p_last = el.LookupParameter('Last Modified By')
        if p_last:
            p_last.Set(value)

        # Update mirror state
        value = 'Mirrored' if el.Mirrored else 'Not Mirrored'
        p_mirrored = el.LookupParameter('Is Mirrored')
        if p_mirrored:
            p_mirrored.Set(value)

Please go to Erik you tube channel to see how this specific example works, since it involves some shared parameters creation.

@Gil_Granados Happy to help!

If someone else decides to work with IUpdater, please ensure your code is as efficient as possible, because it will be run a lot of times!

I’m also implementing an updater for my extension but i cannot manage to trigger it with new elements. i followed @ErikFrits tutorial and it fires on element edits but not on new elements (or deleted elements, but that’s not what i’m looking for).
for now i implemented an IUpdater as a c# document revit macro but id really like to have it in my extension as python code cause the the updater is crucial to my overall features and macros could be disabled crippling my entire architecture.
so my question is, am i the only one that’s having issues on usign the updater hook on new elements?

Rather than using the Hook for the IUpdater I tend to build and call custom discreet IUpdaters that are specific to the elements or task I wish to monitor - not that there is anything inherently wrong with using the hook.

As an example below an updater that is called when a new view is created that automatically populates the username into an available parameter. Its not perfect (It was the first IUpdater I ever wrote) but it should get you started.

 `# -*- coding: UTF-8 -*-
#-----------IMPORTS-----------
# ---COMMON---
import os

# --- DOT NET---
from System import Guid

# ---PYREVIT---
from pyrevit import DB, HOST_APP, script

#-----------VARIABLES-----------
app = HOST_APP.app
GUID = Guid("4b341769-2ff1-4abc-8884-925153e77660") 				#GUID for the I-UPDATER GENERATED WITH GUID GENERATOR
username_os = os.environ.get("USERNAME","user.name").title() 		#GET USER ID NAME FROM WINDOWS (CLEANER THAN REVIT USERNAME)
username_revit = app.Username 										#GET USERNAME FROM REVIT

#--- CLASS & DEFINITIONS---
class NewViewsUpdater(DB.IUpdater):

	def __init__(self,updater_id):
		self.updaterID = DB.UpdaterId(updater_id, GUID)

	def GetUpdaterId(self):
		return self.updaterID

	def GetUpdaterName(self):
		return "New View Updater"

	def GetAdditionalInformation(self):
		return "Adds current user Username to new Views"

	def GetChangePriority(self):
		return DB.ChangePriority.Views

	def Execute(self,updated_data):
		if not script.get_envvar('PAUSEUPDATERS_ENV_VAR'):
			#GET DATA FROM UPDATER
			try:
				doc = updated_data.GetDocument()
				workshared = doc.IsWorkshared
				element_IDs = updated_data.GetAddedElementIds()
			except:
				return

			#START SUB-TRANSACTION IF ELEMENTS RETURNED
			if element_IDs:
				t = DB.SubTransaction(doc)
				t.Start()
				try:
					for ID in element_IDs:
						view_element = doc.GetElement(ID)
						created_by = username_revit

			# CHECK FOR WORK-SHARING OWNER STATUS
						if workshared:
							tooltip = DB.WorksharingUtils.GetWorksharingTooltipInfo(doc, view_element.Id)
							created_by = tooltip.LastChangedBy if tooltip.LastChangedBy != "" else tooltip.Owner

			# SET PARAMETERS
						if created_by == username_revit and not view_element.IsTemplate: # Only run if you CURRENTLY own the VIEW and the VIEW is NOT a TEMPLATE
							parameter_names = ["⌂ Owner│View", "• Owner│View", "• View│Owner"]
							for name in parameter_names:
								parameter = view_element.LookupParameter(name)
								if parameter is not None and not parameter.IsReadOnly:
									parameter.Set(username_os)
								parameter.Set(username_os) if parameter else None
					t.Commit()
				except Exception as e:
					t.RollBack()
		else:
			script.exit()


def register_view_updater(app):
	updater_id = app.ActiveAddInId
	updater = NewViewsUpdater(updater_id)

	if not DB.UpdaterRegistry.IsUpdaterRegistered(DB.UpdaterId(updater_id, GUID)):
		DB.UpdaterRegistry.RegisterUpdater(updater, True)
		updater_id = updater.GetUpdaterId()
		category_filter = DB.ElementCategoryFilter(DB.BuiltInCategory.OST_Views)
		addition = DB.Element.GetChangeTypeElementAddition()
		DB.UpdaterRegistry.AddTrigger(updater_id, category_filter, addition)

#-----------MAIN-----------
register_view_updater(app)`

These scripts live in the main extension folder and are called when pyRevit initializes. This is done by adding a startup.py file to the same folder. It simply contain a line importing the relevant IUpdater scripts (eg. import IUpdater_ViewCreated).

Hopefully this helps.

2 Likes

This is due to how the updater is configured.

ChangeType.ConcatenateChangeTypes(Element.GetChangeTypeAny(), Element.GetChangeTypeElementAddition())

Above is the C# code for triggering both on element changes and element additions.

        private static void ActivateUpdaterListener() {
            if (updaterListener == null) {
                updaterListener = new UpdaterListener();
                UpdaterRegistry.RegisterUpdater(updaterListener);
                UpdaterRegistry.AddTrigger(
                    updaterListener.GetUpdaterId(),
                    new ElementCategoryFilter(BuiltInCategory.INVALID, inverted: true),
                    Element.GetChangeTypeAny());
            }
        }

This is the method that sets up the updater triggers that pyRevit uses. It only includes GetChangeTypeAny() and not GetChangeTypeElementAddition()

I had been considering creating a pull request to include support for both using the ChangeType.ConcatenateChangeTypes() as shown above, but not sure if there would be any negative affects for existing updaters out in the wild.

2 Likes

@joshuabudarick Thanks! this solution is very nice! I just tested it. I get some weird stuff sometimes but that’s just regular debugging I’m doing right now.
also @jpitts thanks for the insight. I found the line in the pyrevit codebase, thank you for the explanation, very cool to see the implementation and understating why it does not fire.
pyRevit/pyrevitlib/pyrevit/runtime/EventHandling.cs at f9fa0d296386d548e198324dd752fb7fb5cd7b58 · pyrevitlabs/pyRevit · GitHub

1 Like