# Stage 45: export v10 as an AccuRig-loadable FBX. # # blender --background --python 45_accurig_export.py -- [target_height_m] # # NO RIG BAIT. `tools/rigbait_decimate.py` exists because the source GLBs are 1.9 M tris and # AccuRig refuses them outright. v10 is 62,929 tris — under the lane's own 78k bait budget — so it # goes in whole. That also deletes the decimated->full-res weight transfer, which is exactly where # the July lane's finger mangling happened. # # SCALE. Per `.agents/plans/rig-graft-lane-2026-08-04.md` the GLB never changes size; only the # disposable FBX does. AccuRig misplaces joints on a ~0.98-unit body, so this exports at a # human-scale height and prints the factor to undo on the way back. The male lane settled on # 1.700 m and on scaling to HEIGHT, not to the pelvis. # # NO MATERIALS. The lane's hard rule is that visual data is never sourced from FBX — the male FBX # came back with mislabeled and duplicated textures. AccuRig only needs the surface, so nothing # visual is sent and nothing visual comes back; the GLB keeps its own atlas. import bpy, sys, os import numpy as np argv = sys.argv[sys.argv.index("--") + 1:] BLEND, OUT = argv[0], os.path.abspath(argv[1]) TARGET_M = float(argv[2]) if len(argv) > 2 else 1.700 bpy.ops.wm.open_mainfile(filepath=BLEND) ob = max([o for o in bpy.data.objects if o.type == 'MESH'], key=lambda o: len(o.data.vertices)) for o in list(bpy.data.objects): if o is not ob: bpy.data.objects.remove(o, do_unlink=True) bpy.context.view_layer.objects.active = ob ob.select_set(True) me = ob.data n = len(me.vertices) co = np.empty(n * 3); me.vertices.foreach_get("co", co); co = co.reshape(-1, 3) h0 = co[:, 2].max() - co[:, 2].min() k = TARGET_M / h0 ob.scale = (k, k, k) bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) co2 = np.empty(n * 3); me.vertices.foreach_get("co", co2); co2 = co2.reshape(-1, 3) # re-ground the feet: scaling about the origin leaves a residual if min-Z was not exactly 0 dz = co2[:, 2].min() if abs(dz) > 1e-9: co2[:, 2] -= dz me.vertices.foreach_set("co", co2.reshape(-1)) me.update() me.materials.clear() print(f"EXPORT {n} v / {len(me.polygons)} tris") print(f" height {h0:.5f} -> {co2[:,2].max()-co2[:,2].min():.5f} scale factor k = {k:.6f}") print(f" UNDO IN THE GRAFT: scale the returned skeleton by 1/k = {1.0/k:.6f}") print(f" feet min-Z {co2[:,2].min():+.8f} x centre {co2[:,0].mean():+.6f}") bpy.ops.export_scene.fbx(filepath=OUT, use_selection=True, object_types={'MESH'}, mesh_smooth_type='FACE', add_leaf_bones=False, path_mode='STRIP', embed_textures=False, apply_scale_options='FBX_SCALE_NONE', global_scale=1.0, bake_space_transform=False, use_mesh_modifiers=False) print(f"WROTE {OUT} ({os.path.getsize(OUT)/1e6:.2f} MB)") print("ACCURIG_EXPORT_DONE")