Hello, it's been a while since I've posted anything here, but it's not that I've abandoned it, and I still have several ideas to share. On this occasion, I'm going to share a problem I've been having lately while working with rigs. Calculating the distance between two points is a frequently used operation, and we almost always have to compensate for scale, since animators tend to scale the controls. However, this usually only works if everything is scaled uniformly; otherwise, the distance calculation will be altered.
Saturday, October 18, 2025
Calculate Distance Using Non-Uniform Scale
Wednesday, June 11, 2025
Curve Rivet v2
Hello there! Last year I was battling with this issue, I have always used ribbons for all my setups that require bendy bones. Once an animator told me something related with them. In some axis, on an extreme position, the ribbon breaks and sometimes we don't need to control the twist.
I've been looking for an answer, but I've been told to only use SplineIK or Motion Path or a custom node. Therefore, I wanted to do a simple setup to mimic the behaviour of the SplineIK without the issues that it has. Besides, I got interested in doing rigs setup with less DAG nodes. I came across with the way how Vasil Shotarov does the rivet on NURBS Surfaces without any DAG node.
I've been doing several tests related with this approach. I'm going to explain each method to use a curve to drive transforms, without using custom nodes, and explain its advantages and disadvantages.
SplineIK:
- Customizable a bit tricky ✔
- It has a built-in twist setup ✔
- It's required to know the primary and secondary axis ✖
- It's unstable when you stretch ✖
- Require lots of dag nodes and joints in order to work properly ✖
- Can keep the same distance between transforms ✔
- Very heavy for parallel evaluation ✖✖
MotionPath:
- Very simple and straightforward ✔
- It needs a separate twist setup ✖
- It's required to know the primary and secondary axis ✖
- It's stable when you stretch it ✔
- It has issues related with flipping in the up axis✖
- It doesn't require any dag nodes, except for the worldUpObject ✔
- Can keep the same distance between transforms ✔
- Light for parallel evaluation ✔
IkHandle + PointOnCurveInfo + Extend Curve:
- Very tricky setup ✖✖
- It needs a separate twist setup ✖
- Doesn't required to know any axis ✔✔
- It's stable when you stretch it ✔
- Sometimes it will calculate the rotation incorrectly, I haven't tested it enough ✖
- Require lots of dag nodes and joints in order to work properly ✖
- Cannot keep the same distance between transforms ✖
- Heavy for parallel evaluation ✖
As you can see here each method has its differences, you will need to outweigh each method and use one of them depending on your necessities. However, I recently discovered a way to solve the flipping, instability and it don't require a bunch of dag nodes. This method I'm going to call it Curve Rivet. I'm going to explain how to set it up.
Curve Rivet:
- Tricky setup ✖
- It needs a separate twist setup ✖
- It's stable when you stretch it ✔
- Doesn't have any issues related with flipping ✔✔
- It doesn't require any dag nodes, except for the worldUpObject ✔
- Can keep the same distance between transforms ✔
- Light for parallel evaluation ✔
I'm showing the graph of the setup, which is quite straightforward. There are a few things to take into account:
- The curve driver must be normalized.
- The orientation of the worldUpObj doesn't matter, because it's been compensated.
- The final composeMatrix should use quaternions not euler.
- As it's shown, we need to know the aimOffset. There are several ways to obtain them. I'll show you the method that I use:
import maya.api.OpenMaya as om2 import maya.cmds as mc parent = 'C_tailJ_chains_group_worldSpace_decomMatrix' child = 'C_tailJ_1_ctrl_Pre_eulerToQuat' quatParent = om2.MQuaternion(mc.getAttr(parent+'.outputQuat')[0])
quatChild = om2.MQuaternion(mc.getAttr(child+'.outputQuat')[0])
aimOffset = quatChild*quatParent.inverse()
- If you want to make the setup even more stable, since the tips may flip, you can change the worldUpObject to a previous chain. This I called chainedWorldUp
As you can see is working fine, but you may know that the PoCI (pointOnCurveInfo) cannot keep the same distance between the transforms while we move the curve. We can use a motionPath instead of it. This node has a build-in attribute called "Parametric length", which will do what the PoCI can't. The only drawback of the motionPath is the tangent vector is not obtainable from it. For this reason, I will explain the following:
Fake Tangents:
As I mentioned before we can use a motionPath, but it's mandatory to have a tangent vector to create the rivet, so in this case I am going to use what I called "Fake Tangents" which is basically a point which is very close to the base point. We can make a small script to keep the U Value of the Fake Tangent between 0 to 1.
threshold = 0.05 uValueFakeTan = uValue + threshold if uValueFakeTan > 1:
uValueFakeTan = uValue - threshold
- 201 joints.
- 5 controls, randomly animated.
- A curve or surface depending on the case.
- A transform used as worldUpObj.
- Used the matrix constraint.
- The cycle clusters were removed.
Method |
FPS |
Bare Motion Path |
100 |
Matrix Rivet |
96 |
Curve Rivet |
90 |
Curve Rivet + Fake Tangents |
69 |
Spline IK |
41 |
Thursday, July 18, 2024
Convert ngSkinTools file 1to2
Hi, I wanted to write something small and quite useful. I have been pretty steady using ngSkinTools v1, but it is already a discontinued version.
I'm going to share a small script to convert all the .ma files that are in the first version to the second version. This works for folders and subfolders. According to the internal docs, this only works if you have both versions installed. For this example I will use Maya 2020, which was the last version.
The code:
This won't override the files, it will create a folder where the conversion will be saved in another .ma file. I hope you find this useful!
Tuesday, January 16, 2024
Geo Connector Rivet
- Create a set for a single or a group of vertices, if it's a group the rivet position will be averaged between them.
- We create a locator and a geoConnector node.
- Now, we need to create a vectorArray attribute to the geoConnector to hold a connection. We can called whatever we want.
mc.addAttr(geoCon,ln='ComponentPositionStatic',dt='vectorArray')
- We need to find out the groupID, which was created when we create the set. You can localized in the node editor. Remember to turn on the visualization of the auxiliary nodes.
- However, we have in the outliner a set without any function. Moreover, this depend on the number of rivets that we have in scene. Well, here is the solution for that. Just disconnect and delete in that order.
- If we made everything as it was explained, the rivet will keep working.
- We need to do this after applying all the deformation to our mesh. If we create a deformer after the rivet was created, it won't work when you reopen the scene.
- This method is not parallel friendly, because of the geoConnector. Use it wisely.
Friday, December 29, 2023
Local Rigging vol. 2
This post is the continuation of the previous post, please check it before read this one.
Relative World:
The main idea of this setup is allow you to create groups above the controls and the joints will keep following the controls, but they are not going to follow the controls if they are moved from a certain group above them. I'm going to name each component: the control (source), joint (target), a group above the control (top parent). The top parent is not necessary to be an immediate parent of control. Some notes to take into account:
- The offset is the offset between the source and the top parent.
- In this case I'm not using the parentInvertMatrix of the target, because I don't want to create more cycles for parallel evaluation. Instead I'm turning off the inheritsTransform.
So if everything is done correctly, all the controls will work properly and the groups above them will also work correctly. However if you move the top parent, the joints won't move at all.
This is an alternative method for local rigging. I hope you find it useful.
Sunday, December 10, 2023
Local Rigging vol. 1
Recently, I took the course of Optimizing Rigs for Parallel Evaluation on Rigging Dojo by Charles Wardlaw, a very good course by the way. One of the topics that he explains is the local rig. When I started doing rigging years ago ( I feel old now D: ) I made local rigs for many parts of the face without any control, for the eyebrows, for eyelids, for lips, for mouth, for everything. The main problems that I faced was the normalization of the skinCluster and if you don't have a good control of that, it can be a nightmare to edit the rig. Moreover, when you want to move a control and at the same time the joint, you will need to duplicate the setup on the local rig. Since, I built my own autorig system I tried to avoid that, but the main problem of having the whole setup on a single chain is the performance. Obviously, if you want to export the skeleton to a game engine is mandatory to have the whole system on a single chain, but for films is not required.
I bumped up with the idea of using locals rigs without using direct connections from controls to joints. I discovered two methods that you can use to approach this setup. I divided this into two posts
Reciprocal Driving:
This setup is a bit complex to make, but if you understand it, you will get good results. First of all, you need to make the setup (groups, parents, constrains, etc.) on the local rigging group not in the global rigging. In the global rigging, you will only put the controls and a couple of groups above them. First I will name the transforms that are involved:
- controls = animatable controls
- relative controls = controls on the local rigging
- buffers = group above the animatable controls
The connections will be like this:
1. Direct connection from the control to the relative controls (T,R,S)
2. We will use the parent matrix of the relative controls and we will multiply by itself but static and inverted. The result should be an identity matrix, then we will decompensate the matrix and connect to buffers or use the offsetParentMatrix.
Voilà ! You now have a local rigging setup if you want to create more groups above controls, maybe to control them, just create them above the relative controls. The only downside that I found is this only will work if the all the controls are not parented between them.
In the next post I will explain the second method, which is very different from this one. See you there and happy rigging!
Friday, November 17, 2023
Maya Wheel Rig with Constraints
Sometimes I need to rig vehicles, and one of the things that are done very straightforward is the wheels. For the most part I have used the classic algorithm.
Generally "d" is always or almost always used as a local translation axis, to avoid further complications. However, this does not work very well in all cases. I've seen a video where explain a different method using the same formula. The tutorial was made by Andrew Christophersen. It's quite old, but he uses some good technics like the dot product to obtain "d". Nevertheless this could be simplified using just a couple of constraints.
$autoRot = "global_ctrl.autoRotate"; $rad = 41.8101; wheel_ctrl_expression.rotateX = 360*wheel_out_loc.translateZ/(2*3.1416*$rad);
The result basically will be the same as the video tutorial, but personally I find it easy to understand. A video demonstration.
Sunday, November 12, 2023
IKFK switch using matrices
After all these years, more than 4 years. I've had rough time, but I'm back. Since I started using matrices for all my rigs, I came across the idea of using matrices for switching IK to FK. I've seen other methods to achieve this approach, but require new nodes like pickMatrix or blendMatrix which can be useful, but for this time I'm going to simplify the connections making it simpler.
To blend them I'll use blendColors and pairBlend nodes, to avoid the unitConversion from degrees. Here is the connections in the node editor.
From my point of view is a simple setup. Require 10 nodes to achieve it. It has some rules to follow but still very simple.
Rules:
- BK chain is the Blend Kinematics chain
- The dag node "L_Forearm_FK_CN" is child of "L_Upperarm_FK_CN", same in the IK chain
- The dag node "L_Forearm_BK_NULL" is child of "L_Upperarm_BK_NULL"
- The FK and IK chains don't need to have the same rotation order
- The BK chain needs to be in xyz
IKFK switch using matrices from Steffano Richi on Vimeo.
Thursday, July 19, 2018
Transfer Shader Tool
- First we must have the model with the final shader to be able to pass it to the other model. Where it says "Node Type" we will have to put the name of the shader node that we want to obtain. Example: aiStandardSurface, redShiftMaterial, miaMaterialX, VRayMtl, etc. We can join more than two shader types by separating them with commas. As follows: "aiStandardSurface, redShiftMaterial", without commas.
- Once this is done we press the "Get Faces" button, and then "Export Shader". This will write a couple of files (json. and ma.) in temporary.
- Then we will open the other file we want to transfer the shaders to. Select the meshes and press "lambert1/Unused". This will apply lambert1 to the meshes and delete the shaders it has.
- We will import the shaders that we had exported before, pressing the button "Import Shaders".
- Finally we press "Assing Shader", our model should be with its correct shader. The tool will skip the meshes that do not exist in the target model.
Hope you find this useful. Thanks
Monday, July 2, 2018
Transfer Vertex Order
- First we must verify that both meshes have the same amount of vertices and edges, also we must verify that they have exactly the same topology.
- Make sure you have saved your file, as the tool is a bit unstable.
- Select both meshes and go to component mode.
- Now we go to the menu "Mesh"--> "Transfer Vertex Order.
- Select 3 consecutive vertices or vertices that form a triangle within the source mesh, then select another 3 vertices of the target mesh that are in the same position as the vertices of the source mesh.
- You should see the following message
Tuesday, December 26, 2017
Stretchable Spine - Spline IK with DQ
- Use the parent Constrain.
- To relate directly in the joint, it would only work if we relate joint to joint.
Saturday, September 30, 2017
UV Smoothing
- Move part of the UV so that it does not overlap.
- Try to make the UV cut elsewhere, trying not to overlap.
- Limit the amount of subdivision to be performed, as follows:
After this we go to menu "Modify"->Convert ->Smooth Mesh Preview to Polygons and send it to our painting software.
Saturday, June 24, 2017
Rubik's Cube
- Draw the curve manually.
- Extract an Alembic Cache and smooth the movement.

















































