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.