Saturday, March 25, 2023

doodle facial rig setup

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 controls
todo: 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!