87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
"""Batch-convert GLB files to FBX through Blender.
|
|
|
|
Run this file with Blender's Python, not the system Python. The script keeps
|
|
the source folder layout and writes an errors.csv next to the output files.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import csv
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import bpy
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source", required=True, type=Path)
|
|
parser.add_argument("--destination", required=True, type=Path)
|
|
parser.add_argument("--overwrite", action="store_true")
|
|
return parser.parse_args(sys.argv[sys.argv.index("--") + 1 :] if "--" in sys.argv else [])
|
|
|
|
|
|
def clear_scene() -> None:
|
|
bpy.ops.object.select_all(action="SELECT")
|
|
bpy.ops.object.delete(use_global=False)
|
|
for collection in (bpy.data.meshes, bpy.data.materials, bpy.data.images, bpy.data.armatures):
|
|
for item in list(collection):
|
|
collection.remove(item)
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
source = args.source.resolve()
|
|
destination = args.destination.resolve()
|
|
if not source.is_dir():
|
|
print(f"ERROR: source folder does not exist: {source}")
|
|
return 2
|
|
if source == destination:
|
|
print("ERROR: destination must be different from source")
|
|
return 2
|
|
|
|
glb_files = sorted(source.rglob("*.glb"))
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
errors: list[tuple[str, str]] = []
|
|
converted = skipped = 0
|
|
|
|
for index, input_file in enumerate(glb_files, start=1):
|
|
output_file = destination / input_file.relative_to(source).with_suffix(".fbx")
|
|
if output_file.exists() and not args.overwrite:
|
|
print(f"[{index}/{len(glb_files)}] SKIP {input_file.name}")
|
|
skipped += 1
|
|
continue
|
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
clear_scene()
|
|
bpy.ops.import_scene.gltf(filepath=str(input_file))
|
|
bpy.ops.export_scene.fbx(
|
|
filepath=str(output_file),
|
|
use_selection=False,
|
|
object_types={"EMPTY", "CAMERA", "LIGHT", "ARMATURE", "MESH", "OTHER"},
|
|
use_mesh_modifiers=True,
|
|
add_leaf_bones=False,
|
|
path_mode="COPY",
|
|
embed_textures=True,
|
|
)
|
|
converted += 1
|
|
print(f"[{index}/{len(glb_files)}] OK {input_file.name}")
|
|
except Exception as exc: # continue converting the remaining assets
|
|
errors.append((str(input_file), repr(exc)))
|
|
print(f"[{index}/{len(glb_files)}] FAIL {input_file.name}: {exc}")
|
|
|
|
if errors:
|
|
error_log = destination / "errors.csv"
|
|
with error_log.open("w", newline="", encoding="utf-8-sig") as handle:
|
|
writer = csv.writer(handle)
|
|
writer.writerow(("source_file", "error"))
|
|
writer.writerows(errors)
|
|
print(f"Error log: {error_log}")
|
|
print(f"Finished: {converted} converted, {skipped} skipped, {len(errors)} failed.")
|
|
return 1 if errors else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|