Files
animation/characters/work/lena/45_accurig_export.py
T

62 lines
2.9 KiB
Python
Raw Normal View History

# Stage 45: export v10 as an AccuRig-loadable FBX.
#
# blender --background --python 45_accurig_export.py -- <in.blend> <out.fbx> [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")