I’ve been working on a script to find all Properties of a particular type (class) within Blender’s Property system. It seems to work, but since I’m relatively new to Python and Blender, I suspect there are better ways to do some of it. So I’m looking for suggestions on how to improve it.
The basic idea is to accept an object and then recursively search it and its “branches” for objects of the requested type (currently hard-coded to look for objects of class “Var_Group”). All the objects of the requested type are then returned in a list.
The logic is simple. It starts with an object argument and an empty list, and it performs these tests:
- Is this object of the requested type? If so, add it to the list and return the list.
- Is this object a Property Group? If so, recursively call this function with each Property in the Group.
- Is this object a Collection Property? If so, recursively call this function with each Property in the Collection.
Of course, the devil is in the details.
For Property Groups, I’ve had trouble getting a list of all the Properties in the Group. I originally searched obj.keys(), but some Properties didn’t show up there until they’d been displayed in the GUI. So I switched to using dir(obj), but that produces a lot of non-property objects. That’s OK, but the “bl_rna” Property ended up causing infinite recursion, so I had to explicitly filter it out (see code below). Is there a better way to get a list of the Property objects in a Property Group?
For Collection Properties, I could not figure a way to use “isinstance” to find if a given object was a Collection Property. So I fudged it by testing if str(type(obj)) == “<class ‘bpy_prop_collection_idprop’>”. This is a pretty ugly way to do a simple thing, and I’m hoping someone can post a better way.
Here’s the code that walks the property tree starting with “obj” and an empty list (“plist”). It returns a list of the objects of type “bpy.types.Var_Group” found in the tree:
def get_props ( obj, plist ):
""" Recursive routine that builds a list of Var_Group properties """
if isinstance(obj,(bpy.types.Var_Group)):
# This is what we're looking for so add it to the list
plist.append ( obj )
elif isinstance(obj,bpy.types.PropertyGroup):
# This is a property group, so walk through all of its property keys
for objkey in dir(obj):
if (str(objkey) == 'bl_rna'):
# Processing the bl_rna causes infinite recursion
pass
else:
try:
plist = get_props(getattr(obj,objkey), plist)
except Exception as e:
# This seems to happen with properties in a .blend file
# that are no longer in the code or have been renamed?
print("Error in get_props("+str(obj)+","+str(plist)+")=>"+str(e))
elif str(type(obj)) == "<class 'bpy_prop_collection_idprop'>":
# This is a collection, so step through the elements as an array
for index in range(len(obj)):
plist = get_props(obj[index],plist)
else:
# This could be anything else ... like <'int'> or <'str'>
pass
return plist
Here’s the output from the resulting list for a test case Property Group containing nested Properties, Property Groups, and Property Collections:
----- Application Properties -----
There are 31 properties defined
bpy.data.scenes['Scene'].app.a = "Count" = 3.0
bpy.data.scenes['Scene'].app.b = "ABC" = 0.0
bpy.data.scenes['Scene'].app.g_list[0].ssg_list[0].p = "ssgp0" = 10.0
bpy.data.scenes['Scene'].app.g_list[0].ssg_list[0].q = "ssgq0" = 99.0
bpy.data.scenes['Scene'].app.g_list[0].ssg_list[1].p = "ssgp1" = 100.0
bpy.data.scenes['Scene'].app.g_list[0].ssg_list[1].q = "ssgq1" = 999.0
bpy.data.scenes['Scene'].app.g_list[0].x = "String 1_2" = 4.0
bpy.data.scenes['Scene'].app.g_list[0].y = "String 1_3" = 6.0
bpy.data.scenes['Scene'].app.g_list[1].x = "String 1_4" = 8.0
bpy.data.scenes['Scene'].app.g_list[1].y = "String 1_5" = 10.0
bpy.data.scenes['Scene'].app.g_list[2].ssg_list[0].p = "ssgp0" = 10.0
bpy.data.scenes['Scene'].app.g_list[2].ssg_list[0].q = "ssgq0" = 99.0
bpy.data.scenes['Scene'].app.g_list[2].ssg_list[1].p = "ssgp1" = 100.0
bpy.data.scenes['Scene'].app.g_list[2].ssg_list[1].q = "ssgq1" = 999.0
bpy.data.scenes['Scene'].app.g_list[2].x = "String 2_2" = 8.0
bpy.data.scenes['Scene'].app.g_list[2].y = "String 2_3" = 12.0
bpy.data.scenes['Scene'].app.g_list[3].x = "String 2_4" = 16.0
bpy.data.scenes['Scene'].app.g_list[3].y = "String 2_5" = 20.0
bpy.data.scenes['Scene'].app.g_list[4].ssg_list[0].p = "ssgp0" = 10.0
bpy.data.scenes['Scene'].app.g_list[4].ssg_list[0].q = "ssgq0" = 99.0
bpy.data.scenes['Scene'].app.g_list[4].ssg_list[1].p = "ssgp1" = 100.0
bpy.data.scenes['Scene'].app.g_list[4].ssg_list[1].q = "ssgq1" = 999.0
bpy.data.scenes['Scene'].app.g_list[4].x = "String 3_2" = 12.0
bpy.data.scenes['Scene'].app.g_list[4].y = "String 3_3" = 18.0
bpy.data.scenes['Scene'].app.g_list[5].x = "String 3_4" = 24.0
bpy.data.scenes['Scene'].app.g_list[5].y = "String 3_5" = 30.0
bpy.data.scenes['Scene'].app.s_list[0] = "String 1" = 2.0
bpy.data.scenes['Scene'].app.s_list[1] = "String 2" = 4.0
bpy.data.scenes['Scene'].app.s_list[2] = "String 3" = 6.0
bpy.data.scenes['Scene'].app.sg.x = "ABC" = 0.0
bpy.data.scenes['Scene'].app.sg.y = "ABC" = 0.0
I believe the output is correct, but I’d like to improve the script to eliminate the kludges.