Dev:Py/Scripts/Cookbook/Code snippets/Armatures
Armatures
Armature
This program creates an armature.
#---------------------------------------------------
# File armature.py
#---------------------------------------------------
import bpy, math
from mathutils import Vector, Matrix
def createRig(name, origin, boneTable):
# Create armature and object
bpy.ops.object.add(
type='ARMATURE',
enter_editmode=True,
location=origin)
ob = bpy.context.object
ob.show_x_ray = True
ob.name = name
amt = ob.data
amt.name = name+'Amt'
amt.show_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.use_connect = False
(trans, rot, scale) = parent.matrix.decompose()
else:
bone.head = (0,0,0)
rot = Matrix.Translation((0,0,0)) # identity matrix
bone.tail = rot * Vector(vector) + bone.head
bpy.ops.object.mode_set(mode='OBJECT')
return ob
def poseRig(ob, poseTable):
bpy.context.scene.objects.active = ob
bpy.ops.object.mode_set(mode='POSE')
for (bname, axis, angle) in poseTable:
pbone = ob.pose.bones[bname]
# Set rotation mode to Euler XYZ, easier to understand
# than default quaternions
pbone.rotation_mode = 'XYZ'
# Documentation bug: Euler.rotate(angle,axis):
# axis in ['x','y','z'] and not ['X','Y','Z']
pbone.rotation_euler.rotate_axis(axis, math.radians(angle))
bpy.ops.object.mode_set(mode='OBJECT')
def run(origo):
origin = Vector(origo)
# Table of bones in the form (bone, parent, vector)
# The vector is given in local coordinates
boneTable1 = [
('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)
# Pose second rig
poseTable2 = [
('Base', 'X', 90),
('Mid2', 'Z', 45),
('Tip', 'Y', -45)
]
poseRig(straight, poseTable2)
# Pose first rig
poseTable1 = [
('Tip', 'Y', 45),
('Mid', 'Y', 45),
('Base', 'Y', 45)
]
poseRig(bent, poseTable1)
if __name__ == "__main__":
run((0,5,0))
Rigged mesh
This program adds an armature and a mesh. The armature has three bones (Base, Mid, Tip) and constraints:
- An IK constraint Mid -> Tip.
- A Stretch To constraint Mid -> Tip.
- A Copy Rotation constraint Base -> Tip.
The mesh is deformed by the armature. Hence an armature modifier and the corresponding vertex groups are created.
#----------------------------------------------------------
# File rigged_mesh.py
#----------------------------------------------------------
import bpy, mathutils
def createArmature(origin):
# Create armature and object
amt = bpy.data.armatures.new('MyRigData')
rig = bpy.data.objects.new('MyRig', amt)
rig.location = origin
rig.show_x_ray = True
amt.show_names = True
# Link object to scene
scn = bpy.context.scene
scn.objects.link(rig)
scn.objects.active = rig
scn.update()
# Create bones
#next two lines by PKHG SVN 36504 W32
bpy.ops.object.editmode_toggle()
# bpy.ops.object.mode_set(mode='EDIT')
#original does not work??!! 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)
mid.tail = (0,0,2)
mid.parent = base
mid.use_connect = True
tip = amt.edit_bones.new('Tip')
tip.head = (0,0,2)
tip.tail = (0,0,3)
# Bone constraints. Armature must be in pose mode.
bpy.ops.object.mode_set(mode='POSE')
# IK constraint Mid -> Tip
pMid = rig.pose.bones['Mid']
cns1 = pMid.constraints.new('IK')
cns1.name = 'Ik'
cns1.target = rig
cns1.subtarget = 'Tip'
cns1.chain_count = 1
# StretchTo constraint Mid -> Tip with influence 0.5
cns2 = pMid.constraints.new('STRETCH_TO')
cns2.name = 'Stretchy'
cns2.target = rig
cns2.subtarget = 'Tip'
cns2.influence = 0.5
cns2.keep_axis = 'PLANE_X'
cns2.volume = 'VOLUME_XZX'
# Copy rotation constraints Base -> Tip
pBase = rig.pose.bones['Base']
cns3 = pBase.constraints.new('COPY_ROTATION')
cns3.name = 'Copy_Rotation'
cns3.target = rig
cns3.subtarget = 'Tip'
cns3.owner_space = 'WORLD'
cns3.target_space = 'WORLD'
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
scn = bpy.context.scene
scn.objects.link(ob)
scn.objects.active = ob
scn.update()
# List of vertex coordinates
verts = [
(0.5, 0.5,0), (0.5,-0.5,0), (-0.5,-0.5,0), (-0.5,0.5,0),
(0.5,0.5,1), (0.5,-0.5,1), (-0.5,-0.5,1), (-0.5,0.5,1),
(-0.5,0.5,2), (-0.5,-0.5,2), (0.5,-0.5,2), (0.5,0.5,2),
(0.5,0.5,3), (0.5,-0.5,3), (-0.5,-0.5,3), (-0.5, 0.5,3)
]
# List of faces.
faces = [
(0, 1, 2, 3),
(0, 4, 5, 1),
(1, 5, 6, 2),
(2, 6, 7, 3),
(4, 0, 3, 7),
(4, 7, 8, 11),
(7, 6, 9, 8),
(6, 5, 10, 9),
(5, 4, 11, 10),
(10, 11, 12, 13),
(9, 10, 13, 14),
(8, 9, 14, 15),
(11, 8, 15, 12),
(12, 15, 14, 13)
]
# Create mesh from given verts, edges, faces. Either edges or
# faces should be [], or you ask for problems
me.from_pydata(verts, [], faces)
# Update mesh with new data
me.update(calc_edges=True)
return ob
def skinMesh(ob, rig):
# List of vertex groups, in the form (vertex, weight)
vgroups = {}
vgroups['Base'] = [
(0, 1.0), (1, 1.0), (2, 1.0), (3, 1.0),
(4, 0.5), (5, 0.5), (6, 0.5), (7, 0.5)]
vgroups['Mid'] = [
(4, 0.5), (5, 0.5), (6, 0.5), (7, 0.5),
(8, 1.0), (9, 1.0), (10, 1.0), (11, 1.0)]
vgroups['Tip'] = [(12, 1.0), (13, 1.0), (14, 1.0), (15, 1.0)]
# Create vertex groups, and add verts and weights
# First arg in assignment is a list, can assign several verts at once
for name, vgroup in vgroups.items():
grp = ob.vertex_groups.new(name)
for (v, w) in vgroup:
grp.add([v], w, 'REPLACE')
# Give mesh object an armature modifier, using vertex groups but
# not envelopes
mod = ob.modifiers.new('MyRigModif', 'ARMATURE')
mod.object = rig
mod.use_bone_envelopes = False
mod.use_vertex_groups = True
def run(origin):
rig = createArmature(origin)
ob = createMesh(origin)
skinMesh(ob, rig)
# Move and rotate the tip bone in pose mode
bpy.context.scene.objects.active = rig
bpy.ops.object.mode_set(mode='POSE')
ptip = rig.pose.bones['Tip']
ptip.location = (0.2,-0.5,0)
rotMatrix = mathutils.Matrix.Rotation(0.6, 3, 'X')
ptip.rotation_quaternion = rotMatrix.to_quaternion()
if __name__ == "__main__":
run((0,0,0))
Edit mode versus pose mode
Bone attributes which affect the rest pose of an armature (head, tail, roll, parent, use connect, etc.) are only available in edit mode (using a bone in ob.data.edit bones), whereas attributes which involve posing require the the armature is in pose mode (using a bone in ob.pose.bones). To my knowledge, the only way to switch between edit and pose mode is with the operator calls
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='POSE')
Since operators act on the active object, we must ensure that the right object is active by setting bpy.context.scene.objects.active
.
This script copies the roll angles from a source rig (object name 'SrcRig') to a target rig (object name 'TrgRig'). Both armatures must have the same number of bones with identical names.
#----------------------------------------------------------
# File copy_roll.py
#----------------------------------------------------------
import bpy
def copyRolls(src, trg):
rolls = {}
bpy.context.scene.objects.active = src
bpy.ops.object.mode_set(mode='EDIT')
for eb in src.data.edit_bones:
rolls[eb.name] = eb.roll
bpy.ops.object.mode_set(mode='POSE')
bpy.context.scene.objects.active = trg
bpy.ops.object.mode_set(mode='EDIT')
for eb in trg.data.edit_bones:
oldRoll = eb.roll
eb.roll = rolls[eb.name]
print(eb.name, oldRoll, eb.roll)
bpy.ops.object.mode_set(mode='POSE')
objects = bpy.context.scene.objects
copyRolls(objects['SrcRig'], objects['TrgRig'])