From f1a8c26aa3a8e3e3628d279ad32f3e1ea261a695 Mon Sep 17 00:00:00 2001 From: Benjy Cook Date: Thu, 11 Aug 2011 16:46:27 +0000 Subject: Additional work on animation stitching, now with auto-guess capability. Only a few bugs left, regarding animations translation --- release/scripts/modules/mocap_tools.py | 90 ++++++++++++++++++++++------------ release/scripts/modules/retarget.py | 27 ++++++---- release/scripts/startup/ui_mocap.py | 20 ++++++++ 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/release/scripts/modules/mocap_tools.py b/release/scripts/modules/mocap_tools.py index 3f821270e3c..f4b6a93f531 100644 --- a/release/scripts/modules/mocap_tools.py +++ b/release/scripts/modules/mocap_tools.py @@ -105,64 +105,75 @@ class dataPoint: 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 fcurve in fcurves: + for fcurve in curvesA: 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 - 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[i] += comp(data[j], data[j - i]) + Rxy[i] += comp(dataA[j], dataB[j - 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) - 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]))) - 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... @@ -824,3 +835,18 @@ def anim_stitch(context, enduser_obj): 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 diff --git a/release/scripts/modules/retarget.py b/release/scripts/modules/retarget.py index 0235bfc1474..827d3d11ddc 100644 --- a/release/scripts/modules/retarget.py +++ b/release/scripts/modules/retarget.py @@ -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): + 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) @@ -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 + bpy.ops.object.mode_set(mode='EDIT') orgLocTrg = originalLocationTarget(pose_bone, enduser_obj) + bpy.ops.object.mode_set(mode='OBJECT') 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 @@ -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) + bpy.ops.object.mode_set(mode='OBJECT') 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): - 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. @@ -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) + 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: diff --git a/release/scripts/startup/ui_mocap.py b/release/scripts/startup/ui_mocap.py index dd5e6fa5d6d..19a96750e49 100644 --- a/release/scripts/startup/ui_mocap.py +++ b/release/scripts/startup/ui_mocap.py @@ -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.operator('mocap.animstitchguess', text="Guess Settings") layout.operator('mocap.animstitch', text="Stitch Animations") @@ -765,6 +766,25 @@ class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator): 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__) -- cgit v1.2.3