diff options
Diffstat (limited to 'rigify/rigs/spine.py')
-rw-r--r-- | rigify/rigs/spine.py | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/rigify/rigs/spine.py b/rigify/rigs/spine.py new file mode 100644 index 00000000..37dda1b1 --- /dev/null +++ b/rigify/rigs/spine.py @@ -0,0 +1,484 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +import bpy +from mathutils import Vector +from rigify.utils import MetarigError +from rigify.utils import copy_bone, flip_bone, put_bone +from rigify.utils import connected_children_names +from rigify.utils import strip_org, make_mechanism_name, make_deformer_name +from rigify.utils import obj_to_bone, create_circle_widget +from rna_prop_ui import rna_idprop_ui_prop_get + + +script = """ +hips = "%s" +ribs = "%s" +if is_selected([hips, ribs]): + layout.prop(pose_bones[ribs], '["pivot_slide"]', text="Pivot Slide (" + ribs + ")", slider=True) +if is_selected(ribs): + layout.prop(pose_bones[ribs], '["isolate"]', text="Isolate Rotation (" + ribs + ")", slider=True) +""" + + +class Rig: + """ A "spine" rig. It turns a chain of bones into a rig with two controls: + One for the hips, and one for the rib cage. + + """ + def __init__(self, obj, bone_name, params): + """ Gather and validate data about the rig. + + """ + self.obj = obj + self.org_bones = [bone_name] + connected_children_names(obj, bone_name) + self.params = params + + if len(self.org_bones) <= 1: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones." % (strip_org(bone))) + + def gen_deform(self): + """ Generate the deformation rig. + + """ + for name in self.org_bones: + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + # Create deform bone + bone_e = eb[copy_bone(self.obj, name)] + + # Change its name + bone_e.name = make_deformer_name(strip_org(name)) + bone_name = bone_e.name + + # Leave edit mode + bpy.ops.object.mode_set(mode='OBJECT') + + # Get the pose bone + bone = self.obj.pose.bones[bone_name] + + # Constrain to the original bone + con = bone.constraints.new('COPY_TRANSFORMS') + con.name = "copy_transforms" + con.target = self.obj + con.subtarget = name + + def gen_control(self): + """ Generate the control rig. + + """ + #--------------------------------- + # Create the hip and rib controls + bpy.ops.object.mode_set(mode='EDIT') + + # Copy org bones + hip_control = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0])) + rib_control = copy_bone(self.obj, self.org_bones[-1], strip_org(self.org_bones[-1])) + rib_mch = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".follow"))) + hinge = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[-1]) + ".hinge")) + + eb = self.obj.data.edit_bones + + hip_control_e = eb[hip_control] + rib_control_e = eb[rib_control] + rib_mch_e = eb[rib_mch] + hinge_e = eb[hinge] + + # Parenting + hip_control_e.use_connect = False + rib_control_e.use_connect = False + rib_mch_e.use_connect = False + hinge_e.use_connect = False + + hinge_e.parent = None + rib_control_e.parent = hinge_e + rib_mch_e.parent = rib_control_e + + # Position + flip_bone(self.obj, hip_control) + flip_bone(self.obj, hinge) + + hinge_e.length /= 2 + rib_mch_e.length /= 2 + + put_bone(self.obj, rib_control, hip_control_e.head) + put_bone(self.obj, rib_mch, hip_control_e.head) + + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + # Switch to object mode + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + hip_control_p = pb[hip_control] + rib_control_p = pb[rib_control] + hinge_p = pb[hinge] + + # No translation on rib control + rib_control_p.lock_location = [True, True, True] + + # Hip does not use local location + hip_control_p.bone.use_local_location = False + + # Custom hinge property + prop = rna_idprop_ui_prop_get(rib_control_p, "isolate", create=True) + rib_control_p["isolate"] = 1.0 + prop["soft_min"] = prop["min"] = 0.0 + prop["soft_max"] = prop["max"] = 1.0 + + # Constraints + con = hinge_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = hip_control + + con1 = hinge_p.constraints.new('COPY_ROTATION') + con1.name = "isolate_off.01" + con1.target = self.obj + con1.subtarget = hip_control + + con2 = rib_control_p.constraints.new('COPY_SCALE') + con2.name = "isolate_off.02" + con2.target = self.obj + con2.subtarget = hip_control + con2.use_offset = True + con2.target_space = 'LOCAL' + con2.owner_space = 'LOCAL' + + # Drivers for "isolate_off" + fcurve = con1.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["isolate"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + fcurve = con2.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "var" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["isolate"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1.0 + mod.coefficients[1] = -1.0 + + # Appearence + hip_control_p.custom_shape_transform = pb[self.org_bones[0]] + rib_control_p.custom_shape_transform = pb[self.org_bones[-1]] + + #------------------------- + # Create flex spine chain + + # Create bones/parenting/positiong + bpy.ops.object.mode_set(mode='EDIT') + flex_bones = [] + flex_helpers = [] + prev_bone = None + for b in self.org_bones: + # Create bones + bone = copy_bone(self.obj, b, make_mechanism_name(strip_org(b) + ".flex")) + helper = copy_bone(self.obj, rib_mch, make_mechanism_name(strip_org(b) + ".flex_h")) + flex_bones += [bone] + flex_helpers += [helper] + + eb = self.obj.data.edit_bones + bone_e = eb[bone] + helper_e = eb[helper] + + # Parenting + bone_e.use_connect = False + helper_e.use_connect = False + if prev_bone is None: + helper_e.parent = eb[hip_control] + bone_e.parent = helper_e + + # Position + put_bone(self.obj, helper, bone_e.head) + helper_e.length /= 4 + + prev_bone = bone + + # Constraints + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + rib_control_p = pb[rib_control] + rib_mch_p = pb[rib_mch] + + inc = 1.0 / (len(flex_helpers) - 1) + inf = 1.0 / (len(flex_helpers) - 1) + for b in zip(flex_helpers[1:], flex_bones[:-1], self.org_bones[1:]): + bone_p = pb[b[0]] + + # Scale constraints + con = bone_p.constraints.new('COPY_SCALE') + con.name = "copy_scale1" + con.target = self.obj + con.subtarget = flex_helpers[0] + con.influence = 1.0 + + con = bone_p.constraints.new('COPY_SCALE') + con.name = "copy_scale2" + con.target = self.obj + con.subtarget = rib_mch + con.influence = inf + + # Bend constraints + con = bone_p.constraints.new('COPY_ROTATION') + con.name = "bend1" + con.target = self.obj + con.subtarget = flex_helpers[0] + con.influence = 1.0 + + con = bone_p.constraints.new('COPY_ROTATION') + con.name = "bend2" + con.target = self.obj + con.subtarget = rib_mch + con.influence = inf + + # If not the rib control + if b[0] != flex_helpers[-1]: + # Custom bend property + prop_name = "bend_" + strip_org(b[2]) + prop = rna_idprop_ui_prop_get(rib_control_p, prop_name, create=True) + rib_control_p[prop_name] = inf + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + + # Bend driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = prop_name + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["' + prop_name + '"]' + + # Location constraint + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = b[1] + con.head_tail = 1.0 + + inf += inc + + #---------------------------- + # Create reverse spine chain + + # Create bones/parenting/positioning + bpy.ops.object.mode_set(mode='EDIT') + rev_bones = [] + prev_bone = None + for b in zip(flex_bones, self.org_bones): + # Create bones + bone = copy_bone(self.obj, b[1], make_mechanism_name(strip_org(b[1]) + ".reverse")) + rev_bones += [bone] + eb = self.obj.data.edit_bones + bone_e = eb[bone] + + # Parenting + bone_e.use_connect = False + bone_e.parent = eb[b[0]] + + # Position + flip_bone(self.obj, bone) + bone_e.tail = Vector(eb[b[0]].head) + #bone_e.head = Vector(eb[b[0]].tail) + if prev_bone is None: + pass # Position base bone wherever you want, for now do nothing (i.e. position at hips) + else: + put_bone(self.obj, bone, eb[prev_bone].tail) + + prev_bone = bone + + # Constraints + bpy.ops.object.mode_set(mode='OBJECT') + pb = self.obj.pose.bones + prev_bone = None + for bone in rev_bones: + bone_p = pb[bone] + + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + if prev_bone is None: + con.subtarget = hip_control # Position base bone wherever you want, for now hips + else: + con.subtarget = prev_bone + con.head_tail = 1.0 + prev_bone = bone + + #--------------------------------------------- + # Constrain org bones to flex bone's rotation + pb = self.obj.pose.bones + for b in zip(self.org_bones, flex_bones): + con = pb[b[0]].constraints.new('COPY_TRANSFORMS') + con.name = "copy_rotation" + con.target = self.obj + con.subtarget = b[1] + + #--------------------------- + # Create pivot slide system + pb = self.obj.pose.bones + bone_p = pb[self.org_bones[0]] + rib_control_p = pb[rib_control] + + # Custom pivot_slide property + prop = rna_idprop_ui_prop_get(rib_control_p, "pivot_slide", create=True) + rib_control_p["pivot_slide"] = 1.0 / len(self.org_bones) + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 1.0 / len(self.org_bones) + prop["soft_max"] = 1.0 - (1.0 / len(self.org_bones)) + + # Anchor constraint + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "copy_location" + con.target = self.obj + con.subtarget = rev_bones[0] + + # Slide constraints + i = 1 + tot = len(rev_bones) + for rb in rev_bones: + con = bone_p.constraints.new('COPY_LOCATION') + con.name = "slide." + str(i) + con.target = self.obj + con.subtarget = rb + con.head_tail = 1.0 + + # Driver + fcurve = con.driver_add("influence") + driver = fcurve.driver + var = driver.variables.new() + driver.type = 'AVERAGE' + var.name = "slide" + var.targets[0].id_type = 'OBJECT' + var.targets[0].id = self.obj + var.targets[0].data_path = rib_control_p.path_from_id() + '["pivot_slide"]' + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = 1 - i + mod.coefficients[1] = tot + + i += 1 + + # Create control widgets + w1 = create_circle_widget(self.obj, hip_control, radius=1.0, head_tail=1.0) + w2 = create_circle_widget(self.obj, rib_control, radius=1.0, head_tail=0.0) + + if w1 != None: + obj_to_bone(w1, self.obj, self.org_bones[0]) + if w2 != None: + obj_to_bone(w2, self.obj, self.org_bones[-1]) + + # Return control names + return hip_control, rib_control + + def generate(self): + """ Generate the rig. + Do NOT modify any of the original bones, except for adding constraints. + The main armature should be selected and active before this is called. + + """ + self.gen_deform() + hips, ribs = self.gen_control() + + return [script % (hips, ribs)] + + @classmethod + def create_sample(self, obj): + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('hips') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = -0.0000, -0.0590, 0.2804 + bone.roll = -0.0000 + bone.use_connect = False + bones['hips'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = -0.0000, -0.0590, 0.2804 + bone.tail[:] = 0.0000, 0.0291, 0.5324 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['hips']] + bones['spine'] = bone.name + bone = arm.edit_bones.new('ribs') + bone.head[:] = 0.0000, 0.0291, 0.5324 + bone.tail[:] = -0.0000, 0.0000, 1.0000 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine']] + bones['ribs'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['hips']] + pbone.rigify_type = 'spine' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['ribs']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['hips']] + pbone['rigify_type'] = 'spine' + + bpy.ops.object.mode_set(mode='EDIT') + for bone in arm.edit_bones: + bone.select = False + bone.select_head = False + bone.select_tail = False + for b in bones: + bone = arm.edit_bones[bones[b]] + bone.select = True + bone.select_head = True + bone.select_tail = True + arm.edit_bones.active = bone + |