70 lines
4.0 KiB
Python
70 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
import tempfile
|
|
|
|
import bpy
|
|
|
|
|
|
def rounded(value):
|
|
return round(float(value), 6)
|
|
|
|
|
|
def canonical():
|
|
obj = bpy.data.objects.get("WBV2P3004RoundTripMeshObject")
|
|
armature = bpy.data.objects.get("WBV2P3004ArmatureObject")
|
|
if obj is None or armature is None or obj.type != "MESH" or armature.type != "ARMATURE":
|
|
raise RuntimeError("GLB_INVALID: roundtrip fixture objects are missing")
|
|
material = obj.data.materials[0] if obj.data.materials else None
|
|
principled = material.node_tree.nodes.get("Principled BSDF") if material and material.node_tree else None
|
|
shape_keys = []
|
|
if obj.data.shape_keys:
|
|
for key in obj.data.shape_keys.key_blocks[1:]:
|
|
shape_keys.append({"name": key.name, "value": rounded(key.value), "vertexCount": len(key.data)})
|
|
weights = []
|
|
for vertex in obj.data.vertices:
|
|
weights.append(sorted(rounded(group.weight) for group in vertex.groups))
|
|
action = obj.animation_data.action if obj.animation_data else None
|
|
channels = []
|
|
if action:
|
|
for layer in action.layers:
|
|
for strip in layer.strips:
|
|
for bag in strip.channelbags:
|
|
channels.extend(curve.data_path for curve in bag.fcurves)
|
|
return {
|
|
"mesh": {"name": obj.data.name, "vertexCount": len(obj.data.vertices), "triangleCount": len(obj.data.loop_triangles), "uvLayers": [layer.name for layer in obj.data.uv_layers], "shapeKeys": shape_keys},
|
|
"material": {"name": material.name if material else None, "baseColor": [rounded(value) for value in principled.inputs["Base Color"].default_value] if principled else [], "metallic": rounded(principled.inputs["Metallic"].default_value) if principled else None, "roughness": rounded(principled.inputs["Roughness"].default_value) if principled else None, "imagePacked": bool(material and any(node.type == "TEX_IMAGE" and node.image and node.image.packed_file for node in material.node_tree.nodes))},
|
|
"skin": {"bones": [bone.name for bone in armature.data.bones], "parents": {bone.name: bone.parent.name if bone.parent else None for bone in armature.data.bones}, "vertexGroups": [group.name for group in obj.vertex_groups], "weightSums": weights},
|
|
"animation": {"actionName": action.name if action else None, "channelCount": len(channels), "frameRange": [bpy.context.scene.frame_start, bpy.context.scene.frame_end]},
|
|
}
|
|
|
|
|
|
def main():
|
|
arguments = sys.argv[sys.argv.index("--") + 1:]
|
|
if len(arguments) != 2:
|
|
raise SystemExit("usage: blender -b --factory-startup --python check-wbv2-p3-004-desktop.py -- FIXTURE REPORT")
|
|
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
|
|
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
|
|
before = canonical()
|
|
descriptor, temporary = tempfile.mkstemp(prefix="wbv2-p3-004-reopen-", suffix=".blend", dir=fixture.parent)
|
|
os.close(descriptor)
|
|
try:
|
|
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
|
|
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
|
|
after = canonical()
|
|
finally:
|
|
pathlib.Path(temporary).unlink(missing_ok=True)
|
|
if before != after:
|
|
raise RuntimeError(f"GLB_INVALID: desktop save/reopen drift: {before} != {after}")
|
|
value = {"schemaVersion": 1, "task": "WBV2-P3-004", "operation": "IO_GLB_ROUNDTRIP_DESKTOP", "fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(), "canonical": after, "saveReopen": "EXACT", "blenderVersion": bpy.app.version_string}
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
output.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
print("wbv2-p3-004-desktop-ok vertices=%d triangles=%d bones=%d morphs=%d channels=%d texture=packed saveReopen=exact" % (after["mesh"]["vertexCount"], after["mesh"]["triangleCount"], len(after["skin"]["bones"]), len(after["mesh"]["shapeKeys"]), after["animation"]["channelCount"]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|