Skip to content

Instantly share code, notes, and snippets.

@avramovic
Last active October 3, 2025 22:01
Show Gist options
  • Select an option

  • Save avramovic/2c50405b32dece773882693b0d251ad1 to your computer and use it in GitHub Desktop.

Select an option

Save avramovic/2c50405b32dece773882693b0d251ad1 to your computer and use it in GitHub Desktop.
Blender animation retargeting
import bpy
import sys
import os
# Parse arguments
argv = sys.argv
argv = argv[argv.index("--") + 1:]
src_file = os.path.abspath(argv[0])
tgt_file = os.path.abspath(argv[1])
out_file = os.path.abspath(argv[2])
print("Source:", src_file)
print("Target:", tgt_file)
print("Output:", out_file)
# Reset scene
bpy.ops.wm.read_factory_settings(use_empty=True)
# Import source
print("Importing source...")
bpy.ops.import_scene.gltf(filepath=src_file)
# Find source armature
arm_source = next((obj for obj in bpy.context.scene.objects if obj.type == "ARMATURE"), None)
if not arm_source:
raise Exception("No armature found in source file")
print("Source armature:", arm_source.name)
# Import target
print("Importing target...")
bpy.ops.import_scene.gltf(filepath=tgt_file)
# Find target armature
arm_target = next((obj for obj in bpy.context.scene.objects if obj.type == "ARMATURE" and obj != arm_source), None)
if not arm_target:
raise Exception("No armature found in target file")
print("Target armature:", arm_target.name)
# Bone mapping
bone_map = {
"DEF-hips": "Hips", "DEF-spine.001": "Spine", "DEF-spine.002": "Spine1", "DEF-spine.003": "Spine2",
"DEF-neck": "Neck", "DEF-head": "Head",
"DEF-shoulder.L": "LeftShoulder", "DEF-upper_arm.L": "LeftArm", "DEF-forearm.L": "LeftForeArm", "DEF-hand.L": "LeftHand",
"DEF-thumb.01.L": "LeftHandThumb1", "DEF-thumb.02.L": "LeftHandThumb2", "DEF-thumb.03.L": "LeftHandThumb3",
"DEF-f_index.01.L": "LeftHandIndex1", "DEF-f_index.02.L": "LeftHandIndex2", "DEF-f_index.03.L": "LeftHandIndex3",
"DEF-f_middle.01.L": "LeftHandMiddle1", "DEF-f_middle.02.L": "LeftHandMiddle2", "DEF-f_middle.03.L": "LeftHandMiddle3",
"DEF-f_ring.01.L": "LeftHandRing1", "DEF-f_ring.02.L": "LeftHandRing2", "DEF-f_ring.03.L": "LeftHandRing3",
"DEF-f_pinky.01.L": "LeftHandPinky1", "DEF-f_pinky.02.L": "LeftHandPinky2", "DEF-f_pinky.03.L": "LeftHandPinky3",
"DEF-shoulder.R": "RightShoulder", "DEF-upper_arm.R": "RightArm", "DEF-forearm.R": "RightForeArm", "DEF-hand.R": "RightHand",
"DEF-thumb.01.R": "RightHandThumb1", "DEF-thumb.02.R": "RightHandThumb2", "DEF-thumb.03.R": "RightHandThumb3",
"DEF-f_index.01.R": "RightHandIndex1", "DEF-f_index.02.R": "RightHandIndex2", "DEF-f_index.03.R": "RightHandIndex3",
"DEF-f_middle.01.R": "RightHandMiddle1", "DEF-f_middle.02.R": "RightHandMiddle2", "DEF-f_middle.03.R": "RightHandMiddle3",
"DEF-f_ring.01.R": "RightHandRing1", "DEF-f_ring.02.R": "RightHandRing2", "DEF-f_ring.03.R": "RightHandRing3",
"DEF-f_pinky.01.R": "RightHandPinky1", "DEF-f_pinky.02.R": "RightHandPinky2", "DEF-f_pinky.03.R": "RightHandPinky3",
"DEF-thigh.L": "LeftUpLeg", "DEF-shin.L": "LeftLeg", "DEF-foot.L": "LeftFoot", "DEF-toe.L": "LeftToeBase",
"DEF-thigh.R": "RightUpLeg", "DEF-shin.R": "RightLeg", "DEF-foot.R": "RightFoot", "DEF-toe.R": "RightToeBase"
}
print("Bone mapping loaded.")
# Rename original actions to [name]_old
for action in list(bpy.data.actions):
if not action.name.endswith("_old"):
action.name = f"{action.name}_old"
# Retarget animations
for action in bpy.data.actions:
if not action.name.endswith("_old"):
continue
print("Retargeting:", action.name)
new_action = action.copy()
new_action.name = action.name.replace("_old", "")
for fcurve in new_action.fcurves:
path = fcurve.data_path
for src_bone, tgt_bone in bone_map.items():
if f'pose.bones["{src_bone}"]' in path:
fcurve.data_path = path.replace(src_bone, tgt_bone)
if not arm_target.animation_data:
arm_target.animation_data_create()
arm_target.animation_data.action = new_action
# Remove old actions
for action in list(bpy.data.actions):
if action.name.endswith("_old"):
bpy.data.actions.remove(action)
# Rotate armature and children 180 degrees around Z axis
print("Rotating target armature and children...")
bpy.ops.object.select_all(action='DESELECT')
arm_target.select_set(True)
for child in arm_target.children:
child.select_set(True)
bpy.context.view_layer.objects.active = arm_target
for obj in [arm_target] + list(arm_target.children):
obj.rotation_euler[2] += 3.14159 # 180 degrees in radians
bpy.context.view_layer.objects.active = obj
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
# Delete source model
print("Deleting source...")
bpy.ops.object.select_all(action='DESELECT')
arm_source.select_set(True)
for child in arm_source.children:
child.select_set(True)
bpy.ops.object.delete()
# Export target
print("Exporting target with animations...")
bpy.ops.object.select_all(action='DESELECT')
arm_target.select_set(True)
for child in arm_target.children:
child.select_set(True)
bpy.context.view_layer.objects.active = arm_target
bpy.ops.export_scene.gltf(filepath=out_file, export_format='GLB', use_selection=True)
print("DONE. Exported to:", out_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment