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 ========================
23 from mathutils import Matrix, Vector
24 from math import acos, pi
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.
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
41 if abs(v[0]) < abs(v[1]):
46 # Use cross prouct to generate a vector perpendicular to
47 # both tv and (more importantly) v.
51 def rotation_difference(mat1, mat2):
52 """ Returns the shortest-path rotational difference between two
55 q1 = mat1.to_quaternion()
56 q2 = mat2.to_quaternion()
57 angle = acos(min(1,max(-1,q1.dot(q2)))) * 2
59 angle = -angle + (2*pi)
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.
74 rest = pose_bone.bone.matrix_local.copy()
75 rest_inv = rest.inverted()
77 par_mat = pose_bone.parent.matrix.copy()
78 par_inv = par_mat.inverted()
79 par_rest = pose_bone.parent.bone.matrix_local.copy()
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
96 def get_local_pose_matrix(pose_bone):
97 """ Returns the local transform matrix of the given pose bone.
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()
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()
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]
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
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
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
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
199 # Get a vector perpendicular to ikv
200 pv = perpendicular_vector(ikv).normalized() * length
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')
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
224 ang1 = rotation_difference(ik_first.matrix, match_bone.matrix)
226 pv2 = Matrix.Rotation(-angle, 4, ikv) * pv
228 ang2 = rotation_difference(ik_first.matrix, match_bone.matrix)
230 # Do the one with the smaller angle
235 def fk2ik_arm(obj, fk, ik):
236 """ Matches the fk bones in an arm rig to the ik bones.
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]]
249 if handi['auto_stretch'] == 0.0:
250 uarm['stretch_length'] = handi['stretch_length']
252 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
253 uarm['stretch_length'] *= diff
256 match_pose_rotation(uarm, uarmi)
257 match_pose_scale(uarm, uarmi)
260 match_pose_rotation(farm, farmi)
261 match_pose_scale(farm, farmi)
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.
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]]
283 handi['stretch_length'] = uarm['stretch_length']
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.
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]]
310 if footi['auto_stretch'] == 0.0:
311 thigh['stretch_length'] = footi['stretch_length']
313 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
314 thigh['stretch_length'] *= diff
317 match_pose_rotation(thigh, thighi)
318 match_pose_scale(thigh, thighi)
321 match_pose_rotation(shin, shini)
322 match_pose_scale(shin, shini)
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.
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]]
350 footi['stretch_length'] = thigh['stretch_length']
353 set_pose_rotation(footroll, Matrix())
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")
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
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])
397 context.user_preferences.edit.use_global_undo = use_global_undo
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")
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
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])
427 context.user_preferences.edit.use_global_undo = use_global_undo
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")
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
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])
458 context.user_preferences.edit.use_global_undo = use_global_undo
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")
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
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])
490 context.user_preferences.edit.use_global_undo = use_global_undo
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"
505 def poll(self, context):
506 if context.mode != 'POSE':
509 return (context.active_object.data.get("rig_id") == rig_id)
510 except (AttributeError, KeyError, TypeError):
513 def draw(self, context):
515 pose_bones = context.active_object.pose.bones
517 selected_bones = [bone.name for bone in context.selected_pose_bones]
518 selected_bones += [context.active_pose_bone.name]
519 except (AttributeError, TypeError):
522 def is_selected(names):
523 # Returns whether any of the named bones are selected.
524 if type(names) == list:
526 if name in selected_bones:
528 elif names in selected_bones:
536 def layers_ui(layers
, layout
):
537 """ Turn a list of booleans + a list of names into a layer UI.
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"
548 def poll(self, context):
550 return (context.active_object.data.get("rig_id") == rig_id)
551 except (AttributeError, KeyError, TypeError):
554 def draw(self, context):
556 col = layout.column()
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())
569 code
+= "\n row = col.row()\n"
573 code
+= "\n row = col.row()\n"
575 code
+= " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='%s')\n" % (str(l
[1]), l
[0])
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"
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)
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)