While making mesh objects with Python, I felt encouraged by the community to avoid using bpy.ops, as it is less efficient. I am now making armature objects. I am making a kind of snake where the body will simply follow a curve, but the 'neck' will have a 2 stage IK rig with two control bones. This code makes one neck. The def contains my effort so far, based on what I learned from meshes. I'm still working in 2.83 due to other code I have. I have got this far with bpy.ops and could carry on, but I would like to know the bones better.

import bpy
from math import pi, radians

# Rig A
rot_x = radians(10)
rot_z = radians(-0.33)
xlate = -0.01
    location=(-0.009134, 0.25, 0.0),
    rotation=(pi/2, 0.0, rot_z))
ob_a = bpy.data.objects.get('Armature')

# Make a chain of bones, including some pose angles while the
# extrude bone is selected
for i in range(45):
        TRANSFORM_OT_translate={"value":(0, xlate, 0)})
    bpy.ops.transform.rotate(value=rot_z, orient_axis='Z')
    if i > 40:
        bpy.ops.transform.rotate(value=rot_x, orient_axis='X')

arm_a = bpy.data.armatures.get('Armature')
# deselect the last extruded bone's tail, works in edit mode!
bn = arm_a.edit_bones[-1]
bn.select_tail = False

# Make a copy of two bones for use as control bones later,
# one halfway along the chain, one near the end 
control_neck = arm_a.edit_bones.get("Bone.020")
control_head = arm_a.edit_bones.get("Bone.044")

arm_a.edit_bones.active = control_neck
arm_a.edit_bones.active = control_head
control_neck.select = True
control_head.select = True

# must select tail in object mode
control_neck.select_tail = True
control_head.select_tail = True
    TRANSFORM_OT_translate={"value":(0.0, 0.0, 0.02)})


I have tried without bpy.ops and come to a halt.

import bpy
from math import pi, radians
from mathutils import Matrix, Vector

def new_armature(name):
    arm = bpy.data.armatures.new(name)
    obj = bpy.data.objects.new(name, arm)

    return obj

# Rig A
rot_x = radians(10)
rot_z = radians(-0.33)
xlate = -0.01

ob = new_armature('Armature')
ob.location = (-0.009134, 0.25, 0.0)
ob.rotation_euler = (pi/2, 0.0, rot_z)
ob.scale = (0.0125, 0.0125, 0.0125)
bpy.context.view_layer.objects.active = ob
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
edit_bones = ob.data.edit_bones
identity = Matrix()
for i in range(12):
    bone_name = 'b.0' if i < 10 else 'b.'
    b = edit_bones.new(bone_name + str(i))
    b.tail = (0.0, 0.0, 1.0)
    if i:
        b.transform(Matrix.Translation((0,0,1)))# just a stab in the dark
        b.parent = prev
        b.head = prev.tail
        b.tail[2] = prev.tail[2] + 1.0
        b.use_relative_parent = True
    prev = b
# Parenting looks ok in Outliner, but bones don't seem connected

I am obviously missing something, or need pointing to the next step. The bpy.ops bones are connected when grabbed (G) in edit mode, but the other code they are not.

My bpy.ops code works ok; note however that 'duplicate_move' only works if a bone AND its tail are selected, and the tail has to be selected in object mode. Nevertheless I found this was easier than adding new single bones, and orienting/scaling them to the desired bone positions.


As far as I know, with running bpy.ops.armature.duplicate() the new bones are connected by default. Not so when using edit_bones.new(). So insert b.use_connect = True at the end of the if i: block to connect bones with parents.

  • $\begingroup$ Thank you! I had overlooked that property and it is False by default. $\endgroup$
    – koober
    Nov 26 '21 at 16:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.