Last active
June 12, 2024 14:39
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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') |
Author
jupiterbjy
commented
Jun 12, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment