Auto Rigging Script


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 with 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)
                                                                                                                 
#######################################################################
#######################################################################
#######################################################################            
..........................................................................................................................................................................................................................................................................................


.

.

fester

fester