Some optimizations and coding style improvements across the retargeting and constrain...
authorBenjy Cook <benjycook@hotmail.com>
Sat, 9 Jul 2011 21:52:25 +0000 (21:52 +0000)
committerBenjy Cook <benjycook@hotmail.com>
Sat, 9 Jul 2011 21:52:25 +0000 (21:52 +0000)
release/scripts/modules/mocap_constraints.py
release/scripts/modules/retarget.py
release/scripts/startup/ui_mocap.py

index 07ebb01ea3d5ea0ce153661affc916c1e272749e..f4d96d6a5d086adc367e9432bb6edd04d8890acd 100644 (file)
 import bpy
 from mathutils import *
 from bl_operators import nla
+from retarget import hasIKConstraint
 
 ### Utility Functions
 
 
-def hasIKConstraint(pose_bone):
-    #utility function / predicate, returns True if given bone has IK constraint
-    return ("IK" in [constraint.type for constraint in pose_bone.constraints])
-
-
 def getConsObj(bone):
     #utility function - returns related IK target if bone has IK
     ik = [constraint for constraint in bone.constraints if constraint.type == "IK"]
@@ -63,21 +59,18 @@ def addNewConstraint(m_constraint, cons_obj):
     real_constraint.name = "Mocap constraint " + str(len(cons_obj.constraints))
     m_constraint.real_constraint_bone = consObjToBone(cons_obj)
     m_constraint.real_constraint = real_constraint.name
-    setConstraint(m_constraint)
+    setConstraint(m_constraint, bpy.context)
 
 
 def removeConstraint(m_constraint, cons_obj):
     oldConstraint = cons_obj.constraints[m_constraint.real_constraint]
+    removeInfluenceFcurve(cons_obj, bpy.context.active_object, oldConstraint)
     cons_obj.constraints.remove(oldConstraint)
 
 ### Update functions. There are 2: UpdateType/UpdateBone
 ### and update for the others.
 
 
-def updateConstraint(self, context):
-    setConstraint(self)
-
-
 def updateConstraintBoneType(m_constraint, context):
     #If the constraint exists, we need to remove it
     #from the old bone
@@ -94,10 +87,24 @@ def updateConstraintBoneType(m_constraint, context):
         addNewConstraint(m_constraint, cons_obj)
 
 
-# Function that copies all settings from m_constraint to the real Blender constraints
-# Is only called when blender constraint already exists
+def setConstraintFraming(m_constraint, context):
+    obj = context.active_object
+    bones = obj.pose.bones
+    bone = bones[m_constraint.constrained_bone]
+    cons_obj = getConsObj(bone)
+    real_constraint = cons_obj.constraints[m_constraint.real_constraint]
+    removeInfluenceFcurve(cons_obj, obj, real_constraint)
+    s, e = m_constraint.s_frame, m_constraint.e_frame
+    s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
+    real_constraint.influence = 1
+    real_constraint.keyframe_insert(data_path="influence", frame=s)
+    real_constraint.keyframe_insert(data_path="influence", frame=e)
+    real_constraint.influence = 0
+    real_constraint.keyframe_insert(data_path="influence", frame=s - s_in)
+    real_constraint.keyframe_insert(data_path="influence", frame=e + s_out)
 
-def setConstraintFraming(m_constraint, cons_obj, obj, real_constraint):
+
+def removeInfluenceFcurve(cons_obj, obj, real_constraint):
     if isinstance(cons_obj, bpy.types.PoseBone):
         fcurves = obj.animation_data.action.fcurves
     else:
@@ -107,30 +114,24 @@ def setConstraintFraming(m_constraint, cons_obj, obj, real_constraint):
     fcurve = [fcurve for fcurve in fcurves if fcurve.data_path == influence_RNA]
     #clear the fcurve and set the frames.
     if fcurve:
-        fcurve = fcurve[0]
-        for i in range(len(fcurve.keyframe_points) - 1, 0, -1):
-            fcurve.keyframe_points.remove(fcurve.keyframe_points[i])
-    s, e = m_constraint.s_frame, m_constraint.e_frame
-    s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
-    real_constraint.influence = 1
-    real_constraint.keyframe_insert(data_path="influence", frame=s)
-    real_constraint.keyframe_insert(data_path="influence", frame=e)
-    real_constraint.influence = 0
-    real_constraint.keyframe_insert(data_path="influence", frame=s - s_in)
-    real_constraint.keyframe_insert(data_path="influence", frame=e + s_out)
+        fcurves.remove(fcurve[0])
+
+
+# Function that copies all settings from m_constraint to the real Blender constraints
+# Is only called when blender constraint already exists
 
 
-def setConstraint(m_constraint):
+def setConstraint(m_constraint, context):
     if not m_constraint.constrained_bone:
         return
-    obj = bpy.context.active_object
+    obj = context.active_object
     bones = obj.pose.bones
     bone = bones[m_constraint.constrained_bone]
     cons_obj = getConsObj(bone)
     real_constraint = cons_obj.constraints[m_constraint.real_constraint]
 
     #frame changing section
-    setConstraintFraming(m_constraint, cons_obj, obj, real_constraint)
+    #setConstraintFraming(m_constraint, cons_obj, obj, real_constraint)
 
     #Set the blender constraint parameters
     if m_constraint.type == "point":
@@ -176,17 +177,17 @@ def setConstraint(m_constraint):
         real_constraint.limit_mode = "LIMITDIST_ONSURFACE"
         real_constraint.distance = m_constraint.targetDist
 
-    # active check
+    # active/baked check
     real_constraint.mute = (not m_constraint.active) and (m_constraint.baked)
 
 
 def updateBake(self, context):
     if self.baked:
         print("baking...")
-        bakeConstraint(self)
+        bakeConstraint(self, context)
     else:
         print("unbaking...")
-        unbakeConstraint(self)
+        unbakeConstraint(self, context)
 
 
 def bakeTransformFK(anim_data, s_frame, e_frame, end_bone, bones, cons_obj):
@@ -210,14 +211,15 @@ def bakeTransformFK(anim_data, s_frame, e_frame, end_bone, bones, cons_obj):
     return mute_ik
 
 
-def bakeConstraint(m_constraint):
-    obj = bpy.context.active_object
+def bakeConstraint(m_constraint, context):
+    obj = context.active_object
     bones = obj.pose.bones
     end_bone = bones[m_constraint.constrained_bone]
     cons_obj = getConsObj(end_bone)
-    scene = bpy.context.scene
-    s_frame = scene.frame_start
-    e_frame = scene.frame_end
+    s, e = m_constraint.s_frame, m_constraint.e_frame
+    s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
+    s_frame = s - s_in
+    e_frame = e + s_out
     mute_ik = bakeTransformFK(obj.animation_data, s_frame, e_frame, end_bone, bones, cons_obj)
     if mute_ik:
         ik_con = hasIKConstraint(end_bone)
@@ -232,16 +234,14 @@ def bakeConstraint(m_constraint):
     constraintStrip.frame_end = e_frame
 
 
-def unbakeConstraint(m_constraint):
+def unbakeConstraint(m_constraint, context):
     # to unbake a constraint we need to delete the whole strip
     # and rebake all the other constraints
-    obj = bpy.context.active_object
+    obj = context.active_object
     bones = obj.pose.bones
     end_bone = bones[m_constraint.constrained_bone]
     cons_obj = getConsObj(end_bone)
     scene = bpy.context.scene
-    s_frame = scene.frame_start
-    e_frame = scene.frame_end
     constraintTrack = obj.animation_data.nla_tracks["Mocap constraints"]
     constraintStrip = constraintTrack.strips[0]
     action = constraintStrip.action
@@ -257,12 +257,3 @@ def unbakeConstraint(m_constraint):
             ik_con.mute = False
     real_constraint = cons_obj.constraints[m_constraint.real_constraint]
     real_constraint.mute = False
-
-
-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
index 885a457061aacdbb554463759165b4a5d93be3a7..ef1bc7a1488d7505c744cf2a94f60fb7fb42f787 100644 (file)
@@ -22,40 +22,33 @@ import bpy
 from mathutils import *
 from math import radians, acos
 
-#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):
-    bonemap = {}
-    #Bonemap: performer to enduser
-    for bone in perf_arm.bones:
-        bonemap[bone.name] = bone.map
-
-    # creation of a reverse map
-    # multiple keys get mapped to list values
-    #Bonemapr: enduser to performer
-    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)
+    # 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
+
     #root is the root of the enduser
     root = end_arm.bones[0].name
     feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
-    return bonemap, bonemapr, feetBones, root
+    return feetBones, root
 # list of empties created to keep track of "original"
 # position data
 # in final product, these locations can be stored as custom props
@@ -69,7 +62,7 @@ def createDictionary(perf_arm, end_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
@@ -96,22 +89,17 @@ 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):
-                pass
+        if inter_bone.bone.reverseMap:
+            perf_bone_name = inter_bone.bone.reverseMap
                 # 1 to many not supported yet
-            else:
                 # then its either a many to 1 or 1 to 1
-
-                if len(perf_bone_name) > 1:
-                    performer_bones_s = [performer_bones[name] for name in perf_bone_name]
-                    #we need to map several performance bone to a single
-                    inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
-                else:
-                    perf_bone = performer_bones[perf_bone_name[0]]
-                    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:
@@ -140,7 +128,7 @@ def createIntermediate(performer_obj, enduser_obj, bonemap, bonemapr, root, s_fr
         inter_bone = inter_bones[root]
         retargetPerfToInter(inter_bone)
 
-    return inter_obj, inter_arm
+    return inter_obj
 
 # this procedure copies the rotations over from the intermediate
 # armature to the end user one.
@@ -176,7 +164,13 @@ def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene):
         rest_matrix_inv.invert()
         bake_matrix = rest_matrix_inv * bake_matrix
         trg_bone.matrix_basis = bake_matrix
-        end_bone.keyframe_insert("rotation_quaternion")
+        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")
 
         for bone in end_bone.children:
             bakeTransform(bone)
@@ -193,13 +187,13 @@ 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):
+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 = bonemapr[root][0]
-    endFeet = [bonemap[perfBone] for perfBone in perfFeet]
+    perfRoot = end_bones[root].bone.reverseMap[0].name
+    endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
     locDictKeys = perfFeet + endFeet + [perfRoot]
 
     def tailLoc(bone):
@@ -208,7 +202,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, bonemap, bonemapr, roo
     #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:
@@ -231,10 +225,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
 
@@ -244,7 +235,7 @@ 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]
             if (v.length < 0.1):
                 #this is a plant frame.
                 #lets see what the original hip delta is, and the corresponding
@@ -268,18 +259,16 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, bonemap, bonemapr, roo
     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]
+            perf_bone = pose_bone.bone.reverseMap[-1].name
             orgLocTrg = originalLocationTarget(pose_bone)
-            ik_constraint = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"][0]
             if not ik_constraint.target:
                 ik_constraint.target = orgLocTrg
                 target = orgLocTrg
@@ -314,8 +303,8 @@ 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
 
 
@@ -350,45 +339,38 @@ def originalLocationTarget(end_bone):
     return empty
 
 
-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")
-    else:
-        performer_obj = performer_obj[0]
-    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, feetBones, root = createDictionary(perf_arm, end_arm)
-    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, feetBones, bonemap, bonemapr, root, s_frame, e_frame, scene, enduser_obj_mat)
-    IKRetarget(bonemap, bonemapr, 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()
+def NLASystemInitialize(enduser_obj, s_frame):
     anim_data = enduser_obj.animation_data
     mocapAction = anim_data.action
-    mocapAction.name = "Base Mocap Action"
+    mocapAction.name = "Base Mocap"
     anim_data.use_nla = True
     mocapTrack = anim_data.nla_tracks.new()
     mocapTrack.name = "Base Mocap Track"
-    mocapStrip = mocapTrack.strips.new("Base Mocap Action", s_frame, mocapAction)
+    mocapStrip = mocapTrack.strips.new("Base Mocap", s_frame, mocapAction)
     constraintTrack = anim_data.nla_tracks.new()
     constraintTrack.name = "Mocap constraints"
-    constraintAction = bpy.data.actions.new("Mocap constraints Action")
-    constraintStrip = constraintTrack.strips.new("Mocap constraints Action", s_frame, constraintAction)
-    #constraintStrip.frame_end = e_frame
+    constraintAction = bpy.data.actions.new("Mocap constraints")
+    constraintStrip = constraintTrack.strips.new("Mocap constraints", s_frame, constraintAction)
     anim_data.nla_tracks.active = constraintTrack
     anim_data.action = constraintAction
 
 
+def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
+    perf_arm = performer_obj.data
+    end_arm = enduser_obj.data
+    feetBones, root = createDictionary(perf_arm, end_arm)
+    perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
+    turnOffIK(enduser_obj)
+    inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene)
+    retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene)
+    stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
+    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()
+    NLASystemInitialize(enduser_obj, s_frame)
+
+
 if __name__ == "__main__":
     totalRetarget()
index d750489191fc022c3946163b8ac28eb2520b2a5e..b09f9705a56d70ff5b34563957b4546e078e3b8a 100644 (file)
@@ -42,7 +42,7 @@ class MocapConstraint(bpy.types.PropertyGroup):
     name = bpy.props.StringProperty(name="Name",
         default="Mocap Constraint",
         description="Name of Mocap Constraint",
-        update=updateConstraint)
+        update=setConstraint)
     constrained_bone = bpy.props.StringProperty(name="Bone",
         default="",
         description="Constrained Bone",
@@ -50,33 +50,33 @@ class MocapConstraint(bpy.types.PropertyGroup):
     constrained_boneB = bpy.props.StringProperty(name="Bone (2)",
         default="",
         description="Other Constrained Bone (optional, depends on type)",
-        update=updateConstraint)
+        update=setConstraint)
     s_frame = bpy.props.IntProperty(name="S",
         default=1,
         description="Start frame of constraint",
-        update=updateConstraint)
+        update=setConstraintFraming)
     e_frame = bpy.props.IntProperty(name="E",
         default=500,
         description="End frame of constrain",
-        update=updateConstraint)
+        update=setConstraintFraming)
     smooth_in = bpy.props.IntProperty(name="In",
         default=10,
         description="Amount of frames to smooth in",
-        update=updateConstraint,
+        update=setConstraintFraming,
         min=0)
     smooth_out = bpy.props.IntProperty(name="Out",
         default=10,
         description="Amount of frames to smooth out",
-        update=updateConstraint,
+        update=setConstraintFraming,
         min=0)
     targetMesh = bpy.props.StringProperty(name="Mesh",
         default="",
         description="Target of Constraint - Mesh (optional, depends on type)",
-        update=updateConstraint)
+        update=setConstraint)
     active = bpy.props.BoolProperty(name="Active",
         default=True,
         description="Constraint is active",
-        update=updateConstraint)
+        update=setConstraint)
     baked = bpy.props.BoolProperty(name="Baked / Applied",
         default=False,
         description="Constraint has been baked to NLA layer",
@@ -84,18 +84,18 @@ class MocapConstraint(bpy.types.PropertyGroup):
     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
         subtype="XYZ", default=(0.0, 0.0, 0.0),
         description="Target of Constraint - Point",
-        update=updateConstraint)
+        update=setConstraint)
     targetDist = bpy.props.FloatProperty(name="Dist",
         default=1,
         description="Distance Constraint - Desired distance",
-        update=updateConstraint)
+        update=setConstraint)
     targetSpace = bpy.props.EnumProperty(
         items=[("WORLD", "World Space", "Evaluate target in global space"),
             ("LOCAL", "Object space", "Evaluate target in object space"),
             ("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
         name="Space",
         description="In which space should Point type target be evaluated",
-        update=updateConstraint)
+        update=setConstraint)
     type = bpy.props.EnumProperty(name="Type of constraint",
         items=[("point", "Maintain Position", "Bone is at a specific point"),
             ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
@@ -148,7 +148,14 @@ def toggleIKBone(self, context):
                 if not bone.is_in_ik_chain:
                     bone.IKRetarget = False
 
+
+class MocapMapping(bpy.types.PropertyGroup):
+    name = bpy.props.StringProperty()
+
+bpy.utils.register_class(MocapMapping)
+
 bpy.types.Bone.map = bpy.props.StringProperty()
+bpy.types.Bone.reverseMap = bpy.props.CollectionProperty(type=MocapMapping)
 bpy.types.Bone.foot = bpy.props.BoolProperty(name="Foot",
     description="Marks this bone as a 'foot', which determines retargeted animation's translation",
     default=False)
@@ -225,6 +232,7 @@ class MocapPanel(bpy.types.Panel):
                         else:
                             row.label(" ")
                             row.label(" ")
+                    self.layout.operator("mocap.savemapping", text='Save mapping')
                     self.layout.operator("mocap.retarget", text='RETARGET!')
 
 
@@ -283,9 +291,49 @@ class OBJECT_OT_RetargetButton(bpy.types.Operator):
     bl_label = "Retargets active action from Performer to Enduser"
 
     def execute(self, context):
-        retarget.totalRetarget()
+        enduser_obj = context.active_object
+        performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
+        if enduser_obj is None or len(performer_obj) != 1:
+            print("Need active and selected armatures")
+        else:
+            performer_obj = performer_obj[0]
+        scene = context.scene
+        s_frame = scene.frame_start
+        e_frame = scene.frame_end
+        retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
+        return {"FINISHED"}
+
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
+        performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
+        if performer_obj:
+            return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
+        else:
+            return False
+
+
+class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
+    bl_idname = "mocap.savemapping"
+    bl_label = "Saves user generated mapping from Performer to Enduser"
+
+    def execute(self, context):
+        enduser_obj = bpy.context.active_object
+        performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
+        retarget.createDictionary(performer_obj.data, enduser_obj.data)
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
+        performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
+        if performer_obj:
+            return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
+        else:
+            return False
+
 
 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
     bl_idname = "mocap.samples"
@@ -295,6 +343,10 @@ class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
         mocap_tools.fcurves_simplify()
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        return context.active_object.animation_data
+
 
 class OBJECT_OT_LooperButton(bpy.types.Operator):
     bl_idname = "mocap.looper"
@@ -304,6 +356,10 @@ class OBJECT_OT_LooperButton(bpy.types.Operator):
         mocap_tools.autoloop_anim()
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        return context.active_object.animation_data
+
 
 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
     bl_idname = "mocap.denoise"
@@ -313,6 +369,14 @@ class OBJECT_OT_DenoiseButton(bpy.types.Operator):
         mocap_tools.denoise_median()
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        return context.active_object
+
+    @classmethod
+    def poll(cls, context):
+        return context.active_object.animation_data
+
 
 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
     bl_idname = "mocap.limitdof"
@@ -321,6 +385,16 @@ class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
     def execute(self, context):
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
+        performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
+        if performer_obj:
+            return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
+        else:
+            return False
+
 
 class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
     bl_idname = "mocap.rotate_fix"
@@ -330,8 +404,10 @@ class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
         mocap_tools.rotate_fix_armature(context.active_object.data)
         return {"FINISHED"}
 
-    #def poll(self, context):
-      #  return context.active_object.data in bpy.data.armatures
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            return isinstance(context.active_object.data, bpy.types.Armature)
 
 
 class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
@@ -344,6 +420,11 @@ class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
         new_mcon = enduser_arm.mocap_constraints.add()
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            return isinstance(context.active_object.data, bpy.types.Armature)
+
 
 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
     bl_idname = "mocap.removeconstraint"
@@ -362,6 +443,11 @@ class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
         m_constraints.remove(self.constraint)
         return {"FINISHED"}
 
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            return isinstance(context.active_object.data, bpy.types.Armature)
+
 
 def register():
     bpy.utils.register_module(__name__)