diff options
Diffstat (limited to 'release/scripts/modules/mocap_tools.py')
-rw-r--r-- | release/scripts/modules/mocap_tools.py | 904 |
1 files changed, 0 insertions, 904 deletions
diff --git a/release/scripts/modules/mocap_tools.py b/release/scripts/modules/mocap_tools.py deleted file mode 100644 index 6c22f718296..00000000000 --- a/release/scripts/modules/mocap_tools.py +++ /dev/null @@ -1,904 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# <pep8 compliant> - -from math import hypot, sqrt, isfinite, radians, pi -import bpy -import time -from mathutils import Vector, Matrix - - -# A Python implementation of n sized Vectors. -# Mathutils has a max size of 4, and we need at least 5 for Simplify Curves and even more for Cross Correlation. -# Vector utility functions -class NdVector: - vec = [] - - def __init__(self, vec): - self.vec = vec[:] - - def __len__(self): - return len(self.vec) - - def __mul__(self, otherMember): - if (isinstance(otherMember, int) or - isinstance(otherMember, float)): - return NdVector([otherMember * x for x in self.vec]) - else: - a = self.vec - b = otherMember.vec - n = len(self) - return sum([a[i] * b[i] for i in range(n)]) - - def __sub__(self, otherVec): - a = self.vec - b = otherVec.vec - n = len(self) - return NdVector([a[i] - b[i] for i in range(n)]) - - def __add__(self, otherVec): - a = self.vec - b = otherVec.vec - n = len(self) - return NdVector([a[i] + b[i] for i in range(n)]) - - def __div__(self, scalar): - return NdVector([x / scalar for x in self.vec]) - - def vecLength(self): - return sqrt(self * self) - - def vecLengthSq(self): - return (self * self) - - def normalize(self): - len = self.length - self.vec = [x / len for x in self.vec] - - def copy(self): - return NdVector(self.vec) - - def __getitem__(self, i): - return self.vec[i] - - def x(self): - return self.vec[0] - - def y(self): - return self.vec[1] - - def resize_2d(self): - return Vector((self.x, self.y)) - - length = property(vecLength) - lengthSq = property(vecLengthSq) - x = property(x) - y = property(y) - - -#Sampled Data Point class for Simplify Curves -class dataPoint: - index = 0 - # x,y1,y2,y3 coordinate of original point - co = NdVector((0, 0, 0, 0, 0)) - #position according to parametric view of original data, [0,1] range - u = 0 - #use this for anything - temp = 0 - - def __init__(self, index, co, u=0): - self.index = index - self.co = co - self.u = u - - -#Cross Correlation Function -#http://en.wikipedia.org/wiki/Cross_correlation -#IN: curvesA, curvesB - bpy_collection/list of fcurves to analyze. Auto-Correlation is when they are the same. -# margin - When searching for the best "start" frame, how large a neighborhood of frames should we inspect (similar to epsilon in Calculus) -#OUT: startFrame, length of new anim, and curvesA -def crossCorrelationMatch(curvesA, curvesB, margin): - dataA = [] - dataB = [] - start, end = curvesA[0].range() - start = int(start) - end = int(end) - - #transfer all fcurves data on each frame to a single NdVector. - for i in range(1, end): - vec = [] - for fcurve in curvesA: - vec.append(fcurve.evaluate(i)) - dataA.append(NdVector(vec)) - vec = [] - for fcurve in curvesB: - vec.append(fcurve.evaluate(i)) - dataB.append(NdVector(vec)) - - #Comparator for Cross Correlation. "Classic" implementation uses dot product, as do we. - def comp(a, b): - return a * b - - #Create Rxy, which holds the Cross Correlation 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(dataA[j], dataB[j - i]) - for j in range(i): - Rxy[i] += comp(dataA[j], dataB[j - i + N]) - Rxy[i] /= float(N) - - #Find the Local maximums in the Cross Correlation data via numerical derivative. - def LocalMaximums(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] - #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 [x[0] for x in maxs] - #~ return max(maxs, key=lambda x: x[1])[0] - - #flms - the possible offsets of the first part of the animation. In Auto-Corr, this is the length of the loop. - flms = LocalMaximums(Rxy[0:int(len(Rxy))]) - ss = [] - - #for every local maximum, find the best one - i.e. also has the best start frame. - 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) - - #Find the best result and return it. - ss.sort(key=lambda x: x[1]) - return ss[0][2], ss[0][0], dataA - - -#Uses auto correlation (cross correlation of the same set of curves) and trims the active_object's fcurves -#Except for location curves (which in mocap tend to be not cyclic, e.g. a walk cycle forward) -#Transfers the fcurve data to a list of NdVector (length of list is number of fcurves), and calls the cross correlation function. -#Then trims the fcurve accordingly. -#IN: Nothing, set the object you want as active and call. Assumes object has animation_data.action! -#OUT: Trims the object's fcurves (except location curves). -def autoloop_anim(): - context = bpy.context - obj = context.active_object - - def locCurve(x): - x.data_path == "location" - - fcurves = [x for x in obj.animation_data.action.fcurves if not locCurve(x)] - - margin = 10 - - flm, s, data = crossCorrelationMatch(fcurves, fcurves, margin) - loop = data[s:s + flm] - - #performs blending with a root falloff on the seam's neighborhood to ensure good tiling. - for i in range(1, margin + 1): - w1 = sqrt(float(i) / margin) - loop[-i] = (loop[-i] * w1) + (loop[0] * (1 - w1)) - - for curve in fcurves: - pts = curve.keyframe_points - for i in range(len(pts) - 1, -1, -1): - pts.remove(pts[i]) - - for c, curve in enumerate(fcurves): - pts = curve.keyframe_points - for i in range(len(loop)): - pts.insert(i + 2, loop[i][c]) - - context.scene.frame_end = flm - - -#simplifyCurves: performes the bulk of the samples to bezier conversion. -#IN: curveGroup - which can be a collection of singleFcurves, or grouped (via nested lists) . -# error - threshold of permittable error (max distance) of the new beziers to the original data -# reparaError - threshold of error where we should try to fix the parameterization rather than split the existing curve. > error, usually by a small constant factor for best performance. -# maxIterations - maximum number of iterations of reparameterizations we should attempt. (Newton-Rahpson is not guarenteed to converge, so this is needed). -# group_mode - boolean, indicating wether we should place bezier keyframes on the same x (frame), or optimize each individual curve. -#OUT: None. Deletes the existing curves and creates the new beziers. -def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode): - - #Calculates the unit tangent of point v - def unitTangent(v, data_pts): - tang = NdVector((0, 0, 0, 0, 0)) - if v != 0: - #If it's not the first point, we can calculate a leftside tangent - tang += data_pts[v].co - data_pts[v - 1].co - if v != len(data_pts) - 1: - #If it's not the last point, we can calculate a rightside tangent - tang += data_pts[v + 1].co - data_pts[v].co - tang.normalize() - return tang - - #assign parametric u value for each point in original data, via relative arc length - #http://en.wikipedia.org/wiki/Arc_length - def chordLength(data_pts, s, e): - totalLength = 0 - for pt in data_pts[s:e + 1]: - i = pt.index - if i == s: - chordLength = 0 - else: - chordLength = (data_pts[i].co - data_pts[i - 1].co).length - totalLength += chordLength - pt.temp = totalLength - for pt in data_pts[s:e + 1]: - if totalLength == 0: - print(s, e) - pt.u = (pt.temp / totalLength) - - # get binomial coefficient lookup table, this function/table is only called with args - # (3,0),(3,1),(3,2),(3,3),(2,0),(2,1),(2,2)! - binomDict = {(3, 0): 1, - (3, 1): 3, - (3, 2): 3, - (3, 3): 1, - (2, 0): 1, - (2, 1): 2, - (2, 2): 1} - - #value at pt t of a single bernstein Polynomial - def bernsteinPoly(n, i, t): - binomCoeff = binomDict[(n, i)] - return binomCoeff * pow(t, i) * pow(1 - t, n - i) - - # fit a single cubic to data points in range [s(tart),e(nd)]. - def fitSingleCubic(data_pts, s, e): - - # A - matrix used for calculating C matrices for fitting - def A(i, j, s, e, t1, t2): - if j == 1: - t = t1 - if j == 2: - t = t2 - u = data_pts[i].u - return t * bernsteinPoly(3, j, u) - - # X component, used for calculating X matrices for fitting - def xComponent(i, s, e): - di = data_pts[i].co - u = data_pts[i].u - v0 = data_pts[s].co - v3 = data_pts[e].co - a = v0 * bernsteinPoly(3, 0, u) - b = v0 * bernsteinPoly(3, 1, u) - c = v3 * bernsteinPoly(3, 2, u) - d = v3 * bernsteinPoly(3, 3, u) - return (di - (a + b + c + d)) - - t1 = unitTangent(s, data_pts) - t2 = unitTangent(e, data_pts) - c11 = sum([A(i, 1, s, e, t1, t2) * A(i, 1, s, e, t1, t2) for i in range(s, e + 1)]) - c12 = sum([A(i, 1, s, e, t1, t2) * A(i, 2, s, e, t1, t2) for i in range(s, e + 1)]) - c21 = c12 - c22 = sum([A(i, 2, s, e, t1, t2) * A(i, 2, s, e, t1, t2) for i in range(s, e + 1)]) - - x1 = sum([xComponent(i, s, e) * A(i, 1, s, e, t1, t2) for i in range(s, e + 1)]) - x2 = sum([xComponent(i, s, e) * A(i, 2, s, e, t1, t2) for i in range(s, e + 1)]) - - # calculate Determinate of the 3 matrices - det_cc = c11 * c22 - c21 * c12 - det_cx = c11 * x2 - c12 * x1 - det_xc = x1 * c22 - x2 * c12 - - # if matrix is not homogenous, fudge the data a bit - if det_cc == 0: - det_cc = 0.01 - - # alpha's are the correct offset for bezier handles - alpha0 = det_xc / det_cc # offset from right (first) point - alpha1 = det_cx / det_cc # offset from left (last) point - - sRightHandle = data_pts[s].co.copy() - sTangent = t1 * abs(alpha0) - sRightHandle += sTangent # position of first pt's handle - eLeftHandle = data_pts[e].co.copy() - eTangent = t2 * abs(alpha1) - eLeftHandle += eTangent # position of last pt's handle. - - # return a 4 member tuple representing the bezier - return (data_pts[s].co, - sRightHandle, - eLeftHandle, - data_pts[e].co) - - # convert 2 given data points into a cubic bezier. - # handles are offset along the tangent at - # a 3rd of the length between the points. - def fitSingleCubic2Pts(data_pts, s, e): - alpha0 = alpha1 = (data_pts[s].co - data_pts[e].co).length / 3 - - sRightHandle = data_pts[s].co.copy() - sTangent = unitTangent(s, data_pts) * abs(alpha0) - sRightHandle += sTangent # position of first pt's handle - eLeftHandle = data_pts[e].co.copy() - eTangent = unitTangent(e, data_pts) * abs(alpha1) - eLeftHandle += eTangent # position of last pt's handle. - - #return a 4 member tuple representing the bezier - return (data_pts[s].co, - sRightHandle, - eLeftHandle, - data_pts[e].co) - - #evaluate bezier, represented by a 4 member tuple (pts) at point t. - def bezierEval(pts, t): - sumVec = NdVector((0, 0, 0, 0, 0)) - for i in range(4): - sumVec += pts[i] * bernsteinPoly(3, i, t) - return sumVec - - #calculate the highest error between bezier and original data - #returns the distance and the index of the point where max error occurs. - def maxErrorAmount(data_pts, bez, s, e): - maxError = 0 - maxErrorPt = s - if e - s < 3: - return 0, None - for pt in data_pts[s:e + 1]: - bezVal = bezierEval(bez, pt.u) - normalize_error = pt.co.length - if normalize_error == 0: - normalize_error = 1 - tmpError = (pt.co - bezVal).length / normalize_error - if tmpError >= maxError: - maxError = tmpError - maxErrorPt = pt.index - return maxError, maxErrorPt - - #calculated bezier derivative at point t. - #That is, tangent of point t. - def getBezDerivative(bez, t): - n = len(bez) - 1 - sumVec = NdVector((0, 0, 0, 0, 0)) - for i in range(n - 1): - sumVec += (bez[i + 1] - bez[i]) * bernsteinPoly(n - 1, i, t) - return sumVec - - #use Newton-Raphson to find a better paramterization of datapoints, - #one that minimizes the distance (or error) - # between bezier and original data. - def newtonRaphson(data_pts, s, e, bez): - for pt in data_pts[s:e + 1]: - if pt.index == s: - pt.u = 0 - elif pt.index == e: - pt.u = 1 - else: - u = pt.u - qu = bezierEval(bez, pt.u) - qud = getBezDerivative(bez, u) - #we wish to minimize f(u), - #the squared distance between curve and data - fu = (qu - pt.co).length ** 2 - fud = (2 * (qu.x - pt.co.x) * (qud.x)) - (2 * (qu.y - pt.co.y) * (qud.y)) - if fud == 0: - fu = 0 - fud = 1 - pt.u = pt.u - (fu / fud) - - #Create data_pts, a list of dataPoint type, each is assigned index i, and an NdVector - def createDataPts(curveGroup, group_mode): - data_pts = [] - if group_mode: - print([x.data_path for x in curveGroup]) - for i in range(len(curveGroup[0].keyframe_points)): - x = curveGroup[0].keyframe_points[i].co.x - y1 = curveGroup[0].keyframe_points[i].co.y - y2 = curveGroup[1].keyframe_points[i].co.y - y3 = curveGroup[2].keyframe_points[i].co.y - y4 = 0 - if len(curveGroup) == 4: - y4 = curveGroup[3].keyframe_points[i].co.y - data_pts.append(dataPoint(i, NdVector((x, y1, y2, y3, y4)))) - else: - for i in range(len(curveGroup.keyframe_points)): - x = curveGroup.keyframe_points[i].co.x - y1 = curveGroup.keyframe_points[i].co.y - y2 = 0 - y3 = 0 - y4 = 0 - data_pts.append(dataPoint(i, NdVector((x, y1, y2, y3, y4)))) - return data_pts - - #Recursively fit cubic beziers to the data_pts between s and e - def fitCubic(data_pts, s, e): - # if there are less than 3 points, fit a single basic bezier - if e - s < 3: - bez = fitSingleCubic2Pts(data_pts, s, e) - else: - #if there are more, parameterize the points - # and fit a single cubic bezier - chordLength(data_pts, s, e) - bez = fitSingleCubic(data_pts, s, e) - - #calculate max error and point where it occurs - maxError, maxErrorPt = maxErrorAmount(data_pts, bez, s, e) - #if error is small enough, reparameterization might be enough - if maxError < reparaError and maxError > error: - for i in range(maxIterations): - newtonRaphson(data_pts, s, e, bez) - if e - s < 3: - bez = fitSingleCubic2Pts(data_pts, s, e) - else: - bez = fitSingleCubic(data_pts, s, e) - - #recalculate max error and point where it occurs - maxError, maxErrorPt = maxErrorAmount(data_pts, bez, s, e) - - #repara wasn't enough, we need 2 beziers for this range. - #Split the bezier at point of maximum error - if maxError > error: - fitCubic(data_pts, s, maxErrorPt) - fitCubic(data_pts, maxErrorPt, e) - else: - #error is small enough, return the beziers. - beziers.append(bez) - return - - # deletes the sampled points and creates beziers. - def createNewCurves(curveGroup, beziers, group_mode): - #remove all existing data points - if group_mode: - for fcurve in curveGroup: - for i in range(len(fcurve.keyframe_points) - 1, 0, -1): - fcurve.keyframe_points.remove(fcurve.keyframe_points[i]) - else: - fcurve = curveGroup - for i in range(len(fcurve.keyframe_points) - 1, 0, -1): - fcurve.keyframe_points.remove(fcurve.keyframe_points[i]) - - #insert the calculated beziers to blender data.\ - if group_mode: - for fullbez in beziers: - for i, fcurve in enumerate(curveGroup): - bez = [Vector((vec[0], vec[i + 1])) for vec in fullbez] - newKey = fcurve.keyframe_points.insert(frame=bez[0].x, value=bez[0].y) - newKey.handle_right = (bez[1].x, bez[1].y) - - newKey = fcurve.keyframe_points.insert(frame=bez[3].x, value=bez[3].y) - newKey.handle_left = (bez[2].x, bez[2].y) - else: - for bez in beziers: - for vec in bez: - vec.resize_2d() - newKey = fcurve.keyframe_points.insert(frame=bez[0].x, value=bez[0].y) - newKey.handle_right = (bez[1].x, bez[1].y) - - newKey = fcurve.keyframe_points.insert(frame=bez[3].x, value=bez[3].y) - newKey.handle_left = (bez[2].x, bez[2].y) - - # indices are detached from data point's frame (x) value and - # stored in the dataPoint object, represent a range - - data_pts = createDataPts(curveGroup, group_mode) - - s = 0 # start - e = len(data_pts) - 1 # end - - beziers = [] - - #begin the recursive fitting algorithm. - fitCubic(data_pts, s, e) - #remove old Fcurves and insert the new ones - createNewCurves(curveGroup, beziers, group_mode) - - -#Main function of simplification, which called by Operator -#IN: -# sel_opt- either "sel" (selected) or "all" for which curves to effect -# error- maximum error allowed, in fraction (20% = 0.0020, which is the default), -# i.e. divide by 10000 from percentage wanted. -# group_mode- boolean, to analyze each curve seperately or in groups, -# where a group is all curves that effect the same property/RNA path -def fcurves_simplify(context, obj, sel_opt="all", error=0.002, group_mode=True): - # main vars - fcurves = obj.animation_data.action.fcurves - - if sel_opt == "sel": - sel_fcurves = [fcurve for fcurve in fcurves if fcurve.select] - else: - sel_fcurves = fcurves[:] - - #Error threshold for Newton Raphson reparamatizing - reparaError = error * 32 - maxIterations = 16 - - if group_mode: - fcurveDict = {} - #this loop sorts all the fcurves into groups of 3 or 4, - #based on their RNA Data path, which corresponds to - #which property they effect - for curve in sel_fcurves: - if curve.data_path in fcurveDict: # if this bone has been added, append the curve to its list - fcurveDict[curve.data_path].append(curve) - else: - fcurveDict[curve.data_path] = [curve] # new bone, add a new dict value with this first curve - fcurveGroups = fcurveDict.values() - else: - fcurveGroups = sel_fcurves - - if error > 0.00000: - #simplify every selected curve. - totalt = 0 - for i, fcurveGroup in enumerate(fcurveGroups): - print("Processing curve " + str(i + 1) + "/" + str(len(fcurveGroups))) - t = time.clock() - simplifyCurves(fcurveGroup, error, reparaError, maxIterations, group_mode) - t = time.clock() - t - print(str(t)[:5] + " seconds to process last curve") - totalt += t - print(str(totalt)[:5] + " seconds, total time elapsed") - - return - - -# Implementation of non-linear median filter, with variable kernel size -# Double pass - one marks spikes, the other smooths them -# Expects sampled keyframes on everyframe -# IN: None. Performs the operations on the active_object's fcurves. Expects animation_data.action to exist! -# OUT: None. Fixes the fcurves "in-place". -def denoise_median(): - context = bpy.context - obj = context.active_object - fcurves = obj.animation_data.action.fcurves - medKernel = 1 # actually *2+1... since it this is offset - flagKernel = 4 - highThres = (flagKernel * 2) - 1 - lowThres = 0 - for fcurve in fcurves: - orgPts = fcurve.keyframe_points[:] - flaggedFrames = [] - # mark frames that are spikes by sorting a large kernel - for i in range(flagKernel, len(fcurve.keyframe_points) - flagKernel): - center = orgPts[i] - neighborhood = orgPts[i - flagKernel: i + flagKernel] - neighborhood.sort(key=lambda pt: pt.co[1]) - weight = neighborhood.index(center) - if weight >= highThres or weight <= lowThres: - flaggedFrames.append((i, center)) - # clean marked frames with a simple median filter - # averages all frames in the kernel equally, except center which has no weight - for i, pt in flaggedFrames: - newValue = 0 - sumWeights = 0 - neighborhood = [neighpt.co[1] for neighpt in orgPts[i - medKernel: i + medKernel + 1] if neighpt != pt] - newValue = sum(neighborhood) / len(neighborhood) - pt.co[1] = newValue - return - - -# Recieves armature, and rotations all bones by 90 degrees along the X axis -# This fixes the common axis issue BVH files have when importing. -# IN: Armature (bpy.types.Armature) -def rotate_fix_armature(arm_data): - global_matrix = Matrix.Rotation(radians(90), 4, "X") - bpy.ops.object.mode_set(mode='EDIT', toggle=False) - #disconnect all bones for ease of global rotation - connectedBones = [] - for bone in arm_data.edit_bones: - if bone.use_connect: - connectedBones.append(bone.name) - bone.use_connect = False - - #rotate all the bones around their center - for bone in arm_data.edit_bones: - bone.transform(global_matrix) - - #reconnect the bones - for bone in connectedBones: - arm_data.edit_bones[bone].use_connect = True - bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - - -#Roughly scales the performer armature to match the enduser armature -#IN: perfromer_obj, enduser_obj, Blender objects whose .data is an armature. -def scale_fix_armature(performer_obj, enduser_obj): - perf_bones = performer_obj.data.bones - end_bones = enduser_obj.data.bones - - def calculateBoundingRadius(bones): - center = Vector() - for bone in bones: - center += bone.head_local - center /= len(bones) - radius = 0 - for bone in bones: - dist = (bone.head_local - center).length - if dist > radius: - radius = dist - return radius - - perf_rad = calculateBoundingRadius(performer_obj.data.bones) - end_rad = calculateBoundingRadius(enduser_obj.data.bones) - #end_avg = enduser_obj.dimensions - factor = end_rad / perf_rad * 1.2 - performer_obj.scale *= factor - - -#Guess Mapping -#Given a performer and enduser armature, attempts to guess the hiearchy mapping -def guessMapping(performer_obj, enduser_obj): - perf_bones = performer_obj.data.bones - end_bones = enduser_obj.data.bones - - root = perf_bones[0] - - def findBoneSide(bone): - if "Left" in bone: - return "Left", bone.replace("Left", "").lower().replace(".", "") - if "Right" in bone: - return "Right", bone.replace("Right", "").lower().replace(".", "") - if "L" in bone: - return "Left", bone.replace("Left", "").lower().replace(".", "") - if "R" in bone: - return "Right", bone.replace("Right", "").lower().replace(".", "") - return "", bone - - def nameMatch(bone_a, bone_b): - # nameMatch - recieves two strings, returns 2 if they are relatively the same, 1 if they are the same but R and L and 0 if no match at all - side_a, noside_a = findBoneSide(bone_a) - side_b, noside_b = findBoneSide(bone_b) - if side_a == side_b: - if noside_a in noside_b or noside_b in noside_a: - return 2 - else: - if noside_a in noside_b or noside_b in noside_a: - return 1 - return 0 - - def guessSingleMapping(perf_bone): - possible_bones = [end_bones[0]] - - while possible_bones: - for end_bone in possible_bones: - match = nameMatch(perf_bone.name, end_bone.name) - if match == 2 and not perf_bone.map: - perf_bone.map = end_bone.name - #~ elif match == 1 and not perf_bone.map: - #~ oppo = perf_bones[oppositeBone(perf_bone)].map - # if oppo: - # perf_bone = oppo - newPossibleBones = [] - for end_bone in possible_bones: - newPossibleBones += list(end_bone.children) - possible_bones = newPossibleBones - - for child in perf_bone.children: - guessSingleMapping(child) - - guessSingleMapping(root) - - -# Creates limit rotation constraints on the enduser armature based on range of motion (max min of fcurves) of the performer. -# IN: context (bpy.context, etc.), and 2 blender objects which are armatures -# OUT: creates the limit constraints. -def limit_dof(context, performer_obj, enduser_obj): - limitDict = {} - perf_bones = [bone for bone in performer_obj.pose.bones if bone.bone.map] - c_frame = context.scene.frame_current - for bone in perf_bones: - limitDict[bone.bone.map] = [1000, 1000, 1000, -1000, -1000, -1000] - for t in range(context.scene.frame_start, context.scene.frame_end): - context.scene.frame_set(t) - for bone in perf_bones: - end_bone = enduser_obj.pose.bones[bone.bone.map] - bake_matrix = bone.matrix - rest_matrix = end_bone.bone.matrix_local - - if end_bone.parent and end_bone.bone.use_inherit_rotation: - srcParent = bone.parent - parent_mat = srcParent.matrix - parent_rest = end_bone.parent.bone.matrix_local - parent_rest_inv = parent_rest.inverted() - parent_mat_inv = parent_mat.inverted() - bake_matrix = parent_mat_inv * bake_matrix - rest_matrix = parent_rest_inv * rest_matrix - - rest_matrix_inv = rest_matrix.inverted() - bake_matrix = rest_matrix_inv * bake_matrix - - mat = bake_matrix - euler = mat.to_euler() - limitDict[bone.bone.map][0] = min(limitDict[bone.bone.map][0], euler.x) - limitDict[bone.bone.map][1] = min(limitDict[bone.bone.map][1], euler.y) - limitDict[bone.bone.map][2] = min(limitDict[bone.bone.map][2], euler.z) - limitDict[bone.bone.map][3] = max(limitDict[bone.bone.map][3], euler.x) - limitDict[bone.bone.map][4] = max(limitDict[bone.bone.map][4], euler.y) - limitDict[bone.bone.map][5] = max(limitDict[bone.bone.map][5], euler.z) - for bone in enduser_obj.pose.bones: - existingConstraint = [constraint for constraint in bone.constraints if constraint.name == "DOF Limitation"] - if existingConstraint: - bone.constraints.remove(existingConstraint[0]) - end_bones = [bone for bone in enduser_obj.pose.bones if bone.name in limitDict.keys()] - for bone in end_bones: - #~ if not bone.is_in_ik_chain: - newCons = bone.constraints.new("LIMIT_ROTATION") - newCons.name = "DOF Limitation" - newCons.owner_space = "LOCAL" - newCons.min_x, newCons.min_y, newCons.min_z, newCons.max_x, newCons.max_y, newCons.max_z = limitDict[bone.name] - newCons.use_limit_x = True - newCons.use_limit_y = True - newCons.use_limit_z = True - context.scene.frame_set(c_frame) - - -# Removes the constraints that were added by limit_dof on the enduser_obj -def limit_dof_toggle_off(context, enduser_obj): - for bone in enduser_obj.pose.bones: - existingConstraint = [constraint for constraint in bone.constraints if constraint.name == "DOF Limitation"] - if existingConstraint: - bone.constraints.remove(existingConstraint[0]) - - -# Reparameterizes a blender path via keyframing it's eval_time to match a stride_object's forward velocity. -# IN: Context, stride object (blender object with location keyframes), path object. -def path_editing(context, stride_obj, path): - y_fcurve = [fcurve for fcurve in stride_obj.animation_data.action.fcurves if fcurve.data_path == "location"][1] - s, e = context.scene.frame_start, context.scene.frame_end # y_fcurve.range() - s = int(s) - e = int(e) - y_s = y_fcurve.evaluate(s) - y_e = y_fcurve.evaluate(e) - direction = (y_e - y_s) / abs(y_e - y_s) - existing_cons = [constraint for constraint in stride_obj.constraints if constraint.type == "FOLLOW_PATH"] - for cons in existing_cons: - stride_obj.constraints.remove(cons) - path_cons = stride_obj.constraints.new("FOLLOW_PATH") - if direction < 0: - path_cons.forward_axis = "TRACK_NEGATIVE_Y" - else: - path_cons.forward_axis = "FORWARD_Y" - path_cons.target = path - path_cons.use_curve_follow = True - path.data.path_duration = e - s - try: - path.data.animation_data.action.fcurves - except AttributeError: - path.data.keyframe_insert("eval_time", frame=0) - eval_time_fcurve = [fcurve for fcurve in path.data.animation_data.action.fcurves if fcurve.data_path == "eval_time"] - eval_time_fcurve = eval_time_fcurve[0] - totalLength = 0 - parameterization = {} - print("evaluating curve") - for t in range(s, e - 1): - if s == t: - chordLength = 0 - else: - chordLength = (y_fcurve.evaluate(t) - y_fcurve.evaluate(t + 1)) - totalLength += chordLength - parameterization[t] = totalLength - for t in range(s + 1, e - 1): - if totalLength == 0: - print("no forward motion") - parameterization[t] /= totalLength - parameterization[t] *= e - s - parameterization[e] = e - s - for t in parameterization.keys(): - eval_time_fcurve.keyframe_points.insert(frame=t, value=parameterization[t]) - y_fcurve.mute = True - print("finished path editing") - - -#Animation Stitching -#Stitches two retargeted animations together via NLA settings. -#IN: enduser_obj, a blender armature that has had two retargets applied. -def anim_stitch(context, enduser_obj): - stitch_settings = enduser_obj.data.stitch_settings - action_1 = stitch_settings.first_action - action_2 = stitch_settings.second_action - if stitch_settings.stick_bone != "": - selected_bone = enduser_obj.pose.bones[stitch_settings.stick_bone] - else: - selected_bone = enduser_obj.pose.bones[0] - scene = context.scene - TrackNamesA = enduser_obj.data.mocapNLATracks[action_1] - TrackNamesB = enduser_obj.data.mocapNLATracks[action_2] - enduser_obj.data.active_mocap = action_1 - anim_data = enduser_obj.animation_data - # add tracks for action 2 - mocapAction = bpy.data.actions[TrackNamesB.base_track] - mocapTrack = anim_data.nla_tracks.new() - mocapTrack.name = TrackNamesB.base_track - mocapStrip = mocapTrack.strips.new(TrackNamesB.base_track, stitch_settings.blend_frame, mocapAction) - mocapStrip.extrapolation = "HOLD_FORWARD" - mocapStrip.blend_in = stitch_settings.blend_amount - mocapStrip.action_frame_start += stitch_settings.second_offset - mocapStrip.action_frame_end += stitch_settings.second_offset - constraintTrack = anim_data.nla_tracks.new() - constraintTrack.name = TrackNamesB.auto_fix_track - constraintAction = bpy.data.actions[TrackNamesB.auto_fix_track] - constraintStrip = constraintTrack.strips.new(TrackNamesB.auto_fix_track, stitch_settings.blend_frame, constraintAction) - constraintStrip.extrapolation = "HOLD_FORWARD" - constraintStrip.blend_in = stitch_settings.blend_amount - userTrack = anim_data.nla_tracks.new() - userTrack.name = TrackNamesB.manual_fix_track - userAction = bpy.data.actions[TrackNamesB.manual_fix_track] - userStrip = userTrack.strips.new(TrackNamesB.manual_fix_track, stitch_settings.blend_frame, userAction) - userStrip.extrapolation = "HOLD_FORWARD" - userStrip.blend_in = stitch_settings.blend_amount - #stride bone - if enduser_obj.parent: - if enduser_obj.parent.name == "stride_bone": - stride_bone = enduser_obj.parent - stride_anim_data = stride_bone.animation_data - stride_anim_data.use_nla = True - stride_anim_data.action = None - for track in stride_anim_data.nla_tracks: - stride_anim_data.nla_tracks.remove(track) - actionATrack = stride_anim_data.nla_tracks.new() - actionATrack.name = TrackNamesA.stride_action - actionAStrip = actionATrack.strips.new(TrackNamesA.stride_action, 0, bpy.data.actions[TrackNamesA.stride_action]) - actionAStrip.extrapolation = "NOTHING" - actionBTrack = stride_anim_data.nla_tracks.new() - actionBTrack.name = TrackNamesB.stride_action - actionBStrip = actionBTrack.strips.new(TrackNamesB.stride_action, stitch_settings.blend_frame, bpy.data.actions[TrackNamesB.stride_action]) - actionBStrip.action_frame_start += stitch_settings.second_offset - actionBStrip.action_frame_end += stitch_settings.second_offset - actionBStrip.blend_in = stitch_settings.blend_amount - actionBStrip.extrapolation = "NOTHING" - #we need to change the stride_bone's action to add the offset - scene.frame_set(stitch_settings.blend_frame - 1) - desired_pos = (selected_bone.matrix.to_translation() * enduser_obj.matrix_world) - scene.frame_set(stitch_settings.blend_frame) - actual_pos = (selected_bone.matrix.to_translation() * enduser_obj.matrix_world) - offset = actual_pos - desired_pos - - for i, fcurve in enumerate([fcurve for fcurve in bpy.data.actions[TrackNamesB.stride_action].fcurves if fcurve.data_path == "location"]): - print(offset[i], i, fcurve.array_index) - for pt in fcurve.keyframe_points: - pt.co.y -= offset[i] - pt.handle_left.y -= offset[i] - pt.handle_right.y -= offset[i] - - -#Guesses setting for animation stitching via Cross Correlation -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("Guessed the following for start and offset: ", s, flm) - enduser_obj.data.stitch_settings.blend_frame = flm - enduser_obj.data.stitch_settings.second_offset = s |