Created
June 15, 2024 19:30
-
-
Save nathanielanozie/31faba611bfc405798a8a0bb582bf588 to your computer and use it in GitHub Desktop.
bendy bone animator control class in blender 2.79
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#has some tools for adding animator controls to bendy bones | |
# | |
#tested in blender 2.79 | |
#please modify/use at your own risk | |
import bpy | |
import logging | |
logger = logging.getLogger(__name__) | |
logging.basicConfig(level=logging.DEBUG) #without this info logs wouldnt show in console | |
class BendyBoneAnimatorController(object): | |
"""add animator posability to a single bendy bone segment. currently supports adding two animator controls per bendy bone | |
example usage: | |
#bb = BendyBoneAnimatorController("Bone","Armature") #name of bendybone, name of armature | |
#bb.doIt() | |
#here specifying the names used for the animator controllers (they need to be unique in the armature) | |
bb = BendyBoneAnimatorController("Bone","Armature", headControllerName="arm_bottom_anim", tailControllerName="arm_top_anim") #name of bendybone, name of armature | |
bb.doIt() | |
""" | |
def __init__(self, bbone, armatureObjName, boneSize=0.3, headControllerName='', tailControllerName='', verbose=False): | |
""" | |
@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 | |
@param headControllerName (str) optional name for head animator control. if none provided it computes it | |
@param tailControllerName (str) optional name for tail animator control. if none provided it computes it | |
@param verbose (bool) whether to show debug prints | |
""" | |
self.armatureObjName = armatureObjName | |
self.boneSize = boneSize | |
self._verbose = verbose | |
#vaildate possible controller names | |
allArmatureBones = self._getArmatureBones() or [] | |
if headControllerName and headControllerName in allArmatureBones: | |
raise RuntimeError("please specify a unique head controller name") | |
if tailControllerName and tailControllerName in allArmatureBones: | |
raise RuntimeError("please specify a unique tail controller name") | |
# | |
#need to determine these from input bendy bone | |
### | |
uppos = _getStartBonePositions(bbone,armatureObjName) | |
lowpos = _getEndBonePositions(bbone,armatureObjName) | |
if self._verbose: | |
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 = self._getRigPrefix(bbone) | |
side = self._getRigSide(bbone) | |
self.headBoneName = rigPrefix+'_'+'head'+'.'+side if not headControllerName else headControllerName | |
self.tailBoneName = rigPrefix+'_'+'tail'+'.'+side if not tailControllerName else tailControllerName | |
self.midBoneName = bbone | |
def _getRigPrefix(self, bone): | |
""" | |
@return (str) | |
""" | |
result = '' | |
if '.' in bone: | |
result = bone.split('.')[0] | |
else: | |
result = "test"+bone | |
return result | |
def _getRigSide(self, bone): | |
""" | |
@return (str) | |
""" | |
if '.' in bone: | |
result = bone.split('.')[-1] #the last item | |
else: | |
result = "testSide" | |
return result | |
def _getArmatureBones(self): | |
"""get all armature bones names | |
@return (list of str) | |
""" | |
return [b.name for b in bpy.data.objects[self.armatureObjName].data.bones] | |
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: | |
if self._verbose: | |
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): | |
"""todo | |
""" | |
#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(self): | |
"""simple method that uses the direction of bbone to determine positions for a posible end control bone | |
@return list of 2 tuples [startWorldPosition,endWorldPosition] | |
""" | |
bbone = self.midBoneName | |
armature = self.armatureObjName | |
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(self): | |
"""simple method that uses the direction of bbone to determine positions for a posible start control bone | |
@return list of 2 tuples [startWorldPosition,endWorldPosition] | |
""" | |
bbone = self.midBoneName | |
armature = self.armatureObjName | |
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(self): | |
""" | |
@return double - roll of bone | |
""" | |
bbone = self.midBoneName | |
armature = self.armatureObjName | |
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(self): | |
""" | |
""" | |
armature = self.armatureObjName | |
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 = [], verbose=False): | |
"""add animator control bones to bendy bones | |
@param armature (str) name of armature object | |
@param bbones (list of str) optional names for bendy bones to add controls to. if none supplied it uses all bones | |
@param verbose (bool) whether to show debug prints | |
""" | |
assert armature | |
bones = bbones | |
if not bones: | |
bones = getAllDeformBones(armature) or [] | |
for bone in bones: #["socket_dn.L"] | |
bb = BendyBoneAnimatorController(bone, armature, verbose=verbose) | |
bb.doIt() | |
#added support for artist provided animator control names | |
#fixed logic in auto computing animator control names | |
#some coding redesign moving some global methods that use same data to the bendy bone controller class | |
#fixed bug if bone had a parent using head_local tail_local | |
""" | |
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") | |
#testTool.addControlsToBBones("Armature", bbones=["Bone", "Bone.001"], verbose=True) | |
#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")) | |
""" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment