Files
workinf_Blender_Wasm/tools/web/generate-library-override-fixture.py
mes123456 380cbed4ff
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

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()