117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a deterministic operator/RNA inventory from pinned Blender 5.2."""
|
|
|
|
import hashlib
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
def decode(value):
|
|
return value.decode("utf-8", errors="replace") if isinstance(value, bytes) else 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
|
|
return result
|
|
|
|
|
|
def operator_info(module_name, operator_name):
|
|
operator_path = f"{module_name}.{operator_name}"
|
|
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"])
|
|
try:
|
|
poll = bool(operator.poll())
|
|
poll_error = None
|
|
except (AttributeError, RuntimeError) as error:
|
|
poll = None
|
|
poll_error = type(error).__name__
|
|
return {
|
|
"operator": operator_path,
|
|
"rnaIdentifier": rna.identifier,
|
|
"registered": True,
|
|
"poll": poll,
|
|
"pollError": poll_error,
|
|
"properties": properties,
|
|
}
|
|
except (AttributeError, KeyError, RuntimeError) as error:
|
|
return {
|
|
"operator": operator_path,
|
|
"rnaIdentifier": None,
|
|
"registered": False,
|
|
"poll": None,
|
|
"pollError": type(error).__name__,
|
|
"properties": [],
|
|
}
|
|
|
|
|
|
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-operator-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}")
|
|
operators = []
|
|
for module_name in sorted(name for name in dir(bpy.ops) if not name.startswith("_")):
|
|
try:
|
|
module = getattr(bpy.ops, module_name)
|
|
names = sorted(name for name in dir(module) if not name.startswith("_"))
|
|
except (AttributeError, RuntimeError):
|
|
continue
|
|
for operator_name in names:
|
|
try:
|
|
value = getattr(module, operator_name)
|
|
if not hasattr(value, "get_rna_type"):
|
|
continue
|
|
except (AttributeError, RuntimeError):
|
|
continue
|
|
operators.append(operator_info(module_name, operator_name))
|
|
operators.sort(key=lambda entry: entry["operator"])
|
|
operator_ids = [entry["operator"] for entry in operators]
|
|
if len(operator_ids) != len(set(operator_ids)):
|
|
raise RuntimeError("duplicate Blender operator idname")
|
|
binary_path = pathlib.Path(bpy.app.binary_path).resolve()
|
|
inventory = {
|
|
"schemaVersion": 1,
|
|
"task": "M15-01B",
|
|
"operation": "BLENDER_OPERATOR_INVENTORY",
|
|
"sourceAnchor": "blender-5.2.0/source/blender/editors",
|
|
"runtime": {
|
|
"blenderVersion": decode(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),
|
|
"binarySha256": binary_sha256(binary_path),
|
|
},
|
|
"summary": {"count": len(operators), "registered": sum(1 for entry in operators if entry["registered"]), "pollTrue": sum(1 for entry in operators if entry["poll"] is True), "pollFalse": sum(1 for entry in operators if entry["poll"] is False), "pollUnknown": sum(1 for entry in operators if entry["poll"] is None)},
|
|
"operators": operators,
|
|
"nextTask": "M15-01C",
|
|
}
|
|
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-operator-inventory-generated count={len(operators)} blender={bpy.app.version_string} output={output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|