Changed name of Mocap constraints to mocap fixes, for user clarity.
[blender.git] / release / scripts / startup / ui_mocap.py
index b09f9705a56d70ff5b34563957b4546e078e3b8a..044e13e81f58f4d5123743cc180f04a63e40e6dd 100644 (file)
@@ -40,8 +40,8 @@ from mocap_constraints import *
 
 class MocapConstraint(bpy.types.PropertyGroup):
     name = bpy.props.StringProperty(name="Name",
 
 class MocapConstraint(bpy.types.PropertyGroup):
     name = bpy.props.StringProperty(name="Name",
-        default="Mocap Constraint",
-        description="Name of Mocap Constraint",
+        default="Mocap Fix",
+        description="Name of Mocap Fix",
         update=setConstraint)
     constrained_bone = bpy.props.StringProperty(name="Bone",
         default="",
         update=setConstraint)
     constrained_bone = bpy.props.StringProperty(name="Bone",
         default="",
@@ -53,41 +53,40 @@ class MocapConstraint(bpy.types.PropertyGroup):
         update=setConstraint)
     s_frame = bpy.props.IntProperty(name="S",
         default=1,
         update=setConstraint)
     s_frame = bpy.props.IntProperty(name="S",
         default=1,
-        description="Start frame of constraint",
-        update=setConstraintFraming)
+        description="Start frame of Fix",
+        update=setConstraint)
     e_frame = bpy.props.IntProperty(name="E",
         default=500,
     e_frame = bpy.props.IntProperty(name="E",
         default=500,
-        description="End frame of constrain",
-        update=setConstraintFraming)
+        description="End frame of Fix",
+        update=setConstraint)
     smooth_in = bpy.props.IntProperty(name="In",
         default=10,
         description="Amount of frames to smooth in",
     smooth_in = bpy.props.IntProperty(name="In",
         default=10,
         description="Amount of frames to smooth in",
-        update=setConstraintFraming,
+        update=setConstraint,
         min=0)
     smooth_out = bpy.props.IntProperty(name="Out",
         default=10,
         description="Amount of frames to smooth out",
         min=0)
     smooth_out = bpy.props.IntProperty(name="Out",
         default=10,
         description="Amount of frames to smooth out",
-        update=setConstraintFraming,
+        update=setConstraint,
         min=0)
     targetMesh = bpy.props.StringProperty(name="Mesh",
         default="",
         min=0)
     targetMesh = bpy.props.StringProperty(name="Mesh",
         default="",
-        description="Target of Constraint - Mesh (optional, depends on type)",
+        description="Target of Fix - Mesh (optional, depends on type)",
         update=setConstraint)
     active = bpy.props.BoolProperty(name="Active",
         default=True,
         update=setConstraint)
     active = bpy.props.BoolProperty(name="Active",
         default=True,
-        description="Constraint is active",
+        description="Fix is active",
         update=setConstraint)
         update=setConstraint)
-    baked = bpy.props.BoolProperty(name="Baked / Applied",
-        default=False,
-        description="Constraint has been baked to NLA layer",
-        update=updateBake)
+    show_expanded = bpy.props.BoolProperty(name="Show Expanded",
+        default=True,
+        description="Fix is fully shown")
     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
         subtype="XYZ", default=(0.0, 0.0, 0.0),
     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
         subtype="XYZ", default=(0.0, 0.0, 0.0),
-        description="Target of Constraint - Point",
+        description="Target of Fix - Point",
         update=setConstraint)
         update=setConstraint)
-    targetDist = bpy.props.FloatProperty(name="Dist",
-        default=1,
-        description="Distance Constraint - Desired distance",
+    targetDist = bpy.props.FloatProperty(name="Offset",
+        default=0.0,
+        description="Distance and Floor Fixes - Desired offset",
         update=setConstraint)
     targetSpace = bpy.props.EnumProperty(
         items=[("WORLD", "World Space", "Evaluate target in global space"),
         update=setConstraint)
     targetSpace = bpy.props.EnumProperty(
         items=[("WORLD", "World Space", "Evaluate target in global space"),
@@ -101,7 +100,7 @@ class MocapConstraint(bpy.types.PropertyGroup):
             ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
             ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
             ("distance", "Maintain distance", "Target bones maintained specified distance")],
             ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
             ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
             ("distance", "Maintain distance", "Target bones maintained specified distance")],
-        description="Type of constraint",
+        description="Type of Fix",
         update=updateConstraintBoneType)
     real_constraint = bpy.props.StringProperty()
     real_constraint_bone = bpy.props.StringProperty()
         update=updateConstraintBoneType)
     real_constraint = bpy.props.StringProperty()
     real_constraint_bone = bpy.props.StringProperty()
@@ -195,20 +194,22 @@ class MocapPanel(bpy.types.Panel):
         row.operator("mocap.samples", text='Samples to Beziers')
         row.operator("mocap.denoise", text='Clean noise')
         row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
         row.operator("mocap.samples", text='Samples to Beziers')
         row.operator("mocap.denoise", text='Clean noise')
         row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
+        row.operator("mocap.scale_fix", text='Auto scale Performer')
         row2 = self.layout.row(align=True)
         row2.operator("mocap.looper", text='Loop animation')
         row2.operator("mocap.limitdof", text='Constrain Rig')
         self.layout.label("Retargeting")
         row2 = self.layout.row(align=True)
         row2.operator("mocap.looper", text='Loop animation')
         row2.operator("mocap.limitdof", text='Constrain Rig')
         self.layout.label("Retargeting")
-        row3 = self.layout.row(align=True)
-        column1 = row3.column(align=True)
-        column1.label("Performer Rig")
-        column2 = row3.column(align=True)
-        column2.label("Enduser Rig")
         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:
             self.layout.label("Select performer rig and target rig (as active)")
         else:
         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:
             self.layout.label("Select performer rig and target rig (as active)")
         else:
+            self.layout.operator("mocap.guessmapping", text="Guess Hiearchy Mapping")
+            row3 = self.layout.row(align=True)
+            column1 = row3.column(align=True)
+            column1.label("Performer Rig")
+            column2 = row3.column(align=True)
+            column2.label("Enduser Rig")
             performer_obj = performer_obj[0]
             if performer_obj.data and enduser_obj.data:
                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
             performer_obj = performer_obj[0]
             if performer_obj.data and enduser_obj.data:
                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
@@ -232,13 +233,15 @@ class MocapPanel(bpy.types.Panel):
                         else:
                             row.label(" ")
                             row.label(" ")
                         else:
                             row.label(" ")
                             row.label(" ")
-                    self.layout.operator("mocap.savemapping", text='Save mapping')
+                    mapRow = self.layout.row()
+                    mapRow.operator("mocap.savemapping", text='Save mapping')
+                    mapRow.operator("mocap.loadmapping", text='Load mapping')
                     self.layout.operator("mocap.retarget", text='RETARGET!')
 
 
 class MocapConstraintsPanel(bpy.types.Panel):
     #Motion capture constraints panel
                     self.layout.operator("mocap.retarget", text='RETARGET!')
 
 
 class MocapConstraintsPanel(bpy.types.Panel):
     #Motion capture constraints panel
-    bl_label = "Mocap constraints"
+    bl_label = "Mocap Fixes"
     bl_space_type = "PROPERTIES"
     bl_region_type = "WINDOW"
     bl_context = "object"
     bl_space_type = "PROPERTIES"
     bl_region_type = "WINDOW"
     bl_context = "object"
@@ -250,43 +253,49 @@ class MocapConstraintsPanel(bpy.types.Panel):
                 if context.active_object.data.name in bpy.data.armatures:
                     enduser_obj = context.active_object
                     enduser_arm = enduser_obj.data
                 if context.active_object.data.name in bpy.data.armatures:
                     enduser_obj = context.active_object
                     enduser_arm = enduser_obj.data
-                    layout.operator("mocap.addconstraint")
+                    layout.operator_menu_enum("mocap.addmocapfix", "type")
+                    bakeRow = layout.row()
+                    bakeRow.operator("mocap.bakeconstraints")
+                    bakeRow.operator("mocap.unbakeconstraints")
                     layout.separator()
                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
                         box = layout.box()
                     layout.separator()
                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
                         box = layout.box()
-                        box.prop(m_constraint, 'name')
-                        box.prop(m_constraint, 'type')
-                        box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones")
-                        if m_constraint.type == "distance" or m_constraint.type == "point":
-                            box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones")
-                        frameRow = box.row()
-                        frameRow.label("Frame Range:")
-                        frameRow.prop(m_constraint, 's_frame')
-                        frameRow.prop(m_constraint, 'e_frame')
-                        smoothRow = box.row()
-                        smoothRow.label("Smoothing:")
-                        smoothRow.prop(m_constraint, 'smooth_in')
-                        smoothRow.prop(m_constraint, 'smooth_out')
-                        targetRow = box.row()
-                        targetLabelCol = targetRow.column()
-                        targetLabelCol.label("Target settings:")
-                        targetPropCol = targetRow.column()
-                        if m_constraint.type == "floor":
-                            targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
-                        if m_constraint.type == "point" or m_constraint.type == "freeze":
-                            box.prop(m_constraint, 'targetSpace')
-                        if m_constraint.type == "point":
-                            targetPropCol.prop(m_constraint, 'targetPoint')
-                        if m_constraint.type == "distance":
-                            targetPropCol.prop(m_constraint, 'targetDist')
-                        checkRow = box.row()
-                        checkRow.prop(m_constraint, 'active')
-                        checkRow.prop(m_constraint, 'baked')
-                        layout.operator("mocap.removeconstraint", text="Remove constraint").constraint = i
-                        layout.separator()
+                        headerRow = box.row()
+                        headerRow.prop(m_constraint, 'show_expanded', text='', icon='TRIA_DOWN' if m_constraint.show_expanded else 'TRIA_RIGHT', emboss=False)
+                        headerRow.prop(m_constraint, 'type', text='')
+                        headerRow.prop(m_constraint, 'name', text='')
+                        headerRow.prop(m_constraint, 'active', icon='MUTE_IPO_ON' if m_constraint.active else'MUTE_IPO_OFF', text='', emboss=False)
+                        headerRow.operator("mocap.removeconstraint", text="", icon='X', emboss=False).constraint = i
+                        if m_constraint.show_expanded:
+                            box.separator()
+                            box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones", icon='BONE_DATA')
+                            if m_constraint.type == "distance" or m_constraint.type == "point":
+                                box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones", icon='CONSTRAINT_BONE')
+                            frameRow = box.row()
+                            frameRow.label("Frame Range:")
+                            frameRow.prop(m_constraint, 's_frame')
+                            frameRow.prop(m_constraint, 'e_frame')
+                            smoothRow = box.row()
+                            smoothRow.label("Smoothing:")
+                            smoothRow.prop(m_constraint, 'smooth_in')
+                            smoothRow.prop(m_constraint, 'smooth_out')
+                            targetRow = box.row()
+                            targetLabelCol = targetRow.column()
+                            targetLabelCol.label("Target settings:")
+                            targetPropCol = targetRow.column()
+                            if m_constraint.type == "floor":
+                                targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
+                            if m_constraint.type == "point" or m_constraint.type == "freeze":
+                                box.prop(m_constraint, 'targetSpace')
+                            if m_constraint.type == "point":
+                                targetPropCol.prop(m_constraint, 'targetPoint')
+                            if m_constraint.type == "distance" or m_constraint.type == "floor":
+                                targetPropCol.prop(m_constraint, 'targetDist')
+                            layout.separator()
 
 
 class OBJECT_OT_RetargetButton(bpy.types.Operator):
 
 
 class OBJECT_OT_RetargetButton(bpy.types.Operator):
+    '''Retarget animation from selected armature to active armature '''
     bl_idname = "mocap.retarget"
     bl_label = "Retargets active action from Performer to Enduser"
 
     bl_idname = "mocap.retarget"
     bl_label = "Retargets active action from Performer to Enduser"
 
@@ -315,6 +324,7 @@ class OBJECT_OT_RetargetButton(bpy.types.Operator):
 
 
 class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
 
 
 class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
+    '''Save mapping to active armature (for future retargets) '''
     bl_idname = "mocap.savemapping"
     bl_label = "Saves user generated mapping from Performer to Enduser"
 
     bl_idname = "mocap.savemapping"
     bl_label = "Saves user generated mapping from Performer to Enduser"
 
@@ -335,7 +345,30 @@ class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
             return False
 
 
             return False
 
 
+class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
+    '''Load saved mapping from active armature'''
+    bl_idname = "mocap.loadmapping"
+    bl_label = "Loads 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.loadMapping(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):
 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
+    '''Convert active armature's sampled keyframed to beziers'''
     bl_idname = "mocap.samples"
     bl_label = "Converts samples / simplifies keyframes to beziers"
 
     bl_idname = "mocap.samples"
     bl_label = "Converts samples / simplifies keyframes to beziers"
 
@@ -349,6 +382,7 @@ class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
 
 
 class OBJECT_OT_LooperButton(bpy.types.Operator):
 
 
 class OBJECT_OT_LooperButton(bpy.types.Operator):
+    '''Trim active armature's animation to a single cycle, given a cyclic animation (such as a walk cycle)'''
     bl_idname = "mocap.looper"
     bl_label = "loops animation / sampled mocap data"
 
     bl_idname = "mocap.looper"
     bl_label = "loops animation / sampled mocap data"
 
@@ -362,6 +396,7 @@ class OBJECT_OT_LooperButton(bpy.types.Operator):
 
 
 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
 
 
 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
+    '''Denoise active armature's animation. Good for dealing with 'bad' frames inherent in mocap animation'''
     bl_idname = "mocap.denoise"
     bl_label = "Denoises sampled mocap data "
 
     bl_idname = "mocap.denoise"
     bl_label = "Denoises sampled mocap data "
 
@@ -379,6 +414,7 @@ class OBJECT_OT_DenoiseButton(bpy.types.Operator):
 
 
 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
 
 
 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
+    '''UNIMPLEMENTED: Create limit constraints on the active armature from the selected armature's animation's range of motion'''
     bl_idname = "mocap.limitdof"
     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
 
     bl_idname = "mocap.limitdof"
     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
 
@@ -397,6 +433,7 @@ class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
 
 
 class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
 
 
 class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
+    '''Realign the active armature's axis system to match Blender (Commonly needed after bvh import)'''
     bl_idname = "mocap.rotate_fix"
     bl_label = "Rotates selected armature 90 degrees (fix for bvh import)"
 
     bl_idname = "mocap.rotate_fix"
     bl_label = "Rotates selected armature 90 degrees (fix for bvh import)"
 
@@ -410,14 +447,44 @@ class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
             return isinstance(context.active_object.data, bpy.types.Armature)
 
 
             return isinstance(context.active_object.data, bpy.types.Armature)
 
 
-class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
-    bl_idname = "mocap.addconstraint"
-    bl_label = "Add constraint to target armature"
+class OBJECT_OT_ScaleFixArmature(bpy.types.Operator):
+    '''Rescale selected armature to match the active animation, for convienence'''
+    bl_idname = "mocap.scale_fix"
+    bl_label = "Scales performer armature to match target armature"
+
+    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]
+        mocap_tools.scale_fix_armature(performer_obj, enduser_obj)
+        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 MOCAP_OT_AddMocapFix(bpy.types.Operator):
+    '''Add a post-retarget fix - useful for fixing certain artifacts following the retarget'''
+    bl_idname = "mocap.addmocapfix"
+    bl_label = "Add Mocap Fix to target armature"
+    type = bpy.props.EnumProperty(name="Type of Fix",
+    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"),
+        ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
+        ("distance", "Maintain distance", "Target bones maintained specified distance")],
+    description="Type of fix")
 
     def execute(self, context):
         enduser_obj = bpy.context.active_object
         enduser_arm = enduser_obj.data
         new_mcon = enduser_arm.mocap_constraints.add()
 
     def execute(self, context):
         enduser_obj = bpy.context.active_object
         enduser_arm = enduser_obj.data
         new_mcon = enduser_arm.mocap_constraints.add()
+        new_mcon.type = self.type
         return {"FINISHED"}
 
     @classmethod
         return {"FINISHED"}
 
     @classmethod
@@ -427,8 +494,9 @@ class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
 
 
 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
 
 
 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
+    '''Remove this post-retarget fix'''
     bl_idname = "mocap.removeconstraint"
     bl_idname = "mocap.removeconstraint"
-    bl_label = "Removes constraints from target armature"
+    bl_label = "Removes fixes from target armature"
     constraint = bpy.props.IntProperty()
 
     def execute(self, context):
     constraint = bpy.props.IntProperty()
 
     def execute(self, context):
@@ -449,6 +517,58 @@ class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
             return isinstance(context.active_object.data, bpy.types.Armature)
 
 
             return isinstance(context.active_object.data, bpy.types.Armature)
 
 
+class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
+    '''Bake all post-retarget fixes to the Retarget Fixes NLA Track'''
+    bl_idname = "mocap.bakeconstraints"
+    bl_label = "Bake all fixes to target armature"
+
+    def execute(self, context):
+        bakeConstraints(context)
+        return {"FINISHED"}
+
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            return isinstance(context.active_object.data, bpy.types.Armature)
+
+
+class OBJECT_OT_UnbakeMocapConstraints(bpy.types.Operator):
+    '''Unbake all post-retarget fixes - removes the baked data from the Retarget Fixes NLA Track'''
+    bl_idname = "mocap.unbakeconstraints"
+    bl_label = "Unbake all fixes to target armature"
+
+    def execute(self, context):
+        unbakeConstraints(context)
+        return {"FINISHED"}
+
+    @classmethod
+    def poll(cls, context):
+        if context.active_object:
+            return isinstance(context.active_object.data, bpy.types.Armature)
+
+
+class OBJECT_OT_GuessHierachyMapping(bpy.types.Operator):
+    '''Attemps to auto figure out hierarchy mapping'''
+    bl_idname = "mocap.guessmapping"
+    bl_label = "Attemps to auto figure out hierarchy mapping"
+
+    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]
+        mocap_tools.guessMapping(performer_obj, enduser_obj)
+        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
+
+
 def register():
     bpy.utils.register_module(__name__)
 
 def register():
     bpy.utils.register_module(__name__)