1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 from mathutils import *
23 from math import radians, acos
24 from bl_operators import nla
28 def hasIKConstraint(pose_bone):
29 #utility function / predicate, returns True if given bone has IK constraint
30 ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
37 def createDictionary(perf_arm, end_arm):
39 for end_bone in end_arm.bones:
40 for mapping in end_bone.reverseMap:
41 end_bone.reverseMap.remove(0)
43 for perf_bone in perf_arm.bones:
44 #find its match and add perf_bone to the match's mapping
46 end_bone = end_arm.bones[perf_bone.map]
47 newMap = end_bone.reverseMap.add()
48 newMap.name = perf_bone.name
49 end_bone.foot = perf_bone.foot
51 #root is the root of the enduser
52 root = end_arm.bones[0].name
53 feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
54 return feetBones, root
56 def loadMapping(perf_arm, end_arm):
58 for end_bone in end_arm.bones:
59 #find its match and add perf_bone to the match's mapping
60 if end_bone.reverseMap:
61 for perf_bone in end_bone.reverseMap:
62 perf_arm.bones[perf_bone.name].map = end_bone.name
64 #creation of intermediate armature
65 # the intermediate armature has the hiearchy of the end user,
66 # does not have rotation inheritence
67 # and bone roll is identical to the performer
68 # its purpose is to copy over the rotations
69 # easily while concentrating on the hierarchy changes
72 def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene):
73 #creates and keyframes an empty with its location
74 #the original position of the tail bone
75 #useful for storing the important data in the original motion
76 #i.e. using this empty to IK the chain to that pos / DEBUG
78 #Simple 1to1 retarget of a bone
79 def singleBoneRetarget(inter_bone, perf_bone):
80 perf_world_rotation = perf_bone.matrix * performer_obj.matrix_world
81 inter_world_base_rotation = inter_bone.bone.matrix_local * inter_obj.matrix_world
82 inter_world_base_inv = inter_world_base_rotation.inverted()
83 return (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3()).to_4x4()
85 #uses 1to1 and interpolation/averaging to match many to 1 retarget
86 def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
87 retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
88 lerp_matrix = Matrix()
89 for i in range(len(retarget_matrices) - 1):
90 first_mat = retarget_matrices[i]
91 next_mat = retarget_matrices[i + 1]
92 lerp_matrix = first_mat.lerp(next_mat, 0.5)
95 #determines the type of hierachy change needed and calls the
97 def retargetPerfToInter(inter_bone):
98 if inter_bone.bone.reverseMap:
99 perf_bone_name = inter_bone.bone.reverseMap
100 # 1 to many not supported yet
101 # then its either a many to 1 or 1 to 1
102 if len(perf_bone_name) > 1:
103 performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
104 #we need to map several performance bone to a single
105 inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
107 perf_bone = performer_bones[perf_bone_name[0].name]
108 inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
109 inter_bone.keyframe_insert("rotation_quaternion")
111 #creates the intermediate armature object
112 inter_obj = enduser_obj.copy()
113 inter_obj.data = inter_obj.data.copy() # duplicate data
114 bpy.context.scene.objects.link(inter_obj)
115 inter_obj.name = "intermediate"
116 bpy.context.scene.objects.active = inter_obj
117 bpy.ops.object.mode_set(mode='EDIT')
118 #add some temporary connecting bones in case end user bones are not connected to their parents
119 print("creating temp bones")
120 for bone in inter_obj.data.edit_bones:
121 if not bone.use_connect and bone.parent:
122 if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
123 newBone = inter_obj.data.edit_bones.new("Temp")
124 newBone.head = bone.parent.tail
125 newBone.tail = bone.head
126 newBone.parent = bone.parent
127 bone.parent = newBone
128 bone.use_connect = True
129 newBone.use_connect = True
131 print("retargeting to intermediate")
132 bpy.ops.armature.calculate_roll(type='Z')
133 bpy.ops.object.mode_set(mode="OBJECT")
134 inter_obj.data.name = "inter_arm"
135 inter_arm = inter_obj.data
136 performer_bones = performer_obj.pose.bones
137 inter_bones = inter_obj.pose.bones
139 for inter_bone in inter_bones:
140 if inter_bone.bone.reverseMap:
141 inter_bone.bone.use_inherit_rotation = False
143 inter_bone.bone.use_inherit_rotation = True
145 for t in range(s_frame, e_frame):
146 if (t - s_frame) % 10 == 0:
147 print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
149 for bone in inter_bones:
150 retargetPerfToInter(bone)
154 # this procedure copies the rotations over from the intermediate
155 # armature to the end user one.
156 # As the hierarchies are 1 to 1, this is a simple matter of
157 # copying the rotation, while keeping in mind bone roll, parenting, etc.
158 # TODO: Control Bones: If a certain bone is constrained in a way
159 # that its rotation is determined by another (a control bone)
160 # We should determine the right pos of the control bone.
161 # Scale: ? Should work but needs testing.
164 def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene):
165 inter_bones = inter_obj.pose.bones
166 end_bones = enduser_obj.pose.bones
168 def bakeTransform(end_bone):
169 src_bone = inter_bones[end_bone.name]
171 bake_matrix = src_bone.matrix
172 rest_matrix = trg_bone.bone.matrix_local
174 if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
175 srcParent = src_bone.parent
176 if "Temp" in srcParent.name:
177 srcParent = srcParent.parent
178 parent_mat = srcParent.matrix
179 parent_rest = trg_bone.parent.bone.matrix_local
180 parent_rest_inv = parent_rest.inverted()
181 parent_mat_inv = parent_mat.inverted()
182 bake_matrix = parent_mat_inv * bake_matrix
183 rest_matrix = parent_rest_inv * rest_matrix
185 rest_matrix_inv = rest_matrix.inverted()
186 bake_matrix = rest_matrix_inv * bake_matrix
187 end_bone.matrix_basis = bake_matrix
188 rot_mode = end_bone.rotation_mode
189 if rot_mode == "QUATERNION":
190 end_bone.keyframe_insert("rotation_quaternion")
191 elif rot_mode == "AXIS_ANGLE":
192 end_bone.keyframe_insert("rotation_axis_angle")
194 end_bone.keyframe_insert("rotation_euler")
195 if not end_bone.bone.use_connect:
196 end_bone.keyframe_insert("location")
198 for bone in end_bone.children:
201 for t in range(s_frame, e_frame):
202 if (t - s_frame) % 10 == 0:
203 print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
205 end_bone = end_bones[root]
206 end_bone.location = Vector((0, 0, 0))
207 end_bone.keyframe_insert("location")
208 bakeTransform(end_bone)
210 #recieves the performer feet bones as a variable
211 # by "feet" I mean those bones that have plants
212 # (they don't move, despite root moving) somewhere in the animation.
215 def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
217 perf_bones = performer_obj.pose.bones
218 end_bones = enduser_obj.pose.bones
220 perfRoot = perf_bones[0].name
221 endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
222 locDictKeys = perfFeet + endFeet + [perfRoot]
225 return bone.center + (bone.vector / 2)
227 #Step 1 - we create a dict that contains these keys:
228 #(Performer) Hips, Feet
230 # where the values are their world position on each frame in range (s,e)
233 for key in locDictKeys:
236 for t in range(scene.frame_start, scene.frame_end):
238 for bone in perfFeet:
239 locDict[bone].append(tailLoc(perf_bones[bone]))
240 locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
242 locDict[bone].append(tailLoc(end_bones[bone]))
244 # now we take our locDict and analyze it.
245 # we need to derive all chains
247 def locDeriv(key, t):
249 return graph[t + 1] - graph[t]
252 #~ for key in locDictKeys:
253 #~ locDeriv[key] = []
255 #~ for key in locDict.keys():
256 #~ graph = locDict[key]
257 #~ locDeriv[key] = [graph[t + 1] - graph[t] for t in range(len(graph) - 1)]
259 # now find the plant frames, where perfFeet don't move much
264 for i in range(len(locDict[key]) - 1):
267 hipV = locDeriv(perfRoot,i)
268 endV = locDeriv(perf_bones[key].bone.map,i)
269 #this is a plant frame.
270 #lets see what the original hip delta is, and the corresponding
273 linearAvg.append(hipV.length / endV.length)
275 action_name = performer_obj.animation_data.action.name
276 #is there a stride_bone?
277 if "stride_bone" in bpy.data.objects:
278 stride_action = bpy.data.actions.new("Stride Bone " + action_name)
279 stride_action.use_fake_user = True
280 #~ stride_bone = enduser_obj.parent
281 stride_bone.animation_data.action = stride_action
284 stride_bone = bpy.context.active_object
285 stride_bone.name = "stride_bone"
287 stride_bone.location = enduser_obj_mat.to_translation()
290 #determine the average change in scale needed
291 avg = sum(linearAvg) / len(linearAvg)
292 scene.frame_set(s_frame)
293 initialPos = (tailLoc(perf_bones[perfRoot]) / avg) #+ stride_bone.location
294 for t in range(s_frame, e_frame):
296 #calculate the new position, by dividing by the found ratio between performer and enduser
297 newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
298 stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
299 stride_bone.keyframe_insert("location")
302 stride_bone.keyframe_insert("location")
303 stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
308 def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
309 bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
310 end_bones = enduser_obj.pose.bones
311 for pose_bone in end_bones:
312 ik_constraint = hasIKConstraint(pose_bone)
314 target_is_bone = False
315 # set constraint target to corresponding empty if targetless,
316 # if not, keyframe current target to corresponding empty
317 perf_bone = pose_bone.bone.reverseMap[-1].name
318 bpy.ops.object.mode_set(mode='EDIT')
319 orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
320 bpy.ops.object.mode_set(mode='OBJECT')
321 if not ik_constraint.target:
322 ik_constraint.target = enduser_obj
323 ik_constraint.subtarget = pose_bone.name+"IK"
326 # There is a target now
327 if ik_constraint.subtarget:
328 target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
329 target.bone.use_local_location = False
330 target_is_bone = True
332 target = ik_constraint.target
334 # bake the correct locations for the ik target bones
335 for t in range(s_frame, e_frame):
338 final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
340 final_loc = pose_bone.tail
341 target.location = final_loc
342 target.keyframe_insert("location")
343 ik_constraint.mute = False
344 scene.frame_set(s_frame)
345 bpy.ops.object.mode_set(mode='OBJECT')
348 def turnOffIK(enduser_obj):
349 end_bones = enduser_obj.pose.bones
350 for pose_bone in end_bones:
351 if pose_bone.is_in_ik_chain:
354 # set stiffness according to place on chain
355 # and values from analysis that is stored in the bone
356 #pose_bone.ik_stiffness_x = 0.5
357 #pose_bone.ik_stiffness_y = 0.5
358 #pose_bone.ik_stiffness_z = 0.5
359 ik_constraint = hasIKConstraint(pose_bone)
361 ik_constraint.mute = True
364 #copy the object matrixes and clear them (to be reinserted later)
365 def cleanAndStoreObjMat(performer_obj, enduser_obj):
366 perf_obj_mat = performer_obj.matrix_world.copy()
367 enduser_obj_mat = enduser_obj.matrix_world.copy()
369 performer_obj.matrix_world = zero_mat
370 enduser_obj.matrix_world = zero_mat
371 return perf_obj_mat, enduser_obj_mat
374 #restore the object matrixes after parenting the auto generated IK empties
375 def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
376 pose_bones = enduser_obj.pose.bones
377 for pose_bone in pose_bones:
378 if pose_bone.name + "Org" in bpy.data.objects:
379 empty = bpy.data.objects[pose_bone.name + "Org"]
380 empty.parent = stride_bone
381 performer_obj.matrix_world = perf_obj_mat
382 enduser_obj.parent = stride_bone
383 scene.frame_set(s_frame)
384 enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() * Matrix.Translation(stride_bone.matrix_world.to_translation())
385 enduser_obj.matrix_world = enduser_obj_mat
388 #create (or return if exists) the related IK empty to the bone
389 def originalLocationTarget(end_bone, enduser_obj):
390 if not end_bone.name + "IK" in enduser_obj.data.bones:
391 newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
392 newBone.head = end_bone.tail
393 newBone.tail = end_bone.tail + Vector((0,0.1,0))
394 #~ empty = bpy.context.active_object
395 #~ empty.name = end_bone.name + "Org"
396 #~ empty.empty_draw_size = 0.1
397 #~ empty.parent = enduser_obj
399 newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
403 #create the specified NLA setup for base animation, constraints and tweak layer.
404 def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
405 enduser_obj = context.active_object
406 NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
407 name = NLATracks.name
408 anim_data = enduser_obj.animation_data
411 if ("Base " + name) in bpy.data.actions:
412 mocapAction = bpy.data.actions[("Base " + name)]
414 print("That retargeted anim has no base action")
415 anim_data.use_nla = True
416 for track in anim_data.nla_tracks:
417 anim_data.nla_tracks.remove(track)
418 mocapTrack = anim_data.nla_tracks.new()
419 mocapTrack.name = "Base " + name
420 NLATracks.base_track = mocapTrack.name
421 mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
422 constraintTrack = anim_data.nla_tracks.new()
423 constraintTrack.name = "Auto fixes " + name
424 NLATracks.auto_fix_track = constraintTrack.name
425 if ("Auto fixes " + name) in bpy.data.actions:
426 constraintAction = bpy.data.actions[("Auto fixes " + name)]
428 constraintAction = bpy.data.actions.new("Auto fixes " + name)
429 constraintAction.use_fake_user = True
430 constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
431 constraintStrip.extrapolation = "NOTHING"
432 userTrack = anim_data.nla_tracks.new()
433 userTrack.name = "Manual fixes " + name
434 NLATracks.manual_fix_track = userTrack.name
435 if ("Manual fixes " + name) in bpy.data.actions:
436 userAction = bpy.data.actions[("Manual fixes " + name)]
438 userAction = bpy.data.actions.new("Manual fixes " + name)
439 userAction.use_fake_user = True
440 userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
441 userStrip.extrapolation = "HOLD"
442 #userStrip.blend_type = "MULITPLY" - doesn't work due to work, will be activated soon
443 anim_data.nla_tracks.active = constraintTrack
444 #anim_data.action = constraintAction
445 anim_data.action_extrapolation = "NOTHING"
446 #set the stride_bone's action
447 if "stride_bone" in bpy.data.objects:
448 stride_bone = bpy.data.objects["stride_bone"]
449 if NLATracks.stride_action:
450 stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
452 NLATracks.stride_action = stride_bone.animation_data.action.name
453 stride_bone.animation_data.action.use_fake_user = True
454 anim_data.action = None
457 def preAdvancedRetargeting(performer_obj, enduser_obj):
458 createDictionary(performer_obj.data, enduser_obj.data)
459 bones = enduser_obj.pose.bones
460 map_bones = [bone for bone in bones if bone.bone.reverseMap]
461 perf_root = performer_obj.pose.bones[0].name
462 for bone in map_bones:
463 perf_bone = bone.bone.reverseMap[0].name
465 if (not bone.bone.use_connect) and (perf_bone!=perf_root):
466 locks = bone.lock_location
467 #if not (locks[0] or locks[1] or locks[2]):
468 cons = bone.constraints.new('COPY_LOCATION')
469 cons.name = "retargetTemp"
470 cons.use_x = not locks[0]
471 cons.use_y = not locks[1]
472 cons.use_z = not locks[2]
473 cons.target = performer_obj
474 cons.subtarget = perf_bone
475 cons.target_space = 'LOCAL'
476 cons.owner_space = 'LOCAL'
480 cons2 = bone.constraints.new('COPY_ROTATION')
481 cons2.name = "retargetTemp"
482 locks = bone.lock_rotation
483 cons2.use_x = not locks[0]
484 cons2.use_y = not locks[1]
485 cons2.use_z = not locks[2]
486 cons2.target = performer_obj
487 cons2.subtarget = perf_bone
488 cons2.target_space = 'WORLD'
489 cons2.owner_space = 'WORLD'
491 if perf_bone==perf_root:
495 #~ for constraint in bone.constraints:
496 #~ if constraint.type == 'COPY_ROTATION':
497 #~ constraint.target_space = 'LOCAL'
498 #~ constraint.owner_space = 'LOCAL'
501 def prepareForBake(enduser_obj):
502 bones = enduser_obj.pose.bones
504 bone.bone.select = False
505 map_bones = [bone for bone in bones if bone.bone.reverseMap]
506 for bone in map_bones:
507 for cons in bone.constraints:
508 if "retargetTemp" in cons.name:
509 bone.bone.select = True
511 def cleanTempConstraints(enduser_obj):
512 bones = enduser_obj.pose.bones
513 map_bones = [bone for bone in bones if bone.bone.reverseMap]
514 for bone in map_bones:
515 for cons in bone.constraints:
516 if "retargetTemp" in cons.name:
517 bone.constraints.remove(cons)
519 #Main function that runs the retargeting sequence.
520 #If advanced == True, we assume constraint's were already created
521 def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
522 perf_arm = performer_obj.data
523 end_arm = enduser_obj.data
524 advanced = end_arm.advancedRetarget
527 enduser_obj.animation_data.action = bpy.data.actions.new("temp")
528 enduser_obj.animation_data.action.use_fake_user = True
530 print("no need to create new action")
532 print("creating Dictionary")
533 feetBones, root = createDictionary(perf_arm, end_arm)
534 print("cleaning stuff up")
535 perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
537 turnOffIK(enduser_obj)
538 print("Creating intermediate armature (for first pass)")
539 inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene)
540 print("First pass: retargeting from intermediate to end user")
541 retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene)
543 prepareForBake(enduser_obj)
544 print("Retargeting pose (Advanced Retarget)")
545 nla.bake(s_frame, e_frame, action=enduser_obj.animation_data.action, only_selected=True, do_pose=True, do_object=False)
546 name = performer_obj.animation_data.action.name
547 enduser_obj.animation_data.action.name = "Base " + name
548 print("Second pass: retargeting root translation and clean up")
549 stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
551 IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene)
552 bpy.ops.object.select_name(name=stride_bone.name, extend=False)
553 restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame)
554 bpy.ops.object.mode_set(mode='OBJECT')
556 bpy.ops.object.select_name(name=inter_obj.name, extend=False)
557 bpy.ops.object.delete()
559 cleanTempConstraints(enduser_obj)
560 bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
562 if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
563 NLATracks = end_arm.mocapNLATracks.add()
564 NLATracks.name = name
566 NLATracks = end_arm.mocapNLATracks[name]
567 end_arm.active_mocap = name
568 print("retargeting done!")
570 def profileWrapper():
571 context = bpy.context
572 scene = context.scene
573 s_frame = scene.frame_start
574 e_frame = scene.frame_end
575 enduser_obj = context.active_object
576 performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
577 if enduser_obj is None or len(performer_obj) != 1:
578 print("Need active and selected armatures")
580 performer_obj = performer_obj[0]
581 s_frame, e_frame = performer_obj.animation_data.action.frame_range
582 s_frame = int(s_frame)
583 e_frame = int(e_frame)
584 totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
587 def isRigAdvanced(enduser_obj):
588 bones = enduser_obj.pose.bones
590 for constraint in bone.constraints:
591 if constraint.type != "IK":
593 if enduser_obj.data.animation_data:
594 if enduser_obj.data.animation_data.drivers:
597 if __name__ == "__main__":
598 cProfile.run("profileWrapper()")