Selecting Toplevel Empty

Hi, is it possible to select all (if there are several) toplevel Emptys in a Blend-file, one by one, then select and then join the children meshes?

When I have to import cad data, I often have a messy empty - sub- structure, and I only want to keep the top level of that Hirarchy, and join all the meshes that are parented under that top level Emptys

Hi Jimmy,

I’d use a Python-script. It should look through all objects a single time and check if they have a parent. If they don’t, they’re toplevel. Then you can filter by attributes and object-type. Once you got only those toplevel-objects you want, select their children and replace the parent with the new parent-object and remove the old parent. – It should be possible to just parent all old parents to your new parent-object and then delete them. The child-objects should then be able to bind directly to the next-higher parent.

It might look like this (untested):

import bpy

# we just take the active object for our new toplevel object.
new_toplevel = bpy.context.active_object

toplevel = [] # here we store the toplevel objects

for obj in bpy.data.objects: # this will run through ALL objects.
# use "bpy.context.scene.objects" to fetch objects of the active scene
    if obj.parent==None: # no parent, therefore toplevel
        toplevel.append(obj) # add to list of toplevel-objects

# now let's filter for empties. Note that you can "flip" the forloop
# to be inside the list. It is then called list-comprehension.
toplevel_empties = [obj for obj in toplevel if obj.type=="EMPTY"]

# finally let us ensure that our new toplevel object is not in the
# toplevel_empties list.
toplevel_old = [obj for obj in toplevel_empties if obj!=new_toplevel]

## select only the toplevel empties and set the new toplevel object active
bpy.ops.object.select_all(action='DESELECT') # deselect all objects
for obj in toplevel_old: # select toplevel empties
    obj.select_set(True)
new_toplevel.select_set(True) # select new toplevel object
bpy.context.view_layer.objects.active = new_toplevel # set the new toplevel active


### You got your info. Now, how about we do the rest?
# fetch top-children:
children = []
for obj in toplevel_old:
    children.extend(obj.children)
# select children:
for ch in children:
    ch.select_set(True)
# re-parent keeping the transorms
bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
## select only the old toplevel and delete them
bpy.ops.object.select_all(action='DESELECT') # deselect all objects
for obj in toplevel_old: # select toplevel empties
    obj.select_set(True)
bpy.ops.object.delete() # deletes selected objects

Now, we can boil all of that down to this:

import bpy

## Setup. Whichever object you provide here will become the new toplevel.
## As you see it defaults to the active object.
new_toplevel = bpy.context.active_object
toplevel = []

## Re-parent the second-level objects to the new toplevel object.
bpy.ops.object.select_all(action='DESELECT') # deselect all objects
new_toplevel.select_set(True) # select new toplevel object
bpy.context.view_layer.objects.active = new_toplevel # set the new toplevel active
for obj in bpy.data.objects: # you may want to search only a subset...
    if all([obj.parent==None,     # filters on a list of conditions
            obj.type == 'EMPTY',  # You can add your own conditions here,
            obj != new_toplevel]):# provided you append a comma to each.
        toplevel.append(obj) # add to list of toplevel-objects
        for ch in obj.children: ch.select_set(True) # select children
bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)

## Clean-up. We delete the old toplevel objects.
bpy.ops.object.select_all(action='DESELECT') # deselect all objects
for obj in toplevel_old: obj.select_set(True) # select old toplevel
bpy.ops.object.delete() # deletes selected objects

## And for convenience, select and activate the active object again
new_toplevel.select_set(True)
bpy.context.view_layer.objects.active = new_toplevel

Note that either script will only work in object mode and they will delete the old toplevel objects. They expect the new toplevel object to be selected/active and will exempt it from deletion.

Also notice: In your image you seem to plan removing second-level objects of one toplevel object. The process is simillar, but instead of searching all, you can search just the toplevel-children (replace the “obj.parent==None” condition with “old.parent==new_toplevel” OR use “new_toplevel.children” in the loop and remove the “obj.parent==None” condition).
Note that if that is the case, you can simply use the select>more/less>child operator in the select-menu in the view3d-header like so:

  1. select toplevel object
  2. press Select>more/less>child twice. (You now have all thirdlevel objects in said hierarchy selected)
  3. Shift-select the toplevel object.
  4. Parent with “keep transform”
  5. Deselect the toplevel object. You now have the old thirdlevel moved up to second level and selected, while the old second level is still there but not selected.
  6. Hide selection. This should hide your new second level, while keeping the old second level in reach.
  7. select the toplevel and press Select>more/less>child once to select them.
  8. Delete should now clear out the old second level objects
  9. Unhide to unhide your old-thirdlevel-new-secondlevel.
  10. Profit! Congratulations, you just migrated the third level up one level while getting rid of the level in between.

Notice that while you can get rid of multiple levels at once in simmilar fashion, you can only parent to one object at a time…