Some tips for python scripting in Blender 2.79 for Artists

Some tips for python scripting in Blender 2.79 for Artists

(last updated 02-2024) added some tips for migrating code from Blender 2.79 to higher versions

What is in this?

It covers some specific areas in Blender python scripting especially useful in areas such as Rigging, Animation and Modeling. It also has a little information on Cloth and Hair scripting as well. I also want to try to learn more about classes (object oriented programming) and how classes can help an Artist with scripting in Blender.

Some specific areas of blender i will cover are: joints, animation, curves, constraints, drivers, shapekeys/blendshapes, modifiers and then at the end will go over some longer examples. although these notes are specific to blender 2.79 they can still be helpful when scripting in higher blender versions. also learning how to use an api such as Blender's can help with learning api's for other cg applications. If you are new to python i strongly suggest checking out Chad Vernon’s website that has an introduction to python (www dot chadvernon dot com) another very helpful website to learn python is (www dot stackoverflow dot com)

Intro to blender data
Some tips for migrating code from 2.79 to higher Blender versions
Joints/Bones
Animation
Bendy Bones
Constraints
Materials
Weight Painting
Properties
Drivers
Shapekeys/Blendshapes
Modifiers
Lattices
Sequencer
Node Editor (Shaders)
Cloth
Intro to working with text files (xml,json)
Intro to running script without blender gui
Intro to creating command line tool
Simple Class Examples
Add-on examples

Some strategies for learning Blender python

There a lots of ways to learn new software but here are just a few tips that i found helpful.

first start by hovering mouse over the ui element of interest. that way can find things like what is attribute for rotation or translation by hovering mouse over channel box.

second can read the info panel after doing something in Blender then can see what function usually a bpy.ops method that did the action. with that information can try searching for non ops way of doing method on google. why the non ops way? from my understanding non ops way requires less assumptions on the selection requirements for getting the desired result.

Third can use auto completion in the python console to see what possible attribute could be what is needed. (for mac ctrl+space bar autocompletes) by usually searching bpy.data.objects["name of object"]. then use auto completion to see possible things that can go afterwards. or bpy.data. and auto completion to see what options could come next.

Fourth can begin to read simple Blender add-ons on git to learn more about Blender scripting.

Fifth if know the type of object looking for can search Blender's python api docs for the different methods it has and what parameters are needed.

also using google to search for a specific question looking for. like search for "blender bpy how to create a bone"

Intro to Blender data

here are some examples of the different types of data available in Blender. It is helpful for when searching python api documentation online for Blender. These notes are for blender 2.79.

>>> type(bpy.data.scenes['Scene'])
class 'bpy.types.Scene'

>>> bpy.data.objects['Camera'].data
bpy.data.cameras['Camera']

>>> type(bpy.data.objects['Camera'].data)
class 'bpy.types.Camera'

>>> type(bpy.data.objects['Cube'].data)
class 'bpy_types.Mesh'

>>> type(bpy.data.objects['Armature'].data)
class 'bpy.types.Armature'

>>> bpy.data.objects['Armature'].data.bones['Bone']
bpy.data.armatures['Armature'].bones["Bone"]

>>> type(bpy.data.objects['Armature'].data.bones['Bone'])
class 'bpy_types.Bone'

>>> type(bpy.data.objects['Lamp'].data)
class 'bpy.types.PointLamp'

>>> type(bpy.data.objects['Cube'].animation_data)
class 'bpy.types.AnimData'

>>> bpy.data.objects['Cube'].animation_data.action
bpy.data.actions['CubeAction']

>>> type(bpy.data.objects['Cube'].animation_data.action)
class 'bpy.types.Action'

>>> bpy.data.objects['Cube'].animation_data.action.fcurves[0]
bpy.data.actions['CubeAction']...FCurve

>>> type(bpy.data.objects['Cube'].animation_data.action.fcurves[0])
class 'bpy.types.FCurve'

>>> bpy.data.objects['Cube'].animation_data.action.fcurves[0].keyframe_points[0]
bpy.data.actions['CubeAction']...Keyframe

>>> type(bpy.data.objects['Cube'].animation_data.action.fcurves[0].keyframe_points[0])
class 'bpy.types.Keyframe'

>>> type(bpy.data.objects['Cube'].animation_data.action.fcurves[0].keyframe_points[0].co)
class 'Vector'
so if googled
bpy.types.Armature
should be able to go to Blender’s api docs and find all methods available for an armature.

Some tips for migrating code from 2.79 to higher Blender versions

#old 2.79
object.select = True 

#new 3.6.5 
object.select_set(True) #select_get()

#old
bpy.context.scene.objects.active = obj
#new
bpy.context.view_layer.objects.active = obj

#
charArmatureObj.pose.bones[self.baseMid].use_bbone_custom_handles = True
#
charArmatureObj.data.bones[self.baseMid].bbone_handle_type_start = 'ABSOLUTE'

#
charArmatureObj.pose.bones[self.baseMid].bbone_custom_handle_start
#
charArmatureObj.data.bones[self.baseMid].bbone_custom_handle_start

#
selectedObj.matrix_world*face.calc_center_median()
#
selectedObj.matrix_world @ face.calc_center_median()

#
bpy.context.scene.objects.link(obj)
#
bpy.context.scene.collection.objects.link(obj)

#
importObjPath = StringProperty
#
importObjPath : StringProperty

#
bmesh.ops.create_circle(bm,cap_ends=False,diameter=0.1,segments=8)
#
bmesh.ops.create_circle(bm,cap_ends=False,radius=0.1,segments=8)

#
scene.update()
#
scene.view_layers.update()

#
bpy.ops.transform.rotate(value=amount, axis=rotateAxis)
#
bpy.ops.transform.rotate(value=amount, orient_axis=rotateAxis)

#
charArmatureObj.data.draw_type='BBONE'
#
charArmatureObj.data.display_type='BBONE'



#old
boneObj = bpy.data.objects[armatureName].pose.bones[boneName]
boneObj[propertyName] = 0.0
bone_prop = rna_idprop_ui_prop_get(boneObj, propertyName, create = True)
bone_prop['default']= 1.0
bone_prop['min']= 0.0
bone_prop['max']= 1.0

#new
boneObj = bpy.data.objects[armatureName].pose.bones[boneName]
boneObj[propertyName] = 0.0
bone_prop = boneObj.id_properties_ui(propertyName)
bone_prop.update(default=1.0)
bone_prop.update(min=0.0)
bone_prop.update(max=1.0)


#old
bpy.data.objects['Armature'].pose.bones['globalMover.C'].custom_shape_scale = 2

#new
bpy.data.objects['Armature'].pose.bones['globalMover.C'].custom_shape_scale_xyz = (2,2,2)


#old
bpy.data.objects[armatureNameSource].hide = True

#new
bpy.data.objects[armatureNameSource].hide_set(True)

Joints/Bones

bones/joints are important for areas such as rigging because they can be used to move the mesh of the character.

briefly to make a bone need to first have an armature created (here the armature data is given a specific name in quotes) in the snippets after >>> symbol is the actual code run in blender’s python console, underneath that line is what blender returns from calling the command. to quickly get to blender’s console can click the ui drop down and switch to Scripting option. The python console should be at the bottom.

>>> type(bpy.data.armatures)
class 'bpy_prop_collection'
in blender the bpy_prop_collection is kind of like a python dictionary. we can access its keys and values. with no armatures in scene get an empty list.
>>> bpy.data.armatures.keys()
[]
>>> bpy.data.armatures.new('testArmat')
bpy.data.armatures['testArmat']
in the above notice the bpy. well that is used for accessing blender’s api. like other applications like Maya’s cmds or Houdini’s hou or Katana’s api. lots of Blender api can be accessed via bpy. the .new is telling blender to create a new armature. once the armature is created we can see we get something for the keys.
>>> bpy.data.armatures.keys()
['testArmat']
once the armature data is created can create an armature object using that data.
>>> bpy.data.objects.new('testArm', bpy.data.armatures['testArmat'] )
bpy.data.objects['testArm']
similar to bpy.data.armatures bpy.data.objects is like a python dictionary as well.
>>> type(bpy.data.objects)
class 'bpy_prop_collection'
after creating the armature object we get:
>>> bpy.data.objects.keys()
['Camera', 'Cube', 'Lamp', 'testArm']
we can see there are some other objects in the default blender scene like camera and lamp. some more info on the object we created:
>>> 'testArm' in bpy.data.objects
True

>>> bpy.data.objects.get('testArm')
bpy.data.objects['testArm']

>>> type(bpy.data.objects.get('testArm'))
class 'bpy_types.Object'
>>> 
this next part is specific to blender 2.79 and is different for later versions. once armature object is created need to link it to the blender scene.
>>> bpy.data.scenes['Scene'].objects.link( bpy.data.objects['testArm'] )
in above see the use of ’Scene’. in a single blender file can have multiple scenes so we want to add the armature to a specific scene in the blender file. like bpy.data.armatures and bpy.data.objects bpy.data.scenes is like a python dictionary as well.
>>> type(bpy.data.scenes)
class 'bpy_prop_collection'
>>> bpy.data.scenes.keys()
['Scene']
we can see the scene type here:
>>> type(bpy.data.scenes['Scene'])
class 'bpy.types.Scene'
we can see the scene.objects is like a python dictionary as well.
>>> type(bpy.data.scenes['Scene'].objects)
class 'bpy_prop_collection'
to see some of the methods available for the scene.objects can type
bpy.data.scenes['Scene'].objects.
then on mac click ctrl+spacebar. and it brings up all the methods available. in blender 2.79 link method is the one used to link armature object to scene.

once the armature object is linked to the scene we can begin adding bones. in blender there’s a distinction between the kind of bones either an edit bone or a pose bone. edit bones are like the rest bones whereas pose bones are the bones that actually get animated. moving the position of an edit bone changes the position of the pose bone but changing the position of pose bone doesn’t change position of edit bone.

to add an edit bone we need to be in edit mode. one way to enter edit mode is to select the armature object in outliner and then click the drop down at bottom of viewport and switch to edit mode.

Blender has something called modes that is important when scripting. they are used alot so for example some python code might need to be run in edit mode instead of object mode. for example creating edit bones needs to be done in edit mode.

>>> bpy.data.armatures['testArmat'].edit_bones.new('bone_a')
with the bone added we can position it by assigning it a trio tuple for x,y,z positions to its head or tail:
>>> bpy.data.armatures['testArmat'].edit_bones['bone_a'].head
Vector((0.0, 0.0, 0.0))

>>> bpy.data.armatures['testArmat'].edit_bones['bone_a'].tail = (2,0,0)

the pose bones get automatically created after edit bones are created. they will be used by animator. we can add shapes to them for easier selection. we can add colours to them for easier readability (via bone groups). and there are lots more attributes we can change basically if we can do it in the ui then there is an attribute/property we can change via code.

there are lots more properties for bones to go over but above is a starting point to learning about joints in blender.

Animation

creating animation on a pose bone

first create animation_data on the armature object. then a new action needs to be created then a new fcurve created then a new keyframe point created.

#assumes an armature has been created with a bone named Bone.
#start with no animation_data
>>> bpy.data.objects['Armature'].animation_data is None
True
1. create animation_data
>>> bpy.data.objects['Armature'].animation_data_create()
bpy.data.objects['Armature']...AnimData

>>> bpy.data.objects['Armature'].animation_data is None
False
2. create action in animation_data
>>> bpy.data.objects['Armature'].animation_data.action is None
True
>>> bpy.data.objects['Armature'].animation_data.action = bpy.data.actions.new(name = "BoneAction")
>>> bpy.data.objects['Armature'].animation_data.action is None
False
3a. create an fcurve in action
>>> fcurve = bpy.data.objects['Armature'].animation_data.action.fcurves.new('pose.bones["Bone"].location',2) #should be on translation in z. 0 would be x, 1 would be y. in blender translation is same as location.
3b. toggle in and out of pose mode should see color green on translation z channel

4. create a key frame point in fcurve

>>> fcurve.keyframe_points.insert(1,20) #created on frame 1 with a value of 20
bpy.data.actions['BoneAction']...Keyframe
#5. click in graph editor so can see new keyframe and click in viewport and frame bone to see it keyed on frame 1
#6. can create another keyframe to see bone animate
>>> fcurve.keyframe_points.insert(24,0)

these are all the steps together

bpy.data.objects['Armature'].animation_data_create()
bpy.data.objects['Armature'].animation_data.action = bpy.data.actions.new(name = "BoneAction")
fcurve = bpy.data.objects['Armature'].animation_data.action.fcurves.new('pose.bones["Bone"].location',2)
fcurve.keyframe_points.insert(1,20)
fcurve.keyframe_points.insert(24,0)

if go into the dope sheet the action we created and the keyframes should be visible. i think there can be many fcurves for a single action. And each fcurve can have multiple keyframe points. (note about the path used in creating fcurve: pose.bones["Bone"].location. it has the bone name of interest and what attribute should be keyed.)

removing animation on a pose bone

removing an entire fcurve. first need to get the FCurve object to remove then remove it from action’s fcurves.

>>> fcurve = bpy.data.objects['Armature'].animation_data.action.fcurves[0]
>>> fcurve
bpy.data.actions['BoneAction']...FCurve

>>> bpy.data.objects['Armature'].animation_data.action.fcurves.remove(fcurve)
>>> 
#i think have to go in and out of pose mode to see that fcurve was removed

removing a single keyframe point. need to first have an fcurve. then need to get the Keyframe object for keyframe want to remove. then we can remove it from keyframe_points.

>>> keypoint = fcurve.keyframe_points[1]
>>> keypoint
bpy.data.actions['BoneAction.001']...Keyframe

>>> keypoint.co
Vector((24.0, 0.0))

>>> fcurve.keyframe_points.remove(keypoint)
#i think have to go in and out of pose mode to see that the keyframe on frame 24 is now gone.

Bendy Bones

Bendy bones can be used to get simple curvy posing of a bone chain.

For this simple example assumes 3 bones have been created: a middle bone, a low bone and a tail bone. assumes the tail bone is unparented from middle bone.

arm_obj = bpy.data.objects['Armature']
bpy.context.scene.objects.active = arm_obj
make sure armature in bendy bone view mode
arm_obj.data.draw_type = 'BBONE'
set the segments for bendy bone
arm_obj.data.bones[midBoneName].bbone_segments = segments
set the handles for bendy bone
arm_obj.pose.bones[midBoneName].use_bbone_custom_handles = True
arm_obj.pose.bones[midBoneName].bbone_custom_handle_start = arm_obj.pose.bones[headBoneName]
arm_obj.pose.bones[midBoneName].bbone_custom_handle_end = arm_obj.pose.bones[tailBoneName]
to add stretchiness to bone chain a stretch to constraint on middle bone aiming at tail bone can be made - see constraints section.

Constraints

its helpful when scripting constraints to first create them via the ui and see in the info panel what properties are being used and what ui fields are changed.

Action Constraint

This is sort of like a blendshape for joints. it can be used to create an animation for some joints and have a different animator control drive the pose. to make this constraint we first need to have an action already created with possible animation.

Creating the action

bpy.data.actions.new('TestAction')
how to create action animation for some joints/bones?
bpy.data.objects['Armature'].animation_data.action = bpy.data.actions['TestAction'] #makes action selected

#adding a new fcurve to existing action for a given pose bone
bpy.data.objects['Armature'].animation_data.action.fcurves.new('pose.bones["Bone"].location’,1)#y

#adding a keyframe to existing action and fcurve on a given pose bone
bpy.data.objects['Armature'].animation_data.action.fcurves[0].keyframe_points.insert(1,0)
bpy.data.objects['Armature'].animation_data.action.fcurves[0].keyframe_points.insert(20,5)

#toggle in out of pose mode to refresh animation - might want to try bpy.context.scene.frame_set(4)

Creating the action constraint given the action

act = bpy.data.objects['Armature'].pose.bones['Bone'].constraints.new('ACTION') #this is bone to be driven by constraint
act.target = bpy.data.objects['Armature']
act.subtarget = 'anim'  #this is bone used to drive the animation for the blendshape
act.transform_channel = 'LOCATION_X'
act.target_space = 'LOCAL'
act.min = 0 #what value by animator
act.max = 1
act.frame_start = 1 #what value for bone action animation
act.frame_end = 20
act.action = bpy.data.actions['TestAction']

Stretch-to constraint

this constraint can be used add squash and stretch to a bone. so can have for example squash and stretch head for cartoony animation. how to create a stretch to constraint? this example also shows use of function in python

def UT_CreateStretch( bone = None, endBone = None, armatureName = None ):
    """create stretchto constraint on given bone aiming at given end bone
    bone - bone string name with the stretchto constraint
    endBone - end bone, stretching aims at this bone
    armatureName - armature string name
    """
    arm_obj = bpy.data.objects[armatureName]
    bpy.context.scene.objects.active = arm_obj
    bpy.ops.object.mode_set(mode='OBJECT')
    
    constraint = arm_obj.pose.bones[bone].constraints.new('STRETCH_TO')
    constraint.target = arm_obj
    constraint.subtarget = endBone

how to create other constraints example spline-ik constraint?

bpy.ops.pose.constraint_add(type='SPLINE_IK')
bpy.context.object.pose.bones["Bone.001"].constraints["Spline IK"].target = bpy.data.objects["BezierCurve"]
bpy.context.object.pose.bones["Bone.001"].constraints["Spline IK"].chain_count = 5

then from the above info we can see how to build the constraint but this time not using bpy.ops:

act = bpy.data.objects['Armature'].pose.bones['Bone.001'].constraints.new('SPLINE_IK')
act.target = bpy.data.objects["BezierCurve"]
act.chain_count = 5

the above example is hypothetical and requires the armature, bones and curve to exist in the blender scene prior to calling the commands. also notice how we can store the result of a command inside of variable to be later used when setting its properties.

similar technique can be used to create any constraint in blender. simply make it one time using ui read from the info panel the properties used. then remake it but this time using scripting without using bpy.ops.

notice also the use of the pose bone in the commands. constraints are added to the pose bone of armature and not the edit bones.

Materials

if we want to color a mesh then adding a material is one way to do that.

#making a new material
>>> mat = bpy.data.materials.new('matname')
>>> mat
bpy.data.materials['matname']

#assign material to a mesh
>>> bpy.data.objects['Plane'].data.materials.append(mat)

#change material to green color
>>> mat.diffuse_color = (0, 1, 0 ) #using rgb

Weight Painting

for weight painting in Blender a couple of topics that are useful are vertex groups and vertex indices. There is a way to show vertex ids in viewport using bpy.app.debug set to True. it will show the index of the selected vertex. The other concept is vertex group. when bones are skinned to mesh vertex groups are created on the mesh with the same name as the bones. Here are a couple of snippets to understand how to use python for weight painting.

#get weight of selected vertex (after setting weight in edit mode, needed to go to object mode and then back to have weight updated)
>>> bpy.context.edit_object.data.vertices[7].groups[0].weight
0.699999988079071

>>> bpy.context.edit_object.data.vertices[7].groups[1].weight
0.30000001192092896

here the two bones we are setting weight on are named ‘Bone’ and ‘Bone.001’

#set weight on selected vertex (here vertex index is 7)
>>> obj = bpy.context.edit_object  #needs to be in edit mode
#need to be in object mode for setting  weights
>>> obj.vertex_groups['Bone.001'].add([7], 0.8, 'REPLACE')
>>> obj.vertex_groups['Bone'].add([7], 0.2, 'REPLACE')
>>> 

Properties/Custom Attributes

why are they used? how to create them?

properties or custom attributes can be added to various objects in blender for example on a pose bone. they are helpful to be able to control some behaviour for example turning on a blendshape or controlling a translation/rotation on a bone or turning on a constraint etc. They can be helpful to be controls the animator can use to make things happen for example a blink.

here’s an example for creating a property on a pose bone assuming bone and armature are already created:

from rna_prop_ui import rna_idprop_ui_prop_get
pb = bpy.data.objects['Armature'].pose.bones['Bone']
pb['yeaah']=0.0
prop = rna_idprop_ui_prop_get(pb,'yeaah',create = True)
prop['default']=0.0
prop['min']=0.0
prop['max']=100.0

to see the created property can scroll down N-panel to the properties section. or click on the bone icon in menu underneath outliner and scroll down to Custom Properties. clicking Edit of custom property can show that values that were set. hovering over the various fields in the custom property ui can show what attributes to be use for scripting for example it shows description, and soft_min, soft_max could also be set in the prop dictionary from above snippet.

to go back and add a description to the property just created:

>>> prop1 = rna_idprop_ui_prop_get(pb,'yeaah')
>>> prop1['description'] = 'a comment'

Drivers

why are they used? how to create them?

Drivers are a way for one property/channel/attribute to communicate with a different property/channel/attribute. For example a blink animator control drives an action constraint that does the blink closing. Or the movement of a pose bone animator constraint in translation Z tells a shapekey/blendshape when to turn on. Its helpful before trying to script a driver to first use blender’s ui to create a driver. Also when creating the driver via blender ui its helpful to read the info panel to see what driver attribute gets changed when a field or drop down is changed in Driver’s tab ui.

here’s an example where an Empty objects translation in Z direction drives a pose bone’s translation in Y. it assumes the armature, bone and Empty have already been created and have the names used in snippet:

pb = bpy.data.objects['Armature'].pose.bones['Bone']
drv = pb.driver_add('location',1).driver
drv.type = 'SCRIPTED'
var = drv.variables.new()
var.name = 'vary'
var.targets[0].id=bpy.data.objects['Empty']
var.targets[0].data_path='location.z'
drv.expression = 'vary*3'

here’s a brief breakdown of above snippet. first we save the pose bone inside of a variable. then we add a driver to the translation/location 1 index or Y channel. then we specify the driver as a scripted expression (we can use math in the relationship). next since we are using only one thing to drive the pose bone we create a single driver variable and store it into a variable. then we set certain properties on the driver variable and the driver itself. in the scripted expression we have used a multiplication by 3 to show math can be used in the expression.

for seeing the created driver can open a Graph Editor panel. Then switch from F-curve to Drivers in the drop down to the right of key. Then can click the y location channel in graph window. Then with the N-panel open for the graph editor panel can click on Drivers to see the created driver expression.

here’s another example where one bone’s custom property drives the Y location of a different bone. it assumes two bones are created and on the first bone there is a custom property of needed name.

pb = bpy.data.objects['Armature'].pose.bones['Bone.001']
drv = pb.driver_add('location',1).driver
drv.type='SCRIPTED'
var = drv.variables.new()
var.name = 'varBlink'
var.targets[0].id = bpy.data.objects['Armature']
var.targets[0].data_path = 'pose.bones["Bone"]["blink"]'
drv.expression='varBlink'

(in the above example bone called Bone has a custom property called blink that drives the translation Y of bone called Bone.001.)

here’s another example where one bone’s translation in Y drives another bones translation in Y, it assumes armature and two bones have been created:

#one bones local translation drives another bones translation
pb = bpy.data.objects['Armature'].pose.bones['Bone']
drv = pb.driver_add('location',1).driver
var = drv.variables.new()
var.name = 'varName'
var.type = 'TRANSFORMS'
var.targets[0].id = bpy.data.objects['Armature']
var.targets[0].bone_target = 'Bone.001'
var.targets[0].transform_type = 'LOC_Y'
var.targets[0].transform_space = 'LOCAL_SPACE'
drv.expression = 'varName'

here’s another example similar to above but this time uses the SINGLE_PROP type of driver, it assumes armature and two bones have been created:

#one bones local translation drives another bones translation
pb = bpy.data.objects['Armature'].pose.bones['Bone']
drv = pb.driver_add('location',1).driver
var = drv.variables.new()
var.name = 'varName'
var.type = 'SINGLE_PROP'
var.targets[0].id = bpy.data.objects['Armature']
var.targets[0].data_path = "pose.bones[\"Bone.001\"].location.y"
drv.expression = 'varName'

to help get the data_path in above for mac can hold command and click on channel and choose ‘Copy Data Path’. for the above example still needed to add the .y to specify the channel to use of the driver object

Blendshapes/Shapekeys

for animating faces in 3d one way to get different poses of the face is using a blendshape which is also called Shapekey in Blender. Here are some snippets relating to shapekeys.

#getting vertex position of shape key for meshes
>>> bpy.data.meshes['Plane'].shape_keys.key_blocks['Key 1'].data[3].co
Vector((1.0, 2.113257646560669, 0.0))

>>> bpy.data.meshes['Plane'].shape_keys.key_blocks['Basis'].data[3].co
Vector((1.0, 1.0, 0.0))
(vectors can be subtracted)

#for lattices
#number of lattice points
>>> len(bpy.data.objects['Lattice'].data.shape_keys.key_blocks['Key 1'].data)
8
>>> 
>>> bpy.data.objects['Lattice'].data.shape_keys.key_blocks['Key 1'].data[7].co
Vector((0.5, 1.5631684064865112, 0.5))
these are some more snippets they will need to be edited as the mesh names need to be already created and the syntax needs to be edited.
#set position of shape key vertex to default
>>> default_pos = bpy.data.meshes['Plane'].shape_keys.key_blocks['Basis'].data[3].co #3 is the vertex index
>>> bpy.data.meshes['Plane'].shape_keys.key_blocks['Key 1'].data[3].co = default_pos
>>> 

##deleting all shape keys from mesh
#first turn on selected shape key
bpy.data.objects['Plane'].show_only_shape_key = True
>>> for shp in bpy.data.meshes['Plane'].shape_keys.key_blocks:
...     bpy.data.objects['Plane'].shape_key_remove(shp)
##

#deleting a single key by its name
>>> shp = bpy.data.meshes['Plane'].shape_keys.key_blocks["Key 1"]
>>> type(shp)
class 'bpy.types.ShapeKey'

>>> bpy.data.objects['Plane'].shape_key_remove(shp)


#deleting all shape keys leaving mesh as default mesh
non_basis_shps = [shp for shp in bpy.data.meshes['Plane'].shape_keys.key_blocks if shp.name != 'Basis']
for shp in non_basis_shps:
	bpy.data.objects['Plane'].shape_key_remove(shp)
#remove the basis
basis_shp = bpy.data.meshes['Plane'].shape_keys.key_blocks[0]
bpy.data.objects['Plane'].shape_key_remove(basis_shp)
######

Storyboard Video Sequencer

the video sequencer can be used as a tool for creating animatic of storyboards. here are a couple snippets on scripting with the video sequencer.

i think to start to add strip or scenes to video sequencer a sequence editor needs to be created.

>>> bpy.data.scenes['Scene'].sequence_editor_create()
bpy.data.scenes['Scene']...SequenceEditor

>>> 
>>> bpy.data.scenes['Scene'].sequence_editor
bpy.data.scenes['Scene']...SequenceEditor

>>> bpy.data.scenes['Scene'].sequence_editor is None
False

if a scene has been added to the sequence editor this is snippet to change the start frame of the strip. This way to whole strip can be moved to wherever the frame_start is specified. all the animation in the scene is properly offset. setting frame_start to 1 says to start the scene strip at frame 1.

>>> bpy.data.scenes['Scene'].sequence_editor.sequences['Scene.001'].frame_start

i think to set the length of video strip want to use the frame_final_duration. as this way the strip can be moved and changing the frame_final_duration is not in global position but more the offset from the start of the strip to the end. so using frame_final_duration gives the value more likely expecting. for example setting it to 50 say scene is 50 frames long.

>>> bpy.data.scenes['Scene'].sequence_editor.sequences['Scene.001'].frame_final_duration = 90

here is a bit on adding a scene to the sequencer.

>>> scn = bpy.data.scenes['Scene']
>>> scn
bpy.data.scenes['Scene']

>>> type(scn)
class 'bpy.types.Scene'

>>> bpy.data.scenes['Scene'].sequence_editor.sequences.new_scene('testA',scn,1,10)
bpy.data.scenes['Scene'].sequence_editor.sequences_all["testA"]
#bpy.ops.sequencer.scene_strip_add(frame_start=10,channel=1,scene = 'Scene' )

how to playblast a scene strip from sequencer? one way is have avi jpeg video set on the scene. also need to specify the output path set on scene

bpy.data.scenes['Scene'].render.filepath = '/Users/Nathaniel/Desktop/doodles_021922/images/'
bpy.data.scenes['Scene'].render.image_settings.file_format = 'AVI_JPEG'
bpy.ops.render.opengl(animation=True,sequencer = True)

Modifiers

these can be used to modify the shape of a mesh. an example is the armature modifier which is used to have joints/bones moving the skin of a mesh. another example is mesh deform modifier which allows one mesh to deform a second mesh. Here are some snippets:

###
#trying to not use ops as much as possible, Cube to be moved by Cube.001
mesh_def= bpy.data.objects['Cube'].modifiers.new("mesh_def",type="MESH_DEFORM")
#i think toggle in and out of object mode
mesh_def.object = bpy.data.objects['Cube.001']
bpy.context.scene.objects.active = bpy.data.objects['Cube'] #in 2.8 this is different
bpy.ops.object.meshdeform_bind(modifier=mesh_def.name) #i don’t think there’s a non ops replacement
#i think toggle in and out of object mode
###
some more snippets.
###removing modifier - assume object is selected
bpy.ops.object.modifier_remove(modifier = bpy.data.objects['Plane'].modifiers[0].name )

Lattices

Lattices are a way to deform a mesh at a higher level of granularity than moving individual vertices. it also allows for smooth like deformations.

The process to create a lattice via scripting is to first create the lattice data then create the lattice object using that data. Then depending on whether you are in blender 2.79 or higher versions the object needs to be linked. For this example will show blender 2.79 way to link the object to the scene.

once the lattice is created it can then be assigned to a mesh to deform.

once a lattice is able to deform a mesh we can have other objects example joints/bones move the lattice points to get an even higher level of control. joint moves lattice point which moves vertices of mesh. One process to get joints to move lattice points is to add a hook modifier to the lattice and assign it a joint. It is slightly different on how to assign lattice points to the hook modifier depending whether in blender 2.79 or in newer versions.

to create a lattice

>>> dat = bpy.data.lattices.new("testLattice")
>>> dat
bpy.data.lattices['testLattice']

>>> type(dat)


>>> latticeObj = bpy.data.objects.new("testLat",dat)
>>> latticeObj
bpy.data.objects['testLat']

>>> type(latticeObj)


>>> bpy.data.scenes['Scene'].objects.link( bpy.data.objects['testLat'] )

to change dimension of lattice

>>> bpy.data.objects['testLat'].dimensions = (2,2,2)

>>> bpy.data.objects['testLat'].data
bpy.data.lattices['testLattice']

>>> type( bpy.data.objects['testLat'].data )


>>> bpy.data.objects['testLat'].data.points_u = 5
>>> bpy.data.objects['testLat'].data.points_v = 5
>>> bpy.data.objects['testLat'].data.points_w = 5

to apply lattice to some geometry. here its applying lattice to a sphere named 'Sphere'

>>> bpy.data.objects['Sphere'].modifiers.new('lat','LATTICE')
bpy.data.objects['Sphere'].modifiers["lat"]

>>> type( bpy.data.objects['Sphere'].modifiers['lat'] )


>>> bpy.data.objects['Sphere'].modifiers['lat'].object = bpy.data.objects['testLat']

to move lattice points with a joint/bone

>>> len(bpy.data.objects['testLat'].data.points)
125

##get selected lattice point
>>> [x for x in range(0,125) if bpy.data.objects['testLat'].data.points[x].select == True ]
[112]

#make hook modifier
>>> bpy.data.objects['testLat'].modifiers.new('hookMod', type = 'HOOK' )
bpy.data.objects['testLat'].modifiers["hookMod"]

>>> type( bpy.data.objects['testLat'].modifiers["hookMod"] )


bpy.data.objects['testLat'].modifiers["hookMod"].object = bpy.data.objects['Armature']
bpy.data.objects['testLat'].modifiers["hookMod"].subtarget = 'Bone'

#for blender 2.79 need to use ops to actual assign lattice points to bones (in higher blender versions hook modifier has vertex_indices_set to help assign points to bones)
#need to be in edit mode of lattice with point selected
bpy.ops.object.hook_assign(modifier= 'hookMod')
bpy.ops.object.hook_reset(modifier= 'hookMod') #need this so lattice isnt in deformed state

Node Editor (Shaders)

introduction to using scripting to create materials. this knowledge can apply to other tools like Nuke, Katana, Houdini, Maya etc.

will go over creating a node, connecting two different nodes, changing attributes of a node, and moving node around in ui.

creating a node (can see type by hovering over Add menu items in node editor):
bpy.data.materials['Material'].node_tree.nodes.new("ShaderNodeGeometry")


connecting two nodes:
>>> a = bpy.data.materials['Material'].node_tree.nodes['Geometry'].outputs['UV']
>>> b = bpy.data.materials['Material'].node_tree.nodes['Texture'].inputs['Vector']
>>> bpy.data.materials['Material'].node_tree.links.new(a,b) #a -> b
bpy.data.node_groups['Shader Nodetree']...NodeLink


changing an attribute/property of a node
>>> bpy.data.materials['Material'].node_tree.nodes['Texture'].texture = bpy.data.textures['Tex']
or
>>> bpy.data.materials['Material'].node_tree.nodes['Material'].inputs['DiffuseIntensity'].default_value = 0.5

changing position of node in node editor
>>> bpy.data.materials['Material'].node_tree.nodes['Texture'].location = (-50,150) #x,y

##
>>> type(bpy.data.materials['Material'].node_tree.nodes['Geometry'].outputs['UV'])
class 'bpy.types.NodeSocketVector'
>>> type(bpy.data.materials['Material'].node_tree.nodes['Geometry'] )
class 'bpy.types.ShaderNodeGeometry'
>>> type(bpy.data.materials['Material'].node_tree)
class 'bpy.types.ShaderNodeTree'

##
>>> type(bpy.data.textures['Tex'])
class 'bpy.types.ImageTexture'

Cloth

At the most basic level to get a mesh to act like cloth we create a cloth modifier. we then need to tell it to not completely fall by holding some vertices still via pinning. also we want it to collide with another mesh (ex a sphere) we add a collision modifier to the sphere. For the cloth mesh to get it to collide with the sphere we need to tell it to use collisions.

Before we can get cloth like behaviour on mesh we need to make a cloth modifier on it. To make the modifier need to know what type it is here the type is 'CLOTH'. also the modifier are added to the data object of type 'bpy_types.Object'. Here our cloth mesh is called 'Plane':

bpy.data.objects['Plane'].modifiers.new('testModifier','CLOTH')

after the cloth modifier is created there are two sets of settings to access that we can tweak to get the behaviour we want. They are the "collision_settings" and the "settings". Here is an example to tell the cloth to use collisions:

bpy.data.objects['Plane'].modifiers['testModifier'].collision_settings.use_collision = True

what above is saying is look at the mesh called Plane and its cloth modifier called 'testModifier' and turn on its collision ability.

suppose we have some vertices we have already assigned to a vertex group on the cloth mesh. We can tell the cloth modifier to not move these points. We can get what properties to use by hovering over the label in Blender's ui. These two lines should pin the cloth here 'Group' is the name of the vertex group of some vertices of cloth:

bpy.data.objects['Plane'].modifiers['testModifier'].settings.use_pin_cloth = True
bpy.data.objects['Plane'].modifiers['testModifier'].settings.vertex_group_mass = 'Group'

finally to get the collision object to collide with our cloth we need to add a collision modifier on the collision object. Here the collision object is a sphere and the type of modifier is 'COLLISION':

>>> bpy.data.objects['Sphere'].modifiers.new('testCollision','COLLISION')

There are lots of properties to tweak to edit the behaviour of the cloth simulation. Many of those can be accessed using either the 'settings' or 'collision_settings' of the cloth modifier. One example is changing the mass of the cloth:

bpy.data.objects['Plane'].modifiers['testModifier'].settings.mass = 5

Again by hovering over the settings labels in the Physics panel shows what the properties are called when used in a script.

Intro to working with text files (xml,json)

The two file examples covering are for xml and json. (yaml is another option but won't cover it since i don't think its available in blender 2.79). writing to text files is one way to store information that can later be accessed when the information is read back in (for example could write out an action constraint to a json text file and that info could be later read back in possibly to a different blender file)
#writing to xml
import xml.etree.ElementTree as ET
from xml.dom import minidom

root = ET.Element('rig')
bone1 = ET.SubElement( root, 'bone', name = "upArm.L")
bone2 = ET.SubElement( root, 'bone', name = "lowArm.L")

xmlstr = minidom.parseString( ET.tostring(root)).toprettyxml(indent="   ")
with open('temp.xml','w') as ofile:
    ofile.write( xmlstr )

#this is what temp.xml looks like """ <!--xml version="1.0" ?--> <rig> <bone name="upArm.L"/> <bone name="lowArm.L"/> </rig> """
#reading xml
import xml.etree.ElementTree as ET
tree = ET.parse('temp.xml')
root = tree.getroot()

"""seeing the type of first item
>>> type(root[0])

"""

"""from the help can see what attributes Element has
#help(root[0]) 
>>> root[0].attrib
{'name': 'upArm.L'}
"""

"""
>>> for bone in root:
...     print(bone.attrib)
... 
{'name': 'upArm.L'}
{'name': 'lowArm.L'}
"""

"""
>>> for bone in root:
...     print(bone.tag)
...     print(bone.attrib)
... 
bone
{'name': 'upArm.L'}
bone
{'name': 'lowArm.L'}
"""


############
#writing to json
import json

d = {"upLeg":(0,0,0)}
with open('temp.json',"w") as outf:
    json.dump(d,outf,indent=4)

#this is what temp.json looks like
"""
{
    "upLeg": [
        0, 
        0, 
        0
    ]
}
"""

#reading json
import json
with open('temp.json') as f:
    r = json.load(f)

"""
>>> print(r)
{u'upLeg': [0, 0, 0]}
"""

Intro to running script without blender gui

Using subprocess is one way to be able to open blender and do some stuff in blender without needing to open the gui.

in this first example its kindof an intro to using subprocess to list things in current folder

import subprocess

#calling ls from shell
subprocess.call( ['ls','-ltrA'] )

#to get the output of the shell command
p = subprocess.Popen( ['ls','-ltrA'], stdout = subprocess.PIPE, stderr = subprocess.PIPE )
out, err = p.communicate()
print(out)

in this second example we try to create a folder.

#making a folder
p = subprocess.Popen( ['mkdir','folderA'], stdout = subprocess.PIPE, stderr = subprocess.PIPE )
out, err = p.communicate()
"""
>>> out
''
>>> err
''
"""
#but if make same folder again should get an error because it already exists
"""
>>> p = subprocess.Popen( ['mkdir','folderA'], stdout = subprocess.PIPE, stderr = subprocess.PIPE )
>>> out, err = p.communicate()
>>> err
'mkdir: folderA: File exists\n'
>>> out
''
>>> 
"""

in this last example we run a python script that list names of objects in a blender file (here called bfile.blend) by launching blender in the background

bcmd_str = """
import bpy

obj_names = []

for obj in bpy.data.objects:
    obj_names.append(obj.name)

print(obj_names)  
"""

#and here is using subprocess to open blender in background and run that python snippet
import subprocess
blenderPath = "/Applications/blender.app/Contents/MacOS/blender" #change this. this was for a mac
p = subprocess.Popen( [blenderPath,"--background", "bfile.blend", "--python-expr", bcmd_str], stdout = subprocess.PIPE, stderr = subprocess.PIPE )
out, err = p.communicate()
objNames = eval(out.split("\n")[0])
#>>> objNames
#['Camera', 'Cube', 'Lamp']

Intro to creating command line tool

This is an intro to writing a command line tool that uses arguments provided by user. Here we are using argparse for managing the arguments.

#!/usr/bin/env python
"""
need the shebang above to be able to run script without having python in front
ex: ./simple_cmd_tool.py
instead of: python simple_cmd_tool.py
also needed to make script executable
"""

import argparse

def doIt(_file, _mesh, _scale, _required):
    print("meat of tool using file:{0} mesh:{1} scale:{2} required:{3}".format(_file,_mesh,_scale,_required))
    
if __name__ == "__main__":    
    parser = argparse.ArgumentParser(description="this is a simple example of a command line tool using arguments")
    parser.add_argument("-f", "--file", type=str, help="blender file name", default="defaultBlendFile")
    #the default above will be used if -f not used when calling command
    parser.add_argument("-m", "--mesh", action="store_true", help="should meshes be printed")
    #for above including -m in the arguments sets the variable to True otherwise its False
    parser.add_argument("-s", "--scale", type=float, help="scale value to use on objects", default=2.0)
    parser.add_argument("-r", "--required", type=int, help="this is a required argument it should be any int", required = True)
    
    args = parser.parse_args()
    print("blend file {0}".format(args.file))
    print("meshes {0}".format(args.mesh))
    print("scale {0}".format(args.scale))
    print("required {0}".format(args.required))
    
    #these parameters can then be used in the script.
    doIt(args.file, args.mesh, args.scale, args.required)

Some Simple Class Examples

Mirror Meshes Example

I’m by no means an expert on classes but here is one very simple example using a class to help with mirroring separate meshes and also mirror its bone parenting. it could have use in modelling or rigging. In writing this class i first wrote how it will be used the mirror methods then i slowly built those methods. i then started writing the helper methods. then i used blender to help test the helper methods slowly working out each of them. The helper methods that are not the focus of the script were given an underscore at the start.

here is an example usage of the class.
geo = GeometryMirrorOperator( geometryName = "Cone.L")
geo.mirror()
geo.mirrorBoneParenting( armatureName = "Armature" )
and here is the mirror mesh operator class and some helper methods.

class GeometryMirrorOperator(object):
    """a simple class to handle some mirroring operations for a Mesh.
    used for example if have a left hand with geo pieces and want to mirror it to right side.
    also should support mirroring the bone parenting
    """
    def __init__(self, geometryName):
        """
        @param geometryName : data object name for source geometry
        """
        self.geometryName = geometryName
        self.geometryObject = bpy.data.objects[self.geometryName]
        self.mirrorGeometryName = None
        self.mirrorGeometryObject = None
        
    def mirror(self):
        """this is different from applying mirror modifier by not having to separate loose pieces.
        so mesh can have multiple separate pieces joined and those pieces will be joined in mirror result.
        """
        if not _isHaveSide(self.geometryName):
            print("cannot find a side for {0}".format(self.geometryName) )
            return False
            
        #duplicate the geometry
        dupGeoName = self._duplicate() 
        dupRenamedName = self._mirrorName( dupGeoName ) #rename duplicate geometry
        self.mirrorGeometryName = dupRenamedName
        self.mirrorGeometryObject = bpy.data.objects[self.mirrorGeometryName]
        
        #may need to apply all modifiers to duplicate geo
        
        #sets its position to x mirror
        mirrorPosition = self._getMirrorPosition( axis = 'x' ) #assume mirror in x axis
        self.mirrorGeometryObject.location = mirrorPosition

    def mirrorBoneParenting(self, armatureName):
        """tries to mirror the bone parenting from one side to another 
        does nothing if this geometry doesnt end in .L or .R
        also does nothing if this geometry doesnt have a bone parent ending in .L or .R
        
        @param armatureName - required. name for armature that has bones
        """
        #validate all conditions needed for this method are met
        if not _isHaveSide(self.geometryName):
            print("cannot find a side for {0}".format(self.geometryName) )
            return False
        if not _isHaveParent(self.geometryName):
            print("cannot find a parent for {0}".format(self.geometryName) )
            return False
            
        boneName = self.geometryObject.parent_bone
        if not _isBoneHaveMirror(boneName, armatureName):
            print("cannot find a bone mirror for {0}".format(boneName) )
            return False
            
        #figure out mirror bone and parent this geometry to it
        mirrorBone = _getMirrorBone(boneName, armatureName)
        print("found mirrorBone {0}".format(mirrorBone))
        _parentGeoToBone( self.mirrorGeometryName, mirrorBone, armatureName )
        #self.mirrorGeometryObject.parent = bpy.data.objects[armatureName]
        #self.mirrorGeometryObject.parent_bone = mirrorBone
        
        return True
        
    def _duplicate(self):
        """duplicate geometry for this class
        @return - string name for duplicated geometry
        """
        sourceObj = self.geometryObject
        sourceObjData = sourceObj.data
        
        dupObjData = sourceObjData.copy()
        dupObj = sourceObj.copy()
        dupObj.data = dupObjData
        
        _addObjectToBlender( dupObj.name )
        
        return dupObj.name
    def _mirrorName( self, editGeometryName ):
        """mirror the name of geometry of this class
        """
        if not _isHaveSide(self.geometryName):
            print("cannot find a side for {0}".format(self.geometryName) )
            return False
            
        editObj = bpy.data.objects[editGeometryName]
        
        if self.geometryName.endswith('.L'):
            editObj.name = self.geometryName.replace('.L','.R')
        else:
            editObj.name = self.geometryName.replace('.R','.L')
            
        return editObj.name
        
    def _getMirrorPosition( self, axis = 'x' ):
        """get the mirror position of this object
        """
        result = (0,0,0)
        
        currentPosition = self.geometryObject.location
        
        #only supporting mirroring along x axis
        if axis in ["x"]:
            result = ( currentPosition[0]*(-1), currentPosition[1], currentPosition[2] )
            
        return result



def _addObjectToBlender( objectName ):
    """adds object with given name to blender. currently supporting 2.79 workflow adding to scene.
    for higher blender versions need to add to a collection
    """
    obj = bpy.data.objects[objectName]
    bpy.context.scene.objects.link(obj)
    

def _isHaveSide(name):
    """does thing with given name end with a side
    @return boolean
    """
    if name.endswith('.L') or name.endswith('.R'):
        return True
        
    return False
    
def _isHaveParent(objectName):
    """does object with given name have a bone parent
    @return boolean
    """
    obj = bpy.data.objects[objectName]
    par = obj.parent_bone #if its in ui it should exist. for checking bone parent
    if par:
        return True
        
    return False
    
def _isBoneHaveMirror(boneName, armatureName):
    """does bone have a mirror
    @return boolean True if bone has a mirror bone that exists
    """
    if not _isHaveSide(boneName):
        return False
       
    #search armature for mirror bone
    mirrorBone = _getMirrorBone(boneName, armatureName)
    if mirrorBone:
        return True
        
    return False
    
def _getMirrorBone(boneName, armatureName):
    """
    @return - string for mirror bone name
    """
    if not _isHaveSide(boneName):
        print("cannot find side for bone {0}".format(boneName) )
        return False
    
    if boneName.endswith('.L'):
        result = boneName[:-2]+".R"
    elif boneName.endswith('.R'):
        result = boneName[:-2]+".L"
    
    #print("Testing >>> _getMirrorBone result {0}".format(result) )
    if _isBoneExist(result, armatureName):
        return result
        
    return False
    
def _isBoneExist(boneName, armatureName):
    """does given bone name exist in given armature
    """
    armatureObj = bpy.data.objects[armatureName]
    return boneName in armatureObj.data.bones.keys()
    
def _parentGeoToBone( geoName, boneName, armatureName ):
    geoObj = bpy.data.objects[geoName]
    armObj = bpy.data.objects[armatureName]
    
    bpy.ops.object.select_all(action='DESELECT')
    bpy.context.scene.objects.active = armObj #2.79 dependent i think
    bpy.ops.object.mode_set(mode='POSE')
    armObj.data.bones.active = armObj.data.bones[boneName]
    geoObj.select = True
    bpy.ops.object.parent_set(type = 'BONE')    
    

Select opposite controls Example (assumes controls end in .L or .R)


import bpy

class Selector(object):
    """used to select opposite controls. tested in blender 2.79
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/snippets/selectOpposite.py") #change to path to python file

ss = testTool.Selector()
ss.selectOpposite()
    """
    def __init__(self):
        self.armatureName = None
        self.activeBoneName = None
        
        if bpy.context.object.type == "ARMATURE":
            self.armatureName = bpy.context.object.name
            self.activeBoneName = bpy.context.active_pose_bone.name
        
    def selectOpposite(self):
        """
        select opposite animator control.
        assumes animator control ends with .L or .R
        """
        #get current selection. could be multiple
        msg = "requires a pose bone selected in pose mode. doing nothing"
        if not bpy.context.object:
            print(msg)
            return
        if not bpy.context.object.type == "ARMATURE":
            print(msg)
            return
            
        curSel = []
        curSel = [bone.name for bone in bpy.context.selected_pose_bones]
        
        #reset selection
        self._clearSelection()
        
        #select opposite
        oppositeSel = []
        for sel in curSel:
            _oppositeSel = self._getOpposite(sel)
            oppositeSel.append(_oppositeSel)
            
        self._select(oppositeSel)

        oppositeActiveBone = self._getOpposite( self.activeBoneName )
        self._setActiveBone(oppositeActiveBone)
        
    def _getOpposite(self, arg):
        """get the opposite side animator control
        """
        if not arg.endswith('.L') and not arg.endswith('.R'):
            print("requires selected to end in .L or .R")
            return None
            
        if arg.endswith('.L'):
            return arg.split('.L')[0]+'.R'
        else:
            return arg.split('.R')[0]+'.L'
            
    def _clearSelection(self):
        """deselect all"""
        bpy.ops.pose.select_all(action = 'DESELECT')

    def _select(self, nodes=[] ):
        """
        select all nodes
        @param nodes - list of str ex: ['fk_arm.L']
        """
        for node in nodes:
            #i think this is specific to blender 2.79
            bpy.data.objects[self.armatureName].pose.bones[node].bone.select = True
     
    def _setActiveBone(self, node):
        """set active pose bone to given node
        @param node - str ex: 'fk_arm.R'
        """
        #i think this is specific to blender 2.79
        bpy.data.objects[self.armatureName].data.bones.active = bpy.data.objects[self.armatureName].data.bones[node]
        #toggle to refresh bone highlight
        bpy.ops.object.mode_set(mode="OBJECT")
        bpy.ops.object.mode_set(mode="POSE")
     
#inspired by
#https://www.11secondclub.com/forum/viewtopic.php?id=2068

Paint meshes a color Example


import bpy

class Painter(object):
    """used to paint meshes a color. tested in blender 2.79
import imp
testTool = imp.load_source("tool","/Users/Nathaniel/Documents/src_blender/python/snippets/paintMeshes.py") #change to path to python file

matName = testTool._createMaterial(materialName = "bodyColor", rgb=(0,0,0.5) )
pp = testTool.Painter()
pp.assignMaterialToMeshes(materialName = matName, meshes = ["Cube"])    
    """
    
    def assignMaterialToMeshes( self, materialName = None, meshes = [], replace = True):
        """assign given material to given meshes. assumes all inputs exist
        @param materialName - str material name
        @param meshes - list of str of mesh names
        @param replace - bool whether or not to replace material. when true it removes all existing materials from mesh first
        """
        #assign material to meshes
        materialObj = bpy.data.materials[materialName]
        
        if replace:
            #remove any existing material
            self._removeAllMaterials(meshes = meshes)
            
            
        for mesh in meshes:
            if self._isMaterialAdded( mesh, materialName ):
                continue
            bpy.data.objects[mesh].data.materials.append(materialObj)       
    
    def _isMaterialAdded( self, geoName = None, materialName = None ):
        """check if material was already added to mesh
        """
        geo = bpy.data.objects[geoName]
        mat = bpy.data.materials[materialName]
        
        if len(geo.data.materials) == 0:
            return False
        for m in geo.data.materials:
            if m.name == mat.name:
                return True
                
    def _removeAllMaterials( self, meshes = [] ):
        """remove all materials on given meshes
        @param meshes - list of str of mesh names
        """
        for mesh in meshes:
            meshObject = bpy.data.objects[mesh]
            meshObject.active_material_index = 0
            for i in range(0,len(meshObject.material_slots)):
                bpy.ops.object.material_slot_remove({'object':meshObject})
  
  
def _createMaterial( materialName = None, rgb = (0,1,0) ):
    """create material
    @param materialName - str material name
    @param rgb - tuple of 3 elements for rgb
    @return str - created material name
    """
    if not materialName:
        print("requires a material name to use")
        return
    
    mat = None
    if not materialName in bpy.data.materials:
        #create material
        bpy.ops.object.mode_set(mode='OBJECT')
        mat = bpy.data.materials.new(materialName)
    else:
        mat = bpy.data.materials[materialName]
        
    #assign rgb to material even when material already exists
    mat.diffuse_color = rgb
    
    return materialName
    
    
def _assignRGBToMaterial( materialName = None, rgb = (0,1,0) ):
    """assign rgb to material
    @param materialName - str material name
    @param rgb - tuple of 3 elements for rgb    
    """
    #assign rgb to material
    mat = bpy.data.materials[materialName]
    mat.diffuse_color = rgb
    
    
#inspired by
#https://www.11secondclub.com/forum/viewtopic.php?id=1128

Some Simple Addon Examples

These examples are specific to Blender 2.79 there are changes needed for getting them to work in newer Blender versions.

naShapekeyIOAddOn.py

for the ui portion this goes over using property group to keep ui elements organized. operators to be used for the buttons. register and unregister ui functions needed for addon to work in Blender.

for the content of the add-on it goes over shapekeys in some more detail. it goes over importing and exporting shape keys in Blender.

the main take away for writing the ui portion of the add-on is the use of bl_idname in the operator. this is later used in panel to know what operator is what. also the execute method of the operator is where the meat of what the button does gets added. for the panel the meat of what gets done is in the draw method. the bl_space_type and bl_region_type are also important as some scripting requires the ui to be in the viewport to preserve proper selections for example. in the register and unregister all the ui elements created operators, panels, properties need to be registered and unregistered with Blender.

import bpy
from mathutils import Vector
import json
import os


####add on portion
bl_info = {
    "name":"blendshapes import export",
    "description":"blendshapes import export",
    "category": "Object",
    "author":"Nathaniel Anozie",
    "blender":(2,79,0)
}

from bpy.types import(
    Operator,
    Panel,
    PropertyGroup
    )

from bpy.props import(
    StringProperty,
    PointerProperty
    )


class blendshapeIOProperties(PropertyGroup):
    dir_path = StringProperty(
        name = "Browse",
        description = "Pick path to export/import to/from either a file or a directory",
        maxlen = 200,
        subtype = 'FILE_PATH'
    )

    mesh = StringProperty(
        name = "mesh",
        description = "mesh name with shapekeys - the data object name"
        )
    
    blendshapes = StringProperty(
        name = "blendshapes",
        description = "optional specific blendshapes to export/import - comma separated. if none specified it exports/imports all blendshapes of mesh"
        ) 

class exportBlendshapeOperator(Operator):
    """export blendshape. first enter in some blendshapes and enter in face mesh with blendshapes to export. if no blendshapes provided it exports all blendshapes.
    """
    bl_idname = "obj.exportblendshape"
    bl_label = "export"
    bl_options = {"REGISTER"}
    
    def execute(self, context):
        path = context.scene.blendshapeio_prop.dir_path
        mesh = context.scene.blendshapeio_prop.mesh
        blendshapes_arg = context.scene.blendshapeio_prop.blendshapes
        
        abs_path = bpy.path.abspath(path)
        
        if not os.path.isdir(abs_path):
            self.report({'INFO'},"please navigate to a folder to export blendshapes")
            return {'FINISHED'}

        if not mesh:
            self.report({'INFO'},"please enter in name for the mesh with shape keys - the data object name")
            return {'FINISHED'}

        blendshapes = []
        if blendshapes_arg:
            blendshapes = blendshapes_arg.split(',')
        
        exportShapeKey(mesh = mesh, shapes = blendshapes, dirPath = abs_path  )
        return {'FINISHED'}


class importBlendshapeOperator(Operator):
    """import blendshape. first enter in some blendshapes and enter in face mesh with blendshapes to import. if no blendshapes provided it imports all blendshapes that exist in folder specified.
    """
    bl_idname = "obj.importblendshape"
    bl_label = "import"
    bl_options = {"REGISTER"}
    
    def execute(self, context):
        path = context.scene.blendshapeio_prop.dir_path
        mesh = context.scene.blendshapeio_prop.mesh
        blendshapes_arg = context.scene.blendshapeio_prop.blendshapes
        
        abs_path = bpy.path.abspath(path)

        if not os.path.isdir(abs_path) and not os.path.isfile(abs_path) :
            self.report({'INFO'},"please navigate to a folder or file to import blendshapes")
            return {'FINISHED'}

        if not mesh:
            self.report({'INFO'},"please enter in name for the mesh with shape keys - the data object name")
            return {'FINISHED'}
            
        import_file_paths = []
        #if navigated to an exact file only import that file
        
        if os.path.isfile(abs_path):
            import_file_paths = [abs_path]
        
        else:
            #since navigated to a directory figure out files to import
            blendshapes = []
            if blendshapes_arg:
                blendshapes = blendshapes_arg.split(',')     
        
            #try to import only the blendshapes specified
            if blendshapes:
                for shp in blendshapes:
                    fname = _getBlendshapeFileFromBlendshape(shp)
                    import_file_paths.append( os.path.join(abs_path,fname) )
                    
            else:
                #otherwise try to import all blendshape files in folder
                import_file_paths = getAllFullFilesWithSuffixInFolder( folder = abs_path )
   
        print("importing >>> mesh %s file_paths %s" %(mesh,import_file_paths) )
        
        importShapeKey( mesh = mesh, shapeFiles = import_file_paths  )
        return {'FINISHED'}


class naBlendshapeIOPanel(Panel):
    bl_label = "Blendshape Import/Export Panel"
    bl_space_type = "VIEW_3D" #needed for ops working properly
    bl_region_type = "UI"
    
    def draw(self, context):
        layout = self.layout
        layout.prop(context.scene.blendshapeio_prop, "dir_path")
        layout.prop(context.scene.blendshapeio_prop, "mesh")
        layout.prop(context.scene.blendshapeio_prop, "blendshapes")
        layout.operator( "obj.exportblendshape" )
        layout.operator( "obj.importblendshape" )

def register():
    bpy.utils.register_class(exportBlendshapeOperator)
    bpy.utils.register_class(importBlendshapeOperator)
    bpy.utils.register_class(naBlendshapeIOPanel)
    
    bpy.utils.register_class(blendshapeIOProperties)
    bpy.types.Scene.blendshapeio_prop = PointerProperty( type = blendshapeIOProperties )

def unregister():
    bpy.utils.unregister_class(exportBlendshapeOperator)
    bpy.utils.unregister_class(importBlendshapeOperator)
    bpy.utils.unregister_class(naBlendshapeIOPanel)
    
    bpy.utils.unregister_class(blendshapeIOProperties)
    del bpy.types.Scene.blendshapeio_prop
##end ui portion






def exportShapeKey( mesh = '', shapes = [], dirPath = '' ):
    """exports one file per blendshape
    mesh - is the data.objects name, it can be different from data.objects.data name
    shapes - are the actual shapekey names wish to export. if none provided it expects to export all blendshapes
    note it saves file names of form shape_shapeName where it replaces spaces with underscores
    so exporting a 'Key 1' shape with an already saved shape_Key_1.json would overwrite that shape
    dirPath - is directory to save the shape json files
    """

    """form of shape key data written out (vertDelta is in order of all vertices)
    
    {
    'shape':{ 'shapeName':'Key 1', 'vertDelta':[(0,0,0),(0,.25,.35)...(0,0,0)]
        }
    }
    
    """

    if not os.path.isdir(dirPath):
        print('Requires an out directory that exists to write shapekey file')
        return

    if not mesh in bpy.data.objects:
        print('Requires mesh: %s to exist in scene' %mesh)
        return

    if not bpy.data.objects[mesh].type == 'MESH':
        print('Requires mesh type for %s' %mesh)
        return

    #require mesh to have shape keys
    mesh_obj = bpy.data.objects[mesh].data
    
    if mesh_obj.shape_keys is None:
        print('requires mesh %s to have shape keys' %mesh)
        return
    
    #exit if no Basis shape key
    basis_name = 'Basis'
    if not basis_name in mesh_obj.shape_keys.key_blocks.keys():
        print('requires a basis shape named Basis on mesh %s exiting' %mesh)
        return
        
    if not shapes:
        print('trying to export all blendshapes')
        shapes = [x.name for x in mesh_obj.shape_keys.key_blocks if x.name != 'Basis']

    #start looping over shape keys to export
    print("trying to export shapes: %s" %shapes)
    for shp in shapes:
        shape_dict = {}
        num_verts = 0
        
        #check shp shapekey exists on mesh
        if not shp in mesh_obj.shape_keys.key_blocks.keys():
            print('could not find shapekey %s in mesh %s so skipping it' %(shp,mesh) )
            continue
            
        #todo: verify only upper and lower case letters and spaces and dots in shape key name
         
        num_verts = len( mesh_obj.shape_keys.key_blocks[shp].data )
        
        vert_delta_list = []
        
        for i in range(num_verts):
            vdelta = mesh_obj.shape_keys.key_blocks[basis_name].data[i].co - mesh_obj.shape_keys.key_blocks[shp].data[i].co
            vert_delta_list.append( (vdelta.x, vdelta.y, vdelta.z) )
        
        shape_dict['shape']={'shapeName':shp,'vertDelta':vert_delta_list}
        
        #export this shapekey and continue to next
        shp_filename = _getBlendshapeFileFromBlendshape(shp)
        shp_file = os.path.join(dirPath,shp_filename)
        with open( shp_file, 'w' ) as f:
            json.dump(shape_dict, f, indent=4)
            
def _getBlendshapeFileFromBlendshape(blendshape = ''):
    shp_edit = blendshape.replace(' ','_') #dont want spaces in file names
    return shp_edit+'_shape.json'
    


def importShapeKey( mesh = '', shapeFiles = [] ):
    """ 
    it should create the shape key if it doesnt exist if it does exist should overwrite only that shape key. not saving mesh in shape paths in case want to import onto a different named mesh
    with same topology.
    mesh -  is mesh data.objects name it needs to have at least a Basis shape.
    shapeFiles - list of full path to blendshape files - a shape file has vertex deltas from basis for a single shape key.
    """
    def _isShapeExists( mesh, shapeName ):
        mesh_obj = bpy.data.objects[mesh].data
        return shapeName in mesh_obj.shape_keys.key_blocks
        
    def _sculptShape( mesh, shapeName, vertDelta ):
        #sculpt shape using delta from basis
        mesh_obj = bpy.data.objects[mesh].data
        ##go through each vertex using the basis and the delta stored on disc
        for vid in range(num_mesh_verts):
            basis_pos = mesh_obj.shape_keys.key_blocks['Basis'].data[vid].co
            shp_pos = Vector( vertDelta[vid] ) #Vector so can subtract it
            #move vertices to sculpt shape
            mesh_obj.shape_keys.key_blocks[shapeName].data[vid].co = basis_pos - shp_pos
            
    ##check inputs
    #assert mesh exists
    #assert shapeFiles provided
    #assert mesh has a shapekey
    #check Basis shape exists on mesh
    
    if not mesh in bpy.data.objects:
        print('Requires mesh: %s to exist in scene' %mesh)
        return
        
    if not shapeFiles:
        print('Requires shape file names to import')
        return
        
    mesh_obj = bpy.data.objects[mesh].data #bpy.data.meshes[mesh]
    mesh_name = mesh_obj.name
    
    if mesh_obj.shape_keys is None:
        print("Requires at least a Basis shape on mesh %s" %mesh)
        return

    #exit if no Basis shape key on mesh
    basis_name = 'Basis'
    if not basis_name in mesh_obj.shape_keys.key_blocks.keys():
        print('requires a basis shape named Basis on mesh %s exiting' %mesh)
        return
        
    #need to be in object mode to edit shape keys
    bpy.ops.object.mode_set(mode="OBJECT")
    
    #loop over shape files 
    for shp_file in shapeFiles:
        if not os.path.exists(shp_file):
            print('could not find shape file %s , skipping' %shp_file)
            continue
        
        #read shape from disc
        shp_dict = {}
        with open( shp_file, 'r' ) as f:
            shp_dict = json.load(f)
        
        shapeName = shp_dict['shape']['shapeName']
        vertDelta = shp_dict['shape']['vertDelta']
        
        #skip if saved shape has different topology as mesh
        num_mesh_verts = len( mesh_obj.shape_keys.key_blocks['Basis'].data )
        num_shp_file_verts = len(vertDelta)
        if num_mesh_verts != num_shp_file_verts:
            print('Requires shape stored on file %s to have same topology as mesh %s, skipping' %(shp_file,mesh) )
            continue
            
        if _isShapeExists( mesh, shapeName ):
            #Edit existing shape
            _sculptShape( mesh, shapeName, vertDelta )

        else:
            #Create a new shape
            new_shp = bpy.data.objects[mesh].shape_key_add(shapeName)
            _sculptShape( mesh, shapeName, vertDelta )
            new_shp.interpolation = 'KEY_LINEAR'

def getAllFullFilesWithSuffixInFolder( folder = '', suffix = '_shape.json' ):
    """assumes all files end with given suffix. returns full paths
    """
    if not os.path.isdir(folder):
        print("requires a folder to exist: %s" %folder)
        return []
    
    files_in_folder = os.listdir(folder)
    return [os.path.join(folder,x) for x in files_in_folder if x.endswith(suffix) ]
    

Testing code in separate python file

one way to begin writing add-ons is to first test parts of the code the methods, classes in Blender. here is an example snippet of code to test parts of a future add-on.

this is the simplest way i've been using to test a code snippet. using imp.load_source.

import imp
testTool = imp.load_source("tool","pathToPythonFile.py") #change to path to python file
testTool.putTestMethodHere()

this is another way to test some methods when not using ui

import bpy
import sys
#example if wanted to test script without addon part. change to your path here
sys.path.append('/Users/Nathaniel/Documents/src_blender/python/naBlendShape')

import naShapekeyIO as mod
import imp
imp.reload(mod)

#mod.exportShapeKey( mesh = 'Plane', shapes = ['Key 1'], dirPath = '/Users/Nathaniel/Documents/src_blender/python/naBlendShape/tmp' )
#mod.importShapeKey( mesh = 'Plane', shapeFiles = ['/Users/Nathaniel/Documents/src_blender/python/naBlendShape/tmp/shape_Key_1.json']) 

notice the use of sys.path.append so we can add the path of where the python file is we are working on. also notice imp.reload(mod) which is a way of reloading the module to pickup any latest changes.

Thanks for reading.

Happy Sketching!