gutter_runner/blender/batch_export.py

88 lines
3.1 KiB
Python

import bpy
import os
import pathlib
# Ensure blend file is saved
blend_filepath = bpy.data.filepath
if not blend_filepath:
raise Exception("Blend file is not saved")
assert bpy.context.scene is not None
def clean_blend_path(blend_path: str) -> str:
return os.path.join(os.path.dirname(blend_path), bpy.path.clean_name(os.path.basename(blend_path)))
# Directory setup
abs_filepath = bpy.path.abspath(blend_filepath)
src_assets_path = os.path.dirname(abs_filepath)
project_path = os.path.dirname(src_assets_path)
assets_path = os.path.join(project_path, "assets")
rel_blend_path = os.path.relpath(abs_filepath, src_assets_path)
clean_rel_path = clean_blend_path(rel_blend_path)
scene_name = bpy.context.scene.name
instance_output_path = os.path.join(assets_path, "blender", clean_rel_path, f"{scene_name}.scn")
# Utility: returns path to store the glb
def get_model_export_path(obj: bpy.types.Object) -> str:
if obj.instance_type == 'COLLECTION' and obj.instance_collection:
lib_path = obj.instance_collection.library.filepath
collection_name = bpy.path.clean_name(obj.instance_collection.name)
rel_lib_path = os.path.relpath(bpy.path.abspath(lib_path), src_assets_path)
rel_lib_path = clean_blend_path(rel_lib_path)
return os.path.join("blender", rel_lib_path, f"{collection_name}.glb")
else:
obj_name = bpy.path.clean_name(obj.name)
return os.path.join("blender", clean_rel_path, f"{obj_name}.glb")
# Utility: writes an object to glb
def export_object_as_glb(obj: bpy.types.Object, export_path: str):
full_export_path = os.path.join(assets_path, export_path)
pathlib.Path(os.path.dirname(full_export_path)).mkdir(parents=True, exist_ok=True)
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
assert bpy.context.view_layer is not None
bpy.context.view_layer.objects.active = obj
bpy.ops.export_scene.gltf(filepath=full_export_path, use_selection=True, export_apply=True)
print(f"Exported {obj.name} -> {export_path}")
# Collect all visible, non-hidden objects in the scene
visible_objects = [
obj for obj in bpy.context.scene.objects
if obj.visible_get()
]
exported = set()
instances = []
for obj in visible_objects:
model_path = get_model_export_path(obj)
if model_path not in exported:
export_object_as_glb(obj, model_path)
exported.add(model_path)
loc = obj.location
rot = obj.rotation_quaternion
scale = obj.scale
instance_sexpr = (
'(inst'
f'\n\t:model "assets/{model_path}"'
f'\n\t:pos ({loc.x:.6f} {loc.y:.6f} {loc.z:.6f}) '
f'\n\t:rot ({rot.x:.6f} {rot.y:.6f} {rot.z:.6f} {rot.w:.6f}) '
f'\n\t:scale ({scale.x:.6f} {scale.y:.6f} {scale.z:.6f}))'
)
instances.append(instance_sexpr)
# Save instances to scene file
instance_output_abs = os.path.join(project_path, instance_output_path)
pathlib.Path(os.path.dirname(instance_output_abs)).mkdir(parents=True, exist_ok=True)
with open(instance_output_abs, "w", encoding="utf-8") as f:
for line in instances:
f.write(line + "\n")
print(f"Written instances to {instance_output_abs}")