Screencast Keys Addon: Improved mouse silhouette, fixed box width to fit to text...
[blender-addons.git] / rigify / rig_ui_template.py
blob9e755bb5c90dfb00cb0a6bd03d247e9c9399e38f
1 #====================== BEGIN GPL LICENSE BLOCK ======================
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.
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.
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.
17 #======================= END GPL LICENSE BLOCK ========================
19 # <pep8 compliant>
21 UI_SLIDERS = '''
22 import bpy
23 from mathutils import Matrix, Vector
24 from math import acos, pi
26 rig_id = "%s"
29 ############################
30 ## Math utility functions ##
31 ############################
33 def perpendicular_vector(v):
34 """ Returns a vector that is perpendicular to the one given.
35 The returned vector is _not_ guaranteed to be normalized.
36 """
37 # Create a vector that is not aligned with v.
38 # It doesn't matter what vector. Just any vector
39 # that's guaranteed to not be pointing in the same
40 # direction.
41 if abs(v[0]) < abs(v[1]):
42 tv = Vector((1,0,0))
43 else:
44 tv = Vector((0,1,0))
46 # Use cross prouct to generate a vector perpendicular to
47 # both tv and (more importantly) v.
48 return v.cross(tv)
51 def rotation_difference(mat1, mat2):
52 """ Returns the shortest-path rotational difference between two
53 matrices.
54 """
55 q1 = mat1.to_quaternion()
56 q2 = mat2.to_quaternion()
57 angle = acos(min(1,max(-1,q1.dot(q2)))) * 2
58 if angle > pi:
59 angle = -angle + (2*pi)
60 return angle
63 #########################################
64 ## "Visual Transform" helper functions ##
65 #########################################
67 def get_pose_matrix_in_other_space(mat, pose_bone):
68 """ Returns the transform matrix relative to pose_bone's current
69 transform space. In other words, presuming that mat is in
70 armature space, slapping the returned matrix onto pose_bone
71 should give it the armature-space transforms of mat.
72 TODO: try to handle cases with axis-scaled parents better.
73 """
74 rest = pose_bone.bone.matrix_local.copy()
75 rest_inv = rest.inverted()
76 if pose_bone.parent:
77 par_mat = pose_bone.parent.matrix.copy()
78 par_inv = par_mat.inverted()
79 par_rest = pose_bone.parent.bone.matrix_local.copy()
80 else:
81 par_mat = Matrix()
82 par_inv = Matrix()
83 par_rest = Matrix()
85 # Get matrix in bone's current transform space
86 smat = rest_inv * (par_rest * (par_inv * mat))
88 # Compensate for non-local location
89 #if not pose_bone.bone.use_local_location:
90 # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion()
91 # smat.translation = loc
93 return smat
96 def get_local_pose_matrix(pose_bone):
97 """ Returns the local transform matrix of the given pose bone.
98 """
99 return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
102 def set_pose_translation(pose_bone, mat):
103 """ Sets the pose bone's translation to the same translation as the given matrix.
104 Matrix should be given in bone's local space.
106 if pose_bone.bone.use_local_location == True:
107 pose_bone.location = mat.to_translation()
108 else:
109 loc = mat.to_translation()
111 rest = pose_bone.bone.matrix_local.copy()
112 if pose_bone.bone.parent:
113 par_rest = pose_bone.bone.parent.matrix_local.copy()
114 else:
115 par_rest = Matrix()
117 q = (par_rest.inverted() * rest).to_quaternion()
118 pose_bone.location = q * loc
121 def set_pose_rotation(pose_bone, mat):
122 """ Sets the pose bone's rotation to the same rotation as the given matrix.
123 Matrix should be given in bone's local space.
125 q = mat.to_quaternion()
127 if pose_bone.rotation_mode == 'QUATERNION':
128 pose_bone.rotation_quaternion = q
129 elif pose_bone.rotation_mode == 'AXIS_ANGLE':
130 pose_bone.rotation_axis_angle[0] = q.angle
131 pose_bone.rotation_axis_angle[1] = q.axis[0]
132 pose_bone.rotation_axis_angle[2] = q.axis[1]
133 pose_bone.rotation_axis_angle[3] = q.axis[2]
134 else:
135 pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
138 def set_pose_scale(pose_bone, mat):
139 """ Sets the pose bone's scale to the same scale as the given matrix.
140 Matrix should be given in bone's local space.
142 pose_bone.scale = mat.to_scale()
145 def match_pose_translation(pose_bone, target_bone):
146 """ Matches pose_bone's visual translation to target_bone's visual
147 translation.
148 This function assumes you are in pose mode on the relevant armature.
150 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
151 set_pose_translation(pose_bone, mat)
152 bpy.ops.object.mode_set(mode='OBJECT')
153 bpy.ops.object.mode_set(mode='POSE')
156 def match_pose_rotation(pose_bone, target_bone):
157 """ Matches pose_bone's visual rotation to target_bone's visual
158 rotation.
159 This function assumes you are in pose mode on the relevant armature.
161 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
162 set_pose_rotation(pose_bone, mat)
163 bpy.ops.object.mode_set(mode='OBJECT')
164 bpy.ops.object.mode_set(mode='POSE')
167 def match_pose_scale(pose_bone, target_bone):
168 """ Matches pose_bone's visual scale to target_bone's visual
169 scale.
170 This function assumes you are in pose mode on the relevant armature.
172 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
173 set_pose_scale(pose_bone, mat)
174 bpy.ops.object.mode_set(mode='OBJECT')
175 bpy.ops.object.mode_set(mode='POSE')
178 ##############################
179 ## IK/FK snapping functions ##
180 ##############################
182 def match_pole_target(ik_first, ik_last, pole, match_bone, length):
183 """ Places an IK chain's pole target to match ik_first's
184 transforms to match_bone. All bones should be given as pose bones.
185 You need to be in pose mode on the relevant armature object.
186 ik_first: first bone in the IK chain
187 ik_last: last bone in the IK chain
188 pole: pole target bone for the IK chain
189 match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
190 length: distance pole target should be placed from the chain center
192 a = ik_first.matrix.to_translation()
193 b = ik_last.matrix.to_translation() + ik_last.vector
195 # Vector from the head of ik_first to the
196 # tip of ik_last
197 ikv = b - a
199 # Get a vector perpendicular to ikv
200 pv = perpendicular_vector(ikv).normalized() * length
202 def set_pole(pvi):
203 """ Set pole target's position based on a vector
204 from the arm center line.
206 # Translate pvi into armature space
207 ploc = a + (ikv/2) + pvi
209 # Set pole target to location
210 mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
211 set_pose_translation(pole, mat)
213 bpy.ops.object.mode_set(mode='OBJECT')
214 bpy.ops.object.mode_set(mode='POSE')
216 set_pole(pv)
218 # Get the rotation difference between ik_first and match_bone
219 angle = rotation_difference(ik_first.matrix, match_bone.matrix)
221 # Try compensating for the rotation difference in both directions
222 pv1 = Matrix.Rotation(angle, 4, ikv) * pv
223 set_pole(pv1)
224 ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
226 pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
227 set_pole(pv2)
228 ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
230 # Do the one with the smaller angle
231 if ang1 < ang2:
232 set_pole(pv1)
235 def fk2ik_arm(obj, fk, ik):
236 """ Matches the fk bones in an arm rig to the ik bones.
237 obj: armature object
238 fk: list of fk bone names
239 ik: list of ik bone names
241 uarm = obj.pose.bones[fk[0]]
242 farm = obj.pose.bones[fk[1]]
243 hand = obj.pose.bones[fk[2]]
244 uarmi = obj.pose.bones[ik[0]]
245 farmi = obj.pose.bones[ik[1]]
246 handi = obj.pose.bones[ik[2]]
248 # Stretch
249 if handi['auto_stretch'] == 0.0:
250 uarm['stretch_length'] = handi['stretch_length']
251 else:
252 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
253 uarm['stretch_length'] *= diff
255 # Upper arm position
256 match_pose_rotation(uarm, uarmi)
257 match_pose_scale(uarm, uarmi)
259 # Forearm position
260 match_pose_rotation(farm, farmi)
261 match_pose_scale(farm, farmi)
263 # Hand position
264 match_pose_rotation(hand, handi)
265 match_pose_scale(hand, handi)
268 def ik2fk_arm(obj, fk, ik):
269 """ Matches the ik bones in an arm rig to the fk bones.
270 obj: armature object
271 fk: list of fk bone names
272 ik: list of ik bone names
274 uarm = obj.pose.bones[fk[0]]
275 farm = obj.pose.bones[fk[1]]
276 hand = obj.pose.bones[fk[2]]
277 uarmi = obj.pose.bones[ik[0]]
278 farmi = obj.pose.bones[ik[1]]
279 handi = obj.pose.bones[ik[2]]
280 pole = obj.pose.bones[ik[3]]
282 # Stretch
283 handi['stretch_length'] = uarm['stretch_length']
285 # Hand position
286 match_pose_translation(handi, hand)
287 match_pose_rotation(handi, hand)
288 match_pose_scale(handi, hand)
290 # Pole target position
291 match_pole_target(uarmi, farmi, pole, uarm, (uarmi.length + farmi.length))
294 def fk2ik_leg(obj, fk, ik):
295 """ Matches the fk bones in a leg rig to the ik bones.
296 obj: armature object
297 fk: list of fk bone names
298 ik: list of ik bone names
300 thigh = obj.pose.bones[fk[0]]
301 shin = obj.pose.bones[fk[1]]
302 foot = obj.pose.bones[fk[2]]
303 mfoot = obj.pose.bones[fk[3]]
304 thighi = obj.pose.bones[ik[0]]
305 shini = obj.pose.bones[ik[1]]
306 footi = obj.pose.bones[ik[2]]
307 mfooti = obj.pose.bones[ik[3]]
309 # Stretch
310 if footi['auto_stretch'] == 0.0:
311 thigh['stretch_length'] = footi['stretch_length']
312 else:
313 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
314 thigh['stretch_length'] *= diff
316 # Thigh position
317 match_pose_rotation(thigh, thighi)
318 match_pose_scale(thigh, thighi)
320 # Shin position
321 match_pose_rotation(shin, shini)
322 match_pose_scale(shin, shini)
324 # Foot position
325 mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local
326 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat
327 set_pose_rotation(foot, footmat)
328 set_pose_scale(foot, footmat)
329 bpy.ops.object.mode_set(mode='OBJECT')
330 bpy.ops.object.mode_set(mode='POSE')
333 def ik2fk_leg(obj, fk, ik):
334 """ Matches the ik bones in a leg rig to the fk bones.
335 obj: armature object
336 fk: list of fk bone names
337 ik: list of ik bone names
339 thigh = obj.pose.bones[fk[0]]
340 shin = obj.pose.bones[fk[1]]
341 mfoot = obj.pose.bones[fk[2]]
342 thighi = obj.pose.bones[ik[0]]
343 shini = obj.pose.bones[ik[1]]
344 footi = obj.pose.bones[ik[2]]
345 footroll = obj.pose.bones[ik[3]]
346 pole = obj.pose.bones[ik[4]]
347 mfooti = obj.pose.bones[ik[5]]
349 # Stretch
350 footi['stretch_length'] = thigh['stretch_length']
352 # Clear footroll
353 set_pose_rotation(footroll, Matrix())
355 # Foot position
356 mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local
357 footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat
358 set_pose_translation(footi, footmat)
359 set_pose_rotation(footi, footmat)
360 set_pose_scale(footi, footmat)
361 bpy.ops.object.mode_set(mode='OBJECT')
362 bpy.ops.object.mode_set(mode='POSE')
364 # Pole target position
365 match_pole_target(thighi, shini, pole, thigh, (thighi.length + shini.length))
368 ##############################
369 ## IK/FK snapping operators ##
370 ##############################
372 class Rigify_Arm_FK2IK(bpy.types.Operator):
373 """ Snaps an FK arm to an IK arm.
375 bl_idname = "pose.rigify_arm_fk2ik_" + rig_id
376 bl_label = "Rigify Snap FK arm to IK"
377 bl_options = {'UNDO'}
379 uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
380 farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
381 hand_fk = bpy.props.StringProperty(name="Hand FK Name")
383 uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
384 farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
385 hand_ik = bpy.props.StringProperty(name="Hand IK Name")
387 @classmethod
388 def poll(cls, context):
389 return (context.active_object != None and context.mode == 'POSE')
391 def execute(self, context):
392 use_global_undo = context.user_preferences.edit.use_global_undo
393 context.user_preferences.edit.use_global_undo = False
394 try:
395 fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk], ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
396 finally:
397 context.user_preferences.edit.use_global_undo = use_global_undo
398 return {'FINISHED'}
401 class Rigify_Arm_IK2FK(bpy.types.Operator):
402 """ Snaps an IK arm to an FK arm.
404 bl_idname = "pose.rigify_arm_ik2fk_" + rig_id
405 bl_label = "Rigify Snap IK arm to FK"
406 bl_options = {'UNDO'}
408 uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name")
409 farm_fk = bpy.props.StringProperty(name="Forerm FK Name")
410 hand_fk = bpy.props.StringProperty(name="Hand FK Name")
412 uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name")
413 farm_ik = bpy.props.StringProperty(name="Forearm IK Name")
414 hand_ik = bpy.props.StringProperty(name="Hand IK Name")
415 pole = bpy.props.StringProperty(name="Pole IK Name")
417 @classmethod
418 def poll(cls, context):
419 return (context.active_object != None and context.mode == 'POSE')
421 def execute(self, context):
422 use_global_undo = context.user_preferences.edit.use_global_undo
423 context.user_preferences.edit.use_global_undo = False
424 try:
425 ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk], ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole])
426 finally:
427 context.user_preferences.edit.use_global_undo = use_global_undo
428 return {'FINISHED'}
431 class Rigify_Leg_FK2IK(bpy.types.Operator):
432 """ Snaps an FK leg to an IK leg.
434 bl_idname = "pose.rigify_leg_fk2ik_" + rig_id
435 bl_label = "Rigify Snap FK leg to IK"
436 bl_options = {'UNDO'}
438 thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
439 shin_fk = bpy.props.StringProperty(name="Shin FK Name")
440 foot_fk = bpy.props.StringProperty(name="Foot FK Name")
441 mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
443 thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
444 shin_ik = bpy.props.StringProperty(name="Shin IK Name")
445 foot_ik = bpy.props.StringProperty(name="Foot IK Name")
446 mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
448 @classmethod
449 def poll(cls, context):
450 return (context.active_object != None and context.mode == 'POSE')
452 def execute(self, context):
453 use_global_undo = context.user_preferences.edit.use_global_undo
454 context.user_preferences.edit.use_global_undo = False
455 try:
456 fk2ik_leg(context.active_object, fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk], ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
457 finally:
458 context.user_preferences.edit.use_global_undo = use_global_undo
459 return {'FINISHED'}
462 class Rigify_Leg_IK2FK(bpy.types.Operator):
463 """ Snaps an IK leg to an FK leg.
465 bl_idname = "pose.rigify_leg_ik2fk_" + rig_id
466 bl_label = "Rigify Snap IK leg to FK"
467 bl_options = {'UNDO'}
469 thigh_fk = bpy.props.StringProperty(name="Thigh FK Name")
470 shin_fk = bpy.props.StringProperty(name="Shin FK Name")
471 mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name")
473 thigh_ik = bpy.props.StringProperty(name="Thigh IK Name")
474 shin_ik = bpy.props.StringProperty(name="Shin IK Name")
475 foot_ik = bpy.props.StringProperty(name="Foot IK Name")
476 footroll = bpy.props.StringProperty(name="Foot Roll Name")
477 pole = bpy.props.StringProperty(name="Pole IK Name")
478 mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name")
480 @classmethod
481 def poll(cls, context):
482 return (context.active_object != None and context.mode == 'POSE')
484 def execute(self, context):
485 use_global_undo = context.user_preferences.edit.use_global_undo
486 context.user_preferences.edit.use_global_undo = False
487 try:
488 ik2fk_leg(context.active_object, fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk], ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole, self.mfoot_ik])
489 finally:
490 context.user_preferences.edit.use_global_undo = use_global_undo
491 return {'FINISHED'}
494 ###################
495 ## Rig UI Panels ##
496 ###################
498 class RigUI(bpy.types.Panel):
499 bl_space_type = 'VIEW_3D'
500 bl_region_type = 'UI'
501 bl_label = "Rig Main Properties"
502 bl_idname = rig_id + "_PT_rig_ui"
504 @classmethod
505 def poll(self, context):
506 if context.mode != 'POSE':
507 return False
508 try:
509 return (context.active_object.data.get("rig_id") == rig_id)
510 except (AttributeError, KeyError, TypeError):
511 return False
513 def draw(self, context):
514 layout = self.layout
515 pose_bones = context.active_object.pose.bones
516 try:
517 selected_bones = [bone.name for bone in context.selected_pose_bones]
518 selected_bones += [context.active_pose_bone.name]
519 except (AttributeError, TypeError):
520 return
522 def is_selected(names):
523 # Returns whether any of the named bones are selected.
524 if type(names) == list:
525 for name in names:
526 if name in selected_bones:
527 return True
528 elif names in selected_bones:
529 return True
530 return False
536 def layers_ui(layers, layout):
537 """ Turn a list of booleans + a list of names into a layer UI.
540 code = '''
541 class RigLayers(bpy.types.Panel):
542 bl_space_type = 'VIEW_3D'
543 bl_region_type = 'UI'
544 bl_label = "Rig Layers"
545 bl_idname = rig_id + "_PT_rig_layers"
547 @classmethod
548 def poll(self, context):
549 try:
550 return (context.active_object.data.get("rig_id") == rig_id)
551 except (AttributeError, KeyError, TypeError):
552 return False
554 def draw(self, context):
555 layout = self.layout
556 col = layout.column()
558 rows = {}
559 for i in range(28):
560 if layers[i]:
561 if layout[i][1] not in rows:
562 rows[layout[i][1]] = []
563 rows[layout[i][1]] += [(layout[i][0], i)]
565 keys = list(rows.keys())
566 keys.sort()
568 for key in keys:
569 code += "\n row = col.row()\n"
570 i = 0
571 for l in rows[key]:
572 if i > 3:
573 code += "\n row = col.row()\n"
574 i = 0
575 code += " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='%s')\n" % (str(l[1]), l[0])
576 i += 1
578 # Root layer
579 code += "\n row = col.row()"
580 code += "\n row.separator()"
581 code += "\n row = col.row()"
582 code += "\n row.separator()\n"
583 code += "\n row = col.row()\n"
584 code += " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n"
586 return code
589 UI_REGISTER = '''
591 def register():
592 bpy.utils.register_class(Rigify_Arm_FK2IK)
593 bpy.utils.register_class(Rigify_Arm_IK2FK)
594 bpy.utils.register_class(Rigify_Leg_FK2IK)
595 bpy.utils.register_class(Rigify_Leg_IK2FK)
596 bpy.utils.register_class(RigUI)
597 bpy.utils.register_class(RigLayers)
599 def unregister():
600 bpy.utils.unregister_class(Rigify_Arm_FK2IK)
601 bpy.utils.unregister_class(Rigify_Arm_IK2FK)
602 bpy.utils.unregister_class(Rigify_Leg_FK2IK)
603 bpy.utils.unregister_class(Rigify_Leg_IK2FK)
604 bpy.utils.unregister_class(RigUI)
605 bpy.utils.unregister_class(RigLayers)
607 register()