130 lines
4.7 KiB
Python
130 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate deterministic Blender 5.2 modifier, constraint, and node inventories."""
|
|
|
|
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 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):
|
|
enum = rna_type.bl_rna.properties[property_name].enum_items
|
|
entries = [enum_item_info(item) for item in enum]
|
|
entries.sort(key=lambda item: item["identifier"])
|
|
return entries
|
|
|
|
|
|
def node_properties(rna):
|
|
properties = [property_info(prop) for prop in rna.properties if prop.identifier != "rna_type"]
|
|
properties.sort(key=lambda prop: prop["identifier"])
|
|
return properties
|
|
|
|
|
|
def node_inventory():
|
|
entries = []
|
|
for class_name in sorted(name for name in dir(bpy.types) if not name.startswith("_")):
|
|
cls = getattr(bpy.types, class_name)
|
|
try:
|
|
if not isinstance(cls, type) or not issubclass(cls, bpy.types.Node) or cls is bpy.types.Node:
|
|
continue
|
|
is_registered = getattr(cls, "is_registered_node_type", None)
|
|
if is_registered is None or not is_registered():
|
|
continue
|
|
rna = getattr(cls, "bl_rna", None)
|
|
identifier = text(getattr(rna, "identifier", "")) if rna else ""
|
|
family = next((prefix[:-4] for prefix in ("ShaderNode", "GeometryNode", "CompositorNode") if identifier.startswith(prefix)), None)
|
|
if not family:
|
|
continue
|
|
entries.append({
|
|
"family": family,
|
|
"className": class_name,
|
|
"rnaIdentifier": identifier,
|
|
"properties": node_properties(rna),
|
|
})
|
|
except (AttributeError, RuntimeError, TypeError):
|
|
continue
|
|
entries.sort(key=lambda entry: (entry["family"], entry["rnaIdentifier"], entry["className"]))
|
|
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-family-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}")
|
|
modifiers = enum_inventory(bpy.types.Modifier, "type")
|
|
constraints = enum_inventory(bpy.types.Constraint, "type")
|
|
nodes = node_inventory()
|
|
family_counts = {family: sum(1 for node in nodes if node["family"] == family) for family in ("Shader", "Geometry", "Compositor")}
|
|
binary_path = pathlib.Path(bpy.app.binary_path).resolve()
|
|
inventory = {
|
|
"schemaVersion": 1,
|
|
"task": "M15-01C",
|
|
"operation": "BLENDER_FAMILY_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": {
|
|
"modifierCount": len(modifiers),
|
|
"constraintCount": len(constraints),
|
|
"nodeCount": len(nodes),
|
|
"nodesByFamily": family_counts,
|
|
},
|
|
"modifiers": modifiers,
|
|
"constraints": constraints,
|
|
"nodes": nodes,
|
|
"nextTask": "M15-01D",
|
|
}
|
|
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-family-inventory-generated modifiers={len(modifiers)} constraints={len(constraints)} nodes={len(nodes)} output={output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|