* Removing mocap GSoC (is an addon already).

* Fixing ffmpeg-0.8 errors.
* Fixing Ketsji paths.
* Removing DoSound from BGE.
* Fixing audio scene update to use only current scene objects.
This commit is contained in:
Joerg Mueller 2011-08-28 14:21:44 +00:00
parent 8e12b7b054
commit b4b046995b
15 changed files with 20 additions and 2828 deletions

@ -706,8 +706,8 @@ elseif(WIN32)
if(WITH_CODEC_FFMPEG)
set(FFMPEG_INCLUDE_DIRS
${LIBDIR}/ffmpeg-0.8/include
${LIBDIR}/ffmpeg-0.8/include/msvc
${LIBDIR}/ffmpeg/include
${LIBDIR}/ffmpeg/include/msvc
)
set(FFMPEG_LIBRARIES
${LIBDIR}/ffmpeg/lib/avcodec-53.lib
@ -839,7 +839,7 @@ elseif(WIN32)
endif()
if(WITH_CODEC_FFMPEG)
set(FFMPEG ${LIBDIR}/ffmpeg-0.8)
set(FFMPEG ${LIBDIR}/ffmpeg)
set(FFMPEG_INCLUDE_DIRS ${FFMPEG}/include ${FFMPEG}/include)
set(FFMPEG_LIBRARIES avcodec-53 avformat-53 avdevice-53 avutil-51 swscale-2)
set(FFMPEG_LIBPATH ${FFMPEG}/lib)
@ -978,7 +978,7 @@ elseif(APPLE)
endif()
if(WITH_CODEC_FFMPEG)
set(FFMPEG ${LIBDIR}/ffmpeg-0.8)
set(FFMPEG ${LIBDIR}/ffmpeg)
set(FFMPEG_INCLUDE_DIRS ${FFMPEG}/include)
set(FFMPEG_LIBRARIES avcodec avdevice avformat avutil mp3lame swscale x264 xvidcore theora theoradec theoraenc vorbis vorbisenc vorbisfile ogg)
set(FFMPEG_LIBPATH ${FFMPEG}/lib)

@ -98,7 +98,7 @@ else:
# enable ffmpeg support
WITH_BF_FFMPEG = True # -DWITH_FFMPEG
BF_FFMPEG = LIBDIR + '/ffmpeg-0.8'
BF_FFMPEG = LIBDIR + '/ffmpeg'
BF_FFMPEG_INC = "${BF_FFMPEG}/include"
BF_FFMPEG_LIBPATH='${BF_FFMPEG}/lib'
BF_FFMPEG_LIB = 'avcodec avdevice avformat avutil mp3lame swscale x264 xvidcore theora theoradec theoraenc vorbis vorbisenc vorbisfile ogg bz2'

@ -3,7 +3,7 @@ LIBDIR = '${LCGDIR}'
# enable ffmpeg support
WITH_BF_FFMPEG = True # -DWITH_FFMPEG
BF_FFMPEG = LIBDIR +'/ffmpeg-0.8'
BF_FFMPEG = LIBDIR +'/ffmpeg'
BF_FFMPEG_INC = '${BF_FFMPEG}/include ${BF_FFMPEG}/include/msvc'
BF_FFMPEG_LIBPATH='${BF_FFMPEG}/lib'
BF_FFMPEG_LIB = 'avformat-53.lib avcodec-53.lib avdevice-53.lib avutil-51.lib swscale-2.lib'

@ -3,7 +3,7 @@ LIBDIR = '${LCGDIR}'
# enable ffmpeg support
WITH_BF_FFMPEG = True # -DWITH_FFMPEG
BF_FFMPEG = LIBDIR +'/ffmpeg-0.8'
BF_FFMPEG = LIBDIR +'/ffmpeg'
BF_FFMPEG_INC = '${BF_FFMPEG}/include ${BF_FFMPEG}/include/msvc '
BF_FFMPEG_LIBPATH='${BF_FFMPEG}/lib'
BF_FFMPEG_LIB = 'avformat-53.lib avcodec-53.lib avdevice-53.lib avutil-51.lib swscale-2.lib'

@ -1,434 +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>
import bpy
from mathutils import *
from bl_operators import nla
from retarget import hasIKConstraint
### Utility Functions
def getConsObj(bone):
#utility function - returns related IK target if bone has IK
ik = [constraint for constraint in bone.constraints if constraint.type == "IK"]
if ik:
ik = ik[0]
cons_obj = ik.target
if ik.subtarget:
cons_obj = ik.target.pose.bones[ik.subtarget]
else:
cons_obj = bone
return cons_obj
def consObjToBone(cons_obj):
#Utility function - returns related bone from ik object
if cons_obj.name[-3:] == "Org":
return cons_obj.name[:-3]
else:
return cons_obj.name
### And and Remove Constraints (called from operators)
def addNewConstraint(m_constraint, cons_obj):
#Decide the correct Blender constraint according to the Mocap constraint type
if m_constraint.type == "point" or m_constraint.type == "freeze":
c_type = "LIMIT_LOCATION"
if m_constraint.type == "distance":
c_type = "LIMIT_DISTANCE"
if m_constraint.type == "floor":
c_type = "LIMIT_LOCATION"
#create and store the new constraint within m_constraint
real_constraint = cons_obj.constraints.new(c_type)
real_constraint.name = "Auto fixes " + str(len(cons_obj.constraints))
m_constraint.real_constraint_bone = consObjToBone(cons_obj)
m_constraint.real_constraint = real_constraint.name
#set the rest of the constraint properties
setConstraint(m_constraint, bpy.context)
def removeConstraint(m_constraint, cons_obj):
#remove the influence fcurve and Blender constraint
oldConstraint = cons_obj.constraints[m_constraint.real_constraint]
removeFcurves(cons_obj, bpy.context.active_object, oldConstraint, m_constraint)
cons_obj.constraints.remove(oldConstraint)
### Update functions. There are 3: UpdateType/Bone
### update framing (deals with changes in the desired frame range)
### And setConstraint which deals with the rest
def updateConstraintBoneType(m_constraint, context):
#If the constraint exists, we need to remove it
#from the old bone
obj = context.active_object
bones = obj.pose.bones
if m_constraint.real_constraint:
bone = bones[m_constraint.real_constraint_bone]
cons_obj = getConsObj(bone)
removeConstraint(m_constraint, cons_obj)
#Regardless, after that we create a new constraint
if m_constraint.constrained_bone:
bone = bones[m_constraint.constrained_bone]
cons_obj = getConsObj(bone)
addNewConstraint(m_constraint, cons_obj)
def setConstraintFraming(m_constraint, context):
obj = context.active_object
bones = obj.pose.bones
bone = bones[m_constraint.constrained_bone]
cons_obj = getConsObj(bone)
real_constraint = cons_obj.constraints[m_constraint.real_constraint]
#remove the old keyframes
removeFcurves(cons_obj, obj, real_constraint, m_constraint)
#set the new ones according to the m_constraint properties
s, e = m_constraint.s_frame, m_constraint.e_frame
s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
real_constraint.influence = 1
real_constraint.keyframe_insert(data_path="influence", frame=s)
real_constraint.keyframe_insert(data_path="influence", frame=e)
real_constraint.influence = 0
real_constraint.keyframe_insert(data_path="influence", frame=s - s_in)
real_constraint.keyframe_insert(data_path="influence", frame=e + s_out)
def removeFcurves(cons_obj, obj, real_constraint, m_constraint):
#Determine if the constrained object is a bone or an empty
if isinstance(cons_obj, bpy.types.PoseBone):
fcurves = obj.animation_data.action.fcurves
else:
fcurves = cons_obj.animation_data.action.fcurves
#Find the RNA data path of the constraint's influence
RNA_paths = []
RNA_paths.append(real_constraint.path_from_id("influence"))
if m_constraint.type == "floor" or m_constraint.type == "point":
RNA_paths += [real_constraint.path_from_id("max_x"), real_constraint.path_from_id("min_x")]
RNA_paths += [real_constraint.path_from_id("max_y"), real_constraint.path_from_id("min_y")]
RNA_paths += [real_constraint.path_from_id("max_z"), real_constraint.path_from_id("min_z")]
#Retrieve the correct fcurve via the RNA data path and remove it
fcurves_del = [fcurve for fcurve in fcurves if fcurve.data_path in RNA_paths]
#clear the fcurve and set the frames.
if fcurves_del:
for fcurve in fcurves_del:
fcurves.remove(fcurve)
#remove armature fcurves (if user keyframed m_constraint properties)
if obj.data.animation_data and m_constraint.type == "point":
if obj.data.animation_data.action:
path = m_constraint.path_from_id("targetPoint")
m_fcurves = [fcurve for fcurve in obj.data.animation_data.action.fcurves if fcurve.data_path == path]
for curve in m_fcurves:
obj.data.animation_data.action.fcurves.remove(curve)
#Utility function for copying property fcurves over
def copyFCurve(newCurve, oldCurve):
for point in oldCurve.keyframe_points:
newCurve.keyframe_points.insert(frame=point.co.x, value=point.co.y)
#Creates new fcurves for the constraint properties (for floor and point)
def createConstraintFCurves(cons_obj, obj, real_constraint):
if isinstance(cons_obj, bpy.types.PoseBone):
c_fcurves = obj.animation_data.action.fcurves
else:
c_fcurves = cons_obj.animation_data.action.fcurves
c_x_path = [real_constraint.path_from_id("max_x"), real_constraint.path_from_id("min_x")]
c_y_path = [real_constraint.path_from_id("max_y"), real_constraint.path_from_id("min_y")]
c_z_path = [real_constraint.path_from_id("max_z"), real_constraint.path_from_id("min_z")]
c_constraints_path = c_x_path + c_y_path + c_z_path
existing_curves = [fcurve for fcurve in c_fcurves if fcurve.data_path in c_constraints_path]
if existing_curves:
for curve in existing_curves:
c_fcurves.remove(curve)
xCurves, yCurves, zCurves = [], [], []
for path in c_constraints_path:
newCurve = c_fcurves.new(path)
if path in c_x_path:
xCurves.append(newCurve)
elif path in c_y_path:
yCurves.append(newCurve)
else:
zCurves.append(newCurve)
return xCurves, yCurves, zCurves
# Function that copies all settings from m_constraint to the real Blender constraints
# Is only called when blender constraint already exists
def setConstraint(m_constraint, context):
if not m_constraint.constrained_bone:
return
obj = context.active_object
bones = obj.pose.bones
bone = bones[m_constraint.constrained_bone]
cons_obj = getConsObj(bone)
real_constraint = cons_obj.constraints[m_constraint.real_constraint]
NLATracks = obj.data.mocapNLATracks[obj.data.active_mocap]
obj.animation_data.action = bpy.data.actions[NLATracks.auto_fix_track]
#frame changing section
setConstraintFraming(m_constraint, context)
s, e = m_constraint.s_frame, m_constraint.e_frame
s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
s -= s_in
e += s_out
#Set the blender constraint parameters
if m_constraint.type == "point":
constraint_settings = False # are fix settings keyframed?
if not m_constraint.targetSpace == "constrained_boneB":
real_constraint.owner_space = m_constraint.targetSpace
else:
real_constraint.owner_space = "LOCAL"
if obj.data.animation_data:
if obj.data.animation_data.action:
path = m_constraint.path_from_id("targetPoint")
m_fcurves = [fcurve for fcurve in obj.data.animation_data.action.fcurves if fcurve.data_path == path]
if m_fcurves:
constraint_settings = True
xCurves, yCurves, zCurves = createConstraintFCurves(cons_obj, obj, real_constraint)
for curve in xCurves:
copyFCurve(curve, m_fcurves[0])
for curve in yCurves:
copyFCurve(curve, m_fcurves[1])
for curve in zCurves:
copyFCurve(curve, m_fcurves[2])
if m_constraint.targetSpace == "constrained_boneB" and m_constraint.constrained_boneB:
c_frame = context.scene.frame_current
bakedPos = {}
src_bone = bones[m_constraint.constrained_boneB]
if not constraint_settings:
xCurves, yCurves, zCurves = createConstraintFCurves(cons_obj, obj, real_constraint)
print("please wait a moment, calculating fix")
for t in range(s, e):
context.scene.frame_set(t)
src_bone_pos = src_bone.matrix.to_translation()
bakedPos[t] = src_bone_pos + m_constraint.targetPoint # final position for constrained bone in object space
context.scene.frame_set(c_frame)
for frame in bakedPos.keys():
pos = bakedPos[frame]
for xCurve in xCurves:
xCurve.keyframe_points.insert(frame=frame, value=pos.x)
for yCurve in yCurves:
yCurve.keyframe_points.insert(frame=frame, value=pos.y)
for zCurve in zCurves:
zCurve.keyframe_points.insert(frame=frame, value=pos.z)
if not constraint_settings:
x, y, z = m_constraint.targetPoint
real_constraint.max_x = x
real_constraint.max_y = y
real_constraint.max_z = z
real_constraint.min_x = x
real_constraint.min_y = y
real_constraint.min_z = z
real_constraint.use_max_x = True
real_constraint.use_max_y = True
real_constraint.use_max_z = True
real_constraint.use_min_x = True
real_constraint.use_min_y = True
real_constraint.use_min_z = True
if m_constraint.type == "freeze":
real_constraint.owner_space = m_constraint.targetSpace
bpy.context.scene.frame_set(m_constraint.s_frame)
if isinstance(cons_obj, bpy.types.PoseBone):
x, y, z = cons_obj.bone.center + (cons_obj.bone.vector / 2) + obj.matrix_world.to_translation()
else:
x, y, z = cons_obj.matrix_world.to_translation()
real_constraint.max_x = x
real_constraint.max_y = y
real_constraint.max_z = z
real_constraint.min_x = x
real_constraint.min_y = y
real_constraint.min_z = z
real_constraint.use_max_x = True
real_constraint.use_max_y = True
real_constraint.use_max_z = True
real_constraint.use_min_x = True
real_constraint.use_min_y = True
real_constraint.use_min_z = True
if m_constraint.type == "distance" and m_constraint.constrained_boneB:
real_constraint.owner_space = "WORLD"
real_constraint.target = getConsObj(bones[m_constraint.constrained_boneB])
real_constraint.limit_mode = "LIMITDIST_ONSURFACE"
real_constraint.distance = m_constraint.targetDist
if m_constraint.type == "floor" and m_constraint.targetMesh:
real_constraint.mute = True
real_constraint.owner_space = "WORLD"
#calculate the positions thoughout the range
s, e = m_constraint.s_frame, m_constraint.e_frame
s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
s -= s_in
e += s_out
bakedPos = {}
floor = bpy.data.objects[m_constraint.targetMesh]
c_frame = context.scene.frame_current
print("please wait a moment, calculating fix")
for t in range(s, e):
context.scene.frame_set(t)
axis = Vector((0, 0, 100)) * obj.matrix_world.to_3x3()
offset = Vector((0, 0, m_constraint.targetDist)) * obj.matrix_world.to_3x3()
ray_origin = cons_obj.matrix_world.to_translation() - offset # world position of constrained bone
ray_target = ray_origin + axis
#convert ray points to floor's object space
ray_origin *= floor.matrix_world.inverted()
ray_target *= floor.matrix_world.inverted()
hit, nor, ind = floor.ray_cast(ray_origin, ray_target)
if hit != Vector((0, 0, 0)):
bakedPos[t] = (hit * floor.matrix_world)
bakedPos[t] += Vector((0, 0, m_constraint.targetDist))
else:
bakedPos[t] = cons_obj.matrix_world.to_translation()
context.scene.frame_set(c_frame)
#create keyframes for real constraint
xCurves, yCurves, zCurves = createConstraintFCurves(cons_obj, obj, real_constraint)
for frame in bakedPos.keys():
pos = bakedPos[frame]
for xCurve in xCurves:
xCurve.keyframe_points.insert(frame=frame, value=pos.x)
for yCurve in yCurves:
yCurve.keyframe_points.insert(frame=frame, value=pos.y)
for zCurve in zCurves:
zCurve.keyframe_points.insert(frame=frame, value=pos.z)
real_constraint.use_max_x = True
real_constraint.use_max_y = True
real_constraint.use_max_z = True
real_constraint.use_min_x = True
real_constraint.use_min_y = True
real_constraint.use_min_z = True
# active/baked check
real_constraint.mute = (not m_constraint.active)
def locBake(s_frame, e_frame, bones):
scene = bpy.context.scene
bakeDict = {}
for bone in bones:
bakeDict[bone.name] = {}
for t in range(s_frame, e_frame):
scene.frame_set(t)
for bone in bones:
bakeDict[bone.name][t] = bone.matrix.copy()
for t in range(s_frame, e_frame):
for bone in bones:
print(bone.bone.matrix_local.to_translation())
bone.matrix = bakeDict[bone.name][t]
bone.keyframe_insert("location", frame=t)
# Baking function which bakes all bones effected by the constraint
def bakeAllConstraints(obj, s_frame, e_frame, bones):
for bone in bones:
bone.bone.select = False
selectedBones = [] # Marks bones that need a full bake
simpleBake = [] # Marks bones that need only a location bake
for end_bone in bones:
if end_bone.name in [m_constraint.real_constraint_bone for m_constraint in obj.data.mocap_constraints]:
#For all bones that have a constraint:
ik = hasIKConstraint(end_bone)
cons_obj = getConsObj(end_bone)
if ik:
#If it's an auto generated IK:
if ik.chain_count == 0:
selectedBones += bones # Chain len 0, bake everything
else:
selectedBones += [end_bone] + end_bone.parent_recursive[:ik.chain_count - 1] # Bake the chain
else:
#It's either an FK bone which we should just bake
#OR a user created IK target bone
simpleBake += [end_bone]
for bone in selectedBones:
bone.bone.select = True
NLATracks = obj.data.mocapNLATracks[obj.data.active_mocap]
obj.animation_data.action = bpy.data.actions[NLATracks.auto_fix_track]
constraintTrack = obj.animation_data.nla_tracks[NLATracks.auto_fix_track]
constraintStrip = constraintTrack.strips[0]
constraintStrip.action_frame_start = s_frame
constraintStrip.action_frame_end = e_frame
constraintStrip.frame_start = s_frame
constraintStrip.frame_end = e_frame
if selectedBones:
#Use bake function from NLA Bake Action operator
nla.bake(s_frame, e_frame, action=constraintStrip.action, only_selected=True, do_pose=True, do_object=False)
if simpleBake:
#Do a "simple" bake, location only, world space only.
locBake(s_frame, e_frame, simpleBake)
#Calls the baking function and decativates releveant constraints
def bakeConstraints(context):
obj = context.active_object
bones = obj.pose.bones
s_frame, e_frame = context.scene.frame_start, context.scene.frame_end
#Bake relevant bones
bakeAllConstraints(obj, s_frame, e_frame, bones)
for m_constraint in obj.data.mocap_constraints:
end_bone = bones[m_constraint.real_constraint_bone]
cons_obj = getConsObj(end_bone)
# It's a control empty: turn the ik off
if not isinstance(cons_obj, bpy.types.PoseBone):
ik_con = hasIKConstraint(end_bone)
if ik_con:
ik_con.mute = True
# Deactivate related Blender Constraint
m_constraint.active = False
#Deletes the baked fcurves and reactivates relevant constraints
def unbakeConstraints(context):
# to unbake constraints we delete the whole strip
obj = context.active_object
bones = obj.pose.bones
scene = bpy.context.scene
NLATracks = obj.data.mocapNLATracks[obj.data.active_mocap]
obj.animation_data.action = bpy.data.actions[NLATracks.auto_fix_track]
constraintTrack = obj.animation_data.nla_tracks[NLATracks.auto_fix_track]
constraintStrip = constraintTrack.strips[0]
action = constraintStrip.action
# delete the fcurves on the strip
for fcurve in action.fcurves:
action.fcurves.remove(fcurve)
# reactivate relevant constraints
for m_constraint in obj.data.mocap_constraints:
end_bone = bones[m_constraint.real_constraint_bone]
cons_obj = getConsObj(end_bone)
# It's a control empty: turn the ik back on
if not isinstance(cons_obj, bpy.types.PoseBone):
ik_con = hasIKConstraint(end_bone)
if ik_con:
ik_con.mute = False
m_constraint.active = True
def updateConstraints(obj, context):
fixes = obj.data.mocap_constraints
for fix in fixes:
fix.active = False
fix.active = True

@ -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

@ -1,559 +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>
import bpy
from mathutils import *
from math import radians, acos, pi
from bl_operators import nla
def hasIKConstraint(pose_bone):
#utility function / predicate, returns True if given bone has IK constraint
ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
if ik:
return ik[0]
else:
return False
def createDictionary(perf_arm, end_arm):
# clear any old data
for end_bone in end_arm.bones:
for mapping in end_bone.reverseMap:
end_bone.reverseMap.remove(0)
for perf_bone in perf_arm.bones:
#find its match and add perf_bone to the match's mapping
if perf_bone.map:
end_bone = end_arm.bones[perf_bone.map]
newMap = end_bone.reverseMap.add()
newMap.name = perf_bone.name
end_bone.foot = perf_bone.foot
#root is the root of the enduser
root = end_arm.bones[0].name
feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
return feetBones, root
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:
for perf_bone in end_bone.reverseMap:
perf_arm.bones[perf_bone.name].map = end_bone.name
#creation of intermediate armature
# the intermediate armature has the hiearchy of the end user,
# does not have rotation inheritence
# and bone roll is identical to the performer
# its purpose is to copy over the rotations
# easily while concentrating on the hierarchy changes
def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step):
#creates and keyframes an empty with its location
#the original position of the tail bone
#useful for storing the important data in the original motion
#i.e. using this empty to IK the chain to that pos / DEBUG
#Simple 1to1 retarget of a bone
def singleBoneRetarget(inter_bone, perf_bone):
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())
return bake_matrix.to_4x4()
#uses 1to1 and interpolation/averaging to match many to 1 retarget
def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
lerp_matrix = Matrix()
for i in range(len(retarget_matrices) - 1):
first_mat = retarget_matrices[i]
next_mat = retarget_matrices[i + 1]
lerp_matrix = first_mat.lerp(next_mat, 0.5)
return lerp_matrix
#determines the type of hierachy change needed and calls the
#right function
def retargetPerfToInter(inter_bone):
if inter_bone.bone.reverseMap:
perf_bone_name = inter_bone.bone.reverseMap
# 1 to many not supported yet
# then its either a many to 1 or 1 to 1
if len(perf_bone_name) > 1:
performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
#we need to map several performance bone to a single
inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
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
if rot_mode == "QUATERNION":
inter_bone.keyframe_insert("rotation_quaternion")
elif rot_mode == "AXIS_ANGLE":
inter_bone.keyframe_insert("rotation_axis_angle")
else:
inter_bone.keyframe_insert("rotation_euler")
#creates the intermediate armature object
inter_obj = enduser_obj.copy()
inter_obj.data = inter_obj.data.copy() # duplicate data
bpy.context.scene.objects.link(inter_obj)
inter_obj.name = "intermediate"
bpy.context.scene.objects.active = inter_obj
bpy.ops.object.mode_set(mode='EDIT')
#add some temporary connecting bones in case end user bones are not connected to their parents
rollDict = {}
print("creating temp bones")
for bone in inter_obj.data.edit_bones:
if not bone.use_connect and bone.parent:
if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
newBone = inter_obj.data.edit_bones.new("Temp")
newBone.head = bone.parent.tail
newBone.tail = bone.head
newBone.parent = bone.parent
bone.parent = newBone
bone.use_connect = True
newBone.use_connect = True
rollDict[bone.name] = bone.roll
bone.roll = 0
#resets roll
print("retargeting to intermediate")
bpy.ops.object.mode_set(mode="OBJECT")
inter_obj.data.name = "inter_arm"
inter_arm = inter_obj.data
performer_bones = performer_obj.pose.bones
inter_bones = inter_obj.pose.bones
#clears inheritance
for inter_bone in inter_bones:
if inter_bone.bone.reverseMap:
inter_bone.bone.use_inherit_rotation = False
else:
inter_bone.bone.use_inherit_rotation = True
for t in range(s_frame, e_frame, step):
if (t - s_frame) % 10 == 0:
print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
scene.frame_set(t)
for bone in inter_bones:
retargetPerfToInter(bone)
return inter_obj
# this procedure copies the rotations over from the intermediate
# armature to the end user one.
# As the hierarchies are 1 to 1, this is a simple matter of
# copying the rotation, while keeping in mind bone roll, parenting, etc.
# TODO: Control Bones: If a certain bone is constrained in a way
# that its rotation is determined by another (a control bone)
# We should determine the right pos of the control bone.
# Scale: ? Should work but needs testing.
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
bake_matrix = src_bone.matrix
rest_matrix = trg_bone.bone.matrix_local
if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
srcParent = src_bone.parent
if "Temp" in srcParent.name:
srcParent = srcParent.parent
parent_mat = srcParent.matrix
parent_rest = trg_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
end_bone.matrix_basis = bake_matrix
rot_mode = end_bone.rotation_mode
if rot_mode == "QUATERNION":
end_bone.keyframe_insert("rotation_quaternion")
elif rot_mode == "AXIS_ANGLE":
end_bone.keyframe_insert("rotation_axis_angle")
else:
end_bone.keyframe_insert("rotation_euler")
if not end_bone.bone.use_connect:
end_bone.keyframe_insert("location")
for bone in end_bone.children:
bakeTransform(bone)
for t in range(s_frame, e_frame, step):
if (t - s_frame) % 10 == 0:
print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
scene.frame_set(t)
end_bone = end_bones[root]
end_bone.location = Vector((0, 0, 0))
end_bone.keyframe_insert("location")
bakeTransform(end_bone)
#recieves the performer feet bones as a variable
# by "feet" I mean those bones that have plants
# (they don't move, despite root moving) somewhere in the animation.
def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
perf_bones = performer_obj.pose.bones
end_bones = enduser_obj.pose.bones
perfRoot = perf_bones[0].name
endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
locDictKeys = perfFeet + endFeet + [perfRoot]
def tailLoc(bone):
return bone.center + (bone.vector / 2)
#Step 1 - we create a dict that contains these keys:
#(Performer) Hips, Feet
#(End user) Feet
# where the values are their world position on each frame in range (s,e)
locDict = {}
for key in locDictKeys:
locDict[key] = []
for t in range(scene.frame_start, scene.frame_end):
scene.frame_set(t)
for bone in perfFeet:
locDict[bone].append(tailLoc(perf_bones[bone]))
locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
for bone in endFeet:
locDict[bone].append(tailLoc(end_bones[bone]))
# 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]
# 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)
if (v.length < 0.1):
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
if endV.length != 0:
linearAvg.append(hipV.length / endV.length)
action_name = performer_obj.animation_data.action.name
#is there a stride_bone?
if "stride_bone" in bpy.data.objects:
stride_action = bpy.data.actions.new("Stride Bone " + action_name)
stride_action.use_fake_user = True
stride_bone = enduser_obj.parent
stride_bone.animation_data.action = stride_action
else:
bpy.ops.object.add()
stride_bone = bpy.context.active_object
stride_bone.name = "stride_bone"
print(stride_bone)
stride_bone.location = enduser_obj_mat.to_translation()
print(linearAvg)
if linearAvg:
#determine the average change in scale needed
avg = sum(linearAvg) / len(linearAvg)
scene.frame_set(s_frame)
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
newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
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)
return stride_bone
def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
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)
if ik_constraint:
target_is_bone = False
# 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 = enduser_obj
ik_constraint.subtarget = pose_bone.name + "IK"
target = orgLocTrg
# There is a target now
if ik_constraint.subtarget:
target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
target.bone.use_local_location = False
target_is_bone = True
else:
target = ik_constraint.target
# bake the correct locations for the ik target bones
for t in range(s_frame, e_frame, step):
scene.frame_set(t)
if target_is_bone:
final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
else:
final_loc = pose_bone.tail
target.location = final_loc
target.keyframe_insert("location")
ik_constraint.mute = False
scene.frame_set(s_frame)
bpy.ops.object.mode_set(mode='OBJECT')
def turnOffIK(enduser_obj):
end_bones = enduser_obj.pose.bones
for pose_bone in end_bones:
ik_constraint = hasIKConstraint(pose_bone)
if ik_constraint:
ik_constraint.mute = True
#copy the object matrixes and clear them (to be reinserted later)
def cleanAndStoreObjMat(performer_obj, enduser_obj):
perf_obj_mat = performer_obj.matrix_world.copy()
enduser_obj_mat = enduser_obj.matrix_world.copy()
zero_mat = Matrix()
performer_obj.matrix_world = zero_mat
enduser_obj.matrix_world = zero_mat
return perf_obj_mat, enduser_obj_mat
#restore the object matrixes after parenting the auto generated IK empties
def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
pose_bones = enduser_obj.pose.bones
for pose_bone in pose_bones:
if pose_bone.name + "Org" in bpy.data.objects:
empty = bpy.data.objects[pose_bone.name + "Org"]
empty.parent = stride_bone
performer_obj.matrix_world = perf_obj_mat
enduser_obj.parent = stride_bone
scene.frame_set(s_frame)
enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() * Matrix.Translation(stride_bone.matrix_world.to_translation())
enduser_obj.matrix_world = enduser_obj_mat
#create (or return if exists) the related IK empty to the bone
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))
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 = context.active_object
NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
name = NLATracks.name
anim_data = enduser_obj.animation_data
s_frame = 0
print(name)
if ("Base " + name) in bpy.data.actions:
mocapAction = bpy.data.actions[("Base " + name)]
else:
print("That retargeted anim has no base action")
anim_data.use_nla = True
for track in anim_data.nla_tracks:
anim_data.nla_tracks.remove(track)
mocapTrack = anim_data.nla_tracks.new()
mocapTrack.name = "Base " + name
NLATracks.base_track = mocapTrack.name
mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
constraintTrack = anim_data.nla_tracks.new()
constraintTrack.name = "Auto fixes " + name
NLATracks.auto_fix_track = constraintTrack.name
if ("Auto fixes " + name) in bpy.data.actions:
constraintAction = bpy.data.actions[("Auto fixes " + name)]
else:
constraintAction = bpy.data.actions.new("Auto fixes " + name)
constraintAction.use_fake_user = True
constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
constraintStrip.extrapolation = "NOTHING"
userTrack = anim_data.nla_tracks.new()
userTrack.name = "Manual fixes " + name
NLATracks.manual_fix_track = userTrack.name
if ("Manual fixes " + name) in bpy.data.actions:
userAction = bpy.data.actions[("Manual fixes " + name)]
else:
userAction = bpy.data.actions.new("Manual fixes " + name)
userAction.use_fake_user = True
userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
userStrip.extrapolation = "HOLD"
userStrip.blend_type = "ADD"
anim_data.nla_tracks.active = constraintTrack
anim_data.action_extrapolation = "NOTHING"
#set the stride_bone's action
if "stride_bone" in bpy.data.objects:
stride_bone = bpy.data.objects["stride_bone"]
if NLATracks.stride_action:
stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
else:
NLATracks.stride_action = stride_bone.animation_data.action.name
stride_bone.animation_data.action.use_fake_user = True
anim_data.action = None
def preAdvancedRetargeting(performer_obj, enduser_obj):
createDictionary(performer_obj.data, enduser_obj.data)
bones = enduser_obj.pose.bones
map_bones = [bone for bone in bones if bone.bone.reverseMap]
perf_root = performer_obj.pose.bones[0].name
for bone in map_bones:
perf_bone = bone.bone.reverseMap[0].name
cons = bone.constraints.new('COPY_ROTATION')
cons.name = "retargetTemp"
locks = bone.lock_rotation
cons.use_x = not locks[0]
cons.use_y = not locks[1]
cons.use_z = not locks[2]
cons.target = performer_obj
cons.subtarget = perf_bone
cons.target_space = 'WORLD'
cons.owner_space = 'WORLD'
if (not bone.bone.use_connect) and (perf_bone != perf_root):
cons = bone.constraints.new('COPY_LOCATION')
cons.name = "retargetTemp"
cons.target = performer_obj
cons.subtarget = perf_bone
cons.use_x = True
cons.use_y = True
cons.use_z = True
cons.target_space = 'LOCAL'
cons.owner_space = 'LOCAL'
def prepareForBake(enduser_obj):
bones = enduser_obj.pose.bones
for bone in bones:
bone.bone.select = False
map_bones = [bone for bone in bones if bone.bone.reverseMap]
for bone in map_bones:
for cons in bone.constraints:
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]
for bone in map_bones:
for cons in bone.constraints:
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):
perf_arm = performer_obj.data
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")
perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
if not advanced:
turnOffIK(enduser_obj)
print("Creating intermediate armature (for first pass)")
inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step)
print("First pass: retargeting from intermediate to end user")
retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step)
else:
prepareForBake(enduser_obj)
print("Retargeting pose (Advanced Retarget)")
nla.bake(s_frame, e_frame, action=enduser_obj.animation_data.action, only_selected=True, do_pose=True, do_object=False, step=step)
name = performer_obj.animation_data.action.name
enduser_obj.animation_data.action.name = "Base " + name
print("Second pass: retargeting root translation and clean up")
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, step)
bpy.ops.object.select_name(name=stride_bone.name, extend=False)
restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame)
bpy.ops.object.mode_set(mode='OBJECT')
if not advanced:
bpy.ops.object.select_name(name=inter_obj.name, extend=False)
bpy.ops.object.delete()
else:
cleanTempConstraints(enduser_obj)
bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
NLATracks = end_arm.mocapNLATracks.add()
NLATracks.name = name
else:
NLATracks = end_arm.mocapNLATracks[name]
end_arm.active_mocap = name
print("retargeting done!")
def isRigAdvanced(enduser_obj):
bones = enduser_obj.pose.bones
for bone in bones:
for constraint in bone.constraints:
if constraint.type != "IK":
return True
if enduser_obj.data.animation_data:
if enduser_obj.data.animation_data.drivers:
return True

@ -1,850 +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>
import bpy
from bpy.props import *
from bpy import *
import mocap_constraints
import retarget
import mocap_tools
### reloads modules (for testing purposes only)
from imp import reload
reload(mocap_constraints)
reload(retarget)
reload(mocap_tools)
from mocap_constraints import *
# MocapConstraint class
# Defines MocapConstraint datatype, used to add and configute mocap constraints
# Attached to Armature data
class MocapConstraint(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(name="Name",
default="Mocap Fix",
description="Name of Mocap Fix",
update=setConstraint)
constrained_bone = bpy.props.StringProperty(name="Bone",
default="",
description="Constrained Bone",
update=updateConstraintBoneType)
constrained_boneB = bpy.props.StringProperty(name="Bone (2)",
default="",
description="Other Constrained Bone (optional, depends on type)",
update=setConstraint)
s_frame = bpy.props.IntProperty(name="S",
default=0,
description="Start frame of Fix",
update=setConstraint)
e_frame = bpy.props.IntProperty(name="E",
default=100,
description="End frame of Fix",
update=setConstraint)
smooth_in = bpy.props.IntProperty(name="In",
default=10,
description="Amount of frames to smooth in",
update=setConstraint,
min=0)
smooth_out = bpy.props.IntProperty(name="Out",
default=10,
description="Amount of frames to smooth out",
update=setConstraint,
min=0)
targetMesh = bpy.props.StringProperty(name="Mesh",
default="",
description="Target of Fix - Mesh (optional, depends on type)",
update=setConstraint)
active = bpy.props.BoolProperty(name="Active",
default=True,
description="Fix is active",
update=setConstraint)
show_expanded = bpy.props.BoolProperty(name="Show Expanded",
default=True,
description="Fix is fully shown")
targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
subtype="XYZ", default=(0.0, 0.0, 0.0),
description="Target of Fix - Point",
update=setConstraint)
targetDist = bpy.props.FloatProperty(name="Offset",
default=0.0,
description="Distance and Floor Fixes - Desired offset",
update=setConstraint)
targetSpace = bpy.props.EnumProperty(
items=[("WORLD", "World Space", "Evaluate target in global space"),
("LOCAL", "Object space", "Evaluate target in object space"),
("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
name="Space",
description="In which space should Point type target be evaluated",
update=setConstraint)
type = bpy.props.EnumProperty(name="Type of constraint",
items=[("point", "Maintain Position", "Bone is at a specific point"),
("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
("distance", "Maintain distance", "Target bones maintained specified distance")],
description="Type of Fix",
update=updateConstraintBoneType)
real_constraint = bpy.props.StringProperty()
real_constraint_bone = bpy.props.StringProperty()
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")
second_action = bpy.props.StringProperty(name="Action 2",
description="Second action in stitch")
blend_frame = bpy.props.IntProperty(name="Stitch frame",
description="Frame to locate stitch on")
blend_amount = bpy.props.IntProperty(name="Blend amount",
description="Size of blending transitiion, on both sides of the stitch",
default=10)
second_offset = bpy.props.IntProperty(name="Second offset",
description="Frame offset for 2nd animation, where it should start",
default=10)
stick_bone = bpy.props.StringProperty(name="Stick Bone",
description="Bone to freeze during transition",
default="")
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()
auto_fix_track = bpy.props.StringProperty()
manual_fix_track = bpy.props.StringProperty()
stride_action = bpy.props.StringProperty()
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]
if enduser_obj is None or len(performer_obj) != 1:
print("Need active and selected armatures")
return
else:
performer_obj = performer_obj[0]
if self.advancedRetarget:
retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
else:
retarget.cleanTempConstraints(enduser_obj)
#Animation Stitch Settings Property
bpy.types.Armature.stitch_settings = bpy.props.PointerProperty(type=AnimationStitchSettings)
#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)
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!")
ik = self.constraints.new('IK')
#ik the whole chain up to the root, excluding
chainLen = 0
for parent_bone in self.parent_recursive:
chainLen += 1
if hasIKConstraint(parent_bone):
break
deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
#~ if len(deformer_children) > 1:
#~ break
ik.chain_count = chainLen
for bone in self.parent_recursive:
if bone.is_in_ik_chain:
bone.IKRetarget = True
else:
print(self.name + " IK toggled OFF!")
cnstrn_bones = []
newChainLength = []
if hasIKConstraint(self):
cnstrn_bones = [self]
elif self.is_in_ik_chain:
cnstrn_bones = [child for child in self.children_recursive if hasIKConstraint(child)]
for cnstrn_bone in cnstrn_bones:
newChainLength.append(cnstrn_bone.parent_recursive.index(self) + 1)
if cnstrn_bones:
# remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
for i, cnstrn_bone in enumerate(cnstrn_bones):
print(cnstrn_bone.name)
if newChainLength:
ik = hasIKConstraint(cnstrn_bone)
ik.chain_count = newChainLength[i]
else:
ik = hasIKConstraint(cnstrn_bone)
cnstrn_bone.constraints.remove(ik)
cnstrn_bone.IKRetarget = False
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)
def updateIKRetarget():
# ensures that Blender constraints and IK properties are in sync
# currently runs when module is loaded, should run when scene is loaded
# or user adds a constraint to armature. Will be corrected in the future,
# once python callbacks are implemented
for obj in bpy.data.objects:
if obj.pose:
bones = obj.pose.bones
for pose_bone in bones:
if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
pose_bone.IKRetarget = True
else:
pose_bone.IKRetarget = False
updateIKRetarget()
class MocapPanel(bpy.types.Panel):
# Motion capture retargeting panel
bl_label = "Mocap tools"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
self.layout.label("Preprocessing")
row = self.layout.row(align=True)
row.alignment = 'EXPAND'
row.operator("mocap.samples", text='Samples to Beziers')
row.operator("mocap.denoise", text='Clean noise')
row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
row.operator("mocap.scale_fix", text='Auto scale Performer')
row2 = self.layout.row(align=True)
row2.operator("mocap.looper", text='Loop animation')
row2.operator("mocap.limitdof", text='Constrain Rig')
row2.operator("mocap.removelimitdof", text='Unconstrain Rig')
self.layout.label("Retargeting")
enduser_obj = bpy.context.active_object
performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
if enduser_obj is None or len(performer_obj) != 1:
self.layout.label("Select performer rig and target rig (as active)")
else:
self.layout.operator("mocap.guessmapping", text="Guess Hiearchy Mapping")
row3 = self.layout.row(align=True)
column1 = row3.column(align=True)
column1.label("Performer Rig")
column2 = row3.column(align=True)
column2.label("Enduser Rig")
performer_obj = performer_obj[0]
if performer_obj.data and enduser_obj.data:
if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
perf = performer_obj.data
enduser_arm = enduser_obj.data
perf_pose_bones = enduser_obj.pose.bones
for bone in perf.bones:
row = self.layout.row()
row.prop(data=bone, property='foot', text='', icon='POSE_DATA')
row.label(bone.name)
row.prop_search(bone, "map", enduser_arm, "bones")
row.operator("mocap.selectmap", text='', icon='CURSOR').perf_bone = bone.name
label_mod = "FK"
if bone.map:
pose_bone = perf_pose_bones[bone.map]
if pose_bone.is_in_ik_chain:
label_mod = "ik chain"
if hasIKConstraint(pose_bone):
label_mod = "ik end"
row.prop(data=bone, property='twistFix', text='', icon='RNA')
row.prop(pose_bone, 'IKRetarget')
row.label(label_mod)
else:
row.label(" ")
row.label(" ")
mapRow = self.layout.row()
mapRow.operator("mocap.savemapping", text='Save mapping')
mapRow.operator("mocap.loadmapping", text='Load mapping')
self.layout.prop(data=performer_obj.animation_data.action, property='name', text='Action Name')
self.layout.prop(enduser_arm, "advancedRetarget", text='Advanced Retarget')
self.layout.prop(enduser_arm, "frameStep")
self.layout.operator("mocap.retarget", text='RETARGET!')
class MocapConstraintsPanel(bpy.types.Panel):
#Motion capture constraints panel
bl_label = "Mocap Fixes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
layout = self.layout
if context.active_object:
if context.active_object.data:
if context.active_object.data.name in bpy.data.armatures:
enduser_obj = context.active_object
enduser_arm = enduser_obj.data
layout.operator_menu_enum("mocap.addmocapfix", "type")
layout.operator("mocap.updateconstraints", text='Update Fixes')
bakeRow = layout.row()
bakeRow.operator("mocap.bakeconstraints", text='Bake Fixes')
bakeRow.operator("mocap.unbakeconstraints", text='Unbake Fixes')
layout.separator()
for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
box = layout.box()
headerRow = box.row()
headerRow.prop(m_constraint, 'show_expanded', text='', icon='TRIA_DOWN' if m_constraint.show_expanded else 'TRIA_RIGHT', emboss=False)
headerRow.prop(m_constraint, 'type', text='')
headerRow.prop(m_constraint, 'name', text='')
headerRow.prop(m_constraint, 'active', icon='MUTE_IPO_ON' if not m_constraint.active else'MUTE_IPO_OFF', text='', emboss=False)
headerRow.operator("mocap.removeconstraint", text="", icon='X', emboss=False).constraint = i
if m_constraint.show_expanded:
box.separator()
box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones", icon='BONE_DATA')
if m_constraint.type == "distance" or m_constraint.type == "point":
box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones", icon='CONSTRAINT_BONE')
frameRow = box.row()
frameRow.label("Frame Range:")
frameRow.prop(m_constraint, 's_frame')
frameRow.prop(m_constraint, 'e_frame')
smoothRow = box.row()
smoothRow.label("Smoothing:")
smoothRow.prop(m_constraint, 'smooth_in')
smoothRow.prop(m_constraint, 'smooth_out')
targetRow = box.row()
targetLabelCol = targetRow.column()
targetLabelCol.label("Target settings:")
targetPropCol = targetRow.column()
if m_constraint.type == "floor":
targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
if m_constraint.type == "point" or m_constraint.type == "freeze":
box.prop(m_constraint, 'targetSpace')
if m_constraint.type == "point":
targetPropCol.prop(m_constraint, 'targetPoint')
if m_constraint.type == "distance" or m_constraint.type == "floor":
targetPropCol.prop(m_constraint, 'targetDist')
layout.separator()
class ExtraToolsPanel(bpy.types.Panel):
# Motion capture retargeting panel
bl_label = "Extra Mocap Tools"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
layout = self.layout
layout.operator('mocap.pathediting', text="Follow Path")
layout.label("Animation Stitching")
activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
if activeIsArmature:
enduser_arm = context.active_object.data
layout.label("Retargeted Animations:")
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")
layout.prop(settings, "blend_frame")
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")
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"
bl_options = {'REGISTER', 'UNDO'}
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]
s_frame, e_frame = performer_obj.animation_data.action.frame_range
s_frame = int(s_frame)
e_frame = int(e_frame)
if retarget.isRigAdvanced(enduser_obj) and not enduser_obj.data.advancedRetarget:
print("Recommended to use Advanced Retargeting method")
enduser_obj.data.advancedRetarget = True
else:
retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
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) and performer_obj[0].animation_data
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"
def execute(self, context):
enduser_obj = bpy.context.active_object
performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
retarget.createDictionary(performer_obj.data, enduser_obj.data)
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_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"
def execute(self, context):
enduser_obj = bpy.context.active_object
performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
retarget.loadMapping(performer_obj.data, enduser_obj.data)
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_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"
perf_bone = bpy.props.StringProperty()
def execute(self, context):
enduser_obj = bpy.context.active_object
performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
selectedBone = ""
for bone in enduser_obj.data.bones:
boneVis = bone.layers
for i in range(32):
if boneVis[i] and enduser_obj.data.layers[i]:
if bone.select:
selectedBone = bone.name
break
performer_obj.data.bones[self.perf_bone].map = selectedBone
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_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"
def execute(self, context):
mocap_tools.fcurves_simplify(context, context.active_object)
return {"FINISHED"}
@classmethod
def poll(cls, context):
return context.active_object.animation_data
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"
def execute(self, context):
mocap_tools.autoloop_anim()
return {"FINISHED"}
@classmethod
def poll(cls, context):
return context.active_object.animation_data
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 "
def execute(self, context):
mocap_tools.denoise_median()
return {"FINISHED"}
@classmethod
def poll(cls, context):
return context.active_object
@classmethod
def poll(cls, context):
return context.active_object.animation_data
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"
def execute(self, context):
performer_obj = [obj for obj in context.selected_objects if obj != context.active_object][0]
mocap_tools.limit_dof(context, performer_obj, context.active_object)
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_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"
def execute(self, context):
mocap_tools.limit_dof_toggle_off(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)
return activeIsArmature
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)"
def execute(self, context):
mocap_tools.rotate_fix_armature(context.active_object.data)
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
return isinstance(context.active_object.data, bpy.types.Armature)
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"
def execute(self, context):
enduser_obj = bpy.context.active_object
performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
mocap_tools.scale_fix_armature(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 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"
type = bpy.props.EnumProperty(name="Type of Fix",
items=[("point", "Maintain Position", "Bone is at a specific point"),
("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
("distance", "Maintain distance", "Target bones maintained specified distance")],
description="Type of fix")
def execute(self, context):
enduser_obj = bpy.context.active_object
enduser_arm = enduser_obj.data
new_mcon = enduser_arm.mocap_constraints.add()
new_mcon.type = self.type
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
return isinstance(context.active_object.data, bpy.types.Armature)
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"
constraint = bpy.props.IntProperty()
def execute(self, context):
enduser_obj = bpy.context.active_object
enduser_arm = enduser_obj.data
m_constraints = enduser_arm.mocap_constraints
m_constraint = m_constraints[self.constraint]
if m_constraint.real_constraint:
bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
cons_obj = getConsObj(bone)
removeConstraint(m_constraint, cons_obj)
m_constraints.remove(self.constraint)
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
return isinstance(context.active_object.data, bpy.types.Armature)
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"
def execute(self, context):
bakeConstraints(context)
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
return isinstance(context.active_object.data, bpy.types.Armature)
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"
def execute(self, context):
unbakeConstraints(context)
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
return isinstance(context.active_object.data, bpy.types.Armature)
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"
def execute(self, context):
updateConstraints(context.active_object, context)
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
return isinstance(context.active_object.data, bpy.types.Armature)
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"
def execute(self, context):
enduser_obj = bpy.context.active_object
performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
mocap_tools.guessMapping(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_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"
def execute(self, context):
path = [obj for obj in context.selected_objects if obj != context.active_object][0]
mocap_tools.path_editing(context, context.active_object, path)
return {"FINISHED"}
@classmethod
def poll(cls, context):
if context.active_object:
selected_objs = [obj for obj in context.selected_objects if obj != context.active_object and isinstance(obj.data, bpy.types.Curve)]
return selected_objs
else:
return False
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"
def execute(self, context):
mocap_tools.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
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"
def execute(self, context):
mocap_tools.guess_anim_stitch(context, context.active_object)
return {"FINISHED"}
@classmethod
def poll(cls, context):
activeIsArmature = False
if context.active_object:
activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
if activeIsArmature:
stitch_settings = context.active_object.data.stitch_settings
return (stitch_settings.first_action and stitch_settings.second_action)
return False
def register():
bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()

@ -136,7 +136,7 @@ void sound_read_waveform(struct bSound* sound);
int sound_get_channels(struct bSound* sound);
void sound_update_scene(struct Main* bmain, struct Scene* scene);
void sound_update_scene(struct Scene* scene);
void* sound_get_factory(void* sound);

@ -968,7 +968,7 @@ static void scene_update_tagged_recursive(Main *bmain, Scene *scene, Scene *scen
scene_update_drivers(bmain, scene);
/* update sound system animation */
sound_update_scene(bmain, scene);
sound_update_scene(scene);
}
/* this is called in main loop, doing tagged updates before redraw */

@ -3150,18 +3150,14 @@ void seq_update_sound_bounds_all(Scene *scene)
{
Editing *ed = scene->ed;
if(ed)
{
if(ed) {
Sequence *seq;
for(seq = ed->seqbase.first; seq; seq = seq->next)
{
if(seq->type == SEQ_META)
{
for(seq = ed->seqbase.first; seq; seq = seq->next) {
if(seq->type == SEQ_META) {
seq_update_sound_bounds_recursive(scene, seq);
}
else if(ELEM(seq->type, SEQ_SOUND, SEQ_SCENE))
{
else if(ELEM(seq->type, SEQ_SOUND, SEQ_SCENE)) {
seq_update_sound_bounds(scene, seq);
}
}
@ -3170,8 +3166,7 @@ void seq_update_sound_bounds_all(Scene *scene)
void seq_update_sound_bounds(Scene* scene, Sequence *seq)
{
if(seq->scene_sound)
{
if(seq->scene_sound) {
sound_move_scene_sound(scene, seq->scene_sound, seq->startdisp, seq->enddisp, seq->startofs + seq->anim_startofs);
/* mute is set in seq_update_muting_recursive */
}

@ -24,6 +24,7 @@
#include "DNA_sound_types.h"
#include "DNA_speaker_types.h"
#define WITH_AUDASPACE
#ifdef WITH_AUDASPACE
# include "AUD_C-API.h"
#endif
@ -649,9 +650,10 @@ int sound_get_channels(struct bSound* sound)
return info.specs.channels;
}
void sound_update_scene(struct Main* bmain, struct Scene* scene)
void sound_update_scene(struct Scene* scene)
{
Object* ob;
Base* base;
NlaTrack* track;
NlaStrip* strip;
Speaker* speaker;
@ -660,8 +662,9 @@ void sound_update_scene(struct Main* bmain, struct Scene* scene)
void* handle;
float quat[4];
for(ob = bmain->object.first; ob; ob = ob->id.next)
for(base = FIRSTBASE; base; base=base->next)
{
ob = base->object;
if(ob->type == OB_SPEAKER)
{
if(ob->adt)

@ -45,37 +45,14 @@ set(INC
../../blender/gpu
../../blender/imbuf
../../blender/makesdna
../../blender/makesrna
../../blender/python
../../blender/python/generic
../../blender/python/mathutils
../../../intern/container
../../../intern/guardedalloc
../../../intern/container
../../../source/gameengine/Rasterizer/RAS_OpenGLRasterizer
../../../source/gameengine/Converter
../../../source/gameengine/BlenderRoutines
../../../source/blender/imbuf
../../../intern/moto/include
../../../intern/string
../../../source/gameengine/Ketsji
../../../source/blender/blenlib
../../../source/blender/blenfont
../../../source/blender/blenkernel
../../../source/blender/python
../../../source/blender/python/generic
../../../source/blender
../../../source/blender/makesdna
../../../source/blender/makesrna
../../../source/gameengine/Rasterizer
../../../source/gameengine/GameLogic
../../../source/gameengine/Expressions
../../../source/gameengine/Ketsji/KXNetwork
../../../source/gameengine/Network
../../../source/gameengine/SceneGraph
../../../source/gameengine/Physics/common
../../../source/gameengine/Network/LoopBackNetwork
../../../source/blender/blenloader
../../../source/blender/gpu
)
set(INC_SYS

@ -96,7 +96,6 @@ const char KX_KetsjiEngine::m_profileLabels[tc_numCategories][15] = {
"Animations:", // tc_animations
"Network:", // tc_network
"Scenegraph:", // tc_scenegraph
"Sound:", // tc_sound
"Rasterizer:", // tc_rasterizer
"Services:", // tc_services
"Overhead:", // tc_overhead
@ -692,8 +691,6 @@ else
else
if(scene->getSuspendedTime()==0.0)
scene->setSuspendedTime(m_clockTime);
DoSound(scene);
m_logger->StartLog(tc_services, m_kxsystem->GetTimeInSeconds(), true);
}
@ -769,8 +766,6 @@ else
if(scene->getSuspendedTime()==0.0)
scene->setSuspendedTime(m_clockTime);
DoSound(scene);
m_logger->StartLog(tc_services, m_kxsystem->GetTimeInSeconds(), true);
}
}
@ -1003,35 +998,6 @@ const STR_String& KX_KetsjiEngine::GetExitString()
}
void KX_KetsjiEngine::DoSound(KX_Scene* scene)
{
m_logger->StartLog(tc_sound, m_kxsystem->GetTimeInSeconds(), true);
// nothing to do here, everything relative now...
/*KX_Camera* cam = scene->GetActiveCamera();
if (!cam)
return;
AUD_I3DDevice* dev = AUD_get3DDevice();
if(dev)
{
AUD_Vector3 v;
//float q[4];
//cam->NodeGetWorldPosition().getValue(v.get());
dev->setListenerLocation(v);
//cam->GetLinearVelocity().getValue(v.get());
dev->setListenerVelocity(v);
//cam->NodeGetWorldOrientation().getRotation().getValue(q);
//dev->setListenerOrientation(AUD_Quaternion(q[3], q[0], q[1], q[2]));
dev->setListenerOrientation(AUD_Quaternion());
}*/
}
void KX_KetsjiEngine::SetBackGround(KX_WorldInfo* wi)
{
if (wi->hasWorld())

@ -153,7 +153,6 @@ private:
tc_animations,
tc_network,
tc_scenegraph,
tc_sound,
tc_rasterizer,
tc_services, // time spend in miscelaneous activities
tc_overhead, // profile info drawing overhead
@ -199,7 +198,6 @@ private:
void RenderDebugProperties();
void RenderShadowBuffers(KX_Scene *scene);
void SetBackGround(KX_WorldInfo* worldinfo);
void DoSound(KX_Scene* scene);
void RenderFonts(KX_Scene* scene);
public: