Advanced Retargeting option: If the end user armature is complex, on the level of...
[blender.git] / release / scripts / modules / retarget.py
index 9a3ed4b70cbd1fe1f0bd004f49ac458285fc772c..88a5c3a620a297720d8ce0b52dff6526acb8bd28 100644 (file)
 import bpy
 from mathutils import *
 from math import radians, acos
+from bl_operators import nla
+import cProfile
 
-#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 willW
-# be created from a more comfortable UI in the future
+def hasIKConstraint(pose_bone):
+    #utility function / predicate, returns True if given bone has IK constraint
+    ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
+    if ik:
+        return ik[0]
+    else:
+        return False
+
+
+def createDictionary(perf_arm, end_arm):
+    # clear any old data
+    for end_bone in end_arm.bones:
+        for mapping in end_bone.reverseMap:
+            end_bone.reverseMap.remove(0)
 
+    for perf_bone in perf_arm.bones:
+        #find its match and add perf_bone to the match's mapping
+        if perf_bone.map:
+            end_bone = end_arm.bones[perf_bone.map]
+            newMap = end_bone.reverseMap.add()
+            newMap.name = perf_bone.name
+            end_bone.foot = perf_bone.foot
 
-def createDictionary(perf_arm):
-    bonemap = {}
-    for bone in perf_arm.bones:
-        bonemap[bone.name] = bone.map
     #root is the root of the enduser
-    root = "root"
-    # creation of a reverse map
-    # multiple keys get mapped to list values
-    bonemapr = {}
-    for key, value in bonemap.items():
-        if not value in bonemapr:
-            if isinstance(bonemap[key], tuple):
-                for key_x in bonemap[key]:
-                    bonemapr[key_x] = [key]
-            else:
-                bonemapr[bonemap[key]] = [key]
-        else:
-            bonemapr[bonemap[key]].append(key)
-    return bonemap, bonemapr, root
-# 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.
+    root = end_arm.bones[0].name
+    feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
+    return feetBones, root
+
+def loadMapping(perf_arm, end_arm):
+
+    for end_bone in end_arm.bones:
+        #find its match and add perf_bone to the match's mapping
+        if end_bone.reverseMap:
+            for perf_bone in end_bone.reverseMap:
+                perf_arm.bones[perf_bone.name].map = end_bone.name
 
 #creation of intermediate armature
 # the intermediate armature has the hiearchy of the end user,
@@ -65,27 +69,11 @@ def createDictionary(perf_arm):
 # easily while concentrating on the hierarchy changes
 
 
-def createIntermediate(performer_obj, enduser_obj, bonemap, bonemapr, root, s_frame, e_frame, scene):
+def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene):
     #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 / DEBUG
-    def locOfOriginal(inter_bone, perf_bone):
-        if not inter_bone.name + "Org" in bpy.data.objects:
-            bpy.ops.object.add()
-            empty = bpy.context.active_object
-            empty.name = inter_bone.name + "Org"
-            empty.empty_draw_size = 0.1
-            #empty.parent = enduser_obj
-        empty = bpy.data.objects[inter_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):
@@ -108,30 +96,18 @@ def createIntermediate(performer_obj, enduser_obj, bonemap, bonemapr, root, s_fr
     #determines the type of hierachy change needed and calls the
     #right function
     def retargetPerfToInter(inter_bone):
-        if inter_bone.name in bonemapr:
-            perf_bone_name = bonemapr[inter_bone.name]
-            #is it a 1 to many?
-            if isinstance(bonemap[perf_bone_name[0]], tuple):
-                perf_bone = performer_bones[perf_bone_name[0]]
-                if inter_bone.name == bonemap[perf_bone_name[0]][0]:
-                    locOfOriginal(inter_bone, perf_bone)
-            else:
+        if inter_bone.bone.reverseMap:
+            perf_bone_name = inter_bone.bone.reverseMap
+                # 1 to many not supported yet
                 # 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)
-
+            if len(perf_bone_name) > 1:
+                performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
+                #we need to map several performance bone to a single
+                inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
+            else:
+                perf_bone = performer_bones[perf_bone_name[0].name]
+                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
     inter_obj = enduser_obj.copy()
@@ -140,7 +116,20 @@ def createIntermediate(performer_obj, enduser_obj, bonemap, bonemapr, root, s_fr
     inter_obj.name = "intermediate"
     bpy.context.scene.objects.active = inter_obj
     bpy.ops.object.mode_set(mode='EDIT')
+    #add some temporary connecting bones in case end user bones are not connected to their parents
+    print("creating temp bones")
+    for bone in inter_obj.data.edit_bones:
+        if not bone.use_connect and bone.parent:
+            if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
+                newBone = inter_obj.data.edit_bones.new("Temp")
+                newBone.head = bone.parent.tail
+                newBone.tail = bone.head
+                newBone.parent = bone.parent
+                bone.parent = newBone
+                bone.use_connect = True
+                newBone.use_connect = True
     #resets roll
+    print("retargeting to intermediate")
     bpy.ops.armature.calculate_roll(type='Z')
     bpy.ops.object.mode_set(mode="OBJECT")
     inter_obj.data.name = "inter_arm"
@@ -149,14 +138,19 @@ def createIntermediate(performer_obj, enduser_obj, bonemap, bonemapr, root, s_fr
     inter_bones = inter_obj.pose.bones
     #clears inheritance
     for inter_bone in inter_bones:
-        inter_bone.bone.use_inherit_rotation = False
+        if inter_bone.bone.reverseMap:
+            inter_bone.bone.use_inherit_rotation = False
+        else:
+            inter_bone.bone.use_inherit_rotation = True
 
     for t in range(s_frame, e_frame):
+        if (t - s_frame) % 10 == 0:
+            print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
         scene.frame_set(t)
-        inter_bone = inter_bones[root]
-        retargetPerfToInter(inter_bone)
+        for bone in inter_bones:
+            retargetPerfToInter(bone)
 
-    return inter_obj, inter_arm
+    return inter_obj
 
 # this procedure copies the rotations over from the intermediate
 # armature to the end user one.
@@ -179,7 +173,10 @@ def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene):
         rest_matrix = trg_bone.bone.matrix_local
 
         if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
-            parent_mat = src_bone.parent.matrix
+            srcParent = src_bone.parent
+            if "Temp" in srcParent.name:
+                srcParent = srcParent.parent
+            parent_mat = srcParent.matrix
             parent_rest = trg_bone.parent.bone.matrix_local
             parent_rest_inv = parent_rest.copy()
             parent_rest_inv.invert()
@@ -191,16 +188,26 @@ def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene):
         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")
+        end_bone.matrix_basis = bake_matrix
+        rot_mode = end_bone.rotation_mode
+        if rot_mode == "QUATERNION":
+            end_bone.keyframe_insert("rotation_quaternion")
+        elif rot_mode == "AXIS_ANGLE":
+            end_bone.keyframe_insert("rotation_axis_angle")
+        else:
+            end_bone.keyframe_insert("rotation_euler")
+        if not end_bone.bone.use_connect:
+            end_bone.keyframe_insert("location")
 
         for bone in end_bone.children:
             bakeTransform(bone)
 
     for t in range(s_frame, e_frame):
+        if (t - s_frame) % 10 == 0:
+            print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
         scene.frame_set(t)
         end_bone = end_bones[root]
-        end_bone.location = Vector((0,0,0))
+        end_bone.location = Vector((0, 0, 0))
         end_bone.keyframe_insert("location")
         bakeTransform(end_bone)
 
@@ -209,20 +216,22 @@ def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene):
 # (they don't move, despite root moving) somewhere in the animation.
 
 
-def copyTranslation(performer_obj, enduser_obj, perfFeet, bonemap, bonemapr, root, s_frame, e_frame, scene, enduser_obj_mat):
-    endFeet = [bonemap[perfBone] for perfBone in perfFeet]
-    perfRoot = bonemapr[root][0]
-    locDictKeys = perfFeet + endFeet + [perfRoot]
+def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
+
     perf_bones = performer_obj.pose.bones
     end_bones = enduser_obj.pose.bones
 
+    perfRoot = perf_bones[0].name
+    endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
+    locDictKeys = perfFeet + endFeet + [perfRoot]
+
     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
+    # where the values are their world position on each frame in range (s,e)
 
     locDict = {}
     for key in locDictKeys:
@@ -245,10 +254,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, bonemap, bonemapr, roo
 
     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)
+        locDeriv[key] = [graph[t + 1] - graph[t] for t in range(len(graph) - 1)]
 
     # now find the plant frames, where perfFeet don't move much
 
@@ -258,7 +264,8 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, bonemap, bonemapr, roo
         for i in range(len(locDeriv[key]) - 1):
             v = locDeriv[key][i]
             hipV = locDeriv[perfRoot][i]
-            endV = locDeriv[bonemap[key]][i]
+            endV = locDeriv[perf_bones[key].bone.map][i]
+            print(v.length,)
             if (v.length < 0.1):
                 #this is a plant frame.
                 #lets see what the original hip delta is, and the corresponding
@@ -266,53 +273,71 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, bonemap, bonemapr, roo
                 if endV.length != 0:
                     linearAvg.append(hipV.length / endV.length)
 
-    bpy.ops.object.add()
-    stride_bone = bpy.context.active_object
-    stride_bone.name = "stride_bone"
-
+    action_name = performer_obj.animation_data.action.name
+    #is there a stride_bone?
+    if "stride_bone" in bpy.data.objects:
+        stride_action = bpy.data.actions.new("Stride Bone " + action_name)
+        stride_action.use_fake_user = True
+        stride_bone = enduser_obj.parent
+        stride_bone.animation_data.action = stride_action
+    else:
+        bpy.ops.object.add()
+        stride_bone = bpy.context.active_object
+        stride_bone.name = "stride_bone"
+    print(stride_bone)
+    stride_bone.location = Vector((0, 0, 0))
+    print(linearAvg)
     if linearAvg:
+        #determine the average change in scale needed
         avg = sum(linearAvg) / len(linearAvg)
         scene.frame_set(s_frame)
-        initialPos = (tailLoc(perf_bones[perfRoot]) / avg)
+        initialPos = (tailLoc(perf_bones[perfRoot]) / avg) + stride_bone.location
         for t in range(s_frame, e_frame):
             scene.frame_set(t)
+            #calculate the new position, by dividing by the found ratio between performer and enduser
             newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
-            stride_bone.location = (newTranslation - initialPos) * enduser_obj_mat
+            stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
             stride_bone.keyframe_insert("location")
+    else:
+        stride_bone.keyframe_insert("location")
+    stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
+
     return stride_bone
 
 
-def IKRetarget(bonemap, bonemapr, performer_obj, enduser_obj, s_frame, e_frame, scene):
+def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
     end_bones = enduser_obj.pose.bones
     for pose_bone in end_bones:
-        if "IK" in [constraint.type for constraint in pose_bone.constraints]:
+        ik_constraint = hasIKConstraint(pose_bone)
+        if ik_constraint:
+            target_is_bone = False
             # set constraint target to corresponding empty if targetless,
             # if not, keyframe current target to corresponding empty
-            perf_bone = bonemapr[pose_bone.name]
-            if isinstance(perf_bone, list):
-                perf_bone = bonemapr[pose_bone.name][-1]
-            end_empty = bpy.data.objects[pose_bone.name + "Org"]
-            ik_constraint = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"][0]
+            perf_bone = pose_bone.bone.reverseMap[-1].name
+            orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
             if not ik_constraint.target:
-                ik_constraint.target = end_empty
+                ik_constraint.target = orgLocTrg
+                target = orgLocTrg
+
+            # There is a target now
+            if ik_constraint.subtarget:
+                target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
+                target.bone.use_local_location = False
+                target_is_bone = True
             else:
-                #Bone target
-                target_is_bone = False
-                if ik_constraint.subtarget:
-                    target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
-                    target.bone.use_local_location = False
-                    target_is_bone = True
+                target = ik_constraint.target
+
+            # bake the correct locations for the ik target bones
+            for t in range(s_frame, e_frame):
+                scene.frame_set(t)
+                if target_is_bone:
+                    final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
                 else:
-                    target = ik_constraint.target
-                for t in range(s_frame, e_frame):
-                    scene.frame_set(t)
-                    if target_is_bone:
-                        final_loc = end_empty.location - target.bone.matrix_local.to_translation()
-                    else:
-                        final_loc = end_empty.location
-                    target.location = final_loc
-                    target.keyframe_insert("location")
+                    final_loc = pose_bone.tail
+                target.location = final_loc
+                target.keyframe_insert("location")
             ik_constraint.mute = False
+    scene.frame_set(s_frame)
 
 
 def turnOffIK(enduser_obj):
@@ -326,20 +351,22 @@ def turnOffIK(enduser_obj):
             #pose_bone.ik_stiffness_x = 0.5
             #pose_bone.ik_stiffness_y = 0.5
             #pose_bone.ik_stiffness_z = 0.5
-        if "IK" in [constraint.type for constraint in pose_bone.constraints]:
-            ik_constraint = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"][0]
+        ik_constraint = hasIKConstraint(pose_bone)
+        if ik_constraint:
             ik_constraint.mute = True
 
 
+#copy the object matrixes and clear them (to be reinserted later)
 def cleanAndStoreObjMat(performer_obj, enduser_obj):
     perf_obj_mat = performer_obj.matrix_world.copy()
     enduser_obj_mat = enduser_obj.matrix_world.copy()
-    zero_mat = Matrix()  # Matrix(((0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0)))
+    zero_mat = Matrix()
     performer_obj.matrix_world = zero_mat
     enduser_obj.matrix_world = zero_mat
     return perf_obj_mat, enduser_obj_mat
 
 
+#restore the object matrixes after parenting the auto generated IK empties
 def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone):
     pose_bones = enduser_obj.pose.bones
     for pose_bone in pose_bones:
@@ -347,34 +374,179 @@ def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, str
             empty = bpy.data.objects[pose_bone.name + "Org"]
             empty.parent = stride_bone
     performer_obj.matrix_world = perf_obj_mat
-    enduser_obj.matrix_world = enduser_obj_mat
     enduser_obj.parent = stride_bone
+    enduser_obj.matrix_world = enduser_obj_mat
 
 
-def totalRetarget():
-    print("retargeting...")
-    enduser_obj = bpy.context.active_object
-    performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
-    if enduser_obj is None or len(performer_obj) != 1:
-        print("Need active and selected armatures")
+#create (or return if exists) the related IK empty to the bone
+def originalLocationTarget(end_bone, enduser_obj):
+    if not end_bone.name + "Org" in bpy.data.objects:
+        bpy.ops.object.add()
+        empty = bpy.context.active_object
+        empty.name = end_bone.name + "Org"
+        empty.empty_draw_size = 0.1
+        empty.parent = enduser_obj
+    empty = bpy.data.objects[end_bone.name + "Org"]
+    return empty
+
+
+#create the specified NLA setup for base animation, constraints and tweak layer.
+def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
+    enduser_obj = context.active_object
+    NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
+    name = NLATracks.name
+    anim_data = enduser_obj.animation_data
+    s_frame = 0
+    print(name)
+    if ("Base " + name) in bpy.data.actions:
+        mocapAction = bpy.data.actions[("Base " + name)]
+    else:
+        print("That retargeted anim has no base action")
+    anim_data.use_nla = True
+    for track in anim_data.nla_tracks:
+        anim_data.nla_tracks.remove(track)
+    mocapTrack = anim_data.nla_tracks.new()
+    mocapTrack.name = "Base " + name
+    NLATracks.base_track = mocapTrack.name
+    mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
+    constraintTrack = anim_data.nla_tracks.new()
+    constraintTrack.name = "Auto fixes " + name
+    NLATracks.auto_fix_track = constraintTrack.name
+    if ("Auto fixes " + name) in bpy.data.actions:
+        constraintAction = bpy.data.actions[("Auto fixes " + name)]
     else:
-        performer_obj = performer_obj[0]
+        constraintAction = bpy.data.actions.new("Auto fixes " + name)
+        constraintAction.use_fake_user = True
+    constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
+    constraintStrip.extrapolation = "NOTHING"
+    userTrack = anim_data.nla_tracks.new()
+    userTrack.name = "Manual fixes " + name
+    NLATracks.manual_fix_track = userTrack.name
+    if ("Manual fixes " + name) in bpy.data.actions:
+        userAction = bpy.data.actions[("Manual fixes " + name)]
+    else:
+        userAction = bpy.data.actions.new("Manual fixes " + name)
+        userAction.use_fake_user = True
+    userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
+    userStrip.extrapolation = "HOLD"
+    #userStrip.blend_type = "MULITPLY" - doesn't work due to work, will be activated soon
+    anim_data.nla_tracks.active = constraintTrack
+    #anim_data.action = constraintAction
+    anim_data.action_extrapolation = "NOTHING"
+    #set the stride_bone's action
+    if "stride_bone" in bpy.data.objects:
+        stride_bone = bpy.data.objects["stride_bone"]
+        if NLATracks.stride_action:
+            stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
+        else:
+            NLATracks.stride_action = stride_bone.animation_data.action.name
+            stride_bone.animation_data.action.use_fake_user = True
+    anim_data.action = None
+
+
+def preAdvancedRetargeting(performer_obj, enduser_obj):
+    createDictionary(performer_obj.data, enduser_obj.data)
+    bones = enduser_obj.pose.bones
+    map_bones = [bone for bone in bones if bone.bone.reverseMap]
+    for bone in map_bones:
+        perf_bone = bone.bone.reverseMap[0].name
+        addLocalRot = False;
+        if bone.bone.use_connect or not bone.constraints:
+            locks = bone.lock_location
+            if not (locks[0] or locks[1] or locks[2]):  
+                cons = bone.constraints.new('COPY_LOCATION')
+                cons.name = "retargetTemp"
+                cons.use_x = not locks[0]
+                cons.use_y = not locks[1]
+                cons.use_z = not locks[2]
+                cons.target = performer_obj
+                cons.subtarget = perf_bone
+                addLocalRot = True
+
+       
+        cons2 = bone.constraints.new('COPY_ROTATION')
+        cons2.name = "retargetTemp"
+        locks = bone.lock_rotation
+        cons2.use_x = not locks[0]
+        cons2.use_y = not locks[1]
+        cons2.use_z = not locks[2]
+        cons2.target = performer_obj
+        cons2.subtarget = perf_bone
+
+        if addLocalRot:
+            for constraint in bone.constraints:
+                if constraint.type == 'COPY_ROTATION':
+                    constraint.target_space = 'LOCAL'
+                    constraint.owner_space = 'LOCAL_WITH_PARENT'
+
+
+def prepareForBake(enduser_obj):
+    bones = enduser_obj.pose.bones
+    for bone in bones:
+        bone.bone.select = False
+    map_bones = [bone for bone in bones if bone.bone.reverseMap]
+    for bone in map_bones:
+        for cons in bone.constraints:
+            if "retargetTemp" in cons.name:
+                bone.bone.select = True
+
+def cleanTempConstraints(enduser_obj):
+    bones = enduser_obj.pose.bones
+    map_bones = [bone for bone in bones if bone.bone.reverseMap]
+    for bone in map_bones:
+        for cons in bone.constraints:
+            if "retargetTemp" in cons.name:
+                bone.constraints.remove(cons)
+
+#Main function that runs the retargeting sequence.
+#If advanced == True, we assume constraint's were already created
+def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
     perf_arm = performer_obj.data
     end_arm = enduser_obj.data
-    scene = bpy.context.scene
-    s_frame = scene.frame_start
-    e_frame = scene.frame_end
-    bonemap, bonemapr, root = createDictionary(perf_arm)
+    advanced = end_arm.advancedRetarget
+    
+    try:
+        enduser_obj.animation_data.action = bpy.data.actions.new("temp")
+        enduser_obj.animation_data.action.use_fake_user = True
+    except:
+        print("no need to create new action")
+    
+    print("creating Dictionary")
+    feetBones, root = createDictionary(perf_arm, end_arm)
+    print("cleaning stuff up")
     perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
-    turnOffIK(enduser_obj)
-    inter_obj, inter_arm = createIntermediate(performer_obj, enduser_obj, bonemap, bonemapr, root, s_frame, e_frame, scene)
-    retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene)
-    stride_bone = copyTranslation(performer_obj, enduser_obj, ["RightFoot", "LeftFoot"], bonemap, bonemapr, root, s_frame, e_frame, scene,enduser_obj_mat)
-    IKRetarget(bonemap, bonemapr, performer_obj, enduser_obj, s_frame, e_frame, scene)
+    if not advanced:
+        turnOffIK(enduser_obj)
+        print("Creating intermediate armature (for first pass)")
+        inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene)
+        print("First pass: retargeting from intermediate to end user")
+        retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene)
+    else:
+        prepareForBake(enduser_obj)
+        print("Retargeting pose (Advanced Retarget)")
+        nla.bake(s_frame, e_frame, action=enduser_obj.animation_data.action, only_selected=True, do_pose=True, do_object=False)
+    name = performer_obj.animation_data.action.name
+    enduser_obj.animation_data.action.name = "Base " + name
+    print("Second pass: retargeting root translation and clean up")
+    stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
+    if not advanced:
+        IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene)
     restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone)
     bpy.ops.object.mode_set(mode='OBJECT')
-    bpy.ops.object.select_name(name=inter_obj.name, extend=False)
-    bpy.ops.object.delete()
+    if not advanced:
+        bpy.ops.object.select_name(name=inter_obj.name, extend=False)
+        bpy.ops.object.delete()
+    else:
+        cleanTempConstraints(enduser_obj)
+    bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
+
+    if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
+        NLATracks = end_arm.mocapNLATracks.add()
+        NLATracks.name = name
+    else:
+        NLATracks = end_arm.mocapNLATracks[name]
+    end_arm.active_mocap = name
+    print("retargeting done!")
 
 if __name__ == "__main__":
-    totalRetarget()
\ No newline at end of file
+    totalRetarget()