Bugfix: Baking mocap constraints now works for user created IK bones
authorBenjy Cook <benjycook@hotmail.com>
Thu, 14 Jul 2011 13:26:23 +0000 (13:26 +0000)
committerBenjy Cook <benjycook@hotmail.com>
Thu, 14 Jul 2011 13:26:23 +0000 (13:26 +0000)
release/scripts/modules/mocap_constraints.py
release/scripts/modules/retarget.py
release/scripts/startup/ui_mocap.py

index f4d96d6a5d086adc367e9432bb6edd04d8890acd..56589403ce2d64f5aa1b97cccad4bfd542a5b252 100644 (file)
@@ -40,6 +40,7 @@ def getConsObj(bone):
 
 
 def consObjToBone(cons_obj):
+    #Utility function - returns related bone from ik object
     if cons_obj.name[-3:] == "Org":
         return cons_obj.name[:-3]
     else:
@@ -49,26 +50,31 @@ def consObjToBone(cons_obj):
 
 
 def addNewConstraint(m_constraint, cons_obj):
+     #Decide the correct Blender constraint according to the Mocap constraint type
     if m_constraint.type == "point" or m_constraint.type == "freeze":
         c_type = "LIMIT_LOCATION"
     if m_constraint.type == "distance":
         c_type = "LIMIT_DISTANCE"
     if m_constraint.type == "floor":
         c_type = "FLOOR"
+        #create and store the new constraint within m_constraint
     real_constraint = cons_obj.constraints.new(c_type)
     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
+    #set the rest of the constraint properties
     setConstraint(m_constraint, bpy.context)
 
 
 def removeConstraint(m_constraint, cons_obj):
+    #remove the influence fcurve and Blender constraint
     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.
+### Update functions. There are 3: UpdateType/Bone
+### update framing (deals with changes in the desired frame range)
+### And setConstraint which deals with the rest
 
 
 def updateConstraintBoneType(m_constraint, context):
@@ -93,7 +99,9 @@ def setConstraintFraming(m_constraint, context):
     bone = bones[m_constraint.constrained_bone]
     cons_obj = getConsObj(bone)
     real_constraint = cons_obj.constraints[m_constraint.real_constraint]
+    #remove the old keyframes
     removeInfluenceFcurve(cons_obj, obj, real_constraint)
+    #set the new ones according to the m_constraint properties
     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
@@ -105,12 +113,14 @@ def setConstraintFraming(m_constraint, context):
 
 
 def removeInfluenceFcurve(cons_obj, obj, real_constraint):
+    #Determine if the constrained object is a bone or an empty
     if isinstance(cons_obj, bpy.types.PoseBone):
         fcurves = obj.animation_data.action.fcurves
     else:
         fcurves = cons_obj.animation_data.action.fcurves
-
+    #Find the RNA data path of the constraint's influence
     influence_RNA = real_constraint.path_from_id("influence")
+    #Retrieve the correct fcurve via the RNA data path and remove it
     fcurve = [fcurve for fcurve in fcurves if fcurve.data_path == influence_RNA]
     #clear the fcurve and set the frames.
     if fcurve:
@@ -131,7 +141,7 @@ def setConstraint(m_constraint, context):
     real_constraint = cons_obj.constraints[m_constraint.real_constraint]
 
     #frame changing section
-    #setConstraintFraming(m_constraint, cons_obj, obj, real_constraint)
+    setConstraintFraming(m_constraint, context)
 
     #Set the blender constraint parameters
     if m_constraint.type == "point":
@@ -154,7 +164,7 @@ def setConstraint(m_constraint, context):
         real_constraint.owner_space = m_constraint.targetSpace
         bpy.context.scene.frame_set(m_constraint.s_frame)
         if isinstance(cons_obj, bpy.types.PoseBone):
-            x, y, z = cons_obj.center + (cons_obj.vector / 2)
+            x, y, z = cons_obj.bone.center + (cons_obj.bone.vector / 2) + obj.matrix_world.to_translation()
         else:
             x, y, z = cons_obj.matrix_world.to_translation()
 
@@ -178,82 +188,100 @@ def setConstraint(m_constraint, context):
         real_constraint.distance = m_constraint.targetDist
 
     # active/baked check
-    real_constraint.mute = (not m_constraint.active) and (m_constraint.baked)
-
+    real_constraint.mute = (not m_constraint.active)
 
-def updateBake(self, context):
-    if self.baked:
-        print("baking...")
-        bakeConstraint(self, context)
-    else:
-        print("unbaking...")
-        unbakeConstraint(self, context)
 
-
-def bakeTransformFK(anim_data, s_frame, e_frame, end_bone, bones, cons_obj):
-    mute_ik = False
+def locBake(s_frame, e_frame, bones):
+    scene = bpy.context.scene
+    bakeDict = {}
+    for bone in bones:
+        bakeDict[bone.name] = {}
+    for t in range(s_frame, e_frame):
+        scene.frame_set(t)
+        for bone in bones:
+            bakeDict[bone.name][t] = bone.matrix.copy()
+    for t in range(s_frame, e_frame):
+        for bone in bones:
+            print(bone.bone.matrix_local.to_translation())
+            bone.matrix = bakeDict[bone.name][t]
+            bone.keyframe_insert("location", frame=t)
+
+
+# Baking function which bakes all bones effected by the constraint
+def bakeAllConstraints(obj, s_frame, e_frame, bones):
     for bone in bones:
         bone.bone.select = False
-    ik = hasIKConstraint(end_bone)
-    if not isinstance(cons_obj, bpy.types.PoseBone) and ik:
-            if ik.chain_count == 0:
-                selectedBones = bones
+    selectedBones = []  # Marks bones that need a full bake
+    simpleBake = []  # Marks bones that need only a location bake
+    for end_bone in bones:
+        if end_bone.name in [m_constraint.real_constraint_bone for m_constraint in obj.data.mocap_constraints]:
+            #For all bones that have a constraint:
+            ik = hasIKConstraint(end_bone)
+            cons_obj = getConsObj(end_bone)
+            if ik:
+                    #If it's an auto generated IK:
+                    if ik.chain_count == 0:
+                        selectedBones += bones  # Chain len 0, bake everything
+                    else:
+                        selectedBones += [end_bone] + end_bone.parent_recursive[:ik.chain_count - 1]  # Bake the chain
             else:
-                selectedBones = [end_bone] + end_bone.parent_recursive[:ik.chain_count - 1]
-            mute_ik = True
-    else:
-        selectedBones = [end_bone]
-    print(selectedBones)
+                #It's either an FK bone which we should just bake
+                #OR a user created IK target bone
+                simpleBake += [end_bone]
     for bone in selectedBones:
         bone.bone.select = True
-    anim_data.action = nla.bake(s_frame,
-        e_frame, action=anim_data.action)
-    return mute_ik
-
-
-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)
-    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)
-        ik_con.mute = True
-    real_constraint = cons_obj.constraints[m_constraint.real_constraint]
-    real_constraint.mute = True
     constraintTrack = obj.animation_data.nla_tracks["Mocap constraints"]
     constraintStrip = constraintTrack.strips[0]
     constraintStrip.action_frame_start = s_frame
     constraintStrip.action_frame_end = e_frame
     constraintStrip.frame_start = s_frame
     constraintStrip.frame_end = e_frame
+    if selectedBones:
+        #Use bake function from NLA Bake Action operator
+        nla.bake(s_frame, e_frame, action=constraintStrip.action, only_selected=True, do_pose=True, do_object=False)
+    if simpleBake:
+        #Do a "simple" bake, location only, world space only.
+        locBake(s_frame, e_frame, simpleBake)
 
 
-def unbakeConstraint(m_constraint, context):
-    # to unbake a constraint we need to delete the whole strip
-    # and rebake all the other constraints
+#Calls the baking function and decativates releveant constraints
+def bakeConstraints(context):
+    obj = context.active_object
+    bones = obj.pose.bones
+    s_frame, e_frame = context.scene.frame_start, context.scene.frame_end
+    #Bake relevant bones
+    bakeAllConstraints(obj, s_frame, e_frame, bones)
+    for m_constraint in obj.data.mocap_constraints:
+        end_bone = bones[m_constraint.real_constraint_bone]
+        cons_obj = getConsObj(end_bone)
+        # It's a control empty: turn the ik off
+        if not isinstance(cons_obj, bpy.types.PoseBone):
+            ik_con = hasIKConstraint(end_bone)
+            if ik_con:
+                ik_con.mute = True
+        # Deactivate related Blender Constraint
+        m_constraint.active = False
+
+
+#Deletes the baked fcurves and reactivates relevant constraints
+def unbakeConstraints(context):
+    # to unbake constraints we delete the whole strip
     obj = context.active_object
     bones = obj.pose.bones
-    end_bone = bones[m_constraint.constrained_bone]
-    cons_obj = getConsObj(end_bone)
     scene = bpy.context.scene
     constraintTrack = obj.animation_data.nla_tracks["Mocap constraints"]
     constraintStrip = constraintTrack.strips[0]
     action = constraintStrip.action
+    # delete the fcurves on the strip
     for fcurve in action.fcurves:
         action.fcurves.remove(fcurve)
-    for other_m_constraint in obj.data.mocap_constraints:
-        if m_constraint != other_m_constraint:
-            bakeConstraint(other_m_constraint)
-    # It's a control empty: turn the ik back on
-    if not isinstance(cons_obj, bpy.types.PoseBone):
-        ik_con = hasIKConstraint(end_bone)
-        if ik_con:
-            ik_con.mute = False
-    real_constraint = cons_obj.constraints[m_constraint.real_constraint]
-    real_constraint.mute = False
+    # reactivate relevant constraints
+    for m_constraint in obj.data.mocap_constraints:
+        end_bone = bones[m_constraint.real_constraint_bone]
+        cons_obj = getConsObj(end_bone)
+        # It's a control empty: turn the ik back on
+        if not isinstance(cons_obj, bpy.types.PoseBone):
+            ik_con = hasIKConstraint(end_bone)
+            if ik_con:
+                ik_con.mute = False
+        m_constraint.active = True
index ef1bc7a1488d7505c744cf2a94f60fb7fb42f787..f8d424fbb5a579a2189129774bbd93f370128c14 100644 (file)
@@ -49,10 +49,15 @@ def createDictionary(perf_arm, end_arm):
     root = end_arm.bones[0].name
     feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
     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
-# these help with constraining, etc.
+
+
+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,
@@ -248,11 +253,13 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
     stride_bone.name = "stride_bone"
 
     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)
         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.keyframe_insert("location")
@@ -281,6 +288,7 @@ def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
             else:
                 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:
@@ -308,15 +316,17 @@ def turnOffIK(enduser_obj):
             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:
@@ -328,6 +338,7 @@ def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, str
     enduser_obj.parent = stride_bone
 
 
+#create (or return if exists) the related IK empty to the bone
 def originalLocationTarget(end_bone):
     if not end_bone.name + "Org" in bpy.data.objects:
         bpy.ops.object.add()
@@ -339,6 +350,7 @@ def originalLocationTarget(end_bone):
     return empty
 
 
+#create the specified NLA setup for base animation, constraints and tweak layer.
 def NLASystemInitialize(enduser_obj, s_frame):
     anim_data = enduser_obj.animation_data
     mocapAction = anim_data.action
@@ -351,10 +363,13 @@ def NLASystemInitialize(enduser_obj, s_frame):
     constraintTrack.name = "Mocap constraints"
     constraintAction = bpy.data.actions.new("Mocap constraints")
     constraintStrip = constraintTrack.strips.new("Mocap constraints", s_frame, constraintAction)
+    constraintStrip.extrapolation = "NOTHING"
     anim_data.nla_tracks.active = constraintTrack
     anim_data.action = constraintAction
+    anim_data.action_extrapolation = "NOTHING"
 
 
+#Main function that runs the retargeting sequence.
 def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
     perf_arm = performer_obj.data
     end_arm = enduser_obj.data
index b09f9705a56d70ff5b34563957b4546e078e3b8a..9a36f076eceae23c53e009108350c5fa8eb0808d 100644 (file)
@@ -54,20 +54,20 @@ class MocapConstraint(bpy.types.PropertyGroup):
     s_frame = bpy.props.IntProperty(name="S",
         default=1,
         description="Start frame of constraint",
-        update=setConstraintFraming)
+        update=setConstraint)
     e_frame = bpy.props.IntProperty(name="E",
         default=500,
         description="End frame of constrain",
-        update=setConstraintFraming)
+        update=setConstraint)
     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",
-        update=setConstraintFraming,
+        update=setConstraint,
         min=0)
     targetMesh = bpy.props.StringProperty(name="Mesh",
         default="",
@@ -77,10 +77,6 @@ class MocapConstraint(bpy.types.PropertyGroup):
         default=True,
         description="Constraint is active",
         update=setConstraint)
-    baked = bpy.props.BoolProperty(name="Baked / Applied",
-        default=False,
-        description="Constraint has been baked to NLA layer",
-        update=updateBake)
     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
         subtype="XYZ", default=(0.0, 0.0, 0.0),
         description="Target of Constraint - Point",
@@ -232,7 +228,9 @@ class MocapPanel(bpy.types.Panel):
                         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!')
 
 
@@ -251,6 +249,8 @@ class MocapConstraintsPanel(bpy.types.Panel):
                     enduser_obj = context.active_object
                     enduser_arm = enduser_obj.data
                     layout.operator("mocap.addconstraint")
+                    layout.operator("mocap.bakeconstraints")
+                    layout.operator("mocap.unbakeconstraints")
                     layout.separator()
                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
                         box = layout.box()
@@ -281,7 +281,6 @@ class MocapConstraintsPanel(bpy.types.Panel):
                             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()
 
@@ -335,6 +334,27 @@ class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
             return False
 
 
+class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
+    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):
     bl_idname = "mocap.samples"
     bl_label = "Converts samples / simplifies keyframes to beziers"
@@ -449,6 +469,34 @@ class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
             return isinstance(context.active_object.data, bpy.types.Armature)
 
 
+class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
+    bl_idname = "mocap.bakeconstraints"
+    bl_label = "Bake all constraints 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):
+    bl_idname = "mocap.unbakeconstraints"
+    bl_label = "Unbake all constraints 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)
+
+
 def register():
     bpy.utils.register_module(__name__)