Every time I hand off a mocap take I feel like I'm playing "spot the body penetration" roulette. The obvious stuff you catch while scrubbing, but the subtle ones, a wrist briefly clipping a hip bone or a hand drifting through the chest mid-reach, slip through and become the animator's problem to clean up. Which is frustrating when you're the one who already did a cleanup pass.
Wrote a Blender script that iterates the frame range, checks world-space distance between a set of hand/arm bones and body bones each frame, and flags anything under a threshold. Optionally drops timeline markers at flagged frames so you can jump straight to the problem without scrubbing.
import bpy
ARMATURE_NAME = "Armature"
THRESHOLD = 0.08 # meters — tune per rig scale
HAND_BONES = ["hand_l", "hand_r", "lowerarm_twist_01_l", "lowerarm_twist_01_r"]
BODY_BONES = ["spine_02", "spine_03", "clavicle_l", "clavicle_r", "neck_01"]
CREATE_MARKERS = True
def run_check():
obj = bpy.data.objects.get(ARMATURE_NAME)
if not obj or obj.type != 'ARMATURE':
print("Armature not found, check ARMATURE_NAME")
return
scene = bpy.context.scene
flagged = {}
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
bpy.context.view_layer.update()
pb = obj.pose.bones
for hname in HAND_BONES:
if hname not in pb:
continue
hw = obj.matrix_world @ pb[hname].head
for bname in BODY_BONES:
if bname not in pb:
continue
bw = obj.matrix_world @ pb[bname].head
dist = (hw - bw).length
if dist < THRESHOLD:
flagged.setdefault(frame, []).append(
f"{hname} -> {bname} ({dist:.3f}m)"
)
if not flagged:
print("No intersections found.")
return
print(f"{len(flagged)} frame(s) flagged:")
for fr, hits in sorted(flagged.items()):
print(f" Frame {fr:4d}: {', '.join(hits)}")
if CREATE_MARKERS:
scene.timeline_markers.new(f"INTERSECT_{fr}", frame=fr)
run_check()
Bone names are set up for UE5 Mannequin convention, just swap in your rig's naming. The THRESHOLD value took some tuning; 0.08m works for my scale but you'll want to experiment. Point-to-point distance between bone heads isn't perfect and will miss some rotational edge cases, but it catches the bulk of real collisions without being oversensitive.
It's slow on long takes. Full frame-set loop in Python, no way around that. A 300-frame take with this bone config runs in about 4–5 seconds here. Fine for a cleanup pass, not great if you wanted to run it as part of a batch pipeline.
Curious if anyone's found a smarter approach, checking actual mesh proximity, bounding volume heuristics, anything. The bone-distance method is crude but I've already caught several things that would have slipped through to the animator otherwise.