Added smoothing variables to constraint creation, and now Active checkbox is function...
[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 from mocap_constraints import *
26
27 # MocapConstraint class
28 # Defines MocapConstraint datatype, used to add and configute mocap constraints
29 # Attached to Armature data
30
31
32 class MocapConstraint(bpy.types.PropertyGroup):
33     name = bpy.props.StringProperty(name="Name",
34         default="Mocap Constraint",
35         description="Name of Mocap Constraint",
36         update=updateConstraint)
37     constrained_bone = bpy.props.StringProperty(name="Bone",
38         default="",
39         description="Constrained Bone",
40         update=updateConstraintBoneType)
41     constrained_boneB = bpy.props.StringProperty(name="Bone (2)",
42         default="",
43         description="Other Constrained Bone (optional, depends on type)",
44         update=updateConstraint)
45     s_frame = bpy.props.IntProperty(name="S",
46         default=1,
47         description="Start frame of constraint",
48         update=updateConstraint)
49     e_frame = bpy.props.IntProperty(name="E",
50         default=500,
51         description="End frame of constrain",
52         update=updateConstraint)
53     smooth_in = bpy.props.IntProperty(name="In",
54         default=10,
55         description="Amount of frames to smooth in",
56         update=updateConstraint,
57         min=0)
58     smooth_out = bpy.props.IntProperty(name="Out",
59         default=10,
60         description="Amount of frames to smooth out",
61         update=updateConstraint,
62         min=0)
63     targetMesh = bpy.props.StringProperty(name="Mesh",
64         default="",
65         description="Target of Constraint - Mesh (optional, depends on type)",
66         update=updateConstraint)
67     active = bpy.props.BoolProperty(name="Active",
68         default=True,
69         description="Constraint is active",
70         update=updateConstraint)
71     baked = bpy.props.BoolProperty(name="Baked / Applied",
72         default=False,
73         description="Constraint has been baked to NLA layer",
74         update=updateConstraint)
75     targetFrame = bpy.props.IntProperty(name="Frame",
76         default=1,
77         description="Target of Constraint - Frame (optional, depends on type)",
78         update=updateConstraint)
79     targetPoint = bpy.props.FloatVectorProperty(name="Point", size=3,
80         subtype="XYZ", default=(0.0, 0.0, 0.0),
81         description="Target of Constraint - Point",
82         update=updateConstraint)
83     targetSpace = bpy.props.EnumProperty(
84         items=[("world", "World Space", "Evaluate target in global space"),
85             ("object", "Object space", "Evaluate target in object space"),
86             ("constrained_boneB", "Other Bone Space", "Evaluate target in specified other bone space")],
87         name="Space",
88         description="In which space should Point type target be evaluated",
89         update=updateConstraint)
90     type = bpy.props.EnumProperty(name="Type of constraint",
91         items=[("point", "Maintain Position", "Bone is at a specific point"),
92             ("freeze", "Maintain Position at frame", "Bone does not move from location specified in target frame"),
93             ("floor", "Stay above", "Bone does not cross specified mesh object eg floor"),
94             ("distance", "Maintain distance", "Target bones maintained specified distance")],
95         description="Type of constraint",
96         update=updateConstraintBoneType)
97     real_constraint = bpy.props.StringProperty()
98     real_constraint_bone = bpy.props.StringProperty()
99
100
101 bpy.utils.register_class(MocapConstraint)
102
103 bpy.types.Armature.mocap_constraints = bpy.props.CollectionProperty(type=MocapConstraint)
104
105 #Update function for IK functionality. Is called when IK prop checkboxes are toggled.
106
107
108 def toggleIKBone(self, context):
109     if self.IKRetarget:
110         if not self.is_in_ik_chain:
111             print(self.name + " IK toggled ON!")
112             ik = self.constraints.new('IK')
113             #ik the whole chain up to the root, excluding
114             chainLen = len(self.bone.parent_recursive)
115             ik.chain_count = chainLen
116             for bone in self.parent_recursive:
117                 if bone.is_in_ik_chain:
118                     bone.IKRetarget = True
119     else:
120         print(self.name + " IK toggled OFF!")
121         cnstrn_bone = False
122         if hasIKConstraint(self):
123             cnstrn_bone = self
124         elif self.is_in_ik_chain:
125             cnstrn_bone = [child for child in self.children_recursive if hasIKConstraint(child)][0]
126         if cnstrn_bone:
127             # remove constraint, and update IK retarget for all parents of cnstrn_bone up to chain_len
128             ik = [constraint for constraint in cnstrn_bone.constraints if constraint.type == "IK"][0]
129             cnstrn_bone.constraints.remove(ik)
130             cnstrn_bone.IKRetarget = False
131             for bone in cnstrn_bone.parent_recursive:
132                 if not bone.is_in_ik_chain:
133                     bone.IKRetarget = False
134
135 bpy.types.Bone.map = bpy.props.StringProperty()
136 bpy.types.PoseBone.IKRetarget = bpy.props.BoolProperty(name="IK",
137     description="Toggles IK Retargeting method for given bone",
138     update=toggleIKBone, default=False)
139
140
141 def hasIKConstraint(pose_bone):
142     #utility function / predicate, returns True if given bone has IK constraint
143     return ("IK" in [constraint.type for constraint in pose_bone.constraints])
144
145
146 def updateIKRetarget():
147     # ensures that Blender constraints and IK properties are in sync
148     # currently runs when module is loaded, should run when scene is loaded
149     # or user adds a constraint to armature. Will be corrected in the future,
150     # once python callbacks are implemented
151     for obj in bpy.data.objects:
152         if obj.pose:
153             bones = obj.pose.bones
154             for pose_bone in bones:
155                 if pose_bone.is_in_ik_chain or hasIKConstraint(pose_bone):
156                     pose_bone.IKRetarget = True
157                 else:
158                     pose_bone.IKRetarget = False
159
160 updateIKRetarget()
161
162 import retarget
163 import mocap_tools
164
165
166 class MocapPanel(bpy.types.Panel):
167     # Motion capture retargeting panel
168     bl_label = "Mocap tools"
169     bl_space_type = "PROPERTIES"
170     bl_region_type = "WINDOW"
171     bl_context = "object"
172
173     def draw(self, context):
174         self.layout.label("Preprocessing")
175         row = self.layout.row(align=True)
176         row.alignment = 'EXPAND'
177         row.operator("mocap.samples", text='Samples to Beziers')
178         row.operator("mocap.denoise", text='Clean noise')
179         row2 = self.layout.row(align=True)
180         row2.operator("mocap.looper", text='Loop animation')
181         row2.operator("mocap.limitdof", text='Constrain Rig')
182         self.layout.label("Retargeting")
183         row3 = self.layout.row(align=True)
184         column1 = row3.column(align=True)
185         column1.label("Performer Rig")
186         column2 = row3.column(align=True)
187         column2.label("Enduser Rig")
188         self.layout.label("Hierarchy mapping")
189         enduser_obj = bpy.context.active_object
190         performer_obj = [obj for obj in bpy.context.selected_objects if obj != enduser_obj]
191         if enduser_obj is None or len(performer_obj) != 1:
192             self.layout.label("Select performer rig and target rig (as active)")
193         else:
194             performer_obj = performer_obj[0]
195             if performer_obj.data and enduser_obj.data:
196                 if performer_obj.data.name in bpy.data.armatures and enduser_obj.data.name in bpy.data.armatures:
197                     perf = performer_obj.data
198                     enduser_arm = enduser_obj.data
199                     perf_pose_bones = enduser_obj.pose.bones
200                     for bone in perf.bones:
201                         row = self.layout.row()
202                         row.label(bone.name)
203                         row.prop_search(bone, "map", enduser_arm, "bones")
204                         label_mod = "FK"
205                         if bone.map:
206                             pose_bone = perf_pose_bones[bone.map]
207                             if pose_bone.is_in_ik_chain:
208                                 label_mod = "ik chain"
209                             if hasIKConstraint(pose_bone):
210                                 label_mod = "ik end"
211                             row.prop(pose_bone, 'IKRetarget')
212                         row.label(label_mod)
213
214                     self.layout.operator("mocap.retarget", text='RETARGET!')
215
216
217 class MocapConstraintsPanel(bpy.types.Panel):
218     #Motion capture constraints panel
219     bl_label = "Mocap constraints"
220     bl_space_type = "PROPERTIES"
221     bl_region_type = "WINDOW"
222     bl_context = "object"
223
224     def draw(self, context):
225         layout = self.layout
226         if context.active_object:
227             if context.active_object.data:
228                 if context.active_object.data.name in bpy.data.armatures:
229                     enduser_obj = context.active_object
230                     enduser_arm = enduser_obj.data
231                     layout.operator("mocap.addconstraint")
232                     layout.separator()
233                     for i, m_constraint in enumerate(enduser_arm.mocap_constraints):
234                         box = layout.box()
235                         box.prop(m_constraint, 'name')
236                         box.prop(m_constraint, 'type')
237                         box.prop_search(m_constraint, 'constrained_bone', enduser_obj.pose, "bones")
238                         if m_constraint.type == "distance" or m_constraint.type == "point":
239                             box.prop_search(m_constraint, 'constrained_boneB', enduser_obj.pose, "bones")
240                         frameRow = box.row()
241                         frameRow.label("Frame Range:")
242                         frameRow.prop(m_constraint, 's_frame')
243                         frameRow.prop(m_constraint, 'e_frame')
244                         smoothRow = box.row()
245                         smoothRow.label("Smoothing:")
246                         smoothRow.prop(m_constraint, 'smooth_in')
247                         smoothRow.prop(m_constraint, 'smooth_out')
248                         targetRow = box.row()
249                         targetLabelCol = targetRow.column()
250                         targetLabelCol.label("Target settings:")
251                         targetPropCol = targetRow.column()
252                         if m_constraint.type == "floor":
253                             targetPropCol.prop_search(m_constraint, 'targetMesh', bpy.data, "objects")
254                         if m_constraint.type == "freeze":
255                             targetPropCol.prop(m_constraint, 'targetFrame')
256                         if m_constraint.type == "point":
257                             targetPropCol.prop(m_constraint, 'targetPoint')
258                             box.prop(m_constraint, 'targetSpace')
259                         checkRow = box.row()
260                         checkRow.prop(m_constraint, 'active')
261                         checkRow.prop(m_constraint, 'baked')
262                         layout.operator("mocap.removeconstraint", text="Remove constraint").constraint = i
263                         layout.separator()
264
265
266 class OBJECT_OT_RetargetButton(bpy.types.Operator):
267     bl_idname = "mocap.retarget"
268     bl_label = "Retargets active action from Performer to Enduser"
269
270     def execute(self, context):
271         retarget.totalRetarget()
272         return {"FINISHED"}
273
274
275 class OBJECT_OT_ConvertSamplesButton(bpy.types.Operator):
276     bl_idname = "mocap.samples"
277     bl_label = "Converts samples / simplifies keyframes to beziers"
278
279     def execute(self, context):
280         mocap_tools.fcurves_simplify()
281         return {"FINISHED"}
282
283
284 class OBJECT_OT_LooperButton(bpy.types.Operator):
285     bl_idname = "mocap.looper"
286     bl_label = "loops animation / sampled mocap data"
287
288     def execute(self, context):
289         mocap_tools.autoloop_anim()
290         return {"FINISHED"}
291
292
293 class OBJECT_OT_DenoiseButton(bpy.types.Operator):
294     bl_idname = "mocap.denoise"
295     bl_label = "Denoises sampled mocap data "
296
297     def execute(self, context):
298         return {"FINISHED"}
299
300
301 class OBJECT_OT_LimitDOFButton(bpy.types.Operator):
302     bl_idname = "mocap.limitdof"
303     bl_label = "Analyzes animations Max/Min DOF and adds hard/soft constraints"
304
305     def execute(self, context):
306         return {"FINISHED"}
307
308
309 class OBJECT_OT_AddMocapConstraint(bpy.types.Operator):
310     bl_idname = "mocap.addconstraint"
311     bl_label = "Add constraint to target armature"
312
313     def execute(self, context):
314         enduser_obj = bpy.context.active_object
315         enduser_arm = enduser_obj.data
316         new_mcon = enduser_arm.mocap_constraints.add()
317         return {"FINISHED"}
318
319
320 class OBJECT_OT_RemoveMocapConstraint(bpy.types.Operator):
321     bl_idname = "mocap.removeconstraint"
322     bl_label = "Removes constraints from target armature"
323     constraint = bpy.props.IntProperty()
324
325     def execute(self, context):
326         enduser_obj = bpy.context.active_object
327         enduser_arm = enduser_obj.data
328         m_constraints = enduser_arm.mocap_constraints
329         m_constraint = m_constraints[self.constraint]
330         if m_constraint.real_constraint:
331             bone = enduser_obj.pose.bones[m_constraint.real_constraint_bone]
332             cons_obj = getConsObj(bone)
333             removeConstraint(m_constraint, cons_obj)
334         m_constraints.remove(self.constraint)
335         return {"FINISHED"}
336
337
338 def register():
339     bpy.utils.register_module(__name__)
340
341
342 def unregister():
343     bpy.utils.unregister_module(__name__)
344
345 if __name__ == "__main__":
346     register()