Files
workinf_Blender_Wasm/tools/web/generate-library-append-fixture.py
mes123456 5a11045ca5
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Advance Blender 5.2 web parity through M12-03D
2026-08-17 17:30:27 -04:00

182 lines
6.9 KiB
Python

import hashlib
import json
import pathlib
import struct
import sys
import bpy
ROOT_OBJECT = "M12 Append Object"
MESH_NAME = "M12 Append Mesh"
MATERIAL_NAME = "M12 Append Material"
IMAGE_NAME = "M12 Append Image"
IMAGE_NODE_NAME = "M12 Append Image Node"
def sha256_file(path: pathlib.Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def pixel_sha256(image: bpy.types.Image) -> str:
values = list(image.pixels)
return hashlib.sha256(struct.pack(f"<{len(values)}f", *values)).hexdigest()
def reset() -> None:
bpy.ops.wm.read_factory_settings(use_empty=True)
def create_source(path: pathlib.Path) -> None:
reset()
image = bpy.data.images.new(IMAGE_NAME, width=2, height=2, alpha=True, float_buffer=False)
image.colorspace_settings.name = "sRGB"
image.pixels = [
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.5,
]
image.pack()
material = bpy.data.materials.new(MATERIAL_NAME)
material.use_nodes = True
node_tree = material.node_tree
principled = node_tree.nodes.get("Principled BSDF")
image_node = node_tree.nodes.new("ShaderNodeTexImage")
image_node.name = IMAGE_NODE_NAME
image_node.label = IMAGE_NODE_NAME
image_node.image = image
image_node.interpolation = "Closest"
image_node.extension = "REPEAT"
node_tree.links.new(image_node.outputs["Color"], principled.inputs["Base Color"])
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)
uv_layer = mesh.uv_layers.new(name="UVMap")
for loop, uv in zip(uv_layer.data, [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]):
loop.uv = uv
mesh.update()
obj = bpy.data.objects.new(ROOT_OBJECT, mesh)
obj["m12_source_marker"] = "M12-03C"
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:
return {
"idType": id_type,
"name": value.name,
"nameFull": value.name_full,
"library": None if value.library is None else value.library.filepath,
"isLibraryOverride": value.override_library is not None,
}
def inspect_local_graph() -> dict:
obj = bpy.data.objects[ROOT_OBJECT]
mesh = obj.data
material = mesh.materials[0]
image_node = material.node_tree.nodes[IMAGE_NODE_NAME]
image = image_node.image
ids = {
"OBJECT": data_block("OBJECT", obj),
"MESH": data_block("MESH", mesh),
"MATERIAL": data_block("MATERIAL", material),
"IMAGE": data_block("IMAGE", image),
}
return {
"root": ids["OBJECT"],
"ids": ids,
"edges": [
{"from": "Object/M12 Append Object", "relation": "OBJECT_DATA", "to": "Mesh/M12 Append Mesh"},
{"from": "Mesh/M12 Append Mesh", "relation": "MATERIAL_SLOT[0]", "to": "Material/M12 Append Material"},
{"from": "Material/M12 Append Material", "relation": f"NODE_IMAGE[{IMAGE_NODE_NAME}]", "to": "Image/M12 Append Image"},
],
"geometry": {
"vertices": len(mesh.vertices),
"edges": len(mesh.edges),
"polygons": len(mesh.polygons),
"loops": len(mesh.loops),
"uvLayers": [layer.name for layer in mesh.uv_layers],
"materialSlots": [item.name for item in mesh.materials],
},
"image": {
"size": list(image.size),
"channels": image.channels,
"colorspace": image.colorspace_settings.name,
"packed": image.packed_file is not None,
"pixelFloat32Sha256": pixel_sha256(image),
},
"sourceMarker": obj["m12_source_marker"],
}
def append_object(source: pathlib.Path, target: pathlib.Path) -> dict:
reset()
with bpy.data.libraries.load(str(source), link=False) 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 append did not return one object")
bpy.context.scene.collection.objects.link(data_to.objects[0])
before_save = inspect_local_graph()
if any(item["library"] is not None or item["isLibraryOverride"] for item in before_save["ids"].values()):
raise RuntimeError("appended dependency closure is not fully local")
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_local_graph()
if reopened != before_save:
raise RuntimeError("appended dependency mapping 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-append-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_append_source.blend"
target = output_dir / "m12_append_target.blend"
create_source(source)
source_graph = inspect_local_graph()
appended_graph = append_object(source, target)
report = {
"schemaVersion": 1,
"task": "M12-03C",
"operation": "APPEND",
"blenderVersion": "5.2.0",
"source": {"file": source.name, "sha256": sha256_file(source)},
"target": {"file": target.name, "sha256": sha256_file(target)},
"selectedRoots": ["Object/M12 Append Object"],
"sourceGraph": source_graph,
"appendedGraph": appended_graph,
"stableMapping": [
{"source": "Object/M12 Append Object", "local": "Object/M12 Append Object", "owner": "LOCAL_MAIN", "readOnly": False},
{"source": "Mesh/M12 Append Mesh", "local": "Mesh/M12 Append Mesh", "owner": "LOCAL_MAIN", "readOnly": False},
{"source": "Material/M12 Append Material", "local": "Material/M12 Append Material", "owner": "LOCAL_MAIN", "readOnly": False},
{"source": "Image/M12 Append Image", "local": "Image/M12 Append Image", "owner": "LOCAL_MAIN", "readOnly": False},
],
"nextTask": "M12-03D",
}
report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(
f"library-append-fixture-ok roots={len(report['selectedRoots'])} "
f"mapping={len(report['stableMapping'])} source={report['source']['sha256']} "
f"target={report['target']['sha256']} next={report['nextTask']}"
)
main()