I usually use the pyrevit.revit.db.query.get_elements_by_categories function for this.
from pyrevit.revit import query, DB
elements = query.get_elements_by_categories([DB.BuiltInCategory.OST_ElectricalFixtures])
I’m afraid pyrevit library doesn’t include a way to specify the category using the name; I had to build a dictionary that maps the names to the corresponding builtin category enumeration
In the above codes, when I set cat_name = “OST_StructuralColumns” or BuiltInCategory.OST_StructuralColumns, elements = None. This is not useful for me.
My real question is: How to convert OST category name in string to BuiltInCategory name for category filter?
I am using Revit 2024 and pyRevit 2024.
I have tried the following:
import pyrevit
doc = pyrevit.revit.doc
# or doc = __revit__.ActiveUIDocument.Document # type: ignore
categoryString = "OST_StructuralColumns"
categoryId = getattr(BuiltInCategory, categoryString)
# a solution on the internet said that this would give the object name, but this actually gives -2001330
categoryFilter = ElementCategoryFilter(categoryId)
# this gives TypeError : No constructor matches given arguments: (<class 'int'>)
# Revit c# API says that category Id can be used instead of BuiltInCategory Name.
I do not want to use ElementCategoryFilter(BuiltInCategory.OST_StructuralColumns) for some special reasons.
Thank you! However
categoryFilter = DB.ElementCategoryFilter(int(categoryId))
with or without “DB.” or using int(categoryId) gives the following error:
TypeError : No constructor matches given arguments: (<class ‘int’>)
My present solution is:
import pyrevit
from pyrevit import DB
doc = pyrevit.revit.doc
categoryString = "OST_StructuralColumns"
bicId = getattr(DB.BuiltInCategory, categoryString)
for category in doc.Settings.Categories:
if str(bicId) == str(category.Id): # change both to str to compare
categoryFilter = DB.ElementCategoryFilter(category.Id)
elements = DB.FilteredElementCollector(doc).WherePasses(categoryFilter).WhereElementIsNotElementType().ToElements()
elements = sorted(elements, key=lambda elements:elements.Name)
for element in elements:
print (element.Name + " " + str(element.Id))
print ("end")
The following also works:
import pyrevit
#from pyrevit import DB
from Autodesk.Revit.DB import *
doc = pyrevit.revit.doc
# or
#doc = __revit__.ActiveUIDocument.Document # type: ignore
categoryString = "OST_StructuralColumns"
bicId = getattr(BuiltInCategory, categoryString)
for category in doc.Settings.Categories:
if str(bicId) == str(category.Id): # change both to str to compare
categoryFilter = ElementCategoryFilter(category.Id)
elements = FilteredElementCollector(doc).WherePasses(categoryFilter).WhereElementIsNotElementType().ToElements()
elements = sorted(elements, key=lambda elements:elements.Name)
for element in elements:
print (element.Name + " " + str(element.Id))
print ("end")
It may be faster if it is not necessary to loop through doc.Settings.Categories.
Why not just:
FilteredElementCollector(doc).OfCategoy(category).WhereElementIsNotElementType()
Class, CategoryId and Categoy filters are the fastest. ToElements is slower than just the element Id’s. You can process the elements later when you get the name form the element. (Performance aside, I’d do the ToElements as well…)
WherePasses is best used when you are apply multiple conditions for filtering.
And get your category from the string before moving on.
for category in doc.Settings.Categories:
if str(bicId) == str(category.Id): # change both to str to compare
categoryFilter = DB.ElementCategoryFilter(category.Id)
break
elements = etc, etc, etc....
I was just trying to report that DB.ElementCategoryFilter(category.Id) or (category.Name) or (category) did not work and (bic) did work but the elements could not be sorted nor give element.Name or element.Id. The outputs were just a list of “Autodesk.Revit.DB.FamilyInstanceobject”.
import pyrevit
from pyrevit import DB
from Autodesk.Revit.DB import *
doc = pyrevit.revit.doc
categoryString = "OST_StructuralColumns"
bic = getattr(BuiltInCategory, categoryString)
for category in doc.Settings.Categories:
if str(bic) == str(category.Id): # change both to str to compare
elements = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsNotElementType()
break
for element in elements:
print (element)
print ("end")
Now without looping through “for category …” as suggested by you, it works fully.
Sorry - I was editing in RevitPythonShell, which has a lot of the declarations and namespaces predefined. And it seems most of your issues are coming from not importing the right namespaces or referencing the needed dll’s. Mighht look deeper into that. I usually have some boilerplate I keep in my script set up so I can go back and forth from Dynamo to RevitPythonShell to pyRevit. Each is different. Zap what I don’t need at the end.
Below is dressed up fro pyRevit.
import clr
clr.AddReference("RevitAPI")
doc = __revit__.ActiveUIDocument.Document
import Autodesk
from Autodesk.Revit.DB import *
classString = "Wall"
namespace = "Autodesk.Revit.DB."
className = namespace + classString
w = eval(className)
print(w)
elems = FilteredElementCollector(doc).OfClass(w)#.WhereElementIsNotElementType()
for e in elems:
print(e.Name)
To summarize, both methods work, but elements.Name not available for OST_Rooms or .WhereElementIsElementType()
import pyrevit
from pyrevit import DB
from Autodesk.Revit.DB import *
doc = pyrevit.revit.doc
categoryString = "OST_Rooms"
bic = getattr(BuiltInCategory, categoryString)
print ("method 1")
for category in doc.Settings.Categories:
if str(bic) == str(category.Id): # change both to str to compare
elements = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsNotElementType()
# or
#elements = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsElementType()
break
##elements = sorted(elements, key=lambda elements:elements.Name)
for element in elements:
## print (element.Name + " " + str(element.Id))
print (element.Id)
print ("method 2")
elements = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsNotElementType()
# or
#elements = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsElementType()
##elements = sorted(elements, key=lambda elements:elements.Name)
for element in elements:
## print (element.Name + " " + str(element.Id))
print (element.Id)
print ("end")
## elements.Name not available for OST_Rooms or .WhereElementIsElementType()
Also:
Without the sorted(…) line, len(elements) is not available.
Sorting by element.Id is also not possible.
“OST_Rooms” is not a valid string.
“Rooms” is. But not for OfClass. And a Roomis really a SpatialElement - not aa Room.
Really wondering why you are going through all this effort to do things that are pretty simple just querying the DB. The Revit object model is going to keep changing - so don’t hard code things that might change. Like Rooms. Rooms used to be Rooms. Now they are SpatialElements.
This just retrieves a class from the DB given a string.
import clr
import inspect
clr.AddReference("RevitAPI")
import Autodesk.Revit.DB as DB
def findclass(classname):
members = inspect.getmembers(DB)
classes = [member for member in members if inspect.isclass(member[1])]
for c in classes:
if classname == c[0]:
return(c[1])
myclass = findclass("SpatialElement")
print(myclass)
And if you just need a list of classes.
import clr
import inspect
clr.AddReference("RevitAPI")
import Autodesk.Revit.DB as DB
members = inspect.getmembers(DB)
classes = [member for member in members if inspect.isclass(member[1])]
for c in classes:
print(c[0])
In this series of messages, I am finding ways to convert built-in category names or class names in strings to their names in codes exactly because I do not want to hard code them. Those names in strings are stored in an Excel file to be read by the programme. The Excel file contains a list of category or class names together with parameters to define what should be output by the programme. The Excel file can be changed by the users but not the programme.
Basically, I use built-in category names first before considering class names.
I have written C# codes to convert “OST_Rooms” to a built-in category name successfully. Do not know why the equivalent codes in Python which can handle other “OST_…” cannot handle it.
Converting from class names in string is not as easy as converting from built-in category names in string. I have to build up a dictionary for class names first similar to your example but I did not know the equivalent Python codes. Here are my C# codes:
private Type getClass(string className)
{
// initialise dictionary
Dictionary<string, Type> classDict = new Dictionary<string, Type>();
// initialise list of arrays
List<Type[]> typeArrayList = new List<Type[]>();
// load assembly by name and get types there
// only these two assemblies contain namespace AutodeskRevit.DB
typeArrayList.Add(Assembly.Load("RevitAPI").GetTypes());
typeArrayList.Add(Assembly.Load("RevitAPIExtData").GetTypes());
// loop through each array in list
foreach (Type[] typeArray in typeArrayList)
{
// loop through each type in array
foreach (Type type in typeArray)
{
// check if the type is a class and belongs to the specified namespace
if (type.IsClass && type.Namespace == "Autodesk.Revit.DB")
{
// check if key not already exists
if (!classDict.ContainsKey(type.Name))
{
// add key-value pair of type to dictionary
classDict.Add(type.Name, type);
}
}
}
}
return classDict[className];
}
Obviously, Python names are not simply re-using the C# names as shown in the RevitAPI Doc, and a lot of experiments have to be tried and questions raised.
Python codes are easier to write than C# codes, but finding the correct Python codes from RevitAPI Doc written for C# has been a big obstacle.
Jokes aside, you’re building a dictionary with the entire collection of types from two assemblies, just to return a single type. You don’t need any of that.
And the outer loop and the creation of the lists can then be avoided
static Type getRevitClassByName(string className)
{
var klass = getClassByName("RevitAPI", className);
If (klass != null) return klass;
return getClassByName("RevitAPIExtData", className);
}