Category by name in Python

Hey,

I am trying to get the Category by entering the string name.
Like the Dynamo node Category.ByName
It used to work but now all of a sudden it does not…

The Code:

import clr
clr.AddReference('RevitNodes')
import Revit

    
CAT = Revit.Elements.Category.ByName("Electrical Fixtures")
print(CAT)

the error i get:

Hi @MelleH ,

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

1 Like

maybe this is what you are after?

from pyrevit.revit import query
cat_name= # string name or builtincat
elements  = query.get_category(cat_name)
3 Likes

Yes this is it Thanks!

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.

Is this what you’re after?

import pyrevit
from pyrevit import DB
categoryString = "OST_StructuralColumns"
categoryId = getattr(DB.BuiltInCategory, categoryString)
categoryFilter = DB.ElementCategoryFilter(categoryId)
print(categoryFilter)

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....

This should be all you need…

import pyrevit
from Autodesk.Revit.DB import *
doc   = __revit__.ActiveUIDocument.Document
categoryString = "OST_StructuralColumns"
bic = getattr(BuiltInCategory, categoryString)
elements = FilteredElementCollector(doc).OfCategory(bic).WhereElementIsNotElementType().ToElements()
elements = sorted(elements, key=lambda elements:elements.Name)
for element in elements:
	print (element.Name + " " + str(element.Id))
print ("end")
1 Like

Thank you very much!

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.

1 Like

I used WherePass because of possible need to apply multiple conditions for filtering.

If I may, I would like to ask a similar question. How to convert class name in string for ElementClassFilter or OfClass?

import clr
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
classString = "Wall"
namespace = "Autodesk.Revit.DB."
className = namespace + classString
classtype = clr.GetClrType(eval(className))
print(classtype)
elems = FilteredElementCollector(doc).OfClass(classtype).WhereElementIsNotElementType()
for e in elems:
	print(e.Name)

…and this works as well.

import clr
clr.AddReference("RevitAPI")
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)
1 Like

I cannot make it.

eval(className) gives NameError : name ‘Autodesk’ is not defined

I have already added these to the beginning:

import pyrevit
from pyrevit import DB
import pyrevit.revit
doc = pyrevit.revit.doc

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)
1 Like

Thank you! The essential line is: import Autodesk.

When “OST_RoomTag” is used, elements.Name is OK.
When “OST_Rooms” is used, elements.Name gives AttributeError.

Trying to find alternatives.

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

…and if you don’t want a list. Just the class.

import clr
import inspect

clr.AddReference("RevitAPI")
import Autodesk.Revit.DB as DB
    		
def findattrib(classname):
    classname = getattr(DB, classname)
    return classname
    
myclass = findattrib("SpatialElement")
print(myclass)
1 Like

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.

I shall try your suggested codes.

Because you’re overcomplicating the c# code :rofl:

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.

Using linq, you can simplify the inner loop with

static Type getClassByName(string assemblyName, string className)
{
  var types = Assembly.Load(assemblyName).GetTypes();
  return types.FirstOrDefault(t => t.IsClass && t.Namespace == "Autodesk.Revit.DB" && t.Name == className);
}

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);
} 
2 Likes