Safely Modifying Elements in a Workshared Model

I regularly rely on pyRevit scripts that modify parameters across multiple elements in a workshared Revit model. The trouble is that the elements I’m modifying have sometimes been checked out already, which causes Revit to throw a warning and roll back all the changes in the transaction. Is there a solution to this? I see add-ins like RFTools which regularly edit parameters with no trouble, so I’m thinking I need a new approach. My current method has a slow version which checks if the elements are checked out first, and a fast version which does not check, but is at risk of getting rolled back if it encounters a worksharing clash.

def update_string_param(elem, param, new_string, fast_mode = False):
    """
    Updates string parameter if it doesn't match new_string.
    """
    # Assumes param is already confirmed to exist
    x = param.AsString()
    if x == new_string or (x == "" and new_string == None):
        return False
    else:
        # print(param.Definition.Name, x, type(x), new_string, x==new_string)
        print("Data has changed, updating '{}' (old, new): {}".format(param.Definition.Name, (x, new_string)))

        if fast_mode == False:
            if retrieve_data.is_not_available(elem.Id):
                print('This element {} has been checked out by another user and cannot be edited at this time.'.format(elem.Id))
                return False
        
            try:
                DB.WorksharingUtils.CheckoutElements(doc,List[DB.ElementId]([elem.Id]))
            except:
                print('This element {} was unable to be checked out and cannot be edited at this time.'.format(elem.Id))
                return False

        if new_string == None and param.HasValue == True:
            param.Set("")
        else:
            param.Set(new_string)
        # print(':white_heavy_check_mark: Updated: {}'.format(new_string))
        return True
def is_not_available(elem_id):
    if DB.WorksharingUtils.GetCheckoutStatus(doc,elem_id) == DB.CheckoutStatus.OwnedByOtherUser:
        return True
    status = DB.WorksharingUtils.GetModelUpdatesStatus(doc,elem_id)
    if status == DB.ModelUpdatesStatus.DeletedInCentral or status == DB.ModelUpdatesStatus.UpdatedInCentral:
        return True
    return False

Well I don’t think your way of working is wrong perse. I mean it’s pretty diligent to check if an element is checked out or not.

Anyway I could think of a few things maybe that could help you with performance. Though I’m not certain as it largely depends on your code architecture as well.

1. Use a try/except block around your param.Set() block
Sometimes it’s better to ask for forgiveness, than to ask for permission. In the same vein, sometimes it’s better to try something, and see if it sticks, and just deal with the exception if something goes wrong.
If the set works, then it works, if it doens’t, then you’ll know and can act accordingly.
This way you don’t have to worry about all potential things that can go wrong.

Now … I’m not sure if this will prevent a revit dialog from popping up, but it’s worth checking out.

2. Consider providing CheckoutElements with all the elements that need changing at once
This depends on the rest of your script ofcourse, but if you’re dealing with a lot of elements, it’s better to check everything out in 1 big call. (The documentation also says this)

What you could to is something like this to check out as many elements as possible.

def checkout_elements(elements):
    element_ids = [elem.Id for elem in elements]
    checked_out_ids = DB.WorksharingUtils.CheckoutElements(doc,List[DB.ElementId](element_ids))
    unchecked_ids = [id for id in element_ids if id not in checked_out_ids]
    checked_out_elements = [doc.GetElement(id) for id in checked_out_ids]
    unchecked_out_elements = [doc.GetElement(id) for id in unchecked_out_elements ]
    return checked_out_elements, unchecked_out_elements

Now, fair warning here. I’m not sure if you can use if id not in checked_out_ids like that.

Thanks for the suggestions. I’ll give these a try next time I need to run my scripts!