split up metarig hierarchy evaluation and modifying the metarig into 2 steps,

original bone names cant be changed anymore but this means the bones can be re-parented without confusing scripts that run after the rig is modified.
support for defining a bone to have multiple types and  automatically blending between 2 generated rigs
This commit is contained in:
Campbell Barton 2009-12-05 19:26:28 +00:00
parent f287762678
commit 65edb7341f
8 changed files with 556 additions and 336 deletions

@ -91,6 +91,7 @@ def _bone_class_instance_copy(self, from_prefix="", to_prefix=""):
new_slot_ls.append(attr)
from_name_ls.append(bone_name)
bone_name_orig = bone_name_orig.replace("ORG-", "") # XXX - we need a better way to do this
new_name_ls.append(to_prefix + bone_name_orig)
new_bones = copy_bone_simple_list(self.obj.data, from_name_ls, new_name_ls, True)
@ -103,8 +104,10 @@ def _bone_class_instance_copy(self, from_prefix="", to_prefix=""):
return new_bc
def _bone_class_instance_names(self):
return [getattr(self, attr) for attr in self.attr_names]
def _bone_class_instance_blend(self, from_bc, to_bc, target_bone=None, target_prop="blend", use_loc=True, use_rot=True):
def _bone_class_instance_blend(self, from_bc, to_bc, target_bone=None, target_prop="blend"):
'''
Use for blending bone chains.
@ -113,78 +116,19 @@ def _bone_class_instance_blend(self, from_bc, to_bc, target_bone=None, target_pr
XXX - toggles editmode, need to re-validate all editbones :(
'''
if self.attr_names != from_bc.attr_names or self.attr_names != to_bc.attr_names:
raise Exception("can only blend between matching chains")
obj = self.obj
if obj.mode == 'EDIT':
raise Exception("blending cant be called in editmode")
# setup the blend property
if target_bone is None:
target_bone = self.attr_names[-1]
prop_pbone = obj.pose.bones[target_bone]
if prop_pbone.get(target_bone, None) is None:
prop = rna_idprop_ui_prop_get(prop_pbone, target_prop, create=True)
prop_pbone[target_prop] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
driver_path = prop_pbone.path_to_id() + ('["%s"]' % target_prop)
def blend_target(driver):
tar = driver.targets.new()
tar.name = target_bone
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = driver_path
for attr in self.attr_names:
new_pbone = getattr(self, attr + "_p")
from_bone_name = getattr(from_bc, attr)
to_bone_name = getattr(to_bc, attr)
if from_bone_name == to_bone_name:
raise Exception("Matching from/to bone names:" + from_bone_name)
if use_loc:
con = new_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = from_bone_name
con = new_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = to_bone_name
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
fcurve.modifiers.remove(0) # grr dont need a modifier
blend_target(driver)
if use_rot:
con = new_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = from_bone_name
con = new_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = to_bone_name
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
fcurve.modifiers.remove(0) # grr dont need a modifier
blend_target(driver)
apply_bones = [getattr(self, attr) for attr in self.attr_names]
from_bones = [getattr(from_bc, attr) for attr in from_bc.attr_names]
to_bones = [getattr(to_bc, attr) for attr in to_bc.attr_names]
blend_bone_list(self.obj, apply_bones, from_bones, to_bones, target_bone, target_prop)
def bone_class_instance(obj, slots, name="BoneContainer"):
attr_names = tuple(slots) # dont modify the original
slots = slots[:] # dont modify the original
slots = list(slots) # dont modify the original
for i in range(len(slots)):
member = slots[i]
slots.append(member + "_b") # bone bone
@ -196,6 +140,7 @@ def bone_class_instance(obj, slots, name="BoneContainer"):
"attr_names":attr_names, \
"update":_bone_class_instance_update, \
"rename":_bone_class_instance_rename, \
"names":_bone_class_instance_names, \
"copy":_bone_class_instance_copy, \
"blend":_bone_class_instance_blend, \
}
@ -254,6 +199,78 @@ def copy_bone_simple_list(arm, from_bones, to_bones, parent=False):
return copy_bones
def blend_bone_list(obj, apply_bones, from_bones, to_bones, target_bone=None, target_prop="blend"):
if obj.mode == 'EDIT':
raise Exception("blending cant be called in editmode")
# setup the blend property
if target_bone is None:
target_bone = apply_bones[-1] # default to the last bone
prop_pbone = obj.pose.bones[target_bone]
if prop_pbone.get(target_bone, None) is None:
prop = rna_idprop_ui_prop_get(prop_pbone, target_prop, create=True)
prop_pbone[target_prop] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
driver_path = prop_pbone.path_to_id() + ('["%s"]' % target_prop)
def blend_target(driver):
tar = driver.targets.new()
tar.name = target_bone
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = driver_path
def blend_location(new_pbone, from_bone_name, to_bone_name):
con = new_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = from_bone_name
con = new_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = to_bone_name
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
fcurve.modifiers.remove(0) # grr dont need a modifier
blend_target(driver)
def blend_rotation(new_pbone, from_bone_name, to_bone_name):
con = new_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = from_bone_name
con = new_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = to_bone_name
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
fcurve.modifiers.remove(0) # grr dont need a modifier
blend_target(driver)
for i, new_bone_name in enumerate(apply_bones):
from_bone_name = from_bones[i]
to_bone_name = to_bones[i]
# allow skipping some bones by having None in the list
if None in (new_bone_name, from_bone_name, to_bone_name):
continue
new_pbone = obj.pose.bones[new_bone_name]
if not new_pbone.bone.connected:
blend_location(new_pbone, from_bone_name, to_bone_name)
blend_rotation(new_pbone, from_bone_name, to_bone_name)
def add_stretch_to(obj, from_name, to_name, name):
'''
@ -342,7 +359,8 @@ def add_pole_target_bone(obj, base_name, name, mode='CROSS'):
return poll_name
def generate_rig(context, ob):
def generate_rig(context, obj_orig, prefix="ORG-"):
from collections import OrderedDict
global_undo = context.user_preferences.edit.global_undo
context.user_preferences.edit.global_undo = False
@ -351,50 +369,118 @@ def generate_rig(context, ob):
# copy object and data
ob.selected = False
ob_new = ob.copy()
ob_new.data = ob.data.copy()
obj_orig.selected = False
obj = obj_orig.copy()
obj.data = obj_orig.data.copy()
scene = context.scene
scene.objects.link(ob_new)
scene.objects.active = ob_new
ob_new.selected = True
scene.objects.link(obj)
scene.objects.active = obj
obj.selected = True
# enter armature editmode
arm = obj.data
# Only reference bones that have a type, means we can rename any others without lookup errors
pose_names = [pbone.name for pbone in ob_new.pose.bones if "type" in pbone]
# original name mapping
base_names = {}
#for pbone_name in ob_new.pose.bones.keys():
for pbone_name in pose_names:
bpy.ops.object.mode_set(mode='EDIT')
for bone in arm.edit_bones:
bone_name = bone.name
bone.name = prefix + bone_name
base_names[bone.name] = bone_name # new -> old mapping
bpy.ops.object.mode_set(mode='OBJECT')
bone_type = ob_new.pose.bones[pbone_name].get("type", "")
# key: bone name
# value: {type:definition, ...}
# where type is the submodule name - leg, arm etc
# and definition is a list of bone names
bone_definitions = {}
if bone_type == "":
continue
# key: bone name
# value: [functions, ...]
# each function is from the module. eg leg.ik, arm.main
bone_typeinfos = {}
# submodule = getattr(self, bone_type)
# exec("from rigify import %s as submodule")
submodule = __import__(name="%s.%s" % (__package__, bone_type), fromlist=[bone_type])
# inspect all bones and assign their definitions before modifying
for pbone in obj.pose.bones:
bone_name = pbone.name
bone_type = obj.pose.bones[bone_name].get("type", "")
bone_type_list = [bt for bt in bone_type.replace(",", " ").split()]
reload(submodule) # XXX, dev only
for bone_type in bone_type_list:
type_pair = bone_type.split(".")
# 'leg.ik' will look for an ik function in the leg module
# 'leg' will look up leg.main
if len(type_pair) == 1:
type_pair = type_pair[0], "main"
submod_name, func_name = type_pair
# from rigify import leg
submod = __import__(name="%s.%s" % (__package__, submod_name), fromlist=[submod_name])
reload(submod)
bone_def_dict = bone_definitions.setdefault(bone_name, {})
# Only calculate bone definitions once
if submod_name not in bone_def_dict:
metarig_definition_func = getattr(submod, "metarig_definition")
bone_def_dict[submod_name] = metarig_definition_func(obj, bone_name)
# Toggle editmode so the pose data is always up to date
bpy.ops.object.mode_set(mode='EDIT')
submodule.main(ob_new, pbone_name)
bpy.ops.object.mode_set(mode='OBJECT')
bone_typeinfo = bone_typeinfos.setdefault(bone_name, [])
type_func = getattr(submod, func_name)
bone_typeinfo.append((submod_name, type_func))
# now we have all the info about bones we can start operating on them
for pbone in obj.pose.bones:
bone_name = pbone.name
if bone_name not in bone_typeinfos:
continue
bone_def_dict = bone_definitions[bone_name]
# Only blend results from the same submodule, eg.
# leg.ik and arm.fk could not be blended.
results = OrderedDict()
for submod_name, type_func in bone_typeinfos[bone_name]:
# this bones definition of the current typeinfo
definition = bone_def_dict[submod_name]
bpy.ops.object.mode_set(mode='EDIT')
ret = type_func(obj, definition, base_names)
bpy.ops.object.mode_set(mode='OBJECT')
if ret:
result_submod = results.setdefault(submod_name, [])
if result_submod and len(result_submod[-1]) != len(ret):
raise Exception("bone lists not compatible: %s, %s" % (result_submod[-1], ret))
result_submod.append(ret)
for result_submod in results.values():
# blend 2 chains
definition = bone_def_dict[submod_name]
if len(result_submod) == 2:
blend_bone_list(obj, definition, result_submod[0], result_submod[1])
# needed to update driver deps
# context.scene.update()
# Only for demo'ing
# ob.restrict_view = True
ob_new.data.draw_axes = False
# obj.restrict_view = True
obj.data.draw_axes = False
context.user_preferences.edit.global_undo = global_undo
return ob_new
return obj
def write_meta_rig(obj, func_name="metarig_template"):
@ -462,11 +548,11 @@ def generate_test(context):
scene = context.scene
def create_empty_armature(name):
ob_new = bpy.data.add_object('ARMATURE', name)
obj_new = bpy.data.add_object('ARMATURE', name)
armature = bpy.data.add_armature(name)
ob_new.data = armature
scene.objects.link(ob_new)
scene.objects.active = ob_new
obj_new.data = armature
scene.objects.link(obj_new)
scene.objects.active = obj_new
files = os.listdir(os.path.dirname(__file__))
for f in files:
@ -484,10 +570,10 @@ def generate_test(context):
if metarig_template:
create_empty_armature("meta_" + module_name) # sets active
metarig_template()
ob = context.object
ob_new = generate_rig(context, ob)
obj = context.object
obj_new = generate_rig(context, obj)
new_objects.append((ob, ob_new))
new_objects.append((obj, obj_new))
else:
print("note: rig type '%s' has no metarig_template(), can't test this", module_name)
@ -505,12 +591,12 @@ def generate_test_all(context):
base_name = os.path.splitext(bpy.data.filename)[0]
for obj, obj_new in new_objects:
for ob in (obj, obj_new):
fn = base_name + "-" + bpy.utils.clean_name(ob.name)
for obj in (obj, obj_new):
fn = base_name + "-" + bpy.utils.clean_name(obj.name)
path_dot = fn + ".dot"
path_png = fn + ".png"
saved = graphviz_export.graph_armature(ob, path_dot, CONSTRAINTS=True, DRIVERS=True)
saved = graphviz_export.graph_armature(obj, path_dot, CONSTRAINTS=True, DRIVERS=True)
#if saved:
# os.system("dot -Tpng %s > %s; eog %s" % (path_dot, path_png, path_png))

@ -20,6 +20,7 @@ import bpy
from rigify import bone_class_instance, copy_bone_simple, add_pole_target_bone, add_stretch_to
from rna_prop_ui import rna_idprop_ui_get, rna_idprop_ui_prop_get
METARIG_NAMES = "shoulder", "arm", "forearm", "hand"
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
@ -54,7 +55,38 @@ def metarig_template():
pbone['type'] = 'arm'
def main(obj, orig_bone_name):
def metarig_definition(obj, orig_bone_name):
mt = bone_class_instance(obj, METARIG_NAMES) # meta
mt.arm = orig_bone_name
mt.update()
mt.shoulder_p = mt.arm_p.parent
mt.shoulder = mt.shoulder_p.name
if not mt.shoulder_p:
raise Exception("could not find 'arm' parent, skipping:", orig_bone_name)
# We could have some bones attached, find the bone that has this as its 2nd parent
hands = []
for pbone in obj.pose.bones:
index = pbone.parent_index(mt.arm_p)
if index == 2:
hands.append(pbone)
if len(hands) > 1:
raise Exception("more then 1 hand found on:", orig_bone_name)
# first add the 2 new bones
mt.hand_p = hands[0]
mt.hand = mt.hand_p.name
mt.forearm_p = mt.hand_p.parent
mt.forearm = mt.forearm_p.name
return mt.names()
def main(obj, definitions, base_names):
"""
the bone with the 'arm' property is the upper arm, this assumes a chain as follows.
[shoulder, upper_arm, forearm, hand]
@ -64,49 +96,19 @@ def main(obj, orig_bone_name):
- Original
- IK, MCH-%s_ik
- IKSwitch, MCH-%s ()
"""
# Since there are 3 chains, this gets confusing so divide into 3 chains
# Initialize container classes for convenience
mt = bone_class_instance(obj, ["shoulder", "arm", "forearm", "hand"]) # meta
mt = bone_class_instance(obj, METARIG_NAMES) # meta
mt.shoulder, mt.arm, mt.forearm, mt.hand = definitions
ik = bone_class_instance(obj, ["arm", "forearm", "pole", "hand"]) # ik
sw = bone_class_instance(obj, ["socket", "shoulder", "arm", "forearm", "hand"]) # hinge
ex = bone_class_instance(obj, ["arm_hinge"]) # hinge & extras
def chain_init():
'''
Sanity check and return the arm as a list of bone names.
'''
# do a sanity check
mt.arm = orig_bone_name
mt.update()
mt.shoulder_p = mt.arm_p.parent
mt.shoulder = mt.shoulder_p.name
if not mt.shoulder_p:
print("could not find 'arm' parent, skipping:", orig_bone_name)
return
# We could have some bones attached, find the bone that has this as its 2nd parent
hands = []
for pbone in obj.pose.bones:
index = pbone.parent_index(mt.arm_p)
if index == 2:
hands.append(pbone)
if len(hands) > 1:
print("more then 1 hand found on:", orig_bone_name)
return
# first add the 2 new bones
mt.hand_p = hands[0]
mt.hand = mt.hand_p.name
mt.forearm_p = mt.hand_p.parent
mt.forearm = mt.forearm_p.name
arm = obj.data
@ -346,3 +348,5 @@ def main(obj, orig_bone_name):
# Shoulder with its delta and hinge.
# TODO - return a list for fk and IK
return None

@ -19,23 +19,39 @@
import bpy
from rigify import get_bone_data
def main(obj, delta_name):
'''
Use this bone to define a delta thats applied to its child in pose mode.
'''
# not used, defined for completeness
METARIG_NAMES = tuple()
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the head, its parent is the body,
# its only child the first of a chain with matching basenames.
eg.
body -> head -> neck_01 -> neck_02 -> neck_03.... etc
'''
arm = obj.data
mode_orig = obj.mode
bpy.ops.object.mode_set(mode='OBJECT')
delta_pbone = obj.pose.bones[delta_name]
children = delta_pbone.children
delta = arm.bones[orig_bone_name]
children = delta.children
if len(children) != 1:
print("only 1 child supported for delta")
child_name = children[0].name
bone_definition = [delta.name, children[0].name]
return bone_definition
def main(obj, bone_definition, base_names):
'''
Use this bone to define a delta thats applied to its child in pose mode.
'''
mode_orig = obj.mode
bpy.ops.object.mode_set(mode='OBJECT')
delta_name, child_name = bone_definition
delta_pbone = obj.pose.bones[delta_name]
arm, child_pbone, child_bone = get_bone_data(obj, child_name)
delta_phead = delta_pbone.head.copy()
@ -62,7 +78,7 @@ def main(obj, delta_name):
child_tail = child_ebone.tail.copy()
arm.edit_bones.remove(delta_ebone)
del delta_ebone # cant use thz
del delta_ebone # cant use this
bpy.ops.object.mode_set(mode='OBJECT')
@ -107,3 +123,6 @@ def main(obj, delta_name):
bpy.ops.object.mode_set(mode=mode_orig)
# no blendeing
return None

@ -17,10 +17,11 @@
# ##### END GPL LICENSE BLOCK #####
import bpy
from rigify import get_bone_data, empty_layer
from rigify import get_bone_data, empty_layer, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_get, rna_idprop_ui_prop_get
from functools import reduce
METARIG_NAMES = "finger_01", "finger_02", "finger_03"
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
@ -48,13 +49,43 @@ def metarig_template():
pbone = obj.pose.bones['finger.01']
pbone['type'] = 'finger'
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the first in a chain
Expects a chain of at least 2 children.
eg.
finger -> finger_01 -> finger_02
'''
def main(obj, orig_bone_name):
bone_definition = []
orig_bone = obj.data.bones[orig_bone_name]
bone_definition.append(orig_bone.name)
bone = orig_bone
chain = 0
while chain < 2: # first 2 bones only have 1 child
children = bone.children
if len(children) != 1:
raise Exception("expected the chain to have 2 children without a fork")
bone = children[0]
bone_definition.append(bone.name) # finger_02, finger_03
chain += 1
if len(bone_definition) != len(METARIG_NAMES):
raise Exception("internal problem, expected %d bones" % len(METARIG_NAMES))
return bone_definition
def main(obj, bone_definition, base_names):
# *** EDITMODE
# get assosiated data
arm, orig_pbone, orig_ebone = get_bone_data(obj, orig_bone_name)
arm, orig_pbone, orig_ebone = get_bone_data(obj, bone_definition[0])
obj.animation_data_create() # needed if its a new armature with no keys
@ -63,22 +94,16 @@ def main(obj, orig_bone_name):
children = orig_pbone.children_recursive
tot_len = reduce(lambda f, pbone: f + pbone.bone.length, children, orig_pbone.bone.length)
base_name = orig_pbone.basename
base_name = base_names[bone_definition[0]].rsplit(".", 1)[0]
# first make a new bone at the location of the finger
control_ebone = arm.edit_bones.new(base_name)
#control_ebone = arm.edit_bones.new(base_name)
control_ebone = copy_bone_simple(arm, base_name, base_name)
control_bone_name = control_ebone.name # we dont know if we get the name requested
control_ebone.connected = orig_ebone.connected
control_ebone.parent = orig_ebone.parent
# Place the finger bone
head = orig_ebone.head.copy()
tail = orig_ebone.tail.copy()
control_ebone.head = head
control_ebone.tail = head + ((tail - head).normalize() * tot_len)
control_ebone.roll = orig_ebone.roll
control_ebone.length = tot_len
# now add bones inbetween this and its children recursively
@ -99,19 +124,10 @@ def main(obj, orig_bone_name):
driver_bone_name = child_bone_name.split('.')
driver_bone_name = driver_bone_name[0] + "_driver." + ".".join(driver_bone_name[1:])
driver_ebone = arm.edit_bones.new(driver_bone_name)
driver_bone_name = driver_ebone.name # cant be too sure!
driver_ebone = copy_bone_simple(arm, child_ebone.name, driver_bone_name)
driver_ebone.length *= 0.5
driver_ebone.layer = other_layer
new_len = pbone_child.bone.length / 2.0
head = child_ebone.head.copy()
tail = child_ebone.tail.copy()
driver_ebone.head = head
driver_ebone.tail = head + ((tail - head).normalize() * new_len)
driver_ebone.roll = child_ebone.roll
# Insert driver_ebone in the chain without connected parents
driver_ebone.connected = False
driver_ebone.parent = child_ebone.parent
@ -129,7 +145,7 @@ def main(obj, orig_bone_name):
bpy.ops.object.mode_set(mode='OBJECT')
arm, orig_pbone, orig_bone = get_bone_data(obj, orig_bone_name)
arm, orig_pbone, orig_bone = get_bone_data(obj, bone_definition[0])
arm, control_pbone, control_bone= get_bone_data(obj, control_bone_name)
@ -199,3 +215,5 @@ def main(obj, orig_bone_name):
i += 1
# no blending the result of this
return None

@ -17,9 +17,11 @@
# ##### END GPL LICENSE BLOCK #####
import bpy
from rigify import bone_class_instance, copy_bone_simple, add_pole_target_bone, add_stretch_to
from rigify import bone_class_instance, copy_bone_simple, copy_bone_simple_list, add_pole_target_bone, add_stretch_to
from rna_prop_ui import rna_idprop_ui_get, rna_idprop_ui_prop_get
METARIG_NAMES = "hips", "thigh", "shin", "foot", "toe", "heel"
def metarig_template():
# generated by rigify.write_meta_rig
bpy.ops.object.mode_set(mode='EDIT')
@ -65,37 +67,60 @@ def metarig_template():
pbone = obj.pose.bones['thigh']
pbone['type'] = 'leg'
def validate(obj, orig_bone_name):
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the first in a chain
Expects a chain of at least 3 children.
eg.
thigh -> shin -> foot -> [toe, heel]
'''
bone_definition = []
orig_bone = obj.data.bones[orig_bone_name]
orig_bone_parent = orig_bone.parent
if orig_bone_parent is None:
raise Exception("expected the thigh bone to have a parent hip bone")
bone_definition.append(orig_bone_parent.name)
bone_definition.append(orig_bone.name)
bone = orig_bone
chain = 0
while chain < 3: # first 2 bones only have 1 child
while chain < 2: # first 2 bones only have 1 child
children = bone.children
if len(children) != 1:
return "expected the thigh bone to have 3 children without a fork"
raise Exception("expected the thigh bone to have 3 children without a fork")
bone = children[0]
bone_definition.append(bone.name) # shin, foot
chain += 1
children = bone.children
# Now there must be 2 children, only one connected
if len(children) != 2:
return "expected the foot to have 2 children"
raise Exception("expected the foot to have 2 children")
if children[0].connected == children[1].connected:
return "expected one bone to be connected"
raise Exception("expected one bone to be connected")
return ''
toe, heel = children
if heel.connected:
toe, heel = heel, toe
def main(obj, orig_bone_name):
bone_definition.append(toe.name)
bone_definition.append(heel.name)
if len(bone_definition) != len(METARIG_NAMES):
raise Exception("internal problem, expected %d bones" % len(METARIG_NAMES))
return bone_definition
def ik(obj, bone_definition, base_names):
from Mathutils import Vector
arm = obj.data
@ -107,55 +132,26 @@ def main(obj, orig_bone_name):
# children of ik_foot
ik = bone_class_instance(obj, ["foot", "foot_roll", "foot_roll_01", "foot_roll_02", "knee_target"])
mt_chain.thigh_e = arm.edit_bones[orig_bone_name]
mt_chain.thigh = orig_bone_name
mt.hips_e = mt_chain.thigh_e.parent
mt.hips_e.name = "ORG-" + mt.hips_e.name
mt.hips = mt.hips_e.name
mt_chain.shin_e = mt_chain.thigh_e.children[0]
mt_chain.shin = mt_chain.shin_e.name
mt_chain.foot_e = mt_chain.shin_e.children[0]
mt_chain.foot = mt_chain.foot_e.name
mt_chain.toe_e, mt.heel_e = mt_chain.foot_e.children
# We dont know which is which, but know the heel is disconnected
if not mt_chain.toe_e.connected:
mt_chain.toe_e, mt.heel_e = mt.heel_e, mt_chain.toe_e
mt.heel_e.name = "ORG-" + mt.heel_e.name
mt_chain.toe, mt.heel = mt_chain.toe_e.name, mt.heel_e.name
ex.thigh_socket_e = copy_bone_simple(arm, mt_chain.thigh, "MCH-%s_socket" % mt_chain.thigh, parent=True)
ex.thigh_socket = ex.thigh_socket_e.name
ex.thigh_socket_e.tail = ex.thigh_socket_e.head + Vector(0.0, 0.0, ex.thigh_socket_e.length / 4.0)
ex.thigh_hinge_e = copy_bone_simple(arm, mt_chain.thigh, "MCH-%s_hinge" % mt_chain.thigh, parent=True)
ex.thigh_hinge = ex.thigh_hinge_e.name
ex.thigh_hinge_e.tail = ex.thigh_hinge_e.head + Vector(0.0, 0.0, mt_chain.thigh_e.head.length)
ex.thigh_hinge_e.translate(Vector(-(mt.hips_e.head.x - mt_chain.thigh_e.head.x), 0.0, 0.0))
ex.thigh_hinge_e.length = mt.hips_e.length
# XXX - duplicate below
for bone_class in (mt, mt_chain):
for attr in bone_class.attr_names:
i = METARIG_NAMES.index(attr)
ebone = arm.edit_bones[bone_definition[i]]
setattr(bone_class, attr, ebone.name)
bone_class.update()
# XXX - end dupe
# Make a new chain, ORG are the original bones renamed.
fk_chain = mt_chain.copy(from_prefix="ORG-") # fk has no prefix!
ik_chain = fk_chain.copy(to_prefix="MCH-")
fk_chain.thigh_e.connected = False
fk_chain.thigh_e.parent = ex.thigh_hinge_e
# fk_chain.thigh_socket_e.parent = MCH-leg_hinge
ik_chain = mt_chain.copy(to_prefix="MCH-")
# simple rename
ik_chain.rename("thigh", ik_chain.thigh + "_ik")
ik_chain.rename("shin", ik_chain.shin + "_ik")
# ik foot, no parents
base_foot_name = fk_chain.foot # whatever the foot is called, use that!
ik.foot_e = copy_bone_simple(arm, fk_chain.foot, "%s_ik" % base_foot_name)
base_foot_name = base_names[mt_chain.foot] # whatever the foot is called, use that!, XXX - ORG!
ik.foot_e = copy_bone_simple(arm, mt_chain.foot, "%s_ik" % base_foot_name)
ik.foot = ik.foot_e.name
ik.foot_e.tail.z = ik.foot_e.head.z
ik.foot_e.roll = 0.0
@ -183,7 +179,7 @@ def main(obj, orig_bone_name):
# rename 'MCH-toe' --> to 'toe_ik' and make the child of ik.foot_roll_01
# ------------------ FK or IK?
ik_chain.rename("toe", fk_chain.toe + "_ik") # only fk for the basename
ik_chain.rename("toe", base_names[mt_chain.toe] + "_ik")
ik_chain.toe_e.connected = False
ik_chain.toe_e.parent = ik.foot_roll_01_e
@ -213,43 +209,6 @@ def main(obj, orig_bone_name):
ex.update()
mt_chain.update()
ik_chain.update()
fk_chain.update()
con = fk_chain.thigh_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = ex.thigh_socket
# hinge
prop = rna_idprop_ui_prop_get(fk_chain.thigh_p, "hinge", create=True)
fk_chain.thigh_p["hinge"] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
con = ex.thigh_hinge_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = mt.hips
# add driver
hinge_driver_path = fk_chain.thigh_p.path_to_id() + '["hinge"]'
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "var"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = hinge_driver_path
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = 1.0
mod.coefficients[1] = -1.0
# adds constraints to the original bones.
mt_chain.blend(fk_chain, ik_chain, target_bone=ik.foot, target_prop="ik", use_loc=False)
# IK
con = ik_chain.shin_p.constraints.new('IK')
@ -290,3 +249,79 @@ def main(obj, orig_bone_name):
else:
con.minimum_x = -180.0 # XXX -deg
con.maximum_x = 0.0
return None, ik_chain.thigh, ik_chain.shin, ik_chain.foot, ik_chain.toe, None
def fk(obj, bone_definition, base_names):
from Mathutils import Vector
arm = obj.data
# these account for all bones in METARIG_NAMES
mt_chain = bone_class_instance(obj, ["thigh", "shin", "foot", "toe"])
mt = bone_class_instance(obj, ["hips", "heel"])
# new bones
ex = bone_class_instance(obj, ["thigh_socket", "thigh_hinge"])
for bone_class in (mt, mt_chain):
for attr in bone_class.attr_names:
i = METARIG_NAMES.index(attr)
ebone = arm.edit_bones[bone_definition[i]]
setattr(bone_class, attr, ebone.name)
bone_class.update()
ex.thigh_socket_e = copy_bone_simple(arm, mt_chain.thigh, "MCH-%s_socket" % base_names[mt_chain.thigh], parent=True)
ex.thigh_socket = ex.thigh_socket_e.name
ex.thigh_socket_e.tail = ex.thigh_socket_e.head + Vector(0.0, 0.0, ex.thigh_socket_e.length / 4.0)
ex.thigh_hinge_e = copy_bone_simple(arm, mt_chain.thigh, "MCH-%s_hinge" % base_names[mt_chain.thigh], parent=True)
ex.thigh_hinge = ex.thigh_hinge_e.name
ex.thigh_hinge_e.tail = ex.thigh_hinge_e.head + Vector(0.0, 0.0, mt_chain.thigh_e.head.length)
ex.thigh_hinge_e.translate(Vector(-(mt.hips_e.head.x - mt_chain.thigh_e.head.x), 0.0, 0.0))
ex.thigh_hinge_e.length = mt.hips_e.length
fk_chain = mt_chain.copy() # fk has no prefix!
fk_chain.thigh_e.connected = False
fk_chain.thigh_e.parent = ex.thigh_hinge_e
bpy.ops.object.mode_set(mode='OBJECT')
ex.update()
mt_chain.update()
fk_chain.update()
con = fk_chain.thigh_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = ex.thigh_socket
# hinge
prop = rna_idprop_ui_prop_get(fk_chain.thigh_p, "hinge", create=True)
fk_chain.thigh_p["hinge"] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
con = ex.thigh_hinge_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = mt.hips
# add driver
hinge_driver_path = fk_chain.thigh_p.path_to_id() + '["hinge"]'
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "var"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = hinge_driver_path
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = 1.0
mod.coefficients[1] = -1.0
# dont blend the hips or heel
return None, fk_chain.thigh, fk_chain.shin, fk_chain.foot, fk_chain.toe, None

@ -20,6 +20,8 @@ import bpy
from rigify import bone_class_instance, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_prop_get
# not used, defined for completeness
METARIG_NAMES = ("body", "head")
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
@ -72,30 +74,43 @@ def metarig_template():
pbone['type'] = 'neck'
def main(obj, orig_bone_name):
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the head, its parent is the body,
# its only child the first of a chain with matching basenames.
eg.
body -> head -> neck_01 -> neck_02 -> neck_03.... etc
'''
arm = obj.data
head = arm.bones[orig_bone_name]
body = head.parent
children = head.children
if len(children) != 1:
print("expected the head to have only 1 child.")
child = children[0]
bone_definition = [body.name, head.name, child.name]
bone_definition.extend([child.name for child in child.children_recursive_basename])
return bone_definition
def main(obj, bone_definition, base_names):
from Mathutils import Vector
arm = obj.data
# Initialize container classes for convenience
mt = bone_class_instance(obj, ["body", "head"]) # meta
mt.head = orig_bone_name
mt.update()
mt.body = mt.head_e.parent.name
mt.body = bone_definition[0]
mt.head = bone_definition[1]
mt.update()
# child chain of the 'head'
children = mt.head_e.children
if len(children) != 1:
print("expected the head to have only 1 child.")
child = children[0]
neck_chain = [child] + child.children_recursive_basename
neck_chain = [child.name for child in neck_chain]
neck_chain = bone_definition[2:]
mt_chain = bone_class_instance(obj, [("neck_%.2d" % (i + 1)) for i in range(len(neck_chain))]) # 99 bones enough eh?
for i, child_name in enumerate(neck_chain):
setattr(mt_chain, ("neck_%.2d" % (i + 1)), child_name)
for i, attr in enumerate(mt_chain.attr_names):
setattr(mt_chain, attr, neck_chain[i])
mt_chain.update()
neck_chain_basename = mt_chain.neck_01_e.basename
@ -135,8 +150,8 @@ def main(obj, orig_bone_name):
mt.head_e.head.y += head_length / 4.0
mt.head_e.tail.y += head_length / 4.0
for i in range(len(neck_chain)):
neck_e = getattr(mt_chain, "neck_%.2d_e" % (i + 1))
for i, attr in enumerate(mt_chain.attr_names):
neck_e = getattr(mt_chain, attr + "_e")
# dont store parent names, re-reference as each chain bones parent.
neck_e_parent = arm.edit_bones.new("MCH-rot_%s" % neck_e.name)
@ -206,8 +221,8 @@ def main(obj, orig_bone_name):
expression_suffix = "/max(0.001,%s)" % "+".join(target_names)
for i in range(len(neck_chain)):
neck_p = getattr(mt_chain, "neck_%.2d_p" % (i + 1))
for i, attr in enumerate(mt_chain.attr_names):
neck_p = getattr(mt_chain, attr + "_p")
neck_p.lock_location = True, True, True
neck_p.lock_location = True, True, True
neck_p.lock_rotations_4d = True
@ -243,3 +258,6 @@ def main(obj, orig_bone_name):
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = head_driver_path + ('["bend_%.2d"]' % (j + 1))
# no blending the result of this
return None

@ -20,6 +20,8 @@ import bpy
from rigify import get_bone_data, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_get, rna_idprop_ui_prop_get
# not used, defined for completeness
METARIG_NAMES = tuple()
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
@ -72,20 +74,39 @@ def metarig_template():
pbone['type'] = 'palm'
def main(obj, orig_bone_name):
arm, palm_pbone, palm_ebone = get_bone_data(obj, orig_bone_name)
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the first in a chain
Expects an array of children sorted with the little finger lowest.
eg.
parent -> [pinky, ring... etc]
'''
arm = obj.data
bone_definition = [orig_bone_name]
palm_ebone = arm.bones[orig_bone_name]
children = [ebone.name for ebone in palm_ebone.children]
children.sort() # simply assume the pinky has the lowest name
bone_definition.extend(children)
return bone_definition
def main(obj, bone_definition, base_names):
arm, palm_pbone, palm_ebone = get_bone_data(obj, bone_definition[0])
children = bone_definition[1:]
# Make a copy of the pinky
# simply assume the pinky has the lowest name
pinky_ebone = arm.edit_bones[children[0]]
ring_ebone = arm.edit_bones[children[1]]
control_ebone = copy_bone_simple(arm, pinky_ebone.name, "palm_control", parent=True)
control_name = control_ebone.name
offset = (arm.edit_bones[children[0]].head - arm.edit_bones[children[1]].head)
offset = (pinky_ebone.head - ring_ebone.head)
control_ebone.head += offset
control_ebone.tail += offset
control_ebone.translate(offset)
bpy.ops.object.mode_set(mode='OBJECT')
@ -180,3 +201,5 @@ def main(obj, orig_bone_name):
child_pbone = obj.pose.bones[children[-1]]
child_pbone.rotation_mode = 'QUATERNION'
# no blending the result of this
return None

@ -20,6 +20,8 @@ import bpy
from rigify 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():
bpy.ops.object.mode_set(mode='EDIT')
@ -84,30 +86,32 @@ def metarig_template():
pbone['type'] = 'spine'
def validate(obj, orig_bone_name):
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.
'''
orig_bone = obj.data.bones[orig_bone_name]
if not orig_bone.parent:
return "expected spine bone '%s' to have a parent" % orig_bone_name
children = orig_bone.children
arm = obj.data
ribcage = arm.bones[orig_bone_name]
pelvis = ribcage.parent
children = ribcage.children
if len(children) != 1:
return "expected spine bone '%s' to have only 1 child for the sine chain" % orig_bone_name
print("expected the ribcage to have only 1 child.")
children_spine = children[0].children_recursive_basename
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
if len(children_spine) == 0:
return "expected '%s' to define a chain of children with its basename (2 or more)" % children[0]
def fk(*args):
main(*args)
return ''
def main(obj, orig_bone_name):
def main(obj, bone_definition, base_names):
from Mathutils import Vector, Matrix, RotationMatrix
from math import radians, pi
@ -115,17 +119,26 @@ def main(obj, orig_bone_name):
# Initialize container classes for convenience
mt = bone_class_instance(obj, ["pelvis", "ribcage"]) # meta
mt.ribcage = orig_bone_name
mt.update()
mt.pelvis = mt.ribcage_e.parent.name
mt.pelvis = bone_definition[0]
mt.ribcage = bone_definition[1]
mt.update()
spine_chain_orig = 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) # probably 'ORG-spine.01' -> 'spine'
spine_chain_len = len(spine_chain_orig)
'''
children = mt.ribcage_e.children
child = children[0] # validate checks for 1 only.
spine_chain_basename = child.basename # probably 'spine'
spine_chain_segment_length = child.length
spine_chain = [child] + child.children_recursive_basename
spine_chain_orig = [child.name for child in spine_chain]
'''
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
@ -177,16 +190,17 @@ def main(obj, orig_bone_name):
# - original (ORG_*)
# - copy (*use original name*)
# - reverse (MCH-rev_*)
spine_chain_attrs = [("spine_%.2d" % (i + 1)) for i in range(len(spine_chain_orig))]
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 = spine_chain_orig[i]
attr = spine_chain_attrs[i] # eg. spine_04
attr = mt_chain.attr_names[i] # eg. spine_04
setattr(mt_chain, attr, spine_chain[i]) # use the new name
@ -203,12 +217,12 @@ def main(obj, orig_bone_name):
# Now we need to re-parent these chains
for i, child_name in enumerate(spine_chain_orig):
attr = spine_chain_attrs[i] + "_e"
attr = ex_chain.attr_names[i] + "_e"
if i == 0:
getattr(ex_chain, attr).parent = mt.pelvis_e
else:
attr_parent = spine_chain_attrs[i-1] + "_e"
attr_parent = ex_chain.attr_names[i-1] + "_e"
getattr(ex_chain, attr).parent = getattr(ex_chain, attr_parent)
# intentional! get the parent from the other paralelle chain member
@ -217,9 +231,9 @@ def main(obj, orig_bone_name):
# ex_chain needs to interlace bones!
# Note, skip the first bone
for i in range(1, len(spine_chain_attrs)): # similar to neck
for i in range(1, spine_chain_len): # similar to neck
child_name_orig = spine_chain_orig[i]
spine_e = getattr(mt_chain, spine_chain_attrs[i] + "_e")
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)
@ -227,7 +241,7 @@ def main(obj, orig_bone_name):
spine_e_parent.tail = spine_e.head + Vector(0.0, 0.0, spine_chain_segment_length / 2.0)
spine_e_parent.roll = 0.0
spine_e = getattr(ex_chain, spine_chain_attrs[i] + "_e")
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
@ -239,8 +253,8 @@ def main(obj, orig_bone_name):
# 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 in range(len(spine_chain_attrs)): # similar to neck
spine_e = getattr(rv_chain, spine_chain_attrs[i] + "_e")
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
@ -326,12 +340,12 @@ def main(obj, orig_bone_name):
# ex.ribcage_p / MCH-wgt_rib_cage
con = ex.ribcage_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = getattr(mt_chain, spine_chain_attrs[-1])
con.subtarget = getattr(mt_chain, mt_chain.attr_names[-1])
con.head_tail = 0.0
con = ex.ribcage_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = getattr(mt_chain, spine_chain_attrs[-1])
con.subtarget = getattr(mt_chain, mt_chain.attr_names[-1])
# mt.pelvis_p / rib_cage
con = mt.ribcage_p.constraints.new('COPY_LOCATION')
@ -347,10 +361,10 @@ def main(obj, orig_bone_name):
prop = rna_idprop_ui_prop_get(mt.ribcage_p, "pivot_slide", create=True)
mt.ribcage_p["pivot_slide"] = 0.5
prop["soft_min"] = 1.0 / len(spine_chain_attrs)
prop["soft_min"] = 1.0 / spine_chain_len
prop["soft_max"] = 1.0
for i in range(len(spine_chain_attrs) - 1):
for i in range(spine_chain_len - 1):
prop_name = "bend_%.2d" % (i + 1)
prop = rna_idprop_ui_prop_get(mt.ribcage_p, prop_name, create=True)
mt.ribcage_p[prop_name] = 1.0
@ -361,9 +375,9 @@ def main(obj, orig_bone_name):
# positioned at the tip.
# reverse bones / MCH-rev_spine.##
for i in range(1, len(spine_chain_attrs)):
spine_p = getattr(rv_chain, spine_chain_attrs[i] + "_p")
spine_fake_parent_name = getattr(rv_chain, spine_chain_attrs[i - 1])
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
@ -375,14 +389,14 @@ def main(obj, orig_bone_name):
# Constrain 'inbetween' bones
# b01/max(0.001,b01+b02+b03+b04+b05)
target_names = [("b%.2d" % (i + 1)) for i in range(len(spine_chain_attrs) - 1)]
target_names = [("b%.2d" % (i + 1)) for i in range(spine_chain_len - 1)]
expression_suffix = "/max(0.001,%s)" % "+".join(target_names)
rib_driver_path = mt.ribcage_p.path_to_id()
for i in range(1, len(spine_chain_attrs)):
for i in range(1, spine_chain_len):
spine_p = getattr(ex_chain, spine_chain_attrs[i] + "_p")
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')
@ -400,7 +414,7 @@ def main(obj, orig_bone_name):
driver.expression = target_names[i - 1] + expression_suffix
fcurve.modifiers.remove(0) # grr dont need a modifier
for j in range(len(spine_chain_attrs) - 1):
for j in range(spine_chain_len - 1):
tar = driver.targets.new()
tar.name = target_names[j]
tar.id_type = 'OBJECT'
@ -410,12 +424,12 @@ def main(obj, orig_bone_name):
# original bone drivers
# note: the first bone has a lot more constraints, but also this simple one is first.
for i in range(len(spine_chain_attrs)):
spine_p = getattr(mt_chain, spine_chain_attrs[i] + "_p")
for i in attr, 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, spine_chain_attrs[i]) # lock to the copy's rotation
con.subtarget = getattr(ex_chain, attr) # lock to the copy's rotation
del spine_p
# pivot slide: - lots of copy location constraints.
@ -425,19 +439,19 @@ def main(obj, orig_bone_name):
con.target = obj
con.subtarget = rv_chain.spine_01 # lock to the reverse location
for i in range(1, len(spine_chain_attrs) + 1):
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 == len(spine_chain_attrs):
attr = spine_chain_attrs[i - 1]
if i == spine_chain_len:
attr = mt_chain.attr_names[i - 1]
else:
attr = spine_chain_attrs[i]
attr = mt_chain.attr_names[i]
con.subtarget = getattr(rv_chain, attr) # lock to the reverse location
if i == len(spine_chain_attrs):
if i == spine_chain_len:
con.head_tail = 1.0
fcurve = con.driver_add("influence", 0)
@ -452,5 +466,8 @@ def main(obj, orig_bone_name):
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = - (i - 1)
mod.coefficients[1] = len(spine_chain_attrs)
mod.coefficients[1] = spine_chain_len
# no support for blending chains
return None