blender/release/scripts/modules/mocap_constraints.py

435 lines
18 KiB
Python

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