Showing posts with label rigging. Show all posts
Showing posts with label rigging. Show all posts

Saturday, October 18, 2025

Calculate Distance Using Non-Uniform Scale

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.

Non-uniform scale does not work

I haven't found much information on this subject; we almost always limit ourselves to making it work only when we use uniform scales. Well, I set myself the task of finding a way to fix this error. I found two ways, both of which require a transformation node to calculate the scale. Here I describe them:

Orient and Scale:
One of the first things that came to mind was to find the orientation between the two points and scale in that orientation, thus preserving the distance. The idea is to find the orientation every time the points move; in other words, we have to apply an aimConstraint to make this work. However, to do this, many DAG nodes would have to be created, which would make it difficult. For this reason, I did everything directly with matrices. 

Connections in node editor

This aimMatrix does not require any input for an upVector, because we are looking the angle between them. We are using in the primary target vector of the aimMatrix (1,0,0), because that will be our primary axis, so in the multiplyDivide node we need to conect the distance in the input1X. It seems a bit long, but it works very well.

Local Space:
This method seems to be the most obvious, and I discovered it after trying the first method. It is quite simple; it basically works by making our distance calculations work in a local space, so you will never have the problem of scale.

Connections in node editor

As with the first method, our points that calculate distance need to be parented to the control. This parent does not necessarily have to be direct; it can have a zeroOut group, but this control is what will adjust the scale. Here is a demo of the first method, but the second should work equally.

Orient and Scale method

That's all, thanks for stopping by to read. Happy measuring!

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. 

Rotation in the secondary axis, no issues

Rotation in the up axis, flipping

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

Let's create another motionPath using the uValueFakeTan on the "U value" attribute. Now we need to obtain the Fake Tangent Vector so to obtain it let's subtract the position of the fake tangent with the base point position using a plusMinusAverage.


After the node "angleBetween" the setup will remain the same as the curve rivet, this variant I call Curve Rivet with Fake Tangents.

Used curve rivet with fake tangents + parametric length
with dual joints (parent + point)

Why not obtain the "real" tangent using PoCI?
I've done many tests about it, but the only thing that we can do to keep using PoCI is using the NPOC (nearestPointOnCurve) to calculate the new position which is provided by the motionPath. That node is very heavy, so is not advisable to use it in large quantities.


Rivet on Beziers?
Sure thing, sometimes we need to do rivets using a Bezier curve, as it's known in Maya we can control the Handle and we can overlap it with the Anchor Point.

However, if we overlap both items, we cannot obtain the tangent using PoCI. For some reason Maya doesn't calculate the tangent of that point, as we see here. This only will happen if our parameter is 0 or 1.


If we want to pin a transform using a Bezier and we know that Handle and Anchor Point will overlap, we cannot use PoCI, the only thing that we can use is Fake Tangents + Parametric Length

I'm sharing the scenes if you want to take a look:

That is the complete setup. I'm showing you the results using the Evaluation Toolkit of Maya. I am using an average PC, not a fancy one. In the scene:
  • 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


As you can see this method is quite functional, it can be used for many things, especially for things that need a lot of range of motion. Just it require a transform to calculate the twist correctly. That's all for now and happy rigging!

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

Hello, I had written this post years ago. This a rewritten version. I have a friend who taught me how to pin a transform on a vertex using a particle emitter. I haven't heard about this method, it could be because it is quite old. However, I going to explain in simplified way using just the geoConnector

As you know there are several ways to make a rivet (4x4 matrix, uvPin, follice, etc.) This method works quite well for pinning a vertex. The only downside is the performance, that's something to bear in mind. These are the steps to follow:
  • 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.
  • We need to make these connections.
  • The rivet setup is complete.
  • 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.
Things to take into account:
  • 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.
That's all for now. Thank you

Tuesday, January 2, 2024

Set Default Tool

Hey there! I am back. First of all. Happy New Year! I hope to keep posting things. 

This post will be brief. Some times we need to set a default values for float or integer attributes on the channel box. Maya doesn't have a tool to do that, you need to do it by cmds. This is particularly useful when the default value is not 0. We can use the next command to query the default value, to reset the attribute.

dValue = mc.addAttr(source + '.' + attr, q=1,dv=1)

mc.setAttr(source + '.' +attr, dValue)

So, I built a simple tool for that. You just need to select a custom attribute float or integer and press the button. The value won't change, but internally the default value will.

The code:

I hope you find this helpful. Thanks

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.

The skeleton hierarchy of half-life 1 models

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.

I am using a compose and an inverse matrix, but you can use pymel or openMaya to make this operation
without this nodes

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.


The general idea is to obtain a global z-translation. For this we create a locator outside the control that will move the tire and another one inside. Then we create a group above the outside locator. We make a pointConstraint from the inside locator to the outside locator. Then we make an orientConstraint from the control that will move the tire to the outside locator group. 

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

I hope you find this useful. Thanks

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

The connections of  the first node of the chain is slightly different, in the "localSpace_MultMatrix" it will go just the first chain and in the node "worldSpace_MultMatrix" in the index [1] it won't need any connection. Just as simple as that. I hope you find it useful.

IKFK switch using matrices from Steffano Richi on Vimeo.


Wednesday, January 2, 2019

Exporter & Importer for ngSkinTools v1

Hi how are you, it's been a while since I've posted anything. After researching and asking a colleague about how to automate the task of importing and exporting layers from ngSkinTools. I managed to import the .json files and place them in the layers. A somewhat easy task, but complicated and tedious if there are several meshes to import.

TO DO:
  • Import Mode: Closest, ID, UV
The code:

Hope you find this useful. Thanks

Monday, July 2, 2018

Transfer Vertex Order

Hi, after destroying several rigs to try to save blendshapes, I discovered how useful can be the tool called "Transfer Vertex Order" to re order vertices. Investigating a little further, it's a tool that they put it in Maya 2017, but in this year's version they improved it quite a bit. It is somewhat difficult to use and in certain cases can cause Maya to shut down unexpectedly. It works as follows.

  • 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

The tool as I said before is somewhat unstable, but it is quite useful when rescuing blendshapes from the same modified geometry.

Tuesday, December 26, 2017

Stretchable Spine - Spline IK with DQ

Hello, I'm back to the blog. This time I will show you how I set the column of a character. As you all know when setting the column, the most common thing to use is the Spline IK Handle. This technique is quite good and animators seem to be used to it. However when we want to make the column stretchable, we have to make two decisions, either work with the translation of the joint or with the scale. I have seen that we usually work with the scale since the calculation is a simple division.


With the translation it would be necessary to make a bigger and more complex calculation. In the case of scaling it works quite well, although I have heard some people say: Joints should not be scaled. I think they said it for this reason.

As we will see the bottom cut is moving down when in fact it should be moving up. Now this may be a skinning issue, however in skin it should not have any kind of influence below the joint. Which is almost impossible to do.


To solve this problem what we should do is to use the scale only as a driver of the translation, but not on the scale of the main axis (usually the X). To be able to do this we would have to relate to each joint of the IK Spline chain another joint. We have two options:

  • Use the parent Constrain.
  • To relate directly in the joint, it would only work if we relate joint to joint.
This only works because when matching joint to joint this automatically connects the scale of one with the inverse scale of the other.
Spine_3_BON.scale --> Spine_3_JNT.inverseScale

Wednesday, November 1, 2017

Reset SkinCluster

Hey, after figuring out if there was a way to somehow reset the skinCluster. I found macaroniKazoo's script that allowed to do it. However in some versions of maya it didn't work. Well I edited it and now it works correctly, plus it automatically detects that skinCluster is attached to the geometry.

The code:

UPDATE: Now works for 2016-2015 version

Saturday, June 24, 2017

Rubik's Cube

I have already encountered the same problem several times. How to rig a rukic cube so that gimbal lock does not happen? Recently with the help of a colleague we created an easy setup, well just for one detail.

We are going to rig the cube with simple controls, obviously pivoting correctly.


To be able to animate correctly we will take all the cube controls and we will set initial and final key. Then we will go to "Graph Editor" and we will look for inside the menu "Curves" the one that says "Change Rotation Interp" and click on the one that says "Quaternion Slerp".


We will see that our curve has become linear and here is the detail. As long as we are in "Quaternion Slerp" all the animation will be linear. But here we have two possible solutions:
  • Draw the curve manually.
  • Extract an Alembic Cache and smooth the movement.

PS: On "Quaternion Slerp" is mandatory to animate on "World"

Final Result