#!/usr/bin/env python3 """Generate the format capability inventory from one pinned Blender runtime.""" import hashlib import json import os import pathlib import sys import bpy def decode(value): if isinstance(value, bytes): return value.decode("utf-8", errors="replace") return str(value) def binary_sha256(path): digest = hashlib.sha256() with open(path, "rb") as handle: for block in iter(lambda: handle.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def property_info(prop): result = {"identifier": prop.identifier, "type": prop.type} if hasattr(prop, "array_length"): result["arrayLength"] = prop.array_length if prop.type == "ENUM": try: result["enumItems"] = [item.identifier for item in prop.enum_items] except (AttributeError, RuntimeError): result["enumItems"] = [] return result def operator_info(operator_path, build_option): module_name, operator_name = operator_path.split(".", 1) try: operator = getattr(getattr(bpy.ops, module_name), operator_name) rna = operator.get_rna_type() properties = [property_info(prop) for prop in rna.properties if prop.identifier != "rna_type"] properties.sort(key=lambda prop: prop["identifier"]) enabled = True if build_option is None else bool(getattr(bpy.app.build_options, build_option)) return { "operator": operator_path, "registered": True, "rnaIdentifier": rna.identifier, "buildOption": build_option, "buildOptionEnabled": enabled, "runtimeStatus": "AVAILABLE" if enabled else "BUILD_OPTION_DISABLED", "properties": properties, } except (AttributeError, KeyError, RuntimeError) as error: return { "operator": operator_path, "registered": False, "rnaIdentifier": None, "buildOption": build_option, "buildOptionEnabled": None, "runtimeStatus": "OPERATOR_UNREGISTERED", "properties": [], "error": type(error).__name__, } def format_entry(format_id, family, extensions, import_operator, export_operator, build_option, variants): return { "format": format_id, "family": family, "extensions": extensions, "variants": variants, "import": operator_info(import_operator, build_option), "export": operator_info(export_operator, build_option), } def main(): if "--" not in sys.argv or len(sys.argv[sys.argv.index("--") + 1:]) != 1: raise SystemExit("usage: generate-io-format-runtime-inventory.py OUTPUT.json") output = pathlib.Path(sys.argv[sys.argv.index("--") + 1]).resolve() version = tuple(int(value) for value in bpy.app.version) if version != (5, 2, 0): raise RuntimeError(f"expected Blender 5.2.0, got {version}") binary_path = pathlib.Path(bpy.app.binary_path).resolve() options = { "alembic": bool(bpy.app.build_options.alembic), "usd": bool(bpy.app.build_options.usd), "io_ply": bool(bpy.app.build_options.io_ply), "io_stl": bool(bpy.app.build_options.io_stl), "io_wavefront_obj": bool(bpy.app.build_options.io_wavefront_obj), } formats = [ format_entry("GLTF", "GLTF", [".gltf"], "import_scene.gltf", "export_scene.gltf", None, ["GLTF_SEPARATE"]), format_entry("GLB", "GLTF", [".glb"], "import_scene.gltf", "export_scene.gltf", None, ["GLB"]), format_entry("OBJ", "OBJ", [".obj"], "wm.obj_import", "wm.obj_export", "io_wavefront_obj", ["OBJ"]), format_entry("STL", "STL", [".stl"], "wm.stl_import", "wm.stl_export", "io_stl", ["STL_BINARY", "STL_ASCII"]), format_entry("PLY", "PLY", [".ply"], "wm.ply_import", "wm.ply_export", "io_ply", ["PLY"]), format_entry("USD", "USD", [".usd", ".usda", ".usdc", ".usdz"], "wm.usd_import", "wm.usd_export", "usd", ["USD", "USDA", "USDC", "USDZ"]), format_entry("ALEMBIC", "ALEMBIC", [".abc"], "wm.alembic_import", "wm.alembic_export", "alembic", ["ALEMBIC"]), ] inventory = { "schemaVersion": 1, "task": "M12-05A", "runtime": { "blenderVersion": bpy.app.version_string, "versionTuple": list(version), "buildHash": decode(bpy.app.build_hash), "buildBranch": decode(bpy.app.build_branch), "buildPlatform": decode(bpy.app.build_platform), "buildType": decode(bpy.app.build_type), "buildDate": decode(bpy.app.build_date), "buildTime": decode(bpy.app.build_time), "buildCommitTimestamp": int(bpy.app.build_commit_timestamp), "binarySha256": binary_sha256(binary_path), "buildOptions": options, }, "formats": formats, } output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(inventory, sort_keys=True, indent=2) + "\n", encoding="utf-8") print(f"io-format-runtime-inventory-generated formats={len(formats)} blender={bpy.app.version_string} output={output}") if __name__ == "__main__": main()