Vili/scripts/bvh-to-fbx.py
Meta Vens f7fba1c115 bvh-to-fbx.py: the header names no in-place bake, matching the negative grep
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:07:57 +02:00

52 lines
2.6 KiB
Python

# Converts one Kimodo standard-T-pose BVH to one FBX. Blender converts formats and nothing else: no retarget, no
# rename, no key reduction, no resample, no root-motion bake. Run by the daemon (scripts/vili-daemon.mjs exportClip):
# blender -b --factory-startup --python-use-system-env --python-exit-code 1 --python bvh-to-fbx.py -- <bvh> <fbx> <clipName>
# with PYTHONHASHSEED=0 in the environment. The settings are Blender API parameters, cross-checked against the
# 'unity' preset of wifimetas/kimodo-mcp blender_export.py (Apache-2.0, 83b5361); no code is copied.
import datetime
import json
import sys
import types
import bpy
# io_scene_fbx hashes strings for UUIDs (fbx_utils.py) and stamps the file with now() (export_fbx_bin.py): a fixed
# hash seed and a fixed time make one BVH on one host body give one set of FBX bytes.
if sys.flags.hash_randomization != 0:
sys.exit(3)
bvh, fbx, clip = sys.argv[sys.argv.index("--") + 1:]
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.unit_settings.system = "METRIC"
scene.unit_settings.scale_length = 1.0
import io_scene_fbx.export_fbx_bin as fbx_bin # after the factory reset, which the pin must outlive
if not hasattr(fbx_bin, "datetime"):
raise RuntimeError("io_scene_fbx.export_fbx_bin no longer stamps with datetime: the pin must move")
fixed_time = datetime.datetime(1970, 1, 1)
fbx_bin.datetime = types.SimpleNamespace(datetime=types.SimpleNamespace(now=lambda: fixed_time))
bpy.ops.import_anim.bvh(
filepath=bvh, global_scale=0.01, axis_forward="-Z", axis_up="Y", rotate_mode="NATIVE",
use_fps_scale=False, update_scene_fps=True, frame_start=1,
)
armature = bpy.context.view_layer.objects.active
action = armature.animation_data.action
# The FBX take takes the scene's name.
armature.name = armature.data.name = action.name = scene.name = clip
first, last = (int(frame) for frame in action.frame_range)
scene.frame_start, scene.frame_end = first, last
bpy.ops.object.select_all(action="DESELECT")
armature.select_set(True)
bpy.ops.export_scene.fbx(
filepath=fbx, use_selection=True, object_types={"ARMATURE"}, apply_unit_scale=True,
apply_scale_options="FBX_SCALE_ALL", axis_forward="-Z", axis_up="Y", add_leaf_bones=False,
primary_bone_axis="Y", secondary_bone_axis="X", armature_nodetype="NULL", bake_anim=True,
bake_anim_use_all_bones=True, bake_anim_use_nla_strips=False, bake_anim_use_all_actions=False,
bake_anim_force_startend_keying=True, bake_anim_step=1.0, bake_anim_simplify_factor=0.0,
)
print("VILI-FBX " + json.dumps({"bones": len(armature.data.bones), "frames": [first, last], "fps": scene.render.fps}))