Last active
January 5, 2021 02:05
-
-
Save xenogenesi/3e5cdb29f2071109631206ec610291fc to your computer and use it in GitHub Desktop.
Blender script to re-target motion capture
This file contains 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
bl_info = { | |
"name": "Rig Empty Retarget", | |
"blender": (2, 91, 0), | |
"author": "Alex <raziel@eml.cc>", | |
"version": (0, 1), | |
"category": "Rigging", | |
"location": "Operator Search", | |
"description": "Helpers to re-target animations", | |
"warning": "", | |
"doc_url": "", | |
"tracker_url": "", | |
} | |
# select source armature and go into pose mode, select the bone(s) and invoke "Add Bone Empty" from the search menu | |
# edit the bmap below according to your armatures bones name: "target": "source" | |
# select target armature/rig and go into pose mode, select the bones and invoke "Add Bone Copy Rotation" from the search menu | |
# (the names in the bmap will be used as target) | |
# Credits to: | |
# https://youtu.be/LU0u_b88K8k | |
# https://youtu.be/Sc_0PfNnJsw | |
# DONE | |
# 1. place empty(ies) for each selected bone (pose mode) in the source armature | |
# 2. map and add copy-rotation constrain for each bone in the target armature (rigify) | |
# TODO | |
# 3. calculate empty(ies) rotations to re-align target armature in rest-position | |
import bpy | |
from mathutils import Matrix | |
# FIXME the map should be some external file yaml/json/... | |
bmap = { | |
"head": "head", | |
"neck": "neck", | |
"chest": "chest", | |
# "breast.L": ? | |
# "breast.R": ? | |
"torso": "abdomen", # ? | |
"hips": "hip", | |
## LEFT | |
"thigh_fk.L": "lThigh", | |
"shin_fk.L": "lShin", | |
"foot_fk.L": "lFoot", | |
# "toe.L": ? | |
"shoulder.L": "lCollar", | |
"upper_arm_fk.L": "lShldr", | |
"forearm_fk.L": "lForeArm", | |
"hand_fk.L": "lHand", | |
## RIGHT | |
"thigh_fk.R": "rThigh", | |
"shin_fk.R": "rShin", | |
"foot_fk.R": "rFoot", | |
# "toe.R": ? | |
"shoulder.R": "rCollar", | |
"upper_arm_fk.R": "rShldr", | |
"forearm_fk.R": "rForeArm", | |
"hand_fk.R": "rHand" | |
} | |
def bone_add_empty(context): | |
# for ob in context.scene.objects: | |
# print(ob) | |
sel_bones = [] | |
if bpy.context.selected_pose_bones != None: | |
for bone in bpy.context.selected_pose_bones: | |
sel_bones.append(bone) | |
for bone in sel_bones: | |
target = "Empty_" + bone.name | |
armature = bpy.context.active_object | |
if target in bpy.data.objects: | |
if bpy.data.objects[target].parent == armature and bpy.data.objects[target].parent_bone == bone.name: | |
continue | |
empty = bpy.data.objects.new(target, None) | |
empty.empty_display_size = 5 | |
empty.empty_display_type = 'SPHERE' | |
bpy.context.collection.objects.link(empty) | |
empty.parent = armature | |
empty.parent_bone = bone.name | |
empty.parent_type = 'BONE' | |
mpi = empty.matrix_parent_inverse | |
ti = Matrix.Translation([0, bone.length, 0]).inverted() | |
empty.matrix_parent_inverse = mpi @ ti | |
bpy.context.view_layer.update() | |
def bone_add_copy_rotation(context): | |
if bpy.context.selected_pose_bones != None: | |
for bone in bpy.context.selected_pose_bones: | |
if bone.name in bmap: | |
print(f"{bone.name} -> {bmap[bone.name]}") | |
#print(bone.constraints) | |
target = "Empty_" + bmap[bone.name] | |
if not target in bpy.data.objects: | |
print(f"Warning: target '{target}' doesn't exists, ignored") | |
continue | |
# check each constraint, if exists without target, set it (FIXME need to check subtarget?) | |
# if exists and have the same target, skip it | |
# if doesn't exists, create it, set the target and go on | |
found = False | |
for constraint in bone.constraints: | |
if constraint.type == 'COPY_ROTATION': | |
if constraint.target == None: | |
copy_rot_con = constraint | |
copy_rot_con.target = bpy.data.objects[target] | |
found = True | |
break | |
elif constraint.target == bpy.data.objects[target]: | |
copy_rot_con = constraint | |
found = True | |
break | |
if not found: | |
copy_rot_con = bone.constraints.new('COPY_ROTATION') | |
copy_rot_con.target = bpy.data.objects[target] | |
class BoneAddEmpty(bpy.types.Operator): | |
"""Add an empty object to selected pose bones""" | |
bl_idname = "pose.bone_add_empty" | |
bl_label = "Bone Add Empty" | |
@classmethod | |
def poll(cls, context): | |
return context.active_object is not None and context.mode == 'POSE' | |
def execute(self, context): | |
bone_add_empty(context) | |
return {'FINISHED'} | |
class BoneAddCopyRotation(bpy.types.Operator): | |
"""Add Copy-Rotation Constraint to selected pose bones""" | |
bl_idname = "pse.bone_add_copy_rotation" | |
bl_label = "Bone Add Copy Rotation" | |
@classmethod | |
def poll(cls, context): | |
return context.active_object is not None and context.mode == 'POSE' | |
def execute(self, context): | |
bone_add_copy_rotation(context) | |
return {'FINISHED'} | |
def register(): | |
bpy.utils.register_class(BoneAddEmpty) | |
bpy.utils.register_class(BoneAddCopyRotation) | |
def unregister(): | |
bpy.utils.unregister_class(BoneAddEmpty) | |
bpy.utils.unregister_class(BoneAddCopyRotation) | |
if __name__ == "__main__": | |
register() | |
# test call | |
#bpy.ops.object.simple_operator() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment