Dev:Py/Scripts/Cookbook/Code snippets/Armatures

提供: wiki
移動先: 案内検索

Armatures

Armature

This program creates an armature.

Code Snippets Armature.png

#---------------------------------------------------
# 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:

  1. An IK constraint Mid -> Tip.
  2. A Stretch To constraint Mid -> Tip.
  3. A Copy Rotation constraint Base -> Tip.

The mesh is deformed by the armature. Hence an armature modifier and the corresponding vertex groups are created.

Code Snippets RiggedMesh.png

#----------------------------------------------------------
# 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. Code Snippets CopyRoll.png

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