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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'release/scripts')
-rw-r--r--release/scripts/modules/bpy_types.py12
-rw-r--r--release/scripts/modules/mocap_constraints.py434
-rw-r--r--release/scripts/modules/mocap_tools.py856
-rw-r--r--release/scripts/modules/retarget.py621
-rw-r--r--release/scripts/presets/ffmpeg/DV.py1
-rw-r--r--release/scripts/presets/ffmpeg/DVD.py1
-rw-r--r--release/scripts/presets/ffmpeg/SVCD.py1
-rw-r--r--release/scripts/presets/ffmpeg/VCD.py1
-rw-r--r--release/scripts/startup/bl_operators/nla.py40
-rw-r--r--release/scripts/startup/bl_operators/object.py41
-rw-r--r--release/scripts/startup/bl_ui/__init__.py1
-rw-r--r--release/scripts/startup/bl_ui/properties_data_armature.py25
-rw-r--r--release/scripts/startup/bl_ui/properties_data_speaker.py129
-rw-r--r--release/scripts/startup/bl_ui/properties_game.py16
-rw-r--r--release/scripts/startup/bl_ui/properties_object_constraint.py5
-rw-r--r--release/scripts/startup/bl_ui/properties_render.py5
-rw-r--r--release/scripts/startup/bl_ui/properties_scene.py30
-rw-r--r--release/scripts/startup/bl_ui/space_dopesheet.py43
-rw-r--r--release/scripts/startup/bl_ui/space_graph.py4
-rw-r--r--release/scripts/startup/bl_ui/space_info.py3
-rw-r--r--release/scripts/startup/bl_ui/space_nla.py7
-rw-r--r--release/scripts/startup/bl_ui/space_sequencer.py23
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py1
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py9
-rw-r--r--release/scripts/startup/ui_mocap.py842
25 files changed, 3103 insertions, 48 deletions
diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py
index 8766c873dd8..75b199dcc26 100644
--- a/release/scripts/modules/bpy_types.py
+++ b/release/scripts/modules/bpy_types.py
@@ -57,7 +57,7 @@ class Library(bpy_types.ID):
"curves", "grease_pencil", "groups", "images", \
"lamps", "lattices", "materials", "metaballs", \
"meshes", "node_groups", "objects", "scenes", \
- "sounds", "textures", "texts", "fonts", "worlds"
+ "sounds", "speakers", "textures", "texts", "fonts", "worlds"
return tuple(id_block for attr in attr_links for id_block in getattr(bpy.data, attr) if id_block.library == self)
@@ -411,6 +411,16 @@ class Text(bpy_types.ID):
TypeMap = {}
+class Sound(bpy_types.ID):
+ __slots__ = ()
+
+ @property
+ def factory(self):
+ """The aud.Factory object of the sound."""
+ import aud
+ return aud._sound_from_pointer(self.as_pointer())
+
+
class RNAMeta(type):
def __new__(cls, name, bases, classdict, **args):
result = type.__new__(cls, name, bases, classdict)
diff --git a/release/scripts/modules/mocap_constraints.py b/release/scripts/modules/mocap_constraints.py
new file mode 100644
index 00000000000..540e8fa06db
--- /dev/null
+++ b/release/scripts/modules/mocap_constraints.py
@@ -0,0 +1,434 @@
+# ##### 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
diff --git a/release/scripts/modules/mocap_tools.py b/release/scripts/modules/mocap_tools.py
new file mode 100644
index 00000000000..e5d4dcb6554
--- /dev/null
+++ b/release/scripts/modules/mocap_tools.py
@@ -0,0 +1,856 @@
+# ##### 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
+
+
+#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)
+
+
+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
+
+
+def crossCorrelationMatch(curvesA, curvesB, margin):
+ dataA = []
+ dataB = []
+ end = len(curvesA[0].keyframe_points)
+
+ 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))
+
+ def comp(a, b):
+ return a * b
+
+ 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)
+ def bestLocalMaximum(Rxy):
+ Rxyd = [Rxy[i] - Rxy[i - 1] for i in range(1, len(Rxy))]
+ maxs = []
+ for i in range(1, len(Rxyd) - 1):
+ a = Rxyd[i - 1]
+ b = Rxyd[i]
+ #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 = bestLocalMaximum(Rxy[0:int(len(Rxy))])
+ ss = []
+ for flm in flms:
+ diff = []
+
+ for i in range(len(dataA) - flm):
+ diff.append((dataA[i] - dataB[i + flm]).lengthSq)
+
+ def lowerErrorSlice(diff, e):
+ #index, error at index
+ bestSlice = (0, 100000)
+ for i in range(e, len(diff) - e):
+ errorSlice = sum(diff[i - e:i + e + 1])
+ if errorSlice < bestSlice[1]:
+ bestSlice = (i, errorSlice, flm)
+ return bestSlice
+
+ s = lowerErrorSlice(diff, margin)
+ ss.append(s)
+
+ ss.sort(key = lambda x: x[1])
+ return ss[0][2], ss[0][0], dataA
+
+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]
+
+ #find *all* loops, s:s+flm, s+flm:s+2flm, etc...
+ #and interpolate between all
+ # to find "the perfect loop".
+ #Maybe before finding s? interp(i,i+flm,i+2flm)....
+ #~ for i in range(1, margin + 1):
+ #~ w1 = sqrt(float(i) / margin)
+ #~ loop[-i] = (loop[-i] * w1) + (loop[0] * (1 - w1))
+
+ 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
+
+
+def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
+
+ 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
+ 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, 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)
+
+ 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
+
+ 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
+
+ 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
+#sel_opt: either "sel" or "all" for which curves to effect
+#error: maximum error allowed, in fraction (20% = 0.0020),
+#i.e. divide by 10000 from percentage wanted.
+#group_mode: boolean, to analyze each curve seperately or in groups,
+#where group is all curves that effect the same property
+#(e.g. a bone's x,y,z rotation)
+
+
+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 one
+# Expects sampled keyframes on everyframe
+
+
+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
+
+
+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)
+
+
+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
+
+
+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
+ 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)
+
+
+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
+ #~ else:
+ #~ bone.ik_min_x, bone.ik_min_y, bone.ik_min_z, bone.ik_max_x, bone.ik_max_y, bone.ik_max_z = limitDict[bone.name]
+ #~ bone.use_ik_limit_x = True
+ #~ bone.use_ik_limit_y = True
+ #~ bone.use_ik_limit_z= True
+ #~ bone.ik_stiffness_x = 1/((limitDict[bone.name][3] - limitDict[bone.name][0])/(2*pi)))
+ #~ bone.ik_stiffness_y = 1/((limitDict[bone.name][4] - limitDict[bone.name][1])/(2*pi)))
+ #~ bone.ik_stiffness_z = 1/((limitDict[bone.name][5] - limitDict[bone.name][2])/(2*pi)))
+
+ context.scene.frame_set(c_frame)
+
+
+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])
+
+
+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")
+
+
+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]
+
+
+def guess_anim_stitch(context, enduser_obj):
+ stitch_settings = enduser_obj.data.stitch_settings
+ action_1 = stitch_settings.first_action
+ action_2 = stitch_settings.second_action
+ TrackNamesA = enduser_obj.data.mocapNLATracks[action_1]
+ TrackNamesB = enduser_obj.data.mocapNLATracks[action_2]
+ mocapA = bpy.data.actions[TrackNamesA.base_track]
+ mocapB = bpy.data.actions[TrackNamesB.base_track]
+ curvesA = mocapA.fcurves
+ curvesB = mocapB.fcurves
+ flm, s, data = crossCorrelationMatch(curvesA, curvesB, 10)
+ print(flm,s)
+ enduser_obj.data.stitch_settings.blend_frame = flm
+ enduser_obj.data.stitch_settings.second_offset = s \ No newline at end of file
diff --git a/release/scripts/modules/retarget.py b/release/scripts/modules/retarget.py
new file mode 100644
index 00000000000..2c4dcbe6bda
--- /dev/null
+++ b/release/scripts/modules/retarget.py
@@ -0,0 +1,621 @@
+# ##### 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
+import cProfile
+
+
+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())
+ #~ orgEul = inter_bone.bone.matrix_local.to_euler("XYZ")
+ #~ eul = bake_matrix.to_euler("XYZ", orgEul)
+ #~ diff = -bake_matrix.to_euler().y + inter_bone.bone.matrix.to_euler().y
+ #~ eul.rotate_axis("Y", diff)
+ #~ eul.make_compatible(orgEul)
+ #~ bake_matrix = eul.to_matrix()
+ #~ #diff = abs(diff)
+ #bake_matrix = bake_matrix* Matrix.Rotation(pi/2, 3, "Y")
+ #~ scene = bpy.context.scene
+ #~ print(scene.frame_current, inter_bone.name, bake_matrix.to_euler().y)
+ 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)
+ 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.armature.calculate_roll(type='Z')
+ 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
+
+ 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]
+
+ #~ locDeriv = {}
+ #~ for key in locDictKeys:
+ #~ locDeriv[key] = []
+
+ #~ for key in locDict.keys():
+ #~ graph = locDict[key]
+ #~ locDeriv[key] = [graph[t + 1] - graph[t] for t in range(len(graph) - 1)]
+
+ # now find the plant frames, where perfFeet don't move much
+
+ linearAvg = []
+
+ for key in perfFeet:
+ for i in range(len(locDict[key]) - 1):
+ v = locDeriv(key,i)
+ 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) #+ stride_bone.location
+ 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:
+ if pose_bone.is_in_ik_chain:
+ pass
+ # TODO:
+ # set stiffness according to place on chain
+ # and values from analysis that is stored in the bone
+ #pose_bone.ik_stiffness_x = 0.5
+ #pose_bone.ik_stiffness_y = 0.5
+ #pose_bone.ik_stiffness_z = 0.5
+ ik_constraint = hasIKConstraint(pose_bone)
+ if ik_constraint:
+ ik_constraint.mute = True
+
+
+#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))
+ #~ empty = bpy.context.active_object
+ #~ empty.name = end_bone.name + "Org"
+ #~ empty.empty_draw_size = 0.1
+ #~ empty.parent = enduser_obj
+ else:
+ newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
+ return newBone
+
+
+#create the specified NLA setup for base animation, constraints and tweak layer.
+def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
+ 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 = "MULITPLY" - doesn't work due to work, will be activated soon
+ anim_data.nla_tracks.active = constraintTrack
+ #anim_data.action = constraintAction
+ anim_data.action_extrapolation = "NOTHING"
+ #set the stride_bone's action
+ if "stride_bone" in bpy.data.objects:
+ 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
+ addLocalRot = False;
+ if (not bone.bone.use_connect) and (perf_bone!=perf_root):
+ locks = bone.lock_location
+ #if not (locks[0] or locks[1] or locks[2]):
+ cons = bone.constraints.new('COPY_LOCATION')
+ cons.name = "retargetTemp"
+ 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 = 'LOCAL'
+ cons.owner_space = 'LOCAL'
+ addLocalRot = True
+
+
+ cons2 = bone.constraints.new('COPY_ROTATION')
+ cons2.name = "retargetTemp"
+ locks = bone.lock_rotation
+ cons2.use_x = not locks[0]
+ cons2.use_y = not locks[1]
+ cons2.use_z = not locks[2]
+ cons2.target = performer_obj
+ cons2.subtarget = perf_bone
+ cons2.target_space = 'WORLD'
+ cons2.owner_space = 'WORLD'
+
+ if perf_bone==perf_root:
+ addLocalRot = True
+
+ #~ if addLocalRot:
+ #~ for constraint in bone.constraints:
+ #~ if constraint.type == 'COPY_ROTATION':
+ #~ constraint.target_space = 'LOCAL'
+ #~ constraint.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 profileWrapper():
+ context = bpy.context
+ scene = context.scene
+ s_frame = scene.frame_start
+ e_frame = scene.frame_end
+ enduser_obj = context.active_object
+ performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
+ if enduser_obj is None or len(performer_obj) != 1:
+ print("Need active and selected armatures")
+ else:
+ performer_obj = performer_obj[0]
+ s_frame, e_frame = performer_obj.animation_data.action.frame_range
+ s_frame = int(s_frame)
+ e_frame = int(e_frame)
+ totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
+
+
+def isRigAdvanced(enduser_obj):
+ 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
+
+if __name__ == "__main__":
+ cProfile.run("profileWrapper()")
diff --git a/release/scripts/presets/ffmpeg/DV.py b/release/scripts/presets/ffmpeg/DV.py
index 46d2a0a4a2f..926fb241747 100644
--- a/release/scripts/presets/ffmpeg/DV.py
+++ b/release/scripts/presets/ffmpeg/DV.py
@@ -11,3 +11,4 @@ else:
bpy.context.scene.render.ffmpeg_audio_mixrate = 48000
bpy.context.scene.render.ffmpeg_audio_codec = "PCM"
+bpy.context.scene.render.ffmpeg_audio_channels = 2
diff --git a/release/scripts/presets/ffmpeg/DVD.py b/release/scripts/presets/ffmpeg/DVD.py
index e18ec9f817b..196b5d68406 100644
--- a/release/scripts/presets/ffmpeg/DVD.py
+++ b/release/scripts/presets/ffmpeg/DVD.py
@@ -21,3 +21,4 @@ bpy.context.scene.render.ffmpeg_muxrate = 10080000
bpy.context.scene.render.ffmpeg_audio_codec = "AC3"
bpy.context.scene.render.ffmpeg_audio_bitrate = 448
bpy.context.scene.render.ffmpeg_audio_mixrate = 48000
+bpy.context.scene.render.ffmpeg_audio_channels = 6
diff --git a/release/scripts/presets/ffmpeg/SVCD.py b/release/scripts/presets/ffmpeg/SVCD.py
index c71a3851af0..e4459ab5c5c 100644
--- a/release/scripts/presets/ffmpeg/SVCD.py
+++ b/release/scripts/presets/ffmpeg/SVCD.py
@@ -21,3 +21,4 @@ bpy.context.scene.render.ffmpeg_muxrate = 0
bpy.context.scene.render.ffmpeg_audio_bitrate = 224
bpy.context.scene.render.ffmpeg_audio_mixrate = 44100
bpy.context.scene.render.ffmpeg_audio_codec = "MP2"
+bpy.context.scene.render.ffmpeg_audio_channels = 2
diff --git a/release/scripts/presets/ffmpeg/VCD.py b/release/scripts/presets/ffmpeg/VCD.py
index faf27efe9e6..c2b73e682a2 100644
--- a/release/scripts/presets/ffmpeg/VCD.py
+++ b/release/scripts/presets/ffmpeg/VCD.py
@@ -21,3 +21,4 @@ bpy.context.scene.render.ffmpeg_muxrate = 2352 * 75 * 8
bpy.context.scene.render.ffmpeg_audio_bitrate = 224
bpy.context.scene.render.ffmpeg_audio_mixrate = 44100
bpy.context.scene.render.ffmpeg_audio_codec = "MP2"
+bpy.context.scene.render.ffmpeg_audio_channels = 2
diff --git a/release/scripts/startup/bl_operators/nla.py b/release/scripts/startup/bl_operators/nla.py
index 44ed846e530..4b4630fdd4e 100644
--- a/release/scripts/startup/bl_operators/nla.py
+++ b/release/scripts/startup/bl_operators/nla.py
@@ -84,7 +84,7 @@ def bake(frame_start,
do_pose=True,
do_object=True,
do_constraint_clear=False,
- ):
+ action=None):
scene = bpy.context.scene
obj = bpy.context.object
@@ -121,7 +121,8 @@ def bake(frame_start,
# incase animation data hassnt been created
atd = obj.animation_data_create()
- action = bpy.data.actions.new("Action")
+ if action == None:
+ action = bpy.data.actions.new("Action")
atd.action = action
if do_pose:
@@ -254,3 +255,38 @@ class BakeAction(Operator):
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
+
+#################################
+
+
+class ClearUselessActions(bpy.types.Operator):
+ '''Mark actions with no F-Curves for deletion after save+reload of file preserving "action libraries"'''
+ bl_idname = "anim.clear_useless_actions"
+ bl_label = "Clear Useless Actions"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ only_unused = BoolProperty(name="Only Unused",
+ description="Only unused (Fake User only) actions get considered",
+ default=True)
+
+ @classmethod
+ def poll(cls, context):
+ return len(bpy.data.actions) != 0
+
+ def execute(self, context):
+ removed = 0
+
+ for action in bpy.data.actions:
+ # if only user is "fake" user...
+ if ((self.only_unused is False) or
+ (action.use_fake_user and action.users == 1)):
+
+ # if it has F-Curves, then it's a "action library" (i.e. walk, wave, jump, etc.)
+ # and should be left alone as that's what fake users are for!
+ if not action.fcurves:
+ # mark action for deletion
+ action.user_clear()
+ removed += 1
+
+ self.report({'INFO'}, "Removed %d empty and/or fake-user only Actions" % (removed))
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py
index 79f57990f37..413f0e8db9e 100644
--- a/release/scripts/startup/bl_operators/object.py
+++ b/release/scripts/startup/bl_operators/object.py
@@ -681,3 +681,44 @@ class ClearAllRestrictRender(Operator):
for obj in context.scene.objects:
obj.hide_render = False
return {'FINISHED'}
+
+class TransformsToDeltasAnim(bpy.types.Operator):
+ '''Convert object animation for normal transforms to delta transforms'''
+ bl_idname = "object.anim_transforms_to_deltas"
+ bl_label = "Animated Transforms to Deltas"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ obs = context.selected_editable_objects
+ return (obs is not None)
+
+ def execute(self, context):
+ for obj in context.selected_editable_objects:
+ # get animation data
+ adt = obj.animation_data
+ if (adt is None) or (adt.action is None):
+ self.report({'WARNING'}, "No animation data to convert on object: " + obj.name)
+ continue
+
+ # if F-Curve uses standard transform path, just append "delta_" to this path
+ for fcu in adt.action.fcurves:
+ if fcu.data_path == "location":
+ fcu.data_path = "delta_location"
+ obj.location.zero()
+ elif fcu.data_path == "rotation_euler":
+ fcu.data_path = "delta_rotation_euler"
+ obj.rotation_euler.zero()
+ elif fcu.data_path == "rotation_quaternion":
+ fcu.data_path = "delta_rotation_quaternion"
+ obj.rotation_quaternion.identity()
+ #elif fcu.data_path == "rotation_axis_angle": # XXX: currently not implemented
+ # fcu.data_path = "delta_rotation_axis_angle"
+ elif fcu.data_path == "scale":
+ fcu.data_path = "delta_scale"
+ obj.scale = (1, 1, 1)
+
+ # hack: force animsys flush by changing frame, so that deltas get run
+ context.scene.frame_set(context.scene.frame_current)
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py
index bf63c6071b9..5fab3b7fd38 100644
--- a/release/scripts/startup/bl_ui/__init__.py
+++ b/release/scripts/startup/bl_ui/__init__.py
@@ -36,6 +36,7 @@ _modules = (
"properties_data_mesh",
"properties_data_metaball",
"properties_data_modifier",
+ "properties_data_speaker",
"properties_game",
"properties_material",
"properties_object_constraint",
diff --git a/release/scripts/startup/bl_ui/properties_data_armature.py b/release/scripts/startup/bl_ui/properties_data_armature.py
index 94c40d11141..cddf9fef0f2 100644
--- a/release/scripts/startup/bl_ui/properties_data_armature.py
+++ b/release/scripts/startup/bl_ui/properties_data_armature.py
@@ -71,6 +71,9 @@ class DATA_PT_skeleton(ArmatureButtonsPanel, Panel):
flow.prop(arm, "use_deform_envelopes", text="Envelopes")
flow.prop(arm, "use_deform_preserve_volume", text="Quaternion")
+ if context.scene.render.engine == "BLENDER_GAME":
+ col = layout.column()
+ col.prop(arm, "vert_deformer")
class DATA_PT_display(ArmatureButtonsPanel, Panel):
bl_label = "Display"
@@ -97,6 +100,15 @@ class DATA_PT_display(ArmatureButtonsPanel, Panel):
col.prop(arm, "use_deform_delay", text="Delay Refresh")
+class DATA_PT_bone_group_specials(Menu):
+ bl_label = "Bone Group Specials"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("pose.group_sort", icon='SORTALPHA')
+
+
class DATA_PT_bone_groups(ArmatureButtonsPanel, Panel):
bl_label = "Bone Groups"
@@ -109,16 +121,25 @@ class DATA_PT_bone_groups(ArmatureButtonsPanel, Panel):
ob = context.object
pose = ob.pose
+ group = pose.bone_groups.active
row = layout.row()
- row.template_list(pose, "bone_groups", pose.bone_groups, "active_index", rows=2)
+
+ rows = 2
+ if group:
+ rows = 5
+ row.template_list(pose, "bone_groups", pose.bone_groups, "active_index", rows=rows)
col = row.column(align=True)
col.active = (ob.proxy is None)
col.operator("pose.group_add", icon='ZOOMIN', text="")
col.operator("pose.group_remove", icon='ZOOMOUT', text="")
+ col.menu("DATA_PT_bone_group_specials", icon='DOWNARROW_HLT', text="")
+ if group:
+ col.separator()
+ col.operator("pose.group_move", icon='TRIA_UP', text="").direction = 'UP'
+ col.operator("pose.group_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
- group = pose.bone_groups.active
if group:
col = layout.column()
col.active = (ob.proxy is None)
diff --git a/release/scripts/startup/bl_ui/properties_data_speaker.py b/release/scripts/startup/bl_ui/properties_data_speaker.py
new file mode 100644
index 00000000000..fe9f798af0c
--- /dev/null
+++ b/release/scripts/startup/bl_ui/properties_data_speaker.py
@@ -0,0 +1,129 @@
+# ##### 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 rna_prop_ui import PropertyPanel
+
+
+class DataButtonsPanel():
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+
+ @classmethod
+ def poll(cls, context):
+ engine = context.scene.render.engine
+ return context.speaker and (engine in cls.COMPAT_ENGINES)
+
+
+class DATA_PT_context_speaker(DataButtonsPanel, bpy.types.Panel):
+ bl_label = ""
+ bl_options = {'HIDE_HEADER'}
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'}
+
+ def draw(self, context):
+ layout = self.layout
+
+ ob = context.object
+ speaker = context.speaker
+ space = context.space_data
+
+ split = layout.split(percentage=0.65)
+
+ if ob:
+ split.template_ID(ob, "data")
+ elif speaker:
+ split.template_ID(space, "pin_id")
+
+
+class DATA_PT_speaker(DataButtonsPanel, bpy.types.Panel):
+ bl_label = "Sound"
+ COMPAT_ENGINES = {'BLENDER_RENDER'}
+
+ def draw(self, context):
+ layout = self.layout
+
+ speaker = context.speaker
+
+ split = layout.split(percentage=0.75)
+
+ split.template_ID(speaker, "sound", open="sound.open_mono")
+ split.prop(speaker, "muted")
+
+ split = layout.split()
+
+ row = split.row()
+
+ row.prop(speaker, "volume")
+ row.prop(speaker, "pitch")
+
+
+class DATA_PT_distance(DataButtonsPanel, bpy.types.Panel):
+ bl_label = "Distance"
+ COMPAT_ENGINES = {'BLENDER_RENDER'}
+
+ def draw(self, context):
+ layout = self.layout
+
+ speaker = context.speaker
+
+ split = layout.split()
+ col = split.column()
+
+ col.label("Volume:")
+ col.prop(speaker, "volume_min", text="Minimum")
+ col.prop(speaker, "volume_max", text="Maximum")
+ col.prop(speaker, "attenuation")
+
+ col = split.column()
+
+ col.label("Distance:")
+ col.prop(speaker, "distance_max", text="Maximum")
+ col.prop(speaker, "distance_reference", text="Reference")
+
+
+class DATA_PT_cone(DataButtonsPanel, bpy.types.Panel):
+ bl_label = "Cone"
+ COMPAT_ENGINES = {'BLENDER_RENDER'}
+
+ def draw(self, context):
+ layout = self.layout
+
+ speaker = context.speaker
+
+ split = layout.split()
+ col = split.column()
+
+ col.label("Angle:")
+ col.prop(speaker, "cone_angle_outer", text="Outer")
+ col.prop(speaker, "cone_angle_inner", text="Inner")
+
+ col = split.column()
+
+ col.label("Volume:")
+ col.prop(speaker, "cone_volume_outer", text="Outer")
+
+
+class DATA_PT_custom_props_speaker(DataButtonsPanel, PropertyPanel, bpy.types.Panel):
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'}
+ _context_path = "object.data"
+ _property_type = bpy.types.Speaker
+
+if __name__ == "__main__": # only for live edit.
+ bpy.utils.register_module(__name__)
diff --git a/release/scripts/startup/bl_ui/properties_game.py b/release/scripts/startup/bl_ui/properties_game.py
index f8be32e6c07..e3c576e7093 100644
--- a/release/scripts/startup/bl_ui/properties_game.py
+++ b/release/scripts/startup/bl_ui/properties_game.py
@@ -343,6 +343,7 @@ class RENDER_PT_game_performance(RenderButtonsPanel, Panel):
row = layout.row()
row.prop(gs, "use_frame_rate")
row.prop(gs, "use_display_lists")
+ row.prop(gs, "restrict_animation_updates")
class RENDER_PT_game_display(RenderButtonsPanel, Panel):
@@ -361,21 +362,6 @@ class RENDER_PT_game_display(RenderButtonsPanel, Panel):
flow.prop(gs, "show_mouse", text="Mouse Cursor")
-class RENDER_PT_game_sound(RenderButtonsPanel, Panel):
- bl_label = "Sound"
- COMPAT_ENGINES = {'BLENDER_GAME'}
-
- def draw(self, context):
- layout = self.layout
-
- scene = context.scene
-
- layout.prop(scene, "audio_distance_model")
-
- layout.prop(scene, "audio_doppler_speed", text="Speed")
- layout.prop(scene, "audio_doppler_factor")
-
-
class WorldButtonsPanel():
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
diff --git a/release/scripts/startup/bl_ui/properties_object_constraint.py b/release/scripts/startup/bl_ui/properties_object_constraint.py
index 867abe4dd5d..c74a0000499 100644
--- a/release/scripts/startup/bl_ui/properties_object_constraint.py
+++ b/release/scripts/startup/bl_ui/properties_object_constraint.py
@@ -477,6 +477,11 @@ class ConstraintButtonsPanel():
row.label(text="Clamp Region:")
row.prop(con, "limit_mode", text="")
+ row = layout.row()
+ row.prop(con, "use_transform_limit")
+ row.label()
+
+
def STRETCH_TO(self, context, layout, con):
self.target_template(layout, con)
diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py
index fb14372ebea..c906013e094 100644
--- a/release/scripts/startup/bl_ui/properties_render.py
+++ b/release/scripts/startup/bl_ui/properties_render.py
@@ -596,9 +596,8 @@ class RENDER_PT_encoding(RenderButtonsPanel, Panel):
col = split.column()
col.prop(rd, "ffmpeg_audio_bitrate")
- col.prop(rd, "ffmpeg_audio_mixrate")
-
- split.prop(rd, "ffmpeg_audio_volume", slider=True)
+ col = split.column()
+ col.prop(rd, "ffmpeg_audio_volume", slider=True)
class RENDER_PT_bake(RenderButtonsPanel, Panel):
diff --git a/release/scripts/startup/bl_ui/properties_scene.py b/release/scripts/startup/bl_ui/properties_scene.py
index 6e96e1228e7..fd7fc8ed462 100644
--- a/release/scripts/startup/bl_ui/properties_scene.py
+++ b/release/scripts/startup/bl_ui/properties_scene.py
@@ -44,6 +44,36 @@ class SCENE_PT_scene(SceneButtonsPanel, Panel):
layout.prop(scene, "background_set", text="Background")
+class SCENE_PT_audio(SceneButtonsPanel, Panel):
+ bl_label = "Audio"
+ COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'}
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+ rd = context.scene.render
+
+ layout.prop(scene, "audio_volume")
+ layout.operator("sound.bake_animation")
+
+ split = layout.split()
+
+ col = split.column()
+
+ col.label("Listener:")
+ col.prop(scene, "audio_distance_model", text="")
+ col.prop(scene, "audio_doppler_speed", text="Speed")
+ col.prop(scene, "audio_doppler_factor", text="Doppler")
+
+ col = split.column()
+
+ col.label("Format:")
+ col.prop(rd, "ffmpeg_audio_channels", text="")
+ col.prop(rd, "ffmpeg_audio_mixrate", text="Rate")
+
+ layout.operator("sound.mixdown")
+
+
class SCENE_PT_unit(SceneButtonsPanel, Panel):
bl_label = "Units"
COMPAT_ENGINES = {'BLENDER_RENDER'}
diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py
index dfbd7b3ae14..cab58a3aadb 100644
--- a/release/scripts/startup/bl_ui/space_dopesheet.py
+++ b/release/scripts/startup/bl_ui/space_dopesheet.py
@@ -34,14 +34,33 @@ def dopesheet_filter(layout, context, genericFiltersOnly=False):
row.prop(dopesheet, "show_only_selected", text="")
row.prop(dopesheet, "show_hidden", text="")
+ if is_nla:
+ row.prop(dopesheet, "show_missing_nla", text="")
+
if not genericFiltersOnly:
+ if bpy.data.groups:
+ row = layout.row(align=True)
+ row.prop(dopesheet, "show_only_group_objects", text="")
+ if dopesheet.show_only_group_objects:
+ row.prop(dopesheet, "filter_group", text="")
+
+ if not is_nla:
row = layout.row(align=True)
- row.prop(dopesheet, "show_transforms", text="")
+ row.prop(dopesheet, "show_only_matching_fcurves", text="")
+ if dopesheet.show_only_matching_fcurves:
+ row.prop(dopesheet, "filter_fcurve_name", text="")
- if is_nla:
- row.prop(dopesheet, "show_missing_nla", text="")
+ row = layout.row()
+ row.prop(dopesheet, "show_datablock_filters", text="Filters", icon='DISCLOSURE_TRI_RIGHT')
- row = layout.row(align=True)
+ if (not genericFiltersOnly) and (dopesheet.show_datablock_filters):
+ # TODO: put a box around these?
+ subrow = row.row()
+
+ row = subrow.row(align=True)
+ row.prop(dopesheet, "show_transforms", text="")
+
+ row = subrow.row(align=True)
row.prop(dopesheet, "show_scenes", text="")
row.prop(dopesheet, "show_worlds", text="")
row.prop(dopesheet, "show_nodes", text="")
@@ -68,18 +87,8 @@ def dopesheet_filter(layout, context, genericFiltersOnly=False):
row.prop(dopesheet, "show_armatures", text="")
if bpy.data.particles:
row.prop(dopesheet, "show_particles", text="")
-
- if bpy.data.groups:
- row = layout.row(align=True)
- row.prop(dopesheet, "show_only_group_objects", text="")
- if dopesheet.show_only_group_objects:
- row.prop(dopesheet, "filter_group", text="")
-
- if not is_nla:
- row = layout.row(align=True)
- row.prop(dopesheet, "show_only_matching_fcurves", text="")
- if dopesheet.show_only_matching_fcurves:
- row.prop(dopesheet, "filter_fcurve_name", text="")
+ if bpy.data.speakers:
+ row.prop(dopesheet, "show_speakers", text="")
#######################################
@@ -277,7 +286,7 @@ class DOPESHEET_MT_key(Menu):
layout.operator("action.keyframe_insert")
layout.separator()
- layout.operator("action.duplicate")
+ layout.operator("action.duplicate_move")
layout.operator("action.delete")
layout.separator()
diff --git a/release/scripts/startup/bl_ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py
index c379ea95ea2..d4b8c415a7f 100644
--- a/release/scripts/startup/bl_ui/space_graph.py
+++ b/release/scripts/startup/bl_ui/space_graph.py
@@ -78,7 +78,7 @@ class GRAPH_MT_view(Menu):
layout.prop(st, "use_auto_merge_keyframes")
layout.separator()
- layout.prop(st, "use_fancy_drawing")
+ layout.prop(st, "use_beauty_drawing")
layout.separator()
if st.show_handles:
@@ -206,7 +206,7 @@ class GRAPH_MT_key(Menu):
layout.operator("graph.sound_bake")
layout.separator()
- layout.operator("graph.duplicate")
+ layout.operator("graph.duplicate_move")
layout.operator("graph.delete")
layout.separator()
diff --git a/release/scripts/startup/bl_ui/space_info.py b/release/scripts/startup/bl_ui/space_info.py
index 38c1e24f27e..28990328c46 100644
--- a/release/scripts/startup/bl_ui/space_info.py
+++ b/release/scripts/startup/bl_ui/space_info.py
@@ -292,6 +292,9 @@ class INFO_MT_add(Menu):
layout.operator("object.add", text="Empty", icon='OUTLINER_OB_EMPTY').type = 'EMPTY'
layout.separator()
+ layout.operator("object.speaker_add", text="Speaker", icon='OUTLINER_OB_SPEAKER')
+ layout.separator()
+
layout.operator("object.camera_add", text="Camera", icon='OUTLINER_OB_CAMERA')
layout.operator_context = 'EXEC_SCREEN'
layout.operator_menu_enum("object.lamp_add", "type", text="Lamp", icon='OUTLINER_OB_LAMP')
diff --git a/release/scripts/startup/bl_ui/space_nla.py b/release/scripts/startup/bl_ui/space_nla.py
index c69af2c9a60..1d4b7c6828f 100644
--- a/release/scripts/startup/bl_ui/space_nla.py
+++ b/release/scripts/startup/bl_ui/space_nla.py
@@ -69,7 +69,11 @@ class NLA_MT_view(Menu):
layout.separator()
layout.operator("anim.previewrange_set")
layout.operator("anim.previewrange_clear")
-
+
+ layout.separator()
+ layout.operator("nla.view_all")
+ layout.operator("nla.view_selected")
+
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
@@ -162,6 +166,7 @@ class NLA_MT_add(Menu):
layout.operator("nla.actionclip_add")
layout.operator("nla.transition_add")
+ layout.operator("nla.soundclip_add")
layout.separator()
layout.operator("nla.meta_add")
diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py
index 84cc365425e..f796ce8da5f 100644
--- a/release/scripts/startup/bl_ui/space_sequencer.py
+++ b/release/scripts/startup/bl_ui/space_sequencer.py
@@ -112,7 +112,11 @@ class SEQUENCER_MT_view(Menu):
layout.operator("sequencer.view_selected")
- layout.prop(st, "show_frames")
+ if st.show_frames:
+ layout.operator("anim.time_toggle", text="Show Seconds")
+ else:
+ layout.operator("anim.time_toggle", text="Show Frames")
+
layout.prop(st, "show_frame_indicator")
if st.display_mode == 'IMAGE':
layout.prop(st, "show_safe_margin")
@@ -221,6 +225,7 @@ class SEQUENCER_MT_add_effect(Menu):
layout.operator("sequencer.effect_strip_add", text="Speed Control").type = 'SPEED'
layout.operator("sequencer.effect_strip_add", text="Multicam Selector").type = 'MULTICAM'
layout.operator("sequencer.effect_strip_add", text="Adjustment Layer").type = 'ADJUSTMENT'
+ layout.operator("sequencer.effect_strip_add", text="Title Card").type = 'TITLE_CARD'
class SEQUENCER_MT_strip(Menu):
@@ -402,7 +407,7 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel, Panel):
'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP',
'PLUGIN',
'WIPE', 'GLOW', 'TRANSFORM', 'COLOR', 'SPEED',
- 'MULTICAM', 'ADJUSTMENT'}
+ 'MULTICAM', 'ADJUSTMENT', 'TITLE_CARD'}
def draw(self, context):
layout = self.layout
@@ -470,6 +475,11 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel, Panel):
row.label("Cut To")
for i in range(1, strip.channel):
row.operator("sequencer.cut_multicam", text=str(i)).camera = i
+ elif strip.type == "TITLE_CARD":
+ layout.prop(strip, "title")
+ layout.prop(strip, "subtitle")
+ layout.prop(strip, "color_foreground")
+ layout.prop(strip, "color_background")
col = layout.column(align=True)
if strip.type == 'SPEED':
@@ -541,7 +551,8 @@ class SEQUENCER_PT_input(SequencerButtonsPanel, Panel):
'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP',
'PLUGIN',
'WIPE', 'GLOW', 'TRANSFORM', 'COLOR',
- 'MULTICAM', 'SPEED', 'ADJUSTMENT'}
+ 'MULTICAM', 'SPEED', 'ADJUSTMENT',
+ 'TITLE_CARD'}
def draw(self, context):
layout = self.layout
@@ -640,8 +651,11 @@ class SEQUENCER_PT_sound(SequencerButtonsPanel, Panel):
row.prop(strip.sound, "use_memory_cache")
+ layout.prop(strip, "waveform")
layout.prop(strip, "volume")
layout.prop(strip, "attenuation")
+ layout.prop(strip, "pitch")
+ layout.prop(strip, "pan")
col = layout.column(align=True)
col.label(text="Trim Duration:")
@@ -700,7 +714,8 @@ class SEQUENCER_PT_filter(SequencerButtonsPanel, Panel):
'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP',
'PLUGIN',
'WIPE', 'GLOW', 'TRANSFORM', 'COLOR',
- 'MULTICAM', 'SPEED', 'ADJUSTMENT'}
+ 'MULTICAM', 'SPEED', 'ADJUSTMENT',
+ 'TITLE_CARD'}
def draw(self, context):
layout = self.layout
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index 148338368fe..732a38308b0 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -746,6 +746,7 @@ class USERPREF_PT_file(Panel):
col.prop(paths, "save_version")
col.prop(paths, "recent_files")
+ col.prop(paths, "use_update_recent_files_on_load")
col.prop(paths, "use_save_preview_images")
col.label(text="Auto Save:")
col.prop(paths, "use_auto_save_temporary_files")
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index fa22e216ec9..c1add444bf1 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -179,6 +179,10 @@ class VIEW3D_MT_transform(Menu):
layout.operator("object.randomize_transform")
layout.operator("object.align")
+
+ layout.separator()
+
+ layout.operator("object.anim_transforms_to_deltas")
class VIEW3D_MT_mirror(Menu):
@@ -1259,12 +1263,15 @@ class VIEW3D_MT_pose_transform(Menu):
layout.operator("pose.transforms_clear", text="All")
+ layout.separator()
+
layout.operator("pose.loc_clear", text="Location")
layout.operator("pose.rot_clear", text="Rotation")
layout.operator("pose.scale_clear", text="Scale")
- layout.label(text="Origin")
+ layout.separator()
+ layout.operator("pose.user_transforms_clear", text="Reset unkeyed")
class VIEW3D_MT_pose_slide(Menu):
bl_label = "In-Betweens"
diff --git a/release/scripts/startup/ui_mocap.py b/release/scripts/startup/ui_mocap.py
new file mode 100644
index 00000000000..23354f9d722
--- /dev/null
+++ b/release/scripts/startup/ui_mocap.py
@@ -0,0 +1,842 @@
+# ##### 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)
+
+
+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)
+
+
+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)
+
+
+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)
+
+
+
+bpy.types.Armature.stitch_settings = bpy.props.PointerProperty(type=AnimationStitchSettings)
+bpy.types.Armature.active_mocap = bpy.props.StringProperty(update=retarget.NLASystemInitialize)
+bpy.types.Armature.mocapNLATracks = bpy.props.CollectionProperty(type=MocapNLATracks)
+bpy.types.Armature.advancedRetarget = bpy.props.BoolProperty(default=False, update=advancedRetargetToggle)
+bpy.types.Armature.frameStep = smooth_out = bpy.props.IntProperty(name="Frame Skip",
+ default=1,
+ description="Amount of frames to skip - for previewing retargets quickly. 1 is fully sampled",
+ min=1)
+
+#Update function for IK functionality. Is called when IK prop checkboxes are toggled.
+
+
+def toggleIKBone(self, context):
+ 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
+
+
+class MocapMapping(bpy.types.PropertyGroup):
+ name = bpy.props.StringProperty()
+
+bpy.utils.register_class(MocapMapping)
+
+bpy.types.Bone.map = bpy.props.StringProperty()
+bpy.types.Bone.reverseMap = bpy.props.CollectionProperty(type=MocapMapping)
+bpy.types.Bone.foot = bpy.props.BoolProperty(name="Foot",
+ description="Marks this bone as a 'foot', which determines retargeted animation's translation",
+ default=False)
+bpy.types.Bone.twistFix = bpy.props.BoolProperty(name="Twist Fix",
+ description="Fix Twist on this bone",
+ default=False)
+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):
+ '''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)
+ else:
+ return False
+
+
+ #~ class OBJECT_OT_AdvancedRetargetButton(bpy.types.Operator):
+ #~ '''Prepare for advanced retargeting '''
+ #~ bl_idname = "mocap.preretarget"
+ #~ bl_label = "Prepares retarget of active action from Performer to Enduser"
+
+ #~ def execute(self, context):
+ #~ scene = context.scene
+ #~ s_frame = scene.frame_start
+ #~ e_frame = scene.frame_end
+ #~ enduser_obj = context.active_object
+ #~ performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
+ #~ if enduser_obj is None or len(performer_obj) != 1:
+ #~ print("Need active and selected armatures")
+ #~ else:
+ #~ performer_obj = performer_obj[0]
+ #~ retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
+ #~ return {"FINISHED"}
+
+ #~ @classmethod
+ #~ def poll(cls, context):
+ #~ if context.active_object:
+ #~ activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
+ #~ performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
+ #~ if performer_obj:
+ #~ return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
+ #~ else:
+ #~ return False
+
+
+class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
+ '''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'''
+ 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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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 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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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):
+ '''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()