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