Files
workinf_Blender_Wasm/tools/web/blender-check-glb-roundtrip.py
2026-08-12 04:47:48 -04:00

145 lines
6.2 KiB
Python

import json
import math
import os
import sys
import bpy
def rounded(values):
return [round(float(value), 6) for value in values]
def socket_value(node, name, fallback=None):
if node is None or name not in node.inputs:
return fallback
value = node.inputs[name].default_value
return round(float(value), 6) if not hasattr(value, "__len__") else rounded(value)
def main(glb_path, output_path):
bpy.ops.wm.read_factory_settings(use_empty=True)
result = bpy.ops.import_scene.gltf(filepath=os.path.abspath(glb_path))
if "FINISHED" not in result:
raise RuntimeError(f"Blender glTF importer failed: {result}")
scene = bpy.context.scene
scene.render.fps = 24
mesh_object = bpy.data.objects.get("RoundTripMesh")
if mesh_object is None or mesh_object.type != "MESH":
raise RuntimeError("RoundTripMesh was not imported")
mesh = mesh_object.data
mesh.calc_loop_triangles()
shape_keys = {}
if mesh.shape_keys is not None:
basis = mesh.shape_keys.key_blocks[0]
for key in mesh.shape_keys.key_blocks[1:]:
shape_keys[key.name] = [
round((key.data[index].co - basis.data[index].co).length, 6)
for index in range(len(key.data))
]
material = mesh.materials[0] if mesh.materials else None
principled = None
texture = None
if material and material.node_tree:
principled = next((node for node in material.node_tree.nodes if node.type == "BSDF_PRINCIPLED"), None)
texture = next(
(node for candidate in bpy.data.materials if candidate.node_tree
for node in candidate.node_tree.nodes if node.type == "TEX_IMAGE"),
None,
)
def animated_location(frame):
scene.frame_set(frame)
return rounded(mesh_object.matrix_world.translation)
armature = next((obj for obj in bpy.data.objects if obj.type == "ARMATURE"), None)
skinned = bpy.data.objects.get("SkinnedMesh")
weight_sums = []
if skinned is not None and skinned.type == "MESH":
for vertex in skinned.data.vertices:
weight_sums.append(round(sum(group.weight for group in vertex.groups), 6))
image = texture.image if texture is not None else None
report = {
"mesh": {
"vertexCount": len(mesh.vertices),
"triangleCount": len(mesh.loop_triangles),
"positions": [rounded(vertex.co) for vertex in mesh.vertices],
"frame1WorldTranslation": animated_location(1),
"frame25WorldTranslation": animated_location(25),
"shapeKeys": shape_keys,
},
"material": {
"name": material.name if material else None,
"baseColor": rounded(material.diffuse_color) if material else [],
"metallic": round(float(material.metallic), 6) if material else None,
"roughness": round(float(material.roughness), 6) if material else None,
"blendMethod": material.surface_render_method if material else None,
"texture": {
"present": texture is not None,
"interpolation": texture.interpolation if texture else None,
"extension": texture.extension if texture else None,
"imageWidth": image.size[0] if image else 0,
"imageHeight": image.size[1] if image else 0,
"packed": bool(image and image.packed_file),
"colorSpace": image.colorspace_settings.name if image else None,
},
"principled": {
"baseColor": rounded(principled.inputs["Base Color"].default_value) if principled else [],
"alpha": round(float(principled.inputs["Alpha"].default_value), 6) if principled else None,
"metallic": round(float(principled.inputs["Metallic"].default_value), 6) if principled else None,
"roughness": round(float(principled.inputs["Roughness"].default_value), 6) if principled else None,
"ior": socket_value(principled, "IOR"),
"specularIORLevel": socket_value(principled, "Specular IOR Level"),
"transmissionWeight": socket_value(principled, "Transmission Weight"),
"coatWeight": socket_value(principled, "Coat Weight"),
"coatRoughness": socket_value(principled, "Coat Roughness"),
"emissionStrength": socket_value(principled, "Emission Strength"),
},
"nodes": [
{
"type": node.type,
"name": node.name,
"inputs": {
socket.name: rounded(socket.default_value) if hasattr(socket.default_value, "__len__") else round(float(socket.default_value), 6)
for socket in node.inputs
if hasattr(socket, "default_value")
},
}
for node in material.node_tree.nodes
] if material and material.node_tree else [],
},
"animation": {
"actionCount": len(bpy.data.actions),
"frameRange": rounded([scene.frame_start, scene.frame_end]),
},
"skin": {
"armatureCount": sum(1 for obj in bpy.data.objects if obj.type == "ARMATURE"),
"bones": sorted(bone.name for bone in armature.data.bones) if armature else [],
"boneParents": {
bone.name: bone.parent.name if bone.parent else None
for bone in armature.data.bones
} if armature else {},
"modifierCount": len(skinned.modifiers) if skinned else 0,
"vertexGroupCount": len(skinned.vertex_groups) if skinned else 0,
"weightSums": weight_sums,
},
}
with open(output_path, "w", encoding="utf-8") as handle:
json.dump(report, handle, indent=2, sort_keys=True)
handle.write("\n")
print(
"blender-glb-import-ok "
f"vertices={report['mesh']['vertexCount']} bones={len(report['skin']['bones'])} "
f"actions={report['animation']['actionCount']}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python blender-check-glb-roundtrip.py -- input.glb output.json")
main(arguments[0], arguments[1])