How to Batch Export Object Hierarchies to Separate Files in Blender Python
14 Aug 26 (2mo ago)
Automating Object Separation in Blender
When managing modular assets, character rigs, or game-ready meshes, you often need to save different components of a master file into separate .blend files. Doing this manually is tedious and prone to error. Using Blender's Python API (bpy), you can create a "reload loop" that isolates specific hierarchies, purges unwanted data, formats the file names, and sets the UI for the end-user.
The Challenge: Context Errors
A common approach to batch saving is to loop through a list of objects, delete the ones you do not need, and save the file. However, scripts relying on bpy.ops (the Operators API) often break with AttributeError: 'Context' object has no attribute 'object' when reloading the master file.
This happens because the Operators API relies heavily on the user interface state (viewport modes, active selections). When bpy.ops.wm.open_mainfile() reloads the scene, the UI context is momentarily wiped out, causing the script to crash.
The Solution: The Data API
To build a bulletproof script, you must bypass the UI entirely and manipulate the raw data using the Data API (bpy.data).
- Safe Deletion: Instead of selecting objects and triggering a delete operator, build a list of unwanted objects and remove them directly using
bpy.data.objects.remove(obj, do_unlink=True). This bypasses context checks completely. - The Reload Loop: The script operates by capturing the master file path, opening it to ensure a clean slate, processing the data, saving the new
.blendfile, and then reopening the master file to start the next iteration.
Managing Object Visibility
Blender tracks object visibility across three different toggles. When isolating hierarchies, you must ensure the target objects are visible for the final export.
- View Layer (Eye Icon): Controlled via
obj.hide_set(False). - Global Viewport (Monitor Icon): Controlled via
obj.hide_viewport = False. - Render (Camera Icon): Controlled via
obj.hide_render = False.
The Complete Batch Separation Script
The following script iterates through a predefined list of collection groups, deletes the other unneeded groups, ensures visibility, strips naming suffixes for a cleaner file name, and sets the workspace to Layout before exporting.
Prerequisite: You must save your master Blender file to a dedicated directory before running this script, as it relies on bpy.data.filepath to know where to save the isolated files.
import bpy
import os
def export_specific_groups():
# 1. Store the master file path
master_filepath = bpy.data.filepath
if not master_filepath:
print("ERROR: Please save your master Blender file first!")
return
directory = os.path.dirname(master_filepath)
# 2. Define the specific groups to cycle through
target_groups = [
"cut_01_grp", "cut_02_grp", "cut_03_grp",
"cut_04_grp", "cut_05_grp", "cut_06_grp",
"cut_07_grp", "heart_base_grp"
]
for active_group in target_groups:
print(f"--- Exporting: {active_group} ---")
# Reload the master file to reset the scene state
bpy.ops.wm.open_mainfile(filepath=master_filepath)
# Helper function to get an object and ALL its children recursively
def get_hierarchy(obj):
hierarchy = [obj]
for child in obj.children:
hierarchy.extend(get_hierarchy(child))
return hierarchy
# 3. Gather all objects belonging to UNWANTED groups
objects_to_delete = []
for group_name in target_groups:
if group_name != active_group:
obj = bpy.data.objects.get(group_name)
if obj:
objects_to_delete.extend(get_hierarchy(obj))
# 4. Delete them directly via Data API (Immune to Context Errors)
for obj in set(objects_to_delete):
try:
bpy.data.objects.remove(obj, do_unlink=True)
except Exception as e:
print(f"Skipped deleting {obj.name}: {e}")
# 5. Enforce Visibility on the ACTIVE group
obj_to_keep = bpy.data.objects.get(active_group)
if obj_to_keep:
for obj in get_hierarchy(obj_to_keep):
obj.hide_set(False) # View Layer (Eye icon)
obj.hide_viewport = False # Global (Monitor icon)
obj.hide_render = False # Render (Camera icon)
# 6. Set UI to the 'Layout' workspace before saving
layout_ws = bpy.data.workspaces.get('Layout')
if layout_ws and bpy.context.window:
bpy.context.window.workspace = layout_ws
# 7. Format the filename and strip suffixes
base_name = active_group
if base_name.endswith("_grp"):
base_name = base_name[:-4]
new_filename = f"blender_sciepro_heart_anim_{base_name}_v01.1.blend"
new_filepath = os.path.join(directory, new_filename)
# Save to the new isolated file
bpy.ops.wm.save_as_mainfile(filepath=new_filepath)
print(f"Saved: {new_filepath}")
# 8. Restore the original master file
bpy.ops.wm.open_mainfile(filepath=master_filepath)
# Reset UI to the 'Scripting' workspace
scripting_ws = bpy.data.workspaces.get('Scripting')
if scripting_ws and bpy.context.window:
bpy.context.window.workspace = scripting_ws
print("Batch separation complete! Master file restored.")
# Execute the process
export_specific_groups()