3d8825f5a9
REGISTRY rewritten around the central rule: a character folder is born only when a body ships to ariki-game (<character>_base_v<NN> = ship ordinal). lena_nude dissolves accordingly: - characters/female/lena_base_v01/ — SHIPPED 2026-08-10: AccuRig GLB carrier, T-pose/rig FBX + JSON, previews, frozen README - characters/work/lena/ — the live lane: recipes 01-47 (incl. new 36-47: refill/sheets/clay/despeckle/musculature/spin/AccuRig export/graft/pose QC), masters (athletic_v04 blend + textures, accurig blend), lane-history README - hires_claude/hires_work intermediates (blends, logs, probes) pruned Supporting docs: AGENTS.md, working-files rule, rig-graft plan addendum, originals README, prune_lane.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.9 KiB
Python
62 lines
2.9 KiB
Python
# 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")
|