# ##### 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 rigify import RigifyError from rigify_utils import bone_class_instance, copy_bone_simple from rna_prop_ui import rna_idprop_ui_prop_get # not used, defined for completeness METARIG_NAMES = ("pelvis", "ribcage") def metarig_template(): # generated by rigify.write_meta_rig bpy.ops.object.mode_set(mode='EDIT') obj = bpy.context.active_object arm = obj.data bone = arm.edit_bones.new('pelvis') bone.head[:] = 0.0000, -0.0306, 0.1039 bone.tail[:] = 0.0000, -0.0306, -0.0159 bone.roll = 0.0000 bone.connected = False bone = arm.edit_bones.new('rib_cage') bone.head[:] = 0.0000, -0.0306, 0.1039 bone.tail[:] = 0.0000, -0.0306, 0.2236 bone.roll = -0.0000 bone.connected = False bone.parent = arm.edit_bones['pelvis'] bone = arm.edit_bones.new('spine.01') bone.head[:] = 0.0000, 0.0000, -0.0000 bone.tail[:] = 0.0000, -0.0306, 0.1039 bone.roll = -0.0000 bone.connected = False bone.parent = arm.edit_bones['rib_cage'] bone = arm.edit_bones.new('spine.02') bone.head[:] = 0.0000, -0.0306, 0.1039 bone.tail[:] = -0.0000, -0.0398, 0.2045 bone.roll = -0.0000 bone.connected = True bone.parent = arm.edit_bones['spine.01'] bone = arm.edit_bones.new('spine.03') bone.head[:] = -0.0000, -0.0398, 0.2045 bone.tail[:] = -0.0000, -0.0094, 0.2893 bone.roll = -0.0000 bone.connected = True bone.parent = arm.edit_bones['spine.02'] bone = arm.edit_bones.new('spine.04') bone.head[:] = -0.0000, -0.0094, 0.2893 bone.tail[:] = -0.0000, 0.0335, 0.3595 bone.roll = -0.0000 bone.connected = True bone.parent = arm.edit_bones['spine.03'] bone = arm.edit_bones.new('spine.05') bone.head[:] = -0.0000, 0.0335, 0.3595 bone.tail[:] = -0.0000, 0.0555, 0.4327 bone.roll = -0.0000 bone.connected = True bone.parent = arm.edit_bones['spine.04'] bone = arm.edit_bones.new('spine.06') bone.head[:] = -0.0000, 0.0555, 0.4327 bone.tail[:] = -0.0000, 0.0440, 0.5207 bone.roll = -0.0000 bone.connected = True bone.parent = arm.edit_bones['spine.05'] bone = arm.edit_bones.new('spine.07') bone.head[:] = -0.0000, 0.0440, 0.5207 bone.tail[:] = -0.0000, 0.0021, 0.5992 bone.roll = -0.0000 bone.connected = True bone.parent = arm.edit_bones['spine.06'] bpy.ops.object.mode_set(mode='OBJECT') pbone = obj.pose.bones['rib_cage'] pbone['type'] = 'spine_pivot_flex' def metarig_definition(obj, orig_bone_name): ''' The bone given is the second in a chain. Expects at least 1 parent and a chain of children withe the same basename eg. pelvis -> rib_cage -> spine.01 -> spine.02 -> spine.03 note: same as neck. ''' arm = obj.data ribcage = arm.bones[orig_bone_name] pelvis = ribcage.parent if pelvis is None: raise RigifyError("expected the ribcage bone:'%s' to have a parent (ribcage)." % ribcage.name) children = ribcage.children if len(children) != 1: raise RigifyError("expected the ribcage to have only 1 child.") child = children[0] bone_definition = [pelvis.name, ribcage.name, child.name] bone_definition.extend([child.name for child in child.children_recursive_basename]) return bone_definition def fk(*args): main(*args) def deform(obj, definitions, base_names, options): for org_bone_name in definitions[2:]: bpy.ops.object.mode_set(mode='EDIT') # Create deform bone. bone = copy_bone_simple(obj.data, org_bone_name, "DEF-%s" % base_names[org_bone_name], parent=True) # Store name before leaving edit mode bone_name = bone.name # Leave edit mode bpy.ops.object.mode_set(mode='OBJECT') # Get the pose bone bone = obj.pose.bones[bone_name] # Constrain to the original bone # XXX. Todo, is this needed if the bone is connected to its parent? con = bone.constraints.new('COPY_TRANSFORMS') con.name = "copy_loc" con.target = obj con.subtarget = org_bone_name def main(obj, bone_definition, base_names, options): from mathutils import Vector, RotationMatrix from math import radians, pi arm = obj.data # Initialize container classes for convenience mt = bone_class_instance(obj, ["pelvis", "ribcage"]) # meta mt.pelvis = bone_definition[0] mt.ribcage = bone_definition[1] mt.update() spine_chain_orig = tuple(bone_definition[2:]) spine_chain = [arm.edit_bones[child_name] for child_name in spine_chain_orig] spine_chain_basename = base_names[spine_chain[0].name].rsplit(".", 1)[0] # probably 'ORG-spine.01' -> 'spine' spine_chain_len = len(spine_chain_orig) child = spine_chain[0] spine_chain_segment_length = child.length #child.parent = mt.pelvis_e # was mt.ribcage # The first bone in the chain happens to be the basis of others, create them now ex = bone_class_instance(obj, ["pelvis_copy", "ribcage_hinge", "ribcage_copy", "spine_rotate"]) ex.pelvis_copy_e = copy_bone_simple(arm, mt.pelvis, base_names[mt.pelvis]) # no parent ex.pelvis_copy = ex.pelvis_copy_e.name ex.pelvis_copy_e.local_location = False # copy the pelvis, offset to make MCH-spine_rotate and MCH-ribcage_hinge ex.ribcage_hinge_e = copy_bone_simple(arm, mt.pelvis, "MCH-%s_hinge" % base_names[mt.ribcage]) ex.ribcage_hinge = ex.ribcage_hinge_e.name ex.ribcage_hinge_e.translate(Vector((0.0, spine_chain_segment_length / 4.0, 0.0))) ex.spine_rotate_e = copy_bone_simple(arm, mt.ribcage, "MCH-%s_rotate" % spine_chain_basename) ex.spine_rotate = ex.spine_rotate_e.name ex.spine_rotate_e.translate(Vector((0.0, spine_chain_segment_length / 2.0, 0.0))) ex.spine_rotate_e.connected = False ex.spine_rotate_e.parent = ex.pelvis_copy_e # Copy the last bone now child = spine_chain[-1] ex.ribcage_copy_e = copy_bone_simple(arm, mt.ribcage, base_names[mt.ribcage]) ex.ribcage_copy = ex.ribcage_copy_e.name ex.ribcage_copy_e.connected = False ex.ribcage_copy_e.parent = ex.ribcage_hinge_e spine_chain = [child.name for child in spine_chain] # We have 3 spine chains # - original (ORG_*) # - copy (*use original name*) # - reverse (MCH-rev_*) spine_chain_attrs = [("spine_%.2d" % (i + 1)) for i in range(spine_chain_len)] mt_chain = bone_class_instance(obj, spine_chain_attrs) # ORG_* rv_chain = bone_class_instance(obj, spine_chain_attrs) # * ex_chain = bone_class_instance(obj, spine_chain_attrs) # MCH-rev_* del spine_chain_attrs for i, child_name in enumerate(spine_chain): child_name_orig = base_names[spine_chain_orig[i]] attr = mt_chain.attr_names[i] # eg. spine_04 setattr(mt_chain, attr, spine_chain_orig[i]) # the original bone ebone = copy_bone_simple(arm, child_name, child_name_orig) # use the original name setattr(ex_chain, attr, ebone.name) ebone = copy_bone_simple(arm, child_name, "MCH-rev_%s" % child_name_orig) setattr(rv_chain, attr, ebone.name) ebone.connected = False mt_chain.update() ex_chain.update() rv_chain.update() # Now we need to re-parent these chains for i, child_name in enumerate(spine_chain_orig): attr = ex_chain.attr_names[i] + "_e" ebone = getattr(ex_chain, attr) if i == 0: ebone.connected = False ebone.parent = ex.pelvis_copy_e else: attr_parent = ex_chain.attr_names[i - 1] + "_e" ebone.parent = getattr(ex_chain, attr_parent) # intentional! get the parent from the other parallel chain member getattr(rv_chain, attr).parent = ebone # ex_chain needs to interlace bones! # Note, skip the first bone for i in range(1, spine_chain_len): # similar to neck child_name_orig = base_names[spine_chain_orig[i]] spine_e = getattr(mt_chain, mt_chain.attr_names[i] + "_e") # dont store parent names, re-reference as each chain bones parent. spine_e_parent = arm.edit_bones.new("MCH-rot_%s" % child_name_orig) spine_e_parent.head = spine_e.head spine_e_parent.tail = spine_e.head + (mt.ribcage_e.vector.normalize() * spine_chain_segment_length / 2.0) spine_e_parent.roll = mt.ribcage_e.roll spine_e = getattr(ex_chain, ex_chain.attr_names[i] + "_e") orig_parent = spine_e.parent spine_e.connected = False spine_e.parent = spine_e_parent spine_e_parent.connected = False spine_e_parent.parent = orig_parent # Rotate the rev chain 180 about the by the first bones center point pivot = (rv_chain.spine_01_e.head + rv_chain.spine_01_e.tail) * 0.5 matrix = RotationMatrix(radians(180), 3, 'X') for i, attr in enumerate(rv_chain.attr_names): # similar to neck spine_e = getattr(rv_chain, attr + "_e") # use the first bone as the pivot spine_e.head = ((spine_e.head - pivot) * matrix) + pivot spine_e.tail = ((spine_e.tail - pivot) * matrix) + pivot spine_e.roll += pi # 180d roll del spine_e deform(obj, bone_definition, base_names, options) bpy.ops.object.mode_set(mode='OBJECT') # refresh pose bones mt.update() ex.update() mt_chain.update() ex_chain.update() rv_chain.update() # Axis locks ex.ribcage_copy_p.lock_location = True, True, True con = ex.ribcage_hinge_p.constraints.new('COPY_ROTATION') con.name = "hinge" con.target = obj con.subtarget = ex.pelvis_copy # add driver fcurve = con.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 = obj var.targets[0].data_path = ex.ribcage_copy_p.path_from_id() + '["hinge"]' mod = fcurve.modifiers[0] mod.poly_order = 1 mod.coefficients[0] = 1.0 mod.coefficients[1] = -1.0 con = ex.spine_rotate_p.constraints.new('COPY_ROTATION') con.target = obj con.subtarget = ex.ribcage_copy # ex.pelvis_copy_p / rib_cage con = ex.ribcage_copy_p.constraints.new('COPY_LOCATION') con.target = obj con.subtarget = ex.pelvis_copy con.head_tail = 0.0 # This stores all important ID props prop = rna_idprop_ui_prop_get(ex.ribcage_copy_p, "hinge", create=True) ex.ribcage_copy_p["hinge"] = 1.0 prop["soft_min"] = 0.0 prop["soft_max"] = 1.0 prop = rna_idprop_ui_prop_get(ex.ribcage_copy_p, "pivot_slide", create=True) ex.ribcage_copy_p["pivot_slide"] = 1.0 / spine_chain_len prop["soft_min"] = 1.0 / spine_chain_len prop["soft_max"] = 1.0 # Create a fake connected parent/child relationship with bone location constraints # positioned at the tip. # reverse bones / MCH-rev_spine.## for i in range(1, spine_chain_len): spine_p = getattr(rv_chain, rv_chain.attr_names[i] + "_p") spine_fake_parent_name = getattr(rv_chain, rv_chain.attr_names[i - 1]) con = spine_p.constraints.new('COPY_LOCATION') con.target = obj con.subtarget = spine_fake_parent_name con.head_tail = 1.0 del spine_p, spine_fake_parent_name, con # Constrain 'inbetween' bones target_names = [("b%.2d" % (i + 1)) for i in range(spine_chain_len - 1)] rib_driver_path = ex.ribcage_copy_p.path_from_id() ex.ribcage_copy_p["bend_tot"] = 0.0 fcurve = ex.ribcage_copy_p.driver_add('["bend_tot"]') driver = fcurve.driver driver.type = 'SUM' fcurve.modifiers.remove(0) # grr dont need a modifier for i in range(spine_chain_len - 1): var = driver.variables.new() var.name = target_names[i] var.targets[0].id_type = 'OBJECT' var.targets[0].id = obj var.targets[0].data_path = rib_driver_path + ('["bend_%.2d"]' % (i + 1)) for i in range(1, spine_chain_len): # Add bend prop prop_name = "bend_%.2d" % i prop = rna_idprop_ui_prop_get(ex.ribcage_copy_p, prop_name, create=True) if ("bend_%.2d" % i) in options: ex.ribcage_copy_p[prop_name] = options["bend_%.2d" % i] else: ex.ribcage_copy_p[prop_name] = 1.0 prop["soft_min"] = 0.0 prop["soft_max"] = 1.0 spine_p = getattr(ex_chain, ex_chain.attr_names[i] + "_p") spine_p_parent = spine_p.parent # interlaced bone con = spine_p_parent.constraints.new('COPY_ROTATION') con.target = obj con.subtarget = ex.spine_rotate con.owner_space = 'LOCAL' con.target_space = 'LOCAL' del spine_p # add driver fcurve = con.driver_add("influence") driver = fcurve.driver driver.type = 'SCRIPTED' driver.expression = "bend/bend_tot" fcurve.modifiers.remove(0) # grr dont need a modifier # add target var = driver.variables.new() var.name = "bend_tot" var.targets[0].id_type = 'OBJECT' var.targets[0].id = obj var.targets[0].data_path = rib_driver_path + ('["bend_tot"]') var = driver.variables.new() var.name = "bend" var.targets[0].id_type = 'OBJECT' var.targets[0].id = obj var.targets[0].data_path = rib_driver_path + ('["%s"]' % prop_name) # original bone drivers # note: the first bone has a lot more constraints, but also this simple one is first. for i, attr in enumerate(mt_chain.attr_names): spine_p = getattr(mt_chain, attr + "_p") con = spine_p.constraints.new('COPY_ROTATION') con.target = obj con.subtarget = getattr(ex_chain, attr) # lock to the copy's rotation del spine_p # pivot slide: - lots of copy location constraints. con = mt_chain.spine_01_p.constraints.new('COPY_LOCATION') con.name = "base" con.target = obj con.subtarget = rv_chain.spine_01 # lock to the reverse location for i in range(1, spine_chain_len + 1): con = mt_chain.spine_01_p.constraints.new('COPY_LOCATION') con.name = "slide_%d" % i con.target = obj if i == spine_chain_len: attr = mt_chain.attr_names[i - 1] else: attr = mt_chain.attr_names[i] con.subtarget = getattr(rv_chain, attr) # lock to the reverse location if i == spine_chain_len: con.head_tail = 1.0 fcurve = con.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 = obj var.targets[0].data_path = rib_driver_path + '["pivot_slide"]' mod = fcurve.modifiers[0] mod.poly_order = 1 mod.coefficients[0] = - (i - 1) mod.coefficients[1] = spine_chain_len # Set pelvis and ribcage controls to use the first and last bone in the # spine respectively for their custom shape transform ex.ribcage_copy_p.custom_shape_transform = obj.pose.bones[bone_definition[len(bone_definition)-1]] ex.pelvis_copy_p.custom_shape_transform = obj.pose.bones[bone_definition[2]] # last step setup layers if "ex_layer" in options: layer = [n == options["ex_layer"] for n in range(0, 32)] else: layer = list(arm.bones[bone_definition[1]].layer) for attr in ex.attr_names: getattr(ex, attr + "_b").layer = layer for attr in ex_chain.attr_names: getattr(ex_chain, attr + "_b").layer = layer for attr in rv_chain.attr_names: getattr(rv_chain, attr + "_b").layer = layer layer = list(arm.bones[bone_definition[1]].layer) arm.bones[ex.pelvis_copy].layer = layer arm.bones[ex.ribcage_copy].layer = layer # no support for blending chains return None