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, step):
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 bake_matrix = (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3())
84 base_euler = inter_bone.rotation_euler
85 eul = bake_matrix.to_euler(base_euler.order,base_euler)
86 return eul.to_matrix().to_4x4()
88 #uses 1to1 and interpolation/averaging to match many to 1 retarget
89 def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
90 retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
91 lerp_matrix = Matrix()
92 for i in range(len(retarget_matrices) - 1):
93 first_mat = retarget_matrices[i]
94 next_mat = retarget_matrices[i + 1]
95 lerp_matrix = first_mat.lerp(next_mat, 0.5)
98 #determines the type of hierachy change needed and calls the
100 def retargetPerfToInter(inter_bone):
101 if inter_bone.bone.reverseMap:
102 perf_bone_name = inter_bone.bone.reverseMap
103 # 1 to many not supported yet
104 # then its either a many to 1 or 1 to 1
105 if len(perf_bone_name) > 1:
106 performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
107 #we need to map several performance bone to a single
108 inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
110 perf_bone = performer_bones[perf_bone_name[0].name]
111 inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
112 inter_bone.keyframe_insert("rotation_quaternion")
114 #creates the intermediate armature object
115 inter_obj = enduser_obj.copy()
116 inter_obj.data = inter_obj.data.copy() # duplicate data
117 bpy.context.scene.objects.link(inter_obj)
118 inter_obj.name = "intermediate"
119 bpy.context.scene.objects.active = inter_obj
120 bpy.ops.object.mode_set(mode='EDIT')
121 #add some temporary connecting bones in case end user bones are not connected to their parents
122 print("creating temp bones")
123 for bone in inter_obj.data.edit_bones:
124 if not bone.use_connect and bone.parent:
125 if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
126 newBone = inter_obj.data.edit_bones.new("Temp")
127 newBone.head = bone.parent.tail
128 newBone.tail = bone.head
129 newBone.parent = bone.parent
130 bone.parent = newBone
131 bone.use_connect = True
132 newBone.use_connect = True
134 print("retargeting to intermediate")
135 bpy.ops.armature.calculate_roll(type='Z')
136 bpy.ops.object.mode_set(mode="OBJECT")
137 inter_obj.data.name = "inter_arm"
138 inter_arm = inter_obj.data
139 performer_bones = performer_obj.pose.bones
140 inter_bones = inter_obj.pose.bones
142 for inter_bone in inter_bones:
143 if inter_bone.bone.reverseMap:
144 inter_bone.bone.use_inherit_rotation = False
146 inter_bone.bone.use_inherit_rotation = True
148 for t in range(s_frame, e_frame, step):
149 if (t - s_frame) % 10 == 0:
150 print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
152 for bone in inter_bones:
153 retargetPerfToInter(bone)
157 # this procedure copies the rotations over from the intermediate
158 # armature to the end user one.
159 # As the hierarchies are 1 to 1, this is a simple matter of
160 # copying the rotation, while keeping in mind bone roll, parenting, etc.
161 # TODO: Control Bones: If a certain bone is constrained in a way
162 # that its rotation is determined by another (a control bone)
163 # We should determine the right pos of the control bone.
164 # Scale: ? Should work but needs testing.
167 def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step):
168 inter_bones = inter_obj.pose.bones
169 end_bones = enduser_obj.pose.bones
171 def bakeTransform(end_bone):
172 src_bone = inter_bones[end_bone.name]
174 bake_matrix = src_bone.matrix
175 rest_matrix = trg_bone.bone.matrix_local
177 if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
178 srcParent = src_bone.parent
179 if "Temp" in srcParent.name:
180 srcParent = srcParent.parent
181 parent_mat = srcParent.matrix
182 parent_rest = trg_bone.parent.bone.matrix_local
183 parent_rest_inv = parent_rest.inverted()
184 parent_mat_inv = parent_mat.inverted()
185 bake_matrix = parent_mat_inv * bake_matrix
186 rest_matrix = parent_rest_inv * rest_matrix
188 rest_matrix_inv = rest_matrix.inverted()
189 bake_matrix = rest_matrix_inv * bake_matrix
190 end_bone.matrix_basis = bake_matrix
191 rot_mode = end_bone.rotation_mode
192 if rot_mode == "QUATERNION":
193 end_bone.keyframe_insert("rotation_quaternion")
194 elif rot_mode == "AXIS_ANGLE":
195 end_bone.keyframe_insert("rotation_axis_angle")
197 end_bone.keyframe_insert("rotation_euler")
198 if not end_bone.bone.use_connect:
199 end_bone.keyframe_insert("location")
201 for bone in end_bone.children:
204 for t in range(s_frame, e_frame, step):
205 if (t - s_frame) % 10 == 0:
206 print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
208 end_bone = end_bones[root]
209 end_bone.location = Vector((0, 0, 0))
210 end_bone.keyframe_insert("location")
211 bakeTransform(end_bone)
213 #recieves the performer feet bones as a variable
214 # by "feet" I mean those bones that have plants
215 # (they don't move, despite root moving) somewhere in the animation.
218 def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
220 perf_bones = performer_obj.pose.bones
221 end_bones = enduser_obj.pose.bones
223 perfRoot = perf_bones[0].name
224 endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
225 locDictKeys = perfFeet + endFeet + [perfRoot]
228 return bone.center + (bone.vector / 2)
230 #Step 1 - we create a dict that contains these keys:
231 #(Performer) Hips, Feet
233 # where the values are their world position on each frame in range (s,e)
236 for key in locDictKeys:
239 for t in range(scene.frame_start, scene.frame_end):
241 for bone in perfFeet:
242 locDict[bone].append(tailLoc(perf_bones[bone]))
243 locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
245 locDict[bone].append(tailLoc(end_bones[bone]))
247 # now we take our locDict and analyze it.
248 # we need to derive all chains
250 def locDeriv(key, t):
252 return graph[t + 1] - graph[t]
255 #~ for key in locDictKeys:
256 #~ locDeriv[key] = []
258 #~ for key in locDict.keys():
259 #~ graph = locDict[key]
260 #~ locDeriv[key] = [graph[t + 1] - graph[t] for t in range(len(graph) - 1)]
262 # now find the plant frames, where perfFeet don't move much
267 for i in range(len(locDict[key]) - 1):
270 hipV = locDeriv(perfRoot,i)
271 endV = locDeriv(perf_bones[key].bone.map,i)
272 #this is a plant frame.
273 #lets see what the original hip delta is, and the corresponding
276 linearAvg.append(hipV.length / endV.length)
278 action_name = performer_obj.animation_data.action.name
279 #is there a stride_bone?
280 if "stride_bone" in bpy.data.objects:
281 stride_action = bpy.data.actions.new("Stride Bone " + action_name)
282 stride_action.use_fake_user = True
283 stride_bone = enduser_obj.parent
284 stride_bone.animation_data.action = stride_action
287 stride_bone = bpy.context.active_object
288 stride_bone.name = "stride_bone"
290 stride_bone.location = enduser_obj_mat.to_translation()
293 #determine the average change in scale needed
294 avg = sum(linearAvg) / len(linearAvg)
295 scene.frame_set(s_frame)
296 initialPos = (tailLoc(perf_bones[perfRoot]) / avg) #+ stride_bone.location
297 for t in range(s_frame, e_frame):
299 #calculate the new position, by dividing by the found ratio between performer and enduser
300 newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
301 stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
302 stride_bone.keyframe_insert("location")
305 stride_bone.keyframe_insert("location")
306 stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
311 def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
312 bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
313 end_bones = enduser_obj.pose.bones
314 for pose_bone in end_bones:
315 ik_constraint = hasIKConstraint(pose_bone)
317 target_is_bone = False
318 # set constraint target to corresponding empty if targetless,
319 # if not, keyframe current target to corresponding empty
320 perf_bone = pose_bone.bone.reverseMap[-1].name
321 bpy.ops.object.mode_set(mode='EDIT')
322 orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
323 bpy.ops.object.mode_set(mode='OBJECT')
324 if not ik_constraint.target:
325 ik_constraint.target = enduser_obj
326 ik_constraint.subtarget = pose_bone.name+"IK"
329 # There is a target now
330 if ik_constraint.subtarget:
331 target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
332 target.bone.use_local_location = False
333 target_is_bone = True
335 target = ik_constraint.target
337 # bake the correct locations for the ik target bones
338 for t in range(s_frame, e_frame, step):
341 final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
343 final_loc = pose_bone.tail
344 target.location = final_loc
345 target.keyframe_insert("location")
346 ik_constraint.mute = False
347 scene.frame_set(s_frame)
348 bpy.ops.object.mode_set(mode='OBJECT')
351 def turnOffIK(enduser_obj):
352 end_bones = enduser_obj.pose.bones
353 for pose_bone in end_bones:
354 if pose_bone.is_in_ik_chain:
357 # set stiffness according to place on chain
358 # and values from analysis that is stored in the bone
359 #pose_bone.ik_stiffness_x = 0.5
360 #pose_bone.ik_stiffness_y = 0.5
361 #pose_bone.ik_stiffness_z = 0.5
362 ik_constraint = hasIKConstraint(pose_bone)
364 ik_constraint.mute = True
367 #copy the object matrixes and clear them (to be reinserted later)
368 def cleanAndStoreObjMat(performer_obj, enduser_obj):
369 perf_obj_mat = performer_obj.matrix_world.copy()
370 enduser_obj_mat = enduser_obj.matrix_world.copy()
372 performer_obj.matrix_world = zero_mat
373 enduser_obj.matrix_world = zero_mat
374 return perf_obj_mat, enduser_obj_mat
377 #restore the object matrixes after parenting the auto generated IK empties
378 def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
379 pose_bones = enduser_obj.pose.bones
380 for pose_bone in pose_bones:
381 if pose_bone.name + "Org" in bpy.data.objects:
382 empty = bpy.data.objects[pose_bone.name + "Org"]
383 empty.parent = stride_bone
384 performer_obj.matrix_world = perf_obj_mat
385 enduser_obj.parent = stride_bone
386 scene.frame_set(s_frame)
387 enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() * Matrix.Translation(stride_bone.matrix_world.to_translation())
388 enduser_obj.matrix_world = enduser_obj_mat
391 #create (or return if exists) the related IK empty to the bone
392 def originalLocationTarget(end_bone, enduser_obj):
393 if not end_bone.name + "IK" in enduser_obj.data.bones:
394 newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
395 newBone.head = end_bone.tail
396 newBone.tail = end_bone.tail + Vector((0,0.1,0))
397 #~ empty = bpy.context.active_object
398 #~ empty.name = end_bone.name + "Org"
399 #~ empty.empty_draw_size = 0.1
400 #~ empty.parent = enduser_obj
402 newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
406 #create the specified NLA setup for base animation, constraints and tweak layer.
407 def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
408 enduser_obj = context.active_object
409 NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
410 name = NLATracks.name
411 anim_data = enduser_obj.animation_data
414 if ("Base " + name) in bpy.data.actions:
415 mocapAction = bpy.data.actions[("Base " + name)]
417 print("That retargeted anim has no base action")
418 anim_data.use_nla = True
419 for track in anim_data.nla_tracks:
420 anim_data.nla_tracks.remove(track)
421 mocapTrack = anim_data.nla_tracks.new()
422 mocapTrack.name = "Base " + name
423 NLATracks.base_track = mocapTrack.name
424 mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
425 constraintTrack = anim_data.nla_tracks.new()
426 constraintTrack.name = "Auto fixes " + name
427 NLATracks.auto_fix_track = constraintTrack.name
428 if ("Auto fixes " + name) in bpy.data.actions:
429 constraintAction = bpy.data.actions[("Auto fixes " + name)]
431 constraintAction = bpy.data.actions.new("Auto fixes " + name)
432 constraintAction.use_fake_user = True
433 constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
434 constraintStrip.extrapolation = "NOTHING"
435 userTrack = anim_data.nla_tracks.new()
436 userTrack.name = "Manual fixes " + name
437 NLATracks.manual_fix_track = userTrack.name
438 if ("Manual fixes " + name) in bpy.data.actions:
439 userAction = bpy.data.actions[("Manual fixes " + name)]
441 userAction = bpy.data.actions.new("Manual fixes " + name)
442 userAction.use_fake_user = True
443 userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
444 userStrip.extrapolation = "HOLD"
445 #userStrip.blend_type = "MULITPLY" - doesn't work due to work, will be activated soon
446 anim_data.nla_tracks.active = constraintTrack
447 #anim_data.action = constraintAction
448 anim_data.action_extrapolation = "NOTHING"
449 #set the stride_bone's action
450 if "stride_bone" in bpy.data.objects:
451 stride_bone = bpy.data.objects["stride_bone"]
452 if NLATracks.stride_action:
453 stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
455 NLATracks.stride_action = stride_bone.animation_data.action.name
456 stride_bone.animation_data.action.use_fake_user = True
457 anim_data.action = None
460 def preAdvancedRetargeting(performer_obj, enduser_obj):
461 createDictionary(performer_obj.data, enduser_obj.data)
462 bones = enduser_obj.pose.bones
463 map_bones = [bone for bone in bones if bone.bone.reverseMap]
464 perf_root = performer_obj.pose.bones[0].name
465 for bone in map_bones:
466 perf_bone = bone.bone.reverseMap[0].name
468 if (not bone.bone.use_connect) and (perf_bone!=perf_root):
469 locks = bone.lock_location
470 #if not (locks[0] or locks[1] or locks[2]):
471 cons = bone.constraints.new('COPY_LOCATION')
472 cons.name = "retargetTemp"
473 cons.use_x = not locks[0]
474 cons.use_y = not locks[1]
475 cons.use_z = not locks[2]
476 cons.target = performer_obj
477 cons.subtarget = perf_bone
478 cons.target_space = 'LOCAL'
479 cons.owner_space = 'LOCAL'
483 cons2 = bone.constraints.new('COPY_ROTATION')
484 cons2.name = "retargetTemp"
485 locks = bone.lock_rotation
486 cons2.use_x = not locks[0]
487 cons2.use_y = not locks[1]
488 cons2.use_z = not locks[2]
489 cons2.target = performer_obj
490 cons2.subtarget = perf_bone
491 cons2.target_space = 'WORLD'
492 cons2.owner_space = 'WORLD'
494 if perf_bone==perf_root:
498 #~ for constraint in bone.constraints:
499 #~ if constraint.type == 'COPY_ROTATION':
500 #~ constraint.target_space = 'LOCAL'
501 #~ constraint.owner_space = 'LOCAL'
504 def prepareForBake(enduser_obj):
505 bones = enduser_obj.pose.bones
507 bone.bone.select = False
508 map_bones = [bone for bone in bones if bone.bone.reverseMap]
509 for bone in map_bones:
510 for cons in bone.constraints:
511 if "retargetTemp" in cons.name:
512 bone.bone.select = True
514 def cleanTempConstraints(enduser_obj):
515 bones = enduser_obj.pose.bones
516 map_bones = [bone for bone in bones if bone.bone.reverseMap]
517 for bone in map_bones:
518 for cons in bone.constraints:
519 if "retargetTemp" in cons.name:
520 bone.constraints.remove(cons)
522 #Main function that runs the retargeting sequence.
523 #If advanced == True, we assume constraint's were already created
524 def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
525 perf_arm = performer_obj.data
526 end_arm = enduser_obj.data
527 advanced = end_arm.advancedRetarget
528 step = end_arm.frameStep
531 enduser_obj.animation_data.action = bpy.data.actions.new("temp")
532 enduser_obj.animation_data.action.use_fake_user = True
534 print("no need to create new action")
536 print("creating Dictionary")
537 feetBones, root = createDictionary(perf_arm, end_arm)
538 print("cleaning stuff up")
539 perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
541 turnOffIK(enduser_obj)
542 print("Creating intermediate armature (for first pass)")
543 inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step)
544 print("First pass: retargeting from intermediate to end user")
545 retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step)
547 prepareForBake(enduser_obj)
548 print("Retargeting pose (Advanced Retarget)")
549 nla.bake(s_frame, e_frame, action=enduser_obj.animation_data.action, only_selected=True, do_pose=True, do_object=False, step=step)
550 name = performer_obj.animation_data.action.name
551 enduser_obj.animation_data.action.name = "Base " + name
552 print("Second pass: retargeting root translation and clean up")
553 stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
555 IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step)
556 bpy.ops.object.select_name(name=stride_bone.name, extend=False)
557 restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame)
558 bpy.ops.object.mode_set(mode='OBJECT')
560 bpy.ops.object.select_name(name=inter_obj.name, extend=False)
561 bpy.ops.object.delete()
563 cleanTempConstraints(enduser_obj)
564 bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
566 if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
567 NLATracks = end_arm.mocapNLATracks.add()
568 NLATracks.name = name
570 NLATracks = end_arm.mocapNLATracks[name]
571 end_arm.active_mocap = name
572 print("retargeting done!")
574 def profileWrapper():
575 context = bpy.context
576 scene = context.scene
577 s_frame = scene.frame_start
578 e_frame = scene.frame_end
579 enduser_obj = context.active_object
580 performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
581 if enduser_obj is None or len(performer_obj) != 1:
582 print("Need active and selected armatures")
584 performer_obj = performer_obj[0]
585 s_frame, e_frame = performer_obj.animation_data.action.frame_range
586 s_frame = int(s_frame)
587 e_frame = int(e_frame)
588 totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
591 def isRigAdvanced(enduser_obj):
592 bones = enduser_obj.pose.bones
594 for constraint in bone.constraints:
595 if constraint.type != "IK":
597 if enduser_obj.data.animation_data:
598 if enduser_obj.data.animation_data.drivers:
601 if __name__ == "__main__":
602 cProfile.run("profileWrapper()")