Update for 2.8
[blender-addons.git] / mocap / __init__.py
blob27b166ab515f70f1f56e1495d1c34ee56a70d6ab
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 #####
19 # <pep8 compliant>
21 bl_info = {
22 "name": "Motion Capture Tools",
23 "author": "Benjy Cook",
24 "blender": (2, 73, 0),
25 "version": (1, 1, 1),
26 "location": "Active Armature > Object Properties > Mocap tools",
27 "description": "Various tools for working with motion capture animation",
28 "warning": "",
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",
35 if "bpy" in locals():
36 import importlib
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)
43 else:
44 import bpy
45 from bpy.props import (
46 BoolProperty,
47 CollectionProperty,
48 EnumProperty,
49 FloatProperty,
50 FloatVectorProperty,
51 IntProperty,
52 PointerProperty,
53 StringProperty,
55 from . import (
56 mocap_constraints,
57 retarget,
58 mocap_tools,
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"]
69 if ik:
70 return ik[0]
71 else:
72 return False
75 class MocapConstraint(bpy.types.PropertyGroup):
76 name = StringProperty(name="Name",
77 default="Mocap Fix",
78 description="Name of Mocap Fix",
79 update=mocap_constraints.setConstraint)
80 constrained_bone = StringProperty(name="Bone",
81 default="",
82 description="Constrained Bone",
83 update=mocap_constraints.updateConstraintBoneType)
84 constrained_boneB = StringProperty(name="Bone (2)",
85 default="",
86 description="Other Constrained Bone (optional, depends on type)",
87 update=mocap_constraints.setConstraint)
88 s_frame = IntProperty(name="S",
89 default=0,
90 description="Start frame of Fix",
91 update=mocap_constraints.setConstraint)
92 e_frame = IntProperty(name="E",
93 default=100,
94 description="End frame of Fix",
95 update=mocap_constraints.setConstraint)
96 smooth_in = IntProperty(name="In",
97 default=10,
98 description="Number of frames to smooth in",
99 update=mocap_constraints.setConstraint,
100 min=0)
101 smooth_out = IntProperty(name="Out",
102 default=10,
103 description="Number of frames to smooth out",
104 update=mocap_constraints.setConstraint,
105 min=0)
106 targetMesh = StringProperty(name="Mesh",
107 default="",
108 description="Target of Fix - Mesh (optional, depends on type)",
109 update=mocap_constraints.setConstraint)
110 active = BoolProperty(name="Active",
111 default=True,
112 description="Fix is active",
113 update=mocap_constraints.setConstraint)
114 show_expanded = BoolProperty(name="Show Expanded",
115 default=True,
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",
122 default=0.0,
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")],
129 name="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",
153 default=10)
154 second_offset = IntProperty(name="Second offset",
155 description="Frame offset for 2nd animation, where it should start",
156 default=10)
157 stick_bone = StringProperty(name="Stick Bone",
158 description="Bone to freeze during transition",
159 default="")
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")
177 return
178 else:
179 performer_obj = performer_obj[0]
180 if self.advancedRetarget:
181 retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
182 else:
183 retarget.cleanTempConstraints(enduser_obj)
186 def toggleIKBone(self, context):
187 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
188 if self.IKRetarget:
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
193 chainLen = 0
194 for parent_bone in self.parent_recursive:
195 chainLen += 1
196 if hasIKConstraint(parent_bone):
197 break
198 #~ deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
199 #~ if len(deformer_children) > 1:
200 #~ break
201 ik.chain_count = chainLen
202 for bone in self.parent_recursive:
203 if bone.is_in_ik_chain:
204 bone.IKRetarget = True
205 else:
206 print(self.name + " IK toggled OFF!")
207 cnstrn_bones = []
208 newChainLength = []
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)
215 if cnstrn_bones:
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)
219 if newChainLength:
220 ik = hasIKConstraint(cnstrn_bone)
221 ik.chain_count = newChainLength[i]
222 else:
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:
244 if obj.pose:
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
249 else:
250 pose_bone.IKRetarget = False
252 updateIKRetarget()
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"]
259 if ik:
260 return ik[0]
261 else:
262 return False
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"
272 @classmethod
273 def poll(cls, context):
274 obj = context.object
275 return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
276 'POSE',
277 'OBJECT'}
279 def draw(self, context):
280 layout = self.layout
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)")
299 else:
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)
313 nameCol.scale_x = 2
314 mapCol = MappingRow.column(align=True)
315 mapCol.scale_x = 2
316 selectCol = MappingRow.column(align=True)
317 twistCol = MappingRow.column(align=True)
318 IKCol = MappingRow.column(align=True)
319 IKCol.scale_x = 0.3
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
327 label_mod = "FK"
328 if bone.map:
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):
333 label_mod = "ik end"
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)
338 else:
339 twistCol.label(" ")
340 IKCol.label(" ")
341 IKLabel.label(" ")
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'}
361 @classmethod
362 def poll(cls, context):
363 obj = context.object
364 return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
365 'POSE',
366 'OBJECT'}
368 def draw(self, context):
369 layout = self.layout
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')
380 layout.separator()
381 for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
382 box = layout.box()
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:
390 box.separator()
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')
394 frameRow = box.row()
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')
414 layout.separator()
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'}
425 @classmethod
426 def poll(cls, context):
427 obj = context.object
428 return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
429 'POSE',
430 'OBJECT'}
431 def draw(self, context):
432 layout = self.layout
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)
436 if activeIsArmature:
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")
472 else:
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
480 else:
481 retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
482 return {'FINISHED'}
484 @classmethod
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]
489 if performer_obj:
490 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature) and performer_obj[0].animation_data
491 else:
492 return False
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)
505 return {'FINISHED'}
507 @classmethod
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]
512 if performer_obj:
513 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
514 else:
515 return False
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)
528 return {'FINISHED'}
530 @classmethod
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]
535 if performer_obj:
536 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
537 else:
538 return False
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]
551 selectedBone = ""
552 for bone in enduser_obj.data.bones:
553 boneVis = bone.layers
554 for i in range(32):
555 if boneVis[i] and enduser_obj.data.layers[i]:
556 if bone.select:
557 selectedBone = bone.name
558 break
559 performer_obj.data.bones[self.perf_bone].map = selectedBone
560 return {'FINISHED'}
562 @classmethod
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]
567 if performer_obj:
568 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
569 else:
570 return False
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)
581 return {'FINISHED'}
583 @classmethod
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()
597 return {'FINISHED'}
599 @classmethod
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)
613 return {'FINISHED'}
615 @classmethod
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)
631 return {'FINISHED'}
633 @classmethod
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]
638 if performer_obj:
639 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
640 else:
641 return False
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)
652 return {'FINISHED'}
654 @classmethod
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)
671 return {'FINISHED'}
673 @classmethod
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)
690 return {'FINISHED'}
692 @classmethod
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]
697 if performer_obj:
698 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
699 else:
700 return False
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
721 return {'FINISHED'}
723 @classmethod
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)
746 return {'FINISHED'}
748 @classmethod
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)
762 return {'FINISHED'}
764 @classmethod
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)
779 return {'FINISHED'}
781 @classmethod
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)
797 return {'FINISHED'}
799 @classmethod
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)
815 return {'FINISHED'}
817 @classmethod
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]
822 if performer_obj:
823 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
824 else:
825 return False
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)
837 return {'FINISHED'}
839 @classmethod
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)]
843 return selected_objs
844 else:
845 return False
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)
856 return {'FINISHED'}
858 @classmethod
859 def poll(cls, context):
860 activeIsArmature = False
861 if context.active_object:
862 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
863 if activeIsArmature:
864 stitch_settings = context.active_object.data.stitch_settings
865 return (stitch_settings.first_action and stitch_settings.second_action)
866 return False
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)
877 return {'FINISHED'}
879 @classmethod
880 def poll(cls, context):
881 activeIsArmature = False
882 if context.active_object:
883 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
884 if activeIsArmature:
885 stitch_settings = context.active_object.data.stitch_settings
886 return (stitch_settings.first_action and stitch_settings.second_action)
887 return False
890 def register():
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",
901 default=False)
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",
906 default=False)
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",
923 default=1,
924 description="Number of frames to skip - for previewing retargets quickly (1 is fully sampled)",
925 min=1)
926 bpy.utils.register_module(__name__)
929 def unregister():
930 bpy.utils.unregister_module(__name__)
932 if __name__ == "__main__":
933 register()