Merging trunk up to r38193.
[blender.git] / release / scripts / startup / ui_mocap.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22
23 from bpy.props import *
24 from bpy import *
25 import mocap_constraints
26 import retarget
27 import mocap_tools
28 ### reloads modules (for testing purposes only)
29 from imp import reload
30 reload(mocap_constraints)
31 reload(retarget)
32 reload(mocap_tools)
33
34 from mocap_constraints import *
35
36 # MocapConstraint class
37 # Defines MocapConstraint datatype, used to add and configute mocap constraints
38 # Attached to Armature data
39
40
41 class MocapConstraint(bpy.types.PropertyGroup):
42     name = bpy.props.StringProperty(name="Name",
43         default="Mocap Constraint",
44         description="Name of Mocap Constraint",
45         update=updateConstraint)
46     constrained_bone = bpy.props.StringProperty(name="Bone",
47         default="",
48         description="Constrained Bone",
49         update=updateConstraintBoneType)
50     constrained_boneB = bpy.props.StringProperty(name="Bone (2)",
51         default="",
52         description="Other Constrained Bone (optional, depends on type)",
53         update=updateConstraint)
54     s_frame = bpy.props.IntProperty(name="S",
55         default=1,
56         description="Start frame of constraint",
57         update=updateConstraint)
58     e_frame = bpy.props.IntProperty(name="E",
59         default=500,
60         description="End frame of constrain",
61         update=updateConstraint)
62     smooth_in = bpy.props.IntProperty(name="In",
63         default=10,
64         description="Amount of frames to smooth in",
65         update=updateConstraint,
66         min=0)
67     smooth_out = bpy.props.IntProperty(name="Out",
68         default=10,
69         description="Amount of frames to smooth out",
70         update=updateConstraint,
71         min=0)
72     targetMesh = bpy.props.StringProperty(name="Mesh",
73         default="",
74         description="Target of Constraint - Mesh (optional, depends on type)",
75         update=updateConstraint)
76     active = bpy.props.BoolProperty(name="Active",
77         default=True,
78         description="Constraint is active",
79         update=updateConstraint)
80     baked = bpy.props.BoolProperty(name="Baked / Applied",
81         default=False,
82         description="Constraint has been baked to NLA layer",
83         update=updateConstraint)
84     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
85         subtype="XYZ", default=(0.0, 0.0, 0.0),
86         description="Target of Constraint - Point",
87         update=updateConstraint)
88     targetDist = bpy.props.FloatProperty(name="Dist",
89         default=1,
90         description="Distance Constraint - Desired distance",
91         update=updateConstraint)
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=updateConstraint)
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 constraint",
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 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
115
116
117 def toggleIKBone(self, context):
118     if self.IKRetarget:
119         if not self.is_in_ik_chain:
120             print(self.name + " IK toggled ON!")
121             ik = self.constraints.new('IK')
122             #ik the whole chain up to the root, excluding
123             chainLen = 0
124             for parent_bone in self.parent_recursive:
125                 chainLen += 1
126                 if hasIKConstraint(parent_bone):
127                     break
128                 deformer_children = [child for child in parent_bone.children if child.bone.use_deform]
129                 if len(deformer_children) > 1:
130                     break
131             ik.chain_count = chainLen
132             for bone in self.parent_recursive:
133                 if bone.is_in_ik_chain:
134                     bone.IKRetarget = True
135     else:
136         print(self.name + " IK toggled OFF!")
137         cnstrn_bone = False
138         if hasIKConstraint(self):
139             cnstrn_bone = self
140         elif self.is_in_ik_chain:
141             cnstrn_bone = [child for child in self.children_recursive if hasIKConstraint(child)][0]
142         if cnstrn_bone:
143             # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
144             ik = [constraint for constraint in cnstrn_bone.constraints if constraint.type == "IK"][0]
145             cnstrn_bone.constraints.remove(ik)
146             cnstrn_bone.IKRetarget = False
147             for bone in cnstrn_bone.parent_recursive:
148                 if not bone.is_in_ik_chain:
149                     bone.IKRetarget = False
150
151 bpy.types.Bone.map = bpy.props.StringProperty()
152 bpy.types.Bone.foot = bpy.props.BoolProperty(name="Foot",
153     description="Marks this bone as a 'foot', which determines retargeted animation's translation",
154     default=False)
155 bpy.types.PoseBone.IKRetarget = bpy.props.BoolProperty(name="IK",
156     description="Toggles IK Retargeting method for given bone",
157     update=toggleIKBone, default=False)
158
159
160 def hasIKConstraint(pose_bone):
161     #utility function / predicate, returns True if given bone has IK constraint
162     return ("IK" in [constraint.type for constraint in pose_bone.constraints])
163
164
165 def updateIKRetarget():
166     # ensures that Blender constraints and IK properties are in sync
167     # currently runs when module is loaded, should run when scene is loaded
168     # or user adds a constraint to armature. Will be corrected in the future,
169     # once python callbacks are implemented
170     for obj in bpy.data.objects:
171         if obj.pose:
172             bones = obj.pose.bones
173             for pose_bone in bones:
174                 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
175                     pose_bone.IKRetarget = True
176                 else:
177                     pose_bone.IKRetarget = False
178
179 updateIKRetarget()
180
181
182 class MocapPanel(bpy.types.Panel):
183     # Motion capture retargeting panel
184     bl_label = "Mocap tools"
185     bl_space_type = "PROPERTIES"
186     bl_region_type = "WINDOW"
187     bl_context = "object"
188
189     def draw(self, context):
190         self.layout.label("Preprocessing")
191         row = self.layout.row(align=True)
192         row.alignment = 'EXPAND'
193         row.operator("mocap.samples", text='Samples to Beziers')
194         row.operator("mocap.denoise", text='Clean noise')
195         row.operator("mocap.rotate_fix", text='Fix BVH Axis Orientation')
196         row2 = self.layout.row(align=True)
197         row2.operator("mocap.looper", text='Loop animation')
198         row2.operator("mocap.limitdof", text='Constrain Rig')
199         self.layout.label("Retargeting")
200         row3 = self.layout.row(align=True)
201         column1 = row3.column(align=True)
202         column1.label("Performer Rig")
203         column2 = row3.column(align=True)
204         column2.label("Enduser Rig")
205         enduser_obj = bpy.context.active_object
206         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
207         if enduser_obj is None or len(performer_obj) != 1:
208             self.layout.label("Select performer rig and target rig (as active)")
209         else:
210             performer_obj = performer_obj[0]
211             if performer_obj.data and enduser_obj.data:
212                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
213                     perf = performer_obj.data
214                     enduser_arm = enduser_obj.data
215                     perf_pose_bones = enduser_obj.pose.bones
216                     for bone in perf.bones:
217                         row = self.layout.row()
218                         row.prop(data=bone, property='foot', text='', icon='POSE_DATA')
219                         row.label(bone.name)
220                         row.prop_search(bone, "map", enduser_arm, "bones")
221                         label_mod = "FK"
222                         if bone.map:
223                             pose_bone = perf_pose_bones[bone.map]
224                             if pose_bone.is_in_ik_chain:
225                                 label_mod = "ik chain"
226                             if hasIKConstraint(pose_bone):
227                                 label_mod = "ik end"
228                             row.prop(pose_bone, 'IKRetarget')
229                             row.label(label_mod)
230                         else:
231                             row.label(" ")
232                             row.label(" ")
233                         
234
235                     self.layout.operator("mocap.retarget", text='RETARGET!')
236
237
238 class MocapConstraintsPanel(bpy.types.Panel):
239     #Motion capture constraints panel
240     bl_label = "Mocap constraints"
241     bl_space_type = "PROPERTIES"
242     bl_region_type = "WINDOW"
243     bl_context = "object"
244
245     def draw(self, context):
246         layout = self.layout
247         if context.active_object:
248             if context.active_object.data:
249                 if context.active_object.data.name in bpy.data.armatures:
250                     enduser_obj = context.active_object
251                     enduser_arm = enduser_obj.data
252                     layout.operator("mocap.addconstraint")
253                     layout.separator()
254                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
255                         box = layout.box()
256                         box.prop(m_constraint, 'name')
257                         box.prop(m_constraint, 'type')
258                         box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones")
259                         if m_constraint.type == "distance" or m_constraint.type == "point":
260                             box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones")
261                         frameRow = box.row()
262                         frameRow.label("Frame Range:")
263                         frameRow.prop(m_constraint, 's_frame')
264                         frameRow.prop(m_constraint, 'e_frame')
265                         smoothRow = box.row()
266                         smoothRow.label("Smoothing:")
267                         smoothRow.prop(m_constraint, 'smooth_in')
268                         smoothRow.prop(m_constraint, 'smooth_out')
269                         targetRow = box.row()
270                         targetLabelCol = targetRow.column()
271                         targetLabelCol.label("Target settings:")
272                         targetPropCol = targetRow.column()
273                         if m_constraint.type == "floor":
274                             targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
275                         if m_constraint.type == "point" or m_constraint.type == "freeze":
276                             box.prop(m_constraint, 'targetSpace')
277                         if m_constraint.type == "point":
278                             targetPropCol.prop(m_constraint, 'targetPoint')
279                         if m_constraint.type == "distance":
280                             targetPropCol.prop(m_constraint, 'targetDist')
281                         checkRow = box.row()
282                         checkRow.prop(m_constraint, 'active')
283                         checkRow.prop(m_constraint, 'baked')
284                         layout.operator("mocap.removeconstraint", text="Remove constraint").constraint = i
285                         layout.separator()
286
287
288 class OBJECT_OT_RetargetButton(bpy.types.Operator):
289     bl_idname = "mocap.retarget"
290     bl_label = "Retargets active action from Performer to Enduser"
291
292     def execute(self, context):
293         retarget.totalRetarget()
294         return {"FINISHED"}
295
296
297 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
298     bl_idname = "mocap.samples"
299     bl_label = "Converts samples / simplifies keyframes to beziers"
300
301     def execute(self, context):
302         mocap_tools.fcurves_simplify()
303         return {"FINISHED"}
304
305
306 class OBJECT_OT_LooperButton(bpy.types.Operator):
307     bl_idname = "mocap.looper"
308     bl_label = "loops animation / sampled mocap data"
309
310     def execute(self, context):
311         mocap_tools.autoloop_anim()
312         return {"FINISHED"}
313
314
315 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
316     bl_idname = "mocap.denoise"
317     bl_label = "Denoises sampled mocap data "
318
319     def execute(self, context):
320         mocap_tools.denoise_median()
321         return {"FINISHED"}
322
323
324 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
325     bl_idname = "mocap.limitdof"
326     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
327
328     def execute(self, context):
329         return {"FINISHED"}
330
331 class OBJECT_OT_RotateFixArmature(bpy.types.Operator):
332     bl_idname = "mocap.rotate_fix"
333     bl_label = "Rotates selected armature 90 degrees (fix for bvh import)"
334
335     def execute(self, context):
336         mocap_tools.rotate_fix_armature(context.active_object.data)
337         return {"FINISHED"}
338     
339     #def poll(self, context):
340       #  return context.active_object.data in bpy.data.armatures
341
342
343 class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
344     bl_idname = "mocap.addconstraint"
345     bl_label = "Add constraint to target armature"
346
347     def execute(self, context):
348         enduser_obj = bpy.context.active_object
349         enduser_arm = enduser_obj.data
350         new_mcon = enduser_arm.mocap_constraints.add()
351         return {"FINISHED"}
352
353
354 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
355     bl_idname = "mocap.removeconstraint"
356     bl_label = "Removes constraints from target armature"
357     constraint = bpy.props.IntProperty()
358
359     def execute(self, context):
360         enduser_obj = bpy.context.active_object
361         enduser_arm = enduser_obj.data
362         m_constraints = enduser_arm.mocap_constraints
363         m_constraint = m_constraints[self.constraint]
364         if m_constraint.real_constraint:
365             bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
366             cons_obj = getConsObj(bone)
367             removeConstraint(m_constraint, cons_obj)
368         m_constraints.remove(self.constraint)
369         return {"FINISHED"}
370
371
372 def register():
373     bpy.utils.register_module(__name__)
374
375
376 def unregister():
377     bpy.utils.unregister_module(__name__)
378
379 if __name__ == "__main__":
380     register()