165 lines
6.3 KiB
Python
165 lines
6.3 KiB
Python
import hashlib
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
ROOT_OBJECT = "M12 Override Object"
|
|
MESH_NAME = "M12 Override Mesh"
|
|
MATERIAL_NAME = "M12 Override Material"
|
|
SOURCE_MARKER = "M12-03J"
|
|
PROJECT_ID = "m12-03j-project"
|
|
OVERRIDE_X = 2.5
|
|
OVERRIDE_PROPERTY_PATH = '["m12_override_value"]'
|
|
|
|
|
|
def sha256_file(path: pathlib.Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def reset() -> None:
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
|
|
|
|
def create_source(path: pathlib.Path) -> None:
|
|
reset()
|
|
material = bpy.data.materials.new(MATERIAL_NAME)
|
|
material.diffuse_color = (0.15, 0.65, 0.35, 1.0)
|
|
mesh = bpy.data.meshes.new(MESH_NAME)
|
|
mesh.from_pydata([(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)], [], [(0, 1, 2, 3)])
|
|
mesh.materials.append(material)
|
|
obj = bpy.data.objects.new(ROOT_OBJECT, mesh)
|
|
obj["m12_source_marker"] = SOURCE_MARKER
|
|
obj["m12_override_value"] = 1.0
|
|
bpy.context.scene.collection.objects.link(obj)
|
|
bpy.ops.wm.save_as_mainfile(filepath=str(path), check_existing=False)
|
|
|
|
|
|
def data_block(id_type: str, value: object) -> dict:
|
|
library = value.library
|
|
return {
|
|
"idType": id_type,
|
|
"name": value.name,
|
|
"nameFull": value.name_full,
|
|
"library": None if library is None else pathlib.Path(library.filepath).name,
|
|
"isLibraryOverride": value.override_library is not None,
|
|
}
|
|
|
|
|
|
def override_property(value: object) -> dict:
|
|
override = value.override_library
|
|
if override is None:
|
|
raise RuntimeError("override library metadata is missing")
|
|
properties = list(override.properties)
|
|
matches = [item for item in properties if item.rna_path == OVERRIDE_PROPERTY_PATH]
|
|
if not matches:
|
|
print("DEBUG_OVERRIDE_PROPERTIES", [(item.rna_path, len(item.operations)) for item in properties], dir(override.properties))
|
|
raise RuntimeError("custom property override path is missing")
|
|
return {
|
|
"rnaPath": OVERRIDE_PROPERTY_PATH,
|
|
"index": 0,
|
|
"value": float(value["m12_override_value"]),
|
|
"operationCount": sum(len(item.operations) for item in matches),
|
|
"propertyCount": len(properties),
|
|
}
|
|
|
|
|
|
def inspect_override() -> dict:
|
|
obj = bpy.data.objects[ROOT_OBJECT]
|
|
override = obj.override_library
|
|
if override is None or override.reference is None:
|
|
raise RuntimeError("desktop override reference is missing")
|
|
reference = override.reference
|
|
if reference.library is None:
|
|
raise RuntimeError("override reference did not retain source library")
|
|
if obj.library is not None:
|
|
raise RuntimeError("override object must be locally owned")
|
|
return {
|
|
"reference": {
|
|
"dataBlockId": f"Object/{reference.name}",
|
|
"idType": "OBJECT",
|
|
"library": pathlib.Path(reference.library.filepath).name,
|
|
"owner": "SOURCE_LIBRARY",
|
|
"readOnly": True,
|
|
"isLibraryOverride": False,
|
|
},
|
|
"local": {
|
|
"dataBlockId": f"Object/{obj.name}",
|
|
"idType": "OBJECT",
|
|
"library": None,
|
|
"owner": "LOCAL_OVERRIDE",
|
|
"projectId": PROJECT_ID,
|
|
"readOnly": False,
|
|
"referenceSourceDataBlockId": f"Object/{reference.name}",
|
|
"hierarchyRootDataBlockId": f"Object/{obj.name}",
|
|
"isLibraryOverride": True,
|
|
},
|
|
"propertyOverride": override_property(obj),
|
|
"sourceMarker": reference.get("m12_source_marker"),
|
|
}
|
|
|
|
|
|
def create_override(source: pathlib.Path, target: pathlib.Path) -> dict:
|
|
reset()
|
|
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
|
|
if ROOT_OBJECT not in data_from.objects:
|
|
raise RuntimeError("source root object is missing")
|
|
data_to.objects = [ROOT_OBJECT]
|
|
if len(data_to.objects) != 1 or data_to.objects[0] is None:
|
|
raise RuntimeError("desktop link did not return one object")
|
|
linked = data_to.objects[0]
|
|
bpy.context.scene.collection.objects.link(linked)
|
|
bpy.context.view_layer.objects.active = linked
|
|
linked.select_set(True)
|
|
if bpy.ops.object.make_override_library() != {"FINISHED"}:
|
|
raise RuntimeError("desktop override operator did not finish")
|
|
obj = bpy.data.objects[ROOT_OBJECT]
|
|
obj["m12_override_value"] = OVERRIDE_X
|
|
prop = obj.override_library.properties.add(OVERRIDE_PROPERTY_PATH)
|
|
prop.operations.add("REPLACE")
|
|
before_save = inspect_override()
|
|
if before_save["propertyOverride"]["value"] != OVERRIDE_X:
|
|
raise RuntimeError("override property did not apply")
|
|
bpy.ops.wm.save_as_mainfile(filepath=str(target), check_existing=False)
|
|
bpy.ops.wm.open_mainfile(filepath=str(target), load_ui=False)
|
|
reopened = inspect_override()
|
|
if reopened != before_save:
|
|
raise RuntimeError("desktop override metadata drifted after save/reopen")
|
|
return reopened
|
|
|
|
|
|
def main() -> None:
|
|
if "--" not in sys.argv or len(sys.argv[sys.argv.index("--") + 1 :]) != 2:
|
|
raise SystemExit("usage: blender --background --factory-startup --python generate-library-override-fixture.py -- OUTPUT_DIR REPORT")
|
|
output_arg, report_arg = sys.argv[sys.argv.index("--") + 1 :]
|
|
output_dir = pathlib.Path(output_arg).resolve()
|
|
report_path = pathlib.Path(report_arg).resolve()
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
report_path.parent.mkdir(parents=True, exist_ok=True)
|
|
source = output_dir / "m12_override_source.blend"
|
|
target = output_dir / "m12_override_target.blend"
|
|
create_source(source)
|
|
override_graph = create_override(source, target)
|
|
report = {
|
|
"schemaVersion": 1,
|
|
"task": "M12-03J",
|
|
"operation": "LIBRARY_OVERRIDE",
|
|
"blenderVersion": "5.2.0",
|
|
"source": {"file": source.name, "sha256": sha256_file(source)},
|
|
"target": {"file": target.name, "sha256": sha256_file(target)},
|
|
"selectedRoots": [f"Object/{ROOT_OBJECT}"],
|
|
"overrideGraph": override_graph,
|
|
"nextTask": "M12-03K",
|
|
}
|
|
report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
print(
|
|
f"library-override-fixture-ok roots={len(report['selectedRoots'])} "
|
|
f"reference={override_graph['reference']['library']} owner={override_graph['local']['owner']} "
|
|
f"path={override_graph['propertyOverride']['rnaPath']} next={report['nextTask']}"
|
|
)
|
|
|
|
|
|
main()
|