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