After the third time setting up corrective shape keys by hand for elbow deformation, I finally wrote something to handle the driver setup. The manual process is brutal: create the shape key, sculpt the correction, then dig into the graph editor to add a driver, configure the variable to point at the right bone rotation axis, and wire up the normalize expression by hand. Fine once. Not fine when you have twelve joints that need corrections.
The script takes the mesh, shape key name, armature, bone name, axis, and your neutral/target angles, then wires up the driver:
import bpy
import math
def add_corrective_driver(mesh_obj, shape_key_name, armature_obj, bone_name, axis='Y', neutral=0.0, target_angle=-90.0):
sk_data = mesh_obj.data.shape_keys
if not sk_data or shape_key_name not in sk_data.key_blocks:
print(f"Shape key '{shape_key_name}' not found on {mesh_obj.name}")
return
key_block = sk_data.key_blocks[shape_key_name]
# Clear any existing driver on this shape key
if sk_data.animation_data:
dp = f'key_blocks["{shape_key_name}"].value'
for fc in list(sk_data.animation_data.drivers):
if fc.data_path == dp:
sk_data.animation_data.drivers.remove(fc)
break
fc = key_block.driver_add("value")
drv = fc.driver
drv.type = 'SCRIPTED'
var = drv.variables.new()
var.name = 'r'
var.type = 'TRANSFORMS'
tgt = var.targets[0]
tgt.id = armature_obj
tgt.bone_target = bone_name
tgt.transform_type = f'ROT_{axis}'
tgt.transform_space = 'LOCAL_SPACE'
n = math.radians(neutral)
t = math.radians(target_angle)
drv.expression = f'max(0.0, min(1.0, (r - {n:.4f}) / ({t:.4f} - {n:.4f})))'
print(f"Driver added: '{shape_key_name}' <- {bone_name}.ROT_{axis}")
# Example
mesh = bpy.data.objects['Character_Mesh']
arm = bpy.data.objects['Character_Rig']
add_corrective_driver(mesh, 'corrective_elbow_L', arm, 'forearm_L', axis='Z', neutral=0.0, target_angle=-120.0)
The expression normalizes the bone’s local rotation into a 0–1 range so the shape key hits full value at target_angle and zero at neutral. Works reliably for single-axis corrections. Multi-axis deformations like shoulder flexion are a different story. I’ve been looking at RBF (radial basis function) solver approaches for those, but it’s a much deeper rabbit hole.
The part I’m least confident about: axis conventions. Local bone rotation axes in Blender depend heavily on bone roll at rigging time, and I’ve picked the wrong axis more than once before sanity-checking. I’ve been thinking about reading the bone’s matrix_local to auto-detect the primary deformation axis, but haven’t worked that out cleanly yet.
Anyone solved the axis-detection problem in a clean way? Also curious if anyone has set up a full PSD (pose space deformation) workflow in Blender. The geometry nodes approach looks promising but documentation on the production workflow side is pretty sparse.