Added option to each retargeted bone to fix twist issues caused by variable bone...
[blender.git] / release / scripts / modules / retarget.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22 from mathutils import *
23 from math import radians, acos, pi
24 from bl_operators import nla
25 import cProfile
26
27
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"]
31     if ik:
32         return ik[0]
33     else:
34         return False
35
36
37 def createDictionary(perf_arm, end_arm):
38     # clear any old data
39     for end_bone in end_arm.bones:
40         for mapping in end_bone.reverseMap:
41             end_bone.reverseMap.remove(0)
42
43     for perf_bone in perf_arm.bones:
44         #find its match and add perf_bone to the match's mapping
45         if perf_bone.map:
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
50
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
55
56 def loadMapping(perf_arm, end_arm):
57
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
63
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
70
71
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
77
78     #Simple 1to1 retarget of a bone
79     def singleBoneRetarget(inter_bone, perf_bone):
80             perf_world_rotation = perf_bone.matrix
81             inter_world_base_rotation = inter_bone.bone.matrix_local
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             #~ orgEul = inter_bone.bone.matrix_local.to_euler("XYZ")
85             #~ eul = bake_matrix.to_euler("XYZ", orgEul)
86             #~ diff = -bake_matrix.to_euler().y + inter_bone.bone.matrix.to_euler().y
87             #~ eul.rotate_axis("Y", diff)
88             #~ eul.make_compatible(orgEul)
89             #~ bake_matrix = eul.to_matrix()
90             #~ #diff = abs(diff)
91             #bake_matrix = bake_matrix* Matrix.Rotation(pi/2, 3, "Y") 
92             #~ scene = bpy.context.scene
93             #~ print(scene.frame_current, inter_bone.name, bake_matrix.to_euler().y)
94             return bake_matrix.to_4x4()
95
96     #uses 1to1 and interpolation/averaging to match many to 1 retarget
97     def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
98         retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
99         lerp_matrix = Matrix()
100         for i in range(len(retarget_matrices) - 1):
101             first_mat = retarget_matrices[i]
102             next_mat = retarget_matrices[i + 1]
103             lerp_matrix = first_mat.lerp(next_mat, 0.5)
104         return lerp_matrix
105
106     #determines the type of hierachy change needed and calls the
107     #right function
108     def retargetPerfToInter(inter_bone):
109         if inter_bone.bone.reverseMap:
110             perf_bone_name = inter_bone.bone.reverseMap
111                 # 1 to many not supported yet
112                 # then its either a many to 1 or 1 to 1
113             if len(perf_bone_name) > 1:
114                 performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
115                 #we need to map several performance bone to a single
116                 inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
117             else:
118                 perf_bone = performer_bones[perf_bone_name[0].name]
119                 inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
120         if inter_bone.bone.twistFix:
121             inter_bone.matrix_basis *= Matrix.Rotation(radians(180), 4, "Y")
122         rot_mode = inter_bone.rotation_mode
123         if rot_mode == "QUATERNION":
124             inter_bone.keyframe_insert("rotation_quaternion")
125         elif rot_mode == "AXIS_ANGLE":
126             inter_bone.keyframe_insert("rotation_axis_angle")
127         else:
128             inter_bone.keyframe_insert("rotation_euler")
129
130     #creates the intermediate armature object
131     inter_obj = enduser_obj.copy()
132     inter_obj.data = inter_obj.data.copy()  # duplicate data
133     bpy.context.scene.objects.link(inter_obj)
134     inter_obj.name = "intermediate"
135     bpy.context.scene.objects.active = inter_obj
136     bpy.ops.object.mode_set(mode='EDIT')
137     #add some temporary connecting bones in case end user bones are not connected to their parents
138     rollDict = {}
139     print("creating temp bones")
140     for bone in inter_obj.data.edit_bones:
141         if not bone.use_connect and bone.parent:
142             if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
143                 newBone = inter_obj.data.edit_bones.new("Temp")
144                 newBone.head = bone.parent.tail
145                 newBone.tail = bone.head
146                 newBone.parent = bone.parent
147                 bone.parent = newBone
148                 bone.use_connect = True
149                 newBone.use_connect = True
150         rollDict[bone.name] = bone.roll
151         bone.roll = 0
152     #resets roll
153     print("retargeting to intermediate")
154     #bpy.ops.armature.calculate_roll(type='Z')
155     bpy.ops.object.mode_set(mode="OBJECT")
156     inter_obj.data.name = "inter_arm"
157     inter_arm = inter_obj.data
158     performer_bones = performer_obj.pose.bones
159     inter_bones = inter_obj.pose.bones
160     #clears inheritance
161     for inter_bone in inter_bones:
162         if inter_bone.bone.reverseMap:
163             inter_bone.bone.use_inherit_rotation = False
164         else:
165             inter_bone.bone.use_inherit_rotation = True
166
167     for t in range(s_frame, e_frame, step):
168         if (t - s_frame) % 10 == 0:
169             print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
170         scene.frame_set(t)
171         for bone in inter_bones:
172             retargetPerfToInter(bone)
173
174     return inter_obj
175
176 # this procedure copies the rotations over from the intermediate
177 # armature to the end user one.
178 # As the hierarchies are 1 to 1, this is a simple matter of
179 # copying the rotation, while keeping in mind bone roll, parenting, etc.
180 # TODO: Control Bones: If a certain bone is constrained in a way
181 #       that its rotation is determined by another (a control bone)
182 #       We should determine the right pos of the control bone.
183 #       Scale: ? Should work but needs testing.
184
185
186 def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step):
187     inter_bones = inter_obj.pose.bones
188     end_bones = enduser_obj.pose.bones
189
190     def bakeTransform(end_bone):
191         src_bone = inter_bones[end_bone.name]
192         trg_bone = end_bone
193         bake_matrix = src_bone.matrix
194         rest_matrix = trg_bone.bone.matrix_local
195
196         if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
197             srcParent = src_bone.parent
198             if "Temp" in srcParent.name:
199                 srcParent = srcParent.parent
200             parent_mat = srcParent.matrix
201             parent_rest = trg_bone.parent.bone.matrix_local
202             parent_rest_inv = parent_rest.inverted()
203             parent_mat_inv = parent_mat.inverted()
204             bake_matrix = parent_mat_inv * bake_matrix
205             rest_matrix = parent_rest_inv * rest_matrix
206
207         rest_matrix_inv = rest_matrix.inverted()
208         bake_matrix = rest_matrix_inv * bake_matrix
209         end_bone.matrix_basis = bake_matrix
210         rot_mode = end_bone.rotation_mode
211         if rot_mode == "QUATERNION":
212             end_bone.keyframe_insert("rotation_quaternion")
213         elif rot_mode == "AXIS_ANGLE":
214             end_bone.keyframe_insert("rotation_axis_angle")
215         else:
216             end_bone.keyframe_insert("rotation_euler")
217         if not end_bone.bone.use_connect:
218             end_bone.keyframe_insert("location")
219
220         for bone in end_bone.children:
221             bakeTransform(bone)
222
223     for t in range(s_frame, e_frame, step):
224         if (t - s_frame) % 10 == 0:
225             print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
226         scene.frame_set(t)
227         end_bone = end_bones[root]
228         end_bone.location = Vector((0, 0, 0))
229         end_bone.keyframe_insert("location")
230         bakeTransform(end_bone)
231
232 #recieves the performer feet bones as a variable
233 # by "feet" I mean those bones that have plants
234 # (they don't move, despite root moving) somewhere in the animation.
235
236
237 def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
238
239     perf_bones = performer_obj.pose.bones
240     end_bones = enduser_obj.pose.bones
241
242     perfRoot = perf_bones[0].name
243     endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
244     locDictKeys = perfFeet + endFeet + [perfRoot]
245
246     def tailLoc(bone):
247         return bone.center + (bone.vector / 2)
248
249     #Step 1 - we create a dict that contains these keys:
250     #(Performer) Hips, Feet
251     #(End user) Feet
252     # where the values are their world position on each frame in range (s,e)
253
254     locDict = {}
255     for key in locDictKeys:
256         locDict[key] = []
257
258     for t in range(scene.frame_start, scene.frame_end):
259         scene.frame_set(t)
260         for bone in perfFeet:
261             locDict[bone].append(tailLoc(perf_bones[bone]))
262         locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
263         for bone in endFeet:
264             locDict[bone].append(tailLoc(end_bones[bone]))
265
266     # now we take our locDict and analyze it.
267     # we need to derive all chains
268     
269     def locDeriv(key, t):
270         graph = locDict[key]
271         return graph[t + 1] - graph[t]
272
273     #~ locDeriv = {}
274     #~ for key in locDictKeys:
275         #~ locDeriv[key] = []
276
277     #~ for key in locDict.keys():
278         #~ graph = locDict[key]
279         #~ locDeriv[key] = [graph[t + 1] - graph[t] for t in range(len(graph) - 1)]
280
281     # now find the plant frames, where perfFeet don't move much
282
283     linearAvg = []
284
285     for key in perfFeet:
286         for i in range(len(locDict[key]) - 1):
287             v = locDeriv(key,i)
288             if (v.length < 0.1):
289                 hipV = locDeriv(perfRoot,i)
290                 endV = locDeriv(perf_bones[key].bone.map,i)
291                 #this is a plant frame.
292                 #lets see what the original hip delta is, and the corresponding
293                 #end bone's delta
294                 if endV.length != 0:
295                     linearAvg.append(hipV.length / endV.length)
296
297     action_name = performer_obj.animation_data.action.name
298     #is there a stride_bone?
299     if "stride_bone" in bpy.data.objects:
300         stride_action = bpy.data.actions.new("Stride Bone " + action_name)
301         stride_action.use_fake_user = True
302         stride_bone = enduser_obj.parent
303         stride_bone.animation_data.action = stride_action
304     else:
305         bpy.ops.object.add()
306         stride_bone = bpy.context.active_object
307         stride_bone.name = "stride_bone"
308     print(stride_bone)
309     stride_bone.location = enduser_obj_mat.to_translation()
310     print(linearAvg)
311     if linearAvg:
312         #determine the average change in scale needed
313         avg = sum(linearAvg) / len(linearAvg)
314         scene.frame_set(s_frame)
315         initialPos = (tailLoc(perf_bones[perfRoot]) / avg) #+ stride_bone.location
316         for t in range(s_frame, e_frame):
317             scene.frame_set(t)
318             #calculate the new position, by dividing by the found ratio between performer and enduser
319             newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
320             stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
321             stride_bone.keyframe_insert("location")
322     else:
323         
324         stride_bone.keyframe_insert("location")
325     stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
326
327     return stride_bone
328
329
330 def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
331     bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
332     end_bones = enduser_obj.pose.bones
333     for pose_bone in end_bones:
334         ik_constraint = hasIKConstraint(pose_bone)
335         if ik_constraint:
336             target_is_bone = False
337             # set constraint target to corresponding empty if targetless,
338             # if not, keyframe current target to corresponding empty
339             perf_bone = pose_bone.bone.reverseMap[-1].name
340             bpy.ops.object.mode_set(mode='EDIT')
341             orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
342             bpy.ops.object.mode_set(mode='OBJECT')
343             if not ik_constraint.target:
344                 ik_constraint.target = enduser_obj
345                 ik_constraint.subtarget = pose_bone.name+"IK"
346                 target = orgLocTrg
347
348             # There is a target now
349             if ik_constraint.subtarget:
350                 target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
351                 target.bone.use_local_location = False
352                 target_is_bone = True
353             else:
354                 target = ik_constraint.target
355
356             # bake the correct locations for the ik target bones
357             for t in range(s_frame, e_frame, step):
358                 scene.frame_set(t)
359                 if target_is_bone:
360                     final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
361                 else:
362                     final_loc = pose_bone.tail
363                 target.location = final_loc
364                 target.keyframe_insert("location")
365             ik_constraint.mute = False
366     scene.frame_set(s_frame)
367     bpy.ops.object.mode_set(mode='OBJECT')
368
369
370 def turnOffIK(enduser_obj):
371     end_bones = enduser_obj.pose.bones
372     for pose_bone in end_bones:
373         if pose_bone.is_in_ik_chain:
374             pass
375             # TODO:
376             # set stiffness according to place on chain
377             # and values from analysis that is stored in the bone
378             #pose_bone.ik_stiffness_x = 0.5
379             #pose_bone.ik_stiffness_y = 0.5
380             #pose_bone.ik_stiffness_z = 0.5
381         ik_constraint = hasIKConstraint(pose_bone)
382         if ik_constraint:
383             ik_constraint.mute = True
384
385
386 #copy the object matrixes and clear them (to be reinserted later)
387 def cleanAndStoreObjMat(performer_obj, enduser_obj):
388     perf_obj_mat = performer_obj.matrix_world.copy()
389     enduser_obj_mat = enduser_obj.matrix_world.copy()
390     zero_mat = Matrix()
391     performer_obj.matrix_world = zero_mat
392     enduser_obj.matrix_world = zero_mat
393     return perf_obj_mat, enduser_obj_mat
394
395
396 #restore the object matrixes after parenting the auto generated IK empties
397 def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
398     pose_bones = enduser_obj.pose.bones
399     for pose_bone in pose_bones:
400         if pose_bone.name + "Org" in bpy.data.objects:
401             empty = bpy.data.objects[pose_bone.name + "Org"]
402             empty.parent = stride_bone
403     performer_obj.matrix_world = perf_obj_mat
404     enduser_obj.parent = stride_bone
405     scene.frame_set(s_frame)
406     enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() * Matrix.Translation(stride_bone.matrix_world.to_translation())
407     enduser_obj.matrix_world = enduser_obj_mat
408
409
410 #create (or return if exists) the related IK empty to the bone
411 def originalLocationTarget(end_bone, enduser_obj):
412     if not end_bone.name + "IK" in enduser_obj.data.bones:
413         newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
414         newBone.head = end_bone.tail
415         newBone.tail = end_bone.tail + Vector((0,0.1,0))
416         #~ empty = bpy.context.active_object
417         #~ empty.name = end_bone.name + "Org"
418         #~ empty.empty_draw_size = 0.1
419         #~ empty.parent = enduser_obj
420     else:
421         newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
422     return newBone
423
424
425 #create the specified NLA setup for base animation, constraints and tweak layer.
426 def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
427     enduser_obj = context.active_object
428     NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
429     name = NLATracks.name
430     anim_data = enduser_obj.animation_data
431     s_frame = 0
432     print(name)
433     if ("Base " + name) in bpy.data.actions:
434         mocapAction = bpy.data.actions[("Base " + name)]
435     else:
436         print("That retargeted anim has no base action")
437     anim_data.use_nla = True
438     for track in anim_data.nla_tracks:
439         anim_data.nla_tracks.remove(track)
440     mocapTrack = anim_data.nla_tracks.new()
441     mocapTrack.name = "Base " + name
442     NLATracks.base_track = mocapTrack.name
443     mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
444     constraintTrack = anim_data.nla_tracks.new()
445     constraintTrack.name = "Auto fixes " + name
446     NLATracks.auto_fix_track = constraintTrack.name
447     if ("Auto fixes " + name) in bpy.data.actions:
448         constraintAction = bpy.data.actions[("Auto fixes " + name)]
449     else:
450         constraintAction = bpy.data.actions.new("Auto fixes " + name)
451         constraintAction.use_fake_user = True
452     constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
453     constraintStrip.extrapolation = "NOTHING"
454     userTrack = anim_data.nla_tracks.new()
455     userTrack.name = "Manual fixes " + name
456     NLATracks.manual_fix_track = userTrack.name
457     if ("Manual fixes " + name) in bpy.data.actions:
458         userAction = bpy.data.actions[("Manual fixes " + name)]
459     else:
460         userAction = bpy.data.actions.new("Manual fixes " + name)
461         userAction.use_fake_user = True
462     userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
463     userStrip.extrapolation = "HOLD"
464     #userStrip.blend_type = "MULITPLY" - doesn't work due to work, will be activated soon
465     anim_data.nla_tracks.active = constraintTrack
466     #anim_data.action = constraintAction
467     anim_data.action_extrapolation = "NOTHING"
468     #set the stride_bone's action
469     if "stride_bone" in bpy.data.objects:
470         stride_bone = bpy.data.objects["stride_bone"]
471         if NLATracks.stride_action:
472             stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
473         else:
474             NLATracks.stride_action = stride_bone.animation_data.action.name
475             stride_bone.animation_data.action.use_fake_user = True
476     anim_data.action = None
477
478
479 def preAdvancedRetargeting(performer_obj, enduser_obj):
480     createDictionary(performer_obj.data, enduser_obj.data)
481     bones = enduser_obj.pose.bones
482     map_bones = [bone for bone in bones if bone.bone.reverseMap]
483     perf_root = performer_obj.pose.bones[0].name
484     for bone in map_bones:
485         perf_bone = bone.bone.reverseMap[0].name
486         addLocalRot = False;
487         if (not bone.bone.use_connect) and (perf_bone!=perf_root):
488             locks = bone.lock_location
489             #if not (locks[0] or locks[1] or locks[2]):  
490             cons = bone.constraints.new('COPY_LOCATION')
491             cons.name = "retargetTemp"
492             cons.use_x = not locks[0]
493             cons.use_y = not locks[1]
494             cons.use_z = not locks[2]
495             cons.target = performer_obj
496             cons.subtarget = perf_bone
497             cons.target_space = 'LOCAL'
498             cons.owner_space = 'LOCAL'
499             addLocalRot = True
500
501        
502         cons2 = bone.constraints.new('COPY_ROTATION')
503         cons2.name = "retargetTemp"
504         locks = bone.lock_rotation
505         cons2.use_x = not locks[0]
506         cons2.use_y = not locks[1]
507         cons2.use_z = not locks[2]
508         cons2.target = performer_obj
509         cons2.subtarget = perf_bone
510         cons2.target_space = 'WORLD'
511         cons2.owner_space = 'WORLD'
512
513         if perf_bone==perf_root:
514             addLocalRot = True
515
516         #~ if addLocalRot:
517             #~ for constraint in bone.constraints:
518                 #~ if constraint.type == 'COPY_ROTATION':
519                     #~ constraint.target_space = 'LOCAL'
520                     #~ constraint.owner_space = 'LOCAL'
521
522
523 def prepareForBake(enduser_obj):
524     bones = enduser_obj.pose.bones
525     for bone in bones:
526         bone.bone.select = False
527     map_bones = [bone for bone in bones if bone.bone.reverseMap]
528     for bone in map_bones:
529         for cons in bone.constraints:
530             if "retargetTemp" in cons.name:
531                 bone.bone.select = True
532
533 def cleanTempConstraints(enduser_obj):
534     bones = enduser_obj.pose.bones
535     map_bones = [bone for bone in bones if bone.bone.reverseMap]
536     for bone in map_bones:
537         for cons in bone.constraints:
538             if "retargetTemp" in cons.name:
539                 bone.constraints.remove(cons)
540
541 #Main function that runs the retargeting sequence.
542 #If advanced == True, we assume constraint's were already created
543 def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
544     perf_arm = performer_obj.data
545     end_arm = enduser_obj.data
546     advanced = end_arm.advancedRetarget
547     step = end_arm.frameStep
548     
549     try:
550         enduser_obj.animation_data.action = bpy.data.actions.new("temp")
551         enduser_obj.animation_data.action.use_fake_user = True
552     except:
553         print("no need to create new action")
554     
555     print("creating Dictionary")
556     feetBones, root = createDictionary(perf_arm, end_arm)
557     print("cleaning stuff up")
558     perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
559     if not advanced:
560         turnOffIK(enduser_obj)
561         print("Creating intermediate armature (for first pass)")
562         inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step)
563         print("First pass: retargeting from intermediate to end user")
564         retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step)
565     else:
566         prepareForBake(enduser_obj)
567         print("Retargeting pose (Advanced Retarget)")
568         nla.bake(s_frame, e_frame, action=enduser_obj.animation_data.action, only_selected=True, do_pose=True, do_object=False, step=step)
569     name = performer_obj.animation_data.action.name
570     enduser_obj.animation_data.action.name = "Base " + name
571     print("Second pass: retargeting root translation and clean up")
572     stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
573     if not advanced:
574         IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step)
575         bpy.ops.object.select_name(name=stride_bone.name, extend=False)
576     restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame)
577     bpy.ops.object.mode_set(mode='OBJECT')
578     if not advanced:
579         bpy.ops.object.select_name(name=inter_obj.name, extend=False)
580         bpy.ops.object.delete()
581     else:
582         cleanTempConstraints(enduser_obj)
583     bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
584
585     if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
586         NLATracks = end_arm.mocapNLATracks.add()
587         NLATracks.name = name
588     else:
589         NLATracks = end_arm.mocapNLATracks[name]
590     end_arm.active_mocap = name
591     print("retargeting done!")
592     
593 def profileWrapper():
594     context = bpy.context
595     scene = context.scene
596     s_frame = scene.frame_start
597     e_frame = scene.frame_end
598     enduser_obj = context.active_object
599     performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
600     if enduser_obj is None or len(performer_obj) != 1:
601         print("Need active and selected armatures")
602     else:
603         performer_obj = performer_obj[0]
604         s_frame, e_frame = performer_obj.animation_data.action.frame_range
605         s_frame = int(s_frame)
606         e_frame = int(e_frame)
607         totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
608
609
610 def isRigAdvanced(enduser_obj):
611     bones = enduser_obj.pose.bones
612     for bone in bones:
613         for constraint in bone.constraints:
614             if constraint.type != "IK":
615                 return True
616         if enduser_obj.data.animation_data:
617             if enduser_obj.data.animation_data.drivers:
618                 return True
619     
620 if __name__ == "__main__":
621     cProfile.run("profileWrapper()")