Try block not catching owner/permission locks

PerryLackowski
Advocate

Try block not catching owner/permission locks

PerryLackowski
Advocate
Advocate

I have a piece of code that's identifying changes in the model and updating a parameter across a number of detail items whenever the parameter's value is no longer accurate. It gathers the list of items to update, then inside of a transaction it uses a try/except block (I'm using pyrevit) so it can update as many of them as possible. The trouble is that if any of the items are checked out by other users I receive a warning and the entire transaction is rolled back. I'd like to catch this warning in the except block, but that doesn't seem to be happening.

t = DB.Transaction(doc, 'Update')
t.Start()
for item in items_to_update:
    try:
        item[0].LookupParameter('Circuit_Count').Set(int(item[2]))
        print(':white_heavy_check_mark: {} {} Circuit_Count parameter has been set to: {}'.format(item[1],output.linkify(item[0].Id), item[2]))
    except:
        print(':cross_mark: {} {} Failed to set Circuit_Count parameter to: {}'.format(item[1],output.linkify(item[0].Id), item[2]))
t.Commit()

 

The error I receive looks like "Can't edit the element until [user] resaves the element to central and relinquishes it and you Reload Latest." 

0 Likes
Reply
Accepted solutions (1)
451 Views
5 Replies
Replies (5)

jeremy_tammik
Autodesk
Autodesk

So, apparently the transaction is catching the exception internally and aborting. You cannot change that.

  

You could start and commit a separate transaction for each individual call to LookupParameter + Set. Then, you could catch the exception that the aborted transaction is throwing.

  

That would be extremely inefficient.

  

Furthermore, the call to LookupParameter alone is inefficient as well. Why? Because it loops through all parameters and uses a string comparison on each.

  

A more efficient solution to avoid calling LookupParameter inside the loop would be to call it once only before you start looping and use it to retrieve the Parameter object's Definition object:

  

https://www.revitapidocs.com/2023/dc30c65f-cfc4-244e-5a5c-bc333d7cd4c5.htm

  

Then, you can very efficiently retrieve the parameter from the element directly without searching for it using Element.Parameter(Definition):

  

https://www.revitapidocs.com/2023/87d8a88c-906e-85a9-f575-f263788b8584.htm

  

Now, to actually address your question: you are calling LookupParameter and blindly calling Set on the result. However, sometimes no such parameter is found, so LookupParameter returns null, and you are calling Set on a null object. That throws an exception.

  

The solution is simple: check for null before calling Set.

  

The same applies regardless of whether you use LookupParameter of Element.Parameter(Definition) to access the parameter. Check for null first. If the result is null, no such parameter is present on the element, and you can skip it.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

RPTHOMAS108
Mentor
Mentor
Accepted solution

Regarding worksharing there are two aspects you have to check on each element before attempting to edit it:

 

Ownership

WorksharingUtils.GetCheckoutStatus

Only a status of OwnedByOtherUser will cause an issue here

 

Update status

WorksharingUtils.GetModelUpdatesStatus

The following two aspects will cause issues

DeletedInCentral (should not make changes on these elements since they no longer exist)

UpdatedInCentral (You can call reload latest but I find it is generally better to log these)

 

Generally logging is a better approach to reloading since reloading can be time consuming and should be an end user driven decision. However you may implement a system whereby you group the UpdatedInCentral, reload latest and then get status again to confirm they can now be edited. I don't see the need for this especially and it may require more than one iteration depending on what others are doing.

 

 

PerryLackowski
Advocate
Advocate

I had this post open for a month, waiting for the worksharing warning to happen again so I could debug it. @RPTHOMAS108, your solution worked great - I coded up a simple function that'll I'll likely use on some other scripts. Thanks for the help!

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

 

0 Likes

RPTHOMAS108
Mentor
Mentor

I've always used these methods and it has always worked however I noticed recently another post to the contrary.

 

The information for those methods is cached so you should really call WorksharingUtils.CheckoutElements to confirm it since that interacts with the central file. The other get status methods just check the local cache information which is often right but apparently not always. I think I would probably still use the get status methods as a primary check.

 

The RevitAPI.chm gives details of the fitness for purpose for the various methods of WorksharingUtils.

 

Testing these issues is a lot harder than it used to be due to the single licence fixed Revit user log-in. In the past we just switched the Revit user name in the options dialogue and that was that. There should be an API method for faking Revit user names i.e. names in a form that indicate they are obviously not actual Revit users or account holders (just for testing worksharing with add-ins). Instead of: log in as UserA do something then log in as UserB, does it work?

PerryLackowski
Advocate
Advocate

Today was the first time I encountered an issue like you described @RPTHOMAS108. I was working on a new script to auto-populate some parameters on our electrical equipment, and the script failed due to a few items being checked out. Below the is_not_available function (mentioned in my previous post), I added a try/except block which checks out the elements individually, and it seems to be working now, but like you say, it is definitely a little slower. It takes about 15 minutes to update 750 elements. Luckily I only need to run this once a week. I also put in a check before it runs so it skips elements that are already up-to-date - this brings the run-time down to 10-60 seconds.  Thanks for the tip!

 

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

    elem.elem.LookupParameter('my_param').Set(new_string)
    print('Updated.')
    counter += 1

 

0 Likes