Skip to content

Instantly share code, notes, and snippets.

@jupiterbjy
Last active June 8, 2024 20:06
Show Gist options
  • Save jupiterbjy/09820be0064579d1c4db2ea6df59bc1f to your computer and use it in GitHub Desktop.
Save jupiterbjy/09820be0064579d1c4db2ea6df59bc1f to your computer and use it in GitHub Desktop.
Export blender file's all objects while retaining collection structure as directories. Also selects armature and it's parented objects for proper exporting
"""
Blender batch export script with subdir / auto centering / output redirection.
Exports all selected objects into individual files specified in EXPORT_TYPE.
All files will be saved in script's named subdir.
No need to center every object into world center - script will handle that.
Written by jupiterbjy@gmail.com
"""
import os
import pathlib
import json
import bpy
# CONFIG --------------
EXPORT_TYPE = "gltf"
SCALE = 1.0
IGNORE_PREFIX = "_"
# ---------------------
# select extension and exporter
EXTENSION = {
"gltf": ".glb",
"fbx": ".fbx",
"stl": ".stl",
}[EXPORT_TYPE]
EXPORTER = {
"gltf": getattr(bpy.ops.export_scene, "gltf"),
"fbx": getattr(bpy.ops.export_mesh, "fbx"),
"stl": getattr(bpy.ops.export_mesh, "stl"),
}[EXPORT_TYPE]
# check if file is saved
if not bpy.data.filepath:
raise Exception("Blend file is not saved")
# init basedir
_file = pathlib.Path(bpy.data.filepath)
basedir = _file.parent / _file.stem
basedir.mkdir(exist_ok=True)
# cache view layer
view_layer = bpy.context.view_layer
# shadow print() for output redirection to UI ---------
_overrides = []
for window in bpy.context.window_manager.windows:
_overrides.extend(
{"window": window, "screen": window.screen, "area": area} for area in window.screen.areas if
area.type == "CONSOLE"
)
def print(*args, sep=""):
for override in _overrides:
with bpy.context.temp_override(**override):
bpy.ops.console.scrollback_append(text=sep.join(map(str, args)), type="OUTPUT")
def _visit(collection, root):
if collection.name[0] == "_":
print(f"Skipping {root}/{collection.name}")
return
for obj in collection.objects:
if obj.name[0] == "_":
print(f"Skipping {root}/{collection.name}/{obj.name}")
continue
yield (obj, "/".join((root, collection.name)))
print(collection.name)
for child_collection in collection.children:
yield from _visit(child_collection, "/".join((root, collection.name)))
def visit():
"""Visit collection recursively and yield (object, str_collection_path) pairs"""
for collection in bpy.context.scene.collection.children:
yield from _visit(collection, "")
def save_obj(obj, col_path):
"""Save given object at collection path"""
bpy.ops.object.select_all(action='DESELECT')
# prep path
base_path = basedir / col_path
base_path.mkdir(exist_ok=True, parents=True)
# build clean path without illegal characters
fn = base_path / (bpy.path.clean_name(obj.name) + EXTENSION)
# some exporters only use the active object - make sure it's selected and active
obj.select_set(True)
view_layer.objects.active = obj
# if armature, select all meshes parented to the armature
if obj.type == "ARMATURE":
for child_obj in obj.children:
child_obj.select_set(True)
# move object to world center - or some exporter like gltf keeps offset when saving
# need to cache old position explicitly
old_pos = obj.location[:]
obj.location = (0, 0, 0)
old_scale = obj.scale[:]
obj.scale = (SCALE, SCALE, SCALE)
# export
EXPORTER(filepath=fn.as_posix(), export_apply=True, use_selection=True)
print("written: ", fn)
# reset selection and position
obj.location = old_pos
obj.scale = old_scale
obj.select_set(False)
# Driver ---
def main():
# for each cached selection export
for obj, col_path in visit():
# strip preceeding slash
save_obj(obj, col_path[1:])
main()
bpy.ops.object.select_all(action='DESELECT')
@jupiterbjy
Copy link
Author

image

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment