2
$\begingroup$

I have a root element with some objects that could be a variable level of depth, but it mostly just goes 2-3 levels deep.

I am trying to get all the meshes within the root object but doesn't seem to be able to reach them? I also admit I am not good at this recursive thing.

structure:

enter image description here

function:

def findMeshObjectsRecursive(searchObject, levels=10, meshList=None):
    def recurse(searchObject, parent, depth, meshList):
        if(meshList == None):
            meshList = []       
        if(searchObject.type == 'MESH'):
            return searchObject
        if depth > levels: 
            print('return list 1')
            return meshList
        print("  " * depth, searchObject.name)

        for child in searchObject.children:
            meshList.append(recurse(child, searchObject,  depth + 1, meshList))
        return meshList

    try: return recurse(searchObject, searchObject.parent, 0, meshList)
    except Exception:
        traceback.print_exc()
        return meshList

output

 mrotxhi_1
 EMPTY
 shaftxhi_1
 EMPTY
 trotxhi_1
 EMPTY

I would expect it to print more objects, because later in the code I add blend modes and transparencies to these objects but it is not working now because it cannot find any MESH type objects... I previously tested this with a different structure that did work.

$\endgroup$

2 Answers 2

3
$\begingroup$

More Python thing than Blender's one, but you can:

import bpy

def find_meshes_recursive( root, levels=10, meshes=None ):
    # Initialize the result once
    if meshes is None:
        meshes = []

    def recurse( parent, result, level, levels ):
        # Does nothing if level is reached
        if level < levels:
            # Keeps meshes
            if parent.type == 'MESH':
                result.append(parent)
            # Look over children at next level
            for child in parent.children:
                recurse( child, result, level + 1, levels )
    
    recurse( root, meshes, 0, levels )
    return meshes

root = bpy.context.object

meshes = find_meshes_recursive(root, levels = 10)
print(meshes)

Note that you can also do it in non recursive way:

def find_meshes(root, levels=10, meshes=None):
    meshes = [] if meshes is None else meshes
    
    parents = [root]
    while levels > 0 and parents:
        meshes.extend([obj for obj in parents if obj.type == 'MESH'])
        parents = [child for obj in parents for child in obj.children]
        levels -= 1
        
    return meshes
$\endgroup$
2
  • 1
    $\begingroup$ It's a python scope thing. Since you only ever use the value of levels from the "wrapper" defining it as a local for each recurse is ..... $\endgroup$
    – batFINGER
    Sep 25, 2020 at 7:00
  • $\begingroup$ @batFINGER, again a question. Have added non recursive in the answer, so tested the performance comparison (range loop over the call). Strangely, the first tested is always the longer in time. Do you know why? $\endgroup$
    – lemon
    Sep 25, 2020 at 7:47
4
$\begingroup$

Recursive generator.

enter image description here

Test Data.

At its bare basics can walk a tree and yield all objects. List comprehension is used on result to keep or weed out by some condition.

import bpy

context = bpy.context

def walk_children(ob):
    yield ob
    for child in ob.children:
        yield from walk_children(child)
    
# test call
print("-" * 20)
print([o.name for o in walk_children(context.object) if o.type == 'MESH'])

Output.

--------------------
['Sphere', 'Cube', 'Cube.002', 'Cube.001', 'Cube.003']

A dictionary by type

One of my favourite things is the defaultdict type from collections module.

from collections import defaultdict
descendants = defaultdict(list)
for o in walk_children(context.object):
    descendants[o.type].append(o)
    
for k, obs in descendants.items():
    print(k, [o.name for o in obs])

Output:

EMPTY ['Empty']
ARMATURE ['Armature']
MESH ['Sphere', 'Cube', 'Cube.002', 'Cube.001', 'Cube.003']
CAMERA ['Camera', 'Camera.001']

With levels and yield only of a type

import bpy

context = bpy.context

def walk_children(ob, level=0, max_level=50, type='MESH'):
    print(f"{'  ' * level}{ob.name}")
    if ob.type == type:
        yield ob
    if level < max_level:
        for child in ob.children:
            yield from walk_children(child, level=level + 1)
    
# test call
print("-" * 20)
print([o.name for o in walk_children(context.object)])

Output.

--------------------
Empty
  Armature
    Sphere
      Camera
  Cube
    Cube.002
      Camera.001
        Cube.001
    Cube.003
    Lamp

['Sphere', 'Cube', 'Cube.002', 'Cube.001', 'Cube.003']

A "wrapper"

As also demonstrated in the upvoteworthy answer of @lemon

If we wrap our recursive generator above in a method, can pass initial arguments, and manipulate the result.

Example below uses the absolute scene depth of the root object passed to find the names of all objects at a particular depth.

def walk_children(ob, min_level=0, max_level=100):
    def get_level(ob):
        i = 0
        while(ob.parent):
            i += 1
            ob = ob.parent
        return i
    def _walk_children(ob, level=get_level(ob)):

        yield level, ob.name
        for child in ob.children:
            yield from _walk_children(child, level=level + 1)
    # 
    return list(o for lev, o in _walk_children(ob)
        if min_level <= lev <= max_level)
    
# test call
root_obs = [o for o in context.scene.objects 
        if o.parent is None]
for root in root_obs:
    print(root.name, walk_children(root, min_level=2, max_level=2))
       

Output, name of all objects in scene with a depth two ancestors.

Empty ['Sphere', 'Cube.002', 'Cube.003', 'Lamp']
$\endgroup$
3
  • $\begingroup$ Interesting variations. Here this is not a real issue but do you have a overall knowledge of what is best in term of performance, using Python? $\endgroup$
    – lemon
    Sep 25, 2020 at 6:26
  • $\begingroup$ Grumble, the old shitbox computer I'm on atm can handle blender 2.91 . mostly crashes when a script on this site starts running slowly.. anyway to cut what was a long comment short(ish). The main idea behind using a generator here when we "consume" the data it will be freshly made. .. ie there will no stale data. Storing blender object references in a list can be dangerous, if it is referenced after an object is removed. Why most directory walkers are generators.. $\endgroup$
    – batFINGER
    Sep 25, 2020 at 7:11
  • $\begingroup$ Damn yeild is fantastic, yeilds compact minimal code. $\endgroup$ Dec 5, 2021 at 2:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .