#====================== 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