#!/usr/bin/env python3 """Generate deterministic Blender 5.2 editor, space, workspace, and keymap inventory.""" import hashlib import json import pathlib 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 enum_info(rna_type, property_name): entries = [] for item in rna_type.bl_rna.properties[property_name].enum_items: entries.append({"identifier": item.identifier, "name": text(item.name), "description": text(item.description), "value": item.value}) entries.sort(key=lambda entry: entry["identifier"]) return entries 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 space_types(): entries = [] for class_name in sorted(name for name in dir(bpy.types) if name.startswith("Space")): cls = getattr(bpy.types, class_name) try: if not isinstance(cls, type) or cls is bpy.types.Space or not issubclass(cls, bpy.types.Space): continue rna = getattr(cls, "bl_rna", None) if not rna: continue props = [property_info(prop) for prop in rna.properties if prop.identifier != "rna_type"] props.sort(key=lambda prop: prop["identifier"]) entries.append({"className": class_name, "rnaIdentifier": text(rna.identifier), "properties": props}) except (AttributeError, RuntimeError, TypeError): continue return entries def keymaps(): bpy.utils.keyconfig_init() keyconfig = bpy.context.window_manager.keyconfigs.default maps = [] for keymap in sorted(keyconfig.keymaps, key=lambda value: f"{value.name}:{value.space_type}:{value.region_type}"): items = [] for item in keymap.keymap_items: items.append({ "idname": text(item.idname), "type": text(item.type), "value": text(item.value), "ctrl": bool(item.ctrl), "shift": bool(item.shift), "alt": bool(item.alt), "oskey": bool(item.oskey), "any": bool(item.any), "repeat": bool(item.repeat), "keyModifier": text(item.key_modifier), "direction": text(item.direction), "mapType": text(item.map_type), "active": bool(item.active), }) items.sort(key=lambda item: tuple(item.values())) maps.append({"name": text(keymap.name), "spaceType": text(keymap.space_type), "regionType": text(keymap.region_type), "modal": bool(keymap.is_modal), "items": items}) return maps 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-editor-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}") areas = enum_info(bpy.types.Area, "type") regions = enum_info(bpy.types.Region, "type") spaces = enum_info(bpy.types.Space, "type") workspace_modes = enum_info(bpy.types.WorkSpace, "object_mode") space_classes = space_types() maps = keymaps() binary_path = pathlib.Path(bpy.app.binary_path).resolve() inventory = { "schemaVersion": 1, "task": "M15-01E", "operation": "BLENDER_EDITOR_INVENTORY", "sourceAnchor": "blender-5.2.0/source/blender/editors", "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": {"areaTypeCount": len(areas), "regionTypeCount": len(regions), "spaceTypeCount": len(spaces), "spaceClassCount": len(space_classes), "workspaceModeCount": len(workspace_modes), "keymapCount": len(maps), "keymapItemCount": sum(len(keymap["items"]) for keymap in maps)}, "areaTypes": areas, "regionTypes": regions, "spaceTypes": spaces, "spaceClasses": space_classes, "workspaceModes": workspace_modes, "keymaps": maps, "nextTask": "M15-01F", } 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-editor-inventory-generated areas={len(areas)} regions={len(regions)} spaces={len(spaces)} keymaps={len(maps)} items={inventory['summary']['keymapItemCount']} output={output}") if __name__ == "__main__": main()