Files
AIGame/Tools/model_tools/bl_verify_studio_static.py
T

58 lines
3.1 KiB
Python

"""Verify a final static FBX against its Studio GLB, using full scene transforms."""
import sys,json,math,hashlib
from pathlib import Path
import bpy
import numpy as np
from mathutils import Matrix
sys.path.insert(0,str(Path(__file__).resolve().parent))
from bl_decimate import prepare_game_export,validate_fbx_yup_scene
folder=Path(sys.argv[sys.argv.index('--')+1]).resolve()
name=sys.argv[sys.argv.index('--')+2]
source=folder/'model_ex.glb';target=folder/'static'/(name+'.fbx')
out=folder/'axis_pipeline_audit';out.mkdir(parents=True,exist_ok=True)
def load(path):
bpy.ops.wm.read_factory_settings(use_empty=True)
if path.suffix=='.fbx':bpy.ops.import_scene.fbx(filepath=str(path),use_anim=False)
else:bpy.ops.import_scene.gltf(filepath=str(path),merge_vertices=False)
meshes=[o for o in bpy.context.scene.objects if o.type=='MESH']
assert len(meshes)==1
return meshes[0]
def uv(obj):return np.array([tuple(v.uv) for v in obj.data.uv_layers.active.data],dtype='<f4')
def triangles(obj):return sum(len(p.vertices)-2 for p in obj.data.polygons)
obj=load(source);prepare_game_export(obj,restore_axis=True)
expected=np.array([Matrix.Rotation(math.pi/2,4,'X')@obj.matrix_world@v.co for v in obj.data.vertices])
source_uv=uv(obj);source_triangles=triangles(obj)
axes=validate_fbx_yup_scene(str(target))
obj=load(target);actual=np.array([obj.matrix_world@v.co for v in obj.data.vertices]);target_uv=uv(obj)
assert actual.shape==expected.shape and triangles(obj)==source_triangles
error=float(np.max(np.abs(actual-expected)));assert error<2e-6
assert np.array_equal(target_uv,source_uv)
lo=actual.min(0);hi=actual.max(0)
assert np.argmax(hi-lo)==2,'The complete scene is horizontal in Blender Z-up'
origin_error=max(abs(lo[2]),abs((lo[0]+hi[0])/2),abs((lo[1]+hi[1])/2));assert origin_error<2e-6
legacy=out/'legacy'/(name+'.fbx')
legacy_rejected=None
if legacy.is_file():
try:validate_fbx_yup_scene(str(legacy));legacy_rejected=False
except ValueError:legacy_rejected=True
report=dict(source=str(source),fbx=str(target),sha256=hashlib.sha256(target.read_bytes()).hexdigest(),
axes=axes,vertices=len(actual),triangles=source_triangles,uv_preserved=True,
uv_sha256=hashlib.sha256(target_uv.tobytes()).hexdigest(),upright=True,
height_m=float(hi[2]-lo[2]),bottom_center_error_m=float(origin_error),
max_position_error_m=error,legacy_rejected_by_axis_guard=legacy_rejected,
blender_world_bounds_min=lo.tolist(),blender_world_bounds_max=hi.tolist())
convention=json.loads((Path(__file__).resolve().parents[2]/'Config/Art/ModelConvention.json').read_text(encoding='utf-8'))
receipt_path=folder/'mesh_receipt.json'
receipt=json.loads(receipt_path.read_text(encoding='utf-8')) if receipt_path.is_file() else {}
front=receipt.get('fbxAxes',{}).get('anatomicalFront') if receipt.get('fbxSha256')==report['sha256'] else None
report.update(project_forward=convention['forwardAxis'],anatomical_front=front,
project_front_conforms=(front==convention['forwardAxis']) if front else None,
front_visual_check_required=front is None)
(out/'verification.json').write_text(json.dumps(report,indent=2),encoding='utf-8')
print('STATIC_VERIFICATION '+json.dumps(report))