Merging from trunk up to r38119.
[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.PoseBone.IKRetarget = bpy.props.BoolProperty(name="IK",
153     description="Toggles IK Retargeting method for given bone",
154     update=toggleIKBone, default=False)
155
156
157 def hasIKConstraint(pose_bone):
158     #utility function / predicate, returns True if given bone has IK constraint
159     return ("IK" in [constraint.type for constraint in pose_bone.constraints])
160
161
162 def updateIKRetarget():
163     # ensures that Blender constraints and IK properties are in sync
164     # currently runs when module is loaded, should run when scene is loaded
165     # or user adds a constraint to armature. Will be corrected in the future,
166     # once python callbacks are implemented
167     for obj in bpy.data.objects:
168         if obj.pose:
169             bones = obj.pose.bones
170             for pose_bone in bones:
171                 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
172                     pose_bone.IKRetarget = True
173                 else:
174                     pose_bone.IKRetarget = False
175
176 updateIKRetarget()
177
178
179 class MocapPanel(bpy.types.Panel):
180     # Motion capture retargeting panel
181     bl_label = "Mocap tools"
182     bl_space_type = "PROPERTIES"
183     bl_region_type = "WINDOW"
184     bl_context = "object"
185
186     def draw(self, context):
187         self.layout.label("Preprocessing")
188         row = self.layout.row(align=True)
189         row.alignment = 'EXPAND'
190         row.operator("mocap.samples", text='Samples to Beziers')
191         row.operator("mocap.denoise", text='Clean noise')
192         row2 = self.layout.row(align=True)
193         row2.operator("mocap.looper", text='Loop animation')
194         row2.operator("mocap.limitdof", text='Constrain Rig')
195         self.layout.label("Retargeting")
196         row3 = self.layout.row(align=True)
197         column1 = row3.column(align=True)
198         column1.label("Performer Rig")
199         column2 = row3.column(align=True)
200         column2.label("Enduser Rig")
201         self.layout.label("Hierarchy mapping")
202         enduser_obj = bpy.context.active_object
203         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
204         if enduser_obj is None or len(performer_obj) != 1:
205             self.layout.label("Select performer rig and target rig (as active)")
206         else:
207             performer_obj = performer_obj[0]
208             if performer_obj.data and enduser_obj.data:
209                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
210                     perf = performer_obj.data
211                     enduser_arm = enduser_obj.data
212                     perf_pose_bones = enduser_obj.pose.bones
213                     for bone in perf.bones:
214                         row = self.layout.row()
215                         row.label(bone.name)
216                         row.prop_search(bone, "map", enduser_arm, "bones")
217                         label_mod = "FK"
218                         if bone.map:
219                             pose_bone = perf_pose_bones[bone.map]
220                             if pose_bone.is_in_ik_chain:
221                                 label_mod = "ik chain"
222                             if hasIKConstraint(pose_bone):
223                                 label_mod = "ik end"
224                             row.prop(pose_bone, 'IKRetarget')
225                         row.label(label_mod)
226
227                     self.layout.operator("mocap.retarget", text='RETARGET!')
228
229
230 class MocapConstraintsPanel(bpy.types.Panel):
231     #Motion capture constraints panel
232     bl_label = "Mocap constraints"
233     bl_space_type = "PROPERTIES"
234     bl_region_type = "WINDOW"
235     bl_context = "object"
236
237     def draw(self, context):
238         layout = self.layout
239         if context.active_object:
240             if context.active_object.data:
241                 if context.active_object.data.name in bpy.data.armatures:
242                     enduser_obj = context.active_object
243                     enduser_arm = enduser_obj.data
244                     layout.operator("mocap.addconstraint")
245                     layout.separator()
246                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
247                         box = layout.box()
248                         box.prop(m_constraint, 'name')
249                         box.prop(m_constraint, 'type')
250                         box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones")
251                         if m_constraint.type == "distance" or m_constraint.type == "point":
252                             box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones")
253                         frameRow = box.row()
254                         frameRow.label("Frame Range:")
255                         frameRow.prop(m_constraint, 's_frame')
256                         frameRow.prop(m_constraint, 'e_frame')
257                         smoothRow = box.row()
258                         smoothRow.label("Smoothing:")
259                         smoothRow.prop(m_constraint, 'smooth_in')
260                         smoothRow.prop(m_constraint, 'smooth_out')
261                         targetRow = box.row()
262                         targetLabelCol = targetRow.column()
263                         targetLabelCol.label("Target settings:")
264                         targetPropCol = targetRow.column()
265                         if m_constraint.type == "floor":
266                             targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
267                         if m_constraint.type == "point" or m_constraint.type == "freeze":
268                             box.prop(m_constraint, 'targetSpace')
269                         if m_constraint.type == "point":
270                             targetPropCol.prop(m_constraint, 'targetPoint')
271                         if m_constraint.type == "distance":
272                             targetPropCol.prop(m_constraint, 'targetDist')
273                         checkRow = box.row()
274                         checkRow.prop(m_constraint, 'active')
275                         checkRow.prop(m_constraint, 'baked')
276                         layout.operator("mocap.removeconstraint", text="Remove constraint").constraint = i
277                         layout.separator()
278
279
280 class OBJECT_OT_RetargetButton(bpy.types.Operator):
281     bl_idname = "mocap.retarget"
282     bl_label = "Retargets active action from Performer to Enduser"
283
284     def execute(self, context):
285         retarget.totalRetarget()
286         return {"FINISHED"}
287
288
289 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
290     bl_idname = "mocap.samples"
291     bl_label = "Converts samples / simplifies keyframes to beziers"
292
293     def execute(self, context):
294         mocap_tools.fcurves_simplify()
295         return {"FINISHED"}
296
297
298 class OBJECT_OT_LooperButton(bpy.types.Operator):
299     bl_idname = "mocap.looper"
300     bl_label = "loops animation / sampled mocap data"
301
302     def execute(self, context):
303         mocap_tools.autoloop_anim()
304         return {"FINISHED"}
305
306
307 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
308     bl_idname = "mocap.denoise"
309     bl_label = "Denoises sampled mocap data "
310
311     def execute(self, context):
312         mocap_tools.denoise_median()
313         return {"FINISHED"}
314
315
316 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
317     bl_idname = "mocap.limitdof"
318     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
319
320     def execute(self, context):
321         return {"FINISHED"}
322
323
324 class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
325     bl_idname = "mocap.addconstraint"
326     bl_label = "Add constraint to target armature"
327
328     def execute(self, context):
329         enduser_obj = bpy.context.active_object
330         enduser_arm = enduser_obj.data
331         new_mcon = enduser_arm.mocap_constraints.add()
332         return {"FINISHED"}
333
334
335 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
336     bl_idname = "mocap.removeconstraint"
337     bl_label = "Removes constraints from target armature"
338     constraint = bpy.props.IntProperty()
339
340     def execute(self, context):
341         enduser_obj = bpy.context.active_object
342         enduser_arm = enduser_obj.data
343         m_constraints = enduser_arm.mocap_constraints
344         m_constraint = m_constraints[self.constraint]
345         if m_constraint.real_constraint:
346             bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
347             cons_obj = getConsObj(bone)
348             removeConstraint(m_constraint, cons_obj)
349         m_constraints.remove(self.constraint)
350         return {"FINISHED"}
351
352
353 def register():
354     bpy.utils.register_module(__name__)
355
356
357 def unregister():
358     bpy.utils.unregister_module(__name__)
359
360 if __name__ == "__main__":
361     register()