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.
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.