318 lines
13 KiB
Python
318 lines
13 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
|
|
import time
|
|
|
|
from bpy.props import *
|
|
from bpy import *
|
|
from mathutils import Vector
|
|
from math import isfinite
|
|
|
|
# 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 Constraint",
|
|
description = "Name of Mocap Constraint")
|
|
boneA = bpy.props.StringProperty(name = "Bone",
|
|
default = "",
|
|
description = "Constrained Bone")
|
|
boneB = bpy.props.StringProperty(name = "Bone (2)",
|
|
default = "",
|
|
description = "Other Constrained Bone (optional, depends on type)")
|
|
s_frame = bpy.props.IntProperty(name = "S",
|
|
default = 1,
|
|
description = "Start frame of constraint")
|
|
e_frame = bpy.props.IntProperty(name = "E",
|
|
default = 500,
|
|
description = "End frame of constrain")
|
|
targetMesh = bpy.props.StringProperty(name = "Mesh",
|
|
default = "",
|
|
description = "Target of Constraint - Mesh (optional, depends on type)")
|
|
active = bpy.props.BoolProperty(name = "Active",
|
|
default = True,
|
|
description = "Constraint is active")
|
|
baked = bpy.props.BoolProperty(name = "Baked / Applied",
|
|
default = False,
|
|
description = "Constraint has been baked to NLA layer")
|
|
targetFrame = bpy.props.IntProperty(name = "Frame",
|
|
default = 1,
|
|
description = "Target of Constraint - Frame (optional, depends on type)")
|
|
targetPoint = bpy.props.FloatVectorProperty(name = "Point", size = 3,
|
|
subtype = "XYZ", default = (0.0, 0.0, 0.0),
|
|
description = "Target of Constraint - Point")
|
|
targetSpace = bpy.props.EnumProperty(
|
|
items = [("world", "World Space", "Evaluate target in global space"),
|
|
("object", "Object space", "Evaluate target in object space"),
|
|
("boneb", "Other Bone Space", "Evaluate target in specified other bone space")],
|
|
name = "Space",
|
|
description = "In which space should Point type target be evaluated")
|
|
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 constraint")
|
|
realConstraint = bpy.props.StringProperty()
|
|
|
|
|
|
bpy.utils.register_class(MocapConstraint)
|
|
|
|
bpy.types.Armature.mocap_constraints = bpy.props.CollectionProperty(type=MocapConstraint)
|
|
|
|
#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 = len(self.bone.parent_recursive)
|
|
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_bone = False
|
|
if hasIKConstraint(self):
|
|
cnstrn_bone = self
|
|
elif self.is_in_ik_chain:
|
|
cnstrn_bone = [child for child in self.children_recursive if hasIKConstraint(child)][0]
|
|
if cnstrn_bone:
|
|
# remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
|
|
ik = [constraint for constraint in cnstrn_bone.constraints if constraint.type == "IK"][0]
|
|
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
|
|
|
|
bpy.types.Bone.map = bpy.props.StringProperty()
|
|
bpy.types.PoseBone.IKRetarget = bpy.props.BoolProperty(name = "IK",
|
|
description = "Toggles IK Retargeting method for given bone",
|
|
update = toggleIKBone, default = False)
|
|
|
|
|
|
def hasIKConstraint(pose_bone):
|
|
#utility function / predicate, returns True if given bone has IK constraint
|
|
return ("IK" in [constraint.type for constraint in pose_bone.constraints])
|
|
|
|
|
|
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()
|
|
|
|
import retarget
|
|
import mocap_tools
|
|
|
|
|
|
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')
|
|
row2 = self.layout.row(align=True)
|
|
row2.operator("mocap.looper", text='Loop animation')
|
|
row2.operator("mocap.limitdof", text='Constrain Rig')
|
|
self.layout.label("Retargeting")
|
|
row3 = self.layout.row(align=True)
|
|
column1 = row3.column(align=True)
|
|
column1.label("Performer Rig")
|
|
column2 = row3.column(align=True)
|
|
column2.label("Enduser Rig")
|
|
self.layout.label("Hierarchy mapping")
|
|
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:
|
|
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.label(bone.name)
|
|
row.prop_search(bone, "map", enduser_arm, "bones")
|
|
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(pose_bone, 'IKRetarget')
|
|
row.label(label_mod)
|
|
|
|
self.layout.operator("mocap.retarget", text='RETARGET!')
|
|
|
|
|
|
class MocapConstraintsPanel(bpy.types.Panel):
|
|
#Motion capture constraints panel
|
|
bl_label = "Mocap constraints"
|
|
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("mocap.addconstraint", text = 'Add constraint')
|
|
layout.separator()
|
|
for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
|
|
box = layout.box()
|
|
box.prop(m_constraint, 'name')
|
|
box.prop(m_constraint, 'type')
|
|
box.prop_search(m_constraint, 'boneA', enduser_obj.pose, "bones")
|
|
if m_constraint.type == "distance" or m_constraint.type == "point":
|
|
box.prop_search(m_constraint, 'boneB', enduser_obj.pose, "bones")
|
|
frameRow = box.row()
|
|
frameRow.label("Frame Range:")
|
|
frameRow.prop(m_constraint, 's_frame')
|
|
frameRow.prop(m_constraint, 'e_frame')
|
|
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 == "freeze":
|
|
targetPropCol.prop(m_constraint, 'targetFrame')
|
|
if m_constraint.type == "point":
|
|
targetPropCol.prop(m_constraint, 'targetPoint')
|
|
box.prop(m_constraint, 'targetSpace')
|
|
checkRow = box.row()
|
|
checkRow.prop(m_constraint, 'active')
|
|
checkRow.prop(m_constraint, 'baked')
|
|
layout.operator("mocap.removeconstraint", text = "Remove constraint").constraint = i
|
|
layout.separator()
|
|
|
|
|
|
class OBJECT_OT_RetargetButton(bpy.types.Operator):
|
|
bl_idname = "mocap.retarget"
|
|
bl_label = "Retargets active action from Performer to Enduser"
|
|
|
|
def execute(self, context):
|
|
retarget.totalRetarget()
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
|
|
bl_idname = "mocap.samples"
|
|
bl_label = "Converts samples / simplifies keyframes to beziers"
|
|
|
|
def execute(self, context):
|
|
mocap_tools.fcurves_simplify()
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OBJECT_OT_LooperButton(bpy.types.Operator):
|
|
bl_idname = "mocap.looper"
|
|
bl_label = "loops animation / sampled mocap data"
|
|
|
|
def execute(self, context):
|
|
mocap_tools.autoloop_anim()
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OBJECT_OT_DenoiseButton(bpy.types.Operator):
|
|
bl_idname = "mocap.denoise"
|
|
bl_label = "Denoises sampled mocap data "
|
|
|
|
def execute(self, context):
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
|
|
bl_idname = "mocap.limitdof"
|
|
bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
|
|
|
|
def execute(self, context):
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
|
|
bl_idname = "mocap.addconstraint"
|
|
bl_label = "Add constraint to target armature"
|
|
|
|
def execute(self, context):
|
|
enduser_obj = bpy.context.active_object
|
|
enduser_arm = enduser_obj.data
|
|
newCon = enduser_arm.mocap_constraints.add()
|
|
return {"FINISHED"}
|
|
|
|
|
|
class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
|
|
bl_idname = "mocap.removeconstraint"
|
|
bl_label = "Removes constraints from target armature"
|
|
constraint = bpy.props.IntProperty()
|
|
|
|
def execute(self, context):
|
|
enduser_obj = bpy.context.active_object
|
|
enduser_arm = enduser_obj.data
|
|
constraints = enduser_arm.mocap_constraints
|
|
constraints.remove(self.constraint)
|
|
return {"FINISHED"}
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_module(__name__)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_module(__name__)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|