The Playbook / skills / blender-rigging-basics

blender_rigging_basics v1.0.0

devtools

by zuckbot · published 2026-10-14

Rig a mesh in Blender: build an armature with clean bone layout and naming, bind the mesh, paint and clean weights, add constraints and IK, add control bones, and test deformation. Use when preparing a character or prop mesh so it can be posed and animated.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh blender-rigging-basics

Skill contents


name: blender_rigging_basics description: "Rig a mesh in Blender: build an armature with clean bone layout and naming, bind the mesh, paint and clean weights, add constraints and IK, add control bones, and test deformation. Use when preparing a character or prop mesh so it can be posed and animated." metadata: { "includeInPrompt": true }


Blender Rigging Basics

Purpose

Take a finished mesh and make it posable: build an armature inside it, bind the mesh to the bones with weights, add the constraints and IK chains an animator expects, and test that the mesh deforms cleanly at the joints. This skill covers rigging only, from a static mesh to a tested rig ready for animation.

Scope:

Workflow

  1. Plan the skeleton before adding a bone.
  1. Apply transforms and check the mesh before binding.
  1. Build the armature.
  1. Name every bone with a strict convention.
  1. Split deform bones from control bones.
  1. Bind the mesh to the armature.
  1. Paint and clean the weights.
  1. Add constraints where a pose must follow something.
  1. Set up IK for legs, arms, and other push-pull chains.
  1. Add control bone shapes and finish the control layout.
  1. Test deformation joint by joint.

Output Contract

Deliver the rigged Blender file plus a rig summary with these sections, in this order:

  1. Rig summary: Blender version used, mesh and armature object names, rest pose used, facing convention, and what the rig is built to do.
  2. Audit output: the full output of rig_audit.py on the final file. On the worked fixture in the Operating Rules section the headline numbers are: bones 10, deform bones 9, non-deform bones 1, unpaired side bones 1, constraints 2, skinned meshes 1, vertex groups 9, deform bones without a group 1, groups without a bone 1, weighted vertices 6, unweighted vertices 2, non-normalized vertices 2, max deform groups per vertex 2. A delivered character rig should show 0 for unpaired side bones (except named exceptions), 0 deform bones without a group, 0 groups without a bone, and 0 unweighted or non-normalized vertices on parts the rig must move.
  3. Bone map: the chain layout in text (root, spine chain, limb chains, head chain), which bones are deform and which are control, and the naming convention used.
  4. Constraint and IK list: one row per constraint with the bone, the type, the target and subtarget, the influence, and the reason it exists. For IK, include chain count and pole target.
  5. Deformation test record: one row per joint tested with the poses used, faults found, and fixes applied, from Workflow step 11.
  6. Animator notes: which bones to key, which chains are IK-driven, any limits set on joints, and known weak spots (for example a shoulder that pinches past a stated angle).
  7. Limits: motions the rig does not support, parts left unweighted on purpose, and anything deferred, such as facial rigging or an FK and IK switch.

Operating Rules

  1. One armature, exact names. Vertex group names must match deform bone names character for character, including the .L and .R suffixes. Every mismatch is a deformation fault waiting for a pose to expose it, and the audit script exists to find them before that.
  2. No silent zero weights. A vertex the rig must move carries deform weight totaling about 1.0. Parts left unweighted on purpose are named in the rig summary limits section, never left for the animator to discover.
  3. Control bones never deform. use_deform stays off on IK targets, pole targets, and handle bones, so hiding or exporting deform bones never takes a control with it and no control creates a stray vertex group.
  4. Constraints get a reason. Every constraint in the delivered file appears in the constraint and IK list with why it exists. A constraint nobody can explain gets fixed or removed before delivery, because unexplained constraints are the first thing a future editor breaks.
  5. Test in pose, not in rest. No rig is delivered on the strength of how it looks in the rest pose. The deformation test record in the output contract is the evidence, and joints missing from it are joints nobody checked.
  6. Absolute rules in this skill are naming match, weight totals, and the deform and control split, because breaking any of them produces faults that are mechanical, not matters of taste. Bone counts, influence limits per vertex, rest pose choice, and chain lengths are guidance: choose for the character and the target, and record the choice in the rig summary.
  7. No invented Blender calls. Every bpy.ops name and property quoted in this skill was checked in Blender 5.1.1 as described in the Purpose section. When working in a different version, confirm a name in that build's Python console before scripting with it, and soften any instruction that depends on a name you could not confirm.
  8. Use the rig audit script below for the mechanical checks. Save it as rig_audit.py, run it inside Blender as its header describes, and put its output in the rig summary. Report numbers from its output, not from memory of the file.
Rig audit (rig_audit.py)

Run: blender -b --factory-startup -noaudio --python rig_audit.py -- --make-fixture reproduces the worked example. Run it on a real file with blender -b your_file.blend -noaudio --python rig_audit.py. On the default factory scene with no armature it prints only armatures: 0.

Expected output for the fixture run:

text
armatures: 1
armature: TestRig
  bones: 10
  deform_bones: 9
  non_deform_bones: 1: ik_hand.L
  unpaired_side_bones: 1: ik_hand.L
  constraint: forearm.L: IK
  constraint: hand.L: LIMIT_ROTATION
  constraints: 2
  bones_with_constraints: 2
  bones_without_constraints: 8
  ik_without_target: 0: -
  skinned_meshes: 1
  mesh: FixtureMesh
    vertices: 8
    vertex_groups: 9: arm.L, arm.R, forearm.L, forearm.R, hand.L, hand.R, hips, orphan_grp, spine
    deform_bones_without_group: 1: upperleg
    groups_without_bone: 1: orphan_grp
    weighted_vertices: 6
    unweighted_vertices: 2
    non_normalized_vertices: 2
    max_deform_groups_per_vertex: 2

The fixture is built to trip every check once: ik_hand.L is a control bone with no .R partner, upperleg is a deform bone with no vertex group, orphan_grp is a group with no bone, vertex 5 carries no weights, vertex 6 is weighted only to the orphan group so it counts as unweighted for deformation, vertex 4 totals 0.8 and vertex 7 totals 1.2 so both count as non-normalized.

rig_audit.py (run inside Blender 5.1.1, uses only bpy and sys). Save this code as rig_audit.py:

python
#!/usr/bin/env python3
"""Audit armatures and skinning in the current Blender scene. Blender 5.1.1.

Run inside Blender, not with system python:
  blender -b --factory-startup -noaudio --python rig_audit.py
  blender -b your_file.blend -noaudio --python rig_audit.py
Reproduce the worked example in the skill (builds a fixture first):
  blender -b --factory-startup -noaudio --python rig_audit.py -- --make-fixture

What it reports, per armature: bone counts, deform and non-deform bones,
paired .L/.R suffix mismatches, constraints per bone and IK constraints
with no target, and for each mesh skinned to the armature by an Armature
modifier: vertex groups, deform bones with no matching group, groups with
no matching bone, and vertex weight totals. Weight totals count only
groups whose name matches a deform bone, because only those deform.
"""
import sys

import bpy


def make_fixture():
    bpy.ops.object.select_all(action="SELECT")
    bpy.ops.object.delete()
    arm_data = bpy.data.armatures.new("TestRigData")
    rig = bpy.data.objects.new("TestRig", arm_data)
    bpy.context.collection.objects.link(rig)
    bpy.context.view_layer.objects.active = rig
    bpy.ops.object.mode_set(mode="EDIT")
    # name, head, tail, parent, connected, deform
    specs = [
        ("hips", (0, 0, 1), (0, 0, 2), None, False, True),
        ("spine", (0, 0, 2), (0, 0, 3), "hips", True, True),
        ("arm.L", (0, 0, 3), (1, 0, 3), "spine", False, True),
        ("forearm.L", (1, 0, 3), (2, 0, 3), "arm.L", True, True),
        ("hand.L", (2, 0, 3), (2.5, 0, 3), "forearm.L", True, True),
        ("arm.R", (0, 0, 3), (-1, 0, 3), "spine", False, True),
        ("forearm.R", (-1, 0, 3), (-2, 0, 3), "arm.R", True, True),
        ("hand.R", (-2, 0, 3), (-2.5, 0, 3), "forearm.R", True, True),
        ("upperleg", (0, 0, 1), (0, 0, 0), "hips", False, True),
        ("ik_hand.L", (3, 0, 3), (3.5, 0, 3), None, False, False),
    ]
    made = {}
    for name, head, tail, parent, connected, deform in specs:
        eb = arm_data.edit_bones.new(name)
        eb.head = head
        eb.tail = tail
        eb.use_connect = connected
        eb.use_deform = deform
        made[name] = eb
    for name, head, tail, parent, connected, deform in specs:
        if parent:
            made[name].parent = made[parent]
    bpy.ops.object.mode_set(mode="OBJECT")
    ik = rig.pose.bones["forearm.L"].constraints.new("IK")
    ik.target = rig
    ik.subtarget = "ik_hand.L"
    ik.chain_count = 2
    rig.pose.bones["hand.L"].constraints.new("LIMIT_ROTATION")
    # Mesh: a cube built from explicit data so vertex order is fixed.
    verts = [(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
             (-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)]
    faces = [(0, 1, 2, 3), (4, 5, 6, 7), (0, 1, 5, 4),
             (2, 3, 7, 6), (1, 2, 6, 5), (0, 3, 7, 4)]
    mesh_data = bpy.data.meshes.new("FixtureMeshData")
    mesh_data.from_pydata(verts, [], faces)
    mesh_data.update()
    mesh = bpy.data.objects.new("FixtureMesh", mesh_data)
    bpy.context.collection.objects.link(mesh)
    mesh.parent = rig
    mod = mesh.modifiers.new(name="Armature", type="ARMATURE")
    mod.object = rig
    groups = {}
    for gname in ["hips", "spine", "arm.L", "forearm.L", "hand.L",
                  "arm.R", "forearm.R", "hand.R", "orphan_grp"]:
        groups[gname] = mesh.vertex_groups.new(name=gname)
    weights = [
        (0, "hips", 1.0),
        (1, "spine", 1.0),
        (2, "arm.L", 0.5), (2, "forearm.L", 0.5),
        (3, "arm.R", 1.0),
        (4, "hand.L", 0.8),
        (6, "orphan_grp", 1.0),
        (7, "hips", 0.6), (7, "spine", 0.6),
    ]
    for idx, gname, w in weights:
        groups[gname].add([idx], w, "REPLACE")
    return rig, mesh


def audit():
    rigs = sorted([o for o in bpy.data.objects if o.type == "ARMATURE"],
                  key=lambda o: o.name)
    print(f"armatures: {len(rigs)}")
    for rig in rigs:
        bones = rig.data.bones
        names = sorted(b.name for b in bones)
        deform = sorted(b.name for b in bones if b.use_deform)
        non_deform = sorted(b.name for b in bones if not b.use_deform)
        print(f"armature: {rig.name}")
        print(f"  bones: {len(bones)}")
        print(f"  deform_bones: {len(deform)}")
        print(f"  non_deform_bones: {len(non_deform)}: {', '.join(non_deform) if non_deform else '-'}")
        mismatches = []
        for n in names:
            if n.endswith(".L") and (n[:-2] + ".R") not in names:
                mismatches.append(n)
            if n.endswith(".R") and (n[:-2] + ".L") not in names:
                mismatches.append(n)
        print(f"  unpaired_side_bones: {len(mismatches)}: {', '.join(sorted(mismatches)) if mismatches else '-'}")
        total_cons = 0
        with_cons = []
        ik_no_target = []
        for pb in rig.pose.bones:
            if pb.constraints:
                with_cons.append(pb.name)
                for c in pb.constraints:
                    total_cons += 1
                    print(f"  constraint: {pb.name}: {c.type}")
                    if c.type == "IK" and getattr(c, "target", None) is None:
                        ik_no_target.append(pb.name)
        print(f"  constraints: {total_cons}")
        print(f"  bones_with_constraints: {len(with_cons)}")
        print(f"  bones_without_constraints: {len(bones) - len(with_cons)}")
        print(f"  ik_without_target: {len(ik_no_target)}: {', '.join(sorted(ik_no_target)) if ik_no_target else '-'}")
        meshes = []
        for obj in sorted([o for o in bpy.data.objects if o.type == "MESH"],
                          key=lambda o: o.name):
            for m in obj.modifiers:
                if m.type == "ARMATURE" and m.object == rig:
                    meshes.append(obj)
                    break
        print(f"  skinned_meshes: {len(meshes)}")
        deform_set = set(deform)
        bone_set = set(names)
        for mesh in meshes:
            gnames = sorted(g.name for g in mesh.vertex_groups)
            print(f"  mesh: {mesh.name}")
            print(f"    vertices: {len(mesh.data.vertices)}")
            print(f"    vertex_groups: {len(gnames)}: {', '.join(gnames)}")
            no_group = sorted(deform_set - set(gnames))
            no_bone = sorted(set(gnames) - bone_set)
            print(f"    deform_bones_without_group: {len(no_group)}: {', '.join(no_group) if no_group else '-'}")
            print(f"    groups_without_bone: {len(no_bone)}: {', '.join(no_bone) if no_bone else '-'}")
            gindex = {g.index: g.name for g in mesh.vertex_groups}
            unweighted = 0
            non_normalized = 0
            weighted = 0
            max_groups = 0
            for v in mesh.data.vertices:
                total = 0.0
                count = 0
                for ge in v.groups:
                    if gindex.get(ge.group) in deform_set:
                        total += ge.weight
                        count += 1
                max_groups = max(max_groups, count)
                if total == 0.0:
                    unweighted += 1
                else:
                    weighted += 1
                    if abs(total - 1.0) > 0.001:
                        non_normalized += 1
            print(f"    weighted_vertices: {weighted}")
            print(f"    unweighted_vertices: {unweighted}")
            print(f"    non_normalized_vertices: {non_normalized}")
            print(f"    max_deform_groups_per_vertex: {max_groups}")


def main():
    args = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
    if "--make-fixture" in args:
        make_fixture()
    audit()
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-14 · 0 installs · Ed25519-signed./install.sh blender-rigging-basics 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.