Extensions: change the constant for the complete status
[blender-addons-contrib.git] / mocap / __init__.py
blobfecbf8c55ab3ead5416c3ac16b7e29cd4ab148ab
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 bl_info = {
20 "name": "Motion Capture Tools",
21 "author": "Benjy Cook",
22 "blender": (2, 80, 0),
23 "version": (1, 1, 2),
24 "location": "Active Armature > Object Properties > Mocap Tools",
25 "description": "Various tools for working with motion capture animation",
26 "warning": "",
27 "doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Animation/Motion_Capture_Tools",
29 "support": 'OFFICIAL',
30 "category": "Animation",
33 if "bpy" in locals():
34 import importlib
35 if "mocap_constraints" in locals():
36 importlib.reload(mocap_constraints)
37 if "retarget" in locals():
38 importlib.reload(retarget)
39 if "mocap_tools" in locals():
40 importlib.reload(mocap_tools)
41 else:
42 import bpy
43 from bpy.props import (
44 BoolProperty,
45 CollectionProperty,
46 EnumProperty,
47 FloatProperty,
48 FloatVectorProperty,
49 IntProperty,
50 PointerProperty,
51 StringProperty,
53 from . import (
54 mocap_constraints,
55 retarget,
56 mocap_tools,
60 # MocapConstraint class
61 # Defines MocapConstraint datatype, used to add and configute mocap constraints
62 # Attached to Armature data
64 def hasIKConstraint(pose_bone):
65 #utility function / predicate, returns True if given bone has IK constraint
66 ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
67 if ik:
68 return ik[0]
69 else:
70 return False
73 class MocapConstraint(bpy.types.PropertyGroup):
74 name: StringProperty(name="Name",
75 default="Mocap Fix",
76 description="Name of Mocap Fix",
77 update=mocap_constraints.setConstraint)
78 constrained_bone: StringProperty(name="Bone",
79 default="",
80 description="Constrained Bone",
81 update=mocap_constraints.updateConstraintBoneType)
82 constrained_boneB: StringProperty(name="Bone (2)",
83 default="",
84 description="Other Constrained Bone (optional, depends on type)",
85 update=mocap_constraints.setConstraint)
86 s_frame: IntProperty(name="Start",
87 default=0,
88 description="Start frame of Fix",
89 update=mocap_constraints.setConstraint)
90 e_frame: IntProperty(name="End",
91 default=100,
92 description="End frame of Fix",
93 update=mocap_constraints.setConstraint)
94 smooth_in: IntProperty(name="In",
95 default=10,
96 description="Number of frames to smooth in",
97 update=mocap_constraints.setConstraint,
98 min=0)
99 smooth_out: IntProperty(name="Out",
100 default=10,
101 description="Number of frames to smooth out",
102 update=mocap_constraints.setConstraint,
103 min=0)
104 targetMesh: StringProperty(name="Mesh",
105 default="",
106 description="Target of Fix - Mesh (optional, depends on type)",
107 update=mocap_constraints.setConstraint)
108 active: BoolProperty(name="Active",
109 default=True,
110 description="Fix is active",
111 update=mocap_constraints.setConstraint)
112 show_expanded: BoolProperty(name="Show Expanded",
113 default=True,
114 description="Fix is fully shown")
115 targetPoint: FloatVectorProperty(name="Point", size=3,
116 subtype="XYZ", default=(0.0, 0.0, 0.0),
117 description="Target of Fix - Point",
118 update=mocap_constraints.setConstraint)
119 targetDist: FloatProperty(name="Offset",
120 default=0.0,
121 description="Distance and Floor Fixes - Desired offset",
122 update=mocap_constraints.setConstraint)
123 targetSpace: EnumProperty(
124 items=[("WORLD", "World Space", "Evaluate target in global space"),
125 ("LOCAL", "Object space", "Evaluate target in object space"),
126 ("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
127 name="Space",
128 description="In which space should Point type target be evaluated",
129 update=mocap_constraints.setConstraint)
130 type: EnumProperty(name="Type of constraint",
131 items=[("point", "Maintain Position", "Bone is at a specific point"),
132 ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
133 ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
134 ("distance", "Maintain distance", "Target bones maintained specified distance")],
135 description="Type of Fix",
136 update=mocap_constraints.updateConstraintBoneType)
137 real_constraint: StringProperty()
138 real_constraint_bone: StringProperty()
141 # Animation Stitch Settings, used for animation stitching of 2 retargeted animations.
142 class AnimationStitchSettings(bpy.types.PropertyGroup):
143 first_action: StringProperty(name="Action 1",
144 description="First action in stitch")
145 second_action: StringProperty(name="Action 2",
146 description="Second action in stitch")
147 blend_frame: IntProperty(name="Stitch frame",
148 description="Frame to locate stitch on")
149 blend_amount: IntProperty(name="Blend amount",
150 description="Size of blending transition, on both sides of the stitch",
151 default=10)
152 second_offset: IntProperty(name="Second offset",
153 description="Frame offset for 2nd animation, where it should start",
154 default=10)
155 stick_bone: StringProperty(name="Stick Bone",
156 description="Bone to freeze during transition",
157 default="")
160 # MocapNLA Tracks. Stores which tracks/actions are associated with each retargeted animation.
161 class MocapNLATracks(bpy.types.PropertyGroup):
162 name: StringProperty()
163 base_track: StringProperty()
164 auto_fix_track: StringProperty()
165 manual_fix_track: StringProperty()
166 stride_action: StringProperty()
169 #Update function for Advanced Retarget boolean variable.
170 def advancedRetargetToggle(self, context):
171 enduser_obj = context.active_object
172 performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
173 if enduser_obj is None or len(performer_obj) != 1:
174 print("Need active and selected armatures")
175 return
176 else:
177 performer_obj = performer_obj[0]
178 if self.advancedRetarget:
179 retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
180 else:
181 retarget.cleanTempConstraints(enduser_obj)
184 def toggleIKBone(self, context):
185 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
186 if self.IKRetarget:
187 if not self.is_in_ik_chain:
188 print(self.name + " IK toggled ON!")
189 ik = self.constraints.new('IK')
190 #ik the whole chain up to the root, excluding
191 chainLen = 0
192 for parent_bone in self.parent_recursive:
193 chainLen += 1
194 if hasIKConstraint(parent_bone):
195 break
196 #~ deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
197 #~ if len(deformer_children) > 1:
198 #~ break
199 ik.chain_count = chainLen
200 for bone in self.parent_recursive:
201 if bone.is_in_ik_chain:
202 bone.IKRetarget = True
203 else:
204 print(self.name + " IK toggled OFF!")
205 cnstrn_bones = []
206 newChainLength = []
207 if hasIKConstraint(self):
208 cnstrn_bones = [self]
209 elif self.is_in_ik_chain:
210 cnstrn_bones = [child for child in self.children_recursive if hasIKConstraint(child)]
211 for cnstrn_bone in cnstrn_bones:
212 newChainLength.append(cnstrn_bone.parent_recursive.index(self) + 1)
213 if cnstrn_bones:
214 # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
215 for i, cnstrn_bone in enumerate(cnstrn_bones):
216 print(cnstrn_bone.name)
217 if newChainLength:
218 ik = hasIKConstraint(cnstrn_bone)
219 ik.chain_count = newChainLength[i]
220 else:
221 ik = hasIKConstraint(cnstrn_bone)
222 cnstrn_bone.constraints.remove(ik)
223 cnstrn_bone.IKRetarget = False
224 for bone in cnstrn_bone.parent_recursive:
225 if not bone.is_in_ik_chain:
226 bone.IKRetarget = False
229 #MocapMap class for storing mapping on enduser performer,
230 # where a bone may be linked to more than one on the performer
231 class MocapMapping(bpy.types.PropertyGroup):
232 name: StringProperty()
234 # Disabling for now [#28933] - campbell
236 def updateIKRetarget():
237 # ensures that Blender constraints and IK properties are in sync
238 # currently runs when module is loaded, should run when scene is loaded
239 # or user adds a constraint to armature. Will be corrected in the future,
240 # once python callbacks are implemented
241 for obj in bpy.data.objects:
242 if obj.pose:
243 bones = obj.pose.bones
244 for pose_bone in bones:
245 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
246 pose_bone.IKRetarget = True
247 else:
248 pose_bone.IKRetarget = False
250 updateIKRetarget()
254 def hasIKConstraint(pose_bone):
255 #utility function / predicate, returns True if given bone has IK constraint
256 ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
257 if ik:
258 return ik[0]
259 else:
260 return False
263 class MocapPanel(bpy.types.Panel):
264 # Motion capture retargeting panel
265 bl_idname = 'MOCAP_PT_Tools'
266 bl_label = "Mocap Tools"
267 bl_space_type = "PROPERTIES"
268 bl_region_type = "WINDOW"
269 bl_context = "object"
270 bl_options = {'DEFAULT_CLOSED'}
272 @classmethod
273 def poll(cls, context):
274 obj = context.object
275 return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
276 'POSE',
277 'OBJECT'}
279 def draw(self, context):
280 layout = self.layout
282 layout.label(text="Preprocessing:")
284 row = layout.row(align=True)
285 row.operator("mocap.denoise", text='Clean noise')
286 row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
287 row.operator("mocap.scale_fix", text='Auto scale Performer')
289 row = layout.row(align=True)
290 row.operator("mocap.looper", text='Loop animation')
291 row.operator("mocap.limitdof", text='Constrain Rig')
292 row.operator("mocap.removelimitdof", text='Unconstrain Rig')
294 layout.label(text="Retargeting:")
295 enduser_obj = bpy.context.active_object
296 performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
297 if enduser_obj is None or len(performer_obj) != 1:
298 layout.label(text="Select performer rig and target rig (as active)")
299 else:
300 layout.operator("mocap.guessmapping", text="Guess Hierarchy Mapping")
301 labelRow = layout.row(align=True)
302 labelRow.label(text="Performer Rig")
303 labelRow.label(text="End user Rig")
304 performer_obj = performer_obj[0]
305 if performer_obj.data and enduser_obj.data:
306 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
307 perf = performer_obj.data
308 perf_root = performer_obj.pose.bones[0].name
309 enduser_arm = enduser_obj.data
310 perf_pose_bones = enduser_obj.pose.bones
311 MappingRow = layout.row(align=True)
312 footCol = MappingRow.column(align=True)
313 nameCol = MappingRow.column(align=True)
314 nameCol.scale_x = 2
315 mapCol = MappingRow.column(align=True)
316 mapCol.scale_x = 2
317 selectCol = MappingRow.column(align=True)
318 twistCol = MappingRow.column(align=True)
319 twistCol.scale_x = 0.3
320 spaceCol = MappingRow.column(align=True)
321 spaceCol.scale_x = 0.3
322 IKCol = MappingRow.column(align=True)
323 IKCol.scale_x = 0.5
324 IKLabel = MappingRow.column(align=True)
325 IKLabel.scale_x = 0.5
326 for bone in perf.bones:
327 footCol.prop(data=bone, property='foot', text='', icon='CON_FLOOR')
328 nameCol.label(text=bone.name)
329 mapCol.prop_search(bone, "map", enduser_arm, "bones", text='')
330 selectCol.operator("mocap.selectmap", text='', icon='CURSOR').perf_bone = bone.name
331 label_mod = "FK"
332 if bone.map:
333 pose_bone = perf_pose_bones[bone.map]
334 if pose_bone.is_in_ik_chain:
335 label_mod = "ik chain"
336 if hasIKConstraint(pose_bone):
337 label_mod = "ik end"
338 end_bone = enduser_obj.data.bones[bone.map]
339 twistCol.prop(data=end_bone, property='twistFix', text='', icon='RNA')
340 #if (not end_bone.use_connect) and (pose_bone.name != perf_root):
341 if pose_bone.name != perf_root: # use_connect dont return correct value...
342 spaceCol.prop(data=end_bone, property='poseSpace', text='', icon='POSE_HLT')
343 else:
344 spaceCol.label(text=" ")
345 IKCol.prop(pose_bone, 'IKRetarget')
346 IKLabel.label(text=label_mod)
347 else:
348 twistCol.label(text=" ")
349 spaceCol.label(text=" ")
350 IKCol.label(text=" ")
351 IKLabel.label(text=" ")
352 mapRow = layout.row()
353 mapRow.operator("mocap.savemapping", text='Save mapping')
354 mapRow.operator("mocap.loadmapping", text='Load mapping')
355 extraSettings = self.layout.box()
356 if performer_obj.animation_data and performer_obj.animation_data.action:
357 extraSettings.prop(data=performer_obj.animation_data.action, property='name', text='Action Name')
358 extraSettings.prop(enduser_arm, "frameStep")
359 extraSettings.prop(enduser_arm, "advancedRetarget", text='Advanced Retarget')
360 layout.operator("mocap.retarget", text='RETARGET!')
363 class MocapConstraintsPanel(bpy.types.Panel):
364 #Motion capture constraints panel
365 bl_idname = 'MOCAP_PT_Constraints'
366 bl_label = "Mocap Fixes"
367 bl_space_type = "PROPERTIES"
368 bl_region_type = "WINDOW"
369 bl_context = "object"
370 bl_options = {'DEFAULT_CLOSED'}
372 @classmethod
373 def poll(cls, context):
374 obj = context.object
375 return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
376 'POSE',
377 'OBJECT'}
379 def draw(self, context):
380 layout = self.layout
381 if context.active_object:
382 if context.active_object.data:
383 if context.active_object.data.name in bpy.data.armatures:
384 enduser_obj = context.active_object
385 enduser_arm = enduser_obj.data
386 layout.operator_menu_enum("mocap.addmocapfix", "type")
387 layout.operator("mocap.updateconstraints", text='Update Fixes')
388 bakeRow = layout.row()
389 bakeRow.operator("mocap.bakeconstraints", text='Bake Fixes')
390 bakeRow.operator("mocap.unbakeconstraints", text='Unbake Fixes')
391 layout.separator()
392 for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
393 box = layout.box()
394 headerRow = box.row()
395 headerRow.prop(m_constraint, 'show_expanded', text='', icon='TRIA_DOWN' if m_constraint.show_expanded else 'TRIA_RIGHT', emboss=False)
396 headerRow.prop(m_constraint, 'type', text='')
397 headerRow.prop(m_constraint, 'name', text='')
398 headerRow.prop(m_constraint, 'active', icon='MUTE_IPO_ON' if not m_constraint.active else'MUTE_IPO_OFF', text='', emboss=False)
399 headerRow.operator("mocap.removeconstraint", text="", icon='X', emboss=False).constraint = i
400 if m_constraint.show_expanded:
401 box.separator()
402 box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones", icon='BONE_DATA', text="")
403 if m_constraint.type == "distance" or m_constraint.type == "point":
404 box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones", icon='CONSTRAINT_BONE')
405 frameRow = box.row()
406 frameRow.label(text="Frame Range:")
407 frameRow.prop(m_constraint, 's_frame')
408 frameRow.prop(m_constraint, 'e_frame')
409 smoothRow = box.row()
410 smoothRow.label(text="Smoothing:")
411 smoothRow.prop(m_constraint, 'smooth_in')
412 smoothRow.prop(m_constraint, 'smooth_out')
413 targetRow = box.row()
414 targetLabelCol = targetRow.column()
415 targetLabelCol.label(text="Target settings:")
416 targetPropCol = targetRow.column()
417 if m_constraint.type == "floor":
418 targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
419 if m_constraint.type == "point" or m_constraint.type == "freeze":
420 box.prop(m_constraint, 'targetSpace')
421 if m_constraint.type == "point":
422 targetPropCol.prop(m_constraint, 'targetPoint')
423 if m_constraint.type == "distance" or m_constraint.type == "floor":
424 targetPropCol.prop(m_constraint, 'targetDist')
425 layout.separator()
428 class ExtraToolsPanel(bpy.types.Panel):
429 # Motion capture retargeting panel
430 bl_idname = 'MOCAP_PT_ExtraTools'
431 bl_label = "Extra Mocap Tools"
432 bl_space_type = "PROPERTIES"
433 bl_region_type = "WINDOW"
434 bl_context = "object"
435 bl_options = {'DEFAULT_CLOSED'}
437 @classmethod
438 def poll(cls, context):
439 obj = context.object
440 return obj.type == 'ARMATURE' and context.active_object is not None and context.mode in {'EDIT_ARMATURE',
441 'POSE',
442 'OBJECT'}
443 def draw(self, context):
444 layout = self.layout
445 layout.operator("mocap.samples", text='Samples to Beziers')
446 layout.operator('mocap.pathediting', text="Follow Path")
447 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
448 if activeIsArmature:
449 enduser_arm = context.active_object.data
450 selectBox = layout.box()
451 selectRetargets = selectBox.row()
452 selectRetargets.label(text="Retargeted Animations:")
453 selectRetargets.prop_search(enduser_arm, "active_mocap", enduser_arm, "mocapNLATracks")
454 stitchBox = layout.box()
455 stitchBox.label(text="Animation Stitching")
456 settings = enduser_arm.stitch_settings
457 stitchBox.prop_search(settings, "first_action", enduser_arm, "mocapNLATracks")
458 stitchBox.prop_search(settings, "second_action", enduser_arm, "mocapNLATracks")
459 stitchSettings = stitchBox.row()
460 stitchSettings.prop(settings, "blend_frame")
461 stitchSettings.prop(settings, "blend_amount")
462 stitchSettings.prop(settings, "second_offset")
463 stitchBox.prop_search(settings, "stick_bone", context.active_object.pose, "bones")
464 stitchBox.operator('mocap.animstitchguess', text="Guess Settings")
465 stitchBox.operator('mocap.animstitch', text="Stitch Animations")
468 class RetargetOperator(bpy.types.Operator):
469 #Retargeting operator. Assumes selected and active armatures, where the performer (the selected one)
470 # has an action for retargeting
471 """Retarget animation from selected armature to active armature"""
472 bl_idname = "mocap.retarget"
473 bl_label = "Retarget"
474 bl_options = {'REGISTER', 'UNDO'}
476 def execute(self, context):
477 scene = context.scene
478 s_frame = scene.frame_start
479 e_frame = scene.frame_end
480 enduser_obj = context.active_object
481 performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
482 if enduser_obj is None or len(performer_obj) != 1:
483 print("Need active and selected armatures")
484 else:
485 performer_obj = performer_obj[0]
486 s_frame, e_frame = performer_obj.animation_data.action.frame_range
487 s_frame = int(s_frame)
488 e_frame = int(e_frame)
489 if retarget.isRigAdvanced(enduser_obj) and not enduser_obj.data.advancedRetarget:
490 print("Recommended to use Advanced Retargeting method")
491 enduser_obj.data.advancedRetarget = True
492 else:
493 retarget.totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame)
494 return {'FINISHED'}
496 @classmethod
497 def poll(cls, context):
498 if context.active_object:
499 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
500 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
501 if performer_obj:
502 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature) and performer_obj[0].animation_data
503 else:
504 return False
507 class SaveMappingOperator(bpy.types.Operator):
508 #Operator for saving mapping to enduser armature
509 """Save mapping to active armature (for future retargets)"""
510 bl_idname = "mocap.savemapping"
511 bl_label = "Save Mapping"
513 def execute(self, context):
514 enduser_obj = bpy.context.active_object
515 performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
516 retarget.createDictionary(performer_obj.data, enduser_obj.data)
517 return {'FINISHED'}
519 @classmethod
520 def poll(cls, context):
521 if context.active_object:
522 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
523 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
524 if performer_obj:
525 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
526 else:
527 return False
530 class LoadMappingOperator(bpy.types.Operator):
531 """Load saved mapping from active armature"""
532 #Operator for loading mapping to enduser armature
533 bl_idname = "mocap.loadmapping"
534 bl_label = "Load Mapping"
536 def execute(self, context):
537 enduser_obj = bpy.context.active_object
538 performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
539 retarget.loadMapping(performer_obj.data, enduser_obj.data)
540 return {'FINISHED'}
542 @classmethod
543 def poll(cls, context):
544 if context.active_object:
545 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
546 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
547 if performer_obj:
548 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
549 else:
550 return False
553 class SelectMapBoneOperator(bpy.types.Operator):
554 #Operator for setting selected bone in enduser armature to the performer mapping
555 """Select a bone for faster mapping"""
556 bl_idname = "mocap.selectmap"
557 bl_label = "Select Mapping Bone"
558 perf_bone: StringProperty()
560 def execute(self, context):
561 enduser_obj = bpy.context.active_object
562 performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
563 selectedBone = ""
564 for bone in enduser_obj.data.bones:
565 boneVis = bone.layers
566 for i in range(32):
567 if boneVis[i] and enduser_obj.data.layers[i]:
568 if bone.select:
569 selectedBone = bone.name
570 break
571 performer_obj.data.bones[self.perf_bone].map = selectedBone
572 return {'FINISHED'}
574 @classmethod
575 def poll(cls, context):
576 if context.active_object:
577 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
578 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
579 if performer_obj:
580 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
581 else:
582 return False
585 class ConvertSamplesOperator(bpy.types.Operator):
586 #Operator to convert samples to beziers on the selected object
587 """Convert active armature's sampled keyframed to beziers"""
588 bl_idname = "mocap.samples"
589 bl_label = "Convert Samples"
591 def execute(self, context):
592 mocap_tools.fcurves_simplify(context, context.active_object)
593 return {'FINISHED'}
595 @classmethod
596 def poll(cls, context):
597 return context.active_object and context.active_object.animation_data
600 class LooperOperator(bpy.types.Operator):
601 #Operator to trim fcurves which contain a few loops to a single one on the selected object
602 """Trim active armature's animation to a single cycle, given """ \
603 """a cyclic animation (such as a walk cycle)"""
604 bl_idname = "mocap.looper"
605 bl_label = "Loop Mocap"
607 def execute(self, context):
608 mocap_tools.autoloop_anim()
609 return {'FINISHED'}
611 @classmethod
612 def poll(cls, context):
613 return context.active_object and context.active_object.animation_data
616 class DenoiseOperator(bpy.types.Operator):
617 #Operator to denoise impluse noise on the active object's fcurves
618 """Removes spikes from all fcurves on the selected object"""
619 bl_idname = "mocap.denoise"
620 bl_label = "Denoise Mocap"
622 def execute(self, context):
623 obj = context.active_object
624 mocap_tools.denoise(obj, obj.animation_data.action.fcurves)
625 return {'FINISHED'}
627 @classmethod
628 def poll(cls, context):
629 obj = context.active_object
630 return obj and obj.animation_data and obj.animation_data.action
633 class LimitDOFOperator(bpy.types.Operator):
634 #Operator to analyze performer armature and apply rotation constraints on the enduser armature
635 """Create limit constraints on the active armature from """ \
636 """the selected armature's animation's range of motion"""
637 bl_idname = "mocap.limitdof"
638 bl_label = "Set DOF Constraints"
640 def execute(self, context):
641 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object][0]
642 mocap_tools.limit_dof(context, performer_obj, context.active_object)
643 return {'FINISHED'}
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
656 class RemoveLimitDOFOperator(bpy.types.Operator):
657 #Removes constraints created by above operator
658 """Remove previously created limit constraints on the active armature"""
659 bl_idname = "mocap.removelimitdof"
660 bl_label = "Remove DOF Constraints"
662 def execute(self, context):
663 mocap_tools.limit_dof_toggle_off(context, context.active_object)
664 return {'FINISHED'}
666 @classmethod
667 def poll(cls, context):
668 activeIsArmature = False
669 if context.active_object:
670 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
671 return activeIsArmature
674 class RotateFixArmatureOperator(bpy.types.Operator):
675 #Operator to fix common imported Mocap data issue of wrong axis system on active object
676 """Realign the active armature's axis system to match Blender """ \
677 """(commonly needed after bvh import)"""
678 bl_idname = "mocap.rotate_fix"
679 bl_label = "Rotate Fix"
681 def execute(self, context):
682 mocap_tools.rotate_fix_armature(context.active_object.data)
683 return {'FINISHED'}
685 @classmethod
686 def poll(cls, context):
687 if context.active_object:
688 return isinstance(context.active_object.data, bpy.types.Armature)
691 class ScaleFixArmatureOperator(bpy.types.Operator):
692 #Operator to scale down the selected armature to match the active one
693 """Rescale selected armature to match the active animation, """ \
694 """for convenience"""
695 bl_idname = "mocap.scale_fix"
696 bl_label = "Scale Fix"
698 def execute(self, context):
699 enduser_obj = bpy.context.active_object
700 performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
701 mocap_tools.scale_fix_armature(performer_obj, enduser_obj)
702 return {'FINISHED'}
704 @classmethod
705 def poll(cls, context):
706 if context.active_object:
707 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
708 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
709 if performer_obj:
710 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
711 else:
712 return False
715 class AddMocapFixOperator(bpy.types.Operator):
716 #Operator to add a post-retarget fix
717 """Add a post-retarget fix - useful for fixing certain """ \
718 """artifacts following the retarget"""
719 bl_idname = "mocap.addmocapfix"
720 bl_label = "Add Mocap Fix"
721 type: EnumProperty(name="Type of Fix",
722 items=[("point", "Maintain Position", "Bone is at a specific point"),
723 ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
724 ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
725 ("distance", "Maintain distance", "Target bones maintained specified distance")],
726 description="Type of fix")
728 def execute(self, context):
729 enduser_obj = bpy.context.active_object
730 enduser_arm = enduser_obj.data
731 new_mcon = enduser_arm.mocap_constraints.add()
732 new_mcon.type = self.type
733 return {'FINISHED'}
735 @classmethod
736 def poll(cls, context):
737 if context.active_object:
738 return isinstance(context.active_object.data, bpy.types.Armature)
741 class RemoveMocapConstraintOperator(bpy.types.Operator):
742 #Operator to remove a post-retarget fix
743 """Remove this post-retarget fix"""
744 bl_idname = "mocap.removeconstraint"
745 bl_label = "Remove Mocap Fix"
746 constraint: IntProperty()
748 def execute(self, context):
749 enduser_obj = bpy.context.active_object
750 enduser_arm = enduser_obj.data
751 m_constraints = enduser_arm.mocap_constraints
752 m_constraint = m_constraints[self.constraint]
753 if m_constraint.real_constraint:
754 bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
755 cons_obj = mocap_constraints.getConsObj(bone)
756 mocap_constraints.removeConstraint(m_constraint, cons_obj)
757 m_constraints.remove(self.constraint)
758 return {'FINISHED'}
760 @classmethod
761 def poll(cls, context):
762 if context.active_object:
763 return isinstance(context.active_object.data, bpy.types.Armature)
766 class BakeMocapConstraintsOperator(bpy.types.Operator):
767 #Operator to bake all post-retarget fixes
768 """Bake all post-retarget fixes to the Retarget Fixes NLA Track"""
769 bl_idname = "mocap.bakeconstraints"
770 bl_label = "Bake Mocap Fixes"
772 def execute(self, context):
773 mocap_constraints.bakeConstraints(context)
774 return {'FINISHED'}
776 @classmethod
777 def poll(cls, context):
778 if context.active_object:
779 return isinstance(context.active_object.data, bpy.types.Armature)
782 class UnbakeMocapConstraintsOperator(bpy.types.Operator):
783 #Operator to unbake all post-retarget fixes
784 """Unbake all post-retarget fixes - removes the baked data """ \
785 """from the Retarget Fixes NLA Track"""
786 bl_idname = "mocap.unbakeconstraints"
787 bl_label = "Unbake Mocap Fixes"
789 def execute(self, context):
790 mocap_constraints.unbakeConstraints(context)
791 return {'FINISHED'}
793 @classmethod
794 def poll(cls, context):
795 if context.active_object:
796 return isinstance(context.active_object.data, bpy.types.Armature)
799 class UpdateMocapConstraintsOperator(bpy.types.Operator):
800 #Operator to update all post-retarget fixes, similar to update dependencies on drivers
801 #Needed because python properties lack certain callbacks and some fixes take a while to recalculate.
802 """Update all post-retarget fixes (necessary to take under """ \
803 """consideration changes to armature object or pose)"""
804 bl_idname = "mocap.updateconstraints"
805 bl_label = "Update Mocap Fixes"
807 def execute(self, context):
808 mocap_constraints.updateConstraints(context.active_object, context)
809 return {'FINISHED'}
811 @classmethod
812 def poll(cls, context):
813 if context.active_object:
814 return isinstance(context.active_object.data, bpy.types.Armature)
817 class GuessHierachyMappingOperator(bpy.types.Operator):
818 #Operator which calls heurisitic function to guess mapping between 2 armatures
819 """Attempt to auto figure out hierarchy mapping"""
820 bl_idname = "mocap.guessmapping"
821 bl_label = "Guess Hierarchy Mapping"
823 def execute(self, context):
824 enduser_obj = bpy.context.active_object
825 performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj][0]
826 mocap_tools.guessMapping(performer_obj, enduser_obj)
827 return {'FINISHED'}
829 @classmethod
830 def poll(cls, context):
831 if context.active_object:
832 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
833 performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
834 if performer_obj:
835 return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
836 else:
837 return False
840 class PathEditingOperator(bpy.types.Operator):
841 #Operator which calls path editing function, making active object follow the selected curve.
842 """Set active object (stride object) to follow the selected curve"""
843 bl_idname = "mocap.pathediting"
844 bl_label = "Set Path"
846 def execute(self, context):
847 path = [obj for obj in context.selected_objects if obj != context.active_object][0]
848 mocap_tools.path_editing(context, context.active_object, path)
849 return {'FINISHED'}
851 @classmethod
852 def poll(cls, context):
853 if context.active_object:
854 selected_objs = [obj for obj in context.selected_objects if obj != context.active_object and isinstance(obj.data, bpy.types.Curve)]
855 return selected_objs
856 else:
857 return False
860 class AnimationStitchingOperator(bpy.types.Operator):
861 #Operator which calls stitching function, combining 2 animations onto the NLA.
862 """Stitch two defined animations into a single one via alignment of NLA Tracks"""
863 bl_idname = "mocap.animstitch"
864 bl_label = "Stitch Animations"
866 def execute(self, context):
867 mocap_tools.anim_stitch(context, context.active_object)
868 return {'FINISHED'}
870 @classmethod
871 def poll(cls, context):
872 activeIsArmature = False
873 if context.active_object:
874 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
875 if activeIsArmature:
876 stitch_settings = context.active_object.data.stitch_settings
877 return (stitch_settings.first_action and stitch_settings.second_action)
878 return False
881 class GuessAnimationStitchingOperator(bpy.types.Operator):
882 #Operator which calls stitching function heuristic, setting good values for above operator.
883 """Guess the stitch frame and second offset for animation stitch"""
884 bl_idname = "mocap.animstitchguess"
885 bl_label = "Guess Animation Stitch"
887 def execute(self, context):
888 mocap_tools.guess_anim_stitch(context, context.active_object)
889 return {'FINISHED'}
891 @classmethod
892 def poll(cls, context):
893 activeIsArmature = False
894 if context.active_object:
895 activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
896 if activeIsArmature:
897 stitch_settings = context.active_object.data.stitch_settings
898 return (stitch_settings.first_action and stitch_settings.second_action)
899 return False
901 classes = (
902 MocapConstraint,
903 MocapMapping,
904 MocapNLATracks,
905 AnimationStitchSettings,
906 MocapPanel,
907 MocapConstraintsPanel,
908 ExtraToolsPanel,
909 AddMocapFixOperator,
910 AnimationStitchingOperator,
911 BakeMocapConstraintsOperator,
912 ConvertSamplesOperator,
913 DenoiseOperator,
914 GuessAnimationStitchingOperator,
915 GuessHierachyMappingOperator,
916 LimitDOFOperator,
917 LoadMappingOperator,
918 LooperOperator,
919 PathEditingOperator,
920 RemoveLimitDOFOperator,
921 RemoveMocapConstraintOperator,
922 RetargetOperator,
923 RotateFixArmatureOperator,
924 SaveMappingOperator,
925 ScaleFixArmatureOperator,
926 SelectMapBoneOperator,
927 UnbakeMocapConstraintsOperator,
928 UpdateMocapConstraintsOperator
931 def register():
932 for i in classes:
933 bpy.utils.register_class(i)
935 bpy.types.Armature.mocap_constraints = CollectionProperty(type=MocapConstraint)
937 #string property for storing performer->enduser mapping
938 bpy.types.Bone.map = StringProperty()
939 #Collection Property for storing enduser->performer mapping
940 bpy.types.Bone.reverseMap = CollectionProperty(type=MocapMapping)
941 #Boolean property for storing foot bone toggle
942 bpy.types.Bone.foot = BoolProperty(name="Foot",
943 description="Mark this bone as a 'foot', which determines retargeted animation's translation",
944 default=False)
945 #Boolean property for storing if this bone is twisted along the y axis,
946 # which can happen due to various sources of performers
947 bpy.types.Bone.twistFix = BoolProperty(name="Twist Fix",
948 description="Fix Twist on this bone",
949 default=False)
950 #Boolean property for retarget constrants,
951 # copy location on pose space instead of local space
952 bpy.types.Bone.poseSpace = BoolProperty(name="Pose Space",
953 description="copy location in pose space",
954 default=False)
955 #Boolean property for toggling ik retargeting for this bone
956 bpy.types.PoseBone.IKRetarget = BoolProperty(name="IK",
957 description="Toggle IK Retargeting method for given bone",
958 update=toggleIKBone, default=False)
959 #Animation Stitch Settings Property
960 bpy.types.Armature.stitch_settings = PointerProperty(type=AnimationStitchSettings)
961 #Current/Active retargeted animation on the armature
962 bpy.types.Armature.active_mocap = StringProperty(update=retarget.NLASystemInitialize)
963 #Collection of retargeted animations and their NLA Tracks on the armature
964 bpy.types.Armature.mocapNLATracks = CollectionProperty(type=MocapNLATracks)
965 #Advanced retargeting boolean property
966 bpy.types.Armature.advancedRetarget = BoolProperty(default=False, update=advancedRetargetToggle)
967 #frame step - frequency of frames to retarget. Skipping is useful for previewing, faster work etc.
968 bpy.types.Armature.frameStep = IntProperty(name="Frame Skip",
969 default=1,
970 description="Number of frames to skip - for previewing retargets quickly (1 is fully sampled)",
971 min=1)
974 def unregister():
975 del bpy.types.Bone.map
976 del bpy.types.Bone.reverseMap
977 del bpy.types.Bone.foot
978 del bpy.types.Bone.twistFix
979 del bpy.types.Bone.poseSpace
980 del bpy.types.PoseBone.IKRetarget
981 del bpy.types.Armature.mocap_constraints
982 del bpy.types.Armature.stitch_settings
983 del bpy.types.Armature.active_mocap
984 del bpy.types.Armature.mocapNLATracks
985 #Advanced retargeting boolean property
986 del bpy.types.Armature.advancedRetarget
987 #frame step - frequency of frames to retarget. Skipping is useful for previewing, faster work etc.
988 del bpy.types.Armature.frameStep
990 for i in classes:
991 bpy.utils.unregister_class(i)
993 if __name__ == "__main__":
994 register()