MAYA API PYTHON RIGGING

Automated Dragon Wing System

Building a modular, matrix-driven wing rig with automated membrane stretching and UV-based attachments.

Rigging organic wings is one of the most complex tasks in creature setups. The challenge lies in creating a system that folds naturally, preserves volume, and remains performant. For this dragon project, I developed a modular Python tool that generates the entire setup automatically.

The core of the system relies on Matrix Nodes (`blendMatrix`, `aimMatrix`, `pickMatrix`) rather than traditional constraints, ensuring a robust and evaluation-friendly rig.

Dragon Wing Viewport

1. The Matrix-Driven Phalanges

The finger structure (phalanges) needs to handle both FK and IK logic. Instead of heavy orient constraints, I utilized `aimMatrix` nodes to drive the orientation of the guides and joints. This allows for stable aiming vectors that don't flip easily.

In the `dragon_falanges.py` module, the `make` function iterates through guide positions and establishes the matrix relationships:

# dragon_falanges.py snippet
for i in range(len(self.guides)-1):
    aim_matrix = cmds.createNode("aimMatrix", name=f"{self.side}_{name}_AMX")

    # Setup Primary and Secondary Axis
    cmds.setAttr(aim_matrix + ".primaryInputAxis", *self.primary_aim_vector, type="double3")
    cmds.setAttr(aim_matrix + ".secondaryInputAxis", *self.secondary_aim_vector, type="double3")
    
    # Connect Matrices directly (No Constraints)
    cmds.connectAttr(order[i][0] + ".worldMatrix[0]", aim_matrix + ".inputMatrix")
    cmds.connectAttr(order[i][1] + ".primaryTargetMatrix", aim_matrix + ".primaryTargetMatrix")

FK / IK Switching

To handle the switch, I used `blendMatrix` nodes. This blends the World Matrix of the FK chain and the IK chain directly, feeding the result into the skinning joints. This method is cleaner than blending individual translate/rotate/scale channels.

2. The Membrane (Webbing) Logic

The most difficult part of a wing is the skin between fingers. It needs to stretch linearly when fingers open and relax when they close.

I solved this in `membran_module.py` by calculating the weighted center between two fingers using `wtAddMatrix`. This creates a virtual "mid-point" that stays perfectly centered regardless of the hierarchy.

# membran_module.py - Calculating the mid-point
mid_position = cmds.createNode("wtAddMatrix", name=f"..._WTM")

# Connect both finger matrices
cmds.connectAttr(joint_one + ".worldMatrix[0]", mid_position + ".wtMatrix[0].matrixIn")
cmds.connectAttr(joint_two + ".worldMatrix[0]", mid_position + ".wtMatrix[1].matrixIn")

# Weight them equally (0.5) to find the center
cmds.setAttr(mid_position + ".wtMatrix[0].weightIn", 0.5)
cmds.setAttr(mid_position + ".wtMatrix[1].weightIn", 0.5)

3. Surface Attachment with UV Pins

To drive the actual geometry, I generated a NURBS surface based on the finger positions. Instead of using `pointOnSurfaceInfo` nodes (which can be slow), I used Maya's **UV Pin** nodes.

This allows me to attach "Micro-Joints" to specific U/V coordinates on the membrane. As the underlying simulation or rig moves the surface, the joints follow perfectly in tangent space.

# Generating UV Pins dynamically
for i in range(u_row):
    for index in range(v_row):
        
        # Normalize the coordinates based on row count
        u_val = float(i) / (u_row - 1)
        v_val = float(index) / (v_row - 1)

        cmds.setAttr(f"{uv_pin}.coordinate[{count}].coordinateU", u_val)
        cmds.setAttr(f"{uv_pin}.coordinate[{count}].coordinateV", v_val)
        
        # Connect to joint offset
        cmds.connectAttr(f"{uv_pin}.outputMatrix[{count}]", f"{joint}.offsetParentMatrix")

Conclusion

By scripting this process, I can iterate on the resolution of the wing (number of micro-joints) without manually rebuilding constraints. The use of Matrix nodes ensures the rig runs at high frame rates, even with the complex deformation layers.

Future improvements could include adding a collision layer to the membrane using simple Euclidean distance checks within the Python script to drive blendShapes.