Skip to content

Instantly share code, notes, and snippets.

@jupiterbjy
Last active June 12, 2024 14:39
Show Gist options
  • Save jupiterbjy/805186aa2a5b99a720daf9a8d1062e90 to your computer and use it in GitHub Desktop.
Save jupiterbjy/805186aa2a5b99a720daf9a8d1062e90 to your computer and use it in GitHub Desktop.
exports merged gltf file for objects in each collection, while retaining structure of collections as directories. Also properly selects armature's parented objects automatically.
"""
Blender batch export script with subdir / auto centering / output redirection.
Exports all objects in respective collections that doesn't start with IGNORE_PREFIX.
Files will be saved in script's named subdir, which means script must be saved first.
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
objs = []
for obj in collection.objects:
if obj.name[0] == "_":
print(f"Skipping {root}/{collection.name}/{obj.name}")
continue
objs.append(obj)
if objs:
yield (objs, "/".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(objs, 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)
# prep name from collection
name = "_".join(part.lower() for part in base_path.stem.split())
# build clean path without illegal characters
fn = base_path / (bpy.path.clean_name(name) + EXTENSION)
# cache old pos, assuming all objects has same origin
old_pos = objs[0].location[:]
old_scale = objs[0].scale[:]
# some exporters only use the active object - make sure it's selected and active
objs[0].select_set(True)
view_layer.objects.active = objs[0]
# select all obj in list
for obj in objs:
obj.select_set(True)
# 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
obj.location = (0, 0, 0)
obj.scale = (SCALE, SCALE, SCALE)
# export
EXPORTER(filepath=fn.as_posix(), export_apply=True, use_selection=True)
print("written: ", fn)
# reset scale and position
for obj in objs:
obj.location = old_pos
obj.scale = old_scale
bpy.ops.object.select_all(action='DESELECT')
# Driver ---
def main():
# only works in object mode, so change to that
bpy.ops.object.mode_set(mode="OBJECT")
# 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