Additional work on animation stitching, now with auto-guess capability. Only a few...
authorBenjy Cook <benjycook@hotmail.com>
Thu, 11 Aug 2011 16:46:27 +0000 (16:46 +0000)
committerBenjy Cook <benjycook@hotmail.com>
Thu, 11 Aug 2011 16:46:27 +0000 (16:46 +0000)
release/scripts/modules/mocap_tools.py
release/scripts/modules/retarget.py
release/scripts/startup/ui_mocap.py

index 3f821270e3c66ad73efb127c6cc2f0495ec61ef7..f4b6a93f5316fbcb7b23682e3d7465a9de131b25 100644 (file)
@@ -105,64 +105,75 @@ class dataPoint:
         self.u = u
 
 
         self.u = u
 
 
-def autoloop_anim():
-    context = bpy.context
-    obj = context.active_object
-    fcurves = [x for x in obj.animation_data.action.fcurves if x.select]
-
-    data = []
-    end = len(fcurves[0].keyframe_points)
+def crossCorrelationMatch(curvesA, curvesB, margin):
+    dataA = []
+    dataB = []
+    end = len(curvesA[0].keyframe_points)
 
     for i in range(1, end):
         vec = []
 
     for i in range(1, end):
         vec = []
-        for fcurve in fcurves:
+        for fcurve in curvesA:
             vec.append(fcurve.evaluate(i))
             vec.append(fcurve.evaluate(i))
-        data.append(NdVector(vec))
+        dataA.append(NdVector(vec))
+        vec = []
+        for fcurve in curvesB:
+            vec.append(fcurve.evaluate(i))
+        dataB.append(NdVector(vec))
 
     def comp(a, b):
         return a * b
 
 
     def comp(a, b):
         return a * b
 
-    N = len(data)
+    N = len(dataA)
     Rxy = [0.0] * N
     for i in range(N):
         for j in range(i, min(i + N, N)):
     Rxy = [0.0] * N
     for i in range(N):
         for j in range(i, min(i + N, N)):
-            Rxy[i] += comp(data[j], data[j - i])
+            Rxy[i] += comp(dataA[j], dataB[j - i])
         for j in range(i):
         for j in range(i):
-            Rxy[i] += comp(data[j], data[j - i + N])
+            Rxy[i] += comp(dataA[j], dataB[j - i + N])
         Rxy[i] /= float(N)
         Rxy[i] /= float(N)
-
     def bestLocalMaximum(Rxy):
         Rxyd = [Rxy[i] - Rxy[i - 1] for i in range(1, len(Rxy))]
         maxs = []
         for i in range(1, len(Rxyd) - 1):
             a = Rxyd[i - 1]
             b = Rxyd[i]
     def bestLocalMaximum(Rxy):
         Rxyd = [Rxy[i] - Rxy[i - 1] for i in range(1, len(Rxy))]
         maxs = []
         for i in range(1, len(Rxyd) - 1):
             a = Rxyd[i - 1]
             b = Rxyd[i]
-            print(a, b)
             #sign change (zerocrossing) at point i, denoting max point (only)
             if (a >= 0 and b < 0) or (a < 0 and b >= 0):
                 maxs.append((i, max(Rxy[i], Rxy[i - 1])))
             #sign change (zerocrossing) at point i, denoting max point (only)
             if (a >= 0 and b < 0) or (a < 0 and b >= 0):
                 maxs.append((i, max(Rxy[i], Rxy[i - 1])))
-        return max(maxs, key=lambda x: x[1])[0]
-    flm = bestLocalMaximum(Rxy[0:int(len(Rxy))])
-
-    diff = []
-
-    for i in range(len(data) - flm):
-        diff.append((data[i] - data[i + flm]).lengthSq)
+        return [x[0] for x in maxs]
+        #~ return max(maxs, key=lambda x: x[1])[0]
+        
+    flms = bestLocalMaximum(Rxy[0:int(len(Rxy))])
+    ss = []
+    for flm in flms:
+        diff = []
+
+        for i in range(len(dataA) - flm):
+            diff.append((dataA[i] - dataB[i + flm]).lengthSq)
+
+        def lowerErrorSlice(diff, e):
+            #index, error at index
+            bestSlice = (0, 100000)
+            for i in range(e, len(diff) - e):
+                errorSlice = sum(diff[i - e:i + e + 1])
+                if errorSlice < bestSlice[1]:
+                    bestSlice = (i, errorSlice, flm)
+            return bestSlice
+            
+        s = lowerErrorSlice(diff, margin)
+        ss.append(s)
 
 
-    def lowerErrorSlice(diff, e):
-        #index, error at index
-        bestSlice = (0, 100000)
-        for i in range(e, len(diff) - e):
-            errorSlice = sum(diff[i - e:i + e + 1])
-            if errorSlice < bestSlice[1]:
-                bestSlice = (i, errorSlice)
-        return bestSlice[0]
+    ss.sort(key = lambda x: x[1])
+    return ss[0][2], ss[0][0], dataA
 
 
-    margin = 2
+def autoloop_anim():
+    context = bpy.context
+    obj = context.active_object
+    fcurves = [x for x in obj.animation_data.action.fcurves if x.select]
 
 
-    s = lowerErrorSlice(diff, margin)
+    margin = 10
 
 
-    print(flm, s)
+    flm, s, data = crossCorrelationMatch(fcurves, fcurves, margin)
     loop = data[s:s + flm + margin]
 
     #find *all* loops, s:s+flm, s+flm:s+2flm, etc...
     loop = data[s:s + flm + margin]
 
     #find *all* loops, s:s+flm, s+flm:s+2flm, etc...
@@ -824,3 +835,18 @@ def anim_stitch(context, enduser_obj):
                     pt.handle_left.y-=offset[i]
                     pt.handle_right.y-=offset[i]
 
                     pt.handle_left.y-=offset[i]
                     pt.handle_right.y-=offset[i]
 
+
+def guess_anim_stitch(context, enduser_obj):
+    stitch_settings = enduser_obj.data.stitch_settings
+    action_1 = stitch_settings.first_action
+    action_2 = stitch_settings.second_action
+    TrackNamesA = enduser_obj.data.mocapNLATracks[action_1]
+    TrackNamesB = enduser_obj.data.mocapNLATracks[action_2]
+    mocapA = bpy.data.actions[TrackNamesA.base_track]
+    mocapB = bpy.data.actions[TrackNamesB.base_track]
+    curvesA = mocapA.fcurves
+    curvesB = mocapB.fcurves
+    flm, s, data = crossCorrelationMatch(curvesA, curvesB, 10)
+    print(flm,s)
+    enduser_obj.data.stitch_settings.blend_frame = flm
+    enduser_obj.data.stitch_settings.second_offset = s
\ No newline at end of file
index 0235bfc147445e35fe0cec7bf267421129f32222..827d3d11ddc87c57702a887b869ce69976c15640 100644 (file)
@@ -305,6 +305,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
 
 
 def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
 
 
 def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
+    bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
     end_bones = enduser_obj.pose.bones
     for pose_bone in end_bones:
         ik_constraint = hasIKConstraint(pose_bone)
     end_bones = enduser_obj.pose.bones
     for pose_bone in end_bones:
         ik_constraint = hasIKConstraint(pose_bone)
@@ -313,9 +314,12 @@ def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
             # set constraint target to corresponding empty if targetless,
             # if not, keyframe current target to corresponding empty
             perf_bone = pose_bone.bone.reverseMap[-1].name
             # set constraint target to corresponding empty if targetless,
             # if not, keyframe current target to corresponding empty
             perf_bone = pose_bone.bone.reverseMap[-1].name
+            bpy.ops.object.mode_set(mode='EDIT')
             orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
             orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
+            bpy.ops.object.mode_set(mode='OBJECT')
             if not ik_constraint.target:
             if not ik_constraint.target:
-                ik_constraint.target = orgLocTrg
+                ik_constraint.target = enduser_obj
+                ik_constraint.subtarget = pose_bone.name+"IK"
                 target = orgLocTrg
 
             # There is a target now
                 target = orgLocTrg
 
             # There is a target now
@@ -337,6 +341,7 @@ def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
                 target.keyframe_insert("location")
             ik_constraint.mute = False
     scene.frame_set(s_frame)
                 target.keyframe_insert("location")
             ik_constraint.mute = False
     scene.frame_set(s_frame)
+    bpy.ops.object.mode_set(mode='OBJECT')
 
 
 def turnOffIK(enduser_obj):
 
 
 def turnOffIK(enduser_obj):
@@ -379,14 +384,17 @@ def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, str
 
 #create (or return if exists) the related IK empty to the bone
 def originalLocationTarget(end_bone, enduser_obj):
 
 #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
+    if not end_bone.name + "IK" in enduser_obj.data.bones:
+        newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
+        newBone.head = end_bone.tail
+        newBone.tail = end_bone.tail + Vector((0,0.1,0))
+        #~ empty = bpy.context.active_object
+        #~ empty.name = end_bone.name + "Org"
+        #~ empty.empty_draw_size = 0.1
+        #~ empty.parent = enduser_obj
+    else:
+        newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
+    return newBone
 
 
 #create the specified NLA setup for base animation, constraints and tweak layer.
 
 
 #create the specified NLA setup for base animation, constraints and tweak layer.
@@ -530,6 +538,7 @@ def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
     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)
     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)
+        bpy.ops.object.select_name(name=stride_bone.name, extend=False)
     restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone)
     bpy.ops.object.mode_set(mode='OBJECT')
     if not advanced:
     restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone)
     bpy.ops.object.mode_set(mode='OBJECT')
     if not advanced:
index dd5e6fa5d6d2f5ff2906f6f14d5dc6f01ce999de..19a96750e4948187c8965322f06f7fe6ffe4eef9 100644 (file)
@@ -382,6 +382,7 @@ class ExtraToolsPanel(bpy.types.Panel):
             layout.prop(settings, "blend_amount")
             layout.prop(settings, "second_offset")
             layout.prop_search(settings, "stick_bone", context.active_object.pose, "bones")
             layout.prop(settings, "blend_amount")
             layout.prop(settings, "second_offset")
             layout.prop_search(settings, "stick_bone", context.active_object.pose, "bones")
+            layout.operator('mocap.animstitchguess', text="Guess Settings")
             layout.operator('mocap.animstitch', text="Stitch Animations")
 
 
             layout.operator('mocap.animstitch', text="Stitch Animations")
 
 
@@ -765,6 +766,25 @@ class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator):
         return False
     
 
         return False
     
 
+class OBJECT_OT_GuessAnimationStitchingButton(bpy.types.Operator):
+    '''Guesses the stitch frame and second offset for animation stitch'''
+    bl_idname = "mocap.animstitchguess"
+    bl_label = "Guesses the stitch frame and second offset for animation stitch"
+
+    def execute(self, context):
+        mocap_tools.guess_anim_stitch(context, context.active_object)
+        return {"FINISHED"}
+
+    @classmethod
+    def poll(cls, context):
+        activeIsArmature = False
+        if context.active_object:
+            activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
+            if activeIsArmature:
+                stitch_settings = context.active_object.data.stitch_settings
+                return (stitch_settings.first_action and stitch_settings.second_action)
+        return False
+
 def register():
     bpy.utils.register_module(__name__)
 
 def register():
     bpy.utils.register_module(__name__)