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)
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="<JOB_DIR>/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:
# 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
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.
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
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)
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) <= coord <= max(cmin, cmax):
# Dentro il range: peso 1 al centro, blend agli estremi
t = (coord - cmin) / span
w = 1.0
# Blend con l'osso precedente (inizio range)
if i > 0 and abs(blend) > 1e-8:
fade_in = abs((coord - cmin) / blend)
if fade_in < 1.0:
w = min(w, smoothstep(fade_in))
# Osso precedente prende il complemento
vgs[names[i-1]].add([v.index],
(1.0 - smoothstep(fade_in)), 'ADD')
# Blend con l'osso successivo (fine range)
if i < len(names)-1 and abs(blend) > 1e-8:
fade_out = abs((cmax - coord) / blend)
if fade_out < 1.0:
w = min(w, smoothstep(fade_out))
vgs[names[i+1]].add([v.index],
(1.0 - smoothstep(fade_out)), 'ADD')
if w > 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.
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 < influence_radius:
influence = smoothstep(1.0 - dist / influence_radius)
if v.co.z > 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.
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:
- Crea una mesh di partenza dalla forma generale (sfera, cilindro)
- Ritaglia le aperture (occhio a mandorla, bocca, ecc.) in Edit Mode
- Shrinkwrap: la mesh si "avvolge" sull'oggetto sottostante
- Solidify: aggiunge spessore
- Armature: deformazione da scheletro
def skin_with_shrinkwrap(skin_obj, target_obj, wrap_method='NEAREST_SURFACEPOINT',
offset=0.005, thickness=0.04, subsurf_levels=2):
"""
Configura i modificatori per pelle biomeccanica.
L'ordine è critico: Armature → Shrinkwrap → Solidify → SubSurf.
wrap_method:
'NEAREST_SURFACEPOINT' : proietta sul punto più vicino — stabile su mesh curve
'PROJECT' : proietta lungo una direzione — per superfici piatte
'NEAREST_VERTEX' : snapping ai vertici — meno smooth
Esempio (palpebra su sfera occhio):
create_eyeball() → eyeball
create_eyelid() → eyelid_mesh (con apertura a mandorla)
skin_with_shrinkwrap(eyelid_mesh, eyeball,
offset=0.005, thickness=0.04)
"""
# 1. Shrinkwrap
sw = skin_obj.modifiers.new("Shrinkwrap", "SHRINKWRAP")
sw.target = target_obj
sw.wrap_method = wrap_method
sw.offset = offset
# 2. Solidify (spessore verso l'esterno)
sol = skin_obj.modifiers.new("Solidify", "SOLIDIFY")
sol.thickness = thickness
sol.offset = 1.0
# 3. SubSurf (leviga il risultato)
sub = skin_obj.modifiers.new("Subdivision", "SUBSURF")
sub.levels = subsurf_levels
sub.render_levels = subsurf_levels + 1
skin_obj.data.shade_smooth()
return sw, sol, sub
def bind_armature(mesh_obj, arm_obj):
"""
Collega mesh_obj all'armatura arm_obj come modifier.
Da chiamare DOPO aver configurato i vertex group con weight_paint_*.
Il modifier Armature deve essere il PRIMO nella stack.
"""
# Aggiungi Armature come primo modificatore
arm_mod = mesh_obj.modifiers.new("Armature", "ARMATURE")
arm_mod.object = arm_obj
# Sposta in cima alla stack (prima di Shrinkwrap e Solidify)
while mesh_obj.modifiers[0].name != arm_mod.name:
bpy.ops.object.modifier_move_up(modifier=arm_mod.name)
# Parent senza vincolo di offset
mesh_obj.parent = arm_obj
mesh_obj.parent_type = 'OBJECT'
mesh_obj.matrix_parent_inverse.identity()
return arm_mod
ANIMAZIONE PROCEDURALE — Keyframe da Codice
def animate_bone(arm_obj, bone_name, frames_rots,
rotation_mode='XYZ'):
"""
Anima un osso con keyframe da codice.
frames_rots : lista di (frame, (rx_deg, ry_deg, rz_deg))
Esempio (dito che si chiude e si riapre):
animate_bone(hand_rig, "Proximal", [
(1, (0, 0, 0)), # aperto
(30, (70, 0, 0)), # chiuso
(60, (0, 0, 0)), # riaperto
])
"""
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
pbone = arm_obj.pose.bones[bone_name]
pbone.rotation_mode = rotation_mode
for frame, rot_deg in frames_rots:
bpy.context.scene.frame_set(frame)
pbone.rotation_euler = tuple(math.radians(r) for r in rot_deg)
pbone.keyframe_insert(data_path="rotation_euler", frame=frame)
bpy.ops.object.mode_set(mode='OBJECT')
def animate_grasp(arm_obj, open_frame=1, close_frame=30, open_frame2=60,
finger_bones=None):
"""
Animazione procedurale di presa (open → close → open).
finger_bones : dict {bone_name: (open_rot, closed_rot)}
angoli in gradi sull'asse X
Default: catena indice standard (Proximal, Intermediate, Distal).
"""
if finger_bones is None:
finger_bones = {
"Proximal": (0, 70),
"Intermediate": (0, 90),
"Distal": (0, 60),
}
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
for bname, (open_rot, close_rot) in finger_bones.items():
pbone = arm_obj.pose.bones[bname]
pbone.rotation_mode = 'XYZ'
for frame, deg in [(open_frame, open_rot),
(close_frame, close_rot),
(open_frame2, open_rot)]:
bpy.context.scene.frame_set(frame)
pbone.rotation_euler = (math.radians(deg), 0, 0)
pbone.keyframe_insert(data_path="rotation_euler", frame=frame)
bpy.ops.object.mode_set(mode='OBJECT')
def set_interpolation(arm_obj, interp='BEZIER'):
"""
Imposta il tipo di interpolazione per tutte le f-curves dell'armatura.
'BEZIER' : morbido (default — buono per movimenti organici)
'LINEAR' : lineare (buono per turntable, meccanico)
'CONSTANT': scatto (per animazioni stop-motion o flag)
"""
if arm_obj.animation_data and arm_obj.animation_data.action:
for fc in arm_obj.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = interp
WORKFLOW COMPLETO — Mano con Presa
# ══ STEP 1: Armatura ══════════════════════════════════════════
arm_obj, arm = create_armature("Hand_Rig")
# Ossa in edit mode (già attivo da create_armature)
joints = [("Palm", (0,-2.0,0)), ("Proximal",(0,-4.0,0)),
("Intermediate",(0,-5.5,0)), ("Distal",(0,-6.5,0))]
prev = None
for i, (name, tail) in enumerate(joints):
head = (0, ([0,-2.0,-4.0,-5.5][i]), 0)
add_bone(arm, name, head, tail, parent=prev,
connected=(i>0), roll=0.0)
prev = name
bpy.ops.object.mode_set(mode='OBJECT')
# ══ STEP 2: Mesh cilindrica ══════════════════════════════════
bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=0.6,
depth=7.0, location=(0,-3.25,0), rotation=(math.radians(90),0,0))
skin = bpy.context.active_object
skin.name = "Finger_Skin"
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.subdivide(number_cuts=20)
bpy.ops.object.mode_set(mode='OBJECT')
# ══ STEP 3: Weight Painting ═══════════════════════════════════
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.25)
# ══ STEP 4: Shape Key Nocca ═══════════════════════════════════
sk = add_shape_key_with_driver(skin, "Knuckle_Bend", "Proximal",
arm_obj, 'ROT_X', "abs(rot)/1.5")
deform_knuckle(skin, joint_y=-2.0)
# ══ STEP 5: Bind armatura ════════════════════════════════════
bind_armature(skin, arm_obj)
# ══ STEP 6: Socket (oggetto nella mano) ══════════════════════
bpy.ops.mesh.primitive_cylinder_add(radius=0.4, depth=4.0,
location=(0,0,0), rotation=(0,math.radians(90),0))
handle = bpy.context.active_object
handle.name = "Handle"
socket_attach(handle, arm_obj, "Palm",
local_offset=(0,-3.5,-0.6))
# ══ STEP 7: Animazione presa ══════════════════════════════════
animate_grasp(arm_obj, open_frame=1, close_frame=30, open_frame2=60)
bpy.context.scene.frame_end = 80
WORKFLOW COMPLETO — Palpebra Biomeccanica
# 1. Sfera oculare
bpy.ops.mesh.primitive_uv_sphere_add(segments=64, ring_count=32,
radius=1.0, location=(0,0,0))
eyeball = bpy.context.active_object
eyeball.name = "Eyeball"
eyeball.data.shade_smooth()
# 2. Maschera facciale (sfera leggermente più grande con apertura a mandorla)
bpy.ops.mesh.primitive_uv_sphere_add(segments=100, ring_count=50,
radius=1.02, location=(0,0,0))
eyelid = bpy.context.active_object
eyelid.name = "Eyelid"
# Ritaglia l'apertura in Edit Mode (parabola a mandorla)
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(eyelid.data)
width, height = 0.8, 0.35
to_del = [v for v in bm.verts if v.co.y > 0.1 or
(v.co.y < 0.2 and abs(v.co.x) < width and
abs(v.co.z) <= height * (1-(v.co.x/width)**2))]
bmesh.ops.delete(bm, geom=to_del, context='VERTS')
bmesh.update_edit_mesh(eyelid.data)
bpy.ops.object.mode_set(mode='OBJECT')
# 3. Weight paint per upper/lower lid
weight_paint_by_distance(eyelid, "EyelidControl_Upper",
Vector((0,-0.5, 0.35)), max_dist=0.9, falloff='SMOOTH')
weight_paint_by_distance(eyelid, "EyelidControl_Lower",
Vector((0,-0.5,-0.35)), max_dist=0.9, falloff='SMOOTH')
# 4. Shrinkwrap + Solidify
skin_with_shrinkwrap(eyelid, eyeball, offset=0.005, thickness=0.04)
REGOLE QUALITÀ RIGGING
Bone Roll = 0 su tutti gli assi di flessione primari (dita, gomito, ginocchio). Eccezione: assi di abduzione/adduzione possono avere roll 90°.
Ordine modificatori è critico:
Armature → Shrinkwrap → Solidify → SubSurfInvertire Armature e Shrinkwrap causa deformazioni errate.Normalizza i vertex group dopo il weight painting: Ogni vertice deve avere pesi che sommano a 1.0.
bpy.ops.object.vertex_group_normalize_all()in Edit Mode.Driver expression deve essere limitata a [0,1]: Usa
min(1, max(0, abs(rot)/1.5))per sicurezza.Pole Target obbligatorio su catene IK con 2+ ossa. Senza pole target il gomito/ginocchio flippa a 180°.
bpy.ops.constraint.childof_set_inverseda chiamare dopo ogni socket_attach — imposta la matrice inversa corretta.Test in Pose Mode prima del render: Muovi l'osso radice → verifica che tutto il rig si muova insieme. Ruota l'osso foglia → verifica che i driver si attivino.
ANALISI RICHIESTA
| Keyword | Tecnica |
|---|---|
mano / dito / falange |
create_finger + weight_paint_by_coord + animate_grasp |
occhio / palpebra |
sfera UV + maschera a mandorla + skin_with_shrinkwrap |
gamba / ginocchio / gomito |
IK con pole target (setup_ik) |
braccio / catena |
create_armature + add_bone + setup_ik_target |
colonna / vertebra |
catena lineare + parallel_transport (usa blender-procedural) |
spada / scudo / attrezzo |
socket_attach + CHILD_OF |
nocca / muscolo / gonfiore |
Shape Key + Driver (add_shape_key_with_driver) |
presa / animazione pugno |
animate_grasp |
FK / IK switch |
toggle_ik_fk |
pelle / skin / deformazione |
bind_armature + weight_paint_* |
Se richiesta ambigua → chiedi: "L'oggetto deve deformarsi (pelle su scheletro) o solo seguire un osso rigidamente (socket)?"
Output
- Codice Python completo, nessun placeholder
- Bone Roll = 0 su tutti gli assi primari
- Weight paint sempre con smoothstep (non lineare)
- Dopo esecuzione: screenshot → analisi visiva → itera
- Driver expression sempre limitata a [0,1]