"""Filmstrip a SKINNED Mixamo FBX, twice: as-authored, and as the game shows it
(Hips rotation cleared — player.js:_rotOnly).

    blender -b --factory-startup -P skinshot.py -- <skinned.fbx> <label>

Skinned export carries its own mesh at its own scale, so there's no cross-rig
retarget and no unit mismatch — the reason the Hum_M_1 route collapsed.

Camera is fitted to the MEASURED bbox of the animated body rather than guessed,
because guessing framing is how the last three renders came out flat grey.
"""
import bpy, sys, os, math
from mathutils import Vector

argv = sys.argv[sys.argv.index("--") + 1:]
SRC, LABEL = argv[0], argv[1]

bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.import_scene.fbx(filepath=SRC)

arm = next(o for o in bpy.data.objects if o.type == 'ARMATURE')
meshes = [o for o in bpy.data.objects if o.type == 'MESH']
act = arm.animation_data.action if arm.animation_data else None
hips = next((b for b in arm.pose.bones if b.name.lower().endswith("hips")), None)
f0, f1 = (int(act.frame_range[0]), int(act.frame_range[1])) if act else (1, 1)
print(f"  arm={arm.name} meshes={len(meshes)} act={act.name if act else None} frames={f0}..{f1} hips={hips.name if hips else None}")

sc = bpy.context.scene
N = 5
frames = [int(f0 + (f1 - f0) * i / (N - 1)) for i in range(N)] if f1 > f0 else [f0] * N


def world_pts(frame):
    sc.frame_set(frame)
    bpy.context.view_layer.update()
    dg = bpy.context.evaluated_depsgraph_get()
    pts = []
    for m in meshes:
        ev = m.evaluated_get(dg)
        pts += [ev.matrix_world @ Vector(c) for c in ev.bound_box]
    return pts


# --- measure the whole clip, then frame it once so all tiles share a scale.
allp = []
for fr in frames:
    allp += world_pts(fr)
minx, maxx = min(p.x for p in allp), max(p.x for p in allp)
miny, maxy = min(p.y for p in allp), max(p.y for p in allp)
minz, maxz = min(p.z for p in allp), max(p.z for p in allp)
cx, cy, cz = (minx + maxx) / 2, (miny + maxy) / 2, (minz + maxz) / 2
span = max(maxz - minz, maxy - miny, 0.1)
print(f"  clip bbox X[{minx:.2f},{maxx:.2f}] Y[{miny:.2f},{maxy:.2f}] Z[{minz:.2f},{maxz:.2f}] span={span:.2f}")

cam_d = bpy.data.cameras.new("cam"); cam_d.type = 'ORTHO'
cam_d.ortho_scale = span * 1.5
cam = bpy.data.objects.new("cam", cam_d); bpy.context.collection.objects.link(cam)
cam.rotation_mode = 'XYZ'
cam.location = (cx + span * 6, cy, cz)
cam.rotation_euler = (Vector((cx, cy, cz)) - cam.location).to_track_quat('-Z', 'Y').to_euler()
sc.camera = cam
print(f"  cam {tuple(round(v,2) for v in cam.location)} ortho={cam_d.ortho_scale:.2f}")

sc.render.engine = 'BLENDER_WORKBENCH'
sc.render.resolution_x, sc.render.resolution_y = 300, 460
sc.render.image_settings.file_format = 'PNG'
sc.display.shading.light = 'FLAT'
sc.display.shading.color_type = 'SINGLE'
sc.display.shading.single_color = (0.85, 0.32, 0.28)
if sc.world is None:
    sc.world = bpy.data.worlds.new("W")
sc.world.color = (0.42, 0.45, 0.48)

for mode in ("raw", "stripped"):
    for i, fr in enumerate(frames):
        sc.frame_set(fr)
        if mode == "stripped" and hips:
            hips.rotation_mode = 'QUATERNION'
            hips.rotation_quaternion = (1, 0, 0, 0)
        bpy.context.view_layer.update()
        sc.render.filepath = f"/tmp/shadeswind/tile_{LABEL}_{mode}_{i}.png"
        bpy.ops.render.render(write_still=True)

print("DONE " + LABEL)
