Code Snippets

Auto Rigging Script

""" Methods from my custom Auto-Rigging script. I'm revamping most of these. To nuch functionality in a single function gets hard to wrap your head around. Also, by far the best syntax highlighting theme on the planet.... behold "gruvbox-dark-hard" and "gruvbox-dark-pale" on a sunny day.
And if you code for blender, you should use Blender-notebook. Jupyter notebook with a blender kernel. """

  • process is basically using prepositioned empty objects to create
  • bones at specific locations, link these bones in a hierarchy
  • add controls, add constraints, add drivers, add physics bones, skin, transfer weights from default mesh
  • Animate

import bpy
import bmesh
import math as m
import fnmatch

character = "MyCharacter"
rig = f"{character}_Armature"
origin = (0,0,0)
emplist = bpy.data.collections['EmptyCollection'].all_objects

###############################################
#### Bone heirarchy construction functions ####
###############################################

def findEmpty(e):
    for em in emplist:
        if em.name == e:
            return em.location

def SelectReturn(b):
    for bone in Armature.data.edit_bones:
        if fnmatch.fnmatchcase(bone.name, b):
            return bone
                
def SelectListReturn(*args):
    blist = []
    for bone in Armature.data.edit_bones:
        for a in args:
            if fnmatch.fnmatchcase(bone.name, a):
                blist.append(bone)
    return blist

def SelectSymmetrize():
    for bone in Armature.data.edit_bones:
        if fnmatch.fnmatchcase(bone.name, '*l'):
            bone.select = True
            bone.select_head = True
            bone.select_tail = True
    bpy.ops.armature.symmetrize()


def AddConstraint(con, activeBone, subtar, polsubtar=None, limitcon=None, PHY_tar=None, COLL_tar=None):
	
    ##  Keyword args (**kwargs) are checks. Different constraints have different data fields that need to be filled.
    ##  if any of the **kwargs are true (not None) then the if conditionals will redirect the process to meet the 
    ##  needs of that constraint.
    ##  Or.... I could just break these functions (methods in python) into different function for each use case
    
    if limitcon != None:
        if limitcon == 'LOC':
            pbone = bpy.context.object.pose.bones[activeBone]
            constraint = pbone.constraints.new(con)
        elif limitcon == 'ROT':
            pbone = bpy.context.object.pose.bones[activeBone]
            constraint = pbone.constraints.new(con)
    else:
        pbone = bpy.context.object.pose.bones[activeBone]
        if COLL_tar != None:
            target = bpy.data.objects[COLL_tar]
            subtarget = bpy.data.objects[COLL_tar].vertex_groups[subtar]
        elif PHY_tar != None:
            target = bpy.data.objects[PHY_tar]
            subtarget = bpy.data.objects[PHY_tar].vertex_groups[subtar]
        else:
            target = bpy.context.scene.objects.get(rig)
            subtarget = bpy.context.scene.objects.get(rig).pose.bones.get(subtar)
        constraint = pbone.constraints.new(con)
        constraint.target = target
        constraint.subtarget = subtarget.name
        
        if polsubtar != None:
            poletarget = bpy.context.scene.objects.get(rig)
            polesubtarget = bpy.context.scene.objects.get(rig).pose.bones.get(polsubtar)
            constraint.pole_target = poletarget
            constraint.pole_subtarget = polsubtar
            
    return constraint
    
### def SymmetrizeConstraints() - I refactored from someone elses code I found on stackoverflow or one of those sites
### there's no point spending hours re-inventing the wheel when someone else already did it, and posted it.
### HINT, HINT...

def SymmetrizeConstraints():
    copyFrom = "l"
    pBones = [ b for b in bpy.context.object.pose.bones if b.name[-2:] == "_%s" % copyFrom ]

    for b in pBones:
        for c in b.constraints:
            otherSide = "r" if copyFrom == "l" else "l"
            otherBone = bpy.context.object.pose.bones[ 
                b.name.replace( "_%s" % copyFrom, "_%s" % otherSide ) 
            ]

            nc = otherBone.constraints.new( c.type )

            for prop in dir( c ):
                # This is fairly ugly and dirty, but quick and does the trick...
                try:
                    constrProp = getattr( c, prop )
                    if type( constrProp ) == type(str()) and constrProp[-2:] in ["_l", "_r"]:
                        # Replace string property values from L to R and vice versa
                        oppositeVal = constrProp.replace("_l", "_r") if constrProp[-2:] == "_l" else constrProp.replace("_r", "_l")
                        setattr( nc, prop, oppositeVal )
                    elif 'max_' in prop and 'LIMIT_LOCATION' not in c.type:
                        setattr( nc, prop, getattr( c, prop.replace( 'max', 'min' ) ) * -1 )
                    elif 'min_' in prop and 'LIMIT_LOCATION' not in c.type:
                        setattr( nc, prop, getattr( c, prop.replace( 'min', 'max' ) ) * -1 )
                    elif prop == 'influence':
                        # Influence 0 becomes 1 and 1 becomes 0
                        setattr( nc, prop, abs( constrProp) )
                    elif prop == 'distance':
                        # distance stays the same
                        setattr( nc, prop, constrProp )
                    elif prop == 'rest_length':
                        # Copy original length
                        setattr( nc, prop, constrProp )
                    elif prop == 'head_tail':
                        # Copy original length
                        setattr( nc, prop, constrProp )
                    elif prop == 'chain_count':
                        # Copy original length
                        setattr( nc, prop, constrProp )
                    elif prop == 'iterations':
                        # Copy original length
                        setattr( nc, prop, constrProp )
                    elif prop == 'weight':
                        # Copy original length
                        setattr( nc, prop, constrProp )
                    elif type( constrProp ) in [ type( float() ), type( int() ) ] and prop != 'pole_angle' and c.type != 'LIMIT_LOCATION':
                        # Invert float and int values ( mult by -1 )
                        setattr( nc, prop, constrProp * -1 )
                    elif prop == 'pole_angle':
                        # invert original in degrees
                        if constrProp==m.radians(180): 
                            setattr( nc, prop, m.radians(0))
                        elif constrProp==m.radians(0): 
                            setattr( nc, prop, m.radians(180))
                    else:
                        # Copy all other values as they are
                        setattr( nc, prop, constrProp )
                except:
                    pass

def constraintFix(bone, const, prop, value):
    
    code = f"bpy.context.object.pose.bones['{bone}'].constraints['{const}'].{prop} = {value}"
    exec(code)
    
    
##################################
#### Driver Linking Functions ####
##################################

## bone:bone the driver is on, drivenChannel:property channel to drive, tarBone:driving obj or bone
## tarChannel:driving channel on tarbone, space:coordinate space, exp:driver expression, 
## custVar:custom driver var(ex. Distance), varCount:number of non-custom variables,
## package:string path to constraint property to add driver, channelIndex:vector channel (xyz) to add driver..if needed
## vtype:driver variable type, dtype:Driver type (first drop down menu in driver editor)

def AddDriver(bone, drivenChannel, tarBone, tarChannel, space, exp, custVar=[], varCount=1,\
                package=None, channelIndex=-1, vtype='TRANSFORMS', dtype='SCRIPTED'):
    if package!=None:
        con = bpy.data.objects[rig]
        driv = con.driver_add(package)
        driv.driver.type = dtype
        driv.driver.expression = exp
    else:
        driv = bpy.data.objects[rig].pose.bones[bone].driver_add(drivenChannel, channelIndex)
        driv.driver.type = dtype
        driv.driver.expression = exp
    
    if varCount>1:
        for v in range(varCount): 
            var = driv.driver.variables.new()
            var.name = 'var'+str(v)
            var.type = vtype
            var.targets[0].id = bpy.data.objects[rig]
            var.targets[0].bone_target = tarBone[v]
            var.targets[0].transform_type = tarChannel[v]
            var.targets[0].transform_space = space[v]
            
    elif varCount==1:
        var = driv.driver.variables.new()
        var.name = 'var0'
        var.type = vtype
        var.targets[0].id = bpy.data.objects[rig]
        var.targets[0].bone_target = tarBone
        var.targets[0].transform_type = tarChannel
        var.targets[0].transform_space = space
    else:
        pass
    
    if custVar != []:
        if len(custVar)==5:
            var = driv.driver.variables.new()
            var.name = 'var'+str(len(driv.driver.variables)-1)
            var.type = custVar[0]
            var.targets[0].id = bpy.data.objects[rig]
            var.targets[0].bone_target = custVar[1]
            var.targets[0].transform_space = custVar[2]
            var.targets[1].id = bpy.data.objects[rig]
            var.targets[1].bone_target = custVar[3]
            var.targets[1].transform_space = custVar[4]
        elif len(custVar)==3:
            var = driv.driver.variables.new()
            var.name = 'var'+str(len(driv.driver.variables)-1)
            var.type = custVar[0]
            var.targets[0].id = bpy.data.objects[rig]
            var.targets[0].bone_target = custVar[1]
            var.targets[1].id = bpy.data.objects[rig]
            var.targets[1].bone_target = custVar[2]
        else:
            pass
        
    return driv
    
def AddPropertyDriver(bone, drivenChannel, tarId, tarDataPath, exp,\
                    varCount=1, package=None, channelIndex=-1, vtype='SINGLE_PROP', dtype='SCRIPTED'):
    
    if package!=None:
        con = bpy.data.objects[rig]
        driv = con.driver_add(package)
        driv.driver.type = dtype
        driv.driver.expression = exp
    else:
        driv = bpy.data.objects[rig].pose.bones[bone].driver_add(drivenChannel, channelIndex)
        driv.driver.type = dtype
        driv.driver.expression = exp
    
    if varCount>1:
        for v in range(varCount): 
            var = driv.driver.variables.new()
            var.name = 'var'+str(v)
            var.type = vtype
            var.targets[v].id = bpy.data.objects[tarId[v]]
            var.targets[v].data_path = tarDataPath[v]
    else:
        var = driv.driver.variables.new()
        var.name = 'var'
        var.type = vtype
        var.targets[0].id = bpy.data.objects[tarId]
        var.targets[0].data_path = tarDataPath
    
    return driv

def Add_PHY_PropertyDriver(mesh, mod, prop, tarId, tarDataPath, vtype='SINGLE_PROP', dtype='AVERAGE'):
    
    package = f'modifiers["{mod}"].settings.{prop}'
    con = bpy.data.objects[mesh]
    driv = con.driver_add(package)
    driv.driver.type = dtype
    
    var = driv.driver.variables.new()
    var.name = 'var'
    var.type = vtype
    var.targets[0].id = bpy.data.objects[tarId]
    var.targets[0].data_path = tarDataPath
    
    return driv
    
############################################
########## Bone Physics Functions ##########
############################################


### months later after not looking at this code... I really should have commented this function more, really...

def add_Physics(parbone, PHYgroup):

    bpy.context.scene.cursor.location = (0,0,0)    
    parentBoneLOC = bpy.data.objects[rig].matrix_world @ \
                    bpy.data.objects[rig].pose.bones[parbone].matrix.to_translation()  ## get the world space location in ref. to parent bone
                    
    bpy.ops.mesh.primitive_cube_add(enter_editmode=True)  ## create a primitive cube
    PHy_mesh = 'PHY_mesh_'+ parbone  ## save string name of current physics group to a var
    bpy.context.active_object.name = PHy_mesh  ## name the primitive cube as mesh of physics group

    mesh = bmesh.from_edit_mesh(bpy.context.object.data)  ## turn primitive cube to bmesh object

    if mesh.select_mode != 'VERT':
        bpy.ops.mesh.select_mode(type='VERT')  ## switch to vertex mode of cube, now bmesh obj

    bpy.ops.mesh.select_all(action='SELECT')  ## select all verts of cube
    bpy.ops.mesh.merge(type='CENTER')  ## colapse/merge all verts down to one

    bonelist = []  ## create empty list
    mesh.verts.ensure_lookup_table()  ## ensure the vert lookup table is up to date? read that you should do this frequent working with bmesh
    for bone in bpy.data.objects[rig].pose.bones:  ##  loop thru all the pose bones of the rig and append() all bones of this physics group to 
        if PHYgroup in bone.name:				   ##  the empty list you created
            bonelist.append(bone.name)

    fm = bpy.data.objects[rig].matrix_world @ bpy.data.objects[rig].pose.bones[bonelist[0]].matrix  ## get worldspace location of first bone in list 
    fm = fm.to_translation()  ## previous line converts localspace to worldspace....this function converts matrix coordinates to vector... 
    mesh.verts.ensure_lookup_table()
    mesh.verts[0].co = fm  ## relocate your single vert of mesh (simulation obj) to that bone, just the vert, not the obj
        
    bpy.ops.object.vertex_group_add()  ## create a new vertex group for this sim obj
    bpy.context.object.vertex_groups.active.name = 'Vgrp_' + bonelist[0]  ## name vertex group
    bpy.ops.object.vertex_group_assign()  ## assign single vert to vertex group

    if len(bonelist) > 1:           ##  ok, this loop goes thru all the physic bones in our list, duplicates the sim obj *Vert*, now 1 obj 2 verts 
        for i in range(1, len(bonelist)):     ##  and so on for each iteration of the loop, removes the new vert from it's original vertex group and  
            mesh.verts.ensure_lookup_table()		##  creates a new one, relocates new vert to the current bone being processed in the loop 
            mesh.verts[i-1].select_set(True)		##  and loop thru again til done
            bpy.ops.mesh.duplicate_move(MESH_OT_duplicate={"mode":1},\
                TRANSFORM_OT_translate={"value":(0, 0, 0), "orient_axis_ortho":'X',\
                "orient_type":'GLOBAL', "orient_matrix":((0, 0, 0), (0, 0, 0), (0, 0, 0)),\
                "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False),\
                "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH',\
                "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False,\
                "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False,\
                "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False,\
                "remove_on_cancel":False, "view2d_edge_pan":False, "release_confirm":False, "use_accurate":False,\
                "use_automerge_and_split":False})
            for v in mesh.verts:
                v.select_set(False)
            mesh.verts.ensure_lookup_table()
            mesh.verts[i].select_set(True)
            bpy.ops.object.vertex_group_remove_from()
            bpy.ops.object.vertex_group_add()
            bpy.context.object.vertex_groups.active.name = 'Vgrp_' + bonelist[i]
            bpy.ops.object.vertex_group_assign()

            fm = bpy.data.objects[rig].matrix_world\
                 @ bpy.data.objects[rig].pose.bones[bonelist[i]].matrix
            fm = fm.to_translation()
            mesh.verts.ensure_lookup_table()
            mesh.verts[i].co = fm
            
                
    bpy.ops.object.mode_set(mode='OBJECT')  ## get out of edit mode, back to object
    bpy.context.scene.cursor.location = parentBoneLOC  ## move the 3d cursor to the parent bone of the physics bones hierarchy
    bpy.ops.object.origin_set(type='ORIGIN_CURSOR')  ## Set the origin of the sim obj to the location of parent bone

    bpy.ops.object.select_all(action='DESELECT')  ## deselect all
    bpy.data.objects[PHy_mesh].select_set(True)  ## select the simulation mesh
    bpy.ops.object.modifier_add(type='SOFT_BODY')  ## add a softbody modifier
    bpy.data.objects[PHy_mesh].modifiers["Softbody"].settings.friction = 13  ## change friction settings
    bpy.data.objects[PHy_mesh].modifiers["Softbody"].settings.mass = .2  ## ## change mass settings

    bpy.ops.object.select_all(action='DESELECT')  ## deselect all
    bpy.data.objects[rig].select_set(True)  ## select the rig object
    bpy.context.view_layer.objects.active = bpy.data.objects[rig]  ## make the rig object the active object
    bpy.ops.object.mode_set(mode='POSE')  ## switch to pose mode

    for bone in bonelist:    ## matching names and adding constraints to main and aim bones of current physics group
        if fnmatch.fnmatchcase(bone, '*MAIN_l'):
            for bon in bonelist:
                if fnmatch.fnmatchcase(bon, '*AIM_l'):
                    boneVarDamp = AddConstraint('DAMPED_TRACK', bone, bon)
                    boneVarDamp.name = 'PHY_DAMP_' + bone
                    boneVarDamp.head_tail = 0
                    boneVarDamp.track_axis = 'TRACK_Y'
                    boneVarDamp.influence = 1
                    
        if fnmatch.fnmatchcase(bone, '*MAIN_r'):
            for bon in bonelist:
                if fnmatch.fnmatchcase(bon, '*AIM_r'):
                    boneVarDamp = AddConstraint('DAMPED_TRACK', bone, bon)
                    boneVarDamp.name = 'PHY_DAMP_' + bone
                    boneVarDamp.head_tail = 0
                    boneVarDamp.track_axis = 'TRACK_Y'
                    boneVarDamp.influence = 1
        
        ##  copy location constraint of physics bone to sim mesh vert at its location
        ##  so physics bones are following their respective vert via custom vertex groups we made for each vert
        ##  and each vert is effected by softbody dynamics.... In short "makes things shake and jiggle with motion"
        ##  other constraints keep the motion of the physics within realistic ranges
        
        boneVarloc = AddConstraint('COPY_LOCATION', bone, 'Vgrp_' + bone, PHY_tar= PHy_mesh) 
        boneVarloc.name = 'PHY_LOC_' + bone									
        boneVarloc.use_x = True
        boneVarloc.use_y = True
        boneVarloc.use_z = True
        boneVarloc.use_offset = False
        boneVarloc.target_space = 'WORLD'
        boneVarloc.owner_space = 'WORLD'
        if 'MAIN_' in bone:
            boneVarloc.influence = .7
        else:
            boneVarloc.influence = 1

        boneVarLim = AddConstraint('LIMIT_LOCATION', bone, '', limitcon='LOC')
        boneVarLim.name = 'PHY_LIM_' + bone
        if 'AIM_' in bone:
            param = {'min':[-.05,-.05,-.05], 'max':[.05,.05,.05]}
        else:
            param = {'min':[-.03,-.03,-.03], 'max':[.03,.03,.03]}
        x, y, z = 0,1,2
        for end in ['min','max']:
            for ax in ['x','y','z']:
                code = f"boneVarLim.use_{end}_{ax} = True\nboneVarLim.{end}_{ax} = {param.get(end)[eval(ax)]}"
                exec(code)
                ### for some operations in bpy module you have to concatenate code into a string and run it thru an exec() function
                ### I don't know why, you just do...
                ### eval() takes a string literal and finds the corresponding variable within scope 'x' -> x -> which equals 0

        boneVarLim.owner_space = 'LOCAL'
        boneVarLim.influence = 1
            
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)  ## get out of edit mode, back to object

    bpy.ops.object.select_all(action='DESELECT')  ## deselect all
    bpy.data.objects[rig].select_set(True)   ## select the rig object
    bpy.context.view_layer.objects.active = bpy.data.objects[rig]   ## make the rig object the active object
    bpy.ops.object.mode_set(mode='POSE')  ##  go to pose mode.....all this is just parenting the sim obj/mesh to the main bone
    bpy.context.object.pose.bones[parbone].bone.select = True  ##  that was passed as the arg 'parbone' to this function
    bpy.context.object.data.bones.active = bpy.context.object.pose.bones[parbone].bone
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[PHy_mesh].select_set(True)
    bpy.data.objects[rig].select_set(True)

    bpy.ops.object.parent_set(type='BONE', keep_transform=True)
    
    ### ALWAYS COMMENT YOUR CODE!!
    
#################################
############# Setup #############
#################################

#Set the Cursor to the world origin
bpy.context.scene.cursor.location = 0,0,0

#create an armature 
bpy.ops.object.armature_add( enter_editmode=False )

#rename armature
bpy.data.objects['Armature'].name = rig

#assign armature object to variable
Armature = bpy.data.objects[rig]
ArmatureOps = bpy.ops.object
ArmEdit = Armature.data.edit_bones
ArmBones = Armature.data.bones
ArmPose = Armature.pose.bones
ArmContx = bpy.context.object

#select the armature
Armature.select_set(True)

#switch to editmode
ArmatureOps.mode_set(mode='EDIT', toggle=False)

#change bone viewport display type
bpy.data.armatures[bpy.context.object.data.name].display_type = 'STICK'

#delete default bone
ArmEdit.remove(ArmEdit[0])


############################################
############## BUILD SKELETON ##############
############################################

#add root
root = ArmContx.data.edit_bones.new('root')
root.head = origin
root.tail = (0,1,0)

#add gravity_ROOT
gravity_ROOT = ArmContx.data.edit_bones.new('gravity_ROOT')
gravity_ROOT.head = origin
gravity_ROOT.tail = findEmpty('gravity_ROOT_tail')
gravity_ROOT.roll = m.radians(0)

#add gravity_AIM
gravity_AIM = ArmContx.data.edit_bones.new('gravity_AIM')
gravity_AIM.head = findEmpty('gravity_AIM_head')
gravity_AIM.tail = findEmpty('gravity_AIM_tail')
gravity_AIM.roll = m.radians(0)
Armature.data.edit_bones['gravity_AIM'].parent = Armature.data.edit_bones['gravity_ROOT']
Armature.data.edit_bones['gravity_AIM'].use_connect = False

#add PROPERTIES
PROPERTIES = ArmContx.data.edit_bones.new('PROPERTIES')
PROPERTIES.head = findEmpty('PROPERTIES_head')
PROPERTIES.tail = findEmpty('PROPERTIES_tail')
PROPERTIES.roll = m.radians(89)
Armature.data.edit_bones['PROPERTIES'].parent = Armature.data.edit_bones['root']
Armature.data.edit_bones['PROPERTIES'].use_connect = False

#add pelvis
pelvis = ArmContx.data.edit_bones.new('pelvis')
pelvis.head = findEmpty('pelvis_head')
pelvis.tail = findEmpty('spine_01_head')
pelvis.roll = m.radians(89)
Armature.data.edit_bones['pelvis'].parent = Armature.data.edit_bones['root']
Armature.data.edit_bones['pelvis'].use_connect = False

#add spine_01
spine_01 = ArmContx.data.edit_bones.new('spine_01')
#....continues


## add physics bone groups

if True: ## Physics bones need special prefixes PHY_07_
    #add PHY_07_bellyShake_MAIN_l
    PHY_07_bellyShake_MAIN_l = ArmContx.data.edit_bones.new('PHY_07_bellyShake_MAIN_l')
    PHY_07_bellyShake_MAIN_l.head = findEmpty('PHY_07_bellyShake_MAIN_l_head')
    PHY_07_bellyShake_MAIN_l.tail = findEmpty('PHY_07_bellyShake_MAIN_l_tail')
    PHY_07_bellyShake_MAIN_l.roll = m.radians(60)
    Armature.data.edit_bones['PHY_07_bellyShake_MAIN_l'].parent = Armature.data.edit_bones['spine_02_PhyINTR_l']
    Armature.data.edit_bones['PHY_07_bellyShake_MAIN_l'].use_connect = False
         
    #add PHY_07_bellyShake_topOuter_l
    PHY_07_bellyShake_topOuter_l = ArmContx.data.edit_bones.new('PHY_07_bellyShake_topOuter_l')
    PHY_07_bellyShake_topOuter_l.head = findEmpty('PHY_07_bellyShake_topOuter_l_head')
    PHY_07_bellyShake_topOuter_l.tail = findEmpty('PHY_07_bellyShake_topOuter_l_tail')
    PHY_07_bellyShake_topOuter_l.roll = m.radians(60)
    Armature.data.edit_bones['PHY_07_bellyShake_topOuter_l'].parent = Armature.data.edit_bones['PHY_07_bellyShake_MAIN_l']
    Armature.data.edit_bones['PHY_07_bellyShake_topOuter_l'].use_connect = False
         
    #add PHY_07_bellyShake_bottomOuter_l
    PHY_07_bellyShake_bottomOuter_l = ArmContx.data.edit_bones.new('PHY_07_bellyShake_bottomOuter_l')
    PHY_07_bellyShake_bottomOuter_l.head = findEmpty('PHY_07_bellyShake_bottomOuter_l_head')
    PHY_07_bellyShake_bottomOuter_l.tail = findEmpty('PHY_07_bellyShake_bottomOuter_l_tail')
    PHY_07_bellyShake_bottomOuter_l.roll = m.radians(60)
    Armature.data.edit_bones['PHY_07_bellyShake_bottomOuter_l'].parent = Armature.data.edit_bones['PHY_07_bellyShake_MAIN_l']
    Armature.data.edit_bones['PHY_07_bellyShake_bottomOuter_l'].use_connect = False
         
    #add PHY_07_bellyShake_bottomInner_l
    PHY_07_bellyShake_bottomInner_l = ArmContx.data.edit_bones.new('PHY_07_bellyShake_bottomInner_l')
    PHY_07_bellyShake_bottomInner_l.head = findEmpty('PHY_07_bellyShake_bottomInner_l_head')
    PHY_07_bellyShake_bottomInner_l.tail = findEmpty('PHY_07_bellyShake_bottomInner_l_tail')
    PHY_07_bellyShake_bottomInner_l.roll = m.radians(60)
    Armature.data.edit_bones['PHY_07_bellyShake_bottomInner_l'].parent = Armature.data.edit_bones['PHY_07_bellyShake_MAIN_l']
    Armature.data.edit_bones['PHY_07_bellyShake_bottomInner_l'].use_connect = False
    
    
"""
more code in this section. 
- All creating the bones of the rig
- setting the parent child relationship. 
- Set non-deforming bones to false
- Set non-use inherit rotation to false
- Set non-use inherit scale to false
- changing rotation mode to either euler or quanternion
- adding controllers

Skipping a few sections to constraints.
example adding constraints
"""

# Symmetrize bones to right side
SelectSymmetrize()

#############################################
############## ADD CONSTRAINTS ##############
#############################################

#add constraints to------------ calf_IKCONST_l
calf_IKCONST_l = AddConstraint('IK', 'calf_l', 'ikHandleLeg_l', polsubtar='poleVectorLeg_l')
calf_IKCONST_l.name = 'calf_IKCONST_l'
calf_IKCONST_l.pole_angle = m.radians(0)
calf_IKCONST_l.chain_count = 2
calf_IKCONST_l.use_tail = True
calf_IKCONST_l.use_stretch = True
calf_IKCONST_l.influence = 1

#add constraints to------------ autoPoleNormLeg_0_DAMPTRK_l
autoPoleNormLeg_0_DAMPTRK_l = AddConstraint('DAMPED_TRACK', 'autoPoleNormLeg_0_l', 'autoPoleNormLeg_2_l')
autoPoleNormLeg_0_DAMPTRK_l.name = 'autoPoleNormLeg_0_DAMPTRK_l'
autoPoleNormLeg_0_DAMPTRK_l.head_tail = 0
autoPoleNormLeg_0_DAMPTRK_l.track_axis = 'TRACK_Y'
autoPoleNormLeg_0_DAMPTRK_l.influence = 1

if True:# always true. just for formating
    #add constraints to------------ autoPoleNormLeg_1_COPLOC_01_l
    autoPoleNormLeg_1_COPLOC_01_l = AddConstraint('COPY_LOCATION', 'autoPoleNormLeg_1_l', 'autoPoleNormLeg_0_l')
    autoPoleNormLeg_1_COPLOC_01_l.name = 'autoPoleNormLeg_1_COPLOC_01_l'
    autoPoleNormLeg_1_COPLOC_01_l.head_tail = 0
    autoPoleNormLeg_1_COPLOC_01_l.use_x = True
    autoPoleNormLeg_1_COPLOC_01_l.use_y = True
    autoPoleNormLeg_1_COPLOC_01_l.use_z = True
    autoPoleNormLeg_1_COPLOC_01_l.target_space = 'WORLD'
    autoPoleNormLeg_1_COPLOC_01_l.owner_space = 'WORLD'
    autoPoleNormLeg_1_COPLOC_01_l.influence = 1

    #add constraints to------------ autoPoleNormLeg_1_COPLOC_02_l
    autoPoleNormLeg_1_COPLOC_02_l = AddConstraint('COPY_LOCATION', 'autoPoleNormLeg_1_l', 'autoPoleNormLeg_2_l')
    autoPoleNormLeg_1_COPLOC_02_l.name = 'autoPoleNormLeg_1_COPLOC_02_l'
    autoPoleNormLeg_1_COPLOC_02_l.head_tail = 0
    autoPoleNormLeg_1_COPLOC_02_l.use_x = True
    autoPoleNormLeg_1_COPLOC_02_l.use_y = True
    autoPoleNormLeg_1_COPLOC_02_l.use_z = True
    autoPoleNormLeg_1_COPLOC_02_l.target_space = 'WORLD'
    autoPoleNormLeg_1_COPLOC_02_l.owner_space = 'WORLD'
    autoPoleNormLeg_1_COPLOC_02_l.influence = 0.5

    #add constraints to------------ autoPoleNormLeg_1_COPROT_l
    autoPoleNormLeg_1_COPROT_l = AddConstraint('COPY_ROTATION', 'autoPoleNormLeg_1_l', 'autoPoleNormLeg_2_l')
    autoPoleNormLeg_1_COPROT_l.name = 'autoPoleNormLeg_1_COPROT_l'
    autoPoleNormLeg_1_COPROT_l.use_x = True
    autoPoleNormLeg_1_COPROT_l.use_y = True
    autoPoleNormLeg_1_COPROT_l.use_z = True
    autoPoleNormLeg_1_COPROT_l.target_space = 'WORLD'
    autoPoleNormLeg_1_COPROT_l.owner_space = 'WORLD'
    autoPoleNormLeg_1_COPROT_l.influence = 1

"""
more code in this section.
all more if this, adding contraints to bone 
ie. IK, copy rotation, copy location, stretchto, damptrack, etc..

examples adding drivers
"""

#############################################
############## ADD DRIVERS ##################
#############################################

############## BONE DRIVERS ##################
#add driver to----------------- innerThighAdj_l
innerThighAdj_LocX_L = AddDriver("innerThighAdj_l", 'location', ['thigh_l', 'thigh_l'], ['ROT_X', 'ROT_Z'],\
                                ['LOCAL_SPACE', 'LOCAL_SPACE'], 'abs(var0*.3) if var0>0 else (var1*.35)', varCount=2, channelIndex=0)
innerThighAdj_LocY_L = AddDriver("innerThighAdj_l", 'location', 'thigh_l', 'ROT_Z', 'LOCAL_SPACE', 'abs(var0*.45) if var0<0 else (var0*.45)', channelIndex=1)
innerThighAdj_LocZ_L = AddDriver("innerThighAdj_l", 'location', 'thigh_l', 'ROT_Z', 'LOCAL_SPACE', 'var0*.4 if var0<0 else -(var0*.3)', channelIndex=2)
innerThighAdj_LocX_R = AddDriver("innerThighAdj_r", 'location', ['thigh_r', 'thigh_r'], ['ROT_X', 'ROT_Z'],\
                                ['LOCAL_SPACE', 'LOCAL_SPACE'], '-abs(var0*.3) if var0>0 else -(var1*.35)', varCount=2, channelIndex=0)
innerThighAdj_LocY_R = AddDriver("innerThighAdj_r", 'location', 'thigh_r', 'ROT_Z', 'LOCAL_SPACE', 'abs(var0*-.45) if var0>0 else -(var0*.45)', channelIndex=1)
innerThighAdj_LocZ_R = AddDriver("innerThighAdj_r", 'location', 'thigh_r', 'ROT_Z', 'LOCAL_SPACE', 'var0*-.4 if var0>0 else (var0*.3)', channelIndex=2)


#add driver to----------------- thigh_l
thigh_ScaleY_L = AddDriver('thigh_l', 'scale', 'thigh_l', 'ROT_Z', 'LOCAL_SPACE', 'max(1-abs(var0 *.03), .98)', channelIndex=1)
thigh_ScaleY_R = AddDriver('thigh_r', 'scale', 'thigh_r', 'ROT_Z', 'LOCAL_SPACE', 'max(1-abs(var0 *.03), .98)', channelIndex=1)
thigh_ScaleY_L = AddDriver('thigh_l', 'scale', 'thigh_l', 'ROT_Z', 'LOCAL_SPACE', 'max(1-abs(var0 *.03), .98)', channelIndex=1)
thigh_ScaleY_R = AddDriver('thigh_r', 'scale', 'thigh_r', 'ROT_Z', 'LOCAL_SPACE', 'max(1-abs(var0 *-.03), .98)', channelIndex=1)
         
         
### examples adding property drivers

############## PROPERTY DRIVERS ##################
#add driver to----------------- sideMoveHip_l
sideMoveHip_ScaleX_l = AddPropertyDriver('sideMoveHip_l', 'scale', 'PROPERTIES_SPHERE', '["Hip_Width"]', '(var*3) + 1.0', channelIndex=0)
sideMoveHip_ScaleX_r = AddPropertyDriver('sideMoveHip_r', 'scale', 'PROPERTIES_SPHERE', '["Hip_Width"]', '(var*3) + 1.0', channelIndex=0)
sideMoveHip_ScaleZ_l = AddPropertyDriver('sideMoveHip_l', 'scale', 'PROPERTIES_SPHERE', '["Hip_Width"]', '(var*3) + 1.0', channelIndex=2)
sideMoveHip_ScaleZ_r = AddPropertyDriver('sideMoveHip_r', 'scale', 'PROPERTIES_SPHERE', '["Hip_Width"]', '(var*3) + 1.0', channelIndex=2)


### examples adding constraint drivers

############## CONSTRAINT DRIVERS ##################
#add driver to----------------- pelvis_LIMLOC_DRIV
pelvis_LIMLOC_DRIV_package = 'pose.bones["pelvis"].constraints["pelvis_LIMLOC"].influence'
pelvis_LIMLOC_DRIV = AddPropertyDriver('pelvis', '', ['PROPERTIES_SPHERE', 'PROPERTIES_SPHERE'], ['["Hip_Width"]', '["Thigh_Size"]'],\
                                    '-4.0 if var0>.05 or var1>.1 else -5', varCount=2, package=pelvis_LIMLOC_DRIV_package)
                                    

### Utility code for mirroring skin weights from left to right

#########################################
############## UTILITY ##################
#########################################

bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.data.objects['body1'].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects['body1']
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')

for grp in bpy.context.object.vertex_groups:
    if grp.name[-2:] == '_r':
        other_side = bpy.context.object.vertex_groups[grp.name].index
        bpy.context.object.vertex_groups.active_index = other_side
        bpy.ops.object.vertex_group_remove(all=False, all_unlocked=False)
        
for grp in bpy.context.object.vertex_groups:
    if grp.name[-2:] == '_l':
        act = bpy.context.object.vertex_groups[grp.name].index
        bpy.context.object.vertex_groups.active_index = act
        bpy.ops.object.vertex_group_copy()
        bpy.ops.object.vertex_group_mirror(use_topology=False)
        if 'PHY_' in grp.name:
            for i in range(1,12,2):
                if grp.name[4:6] == f'0{i}':
                    new_name = f'PHY_0{i+1}' + grp.name[6:-2] + '_r'
                elif grp.name[4:6] == f'{i}':
                    new_name = f'PHY_{i+1}' + grp.name[6:-2] + '_r'
        else:
            new_name = grp.name[:-2]+'_r'
        bpy.context.object.vertex_groups[grp.name+'_copy'].name = new_name
    else:
        pass
        
##  Hide all bones that are not controller bones used to animate 
#########################################################################
###### add property driver to show/hide the non-controller bones ########
#########################################################################  

con_list = ['spine_02', 'pelvis', 'ikHandleArm_r', 'ikHandleArm_l', 'poleVectorLeg_r', 'poleVectorLeg_l',\
             'neck_02', 'spine_04', 'foot_CON_l', 'footRoll_CON_l', 'foot_CON_r', 'footRoll_CON_r',\
             'poleVectorArm_l', 'poleVectorArm_r', 'hand_l', 'hand_r']
hidden_bones = {}

for bone in bpy.context.object.pose.bones:
    if bone.name not in con_list:
        drive = bpy.data.objects[rig].pose.bones[bone.name].bone.driver_add('hide')
        drive.driver.type = 'SCRIPTED'
        drive.driver.expression = 'var'
        
        var = drive.driver.variables.new()
        var.name = 'var'
        var.type = 'SINGLE_PROP'
        var.targets[0].id = bpy.data.objects['PROPERTIES_SPHERE']
        var.targets[0].data_path = '["_INTERNAL_VISIBILITY"]'
        hidden_bones[f"hide_{bone.name}"] = drive

##  example adding Physics  
#############################################
############## ADD PHYSICS ##################
#############################################

add_Physics('spine_02_PhyINTR_l', 'PHY_07')
add_Physics('spine_02_PhyINTR_r', 'PHY_08')

##  adding physics drivers

#add driver to----------------- PHY_mesh_spine_02_PhyINTR_FRICTION_DRIV_l                                 
PHY_mesh_spine_02_PhyINTR_FRICTION_DRIV_l = Add_PHY_PropertyDriver("PHY_mesh_spine_02_PhyINTR_l", 'Softbody',\
                                                'friction', 'PROPERTIES_SPHERE', '["phyFRICTION_stomach"]')
#add driver to----------------- PHY_mesh_spine_02_PhyINTR_FRICTION_DRIV_r                                 
PHY_mesh_spine_02_PhyINTR_FRICTION_DRIV_r = Add_PHY_PropertyDriver("PHY_mesh_spine_02_PhyINTR_r", 'Softbody',\
                                                'friction', 'PROPERTIES_SPHERE', '["phyFRICTION_stomach"]')
#add driver to----------------- PHY_mesh_spine_02_PhyINTR_MASS_DRIV_l
PHY_mesh_spine_02_PhyINTR_MASS_DRIV_l = Add_PHY_PropertyDriver("PHY_mesh_spine_02_PhyINTR_l", 'Softbody',\
                                                'mass', 'PROPERTIES_SPHERE', '["phyMASS_stomach"]')
#add driver to----------------- PHY_mesh_spine_02_PhyINTR_MASS_DRIV_r                                 
PHY_mesh_spine_02_PhyINTR_MASS_DRIV_r = Add_PHY_PropertyDriver("PHY_mesh_spine_02_PhyINTR_r", 'Softbody',\
                                                'mass', 'PROPERTIES_SPHERE', '["phyMASS_stomach"]')
                                                
##  Final clean up stuff
#####################################################################################                                   
###### change Rest Pose, add Armature Modifiers, Bone Parent other meshes ###########
#####################################################################################

## set rest pose for major driving bones
bpy.ops.object.posemode_toggle()
restlist = ['thigh_l', 'thigh_r', 'calf_l', 'calf_r']
for bone in bpy.context.object.pose.bones:
        bone.bone.select = False
for res in restlist:
    bpy.context.object.pose.bones[res].bone.select = True

bpy.ops.pose.armature_apply(selected=True)

## reset rest length of all stretch to constraints
for bone in bpy.data.objects[rig].pose.bones:
    for const in bone.constraints:
        if const.type == 'STRETCH_TO':
            bpy.context.object.data.bones.active = bone.bone
            bpy.ops.constraint.stretchto_reset(constraint=const.name, owner='BONE')
            
## apply armature modifier to character mesh objects
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

meshlist = bpy.data.collections[character].all_objects

for mes in meshlist:
    mes.modifiers['Armature'].object = bpy.data.objects[rig]

## bone parent the characters eyes and the property sphere to the head bone    
for o in bpy.data.objects:
    if 'Auto_Eye' in o.name or 'PROPERTIES_SPHERE' in o.name:   
        bpy.ops.object.select_all(action='DESELECT')
        bpy.data.objects[rig].select_set(True)
        bpy.context.view_layer.objects.active = bpy.data.objects[rig]
        bpy.ops.object.mode_set(mode='POSE')
        bpy.context.object.pose.bones['head'].bone.select = True
        bpy.context.object.data.bones.active = bpy.context.object.pose.bones['head'].bone
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
        bpy.data.objects[o.name].select_set(True)
        bpy.data.objects[rig].select_set(True)

        bpy.ops.object.parent_set(type='BONE', keep_transform=True)

bpy.data.objects['PROPERTIES_SPHERE'].select_set(False)
                                                                                                                 
#######################################################################
#######################################################################
#######################################################################            

Kinematic Switching Script

""" This is a script that can be use by itself or wrapped in a function and called from another, larger auto rigging script. Modern character rigs require IK/FK switching for ease of animating limbs like arms and legs. That's what this does. I have written several of these. They all involve keeping track of elbow and wrist location, as well as hand orientation. You simply have to match these values when you switch between modes."""


import bpy

def showHide(v, *args):
    for arg in args:
        bpy.data.armatures['Armature'].bones[arg].hide = v
    
def apply_transforms(*args):
    for arg in args:
        bone = bpy.context.object.pose.bones[arg].bone
        bpy.context.object.data.bones.active = bone
        bpy.ops.pose.visual_transform_apply()

def copyRot(conbone, contarget, influence=1, name=None, apply=True):
    bon = bpy.context.object.pose.bones[conbone].bone
    bpy.context.object.data.bones.active = bon
    pbone = bpy.context.active_pose_bone
    target = bpy.context.scene.objects.get("Armature")
    subtarget = bpy.context.scene.objects.get("Armature").pose.bones.get(contarget)
    constraint = pbone.constraints.new('COPY_ROTATION')
    constraint.target = target
    constraint.subtarget = subtarget.name
    constraint.influence = influence
    if name!=None:
        constraint.name = name
    if apply==True:
        bpy.ops.constraint.apply(constraint=constraint.name, owner='BONE', report=False)

def copyLoc(conbone, contarget, headtail=0):
    hand = bpy.context.object.pose.bones[conbone].bone
    bpy.context.object.data.bones.active = hand
    pbone = bpy.context.active_pose_bone
    target = bpy.context.scene.objects.get("Armature")
    subtarget = bpy.context.scene.objects.get("Armature").pose.bones.get(contarget)
    constraint = pbone.constraints.new('COPY_LOCATION')
    constraint.target = target
    constraint.subtarget = subtarget.name
    constraint.head_tail = headtail
    bpy.ops.constraint.apply(constraint=constraint.name, owner='BONE', report=False)
    
switchMode = bpy.data.objects['Armature'].pose.bones['PROPERTIES']['ARM_L_IK/FK SWITCH']
Armedit = bpy.data.objects['Armature'].data

if switchMode == 0:
    
    apply_transforms('upperArm_IKFK.L','foreArm_IKFK.L','hand_IKFK.L',
            'handRoll_0_arm.L','handRoll_1_arm.L','handRoll_IKhandle_2_arm.L',
            'poleHandle_arm.L','handRoll_ROTATE.L')
    
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    Armedit.edit_bones['handRoll_0_arm.L'].parent = None
    bpy.ops.object.mode_set(mode='POSE', toggle=False)
    
    copyLoc('handRoll_0_arm.L', 'hand_IKFK.L')
    copyRot('handRoll_0_arm.L', 'handRoll_ROTATE.L')
    copyRot('poleHandle_arm.L', 'poleHandle_arm_ROTATE.L')
    copyRot('vectorNorm_2_arm.L', 'vectorNorm_MATCH_arm.L', name='vecArmRot.L', apply=False)
    
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[0]=1
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[1]=0
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[2]=0
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[3]=0
    
    bpy.data.objects['Armature'].pose.bones['PROPERTIES']['ARM_L_IK/FK SWITCH'] = float(1)
    
    bpy.ops.constraint.apply(constraint='vecArmRot.L', owner='BONE', report=False)
    
elif switchMode == 1:
    
    bpy.context.object.data.bones.active = bpy.data.objects['Armature'].pose.bones['clavicle.L'].bone
    con = bpy.data.objects['Armature'].pose.bones['clavicle.L'].constraints['clav_ROT'].name
    bpy.ops.constraint.apply(constraint=con, owner='BONE', report=False)
    
    apply_transforms('upperArm_IKFK.L','foreArm_IKFK.L','hand_IKFK.L',
            'handRoll_1_arm.L','handRoll_IKhandle_2_arm.L',
            'poleHandle_arm.L','handRoll_ROTATE.L','clavicle.L','upperArm_IKhandle.L',
            'autoClav_FOLLOW.L','autoClavicle.L','upperArm_ROTATE.L',
            'upperArm_ROTATE_FOLLOW.L')
    
    copyRot('autoClavicle.L', 'clavicle.L')
    copyRot('clavicle.L', 'autoClavicle.L', influence=0.5, name='clav_ROT', apply=False)
    
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    Armedit.edit_bones['handRoll_0_arm.L'].parent = Armedit.edit_bones['hand_IKFK.L']
    bpy.ops.object.mode_set(mode='POSE', toggle=False)
    
    bpy.data.objects['Armature'].pose.bones['PROPERTIES']['ARM_L_IK/FK SWITCH'] = float(0)

    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].location[0]=0
    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].location[1]=0
    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].location[2]=0
    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].rotation_quaternion[0]=1
    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].rotation_quaternion[1]=0
    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].rotation_quaternion[2]=0
    bpy.data.objects['Armature'].pose.bones['handRoll_0_arm.L'].rotation_quaternion[3]=0
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[0]=1
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[1]=0
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[2]=0
    bpy.data.objects['Armature'].pose.bones['vectorNorm_2_arm.L'].rotation_quaternion[3]=0

    copyLoc('handRoll_0_arm.L', 'handRoll_ROTATE.L')
    copyRot('handRoll_0_arm.L', 'handRoll_ROTATE.L')
    
else:
    pass
    

Add Locator Script

""" Utility script for use when rigging or writing other scripts. Adds an empty object to the current location of the 3D cursor. Works well in editmode and posemode. Normally, to add an empty to a bone's location you would have to select the bone in pose mode, snap the 3D cursor to selected, go back to object mode, create the empty at the cursor's location, then go back to edit mode to continue what you were doing. This script does all of that, after you position your cursor. The empty appears right there in posemode or editmode. It's a time saver."""


import bpy

for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        ctx = bpy.context.copy()
        ctx['area'] = area
        ctx['region'] = area.regions[-1]
        bpy.ops.view3d.view_selected(ctx)
        bpy.ops.view3d.snap_cursor_to_selected(ctx)
        
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

cur = bpy.context.scene.cursor.location
bpy.ops.object.empty_add(type='PLAIN_AXES', radius=.2, align='WORLD', location=cur)

bpy.context.view_layer.objects.active = bpy.data.objects['Armature']
bpy.ops.object.mode_set(mode='EDIT')
   
..........................................................................................................................................................................................................................................................................................


.

.

fester

fester