This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/release/scripts/modules/retarget.py

274 lines
10 KiB
Python

import bpy
from mathutils import *
from math import radians, acos
performer_obj = bpy.data.objects["performer"]
enduser_obj = bpy.data.objects["enduser"]
end_arm = bpy.data.armatures["enduser_arm"]
scene = bpy.context.scene
#TODO: Only selected bones get retargeted.
# Selected Bones/chains get original pos empties, if ppl want IK instead of FK
# Some "magic" numbers - frame start and end, eulers of all orders instead of just quats keyframed
# dictionary of mapping
# this is currently manuall input'ed, but will
# be created from a more comfortable UI in the future
bonemap = { "Head": "Head",
"Neck": "Head",
"Spine1": "Chest",
"Spine2": "Chest",
"Spine3": "Chest",
"Spine": "Torso",
"Hips": "root",
"LeftUpLeg": "Thigh.L",
"LeftUpLegRoll": "Thigh.L",
"LeftLeg": "Shin.L",
"LeftLegRoll": "Shin.L",
"LeftFoot": "Foot.L",
"RightUpLeg": "Thigh.R",
"RightUpLegRoll": "Thigh.R",
"RightLeg": "Shin.R",
"RightLegRoll": "Shin.R",
"RightFoot": "Foot.R",
"LeftShoulder": "Shoulder.L",
"LeftArm": "HiArm.L",
"LeftArmRoll": "HiArm.L",
"LeftForeArm": "LoArm.L",
"LeftForeArmRoll": "LoArm.L",
"RightShoulder": "Shoulder.R",
"RightArm": "HiArm.R",
"RightArmRoll": "HiArm.R",
"RightForeArm": "LoArm.R",
"RightForeArmRoll": "LoArm.R" }
root = "root"
# creation of a reverse map
# multiple keys get mapped to list values
bonemapr = {}
for key in bonemap.keys():
if not bonemap[key] in bonemapr:
if type(bonemap[key])==type((0,0)):
for key_x in bonemap[key]:
bonemapr[key_x] = [key]
else:
bonemapr[bonemap[key]] = [key]
else:
bonemapr[bonemap[key]].append(key)
# list of empties created to keep track of "original"
# position data
# in final product, these locations can be stored as custom props
# these help with constraining, etc.
constraints = []
#creation of intermediate armature
# the intermediate armature has the hiearchy of the end user,
# does not have rotation inheritence
# and bone roll is identical to the performer
# its purpose is to copy over the rotations
# easily while concentrating on the hierarchy changes
def createIntermediate():
#creates and keyframes an empty with its location
#the original position of the tail bone
#useful for storing the important data in the original motion
#i.e. using this empty to IK the chain to that pos.
def locOfOriginal(inter_bone,perf_bone):
if not perf_bone.name+"Org" in bpy.data.objects:
bpy.ops.object.add()
empty = bpy.context.active_object
empty.name = perf_bone.name+"Org"
empty = bpy.data.objects[perf_bone.name+"Org"]
offset = perf_bone.vector
if inter_bone.length == 0 or perf_bone.length == 0:
scaling = 1
else:
scaling = perf_bone.length / inter_bone.length
offset/=scaling
empty.location = inter_bone.head + offset
empty.keyframe_insert("location")
#Simple 1to1 retarget of a bone
def singleBoneRetarget(inter_bone,perf_bone):
perf_world_rotation = perf_bone.matrix * performer_obj.matrix_world
inter_world_base_rotation = inter_bone.bone.matrix_local * inter_obj.matrix_world
inter_world_base_inv = Matrix(inter_world_base_rotation)
inter_world_base_inv.invert()
return (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3()).to_4x4()
#uses 1to1 and interpolation/averaging to match many to 1 retarget
def manyPerfToSingleInterRetarget(inter_bone,performer_bones_s):
retarget_matrices = [singleBoneRetarget(inter_bone,perf_bone) for perf_bone in performer_bones_s]
lerp_matrix = Matrix()
for i in range(len(retarget_matrices)-1):
first_mat = retarget_matrices[i]
next_mat = retarget_matrices[i+1]
lerp_matrix = first_mat.lerp(next_mat,0.5)
return lerp_matrix
#determines the type of hierachy change needed and calls the
#right function
def retargetPerfToInter(inter_bone):
if inter_bone.name in bonemapr.keys():
perf_bone_name = bonemapr[inter_bone.name]
#is it a 1 to many?
if type(bonemap[perf_bone_name[0]])==type((0,0)):
perf_bone = performer_bones[perf_bone_name[0]]
if inter_bone.name == bonemap[perf_bone_name[0]][0]:
locOfOriginal(inter_bone,perf_bone)
else:
# then its either a many to 1 or 1 to 1
if len(perf_bone_name) > 1:
performer_bones_s = [performer_bones[name] for name in perf_bone_name]
#we need to map several performance bone to a single
for perf_bone in performer_bones_s:
locOfOriginal(inter_bone,perf_bone)
inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone,performer_bones_s)
else:
perf_bone = performer_bones[perf_bone_name[0]]
locOfOriginal(inter_bone,perf_bone)
inter_bone.matrix_basis = singleBoneRetarget(inter_bone,perf_bone)
inter_bone.keyframe_insert("rotation_quaternion")
for child in inter_bone.children:
retargetPerfToInter(child)
#creates the intermediate armature object
bpy.ops.object.select_name(name="enduser",extend=False)
bpy.ops.object.duplicate(linked=False)
bpy.context.active_object.name = "intermediate"
inter_obj = bpy.context.active_object
bpy.ops.object.mode_set(mode='EDIT')
#resets roll
bpy.ops.armature.calculate_roll(type='Z')
bpy.ops.object.mode_set(mode="OBJECT")
inter_arm = bpy.data.armatures["enduser_arm.001"]
inter_arm.name = "inter_arm"
performer_bones = performer_obj.pose.bones
inter_bones = inter_obj.pose.bones
#clears inheritance
for inter_bone in inter_bones:
inter_bone.bone.use_inherit_rotation = False
for t in range(1,150):
scene.frame_set(t)
inter_bone = inter_bones[root]
retargetPerfToInter(inter_bone)
return inter_obj,inter_arm
# this procedure copies the rotations over from the intermediate
# armature to the end user one.
# As the hierarchies are 1 to 1, this is a simple matter of
# copying the rotation, while keeping in mind bone roll, parenting, etc.
# TODO: Control Bones: If a certain bone is constrained in a way
# that its rotation is determined by another (a control bone)
# We should determine the right pos of the control bone.
# Scale: ? Should work but needs testing.
def retargetEnduser():
inter_bones = inter_obj.pose.bones
end_bones = enduser_obj.pose.bones
def bakeTransform(end_bone):
src_bone = inter_bones[end_bone.name]
trg_bone = end_bone
bake_matrix = src_bone.matrix
rest_matrix = trg_bone.bone.matrix_local
if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
parent_mat = src_bone.parent.matrix
parent_rest = trg_bone.parent.bone.matrix_local
parent_rest_inv = parent_rest.copy()
parent_rest_inv.invert()
parent_mat_inv = parent_mat.copy()
parent_mat_inv.invert()
bake_matrix = parent_mat_inv * bake_matrix
rest_matrix = parent_rest_inv * rest_matrix
rest_matrix_inv = rest_matrix.copy()
rest_matrix_inv.invert()
bake_matrix = rest_matrix_inv * bake_matrix
trg_bone.matrix_basis = bake_matrix
end_bone.keyframe_insert("rotation_quaternion")
for bone in end_bone.children:
bakeTransform(bone)
for t in range(1,150):
scene.frame_set(t)
end_bone = end_bones[root]
bakeTransform(end_bone)
#recieves the performer feet bones as a variable
# by "feet" I mean those bones that have plants
# (they don't move, despite root moving) somewhere in the animation.
def copyTranslation(perfFeet):
endFeet = [bonemap[perfBone] for perfBone in perfFeet]
perfRoot = bonemapr[root][0]
locDictKeys = perfFeet+endFeet+[perfRoot]
perf_bones = performer_obj.pose.bones
end_bones = enduser_obj.pose.bones
def tailLoc(bone):
return bone.center+(bone.vector/2)
#Step 1 - we create a dict that contains these keys:
#(Performer) Hips, Feet
#(End user) Feet
# where the values are their world position on each (1,120) frame
locDict = {}
for key in locDictKeys:
locDict[key] = []
for t in range(scene.frame_start,scene.frame_end):
scene.frame_set(t)
for bone in perfFeet:
locDict[bone].append(tailLoc(perf_bones[bone]))
locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
for bone in endFeet:
locDict[bone].append(tailLoc(end_bones[bone]))
# now we take our locDict and analyze it.
# we need to derive all chains
locDeriv = {}
for key in locDictKeys:
locDeriv[key] = []
for key in locDict.keys():
graph = locDict[key]
for t in range(len(graph)-1):
x = graph[t]
xh = graph[t+1]
locDeriv[key].append(xh-x)
# now find the plant frames, where perfFeet don't move much
linearAvg = []
for key in perfFeet:
for i in range(len(locDeriv[key])-1):
v = locDeriv[key][i]
hipV = locDeriv[perfRoot][i]
endV = locDeriv[bonemap[key]][i]
if (v.length<0.1):
#this is a plant frame.
#lets see what the original hip delta is, and the corresponding
#end bone's delta
if endV.length!=0:
linearAvg.append(hipV.length/endV.length)
if linearAvg:
avg = sum(linearAvg)/len(linearAvg)
print("retargeted root motion should be "+ str(1/avg)+ " of original")
inter_obj, inter_arm = createIntermediate()
retargetEnduser()
copyTranslation(["RightFoot","LeftFoot"])