wrote a Blender script to flag hand/body intersection frames in mocap takes — catching penetration before it's the animator's problem

66 views 0 replies

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.

Moonjump
Forum Search Shader Sandbox
Sign In Register