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