Hi,
this is a personal work in progress doodle i made to begin facial setup using bendybones in Blender 2.79.
todo: easier selection of tweaker controlstodo: creating the macro controls
todo: overall movements like headRotation/jawOpen/eyeControls
todo: better deformations
this is the work in progress python script to add tweaker controls to the bendy bones:
import bpy
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG) #without this info logs wouldnt show in console
#fixed bug if bone had a parent using head_local tail_local
#todo - add to one of the face tools addons or make into its own addon.
#can test it with assets first using snippet
class BendyBoneAnimatorController(object):
"""add animator posability to a single bendy bone segment.
@todo use an input bendy bone for setup
@todo create animator controls for bendy bone
@todo add rotation ability to controls
"""
def __init__(self, bbone, armatureObjName, boneSize=0.3):
"""
@param: bbone - (str) name for bendy bone we want to add animator posability
@param: armatureObjName - (str) name of armature object to add bones to
@param: boneSize - (float) scale for animator control bones smaller number makes it appear smaller
"""
self.armatureObjName = armatureObjName
self.boneSize = boneSize
#need to determine these from input bendy bone
###
uppos = _getStartBonePositions(bbone,armatureObjName)
lowpos = _getEndBonePositions(bbone,armatureObjName)
logger.debug("uppos:")
logger.debug(uppos)
logger.debug("lowpos:")
logger.debug(lowpos)
upHeadPos=uppos[0] #bbone tail
upTailPos=uppos[1]
lowHeadPos=lowpos[0]
lowTailPos=lowpos[1] #bbone head
roll = _getRoll(bbone,armatureObjName)
###
self.upHeadPos = tuple(upHeadPos) #i think there was a bug using Vector
self.upTailPos = tuple(upTailPos)
self.lowHeadPos = tuple(lowHeadPos)
self.lowTailPos = tuple(lowTailPos)
self.roll = roll
#self.segments = segments
rigPrefix = bbone.split('.')[0] #basically same name as bendy bone
side = bbone.split('.')[1] #assumes bendybone has a side
self.headBoneName = rigPrefix+'_'+'head'+'.'+side
self.tailBoneName = rigPrefix+'_'+'tail'+'.'+side
self.midBoneName = bbone
def doIt(self):
self.createBones()
self.scaleControlBones()
self.addConstraint()
self.addAnimatorControls()
def createBones(self):
"""create up and low. the middle parented to up the low not parented to anything
"""
#start with nothing selected
#bpy.ops.object.select_all(action='DESELECT')
arm_obj = bpy.data.objects[self.armatureObjName]
bpy.context.scene.objects.active = arm_obj
#make sure armature in bendy bone view mode
arm_obj.data.draw_type = 'BBONE'
bpy.ops.object.mode_set(mode='OBJECT') #in case started in edit mode and want to update changes
bpy.ops.object.mode_set(mode='EDIT')
#make head bone
headBone = arm_obj.data.edit_bones.new(self.headBoneName)
headBone.head = self.upHeadPos
headBone.tail = self.upTailPos
headBone.use_deform = False
#make tail bone
tailBone = arm_obj.data.edit_bones.new(self.tailBoneName)
tailBone.head = self.lowHeadPos
tailBone.tail = self.lowTailPos
tailBone.use_deform = False
#testing - parent head/tail bones to the parent of midbone
midboneParent = arm_obj.data.edit_bones[self.midBoneName].parent
if midboneParent:
logger.debug("midboneParent:{}".format(midboneParent.name))
arm_obj.data.edit_bones[self.headBoneName].parent = midboneParent
arm_obj.data.edit_bones[self.tailBoneName].parent = midboneParent
#
#parent bbone to headBone
arm_obj.data.edit_bones[self.midBoneName].parent = arm_obj.data.edit_bones[self.headBoneName]
bpy.context.scene.objects.active = arm_obj
bpy.ops.object.mode_set(mode='OBJECT')
#set the handles for bendy bone
arm_obj.pose.bones[self.midBoneName].use_bbone_custom_handles = True
arm_obj.pose.bones[self.midBoneName].bbone_custom_handle_start = arm_obj.pose.bones[self.headBoneName]
arm_obj.pose.bones[self.midBoneName].bbone_custom_handle_end = arm_obj.pose.bones[self.tailBoneName]
def addConstraint(self):
"""add stretch-to constraint on middle bone aiming at end handle tail bone
"""
arm_obj = bpy.data.objects[self.armatureObjName]
bpy.context.scene.objects.active = arm_obj
bpy.ops.object.mode_set(mode='OBJECT')
constraint = arm_obj.pose.bones[self.midBoneName].constraints.new('STRETCH_TO')
constraint.target = arm_obj
constraint.subtarget = self.tailBoneName
def addAnimatorControls(self):
"""
"""
#draw animator controls
#add control curves to appropriate bones
def scaleControlBones(self):
"""
"""
arm_obj = bpy.data.objects[self.armatureObjName]
bpy.context.scene.objects.active = arm_obj
bpy.ops.object.mode_set(mode='OBJECT')
#select the two end bones
_deselectAllBones(self.armatureObjName)
arm_obj.data.bones[self.headBoneName].select = True
arm_obj.data.bones[self.tailBoneName].select = True
#scale them
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.transform.transform(mode='BONE_SIZE', value=(self.boneSize, self.boneSize, self.boneSize,0))
def _getEndBonePositions(bbone,armature):
"""simple method that uses the direction of bbone to determine positions for a posible end control bone
@return list of 2 tuples [startWorldPosition,endWorldPosition]
"""
obj = bpy.data.objects[armature]
bone = obj.data.bones[bbone]
pctbbone_length = 0.25 #1/4 might want to try bigger for a longer control bone
return [ tuple(bone.tail_local), tuple( bone.tail_local + (bone.tail_local-bone.head_local)*(0.25) ) ]
def _getStartBonePositions(bbone,armature):
"""simple method that uses the direction of bbone to determine positions for a posible start control bone
@return list of 2 tuples [startWorldPosition,endWorldPosition]
"""
obj = bpy.data.objects[armature]
bone = obj.data.bones[bbone]
pctbbone_length = 0.25 #1/4 might want to try bigger for a longer control bone
return [ tuple(bone.head_local - (bone.tail_local-bone.head_local)*(0.25)), tuple(bone.head_local) ]
def _getRoll(bone,armature):
"""
@return double - roll of bone
"""
obj = bpy.data.objects[armature]
bpy.context.scene.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
roll = obj.data.edit_bones[bone].roll
#todo restore mode
return roll
def _deselectAllBones(armature):
"""
"""
assert armature
obj = bpy.data.objects[armature]
bpy.context.scene.objects.active = obj
bpy.ops.object.mode_set(mode='OBJECT')
for bone in obj.data.bones:
bone.select=False
def getAllDeformBones(armature):
"""return all deform bones of armature
"""
result = []
obj = bpy.data.objects[armature]
bpy.context.scene.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
result = [bone.name for bone in obj.data.edit_bones if bone.use_deform]
bpy.ops.object.mode_set(mode='OBJECT')#todo return to previous mode
return result
def addControlsToBBones(armature, bbones = []):
"""add animator control bones to bendy bones
@param: armature (str) name of armature object
@param: bbones (list of str) names for bendy bones to add controls to
"""
assert armature
bones = bbones
if not bones:
bones = getAllDeformBones(armature) or []
for bone in bones: #["socket_dn.L"]
bb = BendyBoneAnimatorController(bone,armature)
bb.doIt()
"""
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/riggingTools/faceTools/addControlsToBendyBones.py") #change to path to python file
testTool.addControlsToBBones("Armature")
#bb = testTool.BendyBoneAnimatorController("Bone","Armature") #name of bendybone, name of armature
#bb.doIt()
#print(testTool._getEndBonePositions("Bone","Armature"))
print(testTool._getEndBonePositions("socket_dn.L","Armature"))
print(testTool._getStartBonePositions("socket_dn.L","Armature"))
"""
character design: Alan Stewart (http://alanstewart79 dot blogspot dot com)
modeling/facialSetup: Nathaniel Anozie
Happy Sketching!