It’s often these days that you have to reload Revit models almost on a weekly basis depending on the model exchange frequency on your project. Or you have upgraded your project or restructured your directories. We’ve all been there and most likely wondered why we can’t just reload all from a directory (or several directories)? It is a pain when you have a large amount of models to reload, you have to go through each one individually hitting ReloadFrom if the filename or location has changed. Meh!

Well, I say nay to that. I don’t know about you, but I hate repeating the same boring task over and over again. It irks me a great deal. There are some add-ins that can do this for you (although, I think this should be a built-in feature), we can do this ourselves and create a more custom ReloadFrom Automation routine that suits our project, company directory structure, model naming protocols etc…

So, I contrived this little script that allows me to easily ReloadFrom on a Revit Link or multiple Revit Links at once. You can give it multiple directories too meaning that you can be somewhat lazy and and not care too much if the .rvt file you want to load is definitely in there (although it does help :)). Also, just like the ReloadFrom found in the UI, you can reload a completely different model if you really want (useful for models with revision sequences at the end of the name…but, also quite risky if you get your logic wrong for obvious reasons).

ReloadFrom.JPG

Personally, I will use this for an Auto-Reload script that will find and Reload the latest Revit files within sub-folders in a given directory. Then I know that I always have the latest Architectural or MEP models loaded. And, whenever a new Model Exchange happens, I just run the script and I’m good as gold again.

The fun bit…

ReloadFromDetailed.JPG

The nodes that do most of the heavy-lifting for Reloading the models are the LinkedDoc.Reload (Py) and the WorksetConfigOptions (Py) shown in the graph extract above. For the Get Linked Revit Models (Py) script, see the RevitLink Utils post (ToDo).

Here is the code for each one…

WorksetConfigOptions (Py)

import clr
 
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
 
import System
from System.Collections.Generic import *
 
# return enum values...
OUT = [i for i in System.Enum.GetValues(WorksetConfigurationOption)]

This is using the System.Enum.GetValues (more on Enums here) to return all the values in the WorksetConfigurationOption Enumeration. The reason why I added this is to have an option to be able to choose how we load the worksets in the Revit Document we are about to link in (similar to when you link a Revit Model you can use the drop down arrow next to the open button) although this is not absolutely necessary. If we look at the LoadFrom() method in the RevitLinkType Class you can see that there are two overloads and each requires two arguments. I am using the one that takes the arguments…

RevitLinkType.LoadFrom(ModelPath, WorksetConfiguration)

And the WorksetConfigurationOption Enum value is something that we can set when constructing a WorksetConfiguration(WorksetConfigurationOption) object to pass into the LoadFrom class shown above.

Anyway, having done that easy node, we can now get onto the larger and more complicated node that will do nearly all the hard work…

LinkedDoc.Reload (Py)

import clr
 
import sys
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)
import os
import ntpath
import System
from System.Collections.Generic import *
 
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc =  DocumentManager.Instance.CurrentDBDocument
app = DocumentManager.Instance.CurrentUIApplication.Application
 
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
 
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
 
###################### DEFINITIONS ########################
# Convert to list should we need to...
def tolist(obj1):
    if hasattr(obj1,"__iter__"): return obj1
    else: return [obj1]
     
# turns a list of 1 item into a list of equal length to another list...
def MatchSingletonListLength(l1, l2):
    arr = []
    if len(l1) == 1 and len(l2)>1:
        i = 0
        while i<len(l2):
            arr.append(l1[0])
            i = i+1
    return arr
 
# Creates a model path given a list of directories to search and a model name to find...
def CreateModelPath(_dirs, _mName):
    _mPath = None    
    for dir in _dirs:        
        fp = ""
        # loop through each file in the directory...
        for file in os.listdir(dir):
            # check if...
            # a) it ends with .rvt
            # b) that it contains the fname we have specified
            # c) doesn't have more that two "." as this would be a backup file if you have a workshared model.
            # then if these conditions are met it will create a full filepath from the directory and the file that was found.
            if file.endswith(".rvt") and _mName in file and file.count(".") == 1:
                _mName = file
                fp = os.path.join(dir, _mName)
                # We are stopping the loop at the first satisfactory occurance where conditions are met.
                break
        # we will double check if this file exists first...
        if os.path.exists(fp):
            # and now convert our filepath string to a ModelPath, this is Path to another Linked file in Revit.
            _mPath =  ModelPathUtils.ConvertUserVisiblePathToModelPath(fp)
            # Break out of the directory loop as we have found the model we want...
            break
    return _mPath

# Gets the linked models name removing the extension...
def GetModelNameFromLinkedDoc(_lDoc):
    efr = _lDoc.GetExternalFileReference()
    fPath = ModelPathUtils.ConvertModelPathToUserVisiblePath(efr.GetPath())
    fName = ntpath.basename(fPath)
    if ".rvt" in fName:            
        _mName = fName[:-4]
    else:
        _mName = fName
    return _mName

# Parse the ModelPath to get the model name as a string...
def GetModelNameFromModelPath(_modelName):
    pathStr = ModelPathUtils.ConvertModelPathToUserVisiblePath(_modelName)
    name = pathStr.split("\\")[-1]
    return name    

###################### VARIABLES ######################
run = tolist(IN[0])[0]
# Linked Docs to reload...
lDocs = tolist(UnwrapElement(IN[1]))
# Directories to search...
dirs = None
if IN[2]:
    dirs = tolist(IN[2])

# Model Name input logic...
mNames = None
if not IN[3] == None:
    # if model names are given then strip the filetype from them so we can search for them in the directories as they may have a Rev.
    arr = []
    names = tolist(IN[3])
    for n in names:
        if ".rvt" in n:            
            arr.append(n[:-4])
        else:
            arr.append(n)
    mNames = arr
else:
    # if no model names given then use the linked docs given to populate the model names list. This would effectively be a simple ReloadFrom rather than the option to reload a model with a completely different name. 
    mNames = [GetModelNameFromLinkedDoc(lDoc) for lDoc in lDocs]

# WorksetConfigurationOption input logic...
opts = None
if not IN[4] == None:    
    if not len(tolist(IN[4])) == len(lDocs):
        #if given option count doesn't match the count of linked docs then make them the same by taking the first option and making the counts equal.
        opt = [tolist(IN[4])[0]]
        opts = MatchSingletonListLength(opt,lDocs)
    else:
        # if none there are options and they match the count of linked docs, then wrap in a list just in case there is only one in all inputs.
        opts = tolist(IN[4])
else:
    # if no option given, then default to OpenAllWorksets
    arr = []
    i = 0
    while i < len(lDocs):
        arr.append(WorksetConfigurationOption.OpenAllWorksets)
        i = i+1
    opts = arr

####################### MAIN SCRIPT ########################

if run:
    outList = []
    if lDocs:
        if dirs:
            # Check if the necessary inputs are of equal length...
            if len(lDocs) == len(mNames) == len(opts):
                for lDoc,mName,opt in zip(lDocs,mNames,opts):
                    arr = []        
                    # try and find the model to be linked in the given directories...
                    mPath = CreateModelPath(dirs,mName)
                    # if model has been found then try ReloadFrom...
                    if mPath:
                        try:
                            arr = []
                            # Construct a new WorksetConfig with the specified options...
                            wkStCfg = WorksetConfiguration(opt)
                            # Reload model and get result...
                            loadRes = lDoc.LoadFrom.Overloads.Functions[1](mPath,wkStCfg)
                            # report result as Output...
                            arr.append(GetModelNameFromModelPath(loadRes.GetModelName()) + ":" + loadRes.LoadResult.ToString())
                            arr.append(ModelPathUtils.ConvertModelPathToUserVisiblePath(mPath))
                        except Exception,e:
                            arr.append("Could not load " + mName + "\n" + e.message)
                    else:
                        # if not model could be found in any of the directories given...
                        arr.append("Could not find " + mName + ".rvt in Directory given")
                    outList.append(arr)
                OUT = outList                
            else:
                # if for some reason the list lengths do not match...
                OUT = "List lengths of the inputs do not match."
        # if directory not given then a standard reload will be done for all linked docs given...
        else:
            for lDoc in lDocs:
                arr = []
                try:            
                    loadRes = lDoc.Load()
                    # report result as Output...
                    arr.append(GetModelNameFromModelPath(loadRes.GetModelName()) + ":" + loadRes.LoadResult.ToString())
                except Exception,e:
                    arr.append("Failed to Load:-\n" + e.message)
                outList.append(arr)
            OUT = outList
    else:
        OUT = "No Linked Documents given"
else:
    OUT = "Please set Run to True"

Note: we are handling optional inputs quite a bit in the code above, this is to make this node multi-functional. Here is a breakdown of each optional input:-

  • modelDirectory_Opt – If no model directory given, then a standard Reload() will be done. Else, a ReloadFrom() will be done from any files found in the given directory/directories.
  • modelName_Opt – If no model names are given, then the current linked document name will be used search the directory/directories given.
  • worksetConfigOpts_Opt – If not WorksetConfigurationOption is given, then the default WorksetConfigurationOption will be “OpenAllWorksets”.

Also Note: We are also checking the input list lengths and ensuring that they match the number of Linked Documents given since we are using Pythons zip method to iterate all lists at once. I love this method.

This is my method for Reloading Linked Models through Dynamo, but I am sure there are plenty of variations out there with more or less features.

Anyway, have a play with it. I hope this helps you either create a better script or use this in your own workflows to save you that little bit of hassle Reloading Models on your next Model Exchange. 🙂

EDIT:

I have updated this post just a touch. Basically, the method used before was relying upon collecting RevitLinkInstances to get the linked model name and path e.t.c. and using this as my LinkedDoc input (which is now RevitLinkTypes). This was causing a null if the Revit Link was unloaded meaning I could not Reload or Reload from any Revit Link that was Unloaded. Of course, being overly confident in the nodes ability I forgot to test with links Unloaded <sigh>… rookie mistake, but we all make ’em! This became evident when I was using it on a project where the architects models had been moved to another directory, of course breaking the links. I ran the script, and nothing but nulls!!! Bugger! Should have tested for this! So, I rewrote the Get Revit Links Node so now it gets the RevitLinkTypes rather than instances. Now it works like a charm! 🙂

 

Advertisement