Code Snippets. Introduction To Python Scripting For Blender 2.5x
Code Snippets. Introduction To Python Scripting For Blender 2.5x
Thomas Larsson
August 7, 2010
1
1 Introduction
With the arrival of the Blender 2.5x versions of Blender, Python scripting is
taken to a new level. Whereas the Python API up to Blender 2.49 was quite
incomplete and ad hoc, the API in Blender 2.5x promises to allow for python
access to all Blender features, in a complete and systematic way.
However, the learning curve for this amazing tool can be quite steep. The
purpose of these notes is to simplify the learning process, by providing example
scripts that illustrate various aspects of Python scripting in Blender.
1.1 Content
The focus of these notes is on data. I explain how to add and manipulate
objects, materials and animations from python. Most of the programs are not
very useful, but only constructed to illustrate certains concepts. Here is a list
of covered subjects.
• Meshes.
• Vertex groups and shape keys.
• Armatures.
• Rigged mesh.
• Applying an array modifier.
• Three ways to construct objects.
• Materials.
• Textures.
• Multiple materials on a single mesh.
• UV texture.
• Object actions.
• Posebone actions.
• Parenting.
• Drivers.
• Particles.
• Hair.
2
• Editable hair.
• Texts.
• Lattices.
• Curves.
• Paths.
• Camera and lights.
• Layers.
• Groups.
• Worlds.
• Render settings and viewports.
• Batch run.
Some of the things that you will not find in these notes, at least not in the
present revision:
This is not to say that these are important topics. They are excluded due to
lack of competence on my behalf, and because data creation and manipulation
is a good starting point.
3
1.2 Running the scripts
Each script example is a complete program, which can be copied from this pdf
file and pasted into the Text editor in Blender, which is found on the Scripting
screen. Execute the script by pressing the Run button, or press Alt-P on your
keyboard.
The scripts are also available as separate python files, located in the scripts
folder which should have come bundled with this file. Just load the python file
into the Text editor (Alt-O), and run it. There is also a batch script which runs
all the other scripts at once. It is described in detail in the last section.
It is possible to make a batch run of all scripts by loading and executing the
file batch.py. We can easily confirm that all scripts work correctly (or at least
that they don’t generate any errors) by running the batch script. If there are
problems, look in the console window for further information.
Blender 2.5x is still in beta stage and certainly not bug free. If you have prob-
lems with some examples here, the problem may (or may not) lie with Blender
itself. In particular, some of the example scripts cause memory leaks, which are
generally bad and can lead to confusing behaviour or even program crashes. A
memory leak is reported as an ”Error Totblock” message in the console as you
quit Blender. Of course, you can only see this message if you start Blender from
a console window, which probably only can be done under Linux.
For example, in Blender 2.53.1 rev 31032 (this info is available on the splash
screen), if we run the ob action.py script and then immediately quit Blender,
we get the following message in the console:
*bpy stats* - tot exec: 302, tot run: 0.0947sec, average run: 0.000313sec, tot usage 0.92
Saved session recovery to /tmp/quit.blend
Error Totblock: 3
FCurve len: 104 0x3498ee8
strdup len: 12 0x3498f98
beztriple len: 560 0x39764d8
4
With a miscount of three blocks (this is how I interpret this message), Blender’s
memory has been corrupted. This will probably not result in any immediate
problems, but can result in confusing behaviour later in the Blender session.
To let the developers fix as many bugs as possible, we should use recent builds
from www.graphicall.org.
The example scripts only scratch on the surface of what can be done with Python
scripting in Blender 2.5x. When you write your own scripts, you will certainly
want to access operators and variables not mentioned here. There are several
ways to obtain this information.
• Use tooltips. E.g., hovering over the ”This Layer Only” option in the
Lamp context reveals the following text1 :
texts in the pictures have been added manually afterwards, and look slightly different from
how they appear on screen.
5
• There are tooltips also when adding
• Once an operator has been executed, it leaves a trail in the report window
in the scripting screen.
bpy.ops.mesh.primitive_uv_sphere_add(segments=32, rings=16,
size=1, view_align=False, enter_editmode=False,
location=(0, 0, 0), rotation=(0, 0, 0), layer=(True, False, False,
False, False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False))
6
When we add an UV sphere from the menu, it always has 32 segments,
16 rings, etc. But it is straightforward to figure out which call we need to
make a sphere with other data, e.g. 12 segments, 6 rings, radius 3, and
centered at (1, 1, 1):
bpy.ops.mesh.primitive_uv_sphere_add(
segments=12,
rings=6,
size=3,
enter_editmode=True,
location=(1, 1, 1))
Only operator execution is recorded in the report window, and not e.g.
setting a value.
• Learn from other people’s code. The scripts that come bundled with
Blender are a great source of inspiration.
1.5 License
The scripts are provided as is, with no warranty whatsoever. You are free to
use and modify all scripts as you like, for personal or commercial purposes. No
attribution is necessary. However, in the hypothetical case that I get opportu-
nity to publish some expanded version of these notes, I wish to keep the right
to use the examples without modification.
7
2 Meshes and armatures
2.1 Mesh
This program creates two meshes. The first is a solid pyramid, with both
triangular and quad faces. The second is a wire triangle. The names of both
meshes are displayed. The triangle is translated away so it can be seen beside
the pyramid. This requires that it is selected.
#----------------------------------------------------------
# File meshes.py
#----------------------------------------------------------
import bpy
8
return ob
def run(origin):
(x,y,z) = (0.707107, 0.258819, 0.965926)
verts1 = ((x,x,-1), (x,-x,-1), (-x,-x,-1), (-x,x,-1), (0,0,1))
faces1 = ((1,0,4), (4,2,1), (4,3,2), (4,0,3), (0,1,2,3))
ob1 = createMesh(’Solid’, origin, verts1, [], faces1)
verts2 = ((x,x,0), (y,-z,0), (-z,y,0))
edges2 = ((1,0), (1,2), (2,0))
ob2 = createMesh(’Edgy’, origin, verts2, edges2, [])
if __name__ == "__main__":
run((0,0,0))
This program adds an UV sphere with two vertex groups (Left and Right) and
four shapekeys.
#----------------------------------------------------------
# File shapekey.py
#----------------------------------------------------------
9
import bpy
def run(origin):
# Add UV sphere
bpy.ops.mesh.primitive_uv_sphere_add(
segments=6, rings=5, size=1, location=origin)
ob = bpy.context.object
ob.name = ’ShapeKeyObject’
ob.draw_name = True
# Add TopUp keys: top verts move one unit up. TopUp_L and
# TopUp_R only affect left and right halves, respectively
keylist = [(None, ’’), (’Left’, ’_L’), (’Right’, ’_R’)]
for (vgrp, suffix) in keylist:
bpy.ops.object.shape_key_add(False)
topUp = ob.active_shape_key
topUp.name = ’TopUp’ + suffix
if vgrp:
topUp.vertex_group = vgrp
10
for v in [0, 1, 9, 10, 17, 18, 25]:
pt = topUp.data[v].co
pt[2] = pt[2] + 1
return
if __name__ == "__main__":
run((0,0,0))
2.3 Armature
#---------------------------------------------------
# File armature.py
#---------------------------------------------------
import bpy
import math
import mathutils
from mathutils import Vector, Matrix
11
location=origin)
ob = bpy.context.object
ob.x_ray = True
ob.name = name
amt = ob.data
amt.name = name+’Amt’
amt.draw_axes = True
# Create bones
bpy.ops.object.mode_set(mode=’EDIT’)
for (bname, pname, vector) in boneTable:
bone = amt.edit_bones.new(bname)
if pname:
parent = amt.edit_bones[pname]
bone.parent = parent
bone.head = parent.tail
bone.connected = False
mat = parent.matrix.rotation_part()
else:
bone.head = (0,0,0)
mat = Matrix().identity().to_3x3()
bone.tail = mat * Vector(vector) + bone.head
bpy.ops.object.mode_set(mode=’OBJECT’)
return ob
def run(origo):
origin = Vector(origo)
# Table of bones in the form (bone, parent, vector)
# The vector is given in local coordinates
boneTable1 = [
12
(’Base’, None, (1,0,0)),
(’Mid’, ’Base’, (1,0,0)),
(’Tip’, ’Mid’, (0,0,1))
]
bent = createRig(’Bent’, origin, boneTable1)
# The second rig is a straight line, i.e. bones run along local Y axis
boneTable2 = [
(’Base’, None, (1,0,0)),
(’Mid’, ’Base’, (0,0.5,0)),
(’Mid2’, ’Mid’, (0,0.5,0)),
(’Tip’, ’Mid2’, (0,1,0))
]
straight = createRig(’Straight’, origin+Vector((0,2,0)), boneTable2)
if __name__ == "__main__":
run((0,5,0))
This program adds an armature and a mesh. The armature has three bones
(Base, Mid, Tip) and constraints:
13
The mesh is deformed by the armature. Hence an armature modifier and the
corresponding vertex groups are created.
#----------------------------------------------------------
# File rigged_mesh.py
#----------------------------------------------------------
import bpy
def createArmature(origin):
# Create armature and object
amt = bpy.data.armatures.new(’MyRigData’)
rig = bpy.data.objects.new(’MyRig’, amt)
rig.location = origin
rig.x_ray = True
amt.draw_names = True
# Link object to scene
scn = bpy.context.scene
scn.objects.link(rig)
scn.objects.active = rig
scn.update()
# Create bones
bpy.ops.object.mode_set(mode=’EDIT’)
base = amt.edit_bones.new(’Base’)
base.head = (0,0,0)
base.tail = (0,0,1)
mid = amt.edit_bones.new(’Mid’)
mid.head = (0,0,1)
14
mid.tail = (0,0,2)
mid.parent = base
mid.connected = True
tip = amt.edit_bones.new(’Tip’)
tip.head = (0,0,2)
tip.tail = (0,0,3)
bpy.ops.object.mode_set(mode=’OBJECT’)
return rig
def createMesh(origin):
# Create mesh and object
me = bpy.data.meshes.new(’Mesh’)
ob = bpy.data.objects.new(’MeshObject’, me)
ob.location = origin
# Link object to scene
15
scn = bpy.context.scene
scn.objects.link(ob)
scn.objects.active = ob
scn.update()
16
# Update mesh with new data
me.update(calc_edges=True)
return ob
return
def run(origin):
rig = createArmature(origin)
ob = createMesh(origin)
skinMesh(ob, rig)
return
if __name__ == "__main__":
run((0,0,0))
This program creates a chain with ten links. A link is a primitive torus scaled
along the x axis. We give the link an array modifier, where the offset is controlled
17
by an empty. Finally the array modifier is applied, making the chain into a single
mesh.
#----------------------------------------------------------
# File chain.py
# Creates an array modifier and applies it
#----------------------------------------------------------
import bpy
import math
from math import pi
def run(origin):
# Add single chain link to the scene
bpy.ops.mesh.primitive_torus_add(
#major_radius=1,
#minor_radius=0.25,
major_segments=12,
minor_segments=8,
use_abso=True,
abso_major_rad=1,
abso_minor_rad=0.6,
location=(0,0,0),
rotation=(0,0,0))
# Create an empty
bpy.ops.object.add(
type=’EMPTY’,
location=(0,1.2,0.2),
rotation=(pi/2, pi/4, pi/2))
empty = bpy.context.object
18
# Make chain link active again
scn = bpy.context.scene
scn.objects.active = ob
# Add modifier
mod = ob.modifiers.new(’Chain’, ’ARRAY’)
mod.fit_type = ’FIXED_COUNT’
mod.count = 10
mod.relative_offset = 0
mod.add_offset_object = True
mod.offset_object = empty
return
if __name__ == "__main__":
run((0,0,0))
The examples covered so far show that object can be created from Python using
different paradigms.
The data method closely mimics how data are stored internally in Blender.
19
me = bpy.data.meshes.new(meshName)
ob = bpy.data.objects.new(obName, me)
amt = bpy.data.armatures.new(amtname)
ob = bpy.data.objects.new(obname, amt)
2. Link the object to the current scene and make it active. Optionally, we
can make the newly created object active or selected. This code is the
same for all kinds of objects.
scn = bpy.context.scene
scn.objects.link(ob)
scn.objects.active = ob
ob.select = True
3. Fill in the data. In the mesh case, we add the lists of vertices and faces.
bpy.ops.object.mode_set(mode=’EDIT’)
bone = amt.edit_bones.new(’Bone’)
bone.head = (0,0,0)
bone.tail = (0,0,1)
me.update()
bpy.ops.object.mode_set(mode=’OBJECT’)
The operator method adds an object and a data block at the same time. The
data block is currently empty, and needs to be filled with actual data later.
20
1. Add the object with the bpy.ops.object.add operator. This automati-
cally takes care of several things that we had to do manually in the data
method: it creates object data (i.e. the mesh or armature), links the object
to the scene, makes it active and selects the object. On the other hand, we
must now retrieve the object and its data. This is straightforward because
bpy.context.data always points to the active object.
To add a mesh object, we do
bpy.ops.object.add(type=’MESH’)
ob = bpy.context.object
me = ob.data
bpy.ops.object.add(
type=’ARMATURE’,
enter_editmode=True,
location=origin)
ob = bpy.context.object
amt = ob.data
2. As in the data method, the actual data must be filled in and updated
before use. For a mesh we add the verts and faces:
bone = amt.edit_bones.new(’Bone’)
bone.head = (0,0,0)
bone.tail = (0,0,1)
bpy.ops.object.mode_set(mode=’OBJECT’)
Note that we do not need to explicitly enter edit mode, because the ar-
mature entered edit mode already on creation.
21
bpy.ops.mesh.primitive_cone_add(
vertices=4,
radius=1,
depth=1,
cap_end=True)
bpy.ops.object.armature_add()
bpy.ops.transform.translate(value=origin)
2. As in the operator method, we then retrieve the newly create object from
bpy.context.object.
ob = bpy.context.object
me = ob.data
3.4 Comparison
The primitive method is simplest, but it only works when a suitable primitive
is available. Even in the example program, it creates a pyramid mesh which is
slightly different from the other two methods; the base is not a single quad, but
rather consists of four triangles with a common point in the middle of the base.
The other two methods are more or less equivalent.
A primitive does not need to be particularly simple; there are primitives for
creating a monkey mesh or a human rig. But the primitive method is always
limited to prefabricated objects.
22
#----------------------------------------------------------
# File objects.py
#----------------------------------------------------------
import bpy
import mathutils
from mathutils import Vector
23
bpy.ops.mesh.primitive_cone_add(
vertices=4,
radius=1,
depth=1,
cap_end=True,
view_align=False,
enter_editmode=False,
location=origin,
rotation=(0, 0, 0))
ob = bpy.context.object
ob.name = name
ob.draw_name = True
me = ob.data
me.name = name+’Mesh’
return ob
24
amt = ob.data
amt.name = name+’Amt’
def run(origo):
origin = Vector(origo)
(x,y,z) = (0.707107, 0.258819, 0.965926)
verts = ((x,x,-1), (x,-x,-1), (-x,-x,-1), (-x,x,-1), (0,0,1))
faces = ((1,0,4), (4,2,1), (4,3,2), (4,0,3), (0,1,2,3))
if __name__ == "__main__":
run((0,0,0))
4.1 Materials
This program adds a red, opaque material and a blue, semi-transparent one,
add assigns them to a cube an sphere, respectively.
25
#----------------------------------------------------------
# File material.py
#----------------------------------------------------------
import bpy
def run(origin):
# Create two materials
red = makeMaterial(’Red’, (1,0,0), (1,1,1), 1)
blue = makeMaterial(’BlueSemi’, (0,0,1), (0.5,0.5,0), 0.5)
26
bpy.ops.transform.translate(value=(1,0,0))
setMaterial(bpy.context.object, blue)
if __name__ == "__main__":
run((0,0,0))
4.2 Textures
This program creates a material with two textures: an image texture mapped
to color and alpha, and a procedural bump texture.
#----------------------------------------------------------
# File texture.py
#----------------------------------------------------------
import bpy
import os
def run(origin):
# Load image file. Change here if the snippet folder is
# not located in you home directory.
realpath = os.path.expanduser(’~/snippets/scripts/color.png’)
try:
img = bpy.data.images.load(realpath)
except:
raise NameError("Cannot load image %s" % realpath)
27
cTex.type = ’IMAGE’
cTex = cTex.recast_type()
cTex.image = img
cTex.use_alpha = True
# Create material
mat = bpy.data.materials.new(’TexMat’)
28
me.add_material(mat)
return
if __name__ == "__main__":
run((0,0,0))
#----------------------------------------------------------
# File multi_material.py
#----------------------------------------------------------
import bpy
def run(origin):
# Create three materials
red = bpy.data.materials.new(’Red’)
red.diffuse_color = (1,0,0)
blue = bpy.data.materials.new(’Blue’)
blue.diffuse_color = (0,0,1)
yellow = bpy.data.materials.new(’Yellow’)
yellow.diffuse_color = (1,1,0)
29
me.add_material(blue)
me.add_material(yellow)
if __name__ == "__main__":
run((0,0,0))
4.4 UV layers
#----------------------------------------------------------
# File uvs.py
#----------------------------------------------------------
import bpy
import os
def createMesh(origin):
# Create mesh and object
me = bpy.data.meshes.new(’TetraMesh’)
ob = bpy.data.objects.new(’Tetra’, me)
ob.location = origin
# Link object to scene
scn = bpy.context.scene
scn.objects.link(ob)
scn.objects.active = ob
scn.update()
30
# List of verts and faces
verts = [
(1.41936, 1.41936, -1),
(0.589378, -1.67818, -1),
(-1.67818, 0.58938, -1),
(0, 0, 1)
]
faces = [(1,0,3), (3,2,1), (3,0,2), (0,1,2)]
# Create mesh from given verts, edges, faces. Either edges or
# faces should be [], or you ask for problems
me.from_pydata(verts, [], faces)
# Want to make main layer active, but nothing seems to work - TBF
me.active_uv_texture = uvMain
me.uv_texture_clone = uvMain
uvMain.active_render = True
uvFront.active_render = False
return ob
31
datum = uvtex.data[n]
datum.uv1 = tf[0]
datum.uv2 = tf[1]
datum.uv3 = tf[2]
return uvtex
def createMaterial():
# Create image texture from image. Change here if the snippet
# folder is not located in you home directory.
realpath = os.path.expanduser(’~/snippets/scripts/color.png’)
tex = bpy.data.textures.new(’ColorTex’)
tex.type = ’IMAGE’
tex = tex.recast_type()
tex.image = bpy.data.images.load(realpath)
tex.use_alpha = True
def run(origin):
ob = createMesh(origin)
mat = createMaterial()
ob.data.add_material(mat)
return
if __name__ == "__main__":
run((0,0,0))
A bouncing ball.
32
#--------------------------------------------------
# File ob_action.py
#--------------------------------------------------
import bpy
import math
def run(origin):
# Set animation start and stop
scn = bpy.context.scene
scn.frame_start = 11
scn.frame_end = 200
33
action = ob.animation_data.action
if __name__ == "__main__":
run((0,0,10))
bpy.ops.screen.animation_play(reverse=False, sync=False)
This program creates an armature with two bones, which rotate in some com-
plicated curves.
34
#--------------------------------------------------
# File pose_action.py
#--------------------------------------------------
import bpy
import math
def run(origin):
# Set animation start and stop
scn = bpy.context.scene
scn.frame_start = 1
scn.frame_end = 250
35
bpy.ops.object.mode_set(mode=’OBJECT’)
ob.location=origin
for n in range(26):
pbase.keyframe_insert(
’rotation_euler’,
index=0,
frame=n,
group=’Base’)
ptip.keyframe_insert(
’rotation_euler’,
index=2,
frame=n,
group=’Tip’)
omega = 2*math.pi/250
for n in range(26):
t = 10*n
phi = omega*t
kp = baseKptsRotX[n]
kp.co = (t+1,phi+0.7*math.sin(phi))
kp.interpolation = ’LINEAR’
kp = tipKptsRotZ[n]
kp.co = (t+1, -3*phi+2.7*math.cos(2*phi))
kp.interpolation = ’LINEAR’
36
# Calculate paths for posebones
bpy.ops.pose.select_all(action=’SELECT’)
bpy.ops.pose.paths_calculate()
return
if __name__ == "__main__":
run((10,0,0))
bpy.ops.screen.animation_play(reverse=False, sync=False)
5.3 Parenting
#----------------------------------------------------------
# File epicycle.py
#----------------------------------------------------------
import bpy
import math
from math import pi
def createEpiCycle(origin):
periods = [1, 5, 8, 17]
radii = [1.0, 0.3, 0.5, 0.1]
37
axes = [0, 2, 1, 0]
phases = [0, pi/4, pi/2, 0]
# Add empties
scn = bpy.context.scene
empties = []
nEmpties = len(periods)
for n in range(nEmpties):
empty = bpy.data.objects.new(’Empty_%d’ % n, None)
scn.objects.link(empty)
empties.append(empty)
kp0 = fcu.keyframe_points[0]
kp0.co = (0, phases[n])
kp0.interpolation = ’LINEAR’
kp1 = fcu.keyframe_points[1]
kp1.co = (250.0/periods[n], 2*pi + phases[n])
kp1.interpolation = ’LINEAR’
fcu.extrapolation = ’LINEAR’
last = empties[nEmpties-1]
bpy.ops.mesh.primitive_ico_sphere_add(
size = 0.2,
location=last.location)
ob = bpy.context.object
38
ob.parent = last
empties[0].location = origin
return
def run(origin):
createEpiCycle(origin)
bpy.ops.object.paths_calculate()
return
if __name__ == "__main__":
run((0,0,0))
bpy.ops.screen.animation_play(reverse=False, sync=False)
5.4 Drivers
This program adds an armature with one driver bones and two driven bones.
The tip’s Z rotation is driven by the driver’s x location. The base’s Z rotation
is driven both by the driver’s Y location and its Z rotation.
#----------------------------------------------------------
# File driver.py
#----------------------------------------------------------
import bpy
def run(origin):
# Create armature and object
amt = bpy.data.armatures.new(’MyRigData’)
rig = bpy.data.objects.new(’MyRig’, amt)
rig.location = origin
amt.draw_names = True
39
# Link object to scene
scn = bpy.context.scene
scn.objects.link(rig)
scn.objects.active = rig
scn.update()
# Create bones
bpy.ops.object.mode_set(mode=’EDIT’)
base = amt.edit_bones.new(’Base’)
base.head = (0,0,0)
base.tail = (0,0,1)
tip = amt.edit_bones.new(’Tip’)
tip.head = (0,0,1)
tip.tail = (0,0,2)
tip.parent = base
tip.connected = True
driver = amt.edit_bones.new(’Driver’)
driver.head = (2,0,0)
driver.tail = (2,0,1)
bpy.ops.object.mode_set(mode=’POSE’)
var = drv.variables.new()
var.name = ’x’
var.type = ’TRANSFORMS’
targ = var.targets[0]
targ.id = rig
targ.transform_type = ’LOC_X’
targ.bone_target = ’Driver’
targ.use_local_space_transforms = True
fmod = fcurve.modifiers[0]
fmod.mode = ’POLYNOMIAL’
fmod.poly_order = 1
fmod.coefficients = (1.0, -1.0)
40
# Add driver for Base’s Z rotation
# Base.rotz = z*z - 3*y, where y = Driver.locy and z = Driver.rotz
fcurve = rig.pose.bones["Base"].driver_add(’rotation_quaternion’, 3)
drv = fcurve.driver
drv.type = ’SCRIPTED’
drv.expression = ’z*z - 3*y’
drv.show_debug_info = True
var1 = drv.variables.new()
var1.name = ’y’
var1.type = ’TRANSFORMS’
targ1 = var1.targets[0]
targ1.id = rig
targ1.transform_type = ’LOC_Y’
targ1.bone_target = ’Driver’
targ1.use_local_space_transforms = True
var2 = drv.variables.new()
var2.name = ’z’
var2.type = ’TRANSFORMS’
targ2 = var2.targets[0]
targ2.id = rig
targ2.transform_type = ’ROT_Z’
targ2.bone_target = ’Driver’
targ2.use_local_space_transforms = True
return
if __name__ == "__main__":
run((0,0,0))
6.1 Particles
41
#---------------------------------------------------
# File particle.py
#---------------------------------------------------
import bpy
import mathutils
from mathutils import Vector, Matrix
import math
from math import pi
def run(origo):
# Add emitter mesh
origin = Vector(origo)
bpy.ops.mesh.primitive_plane_add(location=origin)
emitter = bpy.context.object
# Emission
set1 = psys1.settings
set1.name = ’DropSettings’
set1.frame_start = 40
set1.frame_end = 200
set1.lifetime = 50
42
set1.random_lifetime = 0.4
set1.emit_from = ’FACE’
set1.emitter = True
set1.object_aligned_factor = (0,0,1)
# Physics
set1.physics_type = ’NEWTON’
set1.mass = 2.5
set1.particle_size = 0.3
set1.sizemass = True
# Effector weights
ew = set1.effector_weights
ew.gravity = 1.0
ew.wind = 1.0
# Children
set1.child_nbr = 10
set1.rendered_child_nbr = 10
set1.child_type = ’PARTICLES’
# Field settings
fld = wind.field
fld.strength = 2.3
fld.noise = 3.2
fld.flow = 0.3
43
# Add monkey to be used as dupli object
# Hide the monkey on layer 2
layers = 20*[False]
layers[1] = True
bpy.ops.mesh.primitive_monkey_add(
location=origin+Vector((0,5,0)),
rotation = (pi/2, 0, 0),
layer = layers)
monkey = bpy.context.object
# Emission
set2.amount = 4
set2.frame_start = 1
set2.frame_end = 50
set2.lifetime = 250
set2.emit_from = ’FACE’
set2.emitter = True
# Velocity
set2.random_factor = 0.5
# Physics
set2.physics_type = ’NEWTON’
set2.brownian_factor = 0.5
# Effector weights
ew = set2.effector_weights
ew.gravity = 0
ew.wind = 0.2
# Children
set2.child_nbr = 1
set2.rendered_child_nbr = 1
set2.child_size = 3
set2.child_type = ’PARTICLES’
44
set2.display = 1
set2.draw_as = ’RENDER’
set2.dupli_object = monkey
set2.material = 1
set2.particle_size = 0.1
set2.ren_as = ’OBJECT’
set2.render_step = 3
return
if __name__ == "__main__":
run((0,0,0))
bpy.ops.screen.animation_play(reverse=False, sync=False)
6.2 Hair
This program adds a sphere with hair. A strand shader is constructed for the
hair.
#---------------------------------------------------
# File hair.py
#---------------------------------------------------
import bpy
def createHead(origin):
# Add emitter mesh
bpy.ops.mesh.primitive_ico_sphere_add(location=origin)
ob = bpy.context.object
bpy.ops.object.shade_smooth()
45
scalp = ob.add_vertex_group(’Scalp’)
for v in ob.data.verts:
z = v.co[2]
y = v.co[1]
if z > 0.3 or y > 0.3:
w = 2*(z-0.3)
if w > 1:
w = 1
ob.add_vertex_to_group(v.index, scalp, w, ’REPLACE’)
return ob
def createMaterials(ob):
# Some material for the skin
skinmat = bpy.data.materials.new(’Skin’)
skinmat.diffuse_color = (0.6,0.3,0)
# Transparency
hairmat.transparency = True
hairmat.transparency_method = ’Z_TRANSPARENCY’
hairmat.alpha = 0
# Texture
tex = bpy.data.textures.new(’Blend’)
tex.type = ’BLEND’
tex = tex.recast_type()
# Blend specific
tex.progression = ’LINEAR’
tex.flip_axis = ’HORIZONTAL’
46
tex.color_ramp.interpolation = ’B_SPLINE’
# Points in color ramp: (pos, rgba)
# Have not figured out how to add points to ramp
rampTable = [
(0.0, (0.23,0.07,0.03,0.75)),
#(0.2, (0.4,0.4,0,0.5)),
#(0.7, (0.6,0.6,0,0.5)),
(1.0, (0.4,0.3,0.05,0))
]
elts = tex.color_ramp.elements
n = 0
for (pos, rgba) in rampTable:
elts[n].position = pos
elts[n].color = rgba
n += 1
def createHair(ob):
# Create hair particle system
bpy.ops.object.particle_system_add()
psys = ob.active_particle_system
psys.name = ’Hair’
# psys.global_hair = True
psys.vertex_group_density = ’Scalp’
set = psys.settings
set.type = ’HAIR’
set.name = ’HairSettings’
# Emission
set.amount = 40
set.hair_step = 7
set.emit_from = ’FACE’
set.emitter = True
47
# Render
set.material = 2
set.emitter = True
set.ren_as = ’PATH’
set.render_strand = True
set.hair_bspline = True
set.material_color = True
# Children
set.child_type = ’FACES’
set.child_nbr = 10
set.rendered_child_nbr = 500
set.child_length = 1.0
set.child_length_thres = 0.0
set.child_roundness = 0.4
set.clump_factor = 0.862
set.clumppow = 0.999
set.rough_endpoint = 0.0
set.rough_end_shape = 1.0
set.rough1 = 0.0
set.rough1_size = 1.0
set.rough2 = 0.0
set.rough2_size = 1.0
set.rough2_thres = 0.0
set.kink = ’CURL’
set.kink_amplitude = 0.2
set.kink_shape = 0.0
set.kink_frequency = 2.0
return
def run(origin):
ob = createHead(origin)
createMaterials(ob)
createHair(ob)
return
if __name__ == "__main__":
run((0,0,0))
48
6.3 Editable hair
This program adds a sphere with editable hair from given hair guides. There
are quite a few glitches, e.g.
• Hair does not show up in the viewport, but it does appear in renders.
• If we toggle to edit mode, all strands become straight, i.e. the editing is
lost.
• It is unclear to me if the hairs really follow the guides.
#---------------------------------------------------
# File edit_hair.py
# Has flaws, but may be of interest anyway.
#---------------------------------------------------
import bpy
def createHead():
# Add emitter mesh
bpy.ops.mesh.primitive_ico_sphere_add()
ob = bpy.context.object
ob.name = ’EditedHair’
bpy.ops.object.shade_smooth()
return ob
49
nGuides = len(guides)
nSteps = len(guides[0])
# Particle settings
pset = psys.settings
pset.type = ’HAIR’
pset.name = ’HairSettings’
pset.amount = nGuides
pset.hair_step = nSteps-1
pset.emit_from = ’FACE’
pset.emitter = True
# Children
pset.child_type = ’FACES’
pset.child_nbr = 6
pset.rendered_child_nbr = 300
pset.child_length = 1.0
pset.child_length_thres = 0.0
50
# Connect hair to mesh
# Segmentation violation during render if this line is absent.
bpy.ops.particle.connect_hair(all=True)
51
def run(origin):
ob = createHead()
createHair(ob, hairGuides)
ob.location = origin
return
if __name__ == "__main__":
run((0,0,0))
7.1 Text
This program adds a piece of text to the viewport and sets some attributes.
Note that the data type is TextCurve; the type Text is for text in the text
editor.
#----------------------------------------------------------
# File text.py
#----------------------------------------------------------
import bpy
import math
from math import pi
def run(origin):
# Create and name TextCurve object
bpy.ops.object.text_add(
location=origin,
rotation=(pi/2,0,pi))
ob = bpy.context.object
ob.name = ’HelloWorldText’
tcu = ob.data
52
tcu.name = ’HelloWorldData’
# TextCurve attributes
tcu.body = "Hello, world"
tcu.font = bpy.data.fonts[0]
tcu.offset_x = -9
tcu.offset_y = -0.25
tcu.shear = 0.5
tcu.spacing = 2
tcu.text_size = 3
tcu.word_spacing = 4
if __name__ == "__main__":
run((0,0,0))
7.2 Layers
53
#----------------------------------------------------------
# File layers.py
#----------------------------------------------------------
import bpy
def createOnLayer(mat):
for n in range(3, 8):
# Create a n-gon on layer n+11
layers = 20*[False]
layers[n+11] = True
bpy.ops.mesh.primitive_circle_add(
vertices=n,
radius=0.5,
fill=True,
view_align=True,
layer=layers,
location=(n-3,0,0)
)
bpy.context.object.data.add_material(mat)
return
def changeLayerData(mat):
for n in range(3, 8):
# Create a n-gon on layer 1
bpy.ops.mesh.primitive_circle_add(
vertices=n,
radius=0.5,
fill=True,
view_align=True,
location=(n-3,1,0)
)
bpy.context.object.data.add_material(mat)
54
def moveLayerOperator(mat):
for n in range(3, 8):
# Create a n-gon on layer 1
bpy.ops.mesh.primitive_circle_add(
vertices=n,
radius=0.5,
fill=True,
view_align=True,
location=(n-3,2,0)
)
bpy.context.object.data.add_material(mat)
return
def run():
# Create some materials
red = bpy.data.materials.new(’Red’)
red.diffuse_color = (1,0,0)
green = bpy.data.materials.new(’Green’)
green.diffuse_color = (0,1,0)
blue = bpy.data.materials.new(’Blue’)
blue.diffuse_color = (0,0,1)
# Select layers 14 - 20
scn = bpy.context.scene
bpy.ops.object.select_all(action=’SELECT’)
for n in range(13,19):
scn.layers[n] = True
55
# Deselect layer 16
scn.layers[15] = False
return
if __name__ == "__main__":
run()
7.3 Groups
This program shows how to create groups, add objects to groups, and empties
that duplicates the groups. We add four groups, four mesh objects assigned to
two groups each, and four texts assigned to a single group. Then we add four
empties, which dupli-group the four groups. Finally the empties are moved so
each row contains the elements in that group.
#----------------------------------------------------------
# File groups.py
# Create groups
#----------------------------------------------------------
import bpy
import mathutils
from mathutils import Vector
# Layers
Display = 5
Build = 6
56
def setObject(name, mat):
ob = bpy.context.object
ob.name = name
ob.data.add_material(mat)
return ob
def run():
# Create two materials
red = bpy.data.materials.new(’RedMat’)
red.diffuse_color = (1,0,0)
green = bpy.data.materials.new(’GreenMat’)
green.diffuse_color = (0,1,0)
# Locations
origin = Vector((0,0,0))
dx = Vector((2,0,0))
dy = Vector((0,2,0))
dz = Vector((0,0,2))
57
# Create objects
bpy.ops.mesh.primitive_cube_add(location=dz, layer=layers)
redCube = setObject(’RedCube’, red)
bpy.ops.mesh.primitive_cube_add(location=dx+dz, layer=layers)
greenCube = setObject(’GreenCube’, green)
bpy.ops.mesh.primitive_uv_sphere_add(location=2*dx+dz, layer=layers)
redSphere = setObject(’RedSphere’, red)
bpy.ops.mesh.primitive_uv_sphere_add(location=3*dx+dz, layer=layers)
greenSphere = setObject(’GreenSphere’, green)
# Create texts
redText = addText(’Red’, -dx)
greenText = addText(’Green’, -dx)
cubeText = addText(’Cube’, -dx)
sphereText = addText(’Sphere’, -dx)
# Create groups
redGrp = bpy.data.groups.new(’RedGroup’)
greenGrp = bpy.data.groups.new(’GreenGroup’)
cubeGrp = bpy.data.groups.new(’CubeGroup’)
sphereGrp = bpy.data.groups.new(’SphereGroup’)
# List of empties
empties = [
(’RedEmpty’, origin, redGrp),
(’GreenEmpty’, dy, greenGrp),
(’CubeEmpty’, 2*dy, cubeGrp),
(’SphereEmpty’, 3*dy, sphereGrp)
]
58
for (name, loc, group) in empties:
empty = bpy.data.objects.new(name, None)
empty.location = loc
empty.name = name
empty.dupli_type = ’GROUP’
empty.dupli_group = group
scn.objects.link(empty)
moveToLayer(empty, Display)
# For some reason have to update all objects after layer change
for ob in bpy.data.objects:
ob.update(scn)
return
if __name__ == "__main__":
run()
7.4 Lattice
This program adds an icosphere deformed by a lattice. The lattice modifier only
acts on the vertex group on the upper half of the sphere.
59
#----------------------------------------------------------
# File lattice.py
#----------------------------------------------------------
import bpy
import os
def createIcoSphere(origin):
# Create an icosphere
bpy.ops.mesh.primitive_ico_sphere_add(location=origin)
ob = bpy.context.object
me = ob.data
def createLattice(origin):
# Create lattice and object
lat = bpy.data.lattices.new(’MyLattice’)
ob = bpy.data.objects.new(’LatticeObject’, lat)
ob.location = origin
ob.x_ray = True
# Link object to scene
scn = bpy.context.scene
scn.objects.link(ob)
scn.objects.active = ob
scn.update()
60
# Set lattice points
s = 1.0
points = [
(-s,-s,-s), (s,-s,-s), (-s,s,-s), (s,s,-s),
(-s,-s,s), (s,-s,s), (-s,s,s), (s,s,s)
]
for n,pt in enumerate(lat.points):
for k in range(3):
pt.co[k] = points[n][k]
return ob
def run(origin):
sphere = createIcoSphere(origin)
lat = createLattice(origin)
# Create lattice modifier
mod = sphere.modifiers.new(’Lat’, ’LATTICE’)
mod.object = lat
mod.vertex_group = ’Upper’
# Lattice in edit mode for easy deform
bpy.context.scene.update()
bpy.ops.object.mode_set(mode=’EDIT’)
return
if __name__ == "__main__":
run((0,0,0))
7.5 Curve
This program adds a Bezier curve. It also adds a Nurbs circle which is used as
a bevel object.
#----------------------------------------------------------
# File curve.py
#----------------------------------------------------------
61
import bpy
def createBevelObject():
# Create Bevel curve and object
cu = bpy.data.curves.new(’BevelCurve’, ’CURVE’)
ob = bpy.data.objects.new(’BevelObject’, cu)
bpy.context.scene.objects.link(ob)
return ob
def createCurveObject(bevob):
# Create curve and object
cu = bpy.data.curves.new(’MyCurve’, ’CURVE’)
ob = bpy.data.objects.new(’MyCurveObject’, cu)
bpy.context.scene.objects.link(ob)
62
# Set some attributes
cu.bevel_object = bevob
cu.dimensions = ’3D’
cu.back = True
cu.front = True
ob.draw_name = True
# Bezier coordinates
beziers = [
((-1.44,0.20,0.00), (-1.86,-0.51,-0.36), (-1.10,0.75,0.28)),
((0.42,0.13,-0.03), (-0.21,-0.04,-0.27), (1.05,0.29,0.21)),
((1.20,0.75,0.78), (0.52,1.36,1.19), (2.76,-0.63,-0.14))
]
def run(origin):
bevob = createBevelObject()
bevob.location = origin
curveob = createCurveObject(bevob)
curveob.location = origin
bevob.select = False
curveob.select = True
bpy.ops.transform.translate(value=(2,0,0))
return
if __name__ == "__main__":
run((0,0,0))
7.6 Path
This program adds a path and a monkey with a follow path constraint.
63
#----------------------------------------------------------
# File path.py
#----------------------------------------------------------
import bpy
def run(origin):
# Create path data and object
path = bpy.data.curves.new(’MyPath’, ’CURVE’)
pathOb = bpy.data.objects.new(’Path’, path)
pathOb.location = origin
bpy.context.scene.objects.link(pathOb)
# Add a monkey
64
bpy.ops.mesh.primitive_monkey_add()
monkey = bpy.context.object
if __name__ == "__main__":
run((0,0,0))
bpy.ops.screen.animation_play(reverse=False, sync=False)
This program adds a sun light to the scene, and a spot light for every render
object in the scene. Each spot has a TrackTo constraint making to point to its
object, whereas the sun tracks the center of all objects in the scene.
Then a new camera is added and made into the active camera. Finally the
program switches to the Default screen, and changes the viewport to the new
camera.
#----------------------------------------------------------
# File camera.py
# Adds one camera and several lights
#----------------------------------------------------------
import bpy
import mathutils
from mathutils import Vector
import math
from math import pi
def findMidPoint():
sum = Vector((0,0,0))
n = 0
for ob in bpy.data.objects:
if ob.type not in [’CAMERA’, ’LAMP’, ’EMPTY’]:
sum += ob.location
n += 1
65
return sum/n
for ob in bpy.context.scene.objects:
if ob.type == ’MESH’:
spot = createLamp(ob.name+’Spot’, ’SPOT’, ob.location+Vector((0,2,1)))
bpy.ops.transform.resize(value=(0.5,0.5,0.5))
lamp = spot.data
# Lamp
lamp.type = ’SPOT’
lamp.color = (0.5,0.5,0)
lamp.energy = 0.9
lamp.falloff_type = ’INVERSE_LINEAR’
lamp.distance = 7.5
# Spot shape
lamp.spot_size = 30*deg2rad
66
lamp.spot_blend = 0.3
# Shadows
lamp.shadow_method = ’BUFFER_SHADOW’
lamp.shadow_layer = True
lamp.shadow_buffer_type = ’REGULAR’
lamp.shadow_color = (0,0,1)
# Lens
cam.type = ’PERSP’
cam.lens = 90
cam.lens_unit = ’MILLIMETERS’
cam.shift_x = -0.05
cam.shift_y = 0.1
cam.clip_start = 10.0
cam.clip_end = 250.0
# Display
cam.show_title_safe = True
cam.show_name = True
67
def run(origin):
# Add an empty at the middle of all render objects
midpoint = findMidPoint()
bpy.ops.object.add(
type=’EMPTY’,
location=midpoint),
target = bpy.context.object
target.name = ’Target’
createCamera(origin+Vector((50,90,50)), target)
createLamps(origin, target)
return
if __name__ == "__main__":
run(Vector((0,0,0)))
8.1 World
This program modifies the world settings. The picture is a rendering of the
default cube with the default camera and lighting.
#--------------------------------------------------
# File world.py
#--------------------------------------------------
import bpy
68
def run():
world = bpy.context.scene.world
# World settings
world.blend_sky = True
world.ambient_color = (0.05, 0, 0)
world.horizon_color = (0, 0, 0.2)
world.zenith_color = (0.04, 0, 0.04)
# Stars
wstars = world.stars
wstars.use_stars = True
wstars.average_separation = 17.8
wstars.color_randomization = 1.0
wstars.min_distance = 0.7
wstars.size = 10
# World lighting
wli = world.lighting
wli.ao_blend_mode = ’MULTIPLY’
wli.ao_factor = 0.8
wli.gather_method = ’APPROXIMATE’
# Clouds texture
tex = bpy.data.textures.new(’Clouds’)
# Recasting not really necessary, since CLOUDS is default
tex.type = ’CLOUDS’
tex.recast_type()
# Clouds settings
tex.stype = ’GREYSCALE’
tex.noise_type = ’SOFT_NOISE’
tex.noise_basis = ’ORIGINAL_PERLIN’
tex.noise_size = 0.06
tex.noise_depth = 1
69
wtex.map_zenith_down = False
wtex.map_zenith_up = True
wtex.color = (1,1,1)
wtex.texture_coordinates = ’VIEW’
wtex.zenith_up_factor = 1.0
return
if __name__ == "__main__":
run()
This program modifies the render settings, switches to the Default screen, and
changes to Camera viewport. Finally the animation is started, unfortunately in
the old view.
#----------------------------------------------------------
# File view.py
# Changes the view and render settings
#----------------------------------------------------------
import bpy
def setRenderSettings():
render = bpy.context.scene.render
render.resolution_x = 720
render.resolution_y = 576
render.resolution_percentage = 100
render.fps = 24
render.use_raytracing = False
render.color_management = True
render.use_sss = False
return
def setDefaultCameraView():
for scrn in bpy.data.screens:
if scrn.name == ’Default’:
bpy.context.window.screen = scrn
for area in scrn.areas:
if area.type == ’VIEW_3D’:
for space in area.spaces:
if space.type == ’VIEW_3D’:
space.viewport_shading = ’SOLID’
reg = space.region_3d
reg.view_perspective = ’CAMERA’
70
break
return
def run():
setRenderSettings()
setDefaultCameraView()
# Start animation, unfortunately in the old view
bpy.ops.screen.animation_play(reverse=False, sync=False)
return
if __name__ == "__main__":
run()
9 Batch run
The program runs all the previous scripts. The main purpose is to verify that
all scripts run correctly, or at least that they can be executed without causing
errors.
Not all scripts run in earlier versions of Blender. To make ensure that you are
not stuck with an outdated Blender, we first check the current Blender version,
which is available as bpy.app.version.
#----------------------------------------------------------
# File batch.py
#----------------------------------------------------------
import bpy
import sys
import os
import mathutils
from mathutils import Vector
71
for ob in bpy.data.objects:
scn.objects.active = ob
bpy.ops.object.mode_set(mode=’OBJECT’)
scn.objects.unlink(ob)
del ob
# Path to the code. You must change this unless you place the
# snippets folder in your home directory
path = os.path.expanduser(’~/snippets/scripts’)
sys.path.append(path)
print(sys.path)
origin = Vector((0,0,0))
72
import uvs
uvs.run(origin)
origin[0] += 5
import chain
chain.run(origin)
73
path.run(origin)
import camera
camera.run(Vector((0,0,0)))
# View
import world
world.run()
import view
view.run()
At frame 118, your screen should look as the picture below. The rendered
version appears on the front page.
74