Ben Traje
← Back to maya

Generate Custom Skin Boxes from Joint Selections in Maya Python

26 Feb 26 (Today)

This was inspired from Advance Skeleton's skin boxes where it creates a pseudo geometry to represent the joint hierarchy, and eventually make the blocking of skin weightings much more manageable.

In context, a character might have high poly and low poly. The high poly for rendering. The low poly for animating/rigging. But the low poly might still not be "low" enough for blocking weights, so we usually create skin boxes to simplify the process.

The script below is too rudimentary I admit. It creates the edges for each joint but the connecting the edges to each other to create the actual geometry is a hit and miss.

For best results, have the aim axis on the joints set to Y.

import maya.cmds as cmds

def create_precise_lofted_geo(size=1.0):
    # 1. Get selected joints
    selection = cmds.ls(selection=True, type='joint')
    
    if not selection:
        cmds.warning("Please select a joint chain.")
        return

    rects = []
    # Calculate half-size for coordinates
    s = size * 0.5

    for jnt in selection:
        # 2. Create the Rectangle (NURBS curve) with custom size
        # The points are defined based on the 'size' parameter
        rect = cmds.curve(d=1, p=[(-s, 0, -s), (s, 0, -s), (s, 0, s), (-s, 0, s), (-s, 0, -s)], k=[0, 1, 2, 3, 4])
        rects.append(rect)
        
        # 3. Match position and rotation to joint
        cmds.delete(cmds.parentConstraint(jnt, rect, mo=False))

    # 4. Perform the Loft
    # d=1 (Linear) ensures flat facets between joints
    loft_result = cmds.loft(rects, ch=True, rn=True, ar=True, d=1, name="JointLoft_SURF")
    loft_surface = loft_result[0]

    # 5. Convert to Polygons with "Per Span" logic
    # This ensures exactly one segment per joint
    poly_mesh = cmds.nurbsToPoly(loft_surface, 
                                 mnd=1, # Quads
                                 format=3, # General
                                 uType=1, # Per Span
                                 vType=1, # Per Span
                                 uNumber=1, 
                                 vNumber=1, 
                                 ch=True, 
                                 name="JointChain_Loft_GEO")[0]

    # 6. Cleanup
    cmds.delete(rects)
    cmds.delete(loft_surface)
    
    # Visual Style
    cmds.setAttr(f"{poly_mesh}.overrideEnabled", 1)
    cmds.setAttr(f"{poly_mesh}.overrideColor", 17) # Yellow
    
    print(f"Created lofted mesh with size {size}.")

# Change the number below to adjust the radius/size
create_precise_lofted_geo(size=20.0)