Last active
October 3, 2025 22:01
-
-
Save avramovic/2c50405b32dece773882693b0d251ad1 to your computer and use it in GitHub Desktop.
Blender animation retargeting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import bpy, sys, os | |
| 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 = None | |
| for obj in bpy.context.scene.objects: | |
| if obj.type == "ARMATURE": | |
| arm_source = obj | |
| break | |
| if not arm_source: | |
| raise Exception("No armature in source file") | |
| print("Source armature:", arm_source.name) | |
| # Import target (mesh for retarget) | |
| print("Importing target...") | |
| bpy.ops.import_scene.gltf(filepath=tgt_file) | |
| arm_target = None | |
| for obj in bpy.context.scene.objects: | |
| if obj.type == "ARMATURE" and obj != arm_source: | |
| arm_target = obj | |
| break | |
| if not arm_target: | |
| raise Exception("No armature in target file") | |
| print("Target armature:", arm_target.name) | |
| # Bone map | |
| bone_map = { | |
| # Root / Spine | |
| "DEF-hips": "Hips", | |
| "DEF-spine.001": "Spine", | |
| "DEF-spine.002": "Spine1", | |
| "DEF-spine.003": "Spine2", | |
| "DEF-neck": "Neck", | |
| "DEF-head": "Head", | |
| # Left arm | |
| "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", | |
| # Right arm | |
| "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", | |
| # Legs | |
| "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:", bone_map) | |
| # Copy all animations | |
| for action in bpy.data.actions: | |
| print("Copying action:", action.name) | |
| new_action = action.copy() | |
| new_action.name = f"{action.name}_retarget" | |
| for fcurve in new_action.fcurves: | |
| path = fcurve.data_path | |
| for src_bone, tgt_bone in bone_map.items(): | |
| if 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 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() | |
| # Exportuj only 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