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)
#######################################################################
#######################################################################
#######################################################################
0>
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')