forked from bartvdbraak/blender
Additional work on animation stitching, now with auto-guess capability. Only a few bugs left, regarding animations translation
This commit is contained in:
parent
05b7ccb736
commit
f1a8c26aa3
@ -105,64 +105,75 @@ class dataPoint:
|
|||||||
self.u = u
|
self.u = u
|
||||||
|
|
||||||
|
|
||||||
def autoloop_anim():
|
def crossCorrelationMatch(curvesA, curvesB, margin):
|
||||||
context = bpy.context
|
dataA = []
|
||||||
obj = context.active_object
|
dataB = []
|
||||||
fcurves = [x for x in obj.animation_data.action.fcurves if x.select]
|
end = len(curvesA[0].keyframe_points)
|
||||||
|
|
||||||
data = []
|
|
||||||
end = len(fcurves[0].keyframe_points)
|
|
||||||
|
|
||||||
for i in range(1, end):
|
for i in range(1, end):
|
||||||
vec = []
|
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):
|
def comp(a, b):
|
||||||
return a * b
|
return a * b
|
||||||
|
|
||||||
N = len(data)
|
N = len(dataA)
|
||||||
Rxy = [0.0] * N
|
Rxy = [0.0] * N
|
||||||
for i in range(N):
|
for i in range(N):
|
||||||
for j in range(i, min(i + N, 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):
|
def bestLocalMaximum(Rxy):
|
||||||
Rxyd = [Rxy[i] - Rxy[i - 1] for i in range(1, len(Rxy))]
|
Rxyd = [Rxy[i] - Rxy[i - 1] for i in range(1, len(Rxy))]
|
||||||
maxs = []
|
maxs = []
|
||||||
for i in range(1, len(Rxyd) - 1):
|
for i in range(1, len(Rxyd) - 1):
|
||||||
a = Rxyd[i - 1]
|
a = Rxyd[i - 1]
|
||||||
b = Rxyd[i]
|
b = Rxyd[i]
|
||||||
print(a, b)
|
|
||||||
#sign change (zerocrossing) at point i, denoting max point (only)
|
#sign change (zerocrossing) at point i, denoting max point (only)
|
||||||
if (a >= 0 and b < 0) or (a < 0 and b >= 0):
|
if (a >= 0 and b < 0) or (a < 0 and b >= 0):
|
||||||
maxs.append((i, max(Rxy[i], Rxy[i - 1])))
|
maxs.append((i, max(Rxy[i], Rxy[i - 1])))
|
||||||
return max(maxs, key=lambda x: x[1])[0]
|
return [x[0] for x in maxs]
|
||||||
flm = bestLocalMaximum(Rxy[0:int(len(Rxy))])
|
#~ return max(maxs, key=lambda x: x[1])[0]
|
||||||
|
|
||||||
|
flms = bestLocalMaximum(Rxy[0:int(len(Rxy))])
|
||||||
|
ss = []
|
||||||
|
for flm in flms:
|
||||||
|
diff = []
|
||||||
|
|
||||||
diff = []
|
for i in range(len(dataA) - flm):
|
||||||
|
diff.append((dataA[i] - dataB[i + flm]).lengthSq)
|
||||||
|
|
||||||
for i in range(len(data) - flm):
|
def lowerErrorSlice(diff, e):
|
||||||
diff.append((data[i] - data[i + flm]).lengthSq)
|
#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):
|
ss.sort(key = lambda x: x[1])
|
||||||
#index, error at index
|
return ss[0][2], ss[0][0], dataA
|
||||||
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]
|
|
||||||
|
|
||||||
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]
|
loop = data[s:s + flm + margin]
|
||||||
|
|
||||||
#find *all* loops, s:s+flm, s+flm:s+2flm, etc...
|
#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_left.y-=offset[i]
|
||||||
pt.handle_right.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
|
@ -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
|
end_bones = enduser_obj.pose.bones
|
||||||
for pose_bone in end_bones:
|
for pose_bone in end_bones:
|
||||||
ik_constraint = hasIKConstraint(pose_bone)
|
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,
|
# set constraint target to corresponding empty if targetless,
|
||||||
# if not, keyframe current target to corresponding empty
|
# if not, keyframe current target to corresponding empty
|
||||||
perf_bone = pose_bone.bone.reverseMap[-1].name
|
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
|
target = orgLocTrg
|
||||||
|
|
||||||
# There is a target now
|
# There is a target now
|
||||||
@ -337,6 +341,7 @@ def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene):
|
|||||||
target.keyframe_insert("location")
|
target.keyframe_insert("location")
|
||||||
ik_constraint.mute = False
|
ik_constraint.mute = False
|
||||||
scene.frame_set(s_frame)
|
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
|
#create (or return if exists) the related IK empty to the bone
|
||||||
def originalLocationTarget(end_bone, enduser_obj):
|
def originalLocationTarget(end_bone, enduser_obj):
|
||||||
if not end_bone.name + "Org" in bpy.data.objects:
|
if not end_bone.name + "IK" in enduser_obj.data.bones:
|
||||||
bpy.ops.object.add()
|
newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
|
||||||
empty = bpy.context.active_object
|
newBone.head = end_bone.tail
|
||||||
empty.name = end_bone.name + "Org"
|
newBone.tail = end_bone.tail + Vector((0,0.1,0))
|
||||||
empty.empty_draw_size = 0.1
|
#~ empty = bpy.context.active_object
|
||||||
empty.parent = enduser_obj
|
#~ empty.name = end_bone.name + "Org"
|
||||||
empty = bpy.data.objects[end_bone.name + "Org"]
|
#~ empty.empty_draw_size = 0.1
|
||||||
return empty
|
#~ 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)
|
stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
|
||||||
if not advanced:
|
if not advanced:
|
||||||
IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene)
|
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)
|
restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone)
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
if not advanced:
|
if not advanced:
|
||||||
|
@ -382,6 +382,7 @@ class ExtraToolsPanel(bpy.types.Panel):
|
|||||||
layout.prop(settings, "blend_amount")
|
layout.prop(settings, "blend_amount")
|
||||||
layout.prop(settings, "second_offset")
|
layout.prop(settings, "second_offset")
|
||||||
layout.prop_search(settings, "stick_bone", context.active_object.pose, "bones")
|
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():
|
def register():
|
||||||
bpy.utils.register_module(__name__)
|
bpy.utils.register_module(__name__)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user