Tuesday, February 17, 2026

some python doodles to help with rigging in Blender 2.79

these are some miscellaneous doodles and snippets i used when building the facial rigs in the rigging page of this blog.  They would need lots of editing to be usable in different situations and will need tweaking to get them working with higher blender versions.  They have some examples with looping through armature bones, editing bendy bone attributes, putting bones in layers etc.   please modify/use at your own risk.

 

#changing all bones rotation mode to euler xyz (assumes in pose mode)
####
import bpy
#set deform off on selected edit bones
armatureName = bpy.context.object.name #"Armature"
for pb in bpy.data.objects[armatureName].pose.bones:
    pb.rotation_mode = 'XYZ' #bpy.context.object.pose.bones["ANIM_spine_end.C"].rotation_mode = 'XYZ'
####



#putting some bones in last layer to hide for animation
######
#ex put all non anim bones in last layer
import bpy
armatureName = bpy.context.object.name
bonesToHideNames = [b.name for b in bpy.data.objects[armatureName].data.bones if not b.name.startswith('ANIM_')]

#hide backend bones
def UT_PutInLayer(items=[], layer=32, armatureName=None ):
    """put given bones into specified int layer. i think max is 32
    """ 
    layerValues = [False]*32
    layerValues[layer-1] = True

    armObj = bpy.data.objects[armatureName]
    bpy.context.scene.objects.active = armObj    
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    
    for it in items:
        itObj = armObj.data.edit_bones[it] #assumes it exists
        itObj.layers = layerValues
        #put widgets in last layer so they are not seen
        #rootWidgetShape.layers = [False]*19+[True]

UT_PutInLayer(items=bonesToHideNames, layer=32, armatureName=armatureName)
#########





#lock rotation on selected bones (assumes in pose mode)
####
import bpy
lockRotation = True
lockScale = True

#set deform off on selected edit bones
armatureName = bpy.context.object.name #"Armature"
for bone in bpy.data.objects[armatureName].data.bones:
    #skip bones not selected
    if not bone.select:
        continue
    pb = bpy.data.objects[armatureName].pose.bones[bone.name]
    if lockRotation:
        pb.lock_rotation = [True, True, True]
    if lockScale:
        pb.lock_scale = [True, True, True]

####




#parenting some geo to some bones. assumes armature selected in object mode
#change this to names that exist. first is deformation bone, second is list of geo names to parent to bone
import bpy
boneToGeoDict = {
    "ANIM_base.C":["base_geo.C","tail_sphere_a_geo.C"],
    "ANIM_tail_a.C":["tail_a_geo.C","tail_sphere_b_geo.C"],
    "ANIM_tail_b.C":["tail_b_geo.C","tail_sphere_c_geo.C"],
    "ANIM_tail_c.C":["tail_c_geo.C"]
}
armatureName = bpy.context.object.name #"Armature"

def parentGeoToBone(amt = None, geo=None, bone=None):
    """#3. parent cube geo to a single bone, cube and bone exist
    inputs object, object, str
    lots of mode switching cause i think doing without ops will be longer
    """
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    amt.select = True
    bpy.context.scene.objects.active = amt
    bpy.ops.object.mode_set(mode='EDIT')
    amt.data.edit_bones.active = amt.data.edit_bones[bone]
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    
    geo.select = True
    amt.select = True
    bpy.context.scene.objects.active = amt
    bpy.ops.object.parent_set(type='BONE',keep_transform=True)

for bone, geos in boneToGeoDict.items():
    for geo in geos:
        parentGeoToBone(amt=bpy.data.objects[armatureName], geo=bpy.data.objects[geo], bone=bone)

####



###set all mesh objects selectability. helpful so animator not accidentally selecting geo
import bpy
isSelectable = False #change to True for setting all meshes to selectable
meshNames = [obj.name for obj in bpy.data.objects if obj.type=="MESH"]
#print(meshNames)
for mesh in meshNames:
    meshObj = bpy.data.objects[mesh]
    meshObj.hide_select = not isSelectable
####




import bpy
#set deform off on selected edit bones
armatureName = "Armature"
for eb in bpy.data.armatures[armatureName].edit_bones:
    if not eb.select:
        continue
    eb.use_deform = False




#add .L suffix to selected bones
import bpy
#set deform off on selected edit bones
armatureName = bpy.context.object.name
for eb in bpy.data.armatures[armatureName].edit_bones:
    if not eb.select:
        continue
    curName = eb.name
    newName = "{0}.L".format(curName) #add left suffix
    eb.name = newName


#snippet to add one armature to different scene:
#(use ui to append armature data)
import bpy
bpy.data.objects.new("Armature", bpy.data.armatures['Armature'])
bpy.context.scene.objects.link(bpy.data.objects['Armature']) #will show bones in viewport

import bpy
#remove right side of armature
#select right side of armature in edit mode
armatureName = "Armature"
for eb in bpy.data.armatures[armatureName].edit_bones:
    if eb.matrix.translation.x >= 0:
        continue
    eb.select = True
    
    
import bpy
#select bendy bone ends to remove
armatureName = "Armature"
for eb in bpy.data.armatures[armatureName].edit_bones:
    if ("_head" in eb.name) or ("_tail" in eb.name):
        eb.select = True



import bpy
#select lid span bones to remove
armatureName = "Armature"
for eb in bpy.data.armatures[armatureName].edit_bones:
    if ("eyeLid_" in eb.name):
        eb.select = True

##for fitting helper (need to do fitting in edit mode so pose bone gets updated automatically)
import bpy
#reset curve offsets on selected bone in edit mode
armatureName = bpy.context.object.name
ebones = [eb for eb in bpy.data.armatures[armatureName].edit_bones if eb.select]
for eb in ebones:
    for attr in ["bbone_curveinx", "bbone_curveoutx","bbone_curveiny", "bbone_curveouty"]:
        setattr(eb,attr,0)


#ctrl + alt + s > mouse move #to scale bendy bone in edit mode

#testing stuff with actions
#removing all actions in scene
>>> for action in bpy.data.actions:
...     bpy.data.actions.remove(action)



#loosing actions set on constraints. so after appending action also want to set action names in constraints.
import bpy

#create a dictionary.  boneName:(constraintName, actionName)
armatureName = "Armature"
result = dict()

for pb in bpy.data.objects[armatureName].pose.bones:
    #action constraints 
    actionConstraints = [cnt for cnt in pb.constraints if cnt.type == "ACTION"]
    if not actionConstraints:
        continue
    #loop constraints getting action name
    for cnt in actionConstraints:
        #there can be more than one constraint for a single bone so need to store a list
        result.setdefault(pb.name, []).append((cnt.name, cnt.action.name))
print(result)
        

#setting the constraints in scene to use missing actions. assumes have appended actions already
import bpy
result = {} #set this dictionary
armatureName = "Armature"

for k,val in result.items():
    for v in val:
    	cnt, actionName = v
    	pb = bpy.data.objects[armatureName].pose.bones[k]
    	pb.constraints[cnt].action = bpy.data.actions[actionName]


##appending an armature object brings in constraints and actions
#v1 (skips bone if cannot find bone in destination armature)
import bpy
#copy constraint from source armature to destination

sourceArmature = "Armature.001"
destinationArmature = "Armature"

for pb in bpy.data.objects[destinationArmature].pose.bones:
    if pb.name not in bpy.data.objects[sourceArmature].pose.bones:
        print("could not find {} skipping".format(pb.name))
        continue
    src_pb = bpy.data.objects[sourceArmature].pose.bones[pb.name]
    srcActionConstraints = [cnt for cnt in src_pb.constraints if cnt.type == "ACTION"]
    if not srcActionConstraints:
        continue
    for cnt in srcActionConstraints:
        new_cnt = pb.constraints.new(cnt.type)
        #attributes of constraint
        for prop in dir(cnt):
            #dont copy target
            if prop == "target":
                setattr(new_cnt, prop, bpy.data.objects[destinationArmature])
                continue
            try:
                setattr(new_cnt, prop, getattr(cnt, prop))
            except:
                pass




#v0
##copying constraints/and/Actions from one armature to a different armature with identical bone names
import bpy
#copy constraint from source armature to destination

sourceArmature = "Armature"
destinationArmature = "Armature.001"

for pb in bpy.data.objects[destinationArmature].pose.bones:
    src_pb = bpy.data.objects[sourceArmature].pose.bones[pb.name]
    srcActionConstraints = [cnt for cnt in src_pb.constraints if cnt.type == "ACTION"]
    if not srcActionConstraints:
        continue
    for cnt in srcActionConstraints:
        new_cnt = pb.constraints.new(cnt.type)
        #attributes of constraint
        for prop in dir(cnt):
            #dont copy target
            if prop == "target":
                setattr(new_cnt, prop, bpy.data.objects[destinationArmature])
                continue
            try:
                setattr(new_cnt, prop, getattr(cnt, prop))
            except:
                pass

#inspired by
#blender dot stack exchange dot com ‚How to copy constraints from one bone to another post


#########
#ungroup all channels for selected action
import bpy
obj = bpy.context.active_object
action = obj.animation_data.action

#get fcurve objects to ungroup
fcurvesGroupObjsToUngroup = []
for fcurve in action.fcurves:
    if not fcurve.group:
        continue
    fcurvesGroupObjsToUngroup.append(fcurve.group)

#ungroup the given fcurves
for fcurveGroupObj in fcurvesGroupObjsToUngroup:
    if not fcurveGroupObj.name in action.groups.keys():
        continue
    action.groups.remove(fcurveGroupObj)
#to refresh view
curFrame = bpy.context.scene.frame_current
bpy.context.scene.frame_set(curFrame)
#####


#mirror pose of selected bones. (example for jaw action. first copy pose, then paste pose flipped)
import bpy
bpy.ops.pose.copy()
bpy.ops.pose.paste(flipped=True)


#select all right side bones of current action
import re
import bpy
obj = bpy.context.active_object
action = obj.animation_data.action

dataPaths = [fcurve.data_path for fcurve in action.fcurves]
#print(dataPaths)
bonesInAction = [re.match('.*\["(.*)"\].*', dataPath).groups()[0] for dataPath in dataPaths] #'pose.bones["Bone"].location'
bonesInAction = list(set(bonesInAction)) #remove duplicates
bonesInActionLeftSide = [b for b in bonesInAction if b.endswith('.L')]
#print("bonesInAction", bonesInAction)
bonesRightSide = [bone.replace('.L', '.R') for bone in bonesInActionLeftSide]
print('right side', bonesRightSide)
#deselect all pose bones
bpy.ops.pose.select_all(action='DESELECT')
for b in bonesRightSide:
    obj.pose.bones[b].bone.select = True
########


#go to frame 0 of current action
bpy.context.scene.frame_set(0)

#go to default ArmatureAction



######
#save out vertex selection
import bpy
verticesSelected = []
meshName = 'gorilla'
verticesSelected = [v.index for v in bpy.data.objects[meshName].data.vertices if v.select]

#reselect vertex selection (need to be in object mode)
#verticesSelected = [] #paste in vertex ids from above step
verticesSelected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 578, 579, 581, 616, 660, 661, 703, 704, 705, 773, 774, 775, 776, 781, 782, 828, 830, 937, 1279, 1280, 1281, 1282, 1283, 1305, 1306, 1307, 1308, 1309, 1323, 1324, 1325, 1326, 1327, 1342, 1344, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1365, 1366, 1367, 1368, 1369, 1426, 1437, 1554, 1555, 1556, 1557, 1558, 1559, 1560, 1561, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 1569, 1570, 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, 1589, 1590, 1591, 1592, 1593, 1594, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1627, 1628, 1629, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, 1639, 1640, 1641]
meshName = 'gorilla'
for v in bpy.data.objects[meshName].data.vertices:
    if v.index in verticesSelected:
        v.select = True
    else:
        v.select = False
#######




### select all tail and head of given bones
import bpy
mainBones = ["eyeLid_up", "eyeLid_dn"] #put bones here
#eyeLid_up_a.L eyeLid_up_b.L

armatureName = "Armature"
#start by deselecting all bones
for eb in bpy.data.armatures[armatureName].edit_bones:
    eb.select = False
for eb in bpy.data.armatures[armatureName].edit_bones:
    for bone in mainBones:
        #should include tail
        if eb.name.startswith(bone):
            eb.select = True
            break
###




###copy edit bone positions from source armature to destination armature
#copy:
#head
#tail
#roll
#curve xy offsets
###

import bpy

sourceArmature = "Armature_b"
destinationArmature = "Armature"

def _enterEdit(armatureName):
    bpy.data.objects[armatureName].hide = False 
    bpy.ops.object.mode_set(mode='OBJECT')
    #enter edit mode of given armature
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[armatureName].select = True
    bpy.context.scene.objects.active = bpy.data.objects[armatureName]
    bpy.ops.object.mode_set(mode='EDIT')


destinationBoneNames = [b.name for b in bpy.data.armatures[destinationArmature].bones]
for b in destinationBoneNames:
    
    if b not in bpy.data.armatures[sourceArmature].bones:
        print("could not find {} skipping".format(b))
        continue
    
    #enter edit mode source to get position
    _enterEdit(sourceArmature)

    src_eb = bpy.data.armatures[sourceArmature].edit_bones[b]
    src_head = src_eb.head.copy()
    src_tail = src_eb.tail.copy()
    src_roll = src_eb.roll
    src_bboneAttrsDict = dict()

    bboneAttrs = ['bbone_curveinx', 
                'bbone_curveoutx',
                'bbone_curveiny',
                'bbone_curveouty']
    for attr in bboneAttrs:
        src_bboneAttrsDict[attr] = eval("src_eb.{0}".format(attr))

    #enter edit mode destination to apply
    _enterEdit(destinationArmature)
    eb = bpy.data.armatures[destinationArmature].edit_bones[b]
    #head
    eb.head[0] = src_head[0]
    eb.head[1] = src_head[1]
    eb.head[2] = src_head[2]
    #tail
    eb.tail[0] = src_tail[0]
    eb.tail[1] = src_tail[1]
    eb.tail[2] = src_tail[2]
    #roll
    eb.roll = src_roll
    #bbone attrs
    for at, val in src_bboneAttrsDict.items():
        setattr(eb, at, val)

#end copy edit bone positions from one armature to another



#remove all fcurves of bones that dont exist anymore. this could happen if addded/removed bones in the middle of action creation
import bpy
import re
armatureName = 'lipArmature'
action = bpy.data.objects[armatureName].animation_data.action #use selected action
fcurvesToRemove = []
for fcurve in action.fcurves:
    #print(fcurve.data_path)
    dataPath = fcurve.data_path 
    boneName = re.match('.*\["(.*)"\].*', dataPath).groups()[0]
    if boneName not in bpy.data.objects[armatureName].data.bones:
        fcurvesToRemove.append(fcurve)
fcurvesToRemove = list(set(fcurvesToRemove)) #remove duplicates
print('removing fcurves:')
print(fcurvesToRemove)
for fcrv in fcurvesToRemove:
    action.fcurves.remove(fcrv)
#### 
 
Thanks for looking