For a 1:1 this should get you started. Even if it fully executes what you need I I recommend you review the code and documentation closely as there are several very useful aspects of the Revit API exposed in a way which is VERY useful to know how to utilize for similar tasks. As always if ‘why’ something was done, ask and I’ll try to clarify.
It’s not all roses though - there are a few things to be cautious of before you get started:
- This code filters out any file with more than one decimal place in the file path (meaning there is a decimal other than the one which indicates the extension). If you have a decimal point in your file path you’ll need to modify the file filtering.
- This code doesn’t take into account worksharing, which is likely the aspect which will give you the most headaches. Look closely into that aspect of document modification (what if someone else owns one of the elements? how do you sync? etc.) before attempting this in a real project.
- This code also doesn’t account for your project environment (ie: ACC, BIM360, Revit Server, local network, detached files, etc.).
- I didn’t bother with the category filter - assuming your family name would be unique across categories.
- It assumes that all types under the previous family want to be the new family type created by loading the family into the project, and that you’re only loading one family into the project. If this isn’t the case (likely on projects of scale) I recommend taking a break and consider how you’ll map the type options; if there isn’t a 1:1 method then you can still load the family into the document, but someone will have to open each file and make the swap (select the instances via the project browser).
Python code
#import the CLR (common language runtime), os (opperating system) and uuid (unique id) modules
import clr, os, uuid
# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Import the relevant classes from the RevitAPI
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import BuiltInParameter, Element, ElementId, ElementParameterFilter, FamilyInstance, FamilySymbol, FilteredElementCollector, FilterElementIdRule, FilterNumericEquals, IFamilyLoadOptions, ModelPathUtils, OpenOptions, ParameterValueProvider, Transaction, WorksetConfiguration, WorksetConfigurationOption
# Imports iLists module into python
clr.AddReference("System")
from System.Collections.Generic import List as cList #using the alias here as Python already has a List class, but we need the .net List class to simplify changing the family type
# Standard areas for Current Document, Active UI and application
doc = DocumentManager.Instance.CurrentDBDocument
app = DocumentManager.Instance.CurrentUIApplication.Application
#class for the the interface of family load options
class FamilyLoadOptions(IFamilyLoadOptions):
__namespace__ = str(uuid.uuid4()) #classes in CPython require a namespace, which must be unique on subsequent runs so I generate a GUID on each run...
def OnFamilyFound(self, familyInUse, overwriteParameterValues):
return True #you were not overwriting if the family already existed - I don't recommend this, and assumed that was a decision from the AI not you, modify as desired
def OnSharedFamilyFound(self, sharedFamily, familyInUse, source, overwriteParameterValues):
return True #you were not overwriting if the family already existed - I don't recommend this, and assumed that was a decision from the AI not you, modify as desired
#ensures no transactions are active
TransactionManager.ForceCloseTransaction(TransactionManager.Instance)
#inputs from the Dynamo environment
directoryPath = IN[0]
familyName = IN[1]
newFamilyPath = IN[2]
#set the workset configuration to open all worksets
worksetConfig = WorksetConfiguration(WorksetConfigurationOption.OpenAllWorksets)
#set the open options to handle how we open the file
openOptions = OpenOptions()
openOptions.IgnoreExtensibleStorageSchemaConflict = True #enabling this as you're apt to run into it when working in bulk
openOptions.SetOpenWorksetsConfiguration(worksetConfig)
#get the list of files in the directory
files = [ os.path.join(directoryPath,i) for i in os.listdir(directoryPath) if i.endswith(".rvt") ]
files = [ i for i in files if len(i.split(".")) == 2 ]
#convert the files to model paths so we can use the openoptions method
modelPaths = [ ModelPathUtils.ConvertUserVisiblePathToModelPath(i) for i in files ]
#the built in parameter for element family and the provider and evaluator objects for the filter - performing this outside of the loop so we can build it once and use it many times
elementFamilyParameter = BuiltInParameter.ELEM_FAMILY_PARAM
provider = ParameterValueProvider(ElementId(elementFamilyParameter))
evaluator = FilterNumericEquals()
#openg hte family document so we can load it into the projects quickly
familyDocument = app.OpenDocumentFile(newFamilyPath)
#loop over the files found
for path in modelPaths:
# open the file
doc = app.OpenDocumentFile(path, openOptions)
# load the family - this has to happen outside of the transaction - not sure why but that's how it is
loadedFamily = familyDocument.LoadFamily(doc,FamilyLoadOptions())
# start a transaction in the opened document
transaction = Transaction(doc, "Change Families")
transaction.Start()
# get the family symbol (family type) and activate it
famSymbolSet = loadedFamily.GetFamilySymbolIds().GetEnumerator()
famSymbolSet.MoveNext()
famType = doc.GetElement(famSymbolSet.Current)
famType.Activate()
# get the family symbols (types) in the document
familyId = [i for i in FilteredElementCollector(doc).OfClass(FamilySymbol).ToElements() ]
# filter out any types which have a name that doesn't align with the target family
familyId = [i.Id for i in familyId if i.FamilyName == familyName ][0]
# build the rule to filter out family instances which don't have a parent family with a matching name
rule = FilterElementIdRule(provider, evaluator, familyId)
filter = ElementParameterFilter(rule)
# get the element IDs of the family instances which pass the filter
familyInstances = FilteredElementCollector(doc).OfClass(FamilyInstance).WherePasses(filter).ToElementIds()
#convert the python list of family instances to a iList - the type of list used by .net
familyInstances = cList[ElementId](familyInstances)
#convert the family type for all identifed instances to the new family type
Element.ChangeTypeId(doc,familyInstances,famType.Id)
#commit the transaction
transaction.Commit()
#close the document
doc.Close(True)
#close the family document
familyDocument.Close(False)
#repor tthe result to the Dynamo environment
OUT = "Changed out the family in the following documents:\r\t"+"\r\t".join(files)