0820b6183f404014b0abc51e82a9d57e72929ae3
[blender.git] / release / scripts / startup / ui_mocap.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22
23 from bpy.props import *
24 from bpy import *
25 import mocap_constraints
26 import retarget
27 import mocap_tools
28
29 ### reloads modules (for testing purposes only)
30 from imp import reload
31 reload(mocap_constraints)
32 reload(retarget)
33 reload(mocap_tools)
34
35 from mocap_constraints import *
36
37 # MocapConstraint class
38 # Defines MocapConstraint datatype, used to add and configute mocap constraints
39 # Attached to Armature data
40
41
42 class MocapConstraint(bpy.types.PropertyGroup):
43     name = bpy.props.StringProperty(name="Name",
44         default="Mocap Fix",
45         description="Name of Mocap Fix",
46         update=setConstraint)
47     constrained_bone = bpy.props.StringProperty(name="Bone",
48         default="",
49         description="Constrained Bone",
50         update=updateConstraintBoneType)
51     constrained_boneB = bpy.props.StringProperty(name="Bone (2)",
52         default="",
53         description="Other Constrained Bone (optional, depends on type)",
54         update=setConstraint)
55     s_frame = bpy.props.IntProperty(name="S",
56         default=0,
57         description="Start frame of Fix",
58         update=setConstraint)
59     e_frame = bpy.props.IntProperty(name="E",
60         default=100,
61         description="End frame of Fix",
62         update=setConstraint)
63     smooth_in = bpy.props.IntProperty(name="In",
64         default=10,
65         description="Amount of frames to smooth in",
66         update=setConstraint,
67         min=0)
68     smooth_out = bpy.props.IntProperty(name="Out",
69         default=10,
70         description="Amount of frames to smooth out",
71         update=setConstraint,
72         min=0)
73     targetMesh = bpy.props.StringProperty(name="Mesh",
74         default="",
75         description="Target of Fix - Mesh (optional, depends on type)",
76         update=setConstraint)
77     active = bpy.props.BoolProperty(name="Active",
78         default=True,
79         description="Fix is active",
80         update=setConstraint)
81     show_expanded = bpy.props.BoolProperty(name="Show Expanded",
82         default=True,
83         description="Fix is fully shown")
84     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
85         subtype="XYZ", default=(0.0, 0.0, 0.0),
86         description="Target of Fix - Point",
87         update=setConstraint)
88     targetDist = bpy.props.FloatProperty(name="Offset",
89         default=0.0,
90         description="Distance and Floor Fixes - Desired offset",
91         update=setConstraint)
92     targetSpace = bpy.props.EnumProperty(
93         items=[("WORLD", "World Space", "Evaluate target in global space"),
94             ("LOCAL", "Object space", "Evaluate target in object space"),
95             ("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
96         name="Space",
97         description="In which space should Point type target be evaluated",
98         update=setConstraint)
99     type = bpy.props.EnumProperty(name="Type of constraint",
100         items=[("point", "Maintain Position", "Bone is at a specific point"),
101             ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
102             ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
103             ("distance", "Maintain distance", "Target bones maintained specified distance")],
104         description="Type of Fix",
105         update=updateConstraintBoneType)
106     real_constraint = bpy.props.StringProperty()
107     real_constraint_bone = bpy.props.StringProperty()
108
109
110 bpy.utils.register_class(MocapConstraint)
111
112 bpy.types.Armature.mocap_constraints = bpy.props.CollectionProperty(type=MocapConstraint)
113
114
115 class AnimationStitchSettings(bpy.types.PropertyGroup):
116     first_action = bpy.props.StringProperty(name="Action 1",
117             description="First action in stitch")
118     second_action = bpy.props.StringProperty(name="Action 2",
119             description="Second action in stitch")
120     blend_frame = bpy.props.IntProperty(name="Stitch frame",
121             description="Frame to locate stitch on")
122     blend_amount = bpy.props.IntProperty(name="Blend amount",
123             description="Size of blending transitiion, on both sides of the stitch",
124             default=10)
125     second_offset = bpy.props.IntProperty(name="Second offset",
126             description="Frame offset for 2nd animation, where it should start",
127             default=10)
128     stick_bone = bpy.props.StringProperty(name="Stick Bone",
129             description="Bone to freeze during transition",
130             default="")
131
132 bpy.utils.register_class(AnimationStitchSettings)
133
134
135 class MocapNLATracks(bpy.types.PropertyGroup):
136     name = bpy.props.StringProperty()
137     base_track = bpy.props.StringProperty()
138     auto_fix_track = bpy.props.StringProperty()
139     manual_fix_track = bpy.props.StringProperty()
140     stride_action = bpy.props.StringProperty()
141
142 bpy.utils.register_class(MocapNLATracks)
143
144                     
145 def advancedRetargetToggle(self, context):
146     enduser_obj = context.active_object
147     performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
148     if enduser_obj is None or len(performer_obj) != 1:
149         print("Need active and selected armatures")
150         return
151     else:
152         performer_obj = performer_obj[0]
153     if self.advancedRetarget:
154         retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
155     else:
156         retarget.cleanTempConstraints(enduser_obj)
157
158
159
160 bpy.types.Armature.stitch_settings = bpy.props.PointerProperty(type=AnimationStitchSettings)
161 bpy.types.Armature.active_mocap =  bpy.props.StringProperty(update=retarget.NLASystemInitialize)
162 bpy.types.Armature.mocapNLATracks = bpy.props.CollectionProperty(type=MocapNLATracks)
163 bpy.types.Armature.advancedRetarget = bpy.props.BoolProperty(default=False, update=advancedRetargetToggle)
164
165 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
166
167
168 def toggleIKBone(self, context):
169     if self.IKRetarget:
170         if not self.is_in_ik_chain:
171             print(self.name + " IK toggled ON!")
172             ik = self.constraints.new('IK')
173             #ik the whole chain up to the root, excluding
174             chainLen = 0
175             for parent_bone in self.parent_recursive:
176                 chainLen += 1
177                 if hasIKConstraint(parent_bone):
178                     break
179                 deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
180                 #~ if len(deformer_children) > 1:
181                     #~ break
182             ik.chain_count = chainLen
183             for bone in self.parent_recursive:
184                 if bone.is_in_ik_chain:
185                     bone.IKRetarget = True
186     else:
187         print(self.name + " IK toggled OFF!")
188         cnstrn_bones = []
189         newChainLength = []
190         if hasIKConstraint(self):
191             cnstrn_bones = [self]
192         elif self.is_in_ik_chain:
193             cnstrn_bones = [child for child in self.children_recursive if hasIKConstraint(child)]
194             for cnstrn_bone in cnstrn_bones:
195                 newChainLength.append(cnstrn_bone.parent_recursive.index(self) + 1)
196         if cnstrn_bones:
197             # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
198             for i, cnstrn_bone in enumerate(cnstrn_bones):
199                 print(cnstrn_bone.name)
200                 if newChainLength:
201                     ik = hasIKConstraint(cnstrn_bone)
202                     ik.chain_count = newChainLength[i]
203                 else:
204                     ik = hasIKConstraint(cnstrn_bone)
205                     cnstrn_bone.constraints.remove(ik)
206                     cnstrn_bone.IKRetarget = False
207             for bone in cnstrn_bone.parent_recursive:
208                 if not bone.is_in_ik_chain:
209                     bone.IKRetarget = False
210         
211
212 class MocapMapping(bpy.types.PropertyGroup):
213     name = bpy.props.StringProperty()
214
215 bpy.utils.register_class(MocapMapping)
216
217 bpy.types.Bone.map = bpy.props.StringProperty()
218 bpy.types.Bone.reverseMap = bpy.props.CollectionProperty(type=MocapMapping)
219 bpy.types.Bone.foot = bpy.props.BoolProperty(name="Foot",
220     description="Marks this bone as a 'foot', which determines retargeted animation's translation",
221     default=False)
222 bpy.types.PoseBone.IKRetarget = bpy.props.BoolProperty(name="IK",
223     description="Toggles IK Retargeting method for given bone",
224     update=toggleIKBone, default=False)
225
226
227 def updateIKRetarget():
228     # ensures that Blender constraints and IK properties are in sync
229     # currently runs when module is loaded, should run when scene is loaded
230     # or user adds a constraint to armature. Will be corrected in the future,
231     # once python callbacks are implemented
232     for obj in bpy.data.objects:
233         if obj.pose:
234             bones = obj.pose.bones
235             for pose_bone in bones:
236                 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
237                     pose_bone.IKRetarget = True
238                 else:
239                     pose_bone.IKRetarget = False
240
241 updateIKRetarget()
242
243
244 class MocapPanel(bpy.types.Panel):
245     # Motion capture retargeting panel
246     bl_label = "Mocap tools"
247     bl_space_type = "PROPERTIES"
248     bl_region_type = "WINDOW"
249     bl_context = "object"
250
251     def draw(self, context):
252         self.layout.label("Preprocessing")
253         row = self.layout.row(align=True)
254         row.alignment = 'EXPAND'
255         row.operator("mocap.samples", text='Samples to Beziers')
256         row.operator("mocap.denoise", text='Clean noise')
257         row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
258         row.operator("mocap.scale_fix", text='Auto scale Performer')
259         row2 = self.layout.row(align=True)
260         row2.operator("mocap.looper", text='Loop animation')
261         row2.operator("mocap.limitdof", text='Constrain Rig')
262         row2.operator("mocap.removelimitdof", text='Unconstrain Rig')
263         self.layout.label("Retargeting")
264         enduser_obj = bpy.context.active_object
265         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
266         if enduser_obj is None or len(performer_obj) != 1:
267             self.layout.label("Select performer rig and target rig (as active)")
268         else:
269             self.layout.operator("mocap.guessmapping", text="Guess Hiearchy Mapping")
270             row3 = self.layout.row(align=True)
271             column1 = row3.column(align=True)
272             column1.label("Performer Rig")
273             column2 = row3.column(align=True)
274             column2.label("Enduser Rig")
275             performer_obj = performer_obj[0]
276             if performer_obj.data and enduser_obj.data:
277                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
278                     perf = performer_obj.data
279                     enduser_arm = enduser_obj.data
280                     perf_pose_bones = enduser_obj.pose.bones
281                     for bone in perf.bones:
282                         row = self.layout.row()
283                         row.prop(data=bone, property='foot', text='', icon='POSE_DATA')
284                         row.label(bone.name)
285                         row.prop_search(bone, "map", enduser_arm, "bones")
286                         row.operator("mocap.selectmap", text='', icon='CURSOR').perf_bone = bone.name
287                         label_mod = "FK"
288                         if bone.map:
289                             pose_bone = perf_pose_bones[bone.map]
290                             if pose_bone.is_in_ik_chain:
291                                 label_mod = "ik chain"
292                             if hasIKConstraint(pose_bone):
293                                 label_mod = "ik end"
294                             row.prop(pose_bone, 'IKRetarget')
295                             row.label(label_mod)
296                         else:
297                             row.label(" ")
298                             row.label(" ")
299                     mapRow = self.layout.row()
300                     mapRow.operator("mocap.savemapping", text='Save mapping')
301                     mapRow.operator("mocap.loadmapping", text='Load mapping')
302                     self.layout.prop(data=performer_obj.animation_data.action, property='name', text='Action Name')
303                     self.layout.prop(enduser_arm, "advancedRetarget", text='Advanced Retarget')
304                     self.layout.operator("mocap.retarget", text='RETARGET!')
305
306
307 class MocapConstraintsPanel(bpy.types.Panel):
308     #Motion capture constraints panel
309     bl_label = "Mocap Fixes"
310     bl_space_type = "PROPERTIES"
311     bl_region_type = "WINDOW"
312     bl_context = "object"
313
314     def draw(self, context):
315         layout = self.layout
316         if context.active_object:
317             if context.active_object.data:
318                 if context.active_object.data.name in bpy.data.armatures:
319                     enduser_obj = context.active_object
320                     enduser_arm = enduser_obj.data
321                     layout.operator_menu_enum("mocap.addmocapfix", "type")
322                     layout.operator("mocap.updateconstraints", text='Update Fixes')
323                     bakeRow = layout.row()
324                     bakeRow.operator("mocap.bakeconstraints", text='Bake Fixes')
325                     bakeRow.operator("mocap.unbakeconstraints", text='Unbake Fixes')
326                     layout.separator()
327                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
328                         box = layout.box()
329                         headerRow = box.row()
330                         headerRow.prop(m_constraint, 'show_expanded', text='', icon='TRIA_DOWN' if m_constraint.show_expanded else 'TRIA_RIGHT', emboss=False)
331                         headerRow.prop(m_constraint, 'type', text='')
332                         headerRow.prop(m_constraint, 'name', text='')
333                         headerRow.prop(m_constraint, 'active', icon='MUTE_IPO_ON' if not m_constraint.active else'MUTE_IPO_OFF', text='', emboss=False)
334                         headerRow.operator("mocap.removeconstraint", text="", icon='X', emboss=False).constraint = i
335                         if m_constraint.show_expanded:
336                             box.separator()
337                             box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones", icon='BONE_DATA')
338                             if m_constraint.type == "distance" or m_constraint.type == "point":
339                                 box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones", icon='CONSTRAINT_BONE')
340                             frameRow = box.row()
341                             frameRow.label("Frame Range:")
342                             frameRow.prop(m_constraint, 's_frame')
343                             frameRow.prop(m_constraint, 'e_frame')
344                             smoothRow = box.row()
345                             smoothRow.label("Smoothing:")
346                             smoothRow.prop(m_constraint, 'smooth_in')
347                             smoothRow.prop(m_constraint, 'smooth_out')
348                             targetRow = box.row()
349                             targetLabelCol = targetRow.column()
350                             targetLabelCol.label("Target settings:")
351                             targetPropCol = targetRow.column()
352                             if m_constraint.type == "floor":
353                                 targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
354                             if m_constraint.type == "point" or m_constraint.type == "freeze":
355                                 box.prop(m_constraint, 'targetSpace')
356                             if m_constraint.type == "point":
357                                 targetPropCol.prop(m_constraint, 'targetPoint')
358                             if m_constraint.type == "distance" or m_constraint.type == "floor":
359                                 targetPropCol.prop(m_constraint, 'targetDist')
360                             layout.separator()
361
362
363 class ExtraToolsPanel(bpy.types.Panel):
364     # Motion capture retargeting panel
365     bl_label = "Extra Mocap Tools"
366     bl_space_type = "PROPERTIES"
367     bl_region_type = "WINDOW"
368     bl_context = "object"
369
370     def draw(self, context):
371         layout = self.layout
372         layout.operator('mocap.pathediting', text="Follow Path")
373         layout.label("Animation Stitching")
374         activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
375         if activeIsArmature:
376             enduser_arm = context.active_object.data
377             layout.label("Retargeted Animations:")
378             layout.prop_search(enduser_arm, "active_mocap",enduser_arm, "mocapNLATracks")
379             settings = enduser_arm.stitch_settings
380             layout.prop_search(settings, "first_action", enduser_arm, "mocapNLATracks")
381             layout.prop_search(settings, "second_action", enduser_arm, "mocapNLATracks")
382             layout.prop(settings, "blend_frame")
383             layout.prop(settings, "blend_amount")
384             layout.prop(settings, "second_offset")
385             layout.prop_search(settings, "stick_bone", context.active_object.pose, "bones")
386             layout.operator('mocap.animstitchguess', text="Guess Settings")
387             layout.operator('mocap.animstitch', text="Stitch Animations")
388
389
390 class OBJECT_OT_RetargetButton(bpy.types.Operator):
391     '''Retarget animation from selected armature to active armature '''
392     bl_idname = "mocap.retarget"
393     bl_label = "Retargets active action from Performer to Enduser"
394
395     def execute(self, context):
396         scene = context.scene
397         s_frame = scene.frame_start
398         e_frame = scene.frame_end
399         enduser_obj = context.active_object
400         performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
401         if enduser_obj is None or len(performer_obj) != 1:
402             print("Need active and selected armatures")
403         else:
404             performer_obj = performer_obj[0]
405             s_frame, e_frame = performer_obj.animation_data.action.frame_range
406             s_frame = int(s_frame)
407             e_frame = int(e_frame)
408         if retarget.isRigAdvanced(enduser_obj) and not enduser_obj.data.advancedRetarget:
409             print("Recommended to use Advanced Retargeting method")
410             enduser_obj.data.advancedRetarget = True
411         else:
412             retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
413         return {"FINISHED"}
414
415     @classmethod
416     def poll(cls, context):
417         if context.active_object:
418             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
419         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
420         if performer_obj:
421             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
422         else:
423             return False
424             
425     
426     #~ class OBJECT_OT_AdvancedRetargetButton(bpy.types.Operator):
427         #~ '''Prepare for advanced retargeting '''
428         #~ bl_idname = "mocap.preretarget"
429         #~ bl_label = "Prepares retarget of active action from Performer to Enduser"
430
431         #~ def execute(self, context):
432             #~ scene = context.scene
433             #~ s_frame = scene.frame_start
434             #~ e_frame = scene.frame_end
435             #~ enduser_obj = context.active_object
436             #~ performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
437             #~ if enduser_obj is None or len(performer_obj) != 1:
438                 #~ print("Need active and selected armatures")
439             #~ else:
440                 #~ performer_obj = performer_obj[0]
441             #~ retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
442             #~ return {"FINISHED"}
443
444         #~ @classmethod
445         #~ def poll(cls, context):
446             #~ if context.active_object:
447                 #~ activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
448             #~ performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
449             #~ if performer_obj:
450                 #~ return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
451             #~ else:
452                 #~ return False
453
454
455 class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
456     '''Save mapping to active armature (for future retargets) '''
457     bl_idname = "mocap.savemapping"
458     bl_label = "Saves user generated mapping from Performer to Enduser"
459
460     def execute(self, context):
461         enduser_obj = bpy.context.active_object
462         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
463         retarget.createDictionary(performer_obj.data, enduser_obj.data)
464         return {"FINISHED"}
465
466     @classmethod
467     def poll(cls, context):
468         if context.active_object:
469             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
470         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
471         if performer_obj:
472             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
473         else:
474             return False
475
476
477 class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
478     '''Load saved mapping from active armature'''
479     bl_idname = "mocap.loadmapping"
480     bl_label = "Loads user generated mapping from Performer to Enduser"
481
482     def execute(self, context):
483         enduser_obj = bpy.context.active_object
484         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
485         retarget.loadMapping(performer_obj.data, enduser_obj.data)
486         return {"FINISHED"}
487
488     @classmethod
489     def poll(cls, context):
490         if context.active_object:
491             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
492         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
493         if performer_obj:
494             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
495         else:
496             return False
497             
498
499 class OBJECT_OT_SelectMapBoneButton(bpy.types.Operator):
500     '''Select a bone for faster mapping'''
501     bl_idname = "mocap.selectmap"
502     bl_label = "Select a bone for faster mapping"
503     perf_bone = bpy.props.StringProperty()
504
505     def execute(self, context):
506         enduser_obj = bpy.context.active_object
507         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
508         selectedBone = ""
509         for bone in enduser_obj.data.bones:
510             boneVis = bone.layers
511             for i in range(32):
512                 if boneVis[i] and enduser_obj.data.layers[i]:
513                     if bone.select:
514                         selectedBone = bone.name
515                         break
516         performer_obj.data.bones[self.perf_bone].map = selectedBone
517         return {"FINISHED"}
518
519     @classmethod
520     def poll(cls, context):
521         if context.active_object:
522             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
523         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
524         if performer_obj:
525             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
526         else:
527             return False
528
529
530 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
531     '''Convert active armature's sampled keyframed to beziers'''
532     bl_idname = "mocap.samples"
533     bl_label = "Converts samples / simplifies keyframes to beziers"
534
535     def execute(self, context):
536         mocap_tools.fcurves_simplify(context, context.active_object)
537         return {"FINISHED"}
538
539     @classmethod
540     def poll(cls, context):
541         return context.active_object.animation_data
542
543
544 class OBJECT_OT_LooperButton(bpy.types.Operator):
545     '''Trim active armature's animation to a single cycle, given a cyclic animation (such as a walk cycle)'''
546     bl_idname = "mocap.looper"
547     bl_label = "loops animation / sampled mocap data"
548
549     def execute(self, context):
550         mocap_tools.autoloop_anim()
551         return {"FINISHED"}
552
553     @classmethod
554     def poll(cls, context):
555         return context.active_object.animation_data
556
557
558 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
559     '''Denoise active armature's animation. Good for dealing with 'bad' frames inherent in mocap animation'''
560     bl_idname = "mocap.denoise"
561     bl_label = "Denoises sampled mocap data "
562
563     def execute(self, context):
564         mocap_tools.denoise_median()
565         return {"FINISHED"}
566
567     @classmethod
568     def poll(cls, context):
569         return context.active_object
570
571     @classmethod
572     def poll(cls, context):
573         return context.active_object.animation_data
574
575
576 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
577     '''Create limit constraints on the active armature from the selected armature's animation's range of motion'''
578     bl_idname = "mocap.limitdof"
579     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
580
581     def execute(self, context):
582         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object][0]
583         mocap_tools.limit_dof(context, performer_obj, context.active_object)
584         return {"FINISHED"}
585
586     @classmethod
587     def poll(cls, context):
588         if context.active_object:
589             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
590         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
591         if performer_obj:
592             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
593         else:
594             return False
595
596
597 class OBJECT_OT_RemoveLimitDOFButton(bpy.types.Operator):
598     '''Removes previously created limit constraints on the active armature'''
599     bl_idname = "mocap.removelimitdof"
600     bl_label = "Removes previously created limit constraints on the active armature"
601
602     def execute(self, context):
603         mocap_tools.limit_dof_toggle_off(context, context.active_object)
604         return {"FINISHED"}
605
606     @classmethod
607     def poll(cls, context):
608         activeIsArmature = False
609         if context.active_object:
610             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
611         return activeIsArmature
612
613
614 class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
615     '''Realign the active armature's axis system to match Blender (Commonly needed after bvh import)'''
616     bl_idname = "mocap.rotate_fix"
617     bl_label = "Rotates selected armature 90 degrees (fix for bvh import)"
618
619     def execute(self, context):
620         mocap_tools.rotate_fix_armature(context.active_object.data)
621         return {"FINISHED"}
622
623     @classmethod
624     def poll(cls, context):
625         if context.active_object:
626             return isinstance(context.active_object.data, bpy.types.Armature)
627
628
629 class OBJECT_OT_ScaleFixArmature(bpy.types.Operator):
630     '''Rescale selected armature to match the active animation, for convienence'''
631     bl_idname = "mocap.scale_fix"
632     bl_label = "Scales performer armature to match target armature"
633
634     def execute(self, context):
635         enduser_obj = bpy.context.active_object
636         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
637         mocap_tools.scale_fix_armature(performer_obj, enduser_obj)
638         return {"FINISHED"}
639
640     @classmethod
641     def poll(cls, context):
642         if context.active_object:
643             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
644         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
645         if performer_obj:
646             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
647         else:
648             return False
649
650
651 class MOCAP_OT_AddMocapFix(bpy.types.Operator):
652     '''Add a post-retarget fix - useful for fixing certain artifacts following the retarget'''
653     bl_idname = "mocap.addmocapfix"
654     bl_label = "Add Mocap Fix to target armature"
655     type = bpy.props.EnumProperty(name="Type of Fix",
656     items=[("point", "Maintain Position", "Bone is at a specific point"),
657         ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
658         ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
659         ("distance", "Maintain distance", "Target bones maintained specified distance")],
660     description="Type of fix")
661
662     def execute(self, context):
663         enduser_obj = bpy.context.active_object
664         enduser_arm = enduser_obj.data
665         new_mcon = enduser_arm.mocap_constraints.add()
666         new_mcon.type = self.type
667         return {"FINISHED"}
668
669     @classmethod
670     def poll(cls, context):
671         if context.active_object:
672             return isinstance(context.active_object.data, bpy.types.Armature)
673
674
675 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
676     '''Remove this post-retarget fix'''
677     bl_idname = "mocap.removeconstraint"
678     bl_label = "Removes fixes from target armature"
679     constraint = bpy.props.IntProperty()
680
681     def execute(self, context):
682         enduser_obj = bpy.context.active_object
683         enduser_arm = enduser_obj.data
684         m_constraints = enduser_arm.mocap_constraints
685         m_constraint = m_constraints[self.constraint]
686         if m_constraint.real_constraint:
687             bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
688             cons_obj = getConsObj(bone)
689             removeConstraint(m_constraint, cons_obj)
690         m_constraints.remove(self.constraint)
691         return {"FINISHED"}
692
693     @classmethod
694     def poll(cls, context):
695         if context.active_object:
696             return isinstance(context.active_object.data, bpy.types.Armature)
697
698
699 class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
700     '''Bake all post-retarget fixes to the Retarget Fixes NLA Track'''
701     bl_idname = "mocap.bakeconstraints"
702     bl_label = "Bake all fixes to target armature"
703
704     def execute(self, context):
705         bakeConstraints(context)
706         return {"FINISHED"}
707
708     @classmethod
709     def poll(cls, context):
710         if context.active_object:
711             return isinstance(context.active_object.data, bpy.types.Armature)
712
713
714 class OBJECT_OT_UnbakeMocapConstraints(bpy.types.Operator):
715     '''Unbake all post-retarget fixes - removes the baked data from the Retarget Fixes NLA Track'''
716     bl_idname = "mocap.unbakeconstraints"
717     bl_label = "Unbake all fixes to target armature"
718
719     def execute(self, context):
720         unbakeConstraints(context)
721         return {"FINISHED"}
722
723     @classmethod
724     def poll(cls, context):
725         if context.active_object:
726             return isinstance(context.active_object.data, bpy.types.Armature)
727
728
729 class OBJECT_OT_UpdateMocapConstraints(bpy.types.Operator):
730     '''Updates all post-retarget fixes - needed after changes to armature object or pose'''
731     bl_idname = "mocap.updateconstraints"
732     bl_label = "Updates all fixes to target armature - neccesary to take under consideration changes to armature object or pose"
733
734     def execute(self, context):
735         updateConstraints(context.active_object, context)
736         return {"FINISHED"}
737
738     @classmethod
739     def poll(cls, context):
740         if context.active_object:
741             return isinstance(context.active_object.data, bpy.types.Armature)
742
743
744 class OBJECT_OT_GuessHierachyMapping(bpy.types.Operator):
745     '''Attemps to auto figure out hierarchy mapping'''
746     bl_idname = "mocap.guessmapping"
747     bl_label = "Attemps to auto figure out hierarchy mapping"
748
749     def execute(self, context):
750         enduser_obj = bpy.context.active_object
751         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
752         mocap_tools.guessMapping(performer_obj, enduser_obj)
753         return {"FINISHED"}
754
755     @classmethod
756     def poll(cls, context):
757         if context.active_object:
758             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
759         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
760         if performer_obj:
761             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
762         else:
763             return False
764
765
766 class OBJECT_OT_PathEditing(bpy.types.Operator):
767     '''Sets active object (stride object) to follow the selected curve'''
768     bl_idname = "mocap.pathediting"
769     bl_label = "Sets active object (stride object) to follow the selected curve"
770
771     def execute(self, context):
772         path = [obj for obj in context.selected_objects if obj != context.active_object][0]
773         mocap_tools.path_editing(context, context.active_object, path)
774         return {"FINISHED"}
775
776     @classmethod
777     def poll(cls, context):
778         if context.active_object:
779             selected_objs = [obj for obj in context.selected_objects if obj != context.active_object and isinstance(obj.data, bpy.types.Curve)]
780             return selected_objs
781         else:
782             return False
783
784
785 class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator):
786     '''Stitches two defined animations into a single one via alignment of NLA Tracks'''
787     bl_idname = "mocap.animstitch"
788     bl_label = "Stitches two defined animations into a single one via alignment of NLA Tracks"
789
790     def execute(self, context):
791         mocap_tools.anim_stitch(context, context.active_object)
792         return {"FINISHED"}
793
794     @classmethod
795     def poll(cls, context):
796         activeIsArmature = False
797         if context.active_object:
798             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
799             if activeIsArmature:
800                 stitch_settings = context.active_object.data.stitch_settings
801                 return (stitch_settings.first_action and stitch_settings.second_action)
802         return False
803     
804
805 class OBJECT_OT_GuessAnimationStitchingButton(bpy.types.Operator):
806     '''Guesses the stitch frame and second offset for animation stitch'''
807     bl_idname = "mocap.animstitchguess"
808     bl_label = "Guesses the stitch frame and second offset for animation stitch"
809
810     def execute(self, context):
811         mocap_tools.guess_anim_stitch(context, context.active_object)
812         return {"FINISHED"}
813
814     @classmethod
815     def poll(cls, context):
816         activeIsArmature = False
817         if context.active_object:
818             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
819             if activeIsArmature:
820                 stitch_settings = context.active_object.data.stitch_settings
821                 return (stitch_settings.first_action and stitch_settings.second_action)
822         return False
823
824 def register():
825     bpy.utils.register_module(__name__)
826
827
828 def unregister():
829     bpy.utils.unregister_module(__name__)
830
831 if __name__ == "__main__":
832     register()