#!/usr/bin/env python3 """Generate deterministic Blender 5.2 sequencer, physics, and I/O inventories.""" import hashlib import json import pathlib import re import sys import bpy def text(value): return value.decode("utf-8", errors="replace") if isinstance(value, bytes) else str(value) def sha256_file(path): digest = hashlib.sha256() with open(path, "rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def property_info(prop): result = {"identifier": prop.identifier, "type": prop.type, "readOnly": bool(prop.is_readonly)} if hasattr(prop, "array_length"): result["arrayLength"] = prop.array_length return result def enum_item_info(item): return {"identifier": item.identifier, "name": text(item.name), "description": text(item.description), "value": item.value} def enum_inventory(rna_type, property_name): entries = [enum_item_info(item) for item in rna_type.bl_rna.properties[property_name].enum_items] entries.sort(key=lambda item: item["identifier"]) return entries def class_properties(rna): entries = [property_info(prop) for prop in rna.properties if prop.identifier != "rna_type"] entries.sort(key=lambda prop: prop["identifier"]) return entries def physics_inventory(): marker = re.compile(r"(Particle|Boid|Cloth|SoftBody|RigidBody|Fluid|DynamicPaint|Effector|Collision|Field|PointCache|BakeSettings)") entries = [] for class_name in sorted(name for name in dir(bpy.types) if not name.startswith("_")): cls = getattr(bpy.types, class_name) if not isinstance(cls, type) or not marker.search(class_name) or re.match(r"[A-Z0-9]+_(?:PT|OT|MT|UL)_", class_name): continue rna = getattr(cls, "bl_rna", None) if not rna or class_name.endswith("Node"): continue entries.append({"className": class_name, "rnaIdentifier": text(rna.identifier), "properties": class_properties(rna)}) entries.sort(key=lambda entry: (entry["rnaIdentifier"], entry["className"])) return entries def operator_inventory(): entries = [] for module_name in sorted(name for name in dir(bpy.ops) if not name.startswith("_")): module = getattr(bpy.ops, module_name) for operator_name in sorted(name for name in dir(module) if not name.startswith("_")): operator_path = f"{module_name}.{operator_name}" if not re.search(r"(?:import|export)", operator_path, re.IGNORECASE): continue try: operator = getattr(module, operator_name) rna = operator.get_rna_type() properties = class_properties(rna) try: poll = bool(operator.poll()) poll_error = None except (AttributeError, RuntimeError) as error: poll = None poll_error = type(error).__name__ entries.append({"operator": operator_path, "rnaIdentifier": text(rna.identifier), "poll": poll, "pollError": poll_error, "properties": properties}) except (AttributeError, KeyError, RuntimeError): continue entries.sort(key=lambda entry: entry["operator"]) return entries def main(): if "--" not in sys.argv or len(sys.argv[sys.argv.index("--") + 1:]) != 1: raise SystemExit("usage: blender --background --factory-startup --python generate-blender-format-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}") strips = enum_inventory(bpy.types.Strip, "type") physics = physics_inventory() io_operators = operator_inventory() binary_path = pathlib.Path(bpy.app.binary_path).resolve() inventory = { "schemaVersion": 1, "task": "M15-01D", "operation": "BLENDER_FORMAT_INVENTORY", "sourceAnchor": "blender-5.2.0/source/blender/makesrna", "runtime": { "blenderVersion": text(bpy.app.version_string), "versionTuple": list(version), "buildHash": text(bpy.app.build_hash), "buildBranch": text(bpy.app.build_branch), "buildPlatform": text(bpy.app.build_platform), "buildType": text(bpy.app.build_type), "buildDate": text(bpy.app.build_date), "buildTime": text(bpy.app.build_time), "binarySha256": sha256_file(binary_path), }, "summary": {"stripTypeCount": len(strips), "physicsTypeCount": len(physics), "ioOperatorCount": len(io_operators)}, "stripTypes": strips, "physicsTypes": physics, "ioOperators": io_operators, "nextTask": "M15-01E", } output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(inventory, indent=2, sort_keys=True) + "\n", encoding="utf-8") print(f"blender-format-inventory-generated strips={len(strips)} physics={len(physics)} io={len(io_operators)} output={output}") if __name__ == "__main__": main()