"""Read donor contract and render a true rest-pose silhouette, never an idle frame."""
import bpy,sys,json,math
from pathlib import Path
from mathutils import Vector
src,out=map(Path,sys.argv[sys.argv.index('--')+1:]);out.mkdir(parents=True,exist_ok=True)
bpy.ops.wm.read_factory_settings(use_empty=True);bpy.context.scene.render.fps=30;bpy.ops.import_scene.gltf(filepath=str(src))
rigs=[o for o in bpy.context.scene.objects if o.type=='ARMATURE']
if len(rigs)!=1:raise ValueError('Expected exactly one skeleton')
rig=rigs[0];rig.data.pose_position='REST';bpy.context.view_layer.update()
meshes=[o for o in bpy.context.scene.objects if o.type=='MESH' and (any(m.type=='ARMATURE' for m in o.modifiers) or o.parent_type=='BONE')]
pts=[]
for o in meshes:
 ev=o.evaluated_get(bpy.context.evaluated_depsgraph_get());me=ev.to_mesh();pts.extend(ev.matrix_world@v.co for v in me.vertices);ev.to_mesh_clear()
lo=Vector([min(p[i] for p in pts) for i in range(3)]);hi=Vector([max(p[i] for p in pts) for i in range(3)]);center=(lo+hi)/2;size=hi-lo
clips={a.name:(a.frame_range[1]-a.frame_range[0])/30 for a in bpy.data.actions}
roles={}
for role,words in {'idle':['idle'],'walk':['walk'],'run':['gallop','run'],'attack':['attack'],'die':['death','die'],'hit':['hitreact','hit']}.items():
 matches=[n for n in clips if any(w in n.lower() for w in words) and (role!='idle' or 'hit' not in n.lower())]
 if not matches:raise ValueError('Missing clip role '+role)
 roles[role]=matches[0]
bones={b.name:{'head':list(rig.matrix_world@b.head_local),'tail':list(rig.matrix_world@b.tail_local),'parent':b.parent.name if b.parent else None} for b in rig.data.bones}
head=Vector(bones['Head']['head']);tail=Vector(next(v['head'] for k,v in bones.items() if k.lower().startswith('tail')));forward=head-tail;forward.z=0;forward.normalize()
# Input-facing contract is checked, not silently guessed.
if forward.y>-.5:raise ValueError('Only Y-up GLB facing +Z supported by this initial release')
(out/'input.json').write_text(json.dumps({'input':str(src),'height_m':size.z,'bounds':[list(lo),list(hi)],'facing_gltf':'+Z','clips':roles,'clip_durations':clips,'bones':bones},indent=2))
sc=bpy.context.scene;sc.render.engine='CYCLES';sc.cycles.samples=16;sc.render.resolution_x=1024;sc.render.resolution_y=1024;sc.render.resolution_percentage=100;sc.world=bpy.data.worlds.new('White');sc.world.use_nodes=True;sc.world.node_tree.nodes['Background'].inputs[0].default_value=(1,1,1,1);sc.world.node_tree.nodes['Background'].inputs[1].default_value=.8
bpy.ops.object.light_add(type='AREA',location=center+Vector((3,-4,5))*size.z);bpy.context.object.data.energy=1000;bpy.context.object.data.shape='DISK';bpy.context.object.data.size=size.z*4
bpy.ops.object.camera_add(location=center+Vector((5,0,.02))*size.z);cam=bpy.context.object;cam.rotation_euler=(center-cam.location).to_track_quat('-Z','Y').to_euler();cam.data.type='ORTHO';cam.data.ortho_scale=max(size.y,size.z)*1.15;sc.camera=cam;sc.render.image_settings.file_format='PNG';sc.render.filepath=str(out/'neutral.png');bpy.ops.render.render(write_still=True)
