# Blender Rig

> >

- **Type:** Skill
- **Install:** `agentstack add skill-max-786-claude-3d-harness-blender-rig`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [MAX-786](https://agentstack.voostack.com/s/max-786)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [MAX-786](https://github.com/MAX-786)
- **Source:** https://github.com/MAX-786/claude-3d-harness/tree/main/library/gaius/blender-rig

## Install

```sh
agentstack add skill-max-786-claude-3d-harness-blender-rig
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Skill: Blender Procedural Rigging

Sei un esperto di rigging anatomico e procedurale in Blender.
Costruisci scheletri corretti, deformazioni biomeccaniche e animazioni da codice.

---

## Connessione — MCP (predefinito)

```python
mcp__Blender__execute_blender_code(code="""
import bpy, bmesh, math
from mathutils import Vector, Matrix, Quaternion
# ... codice ...
result = {"ok": True}
""")

mcp__Blender__get_screenshot_of_window_as_image()
mcp__Blender__render_viewport_to_path(output_path="/rig_preview.png")
```

---

## IL PRINCIPIO FONDAMENTALE — Bone Roll e Frame Locali

Ogni osso in Blender ha un **frame locale** (assi X, Y, Z):
- **Y locale**: direzione testa→coda (sempre, non modificabile)
- **X locale**: determinato dal **Bone Roll**
- **Z locale**: B = Y × X

**Bone Roll = 0** significa che l'asse X locale è allineato al mondo.
Questo è **fondamentale** per catene articolate: garantisce che ruotando
sull'asse X (flessione) le falangi/vertebre si chiudano senza torsioni strane.

```
Roll sbagliato → l'osso ruota su un asse obliquo → movimenti strani
Roll = 0       → flessione sull'asse X = chiusura anatomica corretta
```

**Come verificare:**
```python
# In Edit Mode — controlla il roll di ogni osso
for bone in arm.data.edit_bones:
    print(f"{bone.name}: roll = {math.degrees(bone.roll):.2f}°")
# Se non è 0° (o 90° per assi specifici), correggilo
```

---

## ARMATURE — Creazione e Convenzioni

```python
import bpy, math
from mathutils import Vector

def create_armature(name="Rig"):
    """
    Crea un'armatura vuota pronta per ricevere ossa.
    Ritorna (arm_obj, armature).
    """
    bpy.ops.object.armature_add(enter_editmode=True,
                                 align='WORLD', location=(0,0,0))
    arm_obj  = bpy.context.active_object
    arm_obj.name = name
    armature = arm_obj.data
    armature.name = name + "_Armature"
    # Rimuovi l'osso di default
    armature.edit_bones.remove(armature.edit_bones[0])
    return arm_obj, armature

def add_bone(armature, name, head, tail, parent=None,
             connected=False, roll=0.0, layer=0):
    """
    Aggiunge un osso all'armatura (deve essere in Edit Mode).
    
    head, tail  : tuple (x,y,z) in world space
    parent      : nome dell'osso padre (stringa) o None
    connected   : se True, la testa si attacca alla coda del padre
    roll        : rotazione dell'asse X locale in radianti (0 = allineato al mondo)
    
    NOMENCLATURA CONSIGLIATA:
      - Lato: _L (sinistra), _R (destra)
      - Segmento: Palm, Proximal, Intermediate, Distal
      - Root: osso radice (non deforma, ancora il rig)
      - DEF_: ossa che deformano la mesh (Deform bones)
      - CTRL_: ossa di controllo (non deformano)
      - MCH_: ossa meccanismo (intermedie, non visibili)
    """
    bone = armature.edit_bones.new(name)
    bone.head  = Vector(head)
    bone.tail  = Vector(tail)
    bone.roll  = roll
    bone.use_deform = True
    if parent:
        bone.parent = armature.edit_bones[parent]
        bone.use_connect = connected
    return bone

# ── Esempio: dito indice ──────────────────────────────────────────────
def create_finger(armature, name, base_y, segments=None):
    """
    Crea una catena di ossa per un dito.
    
    segments: lista di (nome_suffisso, y_start, y_end)
    Se None, usa proporzioni standard (Proximal 40%, Intermediate 30%, Distal 30%).
    """
    if segments is None:
        segments = [
            ("Palm",         0.0,  -2.0),
            ("Proximal",    -2.0,  -4.0),
            ("Intermediate",-4.0,  -5.5),
            ("Distal",      -5.5,  -6.5),
        ]
    
    prev = None
    for i, (seg, y0, y1) in enumerate(segments):
        bname = f"{name}_{seg}"
        bone  = add_bone(armature, bname,
                          head=(0, y0 + base_y, 0),
                          tail=(0, y1 + base_y, 0),
                          parent=prev, connected=(i > 0),
                          roll=0.0)  # ← ROLL = 0: asse X orizzontale
        prev = bname
```

---

## FK / IK — DUALITÀ

**FK (Forward Kinematics)**: ruota ogni osso manualmente dalla radice alla punta.
Preciso, prevedibile, ma lento per animazioni complesse.

**IK (Inverse Kinematics)**: sposti la punta, Blender calcola gli angoli delle giunzioni.
Rapido per posare, ma meno preciso. Richiede un Pole Target per evitare flipping del ginocchio/gomito.

```python
def setup_ik(arm_obj, tip_bone_name, chain_count=3,
              pole_target=None, pole_bone=None, pole_angle=0):
    """
    Aggiunge un vincolo IK all'osso 'tip_bone_name'.
    Deve essere chiamato in POSE mode.
    
    chain_count : numero di ossa nella catena IK (2=gomito, 3=dito, 4=gamba)
    pole_target : oggetto Empty usato come Pole Target (evita il flip del gomito)
    pole_angle  : angolo di offset del pole in gradi (tipico: 0° o 90°)
    
    Esempio — gamba con knee target:
        # 1. Crea Empty per il knee
        bpy.ops.object.empty_add(location=(0, -2, 0.5))
        knee = bpy.context.active_object
        knee.name = "Knee_Target"
        # 2. Attiva pose mode sull'armatura
        bpy.context.view_layer.objects.active = arm_obj
        bpy.ops.object.mode_set(mode='POSE')
        # 3. Applica IK
        setup_ik(arm_obj, "Shin", chain_count=2,
                 pole_target=knee, pole_angle=0)
    """
    bpy.context.view_layer.objects.active = arm_obj
    
    if bpy.context.mode != 'POSE':
        bpy.ops.object.mode_set(mode='POSE')
    
    pbone = arm_obj.pose.bones[tip_bone_name]
    ik    = pbone.constraints.new('IK')
    ik.chain_count = chain_count
    
    if pole_target:
        ik.pole_target      = pole_target
        ik.pole_subtarget   = pole_bone or ""
        ik.pole_angle       = math.radians(pole_angle)
    
    return ik

def setup_ik_target(arm_obj, tip_bone_name, target_obj=None, chain_count=3):
    """
    Alternativa: usa un Empty come target IK (la punta segue l'Empty).
    Crea automaticamente l'Empty se target_obj è None.
    """
    if target_obj is None:
        tip_world = arm_obj.pose.bones[tip_bone_name].tail
        bpy.ops.object.empty_add(location=arm_obj.matrix_world @ tip_world)
        target_obj = bpy.context.active_object
        target_obj.name = f"IK_{tip_bone_name}_Target"
    
    bpy.context.view_layer.objects.active = arm_obj
    bpy.ops.object.mode_set(mode='POSE')
    pbone = arm_obj.pose.bones[tip_bone_name]
    ik    = pbone.constraints.new('IK')
    ik.target      = target_obj
    ik.chain_count = chain_count
    return ik, target_obj

def toggle_ik_fk(arm_obj, bone_names, use_ik=True):
    """
    Commuta tra IK e FK per un gruppo di ossa.
    IK: abilita vincolo IK, disabilita mute su FK
    FK: disabilita vincolo IK (mute=True)
    
    Utile per switch IK/FK durante l'animazione.
    """
    bpy.context.view_layer.objects.active = arm_obj
    bpy.ops.object.mode_set(mode='POSE')
    for bn in bone_names:
        for c in arm_obj.pose.bones[bn].constraints:
            if c.type == 'IK':
                c.mute = not use_ik
```

---

## WEIGHT PAINTING ALGORITMICO

Il weight painting procedurale assegna pesi ai vertex group in base
alla distanza da punti di controllo. Più preciso del painting manuale,
100% riproducibile, zero artefatti di painting.

### Smoothstep interpolator
```python
def smoothstep(x):
    """Interpolazione cubica [0,1] → [0,1]. Derivata zero agli estremi."""
    x = max(0.0, min(1.0, x))
    return x * x * (3 - 2 * x)

def smootherstep(x):
    """Quintica — transizione ancora più morbida. Per blend tra giunzioni."""
    x = max(0.0, min(1.0, x))
    return x * x * x * (x * (x * 6 - 15) + 10)
```

### Weight painting per coordinata (catene lineari)
```python
def weight_paint_by_coord(mesh_obj, bone_ranges, coord_axis='Y',
                           blend_zone=0.3):
    """
    Assegna pesi ai vertex group in base alla coordinata di ogni vertice.
    Ideale per catene lineari (dito, colonna, arto).
    
    bone_ranges : dict {nome_vg: (coord_min, coord_max)}
                  la coordinata è in LOCAL SPACE del mesh_obj
    coord_axis  : 'X', 'Y', o 'Z'
    blend_zone  : frazione del range usata per il blend tra ossa adiacenti [0-0.5]
    
    Esempio (dito lungo Y, da 0 a -6.5):
        weight_paint_by_coord(skin, {
            "Palm":         ( 0.0, -2.0),
            "Proximal":    (-2.0, -4.0),
            "Intermediate":(-4.0, -5.5),
            "Distal":      (-5.5, -6.5),
        }, coord_axis='Y', blend_zone=0.3)
    """
    axis_idx = {'X': 0, 'Y': 1, 'Z': 2}[coord_axis]
    
    # Crea vertex group se non esistono
    vgs = {}
    for name in bone_ranges:
        vg = mesh_obj.vertex_groups.get(name)
        if vg is None:
            vg = mesh_obj.vertex_groups.new(name=name)
        vg.add(list(range(len(mesh_obj.data.vertices))), 0.0, 'REPLACE')
        vgs[name] = vg
    
    names   = list(bone_ranges.keys())
    ranges  = list(bone_ranges.values())
    
    for v in mesh_obj.data.vertices:
        coord = v.co[axis_idx]
        for i, (name, (cmin, cmax)) in enumerate(zip(names, ranges)):
            span  = cmax - cmin if cmax != cmin else 1e-6
            blend = abs(span) * blend_zone
            
            if min(cmin, cmax)  0 and abs(blend) > 1e-8:
                    fade_in  = abs((coord - cmin) / blend)
                    if fade_in  1e-8:
                    fade_out = abs((cmax - coord) / blend)
                    if fade_out  0:
                    vgs[name].add([v.index], w, 'ADD')

### Weight painting per distanza da punto 3D
def weight_paint_by_distance(mesh_obj, vg_name, center_local,
                               max_dist, falloff='SMOOTH'):
    """
    Assegna pesi in base alla distanza da un punto 3D in spazio locale.
    Ideale per aree di influenza circolari (nocca, spalla, muscolatura).
    
    center_local : Vector in coordinate locali del mesh
    max_dist     : distanza oltre cui il peso è 0
    falloff      : 'LINEAR', 'SMOOTH' (smoothstep), 'SHARP' (quadratica)
    
    Esempio (palpebra — bordo dell'apertura):
        eye_center = Vector((0, -0.5, 0.35))
        weight_paint_by_distance(eyelid, "UpperLid", eye_center,
                                  max_dist=0.9, falloff='SMOOTH')
    """
    vg = mesh_obj.vertex_groups.get(vg_name)
    if vg is None:
        vg = mesh_obj.vertex_groups.new(name=vg_name)
    
    center = Vector(center_local)
    for v in mesh_obj.data.vertices:
        dist = (v.co - center).length
        t    = max(0.0, 1.0 - dist / max_dist)
        if falloff == 'SMOOTH':
            w = smoothstep(t)
        elif falloff == 'SHARP':
            w = t * t
        else:  # LINEAR
            w = t
        if w > 0.001:
            vg.add([v.index], w, 'REPLACE')
```

---

## SHAPE KEYS + DRIVER — Biomeccanica Procedurale

I Shape Key + Driver creano deformazioni reattive: la nocca si solleva
quando il dito si piega, l'occhio si gonfia quando si chiude, il muscolo
si contrae quando l'osso ruota.

```python
def add_shape_key_with_driver(mesh_obj, key_name, driver_bone,
                               arm_obj, transform='ROT_X',
                               expression="abs(rot) / 1.5",
                               var_name="rot"):
    """
    Crea uno Shape Key con Driver collegato alla rotazione di un osso.
    
    key_name      : nome della shape key (es: "Knuckle_Bend")
    driver_bone   : nome dell'osso che guida la deformazione
    arm_obj       : l'oggetto armatura
    transform     : 'ROT_X' | 'ROT_Y' | 'ROT_Z' | 'LOC_X' | 'SCALE_X' ecc.
    expression    : espressione Python del driver (var_name è la variabile)
    
    Ritorna: la shape key (aggiungi vertici spostati dopo questa chiamata)
    
    Esempio completo (nocca che si solleva quando il dito si piega):
        sk = add_shape_key_with_driver(skin, "Knuckle_Bend", "Proximal",
                                        arm_obj, 'ROT_X',
                                        "abs(rot) / 1.5")
        # Poi sposta i vertici della nocca nella sk.data:
        for i, v in enumerate(skin.data.vertices):
            if is_near_knuckle(v.co):
                influence = knuckle_weight(v.co)
                sk.data[i].co.z += 0.3 * influence
    """
    # Assicurati che esista la Basis key
    if not mesh_obj.data.shape_keys:
        mesh_obj.shape_key_add(name="Basis", from_mix=False)
    
    sk = mesh_obj.shape_key_add(name=key_name, from_mix=False)
    sk.value = 0.0
    
    # Aggiunge il Driver
    fc     = sk.driver_add("value")
    driver = fc.driver
    driver.type = 'SCRIPTED'
    
    var            = driver.variables.new()
    var.name       = var_name
    var.type       = 'TRANSFORMS'
    t              = var.targets[0]
    t.id           = arm_obj
    t.bone_target  = driver_bone
    t.transform_type  = transform
    t.transform_space = 'LOCAL_SPACE'
    
    driver.expression = expression
    return sk

def deform_knuckle(mesh_obj, joint_y, influence_radius=0.8,
                   lift_z=0.3, compress_z=0.4):
    """
    Applica la deformazione della nocca a una shape key già creata.
    Chiama questa DOPO add_shape_key_with_driver.
    
    joint_y         : coordinata Y del giunto (in local space)
    influence_radius: raggio di influenza della nocca
    lift_z          : quanto si solleva la nocca superiore
    compress_z      : quanto si comprime l'interno del giunto
    
    Il metodo identifica automaticamente:
      - vertici sopra il giunto (nocca) → si sollevano (+z)
      - vertici sotto il giunto (palmo) → si comprimono (verso +z)
    """
    sk = mesh_obj.data.shape_keys.key_blocks[-1]  # l'ultima shape key
    
    for i, v in enumerate(mesh_obj.data.vertices):
        dist = abs(v.co.y - joint_y)
        if dist  0:   # parte superiore → nocca
                sk.data[i].co.z += lift_z * influence
                sk.data[i].co.y += 0.2 * influence
            else:             # parte inferiore → compressione
                sk.data[i].co.z += compress_z * influence
```

---

## SOCKET SYSTEM — Collegare Oggetti a Ossa

Il Socket System usa il vincolo `CHILD_OF` per ancorare oggetti a ossa.
Permette di attaccare armi, attrezzi, oggetti a una mano/braccio.

```python
def socket_attach(mesh_obj, arm_obj, bone_name,
                  local_offset=(0,0,0), local_rotation=(0,0,0)):
    """
    Attacca mesh_obj all'osso bone_name tramite vincolo CHILD_OF.
    
    local_offset   : posizione dell'oggetto in coordinate LOCALI dell'osso
    local_rotation : rotazione in gradi (Euler XYZ)
    
    MATEMATICA DEL VINCOLO:
      M_world_child = M_world_bone × M_offset
      Il CHILD_OF sincronizza automaticamente questa equazione.
    
    Esempio (spada ancorata al palmo):
        socket_attach(sword, hand_rig, "Palm",
                      local_offset=(0, -3.5, -0.6),
                      local_rotation=(0, 0, 0))
    
    Esempio (scudo ancorato al braccio sinistro):
        socket_attach(shield, body_rig, "Forearm_L",
                      local_offset=(0, -1.0, 0.1),
                      local_rotation=(0, 90, 0))
    """
    c = mesh_obj.constraints.new('CHILD_OF')
    c.target     = arm_obj
    c.subtarget  = bone_name
    
    # Offset locale rispetto all'osso
    mesh_obj.location = Vector(local_offset)
    mesh_obj.rotation_euler = tuple(math.radians(r) for r in local_rotation)
    
    # Calcola l'inverse matrix per il vincolo
    bpy.context.view_layer.objects.active = mesh_obj
    bpy.ops.constraint.childof_set_inverse(
        constraint=c.name, owner='OBJECT')
    
    return c

def socket_detach(mesh_obj, bone_name=None):
    """Rimuove tutti i vincoli CHILD_OF (o solo quello verso bone_name)."""
    for c in list(mesh_obj.constraints):
        if c.type == 'CHILD_OF':
            if bone_name is None or c.subtarget == bone_name:
                mesh_obj.constraints.remove(c)
```

---

## PELLE BIOMECCANICA — Shrinkwrap + Solidify

Tecnica per creare pelle che avvolge uno scheletro curvo:
1. Crea una mesh di partenza dalla forma generale (sfera, cili

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [MAX-786](https://github.com/MAX-786)
- **Source:** [MAX-786/claude-3d-harness](https://github.com/MAX-786/claude-3d-harness)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-max-786-claude-3d-harness-blender-rig
- Seller: https://agentstack.voostack.com/s/max-786
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
