1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 "name": "Motion Capture Tools",
23 "author": "Benjy Cook",
24 "blender": (2, 73, 0),
26 "location": "Active Armature > Object Properties > Mocap tools",
27 "description": "Various tools for working with motion capture animation",
29 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
30 "Scripts/Animation/Motion_Capture_Tools",
31 "support": 'OFFICIAL',
32 "category": "Animation",
37 if "mocap_constraints" in locals():
38 importlib
.reload(mocap_constraints
)
39 if "retarget" in locals():
40 importlib
.reload(retarget
)
41 if "mocap_tools" in locals():
42 importlib
.reload(mocap_tools
)
45 from bpy
.props
import (
62 # MocapConstraint class
63 # Defines MocapConstraint datatype, used to add and configute mocap constraints
64 # Attached to Armature data
66 def hasIKConstraint(pose_bone
):
67 #utility function / predicate, returns True if given bone has IK constraint
68 ik
= [constraint
for constraint
in pose_bone
.constraints
if constraint
.type == "IK"]
75 class MocapConstraint(bpy
.types
.PropertyGroup
):
76 name
= StringProperty(name
="Name",
78 description
="Name of Mocap Fix",
79 update
=mocap_constraints
.setConstraint
)
80 constrained_bone
= StringProperty(name
="Bone",
82 description
="Constrained Bone",
83 update
=mocap_constraints
.updateConstraintBoneType
)
84 constrained_boneB
= StringProperty(name
="Bone (2)",
86 description
="Other Constrained Bone (optional, depends on type)",
87 update
=mocap_constraints
.setConstraint
)
88 s_frame
= IntProperty(name
="S",
90 description
="Start frame of Fix",
91 update
=mocap_constraints
.setConstraint
)
92 e_frame
= IntProperty(name
="E",
94 description
="End frame of Fix",
95 update
=mocap_constraints
.setConstraint
)
96 smooth_in
= IntProperty(name
="In",
98 description
="Number of frames to smooth in",
99 update
=mocap_constraints
.setConstraint
,
101 smooth_out
= IntProperty(name
="Out",
103 description
="Number of frames to smooth out",
104 update
=mocap_constraints
.setConstraint
,
106 targetMesh
= StringProperty(name
="Mesh",
108 description
="Target of Fix - Mesh (optional, depends on type)",
109 update
=mocap_constraints
.setConstraint
)
110 active
= BoolProperty(name
="Active",
112 description
="Fix is active",
113 update
=mocap_constraints
.setConstraint
)
114 show_expanded
= BoolProperty(name
="Show Expanded",
116 description
="Fix is fully shown")
117 targetPoint
= FloatVectorProperty(name
="Point", size
=3,
118 subtype
="XYZ", default
=(0.0, 0.0, 0.0),
119 description
="Target of Fix - Point",
120 update
=mocap_constraints
.setConstraint
)
121 targetDist
= FloatProperty(name
="Offset",
123 description
="Distance and Floor Fixes - Desired offset",
124 update
=mocap_constraints
.setConstraint
)
125 targetSpace
= EnumProperty(
126 items
=[("WORLD", "World Space", "Evaluate target in global space"),
127 ("LOCAL", "Object space", "Evaluate target in object space"),
128 ("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
130 description
="In which space should Point type target be evaluated",
131 update
=mocap_constraints
.setConstraint
)
132 type = EnumProperty(name
="Type of constraint",
133 items
=[("point", "Maintain Position", "Bone is at a specific point"),
134 ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
135 ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
136 ("distance", "Maintain distance", "Target bones maintained specified distance")],
137 description
="Type of Fix",
138 update
=mocap_constraints
.updateConstraintBoneType
)
139 real_constraint
= StringProperty()
140 real_constraint_bone
= StringProperty()
143 # Animation Stitch Settings, used for animation stitching of 2 retargeted animations.
144 class AnimationStitchSettings(bpy
.types
.PropertyGroup
):
145 first_action
= StringProperty(name
="Action 1",
146 description
="First action in stitch")
147 second_action
= StringProperty(name
="Action 2",
148 description
="Second action in stitch")
149 blend_frame
= IntProperty(name
="Stitch frame",
150 description
="Frame to locate stitch on")
151 blend_amount
= IntProperty(name
="Blend amount",
152 description
="Size of blending transition, on both sides of the stitch",
154 second_offset
= IntProperty(name
="Second offset",
155 description
="Frame offset for 2nd animation, where it should start",
157 stick_bone
= StringProperty(name
="Stick Bone",
158 description
="Bone to freeze during transition",
162 # MocapNLA Tracks. Stores which tracks/actions are associated with each retargeted animation.
163 class MocapNLATracks(bpy
.types
.PropertyGroup
):
164 name
= StringProperty()
165 base_track
= StringProperty()
166 auto_fix_track
= StringProperty()
167 manual_fix_track
= StringProperty()
168 stride_action
= StringProperty()
171 #Update function for Advanced Retarget boolean variable.
172 def advancedRetargetToggle(self
, context
):
173 enduser_obj
= context
.active_object
174 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= enduser_obj
]
175 if enduser_obj
is None or len(performer_obj
) != 1:
176 print("Need active and selected armatures")
179 performer_obj
= performer_obj
[0]
180 if self
.advancedRetarget
:
181 retarget
.preAdvancedRetargeting(performer_obj
, enduser_obj
)
183 retarget
.cleanTempConstraints(enduser_obj
)
186 def toggleIKBone(self
, context
):
187 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
189 if not self
.is_in_ik_chain
:
190 print(self
.name
+ " IK toggled ON!")
191 ik
= self
.constraints
.new('IK')
192 #ik the whole chain up to the root, excluding
194 for parent_bone
in self
.parent_recursive
:
196 if hasIKConstraint(parent_bone
):
198 #~ deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
199 #~ if len(deformer_children) > 1:
201 ik
.chain_count
= chainLen
202 for bone
in self
.parent_recursive
:
203 if bone
.is_in_ik_chain
:
204 bone
.IKRetarget
= True
206 print(self
.name
+ " IK toggled OFF!")
209 if hasIKConstraint(self
):
210 cnstrn_bones
= [self
]
211 elif self
.is_in_ik_chain
:
212 cnstrn_bones
= [child
for child
in self
.children_recursive
if hasIKConstraint(child
)]
213 for cnstrn_bone
in cnstrn_bones
:
214 newChainLength
.append(cnstrn_bone
.parent_recursive
.index(self
) + 1)
216 # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
217 for i
, cnstrn_bone
in enumerate(cnstrn_bones
):
218 print(cnstrn_bone
.name
)
220 ik
= hasIKConstraint(cnstrn_bone
)
221 ik
.chain_count
= newChainLength
[i
]
223 ik
= hasIKConstraint(cnstrn_bone
)
224 cnstrn_bone
.constraints
.remove(ik
)
225 cnstrn_bone
.IKRetarget
= False
226 for bone
in cnstrn_bone
.parent_recursive
:
227 if not bone
.is_in_ik_chain
:
228 bone
.IKRetarget
= False
231 #MocapMap class for storing mapping on enduser performer,
232 # where a bone may be linked to more than one on the performer
233 class MocapMapping(bpy
.types
.PropertyGroup
):
234 name
= StringProperty()
236 # Disabling for now [#28933] - campbell
238 def updateIKRetarget():
239 # ensures that Blender constraints and IK properties are in sync
240 # currently runs when module is loaded, should run when scene is loaded
241 # or user adds a constraint to armature. Will be corrected in the future,
242 # once python callbacks are implemented
243 for obj in bpy.data.objects:
245 bones = obj.pose.bones
246 for pose_bone in bones:
247 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
248 pose_bone.IKRetarget = True
250 pose_bone.IKRetarget = False
256 def hasIKConstraint(pose_bone
):
257 #utility function / predicate, returns True if given bone has IK constraint
258 ik
= [constraint
for constraint
in pose_bone
.constraints
if constraint
.type == "IK"]
265 class MocapPanel(bpy
.types
.Panel
):
266 # Motion capture retargeting panel
267 bl_label
= "Mocap tools"
268 bl_space_type
= "PROPERTIES"
269 bl_region_type
= "WINDOW"
270 bl_context
= "object"
273 def poll(cls
, context
):
275 return obj
.type == 'ARMATURE' and context
.active_object
is not None and context
.mode
in {'EDIT_ARMATURE',
279 def draw(self
, context
):
282 layout
.label("Preprocessing:")
284 row
= layout
.row(align
=True)
285 row
.operator("mocap.denoise", text
='Clean noise')
286 row
.operator("mocap.rotate_fix", text
='Fix BVH Axis Orientation')
287 row
.operator("mocap.scale_fix", text
='Auto scale Performer')
289 row
= layout
.row(align
=True)
290 row
.operator("mocap.looper", text
='Loop animation')
291 row
.operator("mocap.limitdof", text
='Constrain Rig')
292 row
.operator("mocap.removelimitdof", text
='Unconstrain Rig')
294 layout
.label("Retargeting:")
295 enduser_obj
= bpy
.context
.active_object
296 performer_obj
= [obj
for obj
in bpy
.context
.selected_objects
if obj
!= enduser_obj
]
297 if enduser_obj
is None or len(performer_obj
) != 1:
298 layout
.label("Select performer rig and target rig (as active)")
300 layout
.operator("mocap.guessmapping", text
="Guess Hierarchy Mapping")
301 labelRow
= layout
.row(align
=True)
302 labelRow
.label("Performer Rig")
303 labelRow
.label("End user Rig")
304 performer_obj
= performer_obj
[0]
305 if performer_obj
.data
and enduser_obj
.data
:
306 if performer_obj
.data
.name
in bpy
.data
.armatures
and enduser_obj
.data
.name
in bpy
.data
.armatures
:
307 perf
= performer_obj
.data
308 enduser_arm
= enduser_obj
.data
309 perf_pose_bones
= enduser_obj
.pose
.bones
310 MappingRow
= layout
.row(align
=True)
311 footCol
= MappingRow
.column(align
=True)
312 nameCol
= MappingRow
.column(align
=True)
314 mapCol
= MappingRow
.column(align
=True)
316 selectCol
= MappingRow
.column(align
=True)
317 twistCol
= MappingRow
.column(align
=True)
318 IKCol
= MappingRow
.column(align
=True)
320 IKLabel
= MappingRow
.column(align
=True)
321 IKLabel
.scale_x
= 0.2
322 for bone
in perf
.bones
:
323 footCol
.prop(data
=bone
, property='foot', text
='', icon
='POSE_DATA')
324 nameCol
.label(bone
.name
)
325 mapCol
.prop_search(bone
, "map", enduser_arm
, "bones", text
='')
326 selectCol
.operator("mocap.selectmap", text
='', icon
='CURSOR').perf_bone
= bone
.name
329 pose_bone
= perf_pose_bones
[bone
.map]
330 if pose_bone
.is_in_ik_chain
:
331 label_mod
= "ik chain"
332 if hasIKConstraint(pose_bone
):
334 end_bone
= enduser_obj
.data
.bones
[bone
.map]
335 twistCol
.prop(data
=end_bone
, property='twistFix', text
='', icon
='RNA')
336 IKCol
.prop(pose_bone
, 'IKRetarget')
337 IKLabel
.label(label_mod
)
342 mapRow
= layout
.row()
343 mapRow
.operator("mocap.savemapping", text
='Save mapping')
344 mapRow
.operator("mocap.loadmapping", text
='Load mapping')
345 extraSettings
= self
.layout
.box()
346 if performer_obj
.animation_data
and performer_obj
.animation_data
.action
:
347 extraSettings
.prop(data
=performer_obj
.animation_data
.action
, property='name', text
='Action Name')
348 extraSettings
.prop(enduser_arm
, "frameStep")
349 extraSettings
.prop(enduser_arm
, "advancedRetarget", text
='Advanced Retarget')
350 layout
.operator("mocap.retarget", text
='RETARGET!')
353 class MocapConstraintsPanel(bpy
.types
.Panel
):
354 #Motion capture constraints panel
355 bl_label
= "Mocap Fixes"
356 bl_space_type
= "PROPERTIES"
357 bl_region_type
= "WINDOW"
358 bl_context
= "object"
359 bl_options
= {'DEFAULT_CLOSED'}
362 def poll(cls
, context
):
364 return obj
.type == 'ARMATURE' and context
.active_object
is not None and context
.mode
in {'EDIT_ARMATURE',
368 def draw(self
, context
):
370 if context
.active_object
:
371 if context
.active_object
.data
:
372 if context
.active_object
.data
.name
in bpy
.data
.armatures
:
373 enduser_obj
= context
.active_object
374 enduser_arm
= enduser_obj
.data
375 layout
.operator_menu_enum("mocap.addmocapfix", "type")
376 layout
.operator("mocap.updateconstraints", text
='Update Fixes')
377 bakeRow
= layout
.row()
378 bakeRow
.operator("mocap.bakeconstraints", text
='Bake Fixes')
379 bakeRow
.operator("mocap.unbakeconstraints", text
='Unbake Fixes')
381 for i
, m_constraint
in enumerate(enduser_arm
.mocap_constraints
):
383 headerRow
= box
.row()
384 headerRow
.prop(m_constraint
, 'show_expanded', text
='', icon
='TRIA_DOWN' if m_constraint
.show_expanded
else 'TRIA_RIGHT', emboss
=False)
385 headerRow
.prop(m_constraint
, 'type', text
='')
386 headerRow
.prop(m_constraint
, 'name', text
='')
387 headerRow
.prop(m_constraint
, 'active', icon
='MUTE_IPO_ON' if not m_constraint
.active
else'MUTE_IPO_OFF', text
='', emboss
=False)
388 headerRow
.operator("mocap.removeconstraint", text
="", icon
='X', emboss
=False).constraint
= i
389 if m_constraint
.show_expanded
:
391 box
.prop_search(m_constraint
, 'constrained_bone', enduser_obj
.pose
, "bones", icon
='BONE_DATA', text
="")
392 if m_constraint
.type == "distance" or m_constraint
.type == "point":
393 box
.prop_search(m_constraint
, 'constrained_boneB', enduser_obj
.pose
, "bones", icon
='CONSTRAINT_BONE')
395 frameRow
.label("Frame Range:")
396 frameRow
.prop(m_constraint
, 's_frame')
397 frameRow
.prop(m_constraint
, 'e_frame')
398 smoothRow
= box
.row()
399 smoothRow
.label("Smoothing:")
400 smoothRow
.prop(m_constraint
, 'smooth_in')
401 smoothRow
.prop(m_constraint
, 'smooth_out')
402 targetRow
= box
.row()
403 targetLabelCol
= targetRow
.column()
404 targetLabelCol
.label("Target settings:")
405 targetPropCol
= targetRow
.column()
406 if m_constraint
.type == "floor":
407 targetPropCol
.prop_search(m_constraint
, 'targetMesh', bpy
.data
, "objects")
408 if m_constraint
.type == "point" or m_constraint
.type == "freeze":
409 box
.prop(m_constraint
, 'targetSpace')
410 if m_constraint
.type == "point":
411 targetPropCol
.prop(m_constraint
, 'targetPoint')
412 if m_constraint
.type == "distance" or m_constraint
.type == "floor":
413 targetPropCol
.prop(m_constraint
, 'targetDist')
417 class ExtraToolsPanel(bpy
.types
.Panel
):
418 # Motion capture retargeting panel
419 bl_label
= "Extra Mocap Tools"
420 bl_space_type
= "PROPERTIES"
421 bl_region_type
= "WINDOW"
422 bl_context
= "object"
423 bl_options
= {'DEFAULT_CLOSED'}
426 def poll(cls
, context
):
428 return obj
.type == 'ARMATURE' and context
.active_object
is not None and context
.mode
in {'EDIT_ARMATURE',
431 def draw(self
, context
):
433 layout
.operator("mocap.samples", text
='Samples to Beziers')
434 layout
.operator('mocap.pathediting', text
="Follow Path")
435 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
437 enduser_arm
= context
.active_object
.data
438 selectBox
= layout
.box()
439 selectRetargets
= selectBox
.row()
440 selectRetargets
.label("Retargeted Animations:")
441 selectRetargets
.prop_search(enduser_arm
, "active_mocap", enduser_arm
, "mocapNLATracks")
442 stitchBox
= layout
.box()
443 stitchBox
.label("Animation Stitching")
444 settings
= enduser_arm
.stitch_settings
445 stitchBox
.prop_search(settings
, "first_action", enduser_arm
, "mocapNLATracks")
446 stitchBox
.prop_search(settings
, "second_action", enduser_arm
, "mocapNLATracks")
447 stitchSettings
= stitchBox
.row()
448 stitchSettings
.prop(settings
, "blend_frame")
449 stitchSettings
.prop(settings
, "blend_amount")
450 stitchSettings
.prop(settings
, "second_offset")
451 stitchBox
.prop_search(settings
, "stick_bone", context
.active_object
.pose
, "bones")
452 stitchBox
.operator('mocap.animstitchguess', text
="Guess Settings")
453 stitchBox
.operator('mocap.animstitch', text
="Stitch Animations")
456 class OBJECT_OT_RetargetButton(bpy
.types
.Operator
):
457 #Retargeting operator. Assumes selected and active armatures, where the performer (the selected one)
458 # has an action for retargeting
459 """Retarget animation from selected armature to active armature"""
460 bl_idname
= "mocap.retarget"
461 bl_label
= "Retarget"
462 bl_options
= {'REGISTER', 'UNDO'}
464 def execute(self
, context
):
465 scene
= context
.scene
466 s_frame
= scene
.frame_start
467 e_frame
= scene
.frame_end
468 enduser_obj
= context
.active_object
469 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= enduser_obj
]
470 if enduser_obj
is None or len(performer_obj
) != 1:
471 print("Need active and selected armatures")
473 performer_obj
= performer_obj
[0]
474 s_frame
, e_frame
= performer_obj
.animation_data
.action
.frame_range
475 s_frame
= int(s_frame
)
476 e_frame
= int(e_frame
)
477 if retarget
.isRigAdvanced(enduser_obj
) and not enduser_obj
.data
.advancedRetarget
:
478 print("Recommended to use Advanced Retargeting method")
479 enduser_obj
.data
.advancedRetarget
= True
481 retarget
.totalRetarget(performer_obj
, enduser_obj
, scene
, s_frame
, e_frame
)
485 def poll(cls
, context
):
486 if context
.active_object
:
487 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
488 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
490 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
) and performer_obj
[0].animation_data
495 class OBJECT_OT_SaveMappingButton(bpy
.types
.Operator
):
496 #Operator for saving mapping to enduser armature
497 """Save mapping to active armature (for future retargets)"""
498 bl_idname
= "mocap.savemapping"
499 bl_label
= "Save Mapping"
501 def execute(self
, context
):
502 enduser_obj
= bpy
.context
.active_object
503 performer_obj
= [obj
for obj
in bpy
.context
.selected_objects
if obj
!= enduser_obj
][0]
504 retarget
.createDictionary(performer_obj
.data
, enduser_obj
.data
)
508 def poll(cls
, context
):
509 if context
.active_object
:
510 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
511 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
513 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
)
518 class OBJECT_OT_LoadMappingButton(bpy
.types
.Operator
):
519 """Load saved mapping from active armature"""
520 #Operator for loading mapping to enduser armature
521 bl_idname
= "mocap.loadmapping"
522 bl_label
= "Load Mapping"
524 def execute(self
, context
):
525 enduser_obj
= bpy
.context
.active_object
526 performer_obj
= [obj
for obj
in bpy
.context
.selected_objects
if obj
!= enduser_obj
][0]
527 retarget
.loadMapping(performer_obj
.data
, enduser_obj
.data
)
531 def poll(cls
, context
):
532 if context
.active_object
:
533 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
534 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
536 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
)
541 class OBJECT_OT_SelectMapBoneButton(bpy
.types
.Operator
):
542 #Operator for setting selected bone in enduser armature to the performer mapping
543 """Select a bone for faster mapping"""
544 bl_idname
= "mocap.selectmap"
545 bl_label
= "Select Mapping Bone"
546 perf_bone
= StringProperty()
548 def execute(self
, context
):
549 enduser_obj
= bpy
.context
.active_object
550 performer_obj
= [obj
for obj
in bpy
.context
.selected_objects
if obj
!= enduser_obj
][0]
552 for bone
in enduser_obj
.data
.bones
:
553 boneVis
= bone
.layers
555 if boneVis
[i
] and enduser_obj
.data
.layers
[i
]:
557 selectedBone
= bone
.name
559 performer_obj
.data
.bones
[self
.perf_bone
].map = selectedBone
563 def poll(cls
, context
):
564 if context
.active_object
:
565 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
566 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
568 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
)
573 class OBJECT_OT_ConvertSamplesButton(bpy
.types
.Operator
):
574 #Operator to convert samples to beziers on the selected object
575 """Convert active armature's sampled keyframed to beziers"""
576 bl_idname
= "mocap.samples"
577 bl_label
= "Convert Samples"
579 def execute(self
, context
):
580 mocap_tools
.fcurves_simplify(context
, context
.active_object
)
584 def poll(cls
, context
):
585 return context
.active_object
.animation_data
588 class OBJECT_OT_LooperButton(bpy
.types
.Operator
):
589 #Operator to trim fcurves which contain a few loops to a single one on the selected object
590 """Trim active armature's animation to a single cycle, given """ \
591 """a cyclic animation (such as a walk cycle)"""
592 bl_idname
= "mocap.looper"
593 bl_label
= "Loop Mocap"
595 def execute(self
, context
):
596 mocap_tools
.autoloop_anim()
600 def poll(cls
, context
):
601 return context
.active_object
.animation_data
604 class OBJECT_OT_DenoiseButton(bpy
.types
.Operator
):
605 #Operator to denoise impluse noise on the active object's fcurves
606 """Removes spikes from all fcurves on the selected object"""
607 bl_idname
= "mocap.denoise"
608 bl_label
= "Denoise Mocap"
610 def execute(self
, context
):
611 obj
= context
.active_object
612 mocap_tools
.denoise(obj
, obj
.animation_data
.action
.fcurves
)
616 def poll(cls
, context
):
617 obj
= context
.active_object
618 return obj
and obj
.animation_data
and obj
.animation_data
.action
621 class OBJECT_OT_LimitDOFButton(bpy
.types
.Operator
):
622 #Operator to analyze performer armature and apply rotation constraints on the enduser armature
623 """Create limit constraints on the active armature from """ \
624 """the selected armature's animation's range of motion"""
625 bl_idname
= "mocap.limitdof"
626 bl_label
= "Set DOF Constraints"
628 def execute(self
, context
):
629 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
][0]
630 mocap_tools
.limit_dof(context
, performer_obj
, context
.active_object
)
634 def poll(cls
, context
):
635 if context
.active_object
:
636 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
637 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
639 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
)
644 class OBJECT_OT_RemoveLimitDOFButton(bpy
.types
.Operator
):
645 #Removes constraints created by above operator
646 """Remove previously created limit constraints on the active armature"""
647 bl_idname
= "mocap.removelimitdof"
648 bl_label
= "Remove DOF Constraints"
650 def execute(self
, context
):
651 mocap_tools
.limit_dof_toggle_off(context
, context
.active_object
)
655 def poll(cls
, context
):
656 activeIsArmature
= False
657 if context
.active_object
:
658 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
659 return activeIsArmature
662 class OBJECT_OT_RotateFixArmature(bpy
.types
.Operator
):
663 #Operator to fix common imported Mocap data issue of wrong axis system on active object
664 """Realign the active armature's axis system to match Blender """ \
665 """(commonly needed after bvh import)"""
666 bl_idname
= "mocap.rotate_fix"
667 bl_label
= "Rotate Fix"
669 def execute(self
, context
):
670 mocap_tools
.rotate_fix_armature(context
.active_object
.data
)
674 def poll(cls
, context
):
675 if context
.active_object
:
676 return isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
679 class OBJECT_OT_ScaleFixArmature(bpy
.types
.Operator
):
680 #Operator to scale down the selected armature to match the active one
681 """Rescale selected armature to match the active animation, """ \
682 """for convenience"""
683 bl_idname
= "mocap.scale_fix"
684 bl_label
= "Scale Fix"
686 def execute(self
, context
):
687 enduser_obj
= bpy
.context
.active_object
688 performer_obj
= [obj
for obj
in bpy
.context
.selected_objects
if obj
!= enduser_obj
][0]
689 mocap_tools
.scale_fix_armature(performer_obj
, enduser_obj
)
693 def poll(cls
, context
):
694 if context
.active_object
:
695 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
696 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
698 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
)
703 class MOCAP_OT_AddMocapFix(bpy
.types
.Operator
):
704 #Operator to add a post-retarget fix
705 """Add a post-retarget fix - useful for fixing certain """ \
706 """artifacts following the retarget"""
707 bl_idname
= "mocap.addmocapfix"
708 bl_label
= "Add Mocap Fix"
709 type = EnumProperty(name
="Type of Fix",
710 items
=[("point", "Maintain Position", "Bone is at a specific point"),
711 ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
712 ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
713 ("distance", "Maintain distance", "Target bones maintained specified distance")],
714 description
="Type of fix")
716 def execute(self
, context
):
717 enduser_obj
= bpy
.context
.active_object
718 enduser_arm
= enduser_obj
.data
719 new_mcon
= enduser_arm
.mocap_constraints
.add()
720 new_mcon
.type = self
.type
724 def poll(cls
, context
):
725 if context
.active_object
:
726 return isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
729 class OBJECT_OT_RemoveMocapConstraint(bpy
.types
.Operator
):
730 #Operator to remove a post-retarget fix
731 """Remove this post-retarget fix"""
732 bl_idname
= "mocap.removeconstraint"
733 bl_label
= "Remove Mocap Fix"
734 constraint
= IntProperty()
736 def execute(self
, context
):
737 enduser_obj
= bpy
.context
.active_object
738 enduser_arm
= enduser_obj
.data
739 m_constraints
= enduser_arm
.mocap_constraints
740 m_constraint
= m_constraints
[self
.constraint
]
741 if m_constraint
.real_constraint
:
742 bone
= enduser_obj
.pose
.bones
[m_constraint
.real_constraint_bone
]
743 cons_obj
= mocap_constraints
.getConsObj(bone
)
744 mocap_constraints
.removeConstraint(m_constraint
, cons_obj
)
745 m_constraints
.remove(self
.constraint
)
749 def poll(cls
, context
):
750 if context
.active_object
:
751 return isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
754 class OBJECT_OT_BakeMocapConstraints(bpy
.types
.Operator
):
755 #Operator to bake all post-retarget fixes
756 """Bake all post-retarget fixes to the Retarget Fixes NLA Track"""
757 bl_idname
= "mocap.bakeconstraints"
758 bl_label
= "Bake Mocap Fixes"
760 def execute(self
, context
):
761 mocap_constraints
.bakeConstraints(context
)
765 def poll(cls
, context
):
766 if context
.active_object
:
767 return isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
770 class OBJECT_OT_UnbakeMocapConstraints(bpy
.types
.Operator
):
771 #Operator to unbake all post-retarget fixes
772 """Unbake all post-retarget fixes - removes the baked data """ \
773 """from the Retarget Fixes NLA Track"""
774 bl_idname
= "mocap.unbakeconstraints"
775 bl_label
= "Unbake Mocap Fixes"
777 def execute(self
, context
):
778 mocap_constraints
.unbakeConstraints(context
)
782 def poll(cls
, context
):
783 if context
.active_object
:
784 return isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
787 class OBJECT_OT_UpdateMocapConstraints(bpy
.types
.Operator
):
788 #Operator to update all post-retarget fixes, similar to update dependencies on drivers
789 #Needed because python properties lack certain callbacks and some fixes take a while to recalculate.
790 """Update all post-retarget fixes (neccesary to take under """ \
791 """consideration changes to armature object or pose)"""
792 bl_idname
= "mocap.updateconstraints"
793 bl_label
= "Update Mocap Fixes"
795 def execute(self
, context
):
796 mocap_constraints
.updateConstraints(context
.active_object
, context
)
800 def poll(cls
, context
):
801 if context
.active_object
:
802 return isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
805 class OBJECT_OT_GuessHierachyMapping(bpy
.types
.Operator
):
806 #Operator which calls heurisitic function to guess mapping between 2 armatures
807 """Attempt to auto figure out hierarchy mapping"""
808 bl_idname
= "mocap.guessmapping"
809 bl_label
= "Guess Hierarchy Mapping"
811 def execute(self
, context
):
812 enduser_obj
= bpy
.context
.active_object
813 performer_obj
= [obj
for obj
in bpy
.context
.selected_objects
if obj
!= enduser_obj
][0]
814 mocap_tools
.guessMapping(performer_obj
, enduser_obj
)
818 def poll(cls
, context
):
819 if context
.active_object
:
820 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
821 performer_obj
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
]
823 return activeIsArmature
and isinstance(performer_obj
[0].data
, bpy
.types
.Armature
)
828 class OBJECT_OT_PathEditing(bpy
.types
.Operator
):
829 #Operator which calls path editing function, making active object follow the selected curve.
830 """Set active object (stride object) to follow the selected curve"""
831 bl_idname
= "mocap.pathediting"
832 bl_label
= "Set Path"
834 def execute(self
, context
):
835 path
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
][0]
836 mocap_tools
.path_editing(context
, context
.active_object
, path
)
840 def poll(cls
, context
):
841 if context
.active_object
:
842 selected_objs
= [obj
for obj
in context
.selected_objects
if obj
!= context
.active_object
and isinstance(obj
.data
, bpy
.types
.Curve
)]
848 class OBJECT_OT_AnimationStitchingButton(bpy
.types
.Operator
):
849 #Operator which calls stitching function, combining 2 animations onto the NLA.
850 """Stitch two defined animations into a single one via alignment of NLA Tracks"""
851 bl_idname
= "mocap.animstitch"
852 bl_label
= "Stitch Animations"
854 def execute(self
, context
):
855 mocap_tools
.anim_stitch(context
, context
.active_object
)
859 def poll(cls
, context
):
860 activeIsArmature
= False
861 if context
.active_object
:
862 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
864 stitch_settings
= context
.active_object
.data
.stitch_settings
865 return (stitch_settings
.first_action
and stitch_settings
.second_action
)
869 class OBJECT_OT_GuessAnimationStitchingButton(bpy
.types
.Operator
):
870 #Operator which calls stitching function heuristic, setting good values for above operator.
871 """Guess the stitch frame and second offset for animation stitch"""
872 bl_idname
= "mocap.animstitchguess"
873 bl_label
= "Guess Animation Stitch"
875 def execute(self
, context
):
876 mocap_tools
.guess_anim_stitch(context
, context
.active_object
)
880 def poll(cls
, context
):
881 activeIsArmature
= False
882 if context
.active_object
:
883 activeIsArmature
= isinstance(context
.active_object
.data
, bpy
.types
.Armature
)
885 stitch_settings
= context
.active_object
.data
.stitch_settings
886 return (stitch_settings
.first_action
and stitch_settings
.second_action
)
891 bpy
.utils
.register_class(MocapConstraint
)
892 bpy
.types
.Armature
.mocap_constraints
= CollectionProperty(type=MocapConstraint
)
893 bpy
.utils
.register_class(MocapMapping
)
894 #string property for storing performer->enduser mapping
895 bpy
.types
.Bone
.map = StringProperty()
896 #Collection Property for storing enduser->performer mapping
897 bpy
.types
.Bone
.reverseMap
= CollectionProperty(type=MocapMapping
)
898 #Boolean property for storing foot bone toggle
899 bpy
.types
.Bone
.foot
= BoolProperty(name
="Foot",
900 description
="Mark this bone as a 'foot', which determines retargeted animation's translation",
902 #Boolean property for storing if this bone is twisted along the y axis,
903 # which can happen due to various sources of performers
904 bpy
.types
.Bone
.twistFix
= BoolProperty(name
="Twist Fix",
905 description
="Fix Twist on this bone",
907 #Boolean property for toggling ik retargeting for this bone
908 bpy
.types
.PoseBone
.IKRetarget
= BoolProperty(name
="IK",
909 description
="Toggle IK Retargeting method for given bone",
910 update
=toggleIKBone
, default
=False)
911 bpy
.utils
.register_class(AnimationStitchSettings
)
912 bpy
.utils
.register_class(MocapNLATracks
)
913 #Animation Stitch Settings Property
914 bpy
.types
.Armature
.stitch_settings
= PointerProperty(type=AnimationStitchSettings
)
915 #Current/Active retargeted animation on the armature
916 bpy
.types
.Armature
.active_mocap
= StringProperty(update
=retarget
.NLASystemInitialize
)
917 #Collection of retargeted animations and their NLA Tracks on the armature
918 bpy
.types
.Armature
.mocapNLATracks
= CollectionProperty(type=MocapNLATracks
)
919 #Advanced retargeting boolean property
920 bpy
.types
.Armature
.advancedRetarget
= BoolProperty(default
=False, update
=advancedRetargetToggle
)
921 #frame step - frequency of frames to retarget. Skipping is useful for previewing, faster work etc.
922 bpy
.types
.Armature
.frameStep
= IntProperty(name
="Frame Skip",
924 description
="Number of frames to skip - for previewing retargets quickly (1 is fully sampled)",
926 bpy
.utils
.register_module(__name__
)
930 bpy
.utils
.unregister_module(__name__
)
932 if __name__
== "__main__":