Post Retarget fixes - added an Update Constraints button, that recalculates all fixes...
[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 ### reloads modules (for testing purposes only)
29 from imp import reload
30 reload(mocap_constraints)
31 reload(retarget)
32 reload(mocap_tools)
33
34 from mocap_constraints import *
35
36 # MocapConstraint class
37 # Defines MocapConstraint datatype, used to add and configute mocap constraints
38 # Attached to Armature data
39
40
41 class MocapConstraint(bpy.types.PropertyGroup):
42     name = bpy.props.StringProperty(name="Name",
43         default="Mocap Fix",
44         description="Name of Mocap Fix",
45         update=setConstraint)
46     constrained_bone = bpy.props.StringProperty(name="Bone",
47         default="",
48         description="Constrained Bone",
49         update=updateConstraintBoneType)
50     constrained_boneB = bpy.props.StringProperty(name="Bone (2)",
51         default="",
52         description="Other Constrained Bone (optional, depends on type)",
53         update=setConstraint)
54     s_frame = bpy.props.IntProperty(name="S",
55         default=0,
56         description="Start frame of Fix",
57         update=setConstraint)
58     e_frame = bpy.props.IntProperty(name="E",
59         default=100,
60         description="End frame of Fix",
61         update=setConstraint)
62     smooth_in = bpy.props.IntProperty(name="In",
63         default=10,
64         description="Amount of frames to smooth in",
65         update=setConstraint,
66         min=0)
67     smooth_out = bpy.props.IntProperty(name="Out",
68         default=10,
69         description="Amount of frames to smooth out",
70         update=setConstraint,
71         min=0)
72     targetMesh = bpy.props.StringProperty(name="Mesh",
73         default="",
74         description="Target of Fix - Mesh (optional, depends on type)",
75         update=setConstraint)
76     active = bpy.props.BoolProperty(name="Active",
77         default=True,
78         description="Fix is active",
79         update=setConstraint)
80     show_expanded = bpy.props.BoolProperty(name="Show Expanded",
81         default=True,
82         description="Fix is fully shown")
83     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
84         subtype="XYZ", default=(0.0, 0.0, 0.0),
85         description="Target of Fix - Point",
86         update=setConstraint)
87     targetDist = bpy.props.FloatProperty(name="Offset",
88         default=0.0,
89         description="Distance and Floor Fixes - Desired offset",
90         update=setConstraint)
91     targetSpace = bpy.props.EnumProperty(
92         items=[("WORLD", "World Space", "Evaluate target in global space"),
93             ("LOCAL", "Object space", "Evaluate target in object space"),
94             ("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
95         name="Space",
96         description="In which space should Point type target be evaluated",
97         update=setConstraint)
98     type = bpy.props.EnumProperty(name="Type of constraint",
99         items=[("point", "Maintain Position", "Bone is at a specific point"),
100             ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
101             ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
102             ("distance", "Maintain distance", "Target bones maintained specified distance")],
103         description="Type of Fix",
104         update=updateConstraintBoneType)
105     real_constraint = bpy.props.StringProperty()
106     real_constraint_bone = bpy.props.StringProperty()
107
108
109 bpy.utils.register_class(MocapConstraint)
110
111 bpy.types.Armature.mocap_constraints = bpy.props.CollectionProperty(type=MocapConstraint)
112
113 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
114
115
116 def toggleIKBone(self, context):
117     if self.IKRetarget:
118         if not self.is_in_ik_chain:
119             print(self.name + " IK toggled ON!")
120             ik = self.constraints.new('IK')
121             #ik the whole chain up to the root, excluding
122             chainLen = 0
123             for parent_bone in self.parent_recursive:
124                 chainLen += 1
125                 if hasIKConstraint(parent_bone):
126                     break
127                 deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
128                 #~ if len(deformer_children) > 1:
129                     #~ break
130             ik.chain_count = chainLen
131             for bone in self.parent_recursive:
132                 if bone.is_in_ik_chain:
133                     bone.IKRetarget = True
134     else:
135         print(self.name + " IK toggled OFF!")
136         cnstrn_bones = []
137         newChainLength = []
138         if hasIKConstraint(self):
139             cnstrn_bones = [self]
140         elif self.is_in_ik_chain:
141             cnstrn_bones = [child for child in self.children_recursive if hasIKConstraint(child)]
142             for cnstrn_bone in cnstrn_bones:
143                 newChainLength.append(cnstrn_bone.parent_recursive.index(self) + 1)
144         if cnstrn_bones:
145             # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
146             for i, cnstrn_bone in enumerate(cnstrn_bones):
147                 print(cnstrn_bone.name)
148                 if newChainLength:
149                     ik = hasIKConstraint(cnstrn_bone)
150                     ik.chain_count = newChainLength[i]
151                 else:
152                     ik = hasIKConstraint(cnstrn_bone)
153                     cnstrn_bone.constraints.remove(ik)
154                     cnstrn_bone.IKRetarget = False
155             for bone in cnstrn_bone.parent_recursive:
156                 if not bone.is_in_ik_chain:
157                     bone.IKRetarget = False
158
159
160 class MocapMapping(bpy.types.PropertyGroup):
161     name = bpy.props.StringProperty()
162
163 bpy.utils.register_class(MocapMapping)
164
165 bpy.types.Bone.map = bpy.props.StringProperty()
166 bpy.types.Bone.reverseMap = bpy.props.CollectionProperty(type=MocapMapping)
167 bpy.types.Bone.foot = bpy.props.BoolProperty(name="Foot",
168     description="Marks this bone as a 'foot', which determines retargeted animation's translation",
169     default=False)
170 bpy.types.PoseBone.IKRetarget = bpy.props.BoolProperty(name="IK",
171     description="Toggles IK Retargeting method for given bone",
172     update=toggleIKBone, default=False)
173
174
175 def updateIKRetarget():
176     # ensures that Blender constraints and IK properties are in sync
177     # currently runs when module is loaded, should run when scene is loaded
178     # or user adds a constraint to armature. Will be corrected in the future,
179     # once python callbacks are implemented
180     for obj in bpy.data.objects:
181         if obj.pose:
182             bones = obj.pose.bones
183             for pose_bone in bones:
184                 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
185                     pose_bone.IKRetarget = True
186                 else:
187                     pose_bone.IKRetarget = False
188
189 updateIKRetarget()
190
191
192 class MocapPanel(bpy.types.Panel):
193     # Motion capture retargeting panel
194     bl_label = "Mocap tools"
195     bl_space_type = "PROPERTIES"
196     bl_region_type = "WINDOW"
197     bl_context = "object"
198
199     def draw(self, context):
200         self.layout.label("Preprocessing")
201         row = self.layout.row(align=True)
202         row.alignment = 'EXPAND'
203         row.operator("mocap.samples", text='Samples to Beziers')
204         row.operator("mocap.denoise", text='Clean noise')
205         row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
206         row.operator("mocap.scale_fix", text='Auto scale Performer')
207         row2 = self.layout.row(align=True)
208         row2.operator("mocap.looper", text='Loop animation')
209         row2.operator("mocap.limitdof", text='Constrain Rig')
210         row2.operator("mocap.removelimitdof", text='Unconstrain Rig')
211         self.layout.label("Retargeting")
212         enduser_obj = bpy.context.active_object
213         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
214         if enduser_obj is None or len(performer_obj) != 1:
215             self.layout.label("Select performer rig and target rig (as active)")
216         else:
217             self.layout.operator("mocap.guessmapping", text="Guess Hiearchy Mapping")
218             row3 = self.layout.row(align=True)
219             column1 = row3.column(align=True)
220             column1.label("Performer Rig")
221             column2 = row3.column(align=True)
222             column2.label("Enduser Rig")
223             performer_obj = performer_obj[0]
224             if performer_obj.data and enduser_obj.data:
225                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
226                     perf = performer_obj.data
227                     enduser_arm = enduser_obj.data
228                     perf_pose_bones = enduser_obj.pose.bones
229                     for bone in perf.bones:
230                         row = self.layout.row()
231                         row.prop(data=bone, property='foot', text='', icon='POSE_DATA')
232                         row.label(bone.name)
233                         row.prop_search(bone, "map", enduser_arm, "bones")
234                         label_mod = "FK"
235                         if bone.map:
236                             pose_bone = perf_pose_bones[bone.map]
237                             if pose_bone.is_in_ik_chain:
238                                 label_mod = "ik chain"
239                             if hasIKConstraint(pose_bone):
240                                 label_mod = "ik end"
241                             row.prop(pose_bone, 'IKRetarget')
242                             row.label(label_mod)
243                         else:
244                             row.label(" ")
245                             row.label(" ")
246                     mapRow = self.layout.row()
247                     mapRow.operator("mocap.savemapping", text='Save mapping')
248                     mapRow.operator("mocap.loadmapping", text='Load mapping')
249                     self.layout.operator("mocap.retarget", text='RETARGET!')
250
251
252 class MocapConstraintsPanel(bpy.types.Panel):
253     #Motion capture constraints panel
254     bl_label = "Mocap Fixes"
255     bl_space_type = "PROPERTIES"
256     bl_region_type = "WINDOW"
257     bl_context = "object"
258
259     def draw(self, context):
260         layout = self.layout
261         if context.active_object:
262             if context.active_object.data:
263                 if context.active_object.data.name in bpy.data.armatures:
264                     enduser_obj = context.active_object
265                     enduser_arm = enduser_obj.data
266                     layout.operator_menu_enum("mocap.addmocapfix", "type")
267                     layout.operator("mocap.updateconstraints", text='Update Fixes')
268                     bakeRow = layout.row()
269                     bakeRow.operator("mocap.bakeconstraints", text='Bake Fixes')
270                     bakeRow.operator("mocap.unbakeconstraints", text='Unbake Fixes')
271                     layout.separator()
272                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
273                         box = layout.box()
274                         headerRow = box.row()
275                         headerRow.prop(m_constraint, 'show_expanded', text='', icon='TRIA_DOWN' if m_constraint.show_expanded else 'TRIA_RIGHT', emboss=False)
276                         headerRow.prop(m_constraint, 'type', text='')
277                         headerRow.prop(m_constraint, 'name', text='')
278                         headerRow.prop(m_constraint, 'active', icon='MUTE_IPO_ON' if not m_constraint.active else'MUTE_IPO_OFF', text='', emboss=False)
279                         headerRow.operator("mocap.removeconstraint", text="", icon='X', emboss=False).constraint = i
280                         if m_constraint.show_expanded:
281                             box.separator()
282                             box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones", icon='BONE_DATA')
283                             if m_constraint.type == "distance" or m_constraint.type == "point":
284                                 box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones", icon='CONSTRAINT_BONE')
285                             frameRow = box.row()
286                             frameRow.label("Frame Range:")
287                             frameRow.prop(m_constraint, 's_frame')
288                             frameRow.prop(m_constraint, 'e_frame')
289                             smoothRow = box.row()
290                             smoothRow.label("Smoothing:")
291                             smoothRow.prop(m_constraint, 'smooth_in')
292                             smoothRow.prop(m_constraint, 'smooth_out')
293                             targetRow = box.row()
294                             targetLabelCol = targetRow.column()
295                             targetLabelCol.label("Target settings:")
296                             targetPropCol = targetRow.column()
297                             if m_constraint.type == "floor":
298                                 targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
299                             if m_constraint.type == "point" or m_constraint.type == "freeze":
300                                 box.prop(m_constraint, 'targetSpace')
301                             if m_constraint.type == "point":
302                                 targetPropCol.prop(m_constraint, 'targetPoint')
303                             if m_constraint.type == "distance" or m_constraint.type == "floor":
304                                 targetPropCol.prop(m_constraint, 'targetDist')
305                             layout.separator()
306
307
308 class ExtraToolsPanel(bpy.types.Panel):
309     # Motion capture retargeting panel
310     bl_label = "Extra Mocap Tools"
311     bl_space_type = "PROPERTIES"
312     bl_region_type = "WINDOW"
313     bl_context = "object"
314
315     def draw(self, context):
316         layout = self.layout
317         layout.operator('mocap.pathediting', text="Follow Path")
318
319
320 class OBJECT_OT_RetargetButton(bpy.types.Operator):
321     '''Retarget animation from selected armature to active armature '''
322     bl_idname = "mocap.retarget"
323     bl_label = "Retargets active action from Performer to Enduser"
324
325     def execute(self, context):
326         enduser_obj = context.active_object
327         performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
328         if enduser_obj is None or len(performer_obj) != 1:
329             print("Need active and selected armatures")
330         else:
331             performer_obj = performer_obj[0]
332         scene = context.scene
333         s_frame = scene.frame_start
334         e_frame = scene.frame_end
335         retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
336         return {"FINISHED"}
337
338     @classmethod
339     def poll(cls, context):
340         if context.active_object:
341             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
342         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
343         if performer_obj:
344             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
345         else:
346             return False
347
348
349 class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
350     '''Save mapping to active armature (for future retargets) '''
351     bl_idname = "mocap.savemapping"
352     bl_label = "Saves user generated mapping from Performer to Enduser"
353
354     def execute(self, context):
355         enduser_obj = bpy.context.active_object
356         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
357         retarget.createDictionary(performer_obj.data, enduser_obj.data)
358         return {"FINISHED"}
359
360     @classmethod
361     def poll(cls, context):
362         if context.active_object:
363             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
364         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
365         if performer_obj:
366             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
367         else:
368             return False
369
370
371 class OBJECT_OT_LoadMappingButton(bpy.types.Operator):
372     '''Load saved mapping from active armature'''
373     bl_idname = "mocap.loadmapping"
374     bl_label = "Loads user generated mapping from Performer to Enduser"
375
376     def execute(self, context):
377         enduser_obj = bpy.context.active_object
378         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
379         retarget.loadMapping(performer_obj.data, enduser_obj.data)
380         return {"FINISHED"}
381
382     @classmethod
383     def poll(cls, context):
384         if context.active_object:
385             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
386         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
387         if performer_obj:
388             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
389         else:
390             return False
391
392
393 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
394     '''Convert active armature's sampled keyframed to beziers'''
395     bl_idname = "mocap.samples"
396     bl_label = "Converts samples / simplifies keyframes to beziers"
397
398     def execute(self, context):
399         mocap_tools.fcurves_simplify(context, context.active_object)
400         return {"FINISHED"}
401
402     @classmethod
403     def poll(cls, context):
404         return context.active_object.animation_data
405
406
407 class OBJECT_OT_LooperButton(bpy.types.Operator):
408     '''Trim active armature's animation to a single cycle, given a cyclic animation (such as a walk cycle)'''
409     bl_idname = "mocap.looper"
410     bl_label = "loops animation / sampled mocap data"
411
412     def execute(self, context):
413         mocap_tools.autoloop_anim()
414         return {"FINISHED"}
415
416     @classmethod
417     def poll(cls, context):
418         return context.active_object.animation_data
419
420
421 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
422     '''Denoise active armature's animation. Good for dealing with 'bad' frames inherent in mocap animation'''
423     bl_idname = "mocap.denoise"
424     bl_label = "Denoises sampled mocap data "
425
426     def execute(self, context):
427         mocap_tools.denoise_median()
428         return {"FINISHED"}
429
430     @classmethod
431     def poll(cls, context):
432         return context.active_object
433
434     @classmethod
435     def poll(cls, context):
436         return context.active_object.animation_data
437
438
439 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
440     '''Create limit constraints on the active armature from the selected armature's animation's range of motion'''
441     bl_idname = "mocap.limitdof"
442     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
443
444     def execute(self, context):
445         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object][0]
446         mocap_tools.limit_dof(context, performer_obj, context.active_object)
447         return {"FINISHED"}
448
449     @classmethod
450     def poll(cls, context):
451         if context.active_object:
452             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
453         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
454         if performer_obj:
455             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
456         else:
457             return False
458
459
460 class OBJECT_OT_RemoveLimitDOFButton(bpy.types.Operator):
461     '''Removes previously created limit constraints on the active armature'''
462     bl_idname = "mocap.removelimitdof"
463     bl_label = "Removes previously created limit constraints on the active armature"
464
465     def execute(self, context):
466         mocap_tools.limit_dof_toggle_off(context, context.active_object)
467         return {"FINISHED"}
468
469     @classmethod
470     def poll(cls, context):
471         activeIsArmature = False
472         if context.active_object:
473             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
474         return activeIsArmature
475
476
477 class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
478     '''Realign the active armature's axis system to match Blender (Commonly needed after bvh import)'''
479     bl_idname = "mocap.rotate_fix"
480     bl_label = "Rotates selected armature 90 degrees (fix for bvh import)"
481
482     def execute(self, context):
483         mocap_tools.rotate_fix_armature(context.active_object.data)
484         return {"FINISHED"}
485
486     @classmethod
487     def poll(cls, context):
488         if context.active_object:
489             return isinstance(context.active_object.data, bpy.types.Armature)
490
491
492 class OBJECT_OT_ScaleFixArmature(bpy.types.Operator):
493     '''Rescale selected armature to match the active animation, for convienence'''
494     bl_idname = "mocap.scale_fix"
495     bl_label = "Scales performer armature to match target armature"
496
497     def execute(self, context):
498         enduser_obj = bpy.context.active_object
499         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
500         mocap_tools.scale_fix_armature(performer_obj, enduser_obj)
501         return {"FINISHED"}
502
503     @classmethod
504     def poll(cls, context):
505         if context.active_object:
506             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
507         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
508         if performer_obj:
509             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
510         else:
511             return False
512
513
514 class MOCAP_OT_AddMocapFix(bpy.types.Operator):
515     '''Add a post-retarget fix - useful for fixing certain artifacts following the retarget'''
516     bl_idname = "mocap.addmocapfix"
517     bl_label = "Add Mocap Fix to target armature"
518     type = bpy.props.EnumProperty(name="Type of Fix",
519     items=[("point", "Maintain Position", "Bone is at a specific point"),
520         ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
521         ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
522         ("distance", "Maintain distance", "Target bones maintained specified distance")],
523     description="Type of fix")
524
525     def execute(self, context):
526         enduser_obj = bpy.context.active_object
527         enduser_arm = enduser_obj.data
528         new_mcon = enduser_arm.mocap_constraints.add()
529         new_mcon.type = self.type
530         return {"FINISHED"}
531
532     @classmethod
533     def poll(cls, context):
534         if context.active_object:
535             return isinstance(context.active_object.data, bpy.types.Armature)
536
537
538 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
539     '''Remove this post-retarget fix'''
540     bl_idname = "mocap.removeconstraint"
541     bl_label = "Removes fixes from target armature"
542     constraint = bpy.props.IntProperty()
543
544     def execute(self, context):
545         enduser_obj = bpy.context.active_object
546         enduser_arm = enduser_obj.data
547         m_constraints = enduser_arm.mocap_constraints
548         m_constraint = m_constraints[self.constraint]
549         if m_constraint.real_constraint:
550             bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
551             cons_obj = getConsObj(bone)
552             removeConstraint(m_constraint, cons_obj)
553         m_constraints.remove(self.constraint)
554         return {"FINISHED"}
555
556     @classmethod
557     def poll(cls, context):
558         if context.active_object:
559             return isinstance(context.active_object.data, bpy.types.Armature)
560
561
562 class OBJECT_OT_BakeMocapConstraints(bpy.types.Operator):
563     '''Bake all post-retarget fixes to the Retarget Fixes NLA Track'''
564     bl_idname = "mocap.bakeconstraints"
565     bl_label = "Bake all fixes to target armature"
566
567     def execute(self, context):
568         bakeConstraints(context)
569         return {"FINISHED"}
570
571     @classmethod
572     def poll(cls, context):
573         if context.active_object:
574             return isinstance(context.active_object.data, bpy.types.Armature)
575
576
577 class OBJECT_OT_UnbakeMocapConstraints(bpy.types.Operator):
578     '''Unbake all post-retarget fixes - removes the baked data from the Retarget Fixes NLA Track'''
579     bl_idname = "mocap.unbakeconstraints"
580     bl_label = "Unbake all fixes to target armature"
581
582     def execute(self, context):
583         unbakeConstraints(context)
584         return {"FINISHED"}
585
586     @classmethod
587     def poll(cls, context):
588         if context.active_object:
589             return isinstance(context.active_object.data, bpy.types.Armature)
590
591
592 class OBJECT_OT_UpdateMocapConstraints(bpy.types.Operator):
593     '''Updates all post-retarget fixes - needed after changes to armature object or pose'''
594     bl_idname = "mocap.updateconstraints"
595     bl_label = "Updates all fixes to target armature - neccesary to take under consideration changes to armature object or pose"
596
597     def execute(self, context):
598         updateConstraints(context.active_object, context)
599         return {"FINISHED"}
600
601     @classmethod
602     def poll(cls, context):
603         if context.active_object:
604             return isinstance(context.active_object.data, bpy.types.Armature)
605
606
607 class OBJECT_OT_GuessHierachyMapping(bpy.types.Operator):
608     '''Attemps to auto figure out hierarchy mapping'''
609     bl_idname = "mocap.guessmapping"
610     bl_label = "Attemps to auto figure out hierarchy mapping"
611
612     def execute(self, context):
613         enduser_obj = bpy.context.active_object
614         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
615         mocap_tools.guessMapping(performer_obj, enduser_obj)
616         return {"FINISHED"}
617
618     @classmethod
619     def poll(cls, context):
620         if context.active_object:
621             activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
622         performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
623         if performer_obj:
624             return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
625         else:
626             return False
627
628
629 class OBJECT_OT_PathEditing(bpy.types.Operator):
630     '''Sets active object (stride object) to follow the selected curve'''
631     bl_idname = "mocap.pathediting"
632     bl_label = "Sets active object (stride object) to follow the selected curve"
633
634     def execute(self, context):
635         path = [obj for obj in context.selected_objects if obj != context.active_object][0]
636         mocap_tools.path_editing(context, context.active_object, path)
637         return {"FINISHED"}
638
639     @classmethod
640     def poll(cls, context):
641         if context.active_object:
642             selected_objs = [obj for obj in context.selected_objects if obj != context.active_object and isinstance(obj.data, bpy.types.Curve)]
643             return selected_objs
644         else:
645             return False
646
647
648 def register():
649     bpy.utils.register_module(__name__)
650
651
652 def unregister():
653     bpy.utils.unregister_module(__name__)
654
655 if __name__ == "__main__":
656     register()