---
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:
- blender-material-shaders is materials and shader nodes, uv-unwrapping-basics is UV work, threejs-scene-basics is code scenes, gltf-asset-optimization is delivery prep. This skill owns preparing a mesh so it can be posed and animated: armatures, bone layout and naming, weight painting, constraints, IK, control bones, and deformation testing.
- This skill does not cover modeling the mesh, animating the finished rig, or exporting it. Fix bad topology before rigging: a joint with no edge loops near it will not bend well no matter how the weights are painted.
- Tool claims in this skill were checked against Blender 5.1.1 on 2026-10-14 by running Blender headless (`blender -b --factory-startup -noaudio --python script.py`). Every `bpy.ops` name quoted below exists in that build, and the parent and weight claims marked as verified were run, not recalled. Blender changes operator and property names between major versions, so re-check a quoted name in the Python console of a newer build before relying on it.

## Workflow

1. Plan the skeleton before adding a bone.
   - Write down: what the rig must do (walk, hold props, facial work, mechanical motion), which parts move, and which parts must never deform (armor plates, a robot chest, a hat). The second list decides where control and non-deform bones go in step 5.
   - Place joints where the real or imagined body pivots, not where the mesh happens to have vertices. Shoulders pivot inside the shoulder ball, elbows and knees pivot at the hinge line, and a spine needs at least two or three bones between hips and chest if it has to bend and twist. A joint placed a centimeter off shows up as the limb bending around the wrong point in every pose.
   - Decide the rest pose and record it: T-pose or A-pose for a humanoid, straight legs, fingers spread if fingers get bones. Automatic weights and envelope weights both read the rest pose, so changing it after binding means rebinding.
   - Decide the facing direction and keep it consistent for the whole project. A common convention is the character facing the Front view, which in Blender looks along the Y axis with the character facing negative Y. Pick one convention, write it in the rig notes, and do not mix files that use different ones, because left and right naming in step 3 depends on it.
   - Count the bones the plan needs and keep the count as low as the motion allows. Every deform bone is a vertex group to paint, check, and clean. Extra bones that no pose uses are maintenance cost with no payoff.

2. Apply transforms and check the mesh before binding.
   - Apply object scale and rotation on the mesh before parenting it to an armature. Non-uniform object scale on a skinned mesh distorts deformation in ways that look like bad weights and waste hours. In the UI this is Object, Apply; in Python it is the object's transform state, so confirm scale reads 1, 1, 1 in the item panel before binding.
   - Check the mesh origin. The origin is where the armature modifier math anchors, and a mesh whose origin sits far from the character produces confusing results when the rig moves. Keep the mesh origin at or near the character, usually at the ground between the feet or at the hips.
   - Confirm the mesh has edge loops near every joint that must bend: at minimum a loop on each side of an elbow, knee, shoulder, and finger joint, plus one at the joint. Weights can only bend the mesh where geometry exists to bend.

3. Build the armature.
   - Use one armature object per character in most cases. Add it with `bpy.ops.object.armature_add`, which exists in Blender 5.1.1, or in Python by creating an armature data block with `bpy.data.armatures.new` and linking an object for it, as the audit script fixture in the Operating Rules section does.
   - Add and shape bones in Edit Mode. Each bone has a head, a tail, and a roll. Set them through the EditBone properties `head`, `tail`, and `roll`, all present in 5.1.1, or with the extrude and move tools in the UI. A bone with near-zero length is a defect: it deforms nothing and confuses mirroring.
   - Parent bones into chains with the EditBone `parent` property. Use `use_connect` only where the child head truly sits on the parent tail, such as elbow to wrist. Leave shoulder and hip children disconnected (`use_connect` off) so the limb root can sit at its own pivot while still inheriting the parent's motion.
   - Start with a root bone at the ground or the hips that parents everything else. Moving the root moves the whole character, and animators expect exactly one bone with that job.
   - Watch bone roll. Roll twists the bone's local axes, and a chain whose rolls disagree will bend in mixed directions and fight IK. The operator `bpy.ops.armature.calculate_roll` exists in 5.1.1 and can align rolls along a chain; whatever method you use, check roll by rotating each bone on its local axes in Pose Mode before binding.
   - Keep the armature display readable while you work. The armature `display_type` property in 5.1.1 accepts OCTAHEDRAL, STICK, BBONE, ENVELOPE, and WIRE. STICK or OCTAHEDRAL are practical defaults for a body rig; ENVELOPE is useful only while checking envelope binding in step 6.

4. Name every bone with a strict convention.
   - Side bones end in `.L` or `.R`, with the dot, for example `forearm.L` and `forearm.R`. Center bones (`hips`, `spine`, `chest`, `neck`, `head`) carry no side suffix. This suffix form matters because Blender's side tools key off it: `bpy.ops.armature.autoside_names`, `bpy.ops.armature.flip_names`, and `bpy.ops.armature.symmetrize` all exist in 5.1.1, and symmetrize builds the opposite side from a `.L` or `.R` suffix.
   - Use one separator style and one case style for the whole rig, and avoid spaces in bone names. A vertex group must match its bone name exactly to deform, so a name that is hard to type exactly is a future mismatch. The audit script in the Operating Rules section flags groups with no matching bone and bones with no matching group.
   - Build one side, name it fully, then mirror it. Symmetrize copies names and flips the suffix, which is far more reliable than naming both sides by hand. After mirroring, run the audit and confirm the unpaired side count is only the bones you meant to leave unpaired, such as a single weapon or prop bone.
   - Rename before binding when you can. Renaming a deform bone after binding renames its matching vertex group in Blender, but external tools and saved scripts may still hold the old name. If you rename late, re-run the audit and check the group match both ways.

5. Split deform bones from control bones.
   - A deform bone moves the mesh. A control bone is a handle: an IK target, a pole target, or a parent an animator grabs. Set `use_deform` off on every control bone. The property exists on both Bone and EditBone in 5.1.1, and the audit script counts non-deform bones separately so the split is visible.
   - Give control bones names that say their job, for example `ik_hand.L`, `pole_elbow.L`, or a `ctrl_` prefix used consistently. An animator opening the file cold should be able to tell which bones are safe to key by name alone.
   - Organize bones with bone collections, which the armature data block exposes as `collections` in 5.1.1. A workable split is one collection for deform bones, one for body controls, and one for IK targets and poles. Collections let an animator hide the deform skeleton and work with controls only.
   - Keep IK target and pole bones unparented to the chain they drive, or parented to the root or a control bone outside that chain. An IK target parented into its own chain creates a dependency loop, and Blender will report a dependency cycle and the chain will stop updating.

6. Bind the mesh to the armature.
   - Binding does two things: it adds an Armature modifier to the mesh pointing at the rig, and it creates the vertex groups and weights that say how much each bone moves each vertex. Both halves must be present. A modifier with no weights deforms nothing, and weights with no modifier deform nothing.
   - Parent the mesh to the armature with `bpy.ops.object.parent_set`. Its `type` values in 5.1.1 include ARMATURE_NAME, ARMATURE_ENVELOPE, and ARMATURE_AUTO, and the choice sets the starting weights:
     - ARMATURE_NAME creates empty vertex groups named after the deform bones and no weights. Use it when you plan to paint every weight by hand.
     - ARMATURE_ENVELOPE seeds weights from each bone's envelope distance. Use it for a quick first pass on simple or mechanical shapes, then paint corrections.
     - ARMATURE_AUTO creates automatic weights from bone heat. Verified in 5.1.1: parenting a default cube to a one-bone armature with ARMATURE_AUTO finished, added an Armature modifier, created a vertex group named after the bone, and the weights across the 8 cube vertices summed to 8.0. Use it as the starting point for organic characters, then fix the problem joints by hand in step 7. Automatic weights fail or bleed across nearby parts on some meshes, for example fingers close together or a mesh with holes and disconnected shells, so treat the result as a draft, never as finished.
   - The same two seeding methods exist in Weight Paint mode as `bpy.ops.paint.weight_from_bones` with type AUTOMATIC or ENVELOPES, verified as enum values in 5.1.1. Use that route when the mesh is already parented and only the weights need redoing.
   - After binding, run the audit script. Before any painting, it should already show: every deform bone has a matching vertex group, no group lacks a matching bone, and most vertices carry weight. Fix naming mismatches now, while no painted work can be lost.
   - Do not apply the Armature modifier on a rig you still need to pose. Applying it bakes the current pose into the mesh and ends the rig. Duplicate the mesh first if a baked copy is needed for a separate task, such as delivery prep under gltf-asset-optimization.

7. Paint and clean the weights.
   - Work joint by joint in Weight Paint mode. Select the bone's vertex group, pose the joint to a bent position, and paint while watching the bend. Weights painted in the rest pose hide their problems; the bend is where a bad gradient shows.
   - A good bend has a smooth gradient across the joint: full weight to the upper bone on one side, full weight to the lower bone on the other side, and a short blend zone at the crease. A hard step in the gradient makes a crease that collapses. A gradient that is too wide makes the joint look soft and loses volume along the whole limb.
   - Keep weight totals sane. For most body rigs, each vertex should total about 1.0 across its deform groups, and limiting influences per vertex keeps deformation predictable and keeps the rig usable in stricter targets. `bpy.ops.object.vertex_group_normalize_all`, `bpy.ops.object.vertex_group_limit_total`, and `bpy.ops.object.vertex_group_clean` all exist in 5.1.1 and handle the mechanical part: normalize totals, cap the number of groups per vertex, and drop near-zero weights. A limit of 4 influences per vertex is a common working target for game-bound rigs, but it is a target choice, not a Blender rule; a film rig may allow more.
   - Use `bpy.ops.paint.weight_set` to set an exact weight on selected vertices and `bpy.ops.paint.weight_gradient` for a linear falloff along a stroke; both exist in 5.1.1. Use the paint sample tool (`bpy.ops.paint.weight_sample` exists in 5.1.1) to read the weight under the cursor before guessing why a vertex moves wrong.
   - Mirror weights instead of painting the second side by hand when the mesh is symmetric. `bpy.ops.object.vertex_group_mirror` exists in 5.1.1. After mirroring, run the audit and spot-check one mirrored joint in a bent pose, because mirroring copies errors as faithfully as it copies good weights.
   - Common weight faults and their fixes:
     - A vertex on the left leg moves with the right leg: its weights include the wrong side's group, often from automatic weights bleeding across a narrow gap. Clean that group off the vertex.
     - The elbow collapses to a point: the blend zone is too narrow, or a needed edge loop is missing (step 2). Widen the gradient first; if the crease still folds, the mesh needs geometry, not paint.
     - A patch of mesh stays behind when the rig moves: those vertices have no deform weight. The audit counts them as unweighted vertices.
     - The mesh moves twice as far as the bone: weights total about 2.0 instead of 1.0. The audit counts these as non-normalized vertices. Normalize.
   - Re-run the audit after cleaning. On a healthy body rig the target numbers are: unweighted vertices 0 for the parts the rig must move, non-normalized vertices 0, and deform bones without a group 0. Vertices that belong to parts the rig never moves, such as a separate static prop merged into the same mesh, are a legitimate exception; note them in the rig notes instead of weighting them to a random bone.

8. Add constraints where a pose must follow something.
   - Bone constraints live on pose bones. In Python they are created with `pose_bone.constraints.new(type)`, and the type values verified in 5.1.1 include COPY_LOCATION, COPY_ROTATION, COPY_SCALE, COPY_TRANSFORMS, LIMIT_LOCATION, LIMIT_ROTATION, LIMIT_SCALE, DAMPED_TRACK, TRACK_TO, CHILD_OF, FLOOR, IK, and SPLINE_IK. The Pose Mode operators `bpy.ops.pose.constraint_add` and `bpy.ops.pose.ik_add` also exist in 5.1.1.
   - Reach for constraints for specific jobs:
     - COPY_ROTATION or COPY_TRANSFORMS: a bone that must follow another bone, such as a shoulder pad following the upper arm, or a jaw aid following the head with partial influence.
     - LIMIT_ROTATION and LIMIT_LOCATION: hard stops that keep an animator from bending a joint past its range, such as a knee that must not bend backward. Set limits from the deformation tests in step 10, not from guesses.
     - DAMPED_TRACK or TRACK_TO: a bone that must aim at a target, such as eyes tracking a control, or a head look-at for a simple rig.
     - CHILD_OF: a bone or held prop that switches parents, such as a hand IK target that follows a held object. This constraint has a set-inverse step in the UI; skipping it makes the constrained bone jump when the constraint is enabled.
   - Check every constraint target after creating it. A constraint whose target field is empty usually does nothing while looking configured. The audit script flags IK constraints with no target; for other constraint types, read the target field in the bone constraints panel for each constrained bone.
   - Set influence deliberately. A copy constraint at full influence removes the bone from the animator's control; partial influence is often the point, for example a chest aid that follows the spine at half strength. Write the reason for each non-obvious constraint in the rig notes, because a future editor will otherwise delete it as clutter.
   - Add constraints after weights are clean. A constraint changes how a bone moves, which changes what the deformation test shows, so adding constraints first buries weight faults under constraint behavior.

9. Set up IK for legs, arms, and other push-pull chains.
   - IK goes on the last deform bone of the chain it drives: for a leg, the shin bone; for an arm, the forearm bone. Create it with `constraints.new("IK")` on that pose bone, or with `bpy.ops.pose.ik_add` in Pose Mode with the bone selected.
   - Set the IK constraint fields: `target` is the armature object, `subtarget` is the IK control bone, for example `ik_hand.L`, and `chain_count` is how many bones back up the chain the IK solves. A leg or arm usually uses a chain count of 2, which covers the two bones above and including the constrained bone's chain segment; a spine or tail uses a count matched to its chain length. Chain count 0 means the solver runs to the root of the chain, which is rarely what a limb needs, because moving the foot would then bend the hips too.
   - Give the chain a pole target when the elbow or knee direction matters, which is nearly always for limbs. Without a pole, the solver picks the bend direction from the rest pose and the knee can flip sideways mid-animation. Place the pole control bone in front of the knee or behind the elbow, parent it to the root or the IK target as the rig design requires, and set it in the IK constraint's pole fields (`pole_target` and `pole_subtarget` on the constraint in 5.1.1).
   - Tune per-bone IK behavior on the pose bone. Properties verified in 5.1.1 include `lock_ik_x`, `lock_ik_y`, and `lock_ik_z` to lock a bone out of bending on an axis, `ik_min_x` and the matching min and max properties per axis to bound the bend, and `ik_stiffness_x` and its per-axis siblings to make a bone resist bending. Lock the axes a hinge joint must not use: a knee bends on one axis, and locking the other two stops the solver from twisting the shin to reach a target.
   - Keep an FK option in mind. Production rigs often carry both FK and IK for the same limb with a switch. That switch rig is beyond this skill's scope; a basic rig should ship with IK on legs at minimum, clean FK posing everywhere, and a note in the rig summary saying which chains are IK-driven so the animator is not surprised.
   - Test IK immediately: grab the target bone in Pose Mode, move it through the full range the animation will need, and watch for knee flips, chain stretching, and the foot sliding off the target. Fix pole placement first; most flips are pole problems, not solver problems.

10. Add control bone shapes and finish the control layout.
    - Pose bones accept a custom shape object through the `custom_shape` property, verified present on PoseBone in 5.1.1. Assign simple wireframe shapes (circles for rotations, boxes or arrows for movers) so controls read at a glance and can be grabbed without hunting for a thin bone inside the mesh.
    - Scale and orient each shape so its form suggests its motion: a flat circle around a joint for a rotator, a shape at the hips for the root mover. Keep shapes small enough to see the mesh past them.
    - Decide which bones an animator may key and say so in the rig notes: usually the control bones and the FK deform chain, never the IK-driven bones themselves. Posing an IK-driven bone directly fights the solver and produces keys that appear to do nothing.

11. Test deformation joint by joint.
    - Reset to the rest pose first and save a copy of the file. Every test below is a pose, and a saved test pose left in the file becomes someone else's broken rest pose.
    - For each joint, pose the extremes the animation will actually use, and check the bend at each one:
      - Elbow and knee: bend to about 90 degrees, then to full bend. Look for collapse at the crease, volume loss along the limb, and skin from the far side pulled into the bend.
      - Shoulder: raise the arm forward and sideways, and rotate it. Shoulders fail more than any other joint; check the armpit and the top of the shoulder for pinching and for chest weights dragging the arm skin.
      - Spine: bend forward, bend back, and twist. Check that the bend spreads across all spine bones instead of hinging on one, and that belly and back weights do not trade sides.
      - Wrist, ankle, and fingers if present: bend and twist. Check for candy-wrapper twisting, where the mesh spirals because twist weight sits on one bone only. Splitting twist between two bones along the limb is the standard fix direction.
      - Head and neck: look up, down, and to each side. Check the throat and jaw line for stretching.
    - Test the IK chains again in the same session (step 9), and test each constraint target by moving the target, not the constrained bone.
    - Record every fault as a row: joint, pose, what the fault looks like, and the fix applied (paint change, gradient change, limit change, added loop, roll change). Fix weights before adding corrective devices; most faults in a basic rig are weight faults.
    - Run the audit script one last time on the final file and keep its output with the rig notes. The audit does not judge bend quality, which only the pose tests above can do; it proves the mechanical state (names, groups, weight totals, constraints) is what the rig summary claims.

## 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())
```
