api

Using the Maya API to get uniform spacing along a curve

Today, I am going to share a really quick tip of achieving an uniform spacing along a curve.

Disclaimer: If you are not familiar with using the API, worry not, we are looking at a very simple example and I will try to explain everything, but it also might be a good idea to get some understanding of how it all functions. A good place to start is Chad Vernon’s Introduction to the API.

Very often in rigging we need to use curves. In quite a lot of these cases we need to get uniformly distributed positions along that curve. A simple example is creating controls along a curve. Chances are you would want them to be as uniformly distributed as possible, but in order to get that only using the parameter along the curve, you would need a perfectly uniform one that also matches the actual curvature. To get that you would need to do a lot of rebuilding, inserting knots and tweaking.

For another tip on rigging with curves have a look at my post about getting a stable end joint when working with IK splines.

I suppose that if you are doing it by hand then you can easily tweak the position along the curve and eyeball the distances between them to be roughly equal, but it sounds like too much hassle to me and also, more often than not, you would want to have that automated as I could imagine it being integral to a lot of rig components.

Let us have a look then!

The issue

So, I am sure everyone has run into the situation where they’ve wanted to create a few objects positioned uniformly along a `nurbsCurve` or a `nurbsSurface`, but they get this.

Notice how larger the gap is between the joints on the left-hand side than on the right. The reason for that is that the distance between the isoparms is not equal throughout the surface, but the parameter difference is. What that means is, no matter how much we stretch and deform the surface, the parameter difference between the spans is always going to be the same – .25 in our example (1.0 / `spansU`).

That discrepancy between the parameter space and the 3D space is what causes these non-uniform positions.

Getting uniform positions along a curve

So now that we know that, we can figure out that the way to get a reliable position is to find a relationship between the 3D space and the parameter space. That is where the API’s `MFnNurbsCurve` comes handy.

The 3D space information that we are going to be using is the length of the curve, as we know that is an accurate representation of distance along the curve. If you have a look at the available methods in the `MFnNurbsCurve` class, you will find the following one `findParamFromLength`. Given a distance along the curve this function will give us a parameter.

Example

Let us consider the following curve.

Let us position some joints along the curve using distances only based on the `parameter`.

``````for i in range(11):
pci = mc.createNode("pointOnCurveInfo")
mc.connectAttr("curve1.worldSpace", pci + ".inputCurve")
mc.setAttr(pci+".parameter", i * .1)
jnt = mc.createNode("joint")
mc.connectAttr(pci+".position",jnt+".t")
``````

All we do here is iterate 11 times and create a joint on the curve at the position of parameter equal to `iterator * step` where the `step` is `1.0 / (numberOfJoints - 1)`, which is `.1` in our example.

As expected, the non-uniform distance between the CVs results in an also non-uniform spacing of the joints.

Let us try a different approach then. We will get a reference to an API instance of our curve, and using the above mentioned function we will get parameters based on actual distance along the curve, hence getting an uniform distribution.

``````from maya import OpenMaya as om

def getDagPath(node=None):
sel = om.MSelectionList()
d = om.MDagPath()
sel.getDagPath(0, d)
return d

crvFn = om.MFnNurbsCurve(getDagPath("curveShape4"))

for i in range(11):
parameter = crvFn.findParamFromLength(crvFn.length() * .1 * i)
point = om.MPoint()
crvFn.getPointAtParam(parameter, point)
jnt = mc.createNode("joint")
mc.xform(jnt,t=[point.x,point.y,point.z])
``````

So, the `getDagPath` function takes a name of a node and returns an `MDagPath` instance of that node, which we need in order to create the `MFnNurbsCurve` instance. The `MDagPath` is used for many other things in the API, so it is always a good idea to have that `getDagPath` function somewhere where you can easily access it.

Notice we are passing the curve shape node, as if we are to use the `curve4` transform we will not be able to create the `MFnNurbsCurve` instance.

Having that `MFnNurbsCurve`, we iterate 11 times and following the same logic for getting a position along the curve as before – `iterator * step` – we get the `parameter` at that position, using the `findParamFromLength` method.

Now that we know the `parameter` we could still use the `pointOnCurveInfo` as we did before, but considering we are already working in the API we might as well get all the data from there. So, using the `getPointAtParam` method we can get a world space position of the point on the curve at that parameter.

Notice however that we are first creating an `MPoint` and we are then passing it to the `getPointAtParam` function to populate it.

And here is the result.

Using the same approach to get uniform positions on a surface

So, all that `nurbsCurve` business is great, but how can we apply the same logic to a `nurbsSurface`. Unfortunately, the `MFnNurbsSurface` does not have any method resembling the `findParamFromLength` one, but luckily we can always create a curve from a surface.

So in order to get uniform spacing along a `nurbsSurface` what I usually would do is create a `nurbsCurve` from that surface using the `curveFromSurfaceIso` node and using the described method find the accurate parameters and use those on the surface itself.

While writing this I realized that maybe the same approach can be used to actually get an uniform representation of the surface by getting curves from the surface and using them calculating the new, uniformly spaced CVs of the surface. Seems like we might loose a lot of the curvature of the surface, but it also seems promising, so I will definitely look into it.

Conclusion

Using curves and surfaces is something that I did not do a lot of in the beginning of my rigging path, but obviously they are such an integral part of rigging, that it is very important to be able to work with them in a reliable and predictable fashion. Thus, this tip has helped me a lot when building bits of my rigging system and I really hope you find it valuable in your work as well.

Additionally, I would like to reiterate who powerful of a tool the API is and I would definitely suggest anyone who is not really familiar with it to take the plunge and start learning it by using it. The major benefits are not only functional ones (like the one described in this post), but also performance ones, as the API is incredibly faster that anything to do with `maya.cmds`.