Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjy Cook <benjycook@hotmail.com>2011-08-17 14:13:24 +0400
committerBenjy Cook <benjycook@hotmail.com>2011-08-17 14:13:24 +0400
commit78b147fbc20cc5cbd30057474686a5fb91124fab (patch)
tree11adb90e828f446637e6a5bd3d9e00185f72b03e /release/scripts
parentdb4071d2b6b727857521bd05529fe8141e8bb0a2 (diff)
Commenting and pep8 compliance
Diffstat (limited to 'release/scripts')
-rw-r--r--release/scripts/modules/mocap_tools.py152
-rw-r--r--release/scripts/modules/retarget.py87
-rw-r--r--release/scripts/startup/ui_mocap.py86
3 files changed, 166 insertions, 159 deletions
diff --git a/release/scripts/modules/mocap_tools.py b/release/scripts/modules/mocap_tools.py
index e5d4dcb6554..6c22f718296 100644
--- a/release/scripts/modules/mocap_tools.py
+++ b/release/scripts/modules/mocap_tools.py
@@ -24,7 +24,9 @@ import time
from mathutils import Vector, Matrix
-#Vector utility functions
+# 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 = []
@@ -90,6 +92,7 @@ class NdVector:
y = property(y)
+#Sampled Data Point class for Simplify Curves
class dataPoint:
index = 0
# x,y1,y2,y3 coordinate of original point
@@ -105,11 +108,19 @@ class dataPoint:
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 = []
- end = len(curvesA[0].keyframe_points)
+ 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:
@@ -120,9 +131,11 @@ def crossCorrelationMatch(curvesA, curvesB, margin):
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):
@@ -131,7 +144,9 @@ def crossCorrelationMatch(curvesA, curvesB, margin):
for j in range(i):
Rxy[i] += comp(dataA[j], dataB[j - i + N])
Rxy[i] /= float(N)
- def bestLocalMaximum(Rxy):
+
+ #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):
@@ -142,9 +157,12 @@ def crossCorrelationMatch(curvesA, curvesB, margin):
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 = bestLocalMaximum(Rxy[0:int(len(Rxy))])
+
+ #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 = []
@@ -159,20 +177,28 @@ def crossCorrelationMatch(curvesA, curvesB, margin):
if errorSlice < bestSlice[1]:
bestSlice = (i, errorSlice, flm)
return bestSlice
-
+
s = lowerErrorSlice(diff, margin)
ss.append(s)
- ss.sort(key = lambda x: x[1])
+ #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
@@ -180,13 +206,10 @@ def autoloop_anim():
flm, s, data = crossCorrelationMatch(fcurves, fcurves, margin)
loop = data[s:s + flm]
- #find *all* loops, s:s+flm, s+flm:s+2flm, etc...
- #and interpolate between all
- # to find "the perfect loop".
- #Maybe before finding s? interp(i,i+flm,i+2flm)....
- #~ for i in range(1, margin + 1):
- #~ w1 = sqrt(float(i) / margin)
- #~ loop[-i] = (loop[-i] * w1) + (loop[0] * (1 - w1))
+ #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
@@ -201,8 +224,16 @@ def autoloop_anim():
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:
@@ -214,7 +245,8 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
tang.normalize()
return tang
- #assign parametric u value for each point in original data
+ #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]:
@@ -230,7 +262,7 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
print(s, e)
pt.u = (pt.temp / totalLength)
- # get binomial coefficient, this function/table is only called with args
+ # 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,
@@ -239,8 +271,8 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
(2, 0): 1,
(2, 1): 2,
(2, 2): 1}
- #value at pt t of a single bernstein Polynomial
+ #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)
@@ -380,6 +412,7 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
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:
@@ -403,6 +436,7 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
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:
@@ -437,6 +471,7 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
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:
@@ -483,15 +518,14 @@ def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
#remove old Fcurves and insert the new ones
createNewCurves(curveGroup, beziers, group_mode)
-#Main function of simplification
-#sel_opt: either "sel" or "all" for which curves to effect
-#error: maximum error allowed, in fraction (20% = 0.0020),
-#i.e. divide by 10000 from percentage wanted.
-#group_mode: boolean, to analyze each curve seperately or in groups,
-#where group is all curves that effect the same property
-#(e.g. a bone's x,y,z rotation)
-
+#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
@@ -533,11 +567,12 @@ def fcurves_simplify(context, obj, sel_opt="all", error=0.002, group_mode=True):
return
+
# Implementation of non-linear median filter, with variable kernel size
-# Double pass - one marks spikes, the other smooths one
+# 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
@@ -568,6 +603,9 @@ def denoise_median():
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)
@@ -588,6 +626,8 @@ def rotate_fix_armature(arm_data):
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
@@ -611,6 +651,8 @@ def scale_fix_armature(performer_obj, enduser_obj):
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
@@ -642,11 +684,16 @@ def guessMapping(performer_obj, enduser_obj):
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)
@@ -658,6 +705,9 @@ def guessMapping(performer_obj, enduser_obj):
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]
@@ -705,18 +755,10 @@ def limit_dof(context, performer_obj, enduser_obj):
newCons.use_limit_x = True
newCons.use_limit_y = True
newCons.use_limit_z = True
- #~ else:
- #~ bone.ik_min_x, bone.ik_min_y, bone.ik_min_z, bone.ik_max_x, bone.ik_max_y, bone.ik_max_z = limitDict[bone.name]
- #~ bone.use_ik_limit_x = True
- #~ bone.use_ik_limit_y = True
- #~ bone.use_ik_limit_z= True
- #~ bone.ik_stiffness_x = 1/((limitDict[bone.name][3] - limitDict[bone.name][0])/(2*pi)))
- #~ bone.ik_stiffness_y = 1/((limitDict[bone.name][4] - limitDict[bone.name][1])/(2*pi)))
- #~ bone.ik_stiffness_z = 1/((limitDict[bone.name][5] - limitDict[bone.name][2])/(2*pi)))
-
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"]
@@ -724,6 +766,8 @@ def limit_dof_toggle_off(context, enduser_obj):
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()
@@ -771,11 +815,14 @@ def path_editing(context, stride_obj, path):
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!="":
+ if stitch_settings.stick_bone != "":
selected_bone = enduser_obj.pose.bones[stitch_settings.stick_bone]
else:
selected_bone = enduser_obj.pose.bones[0]
@@ -791,8 +838,8 @@ def anim_stitch(context, enduser_obj):
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
+ 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]
@@ -821,8 +868,8 @@ def anim_stitch(context, enduser_obj):
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.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
@@ -831,15 +878,16 @@ def anim_stitch(context, enduser_obj):
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 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]
+ 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
@@ -851,6 +899,6 @@ def guess_anim_stitch(context, enduser_obj):
curvesA = mocapA.fcurves
curvesB = mocapB.fcurves
flm, s, data = crossCorrelationMatch(curvesA, curvesB, 10)
- print(flm,s)
+ 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 \ No newline at end of file
+ enduser_obj.data.stitch_settings.second_offset = s
diff --git a/release/scripts/modules/retarget.py b/release/scripts/modules/retarget.py
index 662dfc218ab..67e8c7da55d 100644
--- a/release/scripts/modules/retarget.py
+++ b/release/scripts/modules/retarget.py
@@ -22,7 +22,6 @@ import bpy
from mathutils import *
from math import radians, acos, pi
from bl_operators import nla
-import cProfile
def hasIKConstraint(pose_bone):
@@ -53,8 +52,8 @@ def createDictionary(perf_arm, end_arm):
feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
return feetBones, root
-def loadMapping(perf_arm, end_arm):
+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:
@@ -80,17 +79,7 @@ def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene
perf_world_rotation = perf_bone.matrix
inter_world_base_rotation = inter_bone.bone.matrix_local
inter_world_base_inv = inter_world_base_rotation.inverted()
- bake_matrix = (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3())
- #~ orgEul = inter_bone.bone.matrix_local.to_euler("XYZ")
- #~ eul = bake_matrix.to_euler("XYZ", orgEul)
- #~ diff = -bake_matrix.to_euler().y + inter_bone.bone.matrix.to_euler().y
- #~ eul.rotate_axis("Y", diff)
- #~ eul.make_compatible(orgEul)
- #~ bake_matrix = eul.to_matrix()
- #~ #diff = abs(diff)
- #bake_matrix = bake_matrix* Matrix.Rotation(pi/2, 3, "Y")
- #~ scene = bpy.context.scene
- #~ print(scene.frame_current, inter_bone.name, bake_matrix.to_euler().y)
+ bake_matrix = (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3())
return bake_matrix.to_4x4()
#uses 1to1 and interpolation/averaging to match many to 1 retarget
@@ -117,6 +106,7 @@ def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene
else:
perf_bone = performer_bones[perf_bone_name[0].name]
inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
+ #Some bones have incorrect roll on the source armature, and need to be marked for fixing
if inter_bone.bone.twistFix:
inter_bone.matrix_basis *= Matrix.Rotation(radians(180), 4, "Y")
rot_mode = inter_bone.rotation_mode
@@ -151,7 +141,6 @@ def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene
bone.roll = 0
#resets roll
print("retargeting to intermediate")
- #bpy.ops.armature.calculate_roll(type='Z')
bpy.ops.object.mode_set(mode="OBJECT")
inter_obj.data.name = "inter_arm"
inter_arm = inter_obj.data
@@ -187,6 +176,7 @@ def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step)
inter_bones = inter_obj.pose.bones
end_bones = enduser_obj.pose.bones
+ #Basic "visual baking" function, for transfering rotations from intermediate to end user
def bakeTransform(end_bone):
src_bone = inter_bones[end_bone.name]
trg_bone = end_bone
@@ -265,29 +255,21 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
# now we take our locDict and analyze it.
# we need to derive all chains
-
+
def locDeriv(key, t):
graph = locDict[key]
return graph[t + 1] - graph[t]
- #~ locDeriv = {}
- #~ for key in locDictKeys:
- #~ locDeriv[key] = []
-
- #~ for key in locDict.keys():
- #~ graph = locDict[key]
- #~ locDeriv[key] = [graph[t + 1] - graph[t] for t in range(len(graph) - 1)]
-
# now find the plant frames, where perfFeet don't move much
linearAvg = []
for key in perfFeet:
for i in range(len(locDict[key]) - 1):
- v = locDeriv(key,i)
+ v = locDeriv(key, i)
if (v.length < 0.1):
- hipV = locDeriv(perfRoot,i)
- endV = locDeriv(perf_bones[key].bone.map,i)
+ hipV = locDeriv(perfRoot, i)
+ endV = locDeriv(perf_bones[key].bone.map, i)
#this is a plant frame.
#lets see what the original hip delta is, and the corresponding
#end bone's delta
@@ -312,7 +294,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
#determine the average change in scale needed
avg = sum(linearAvg) / len(linearAvg)
scene.frame_set(s_frame)
- initialPos = (tailLoc(perf_bones[perfRoot]) / avg) #+ stride_bone.location
+ 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
@@ -320,7 +302,6 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
stride_bone.keyframe_insert("location")
else:
-
stride_bone.keyframe_insert("location")
stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
@@ -342,7 +323,7 @@ def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
bpy.ops.object.mode_set(mode='OBJECT')
if not ik_constraint.target:
ik_constraint.target = enduser_obj
- ik_constraint.subtarget = pose_bone.name+"IK"
+ ik_constraint.subtarget = pose_bone.name + "IK"
target = orgLocTrg
# There is a target now
@@ -370,14 +351,6 @@ def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
def turnOffIK(enduser_obj):
end_bones = enduser_obj.pose.bones
for pose_bone in end_bones:
- if pose_bone.is_in_ik_chain:
- pass
- # TODO:
- # set stiffness according to place on chain
- # and values from analysis that is stored in the bone
- #pose_bone.ik_stiffness_x = 0.5
- #pose_bone.ik_stiffness_y = 0.5
- #pose_bone.ik_stiffness_z = 0.5
ik_constraint = hasIKConstraint(pose_bone)
if ik_constraint:
ik_constraint.mute = True
@@ -412,18 +385,14 @@ def originalLocationTarget(end_bone, enduser_obj):
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
+ newBone.tail = end_bone.tail + Vector((0, 0.1, 0))
else:
newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
return newBone
#create the specified NLA setup for base animation, constraints and tweak layer.
-def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
+def NLASystemInitialize(enduser_arm, context):
enduser_obj = context.active_object
NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
name = NLATracks.name
@@ -461,9 +430,8 @@ def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
userAction.use_fake_user = True
userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
userStrip.extrapolation = "HOLD"
- #userStrip.blend_type = "MULITPLY" - doesn't work due to work, will be activated soon
+ userStrip.blend_type = "ADD"
anim_data.nla_tracks.active = constraintTrack
- #anim_data.action = constraintAction
anim_data.action_extrapolation = "NOTHING"
#set the stride_bone's action
if "stride_bone" in bpy.data.objects:
@@ -494,8 +462,8 @@ def preAdvancedRetargeting(performer_obj, enduser_obj):
cons.subtarget = perf_bone
cons.target_space = 'WORLD'
cons.owner_space = 'WORLD'
-
- if (not bone.bone.use_connect) and (perf_bone!=perf_root):
+
+ if (not bone.bone.use_connect) and (perf_bone != perf_root):
cons = bone.constraints.new('COPY_LOCATION')
cons.name = "retargetTemp"
cons.target = performer_obj
@@ -517,6 +485,7 @@ def prepareForBake(enduser_obj):
if "retargetTemp" in cons.name:
bone.bone.select = True
+
def cleanTempConstraints(enduser_obj):
bones = enduser_obj.pose.bones
map_bones = [bone for bone in bones if bone.bone.reverseMap]
@@ -525,6 +494,7 @@ def cleanTempConstraints(enduser_obj):
if "retargetTemp" in cons.name:
bone.constraints.remove(cons)
+
#Main function that runs the retargeting sequence.
#If advanced == True, we assume constraint's were already created
def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
@@ -532,13 +502,13 @@ def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
end_arm = enduser_obj.data
advanced = end_arm.advancedRetarget
step = end_arm.frameStep
-
+
try:
enduser_obj.animation_data.action = bpy.data.actions.new("temp")
enduser_obj.animation_data.action.use_fake_user = True
except:
print("no need to create new action")
-
+
print("creating Dictionary")
feetBones, root = createDictionary(perf_arm, end_arm)
print("cleaning stuff up")
@@ -576,22 +546,6 @@ def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
NLATracks = end_arm.mocapNLATracks[name]
end_arm.active_mocap = name
print("retargeting done!")
-
-def profileWrapper():
- context = bpy.context
- scene = context.scene
- s_frame = scene.frame_start
- e_frame = scene.frame_end
- enduser_obj = context.active_object
- performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
- if enduser_obj is None or len(performer_obj) != 1:
- print("Need active and selected armatures")
- else:
- performer_obj = performer_obj[0]
- s_frame, e_frame = performer_obj.animation_data.action.frame_range
- s_frame = int(s_frame)
- e_frame = int(e_frame)
- totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
def isRigAdvanced(enduser_obj):
@@ -603,6 +557,3 @@ def isRigAdvanced(enduser_obj):
if enduser_obj.data.animation_data:
if enduser_obj.data.animation_data.drivers:
return True
-
-if __name__ == "__main__":
- cProfile.run("profileWrapper()")
diff --git a/release/scripts/startup/ui_mocap.py b/release/scripts/startup/ui_mocap.py
index 23354f9d722..3cb33776b0b 100644
--- a/release/scripts/startup/ui_mocap.py
+++ b/release/scripts/startup/ui_mocap.py
@@ -112,6 +112,7 @@ bpy.utils.register_class(MocapConstraint)
bpy.types.Armature.mocap_constraints = bpy.props.CollectionProperty(type=MocapConstraint)
+# Animation Stitch Settings, used for animation stitching of 2 retargeted animations.
class AnimationStitchSettings(bpy.types.PropertyGroup):
first_action = bpy.props.StringProperty(name="Action 1",
description="First action in stitch")
@@ -132,6 +133,7 @@ class AnimationStitchSettings(bpy.types.PropertyGroup):
bpy.utils.register_class(AnimationStitchSettings)
+# MocapNLA Tracks. Stores which tracks/actions are associated with each retargeted animation.
class MocapNLATracks(bpy.types.PropertyGroup):
name = bpy.props.StringProperty()
base_track = bpy.props.StringProperty()
@@ -141,7 +143,8 @@ class MocapNLATracks(bpy.types.PropertyGroup):
bpy.utils.register_class(MocapNLATracks)
-
+
+#Update function for Advanced Retarget boolean variable.
def advancedRetargetToggle(self, context):
enduser_obj = context.active_object
performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
@@ -156,20 +159,23 @@ def advancedRetargetToggle(self, context):
retarget.cleanTempConstraints(enduser_obj)
-
+#Animation Stitch Settings Property
bpy.types.Armature.stitch_settings = bpy.props.PointerProperty(type=AnimationStitchSettings)
-bpy.types.Armature.active_mocap = bpy.props.StringProperty(update=retarget.NLASystemInitialize)
+#Current/Active retargeted animation on the armature
+bpy.types.Armature.active_mocap = bpy.props.StringProperty(update=retarget.NLASystemInitialize)
+#Collection of retargeted animations and their NLA Tracks on the armature
bpy.types.Armature.mocapNLATracks = bpy.props.CollectionProperty(type=MocapNLATracks)
+#Advanced retargeting boolean property
bpy.types.Armature.advancedRetarget = bpy.props.BoolProperty(default=False, update=advancedRetargetToggle)
+#frame step - frequency of frames to retarget. Skipping is useful for previewing, faster work etc.
bpy.types.Armature.frameStep = smooth_out = bpy.props.IntProperty(name="Frame Skip",
default=1,
description="Amount of frames to skip - for previewing retargets quickly. 1 is fully sampled",
min=1)
-#Update function for IK functionality. Is called when IK prop checkboxes are toggled.
-
def toggleIKBone(self, context):
+ #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
if self.IKRetarget:
if not self.is_in_ik_chain:
print(self.name + " IK toggled ON!")
@@ -211,21 +217,29 @@ def toggleIKBone(self, context):
for bone in cnstrn_bone.parent_recursive:
if not bone.is_in_ik_chain:
bone.IKRetarget = False
-
+
+#MocapMap class for storing mapping on enduser performer,
+# where a bone may be linked to more than one on the performer
class MocapMapping(bpy.types.PropertyGroup):
name = bpy.props.StringProperty()
bpy.utils.register_class(MocapMapping)
+#string property for storing performer->enduser mapping
bpy.types.Bone.map = bpy.props.StringProperty()
+#Collection Property for storing enduser->performer mapping
bpy.types.Bone.reverseMap = bpy.props.CollectionProperty(type=MocapMapping)
+#Boolean property for storing foot bone toggle
bpy.types.Bone.foot = bpy.props.BoolProperty(name="Foot",
description="Marks this bone as a 'foot', which determines retargeted animation's translation",
default=False)
+#Boolean property for storing if this bone is twisted along the y axis,
+# which can happen due to various sources of performers
bpy.types.Bone.twistFix = bpy.props.BoolProperty(name="Twist Fix",
description="Fix Twist on this bone",
default=False)
+#Boolean property for toggling ik retargeting for this bone
bpy.types.PoseBone.IKRetarget = bpy.props.BoolProperty(name="IK",
description="Toggles IK Retargeting method for given bone",
update=toggleIKBone, default=False)
@@ -384,7 +398,7 @@ class ExtraToolsPanel(bpy.types.Panel):
if activeIsArmature:
enduser_arm = context.active_object.data
layout.label("Retargeted Animations:")
- layout.prop_search(enduser_arm, "active_mocap",enduser_arm, "mocapNLATracks")
+ layout.prop_search(enduser_arm, "active_mocap", enduser_arm, "mocapNLATracks")
settings = enduser_arm.stitch_settings
layout.prop_search(settings, "first_action", enduser_arm, "mocapNLATracks")
layout.prop_search(settings, "second_action", enduser_arm, "mocapNLATracks")
@@ -397,6 +411,8 @@ class ExtraToolsPanel(bpy.types.Panel):
class OBJECT_OT_RetargetButton(bpy.types.Operator):
+ #Retargeting operator. Assumes selected and active armatures, where the performer (the selected one)
+ # has an action for retargeting
'''Retarget animation from selected armature to active armature '''
bl_idname = "mocap.retarget"
bl_label = "Retargets active action from Performer to Enduser"
@@ -428,41 +444,13 @@ class OBJECT_OT_RetargetButton(bpy.types.Operator):
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)
+ return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature) and performer_obj[0].animation_data
else:
return False
-
-
- #~ class OBJECT_OT_AdvancedRetargetButton(bpy.types.Operator):
- #~ '''Prepare for advanced retargeting '''
- #~ bl_idname = "mocap.preretarget"
- #~ bl_label = "Prepares retarget of active action from Performer to Enduser"
-
- #~ def execute(self, context):
- #~ scene = context.scene
- #~ s_frame = scene.frame_start
- #~ e_frame = scene.frame_end
- #~ enduser_obj = context.active_object
- #~ performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
- #~ if enduser_obj is None or len(performer_obj) != 1:
- #~ print("Need active and selected armatures")
- #~ else:
- #~ performer_obj = performer_obj[0]
- #~ retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
- #~ 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_SaveMappingButton(bpy.types.Operator):
+ #Operator for saving mapping to enduser armature
'''Save mapping to active armature (for future retargets) '''
bl_idname = "mocap.savemapping"
bl_label = "Saves user generated mapping from Performer to Enduser"
@@ -486,6 +474,7 @@ class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
'''Load saved mapping from active armature'''
+ #Operator for loading mapping to enduser armature
bl_idname = "mocap.loadmapping"
bl_label = "Loads user generated mapping from Performer to Enduser"
@@ -504,9 +493,10 @@ class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
else:
return False
-
+
class OBJECT_OT_SelectMapBoneButton(bpy.types.Operator):
+ #Operator for setting selected bone in enduser armature to the performer mapping
'''Select a bone for faster mapping'''
bl_idname = "mocap.selectmap"
bl_label = "Select a bone for faster mapping"
@@ -538,6 +528,7 @@ class OBJECT_OT_SelectMapBoneButton(bpy.types.Operator):
class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
+ #Operator to convert samples to beziers on the selected object
'''Convert active armature's sampled keyframed to beziers'''
bl_idname = "mocap.samples"
bl_label = "Converts samples / simplifies keyframes to beziers"
@@ -552,6 +543,7 @@ class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
class OBJECT_OT_LooperButton(bpy.types.Operator):
+ #Operator to trim fcurves which contain a few loops to a single one on the selected object
'''Trim active armature's animation to a single cycle, given a cyclic animation (such as a walk cycle)'''
bl_idname = "mocap.looper"
bl_label = "loops animation / sampled mocap data"
@@ -566,6 +558,7 @@ class OBJECT_OT_LooperButton(bpy.types.Operator):
class OBJECT_OT_DenoiseButton(bpy.types.Operator):
+ #Operator to denoise impluse noise on the active object's fcurves
'''Denoise active armature's animation. Good for dealing with 'bad' frames inherent in mocap animation'''
bl_idname = "mocap.denoise"
bl_label = "Denoises sampled mocap data "
@@ -584,6 +577,7 @@ class OBJECT_OT_DenoiseButton(bpy.types.Operator):
class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
+ #Operator to analyze performer armature and apply rotation constraints on the enduser armature
'''Create limit constraints on the active armature from the selected armature's animation's range of motion'''
bl_idname = "mocap.limitdof"
bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
@@ -605,6 +599,7 @@ class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
class OBJECT_OT_RemoveLimitDOFButton(bpy.types.Operator):
+ #Removes constraints created by above operator
'''Removes previously created limit constraints on the active armature'''
bl_idname = "mocap.removelimitdof"
bl_label = "Removes previously created limit constraints on the active armature"
@@ -622,6 +617,7 @@ class OBJECT_OT_RemoveLimitDOFButton(bpy.types.Operator):
class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
+ #Operator to fix common imported Mocap data issue of wrong axis system on active object
'''Realign the active armature's axis system to match Blender (Commonly needed after bvh import)'''
bl_idname = "mocap.rotate_fix"
bl_label = "Rotates selected armature 90 degrees (fix for bvh import)"
@@ -637,6 +633,7 @@ class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
class OBJECT_OT_ScaleFixArmature(bpy.types.Operator):
+ #Operator to scale down the selected armature to match the active one
'''Rescale selected armature to match the active animation, for convienence'''
bl_idname = "mocap.scale_fix"
bl_label = "Scales performer armature to match target armature"
@@ -659,6 +656,7 @@ class OBJECT_OT_ScaleFixArmature(bpy.types.Operator):
class MOCAP_OT_AddMocapFix(bpy.types.Operator):
+ #Operator to add a post-retarget fix
'''Add a post-retarget fix - useful for fixing certain artifacts following the retarget'''
bl_idname = "mocap.addmocapfix"
bl_label = "Add Mocap Fix to target armature"
@@ -683,6 +681,7 @@ class MOCAP_OT_AddMocapFix(bpy.types.Operator):
class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
+ #Operator to remove a post-retarget fix
'''Remove this post-retarget fix'''
bl_idname = "mocap.removeconstraint"
bl_label = "Removes fixes from target armature"
@@ -707,6 +706,7 @@ class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
+ #Operator to bake all post-retarget fixes
'''Bake all post-retarget fixes to the Retarget Fixes NLA Track'''
bl_idname = "mocap.bakeconstraints"
bl_label = "Bake all fixes to target armature"
@@ -722,6 +722,7 @@ class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
class OBJECT_OT_UnbakeMocapConstraints(bpy.types.Operator):
+ #Operator to unbake all post-retarget fixes
'''Unbake all post-retarget fixes - removes the baked data from the Retarget Fixes NLA Track'''
bl_idname = "mocap.unbakeconstraints"
bl_label = "Unbake all fixes to target armature"
@@ -737,6 +738,8 @@ class OBJECT_OT_UnbakeMocapConstraints(bpy.types.Operator):
class OBJECT_OT_UpdateMocapConstraints(bpy.types.Operator):
+ #Operator to update all post-retarget fixes, similar to update dependencies on drivers
+ #Needed because python properties lack certain callbacks and some fixes take a while to recalculate.
'''Updates all post-retarget fixes - needed after changes to armature object or pose'''
bl_idname = "mocap.updateconstraints"
bl_label = "Updates all fixes to target armature - neccesary to take under consideration changes to armature object or pose"
@@ -752,6 +755,7 @@ class OBJECT_OT_UpdateMocapConstraints(bpy.types.Operator):
class OBJECT_OT_GuessHierachyMapping(bpy.types.Operator):
+ #Operator which calls heurisitic function to guess mapping between 2 armatures
'''Attemps to auto figure out hierarchy mapping'''
bl_idname = "mocap.guessmapping"
bl_label = "Attemps to auto figure out hierarchy mapping"
@@ -774,6 +778,7 @@ class OBJECT_OT_GuessHierachyMapping(bpy.types.Operator):
class OBJECT_OT_PathEditing(bpy.types.Operator):
+ #Operator which calls path editing function, making active object follow the selected curve.
'''Sets active object (stride object) to follow the selected curve'''
bl_idname = "mocap.pathediting"
bl_label = "Sets active object (stride object) to follow the selected curve"
@@ -793,6 +798,7 @@ class OBJECT_OT_PathEditing(bpy.types.Operator):
class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator):
+ #Operator which calls stitching function, combining 2 animations onto the NLA.
'''Stitches two defined animations into a single one via alignment of NLA Tracks'''
bl_idname = "mocap.animstitch"
bl_label = "Stitches two defined animations into a single one via alignment of NLA Tracks"
@@ -810,9 +816,10 @@ class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator):
stitch_settings = context.active_object.data.stitch_settings
return (stitch_settings.first_action and stitch_settings.second_action)
return False
-
+
class OBJECT_OT_GuessAnimationStitchingButton(bpy.types.Operator):
+ #Operator which calls stitching function heuristic, setting good values for above 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"
@@ -831,6 +838,7 @@ class OBJECT_OT_GuessAnimationStitchingButton(bpy.types.Operator):
return (stitch_settings.first_action and stitch_settings.second_action)
return False
+
def register():
bpy.utils.register_module(__name__)