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