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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-07-04 13:22:11 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-07-04 13:22:11 +0300
commite8dfc682098eab13fa4a28ee3a7cc9e99a575ba0 (patch)
treefc975a3b32a89e60c16e93d2f1842abeead276af
parent9a6e192fcecd676dea09233b35f12b8988d853e3 (diff)
mocap tools: move to contrib: T63750
-rw-r--r--mocap/__init__.py933
-rw-r--r--mocap/mocap_constraints.py451
-rw-r--r--mocap/mocap_tools.py939
-rw-r--r--mocap/retarget.py571
4 files changed, 0 insertions, 2894 deletions
diff --git a/mocap/__init__.py b/mocap/__init__.py
deleted file mode 100644
index f040e0b1..00000000
--- a/mocap/__init__.py
+++ /dev/null
@@ -1,933 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-bl_info = {
- "name": "Motion Capture Tools",
- "author": "Benjy Cook",
- "blender": (2, 73, 0),
- "version": (1, 1, 1),
- "location": "Active Armature > Object Properties > Mocap tools",
- "description": "Various tools for working with motion capture animation",
- "warning": "",
- "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
- "Scripts/Animation/Motion_Capture_Tools",
- "support": 'OFFICIAL',
- "category": "Animation",
-}
-
-if "bpy" in locals():
- import importlib
- if "mocap_constraints" in locals():
- importlib.reload(mocap_constraints)
- if "retarget" in locals():
- importlib.reload(retarget)
- if "mocap_tools" in locals():
- importlib.reload(mocap_tools)
-else:
- import bpy
- from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
- from . import (
- mocap_constraints,
- retarget,
- mocap_tools,
- )
-
-
-# MocapConstraint class
-# Defines MocapConstraint datatype, used to add and configute mocap constraints
-# Attached to Armature data
-
-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
-
-
-class MocapConstraint(bpy.types.PropertyGroup):
- name: StringProperty(name="Name",
- default="Mocap Fix",
- description="Name of Mocap Fix",
- update=mocap_constraints.setConstraint)
- constrained_bone: StringProperty(name="Bone",
- default="",
- description="Constrained Bone",
- update=mocap_constraints.updateConstraintBoneType)
- constrained_boneB: StringProperty(name="Bone (2)",
- default="",
- description="Other Constrained Bone (optional, depends on type)",
- update=mocap_constraints.setConstraint)
- s_frame: IntProperty(name="Start",
- default=0,
- description="Start frame of Fix",
- update=mocap_constraints.setConstraint)
- e_frame: IntProperty(name="End",
- default=100,
- description="End frame of Fix",
- update=mocap_constraints.setConstraint)
- smooth_in: IntProperty(name="In",
- default=10,
- description="Number of frames to smooth in",
- update=mocap_constraints.setConstraint,
- min=0)
- smooth_out: IntProperty(name="Out",
- default=10,
- description="Number of frames to smooth out",
- update=mocap_constraints.setConstraint,
- min=0)
- targetMesh: StringProperty(name="Mesh",
- default="",
- description="Target of Fix - Mesh (optional, depends on type)",
- update=mocap_constraints.setConstraint)
- active: BoolProperty(name="Active",
- default=True,
- description="Fix is active",
- update=mocap_constraints.setConstraint)
- show_expanded: BoolProperty(name="Show Expanded",
- default=True,
- description="Fix is fully shown")
- targetPoint: FloatVectorProperty(name="Point", size=3,
- subtype="XYZ", default=(0.0, 0.0, 0.0),
- description="Target of Fix - Point",
- update=mocap_constraints.setConstraint)
- targetDist: FloatProperty(name="Offset",
- default=0.0,
- description="Distance and Floor Fixes - Desired offset",
- update=mocap_constraints.setConstraint)
- targetSpace: 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=mocap_constraints.setConstraint)
- type: 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=mocap_constraints.updateConstraintBoneType)
- real_constraint: StringProperty()
- real_constraint_bone: StringProperty()
-
-
-# Animation Stitch Settings, used for animation stitching of 2 retargeted animations.
-class AnimationStitchSettings(bpy.types.PropertyGroup):
- first_action: StringProperty(name="Action 1",
- description="First action in stitch")
- second_action: StringProperty(name="Action 2",
- description="Second action in stitch")
- blend_frame: IntProperty(name="Stitch frame",
- description="Frame to locate stitch on")
- blend_amount: IntProperty(name="Blend amount",
- description="Size of blending transition, on both sides of the stitch",
- default=10)
- second_offset: IntProperty(name="Second offset",
- description="Frame offset for 2nd animation, where it should start",
- default=10)
- stick_bone: StringProperty(name="Stick Bone",
- description="Bone to freeze during transition",
- default="")
-
-
-# MocapNLA Tracks. Stores which tracks/actions are associated with each retargeted animation.
-class MocapNLATracks(bpy.types.PropertyGroup):
- name: StringProperty()
- base_track: StringProperty()
- auto_fix_track: StringProperty()
- manual_fix_track: StringProperty()
- stride_action: StringProperty()
-
-
-#Update function for Advanced Retarget boolean variable.
-def advancedRetargetToggle(self, context):
- enduser_obj = context.active_object
- performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
- if enduser_obj is None or len(performer_obj) != 1:
- print("Need active and selected armatures")
- return
- else:
- performer_obj = performer_obj[0]
- if self.advancedRetarget:
- retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
- else:
- retarget.cleanTempConstraints(enduser_obj)
-
-
-def toggleIKBone(self, context):
- #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
- if self.IKRetarget:
- if not self.is_in_ik_chain:
- print(self.name + " IK toggled ON!")
- ik = self.constraints.new('IK')
- #ik the whole chain up to the root, excluding
- chainLen = 0
- for parent_bone in self.parent_recursive:
- chainLen += 1
- if hasIKConstraint(parent_bone):
- break
- #~ deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
- #~ if len(deformer_children) > 1:
- #~ break
- ik.chain_count = chainLen
- for bone in self.parent_recursive:
- if bone.is_in_ik_chain:
- bone.IKRetarget = True
- else:
- print(self.name + " IK toggled OFF!")
- cnstrn_bones = []
- newChainLength = []
- if hasIKConstraint(self):
- cnstrn_bones = [self]
- elif self.is_in_ik_chain:
- cnstrn_bones = [child for child in self.children_recursive if hasIKConstraint(child)]
- for cnstrn_bone in cnstrn_bones:
- newChainLength.append(cnstrn_bone.parent_recursive.index(self) + 1)
- if cnstrn_bones:
- # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
- for i, cnstrn_bone in enumerate(cnstrn_bones):
- print(cnstrn_bone.name)
- if newChainLength:
- ik = hasIKConstraint(cnstrn_bone)
- ik.chain_count = newChainLength[i]
- else:
- ik = hasIKConstraint(cnstrn_bone)
- cnstrn_bone.constraints.remove(ik)
- cnstrn_bone.IKRetarget = False
- for bone in cnstrn_bone.parent_recursive:
- if not bone.is_in_ik_chain:
- bone.IKRetarget = False
-
-
-#MocapMap class for storing mapping on enduser performer,
-# where a bone may be linked to more than one on the performer
-class MocapMapping(bpy.types.PropertyGroup):
- name: StringProperty()
-
-# Disabling for now [#28933] - campbell
-'''
-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()
-'''
-
-
-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
-
-
-class MocapPanel(bpy.types.Panel):
- # Motion capture retargeting panel
- bl_label = "Mocap tools"
- bl_space_type = "PROPERTIES"
- bl_region_type = "WINDOW"
- bl_context = "object"
-
- @classmethod
- def poll(cls, context):
- obj = context.object
- return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
- 'POSE',
- 'OBJECT'}
-
- def draw(self, context):
- layout = self.layout
-
- layout.label(text="Preprocessing:")
-
- row = layout.row(align=True)
- 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')
-
- row = layout.row(align=True)
- row.operator("mocap.looper", text='Loop animation')
- row.operator("mocap.limitdof", text='Constrain Rig')
- row.operator("mocap.removelimitdof", text='Unconstrain Rig')
-
- layout.label(text="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:
- layout.label(text="Select performer rig and target rig (as active)")
- else:
- layout.operator("mocap.guessmapping", text="Guess Hierarchy Mapping")
- labelRow = layout.row(align=True)
- labelRow.label(text="Performer Rig")
- labelRow.label(text="End user 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
- MappingRow = layout.row(align=True)
- footCol = MappingRow.column(align=True)
- nameCol = MappingRow.column(align=True)
- nameCol.scale_x = 2
- mapCol = MappingRow.column(align=True)
- mapCol.scale_x = 2
- selectCol = MappingRow.column(align=True)
- twistCol = MappingRow.column(align=True)
- IKCol = MappingRow.column(align=True)
- IKCol.scale_x = 0.3
- IKLabel = MappingRow.column(align=True)
- IKLabel.scale_x = 0.2
- for bone in perf.bones:
- footCol.prop(data=bone, property='foot', text='', icon='POSE_DATA')
- nameCol.label(bone.name)
- mapCol.prop_search(bone, "map", enduser_arm, "bones", text='')
- selectCol.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"
- end_bone = enduser_obj.data.bones[bone.map]
- twistCol.prop(data=end_bone, property='twistFix', text='', icon='RNA')
- IKCol.prop(pose_bone, 'IKRetarget')
- IKLabel.label(label_mod)
- else:
- twistCol.label(text=" ")
- IKCol.label(text=" ")
- IKLabel.label(text=" ")
- mapRow = layout.row()
- mapRow.operator("mocap.savemapping", text='Save mapping')
- mapRow.operator("mocap.loadmapping", text='Load mapping')
- extraSettings = self.layout.box()
- if performer_obj.animation_data and performer_obj.animation_data.action:
- extraSettings.prop(data=performer_obj.animation_data.action, property='name', text='Action Name')
- extraSettings.prop(enduser_arm, "frameStep")
- extraSettings.prop(enduser_arm, "advancedRetarget", text='Advanced Retarget')
- 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"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- obj = context.object
- return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
- 'POSE',
- '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', text="")
- 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(text="Frame Range:")
- frameRow.prop(m_constraint, 's_frame')
- frameRow.prop(m_constraint, 'e_frame')
- smoothRow = box.row()
- smoothRow.label(text="Smoothing:")
- smoothRow.prop(m_constraint, 'smooth_in')
- smoothRow.prop(m_constraint, 'smooth_out')
- targetRow = box.row()
- targetLabelCol = targetRow.column()
- targetLabelCol.label(text="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"
- bl_options = {'DEFAULT_CLOSED'}
-
- @classmethod
- def poll(cls, context):
- obj = context.object
- return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
- 'POSE',
- 'OBJECT'}
- def draw(self, context):
- layout = self.layout
- layout.operator("mocap.samples", text='Samples to Beziers')
- layout.operator('mocap.pathediting', text="Follow Path")
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- if activeIsArmature:
- enduser_arm = context.active_object.data
- selectBox = layout.box()
- selectRetargets = selectBox.row()
- selectRetargets.label(text="Retargeted Animations:")
- selectRetargets.prop_search(enduser_arm, "active_mocap", enduser_arm, "mocapNLATracks")
- stitchBox = layout.box()
- stitchBox.label(text="Animation Stitching")
- settings = enduser_arm.stitch_settings
- stitchBox.prop_search(settings, "first_action", enduser_arm, "mocapNLATracks")
- stitchBox.prop_search(settings, "second_action", enduser_arm, "mocapNLATracks")
- stitchSettings = stitchBox.row()
- stitchSettings.prop(settings, "blend_frame")
- stitchSettings.prop(settings, "blend_amount")
- stitchSettings.prop(settings, "second_offset")
- stitchBox.prop_search(settings, "stick_bone", context.active_object.pose, "bones")
- stitchBox.operator('mocap.animstitchguess', text="Guess Settings")
- stitchBox.operator('mocap.animstitch', text="Stitch Animations")
-
-
-class OBJECT_OT_RetargetButton(bpy.types.Operator):
- #Retargeting operator. Assumes selected and active armatures, where the performer (the selected one)
- # has an action for retargeting
- """Retarget animation from selected armature to active armature"""
- bl_idname = "mocap.retarget"
- bl_label = "Retarget"
- bl_options = {'REGISTER', 'UNDO'}
-
- def execute(self, context):
- scene = context.scene
- s_frame = scene.frame_start
- e_frame = scene.frame_end
- enduser_obj = context.active_object
- performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
- if enduser_obj is None or len(performer_obj) != 1:
- print("Need active and selected armatures")
- else:
- performer_obj = performer_obj[0]
- s_frame, e_frame = performer_obj.animation_data.action.frame_range
- s_frame = int(s_frame)
- e_frame = int(e_frame)
- if retarget.isRigAdvanced(enduser_obj) and not enduser_obj.data.advancedRetarget:
- print("Recommended to use Advanced Retargeting method")
- enduser_obj.data.advancedRetarget = True
- else:
- retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature) and performer_obj[0].animation_data
- else:
- return False
-
-
-class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
- #Operator for saving mapping to enduser armature
- """Save mapping to active armature (for future retargets)"""
- bl_idname = "mocap.savemapping"
- bl_label = "Save 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]
- retarget.createDictionary(performer_obj.data, enduser_obj.data)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
- else:
- return False
-
-
-class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
- """Load saved mapping from active armature"""
- #Operator for loading mapping to enduser armature
- bl_idname = "mocap.loadmapping"
- bl_label = "Load 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]
- retarget.loadMapping(performer_obj.data, enduser_obj.data)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
- else:
- return False
-
-
-class OBJECT_OT_SelectMapBoneButton(bpy.types.Operator):
- #Operator for setting selected bone in enduser armature to the performer mapping
- """Select a bone for faster mapping"""
- bl_idname = "mocap.selectmap"
- bl_label = "Select Mapping Bone"
- perf_bone: StringProperty()
-
- def execute(self, context):
- enduser_obj = bpy.context.active_object
- performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
- selectedBone = ""
- for bone in enduser_obj.data.bones:
- boneVis = bone.layers
- for i in range(32):
- if boneVis[i] and enduser_obj.data.layers[i]:
- if bone.select:
- selectedBone = bone.name
- break
- performer_obj.data.bones[self.perf_bone].map = selectedBone
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
- else:
- return False
-
-
-class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
- #Operator to convert samples to beziers on the selected object
- """Convert active armature's sampled keyframed to beziers"""
- bl_idname = "mocap.samples"
- bl_label = "Convert Samples"
-
- def execute(self, context):
- mocap_tools.fcurves_simplify(context, context.active_object)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- return context.active_object.animation_data
-
-
-class OBJECT_OT_LooperButton(bpy.types.Operator):
- #Operator to trim fcurves which contain a few loops to a single one on the selected object
- """Trim active armature's animation to a single cycle, given """ \
- """a cyclic animation (such as a walk cycle)"""
- bl_idname = "mocap.looper"
- bl_label = "Loop Mocap"
-
- def execute(self, context):
- mocap_tools.autoloop_anim()
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- return context.active_object.animation_data
-
-
-class OBJECT_OT_DenoiseButton(bpy.types.Operator):
- #Operator to denoise impluse noise on the active object's fcurves
- """Removes spikes from all fcurves on the selected object"""
- bl_idname = "mocap.denoise"
- bl_label = "Denoise Mocap"
-
- def execute(self, context):
- obj = context.active_object
- mocap_tools.denoise(obj, obj.animation_data.action.fcurves)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- obj = context.active_object
- return obj and obj.animation_data and obj.animation_data.action
-
-
-class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
- #Operator to analyze performer armature and apply rotation constraints on the enduser armature
- """Create limit constraints on the active armature from """ \
- """the selected armature's animation's range of motion"""
- bl_idname = "mocap.limitdof"
- bl_label = "Set DOF Constraints"
-
- def execute(self, context):
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object][0]
- mocap_tools.limit_dof(context, performer_obj, context.active_object)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
- else:
- return False
-
-
-class OBJECT_OT_RemoveLimitDOFButton(bpy.types.Operator):
- #Removes constraints created by above operator
- """Remove previously created limit constraints on the active armature"""
- bl_idname = "mocap.removelimitdof"
- bl_label = "Remove DOF Constraints"
-
- def execute(self, context):
- mocap_tools.limit_dof_toggle_off(context, context.active_object)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- activeIsArmature = False
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- return activeIsArmature
-
-
-class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
- #Operator to fix common imported Mocap data issue of wrong axis system on active object
- """Realign the active armature's axis system to match Blender """ \
- """(commonly needed after bvh import)"""
- bl_idname = "mocap.rotate_fix"
- bl_label = "Rotate Fix"
-
- def execute(self, context):
- mocap_tools.rotate_fix_armature(context.active_object.data)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- return isinstance(context.active_object.data, bpy.types.Armature)
-
-
-class OBJECT_OT_ScaleFixArmature(bpy.types.Operator):
- #Operator to scale down the selected armature to match the active one
- """Rescale selected armature to match the active animation, """ \
- """for convenience"""
- bl_idname = "mocap.scale_fix"
- bl_label = "Scale Fix"
-
- def execute(self, context):
- enduser_obj = bpy.context.active_object
- performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
- mocap_tools.scale_fix_armature(performer_obj, enduser_obj)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
- else:
- return False
-
-
-class MOCAP_OT_AddMocapFix(bpy.types.Operator):
- #Operator to add a post-retarget fix
- """Add a post-retarget fix - useful for fixing certain """ \
- """artifacts following the retarget"""
- bl_idname = "mocap.addmocapfix"
- bl_label = "Add Mocap Fix"
- type: EnumProperty(name="Type of Fix",
- items=[("point", "Maintain Position", "Bone is at a specific point"),
- ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
- ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
- ("distance", "Maintain distance", "Target bones maintained specified distance")],
- description="Type of fix")
-
- def execute(self, context):
- enduser_obj = bpy.context.active_object
- enduser_arm = enduser_obj.data
- new_mcon = enduser_arm.mocap_constraints.add()
- new_mcon.type = self.type
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- return isinstance(context.active_object.data, bpy.types.Armature)
-
-
-class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
- #Operator to remove a post-retarget fix
- """Remove this post-retarget fix"""
- bl_idname = "mocap.removeconstraint"
- bl_label = "Remove Mocap Fix"
- constraint: 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 = mocap_constraints.getConsObj(bone)
- mocap_constraints.removeConstraint(m_constraint, cons_obj)
- m_constraints.remove(self.constraint)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- return isinstance(context.active_object.data, bpy.types.Armature)
-
-
-class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
- #Operator to bake all post-retarget fixes
- """Bake all post-retarget fixes to the Retarget Fixes NLA Track"""
- bl_idname = "mocap.bakeconstraints"
- bl_label = "Bake Mocap Fixes"
-
- def execute(self, context):
- mocap_constraints.bakeConstraints(context)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- return isinstance(context.active_object.data, bpy.types.Armature)
-
-
-class OBJECT_OT_UnbakeMocapConstraints(bpy.types.Operator):
- #Operator to unbake all post-retarget fixes
- """Unbake all post-retarget fixes - removes the baked data """ \
- """from the Retarget Fixes NLA Track"""
- bl_idname = "mocap.unbakeconstraints"
- bl_label = "Unbake Mocap Fixes"
-
- def execute(self, context):
- mocap_constraints.unbakeConstraints(context)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- return isinstance(context.active_object.data, bpy.types.Armature)
-
-
-class OBJECT_OT_UpdateMocapConstraints(bpy.types.Operator):
- #Operator to update all post-retarget fixes, similar to update dependencies on drivers
- #Needed because python properties lack certain callbacks and some fixes take a while to recalculate.
- """Update all post-retarget fixes (necessary to take under """ \
- """consideration changes to armature object or pose)"""
- bl_idname = "mocap.updateconstraints"
- bl_label = "Update Mocap Fixes"
-
- def execute(self, context):
- mocap_constraints.updateConstraints(context.active_object, context)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- return isinstance(context.active_object.data, bpy.types.Armature)
-
-
-class OBJECT_OT_GuessHierachyMapping(bpy.types.Operator):
- #Operator which calls heurisitic function to guess mapping between 2 armatures
- """Attempt to auto figure out hierarchy mapping"""
- bl_idname = "mocap.guessmapping"
- bl_label = "Guess Hierarchy Mapping"
-
- def execute(self, context):
- enduser_obj = bpy.context.active_object
- performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
- mocap_tools.guessMapping(performer_obj, enduser_obj)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
- if performer_obj:
- return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
- else:
- return False
-
-
-class OBJECT_OT_PathEditing(bpy.types.Operator):
- #Operator which calls path editing function, making active object follow the selected curve.
- """Set active object (stride object) to follow the selected curve"""
- bl_idname = "mocap.pathediting"
- bl_label = "Set Path"
-
- def execute(self, context):
- path = [obj for obj in context.selected_objects if obj != context.active_object][0]
- mocap_tools.path_editing(context, context.active_object, path)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- if context.active_object:
- selected_objs = [obj for obj in context.selected_objects if obj != context.active_object and isinstance(obj.data, bpy.types.Curve)]
- return selected_objs
- else:
- return False
-
-
-class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator):
- #Operator which calls stitching function, combining 2 animations onto the NLA.
- """Stitch two defined animations into a single one via alignment of NLA Tracks"""
- bl_idname = "mocap.animstitch"
- bl_label = "Stitch Animations"
-
- def execute(self, context):
- mocap_tools.anim_stitch(context, context.active_object)
- return {'FINISHED'}
-
- @classmethod
- def poll(cls, context):
- activeIsArmature = False
- if context.active_object:
- activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
- if activeIsArmature:
- stitch_settings = context.active_object.data.stitch_settings
- return (stitch_settings.first_action and stitch_settings.second_action)
- return False
-
-
-class OBJECT_OT_GuessAnimationStitchingButton(bpy.types.Operator):
- #Operator which calls stitching function heuristic, setting good values for above operator.
- """Guess the stitch frame and second offset for animation stitch"""
- bl_idname = "mocap.animstitchguess"
- bl_label = "Guess 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_class(MocapConstraint)
- bpy.types.Armature.mocap_constraints = CollectionProperty(type=MocapConstraint)
- bpy.utils.register_class(MocapMapping)
- #string property for storing performer->enduser mapping
- bpy.types.Bone.map = StringProperty()
- #Collection Property for storing enduser->performer mapping
- bpy.types.Bone.reverseMap = CollectionProperty(type=MocapMapping)
- #Boolean property for storing foot bone toggle
- bpy.types.Bone.foot = BoolProperty(name="Foot",
- description="Mark this bone as a 'foot', which determines retargeted animation's translation",
- default=False)
- #Boolean property for storing if this bone is twisted along the y axis,
- # which can happen due to various sources of performers
- bpy.types.Bone.twistFix = BoolProperty(name="Twist Fix",
- description="Fix Twist on this bone",
- default=False)
- #Boolean property for toggling ik retargeting for this bone
- bpy.types.PoseBone.IKRetarget = BoolProperty(name="IK",
- description="Toggle IK Retargeting method for given bone",
- update=toggleIKBone, default=False)
- bpy.utils.register_class(AnimationStitchSettings)
- bpy.utils.register_class(MocapNLATracks)
- #Animation Stitch Settings Property
- bpy.types.Armature.stitch_settings = PointerProperty(type=AnimationStitchSettings)
- #Current/Active retargeted animation on the armature
- bpy.types.Armature.active_mocap = StringProperty(update=retarget.NLASystemInitialize)
- #Collection of retargeted animations and their NLA Tracks on the armature
- bpy.types.Armature.mocapNLATracks = CollectionProperty(type=MocapNLATracks)
- #Advanced retargeting boolean property
- bpy.types.Armature.advancedRetarget = BoolProperty(default=False, update=advancedRetargetToggle)
- #frame step - frequency of frames to retarget. Skipping is useful for previewing, faster work etc.
- bpy.types.Armature.frameStep = IntProperty(name="Frame Skip",
- default=1,
- description="Number of frames to skip - for previewing retargets quickly (1 is fully sampled)",
- min=1)
- bpy.utils.register_module(__name__)
-
-
-def unregister():
- bpy.utils.unregister_module(__name__)
-
-if __name__ == "__main__":
- register()
diff --git a/mocap/mocap_constraints.py b/mocap/mocap_constraints.py
deleted file mode 100644
index 3d3e4a60..00000000
--- a/mocap/mocap_constraints.py
+++ /dev/null
@@ -1,451 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-import bpy
-from mathutils import Vector
-from bpy_extras import anim_utils
-from . import retarget
-
-
-### 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":
- context.scene.frame_set(s)
- real_constraint.owner_space = m_constraint.targetSpace
- bpy.context.scene.frame_set(m_constraint.s_frame)
- if isinstance(cons_obj, bpy.types.PoseBone):
- vec = obj.matrix_world * (cons_obj.matrix.to_translation())
- #~ if obj.parent:
- #~ vec = obj.parent.matrix_world * vec
- x, y, z = vec
- 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 = obj
- real_constraint.subtarget = getConsObj(bones[m_constraint.constrained_boneB]).name
- real_constraint.limit_mode = "LIMITDIST_ONSURFACE"
- if m_constraint.targetDist < 0.01:
- m_constraint.targetDist = 0.01
- 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 throughout 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 = obj.matrix_world.to_3x3() * Vector((0, 0, 1))
- offset = obj.matrix_world.to_3x3() * Vector((0, 0, m_constraint.targetDist))
- ray_origin = (cons_obj.matrix * 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_origin
- ray_target = floor.matrix_world.inverted() * ray_target
- ray_direction = ray_target - ray_origin
- ok, hit, nor, ind = floor.ray_cast(ray_origin, ray_direction)
- if ok:
- bakedPos[t] = (floor.matrix_world * hit)
- bakedPos[t] += Vector((0, 0, m_constraint.targetDist))
- else:
- bakedPos[t] = (cons_obj.matrix * 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 = retarget.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
- anim_utils.bake_action(
- obj,
- 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 relevant 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 = retarget.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 = retarget.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/mocap/mocap_tools.py b/mocap/mocap_tools.py
deleted file mode 100644
index 5f9c2a2d..00000000
--- a/mocap/mocap_tools.py
+++ /dev/null
@@ -1,939 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-from math import sqrt, radians, floor, ceil
-import bpy
-import time
-from mathutils import Vector, Matrix
-
-
-# A Python implementation of n sized Vectors.
-# Mathutils has a max size of 4, and we need at least 5 for Simplify Curves and even more for Cross Correlation.
-# Vector utility functions
-class NdVector:
- vec = []
-
- def __init__(self, vec):
- self.vec = vec[:]
-
- def __len__(self):
- return len(self.vec)
-
- def __mul__(self, otherMember):
- # assume anything with list access is a vector
- if isinstance(otherMember, NdVector):
- a = self.vec
- b = otherMember.vec
- n = len(self)
- return sum([a[i] * b[i] for i in range(n)])
- else:
- # int/float
- return NdVector([otherMember * x for x in self.vec])
-
- 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])
-
- @property
- def length(self):
- return sqrt(self * self)
-
- @property
- def lengthSq(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]
-
- @property
- def x(self):
- return self.vec[0]
-
- @property
- def y(self):
- return self.vec[1]
-
- def resize_2d(self):
- return Vector((self.x, self.y))
-
-
-#Sampled Data Point class for Simplify Curves
-class dataPoint:
- index = 0
- # x,y1,y2,y3 coordinate of original point
- co = NdVector((0, 0, 0, 0, 0))
- #position according to parametric view of original data, [0,1] range
- u = 0
- #use this for anything
- temp = 0
-
- def __init__(self, index, co, u=0):
- self.index = index
- self.co = co
- self.u = u
-
-
-# Helper to convert from a sampled fcurve back to editable keyframes one.
-def make_editable_fcurves(fcurves):
- for fc in fcurves:
- if fc.sampled_points:
- fc.convert_to_keyframes(floor(fc.sampled_points[0].co[0]), ceil(fc.sampled_points[-1].co[0]) + 1)
-
-
-#Cross Correlation Function
-#http://en.wikipedia.org/wiki/Cross_correlation
-#IN: curvesA, curvesB - bpy_collection/list of fcurves to analyze. Auto-Correlation is when they are the same.
-# margin - When searching for the best "start" frame, how large a neighborhood of frames should we inspect (similar to epsilon in Calculus)
-#OUT: startFrame, length of new anim, and curvesA
-def crossCorrelationMatch(curvesA, curvesB, margin):
- dataA = []
- dataB = []
- start = int(max(curvesA[0].range()[0], curvesB[0].range()[0]))
- end = int(min(curvesA[0].range()[1], curvesB[0].range()[1]))
-
- #transfer all fcurves data on each frame to a single NdVector.
- for i in range(1, end):
- vec = []
- for fcurve in curvesA:
- if fcurve.data_path in [otherFcurve.data_path for otherFcurve in curvesB]:
- vec.append(fcurve.evaluate(i))
- dataA.append(NdVector(vec))
- vec = []
- for fcurve in curvesB:
- if fcurve.data_path in [otherFcurve.data_path for otherFcurve in curvesA]:
- vec.append(fcurve.evaluate(i))
- dataB.append(NdVector(vec))
-
- #Comparator for Cross Correlation. "Classic" implementation uses dot product, as do we.
- def comp(a, b):
- return a * b
-
- #Create Rxy, which holds the Cross Correlation data.
- N = len(dataA)
- Rxy = [0.0] * N
- for i in range(N):
- for j in range(i, min(i + N, N)):
- Rxy[i] += comp(dataA[j], dataB[j - i])
- for j in range(i):
- Rxy[i] += comp(dataA[j], dataB[j - i + N])
- Rxy[i] /= float(N)
-
- #Find the Local maximums in the Cross Correlation data via numerical derivative.
- def LocalMaximums(Rxy):
- Rxyd = [Rxy[i] - Rxy[i - 1] for i in range(1, len(Rxy))]
- maxs = []
- for i in range(1, len(Rxyd) - 1):
- a = Rxyd[i - 1]
- b = Rxyd[i]
- #sign change (zerocrossing) at point i, denoting max point (only)
- if (a >= 0 and b < 0) or (a < 0 and b >= 0):
- maxs.append((i, max(Rxy[i], Rxy[i - 1])))
- return [x[0] for x in maxs]
- #~ return max(maxs, key=lambda x: x[1])[0]
-
- #flms - the possible offsets of the first part of the animation. In Auto-Corr, this is the length of the loop.
- flms = LocalMaximums(Rxy[0:int(len(Rxy))])
- ss = []
-
- #for every local maximum, find the best one - i.e. also has the best start frame.
- for flm in flms:
- diff = []
-
- for i in range(len(dataA) - flm):
- diff.append((dataA[i] - dataB[i + flm]).lengthSq)
-
- def lowerErrorSlice(diff, e):
- #index, error at index
- bestSlice = (0, 100000)
- for i in range(e, len(diff) - e):
- errorSlice = sum(diff[i - e:i + e + 1])
- if errorSlice < bestSlice[1]:
- bestSlice = (i, errorSlice, flm)
- return bestSlice
-
- s = lowerErrorSlice(diff, margin)
- ss.append(s)
-
- #Find the best result and return it.
- ss.sort(key=lambda x: x[1])
- return ss[0][2], ss[0][0], dataA
-
-
-#Uses auto correlation (cross correlation of the same set of curves) and trims the active_object's fcurves
-#Except for location curves (which in mocap tend to be not cyclic, e.g. a walk cycle forward)
-#Transfers the fcurve data to a list of NdVector (length of list is number of fcurves), and calls the cross correlation function.
-#Then trims the fcurve accordingly.
-#IN: Nothing, set the object you want as active and call. Assumes object has animation_data.action!
-#OUT: Trims the object's fcurves (except location curves).
-def autoloop_anim():
- context = bpy.context
- obj = context.active_object
-
- def locCurve(x):
- x.data_path == "location"
-
- fcurves = [x for x in obj.animation_data.action.fcurves if not locCurve(x)]
-
- margin = 10
-
- flm, s, data = crossCorrelationMatch(fcurves, fcurves, margin)
- loop = data[s:s + flm]
-
- #performs blending with a root falloff on the seam's neighborhood to ensure good tiling.
- for i in range(1, margin + 1):
- w1 = sqrt(float(i) / margin)
- loop[-i] = (loop[-i] * w1) + (loop[0] * (1 - w1))
-
- for curve in fcurves:
- pts = curve.keyframe_points
- for i in range(len(pts) - 1, -1, -1):
- pts.remove(pts[i])
-
- for c, curve in enumerate(fcurves):
- pts = curve.keyframe_points
- for i in range(len(loop)):
- pts.insert(i + 2, loop[i][c])
-
- context.scene.frame_end = flm
-
-
-#simplifyCurves: performs the bulk of the samples to bezier conversion.
-#IN: curveGroup - which can be a collection of singleFcurves, or grouped (via nested lists) .
-# error - threshold of permittable error (max distance) of the new beziers to the original data
-# reparaError - threshold of error where we should try to fix the parameterization rather than split the existing curve. > error, usually by a small constant factor for best performance.
-# maxIterations - maximum number of iterations of reparameterizations we should attempt. (Newton-Rahpson is not guaranteed to converge, so this is needed).
-# group_mode - boolean, indicating whether we should place bezier keyframes on the same x (frame), or optimize each individual curve.
-#OUT: None. Deletes the existing curves and creates the new beziers.
-def simplifyCurves(curveGroup, error, reparaError, maxIterations, group_mode):
- #Calculates the unit tangent of point v
- def unitTangent(v, data_pts):
- tang = NdVector((0, 0, 0, 0, 0))
- if v != 0:
- #If it's not the first point, we can calculate a leftside tangent
- tang += data_pts[v].co - data_pts[v - 1].co
- if v != len(data_pts) - 1:
- #If it's not the last point, we can calculate a rightside tangent
- tang += data_pts[v + 1].co - data_pts[v].co
- tang.normalize()
- return tang
-
- #assign parametric u value for each point in original data, via relative arc length
- #http://en.wikipedia.org/wiki/Arc_length
- def chordLength(data_pts, s, e):
- totalLength = 0
- for pt in data_pts[s:e + 1]:
- i = pt.index
- if i == s:
- chordLength = 0
- else:
- chordLength = (data_pts[i].co - data_pts[i - 1].co).length
- totalLength += chordLength
- pt.temp = totalLength
- for pt in data_pts[s:e + 1]:
- if totalLength == 0:
- print(s, e)
- pt.u = (pt.temp / totalLength)
-
- # get binomial coefficient lookup table, this function/table is only called with args
- # (3,0),(3,1),(3,2),(3,3),(2,0),(2,1),(2,2)!
- binomDict = {(3, 0): 1,
- (3, 1): 3,
- (3, 2): 3,
- (3, 3): 1,
- (2, 0): 1,
- (2, 1): 2,
- (2, 2): 1,
- }
-
- #value at pt t of a single bernstein Polynomial
- def bernsteinPoly(n, i, t):
- binomCoeff = binomDict[(n, i)]
- return binomCoeff * pow(t, i) * pow(1 - t, n - i)
-
- # fit a single cubic to data points in range [s(tart),e(nd)].
- def fitSingleCubic(data_pts, s, e):
-
- # A - matrix used for calculating C matrices for fitting
- def A(i, j, s, e, t1, t2):
- if j == 1:
- t = t1
- if j == 2:
- t = t2
- u = data_pts[i].u
- return t * bernsteinPoly(3, j, u)
-
- # X component, used for calculating X matrices for fitting
- def xComponent(i, s, e):
- di = data_pts[i].co
- u = data_pts[i].u
- v0 = data_pts[s].co
- v3 = data_pts[e].co
- a = v0 * bernsteinPoly(3, 0, u)
- b = v0 * bernsteinPoly(3, 1, u)
- c = v3 * bernsteinPoly(3, 2, u)
- d = v3 * bernsteinPoly(3, 3, u)
- return (di - (a + b + c + d))
-
- t1 = unitTangent(s, data_pts)
- t2 = unitTangent(e, data_pts)
- c11 = sum([A(i, 1, s, e, t1, t2) * A(i, 1, s, e, t1, t2) for i in range(s, e + 1)])
- c12 = sum([A(i, 1, s, e, t1, t2) * A(i, 2, s, e, t1, t2) for i in range(s, e + 1)])
- c21 = c12
- c22 = sum([A(i, 2, s, e, t1, t2) * A(i, 2, s, e, t1, t2) for i in range(s, e + 1)])
-
- x1 = sum([xComponent(i, s, e) * A(i, 1, s, e, t1, t2) for i in range(s, e + 1)])
- x2 = sum([xComponent(i, s, e) * A(i, 2, s, e, t1, t2) for i in range(s, e + 1)])
-
- # calculate Determinate of the 3 matrices
- det_cc = c11 * c22 - c21 * c12
- det_cx = c11 * x2 - c12 * x1
- det_xc = x1 * c22 - x2 * c12
-
- # if matrix is not homogenous, fudge the data a bit
- if det_cc == 0:
- det_cc = 0.01
-
- # alpha's are the correct offset for bezier handles
- alpha0 = det_xc / det_cc # offset from right (first) point
- alpha1 = det_cx / det_cc # offset from left (last) point
-
- sRightHandle = data_pts[s].co.copy()
- sTangent = t1 * abs(alpha0)
- sRightHandle += sTangent # position of first pt's handle
- eLeftHandle = data_pts[e].co.copy()
- eTangent = t2 * abs(alpha1)
- eLeftHandle += eTangent # position of last pt's handle.
-
- # return a 4 member tuple representing the bezier
- return (data_pts[s].co,
- sRightHandle,
- eLeftHandle,
- data_pts[e].co)
-
- # convert 2 given data points into a cubic bezier.
- # handles are offset along the tangent at
- # a 3rd of the length between the points.
- def fitSingleCubic2Pts(data_pts, s, e):
- alpha0 = alpha1 = (data_pts[s].co - data_pts[e].co).length / 3
-
- sRightHandle = data_pts[s].co.copy()
- sTangent = unitTangent(s, data_pts) * abs(alpha0)
- sRightHandle += sTangent # position of first pt's handle
- eLeftHandle = data_pts[e].co.copy()
- eTangent = unitTangent(e, data_pts) * abs(alpha1)
- eLeftHandle += eTangent # position of last pt's handle.
-
- #return a 4 member tuple representing the bezier
- return (data_pts[s].co,
- sRightHandle,
- eLeftHandle,
- data_pts[e].co)
-
- #evaluate bezier, represented by a 4 member tuple (pts) at point t.
- def bezierEval(pts, t):
- sumVec = NdVector((0, 0, 0, 0, 0))
- for i in range(4):
- sumVec += pts[i] * bernsteinPoly(3, i, t)
- return sumVec
-
- #calculate the highest error between bezier and original data
- #returns the distance and the index of the point where max error occurs.
- def maxErrorAmount(data_pts, bez, s, e):
- maxError = 0
- maxErrorPt = s
- if e - s < 3:
- return 0, None
- for pt in data_pts[s:e + 1]:
- bezVal = bezierEval(bez, pt.u)
- normalize_error = pt.co.length
- if normalize_error == 0:
- normalize_error = 1
- tmpError = (pt.co - bezVal).length / normalize_error
- if tmpError >= maxError:
- maxError = tmpError
- maxErrorPt = pt.index
- return maxError, maxErrorPt
-
- #calculated bezier derivative at point t.
- #That is, tangent of point t.
- def getBezDerivative(bez, t):
- n = len(bez) - 1
- sumVec = NdVector((0, 0, 0, 0, 0))
- for i in range(n - 1):
- sumVec += (bez[i + 1] - bez[i]) * bernsteinPoly(n - 1, i, t)
- return sumVec
-
- #use Newton-Raphson to find a better parameterization of datapoints,
- #one that minimizes the distance (or error)
- # between bezier and original data.
- def newtonRaphson(data_pts, s, e, bez):
- for pt in data_pts[s:e + 1]:
- if pt.index == s:
- pt.u = 0
- elif pt.index == e:
- pt.u = 1
- else:
- u = pt.u
- qu = bezierEval(bez, pt.u)
- qud = getBezDerivative(bez, u)
- #we wish to minimize f(u),
- #the squared distance between curve and data
- fu = (qu - pt.co).length ** 2
- fud = (2 * (qu.x - pt.co.x) * (qud.x)) - (2 * (qu.y - pt.co.y) * (qud.y))
- if fud == 0:
- fu = 0
- fud = 1
- pt.u = pt.u - (fu / fud)
-
- #Create data_pts, a list of dataPoint type, each is assigned index i, and an NdVector
- def createDataPts(curveGroup, group_mode):
- make_editable_fcurves(curveGroup if group_mode else (curveGroup,))
-
- if group_mode:
- print([x.data_path for x in curveGroup])
- comp_cos = (0,) * (4 - len(curveGroup)) # We need to add that number of null cos to get our 5D vector.
- kframes = sorted(set(kf.co.x for fc in curveGroup for kf in fc.keyframe_points))
- data_pts = [dataPoint(i, NdVector((fra,) + tuple(fc.evaluate(fra) for fc in curveGroup) + comp_cos))
- for i, fra in enumerate(kframes)]
- else:
- data_pts = [dataPoint(i, NdVector((kf.co.x, kf.co.y, 0, 0, 0)))
- for i, kf in enumerate(curveGroup.keyframe_points)]
- return data_pts
-
- #Recursively fit cubic beziers to the data_pts between s and e
- def fitCubic(data_pts, s, e):
- # if there are less than 3 points, fit a single basic bezier
- if e - s < 3:
- bez = fitSingleCubic2Pts(data_pts, s, e)
- else:
- #if there are more, parameterize the points
- # and fit a single cubic bezier
- chordLength(data_pts, s, e)
- bez = fitSingleCubic(data_pts, s, e)
-
- #calculate max error and point where it occurs
- maxError, maxErrorPt = maxErrorAmount(data_pts, bez, s, e)
- #if error is small enough, reparameterization might be enough
- if maxError < reparaError and maxError > error:
- for i in range(maxIterations):
- newtonRaphson(data_pts, s, e, bez)
- if e - s < 3:
- bez = fitSingleCubic2Pts(data_pts, s, e)
- else:
- bez = fitSingleCubic(data_pts, s, e)
-
- #recalculate max error and point where it occurs
- maxError, maxErrorPt = maxErrorAmount(data_pts, bez, s, e)
-
- #repara wasn't enough, we need 2 beziers for this range.
- #Split the bezier at point of maximum error
- if maxError > error:
- fitCubic(data_pts, s, maxErrorPt)
- fitCubic(data_pts, maxErrorPt, e)
- else:
- #error is small enough, return the beziers.
- beziers.append(bez)
- return
-
- # deletes the sampled points and creates beziers.
- def createNewCurves(curveGroup, beziers, group_mode):
- #remove all existing data points
- if group_mode:
- for fcurve in curveGroup:
- for i in range(len(fcurve.keyframe_points) - 1, 0, -1):
- fcurve.keyframe_points.remove(fcurve.keyframe_points[i], fast=True)
- else:
- fcurve = curveGroup
- for i in range(len(fcurve.keyframe_points) - 1, 0, -1):
- fcurve.keyframe_points.remove(fcurve.keyframe_points[i], fast=True)
-
- #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, options={'FAST'})
- newKey.handle_right = (bez[1].x, bez[1].y)
-
- newKey = fcurve.keyframe_points.insert(frame=bez[3].x, value=bez[3].y, options={'FAST'})
- 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, options={'FAST'})
- newKey.handle_right = (bez[1].x, bez[1].y)
-
- newKey = fcurve.keyframe_points.insert(frame=bez[3].x, value=bez[3].y, options={'FAST'})
- newKey.handle_left = (bez[2].x, bez[2].y)
-
- # We used fast remove/insert, time to update the curves!
- for fcurve in (curveGroup if group_mode else (curveGroup,)):
- fcurve.update()
-
- # 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)
-
- if not data_pts:
- return
-
- s = 0 # start
- e = len(data_pts) - 1 # end
-
- beziers = []
-
- #begin the recursive fitting algorithm.
- fitCubic(data_pts, s, e)
-
- #remove old Fcurves and insert the new ones
- createNewCurves(curveGroup, beziers, group_mode)
-
-
-#Main function of simplification, which called by Operator
-#IN:
-# sel_opt- either "sel" (selected) or "all" for which curves to effect
-# error- maximum error allowed, in fraction (20% = 0.0020, which is the default),
-# i.e. divide by 10000 from percentage wanted.
-# group_mode- boolean, to analyze each curve separately or in groups,
-# where a group is all curves that effect the same property/RNA path
-def fcurves_simplify(context, obj, sel_opt="all", error=0.002, group_mode=True):
- # main vars
- fcurves = obj.animation_data.action.fcurves
-
- if sel_opt == "sel":
- sel_fcurves = [fcurve for fcurve in fcurves if fcurve.select]
- else:
- sel_fcurves = fcurves[:]
-
- #Error threshold for Newton Raphson reparamatizing
- reparaError = error * 32
- maxIterations = 16
-
- if group_mode:
- fcurveDict = {}
- #this loop sorts all the fcurves into groups of 3 or 4,
- #based on their RNA Data path, which corresponds to
- #which property they effect
- for curve in sel_fcurves:
- if curve.data_path in fcurveDict: # if this bone has been added, append the curve to its list
- fcurveDict[curve.data_path].append(curve)
- else:
- fcurveDict[curve.data_path] = [curve] # new bone, add a new dict value with this first curve
- fcurveGroups = fcurveDict.values()
- else:
- fcurveGroups = sel_fcurves
-
- if error > 0.00000:
- #simplify every selected curve.
- totalt = 0
- for i, fcurveGroup in enumerate(fcurveGroups):
- print("Processing curve " + str(i + 1) + "/" + str(len(fcurveGroups)))
- t = time.clock()
- simplifyCurves(fcurveGroup, error, reparaError, maxIterations, group_mode)
- t = time.clock() - t
- print(str(t)[:5] + " seconds to process last curve")
- totalt += t
- print(str(totalt)[:5] + " seconds, total time elapsed")
-
- return
-
-
-def detect_min_max(v):
- """
- Converted from MATLAB script at http://billauer.co.il/peakdet.html
-
- Yields indices of peaks, i.e. local minima/maxima.
-
- % Eli Billauer, 3.4.05 (Explicitly not copyrighted).
- % This function is released to the public domain; Any use is allowed.
- """
-
- min_val, max_val = float('inf'), -float('inf')
-
- check_max = True
-
- for i, val in enumerate(v):
- if val > max_val:
- max_val = val
- if val < min_val:
- min_val = val
-
- if check_max:
- if val < max_val:
- yield i
- min_val = val
- check_max = False
- else:
- if val > min_val:
- yield i
- max_val = val
- check_max = True
-
-
-def denoise(obj, fcurves):
- """
- Implementation of non-linear blur filter.
- Finds spikes in the fcurve, and replaces spikes that are too big with the average of the surrounding keyframes.
- """
- make_editable_fcurves(fcurves)
-
- for fcurve in fcurves:
- org_pts = fcurve.keyframe_points[:]
-
- for idx in detect_min_max(pt.co.y for pt in fcurve.keyframe_points[1:-1]):
- # Find the neighbours
- prev_pt = org_pts[idx - 1].co.y
- next_pt = org_pts[idx + 1].co.y
- this_pt = org_pts[idx]
-
- # Check the distance from the min/max to the average of the surrounding points.
- avg = (prev_pt + next_pt) / 2
- is_peak = abs(this_pt.co.y - avg) > avg * 0.02
-
- if is_peak:
- diff = avg - fcurve.keyframe_points[idx].co.y
- fcurve.keyframe_points[idx].co.y = avg
- fcurve.keyframe_points[idx].handle_left.y += diff
- fcurve.keyframe_points[idx].handle_right.y += diff
-
- # Important to update the curve after modifying it!
- fcurve.update()
-
-
-# Receives armature, and rotations all bones by 90 degrees along the X axis
-# This fixes the common axis issue BVH files have when importing.
-# IN: Armature (bpy.types.Armature)
-def rotate_fix_armature(arm_data):
- global_matrix = Matrix.Rotation(radians(90), 4, "X")
- bpy.ops.object.mode_set(mode='EDIT', toggle=False)
- #disconnect all bones for ease of global rotation
- connectedBones = []
- for bone in arm_data.edit_bones:
- if bone.use_connect:
- connectedBones.append(bone.name)
- bone.use_connect = False
-
- #rotate all the bones around their center
- for bone in arm_data.edit_bones:
- bone.transform(global_matrix)
-
- #reconnect the bones
- for bone in connectedBones:
- arm_data.edit_bones[bone].use_connect = True
- bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
-
-
-#Roughly scales the performer armature to match the enduser armature
-#IN: perfromer_obj, enduser_obj, Blender objects whose .data is an armature.
-def scale_fix_armature(performer_obj, enduser_obj):
- perf_bones = performer_obj.data.bones
- end_bones = enduser_obj.data.bones
-
- def calculateBoundingRadius(bones):
- # Calculate the average position of each bone
- center = sum((bone.head_local for bone in bones), Vector())
- center /= len(bones)
-
- # The radius is defined as the max distance from the center.
- radius = max((bone.head_local - center).length for bone in bones)
- return radius
-
- perf_rad = calculateBoundingRadius(performer_obj.data.bones)
- end_rad = calculateBoundingRadius(enduser_obj.data.bones)
-
- factor = end_rad / perf_rad
- performer_obj.scale = factor * Vector((1, 1, 1))
-
-
-#Guess Mapping
-#Given a performer and enduser armature, attempts to guess the hierarchy mapping
-def guessMapping(performer_obj, enduser_obj):
- perf_bones = performer_obj.data.bones
- end_bones = enduser_obj.data.bones
-
- root = perf_bones[0]
-
- def findBoneSide(bone):
- if "Left" in bone:
- return "Left", bone.replace("Left", "").lower().replace(".", "")
- if "Right" in bone:
- return "Right", bone.replace("Right", "").lower().replace(".", "")
- if "L" in bone:
- return "Left", bone.replace("Left", "").lower().replace(".", "")
- if "R" in bone:
- return "Right", bone.replace("Right", "").lower().replace(".", "")
- return "", bone
-
- def nameMatch(bone_a, bone_b):
- # nameMatch - receives two strings, returns 2 if they are relatively the same, 1 if they are the same but R and L and 0 if no match at all
- side_a, noside_a = findBoneSide(bone_a)
- side_b, noside_b = findBoneSide(bone_b)
- if side_a == side_b:
- if noside_a in noside_b or noside_b in noside_a:
- return 2
- else:
- if noside_a in noside_b or noside_b in noside_a:
- return 1
- return 0
-
- def guessSingleMapping(perf_bone):
- possible_bones = [end_bones[0]]
-
- while possible_bones:
- for end_bone in possible_bones:
- match = nameMatch(perf_bone.name, end_bone.name)
- if match == 2 and not perf_bone.map:
- perf_bone.map = end_bone.name
- #~ elif match == 1 and not perf_bone.map:
- #~ oppo = perf_bones[oppositeBone(perf_bone)].map
- # if oppo:
- # perf_bone = oppo
- newPossibleBones = []
- for end_bone in possible_bones:
- newPossibleBones += list(end_bone.children)
- possible_bones = newPossibleBones
-
- for child in perf_bone.children:
- guessSingleMapping(child)
-
- guessSingleMapping(root)
-
-
-# Creates limit rotation constraints on the enduser armature based on range of motion (max min of fcurves) of the performer.
-# IN: context (bpy.context, etc.), and 2 blender objects which are armatures
-# OUT: creates the limit constraints.
-def limit_dof(context, performer_obj, enduser_obj):
- limitDict = {}
- perf_bones = [bone for bone in performer_obj.pose.bones if bone.bone.map]
- c_frame = context.scene.frame_current
- for bone in perf_bones:
- limitDict[bone.bone.map] = [1000, 1000, 1000, -1000, -1000, -1000]
- for t in range(context.scene.frame_start, context.scene.frame_end):
- context.scene.frame_set(t)
- for bone in perf_bones:
- end_bone = enduser_obj.pose.bones[bone.bone.map]
- bake_matrix = bone.matrix
- rest_matrix = end_bone.bone.matrix_local
-
- if end_bone.parent and end_bone.bone.use_inherit_rotation:
- srcParent = bone.parent
- parent_mat = srcParent.matrix
- parent_rest = end_bone.parent.bone.matrix_local
- parent_rest_inv = parent_rest.inverted()
- parent_mat_inv = parent_mat.inverted()
- bake_matrix = parent_mat_inv * bake_matrix
- rest_matrix = parent_rest_inv * rest_matrix
-
- rest_matrix_inv = rest_matrix.inverted()
- bake_matrix = rest_matrix_inv * bake_matrix
-
- mat = bake_matrix
- euler = mat.to_euler()
- limitDict[bone.bone.map][0] = min(limitDict[bone.bone.map][0], euler.x)
- limitDict[bone.bone.map][1] = min(limitDict[bone.bone.map][1], euler.y)
- limitDict[bone.bone.map][2] = min(limitDict[bone.bone.map][2], euler.z)
- limitDict[bone.bone.map][3] = max(limitDict[bone.bone.map][3], euler.x)
- limitDict[bone.bone.map][4] = max(limitDict[bone.bone.map][4], euler.y)
- limitDict[bone.bone.map][5] = max(limitDict[bone.bone.map][5], euler.z)
- for bone in enduser_obj.pose.bones:
- existingConstraint = [constraint for constraint in bone.constraints if constraint.name == "DOF Limitation"]
- if existingConstraint:
- bone.constraints.remove(existingConstraint[0])
- end_bones = [bone for bone in enduser_obj.pose.bones if bone.name in limitDict.keys()]
- for bone in end_bones:
- #~ if not bone.is_in_ik_chain:
- newCons = bone.constraints.new("LIMIT_ROTATION")
- newCons.name = "DOF Limitation"
- newCons.owner_space = "LOCAL"
- newCons.min_x, newCons.min_y, newCons.min_z, newCons.max_x, newCons.max_y, newCons.max_z = limitDict[bone.name]
- newCons.use_limit_x = True
- newCons.use_limit_y = True
- newCons.use_limit_z = True
- context.scene.frame_set(c_frame)
-
-
-# Removes the constraints that were added by limit_dof on the enduser_obj
-def limit_dof_toggle_off(context, enduser_obj):
- for bone in enduser_obj.pose.bones:
- existingConstraint = [constraint for constraint in bone.constraints if constraint.name == "DOF Limitation"]
- if existingConstraint:
- bone.constraints.remove(existingConstraint[0])
-
-
-# Reparameterizes a blender path via keyframing it's eval_time to match a stride_object's forward velocity.
-# IN: Context, stride object (blender object with location keyframes), path object.
-def path_editing(context, stride_obj, path):
- y_fcurve = [fcurve for fcurve in stride_obj.animation_data.action.fcurves if fcurve.data_path == "location"][1]
- s, e = context.scene.frame_start, context.scene.frame_end # y_fcurve.range()
- s = int(s)
- e = int(e)
- y_s = y_fcurve.evaluate(s)
- y_e = y_fcurve.evaluate(e)
- direction = (y_e - y_s) / abs(y_e - y_s)
- existing_cons = [constraint for constraint in stride_obj.constraints if constraint.type == "FOLLOW_PATH"]
- for cons in existing_cons:
- stride_obj.constraints.remove(cons)
- path_cons = stride_obj.constraints.new("FOLLOW_PATH")
- if direction < 0:
- path_cons.forward_axis = "TRACK_NEGATIVE_Y"
- else:
- path_cons.forward_axis = "FORWARD_Y"
- path_cons.target = path
- path_cons.use_curve_follow = True
- path.data.path_duration = e - s
- try:
- path.data.animation_data.action.fcurves
- except AttributeError:
- path.data.keyframe_insert("eval_time", frame=0)
- eval_time_fcurve = [fcurve for fcurve in path.data.animation_data.action.fcurves if fcurve.data_path == "eval_time"]
- eval_time_fcurve = eval_time_fcurve[0]
- totalLength = 0
- parameterization = {}
- print("evaluating curve")
- for t in range(s, e - 1):
- if s == t:
- chordLength = 0
- else:
- chordLength = (y_fcurve.evaluate(t) - y_fcurve.evaluate(t + 1))
- totalLength += chordLength
- parameterization[t] = totalLength
- for t in range(s + 1, e - 1):
- if totalLength == 0:
- print("no forward motion")
- parameterization[t] /= totalLength
- parameterization[t] *= e - s
- parameterization[e] = e - s
- for t in parameterization.keys():
- eval_time_fcurve.keyframe_points.insert(frame=t, value=parameterization[t])
- y_fcurve.mute = True
- print("finished path editing")
-
-
-#Animation Stitching
-#Stitches two retargeted animations together via NLA settings.
-#IN: enduser_obj, a blender armature that has had two retargets applied.
-def anim_stitch(context, enduser_obj):
- stitch_settings = enduser_obj.data.stitch_settings
- action_1 = stitch_settings.first_action
- action_2 = stitch_settings.second_action
- if stitch_settings.stick_bone != "":
- selected_bone = enduser_obj.pose.bones[stitch_settings.stick_bone]
- else:
- selected_bone = enduser_obj.pose.bones[0]
- scene = context.scene
- TrackNamesA = enduser_obj.data.mocapNLATracks[action_1]
- TrackNamesB = enduser_obj.data.mocapNLATracks[action_2]
- enduser_obj.data.active_mocap = action_1
- anim_data = enduser_obj.animation_data
- # add tracks for action 2
- mocapAction = bpy.data.actions[TrackNamesB.base_track]
- mocapTrack = anim_data.nla_tracks.new()
- mocapTrack.name = TrackNamesB.base_track
- mocapStrip = mocapTrack.strips.new(TrackNamesB.base_track, stitch_settings.blend_frame, mocapAction)
- mocapStrip.extrapolation = "HOLD_FORWARD"
- mocapStrip.blend_in = stitch_settings.blend_amount
- mocapStrip.action_frame_start += stitch_settings.second_offset
- mocapStrip.action_frame_end += stitch_settings.second_offset
- constraintTrack = anim_data.nla_tracks.new()
- constraintTrack.name = TrackNamesB.auto_fix_track
- constraintAction = bpy.data.actions[TrackNamesB.auto_fix_track]
- constraintStrip = constraintTrack.strips.new(TrackNamesB.auto_fix_track, stitch_settings.blend_frame, constraintAction)
- constraintStrip.extrapolation = "HOLD_FORWARD"
- constraintStrip.blend_in = stitch_settings.blend_amount
- userTrack = anim_data.nla_tracks.new()
- userTrack.name = TrackNamesB.manual_fix_track
- userAction = bpy.data.actions[TrackNamesB.manual_fix_track]
- userStrip = userTrack.strips.new(TrackNamesB.manual_fix_track, stitch_settings.blend_frame, userAction)
- userStrip.extrapolation = "HOLD_FORWARD"
- userStrip.blend_in = stitch_settings.blend_amount
- #stride bone
- if enduser_obj.parent:
- if enduser_obj.parent.name == "stride_bone":
- stride_bone = enduser_obj.parent
- stride_anim_data = stride_bone.animation_data
- stride_anim_data.use_nla = True
- stride_anim_data.action = None
- for track in stride_anim_data.nla_tracks:
- stride_anim_data.nla_tracks.remove(track)
- actionATrack = stride_anim_data.nla_tracks.new()
- actionATrack.name = TrackNamesA.stride_action
- actionAStrip = actionATrack.strips.new(TrackNamesA.stride_action, 0, bpy.data.actions[TrackNamesA.stride_action])
- actionAStrip.extrapolation = "NOTHING"
- actionBTrack = stride_anim_data.nla_tracks.new()
- actionBTrack.name = TrackNamesB.stride_action
- actionBStrip = actionBTrack.strips.new(TrackNamesB.stride_action, stitch_settings.blend_frame, bpy.data.actions[TrackNamesB.stride_action])
- actionBStrip.action_frame_start += stitch_settings.second_offset
- actionBStrip.action_frame_end += stitch_settings.second_offset
- actionBStrip.extrapolation = "NOTHING"
- #we need to change the stride_bone's action to add the offset
- aStrideCurves = [fcurve for fcurve in bpy.data.actions[TrackNamesA.stride_action].fcurves if fcurve.data_path == "location"]
- bStrideCurves = [fcurve for fcurve in bpy.data.actions[TrackNamesB.stride_action].fcurves if fcurve.data_path == "location"]
- scene.frame_set(stitch_settings.blend_frame - 1)
- desired_pos = (enduser_obj.matrix_world * selected_bone.matrix.to_translation())
- scene.frame_set(stitch_settings.blend_frame)
- actual_pos = (enduser_obj.matrix_world * selected_bone.matrix.to_translation())
- print(desired_pos, actual_pos)
- offset = Vector(actual_pos) - Vector(desired_pos)
-
- for i, fcurve in enumerate(bStrideCurves):
- 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]
-
- #actionBStrip.blend_in = stitch_settings.blend_amount
-
-
-#Guesses setting for animation stitching via Cross Correlation
-def guess_anim_stitch(context, enduser_obj):
- stitch_settings = enduser_obj.data.stitch_settings
- action_1 = stitch_settings.first_action
- action_2 = stitch_settings.second_action
- TrackNamesA = enduser_obj.data.mocapNLATracks[action_1]
- TrackNamesB = enduser_obj.data.mocapNLATracks[action_2]
- mocapA = bpy.data.actions[TrackNamesA.base_track]
- mocapB = bpy.data.actions[TrackNamesB.base_track]
- curvesA = mocapA.fcurves
- curvesB = mocapB.fcurves
- flm, s, data = crossCorrelationMatch(curvesA, curvesB, 10)
- print("Guessed the following for start and offset: ", s, flm)
- enduser_obj.data.stitch_settings.blend_frame = flm
- enduser_obj.data.stitch_settings.second_offset = s
diff --git a/mocap/retarget.py b/mocap/retarget.py
deleted file mode 100644
index d5a17097..00000000
--- a/mocap/retarget.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-import bpy
-from mathutils import Vector, Matrix
-from math import radians
-from bpy_extras.anim_utils import bake_action
-
-
-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 hierarchy of the end user,
-# does not have rotation inheritance
-# and bone roll is identical to the performer
-# its purpose is to copy over the rotations
-# easily while concentrating on the hierarchy changes
-
-
-def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step):
- #creates and keyframes an empty with its location
- #the original position of the tail bone
- #useful for storing the important data in the original motion
- #i.e. using this empty to IK the chain to that pos / DEBUG
-
- #Simple 1to1 retarget of a bone
- def singleBoneRetarget(inter_bone, perf_bone):
- perf_world_rotation = perf_bone.matrix
- inter_world_base_rotation = inter_bone.bone.matrix_local
- inter_world_base_inv = inter_world_base_rotation.inverted()
- bake_matrix = (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3())
- return bake_matrix.to_4x4()
-
- #uses 1to1 and interpolation/averaging to match many to 1 retarget
- def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
- retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
- lerp_matrix = Matrix()
- for i in range(len(retarget_matrices) - 1):
- first_mat = retarget_matrices[i]
- next_mat = retarget_matrices[i + 1]
- lerp_matrix = first_mat.lerp(next_mat, 0.5)
- return lerp_matrix
-
- #determines the type of hierarchy change needed and calls the
- #right function
- def retargetPerfToInter(inter_bone):
- if inter_bone.bone.reverseMap:
- perf_bone_name = inter_bone.bone.reverseMap
- # 1 to many not supported yet
- # then its either a many to 1 or 1 to 1
- if len(perf_bone_name) > 1:
- performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
- #we need to map several performance bone to a single
- inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
- else:
- perf_bone = performer_bones[perf_bone_name[0].name]
- inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
- #Some bones have incorrect roll on the source armature, and need to be marked for fixing
- if inter_bone.bone.twistFix:
- inter_bone.matrix_basis *= Matrix.Rotation(radians(180), 4, "Y")
- rot_mode = inter_bone.rotation_mode
- if rot_mode == "QUATERNION":
- inter_bone.keyframe_insert("rotation_quaternion")
- elif rot_mode == "AXIS_ANGLE":
- inter_bone.keyframe_insert("rotation_axis_angle")
- else:
- inter_bone.keyframe_insert("rotation_euler")
-
- #creates the intermediate armature object
- inter_obj = enduser_obj.copy()
- inter_obj.data = inter_obj.data.copy() # duplicate data
- bpy.context.collection.objects.link(inter_obj)
- inter_obj.name = "intermediate"
- bpy.context.view_layer.objects.active = inter_obj
- bpy.ops.object.mode_set(mode='EDIT')
- #add some temporary connecting bones in case end user bones are not connected to their parents
- rollDict = {}
- print("creating temp bones")
- for bone in inter_obj.data.edit_bones:
- if not bone.use_connect and bone.parent:
- if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
- newBone = inter_obj.data.edit_bones.new("Temp")
- newBone.head = bone.parent.tail
- newBone.tail = bone.head
- newBone.parent = bone.parent
- bone.parent = newBone
- bone.use_connect = True
- newBone.use_connect = True
- rollDict[bone.name] = bone.roll
- bone.roll = 0
- #resets roll
- print("retargeting to intermediate")
- bpy.ops.object.mode_set(mode="OBJECT")
- inter_obj.data.name = "inter_arm"
- inter_arm = inter_obj.data
- performer_bones = performer_obj.pose.bones
- inter_bones = inter_obj.pose.bones
- #clears inheritance
- for inter_bone in inter_bones:
- if inter_bone.bone.reverseMap:
- inter_bone.bone.use_inherit_rotation = False
- else:
- inter_bone.bone.use_inherit_rotation = True
-
- for t in range(s_frame, e_frame, step):
- if (t - s_frame) % 10 == 0:
- print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
- scene.frame_set(t)
- for bone in inter_bones:
- retargetPerfToInter(bone)
-
- return inter_obj
-
-# this procedure copies the rotations over from the intermediate
-# armature to the end user one.
-# As the hierarchies are 1 to 1, this is a simple matter of
-# copying the rotation, while keeping in mind bone roll, parenting, etc.
-# TODO: Control Bones: If a certain bone is constrained in a way
-# that its rotation is determined by another (a control bone)
-# We should determine the right pos of the control bone.
-# Scale: ? Should work but needs testing.
-
-
-def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step):
- inter_bones = inter_obj.pose.bones
- end_bones = enduser_obj.pose.bones
-
- #Basic "visual baking" function, for transferring rotations from intermediate to end user
- def bakeTransform(end_bone):
- src_bone = inter_bones[end_bone.name]
- trg_bone = end_bone
- bake_matrix = src_bone.matrix
- rest_matrix = trg_bone.bone.matrix_local
-
- if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
- srcParent = src_bone.parent
- if "Temp" in srcParent.name:
- srcParent = srcParent.parent
- parent_mat = srcParent.matrix
- parent_rest = trg_bone.parent.bone.matrix_local
- parent_rest_inv = parent_rest.inverted()
- parent_mat_inv = parent_mat.inverted()
- bake_matrix = parent_mat_inv * bake_matrix
- rest_matrix = parent_rest_inv * rest_matrix
-
- rest_matrix_inv = rest_matrix.inverted()
- bake_matrix = rest_matrix_inv * bake_matrix
- end_bone.matrix_basis = bake_matrix
- rot_mode = end_bone.rotation_mode
- if rot_mode == "QUATERNION":
- end_bone.keyframe_insert("rotation_quaternion")
- elif rot_mode == "AXIS_ANGLE":
- end_bone.keyframe_insert("rotation_axis_angle")
- else:
- end_bone.keyframe_insert("rotation_euler")
- if not end_bone.bone.use_connect:
- end_bone.keyframe_insert("location")
-
- for bone in end_bone.children:
- bakeTransform(bone)
-
- for t in range(s_frame, e_frame, step):
- if (t - s_frame) % 10 == 0:
- print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
- scene.frame_set(t)
- end_bone = end_bones[root]
- end_bone.location = Vector((0, 0, 0))
- end_bone.keyframe_insert("location")
- bakeTransform(end_bone)
-
-#receives the performer feet bones as a variable
-# by "feet" I mean those bones that have plants
-# (they don't move, despite root moving) somewhere in the animation.
-
-
-def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
-
- perf_bones = performer_obj.pose.bones
- end_bones = enduser_obj.pose.bones
-
- perfRoot = perf_bones[0].name
- endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
- locDictKeys = perfFeet + endFeet + [perfRoot]
-
- def tailLoc(bone):
- return bone.center + (bone.vector / 2)
-
- #Step 1 - we create a dict that contains these keys:
- #(Performer) Hips, Feet
- #(End user) Feet
- # where the values are their world position on each frame in range (s,e)
-
- locDict = {}
- for key in locDictKeys:
- locDict[key] = []
-
- for t in range(scene.frame_start, scene.frame_end):
- scene.frame_set(t)
- for bone in perfFeet:
- locDict[bone].append(tailLoc(perf_bones[bone]))
- locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
- for bone in endFeet:
- locDict[bone].append(tailLoc(end_bones[bone]))
-
- # now we take our locDict and analyze it.
- # we need to derive all chains
-
- def locDeriv(key, t):
- graph = locDict[key]
- return graph[t + 1] - graph[t]
-
- # now find the plant frames, where perfFeet don't move much
-
- linearAvg = []
- for key in perfFeet:
- for i in range(len(locDict[key]) - 1):
- v = locDeriv(key, i)
- if (v.length < 0.1):
- hipV = locDeriv(perfRoot, i)
- endV = locDeriv(perf_bones[key].bone.map, i)
- #this is a plant frame.
- #lets see what the original hip delta is, and the corresponding
- #end bone's delta
- if endV.length != 0:
- linearAvg.append(hipV.length / endV.length)
-
- action_name = performer_obj.animation_data.action.name
- #is there a stride_bone?
- if "stride_bone" in bpy.data.objects:
- stride_action = bpy.data.actions.new("Stride Bone " + action_name)
- stride_action.use_fake_user = True
- stride_bone = enduser_obj.parent
- stride_bone.animation_data.action = stride_action
- else:
- bpy.ops.object.mode_set(mode='OBJECT')
- bpy.ops.object.add()
- stride_bone = bpy.context.active_object
- stride_bone.name = "stride_bone"
- stride_bone.location = enduser_obj_mat.to_translation()
- if linearAvg:
- #determine the average change in scale needed
- avg = sum(linearAvg) / len(linearAvg)
- else:
- avg = 1
- scene.frame_set(s_frame)
- initialPos = (tailLoc(perf_bones[perfRoot]) / avg)
- for t in range(s_frame, e_frame):
- scene.frame_set(t)
- #calculate the new position, by dividing by the found ratio between performer and enduser
- newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
- stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
- stride_bone.keyframe_insert("location")
- 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):
- end_bones = enduser_obj.pose.bones
- for pose_bone in end_bones:
- ik_constraint = hasIKConstraint(pose_bone)
- if ik_constraint:
- target_is_bone = False
- # set constraint target to corresponding empty if targetless,
- # if not, keyframe current target to corresponding empty
- perf_bone = pose_bone.bone.reverseMap[-1].name
- bpy.ops.object.mode_set(mode='EDIT')
- orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
- bpy.ops.object.mode_set(mode='OBJECT')
- if not ik_constraint.target:
- ik_constraint.target = enduser_obj
- ik_constraint.subtarget = pose_bone.name + "IK"
- target = orgLocTrg
-
- # There is a target now
- if ik_constraint.subtarget:
- target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
- target.bone.use_local_location = False
- target_is_bone = True
- else:
- target = ik_constraint.target
-
- # bake the correct locations for the ik target bones
- for t in range(s_frame, e_frame, step):
- scene.frame_set(t)
- if target_is_bone:
- final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
- else:
- final_loc = pose_bone.tail
- target.location = final_loc
- target.keyframe_insert("location")
- ik_constraint.mute = False
- scene.frame_set(s_frame)
- bpy.ops.object.mode_set(mode='OBJECT')
-
-
-def turnOffIK(enduser_obj):
- end_bones = enduser_obj.pose.bones
- for pose_bone in end_bones:
- ik_constraint = hasIKConstraint(pose_bone)
- if ik_constraint:
- ik_constraint.mute = True
-
-
-#copy the object matrixes and clear them (to be reinserted later)
-def cleanAndStoreObjMat(performer_obj, enduser_obj):
- perf_obj_mat = performer_obj.matrix_world.copy()
- enduser_obj_mat = enduser_obj.matrix_world.copy()
- zero_mat = Matrix()
- performer_obj.matrix_world = zero_mat
- enduser_obj.matrix_world = zero_mat
- return perf_obj_mat, enduser_obj_mat
-
-
-#restore the object matrixes after parenting the auto generated IK empties
-def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
- pose_bones = enduser_obj.pose.bones
- for pose_bone in pose_bones:
- if pose_bone.name + "Org" in bpy.data.objects:
- empty = bpy.data.objects[pose_bone.name + "Org"]
- empty.parent = stride_bone
- performer_obj.matrix_world = perf_obj_mat
- enduser_obj.parent = stride_bone
- scene.frame_set(s_frame)
- enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() * Matrix.Translation(stride_bone.matrix_world.to_translation())
- enduser_obj.matrix_world = enduser_obj_mat
-
-
-#create (or return if exists) the related IK empty to the bone
-def originalLocationTarget(end_bone, enduser_obj):
- ik_bone = hasIKConstraint(end_bone).subtarget
- if not ik_bone:
- print("Adding IK bones for: " + end_bone.name)
- newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
- newBone.head = end_bone.tail
- newBone.tail = end_bone.tail + Vector((0, 0.1, 0))
- else:
- newBone = enduser_obj.pose.bones[ik_bone]
- return newBone
-
-
-#create the specified NLA setup for base animation, constraints and tweak layer.
-def NLASystemInitialize(enduser_arm, context):
- enduser_obj = context.active_object
- NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
- name = NLATracks.name
- anim_data = enduser_obj.animation_data
- s_frame = 0
- if ("Base " + name) in bpy.data.actions:
- mocapAction = bpy.data.actions[("Base " + name)]
- else:
- print("That retargeted anim has no base action")
- anim_data.use_nla = True
- for track in anim_data.nla_tracks:
- anim_data.nla_tracks.remove(track)
- mocapTrack = anim_data.nla_tracks.new()
- mocapTrack.name = "Base " + name
- NLATracks.base_track = mocapTrack.name
- mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
- constraintTrack = anim_data.nla_tracks.new()
- constraintTrack.name = "Auto fixes " + name
- NLATracks.auto_fix_track = constraintTrack.name
- if ("Auto fixes " + name) in bpy.data.actions:
- constraintAction = bpy.data.actions[("Auto fixes " + name)]
- else:
- constraintAction = bpy.data.actions.new("Auto fixes " + name)
- constraintAction.use_fake_user = True
- constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
- constraintStrip.extrapolation = "NOTHING"
- userTrack = anim_data.nla_tracks.new()
- userTrack.name = "Manual fixes " + name
- NLATracks.manual_fix_track = userTrack.name
- if ("Manual fixes " + name) in bpy.data.actions:
- userAction = bpy.data.actions[("Manual fixes " + name)]
- else:
- userAction = bpy.data.actions.new("Manual fixes " + name)
- userAction.use_fake_user = True
- userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
- userStrip.extrapolation = "HOLD"
- userStrip.blend_type = "ADD"
- anim_data.nla_tracks.active = constraintTrack
- anim_data.action_extrapolation = "NOTHING"
- #set the stride_bone's action
- if "stride_bone" in bpy.data.objects:
- stride_bone = bpy.data.objects["stride_bone"]
- if NLATracks.stride_action:
- stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
- else:
- NLATracks.stride_action = stride_bone.animation_data.action.name
- stride_bone.animation_data.action.use_fake_user = True
- anim_data.action = None
-
-
-def preAdvancedRetargeting(performer_obj, enduser_obj):
- createDictionary(performer_obj.data, enduser_obj.data)
- bones = enduser_obj.pose.bones
- map_bones = [bone for bone in bones if bone.bone.reverseMap]
- perf_root = performer_obj.pose.bones[0].name
- for bone in map_bones:
- perf_bone = bone.bone.reverseMap[0].name
-
- cons = bone.constraints.new('COPY_ROTATION')
- cons.name = "retargetTemp"
- locks = bone.lock_rotation
- cons.use_x = not locks[0]
- cons.use_y = not locks[1]
- cons.use_z = not locks[2]
- cons.target = performer_obj
- cons.subtarget = perf_bone
- cons.target_space = 'WORLD'
- cons.owner_space = 'WORLD'
-
- if (not bone.bone.use_connect) and (perf_bone != perf_root):
- cons = bone.constraints.new('COPY_LOCATION')
- cons.name = "retargetTemp"
- cons.target = performer_obj
- cons.subtarget = perf_bone
- cons.use_x = True
- cons.use_y = True
- cons.use_z = True
- cons.target_space = 'LOCAL'
- cons.owner_space = 'LOCAL'
-
-
-def prepareForBake(enduser_obj):
- bones = enduser_obj.pose.bones
- for bone in bones:
- bone.bone.select = False
- map_bones = [bone for bone in bones if bone.bone.reverseMap]
- for bone in map_bones:
- for cons in bone.constraints:
- if "retargetTemp" in cons.name:
- bone.bone.select = True
-
-
-def cleanTempConstraints(enduser_obj):
- bones = enduser_obj.pose.bones
- map_bones = [bone for bone in bones if bone.bone.reverseMap]
- for bone in map_bones:
- for cons in bone.constraints:
- if "retargetTemp" in cons.name:
- bone.constraints.remove(cons)
-
-
-#Main function that runs the retargeting sequence.
-#If advanced == True, we assume constraint's were already created
-def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
- perf_arm = performer_obj.data
- end_arm = enduser_obj.data
- advanced = end_arm.advancedRetarget
- step = end_arm.frameStep
- enduser_obj.animation_data_create()
-
- 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)")
- bake_action(
- bpy.context.object,
- s_frame, e_frame,
- action=enduser_obj.animation_data.action,
- only_selected=True,
- do_pose=True,
- do_object=False,
- frame_step=step,
- )
- name = performer_obj.animation_data.action.name[:10]
- #We trim the name down to 10 chars because of Action Name length maximum
- 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:
- print("hry")
- bpy.ops.object.select_all(action='DESELECT')
- bpy.context.view_layer.objects.active = enduser_obj
- bpy.ops.object.select_pattern(pattern=enduser_obj.name, extend=False)
- IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step)
- bpy.ops.object.select_pattern(pattern=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_pattern(pattern=inter_obj.name, extend=False)
- bpy.ops.object.delete()
- else:
- cleanTempConstraints(enduser_obj)
- bpy.ops.object.select_pattern(pattern=enduser_obj.name, extend=False)
-
- if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
- NLATracks = end_arm.mocapNLATracks.add()
- NLATracks.name = name
- else:
- NLATracks = end_arm.mocapNLATracks[name]
- end_arm.active_mocap = name
- print("retargeting done!")
-
-
-def isRigAdvanced(enduser_obj):
- bones = enduser_obj.pose.bones
- for bone in bones:
- for constraint in bone.constraints:
- if constraint.type != "IK":
- return True
- if enduser_obj.data.animation_data:
- if enduser_obj.data.animation_data.drivers:
- return True