import json,struct,io,os
from PIL import Image
b=open('stock-car.glb','rb').read();n=struct.unpack_from('<I',b,12)[0];d=json.loads(b[20:20+n]);binary=b[28+n:]
tri=sum(d['accessors'][p['indices']]['count']//3 for m in d['meshes'] for p in m['primitives'])
assert 60000<=tri<=110000,tri
assert len(b)<=12*1024**2,len(b)
assert len(d['materials'])<=8
required=['Body','Wheel_FL','Wheel_FR','Wheel_RL','Wheel_RR','SteeringWheel','Seat_Driver','Door_L','Door_R','Exhaust','Light_Head_L','Light_Head_R','Light_Tail_L','Light_Tail_R']
assert set(required)=={n['name'] for n in d['nodes']},[n['name'] for n in d['nodes']]
for node in d['nodes']:
 assert 'camera' not in node
 assert 'scale' not in node or node['scale']==[1,1,1]
 assert 'rotation' not in node or max(abs(x-y) for x,y in zip(node['rotation'],[0,0,0,1]))<1e-6
 if node['name'].startswith('Wheel_'):
  want=[.826 if node['name'][-1]=='L' else -.826,.355,1.395 if node['name'][-2]=='F' else -1.395]
  assert max(abs(x-y) for x,y in zip(node['translation'],want))<1e-5,(node,want)
images=[]
for im in d['images']:
 v=d['bufferViews'][im['bufferView']];o=v.get('byteOffset',0);img=Image.open(io.BytesIO(binary[o:o+v['byteLength']]));assert max(img.size)<=1024
 images.append({'name':im.get('name'), 'size':list(img.size),'bytes':v['byteLength']})
for m in d['materials']:
 for key in ['metallicRoughnessTexture']:
  t=m.get('pbrMetallicRoughness',{}).get(key)
  if t: assert max(images[d['textures'][t['index']]['source']]['size'])<=512
 if m['name']=='Paint':
  assert m['pbrMetallicRoughness']['baseColorFactor']==[.02,.1,.45,1]
  assert m['extensions']['KHR_materials_clearcoat']['clearcoatFactor']==1
 if m['name']=='Glass':assert m['alphaMode']=='BLEND'
meta=json.load(open('nodes.json'));a=meta['bounding_box']['min'];z=meta['bounding_box']['max'];dims=[z[i]-a[i] for i in range(3)]
assert max(abs(x-y) for x,y in zip(dims,[1.98,1.30,5.08]))<.001,dims
assert abs(a[1])<1e-6
# Verify bounds from actual exported POSITION accessors, not only metadata.
mins=[float('inf')]*3;maxs=[-float('inf')]*3
for node in d['nodes']:
 if 'mesh' not in node:continue
 translation=node.get('translation',[0,0,0])
 for primitive in d['meshes'][node['mesh']]['primitives']:
  accessor=d['accessors'][primitive['attributes']['POSITION']]
  for axis in range(3):
   mins[axis]=min(mins[axis],accessor['min'][axis]+translation[axis])
   maxs[axis]=max(maxs[axis],accessor['max'][axis]+translation[axis])
assert max(abs(mins[i]-a[i]) for i in range(3))<.00001,(mins,a)
assert max(abs(maxs[i]-z[i]) for i in range(3))<.00001,(maxs,z)
assert len(meta['paints'])==6
for name in ['final.png','side.png','clay.png']:assert Image.open(name).size==(1920,1080)
assert Image.open('paints.png').size==(1920,720)
assert len(open('NOTES.md').read().splitlines())==6
r={'triangles':tri,'glb_bytes':len(b),'materials':len(d['materials']),'nodes':len(d['nodes']),'dimensions_XYZ':dims,'images':images,'checks':'PASS'}
json.dump(r,open('validation.json','w'),indent=2);print(json.dumps({k:v for k,v in r.items() if k!='images'},indent=2))
