A script to pin an object to a NurbsSurface in Autodesk Maya
import pymel.core as pm | |
''' | |
Here are some examples of how to use the pin_to_surface.py script. | |
''' | |
# make a nurbsPlane | |
oNurbs = pm.nurbsPlane(n='nurbsPlane1') | |
# You can specify the nurbsSurface by string, PyNode transform or PyNode shape. | |
pin_to_surface('nurbsPlane1', uPos=0.5, vPos=0.5) | |
pin_to_surface(pm.PyNode('nurbsPlane1'), uPos=0.5, vPos=0.5) | |
# To place a range of "follicles", just loop over U or V or both. | |
# Make sure to multiply by the U or V range of your surface. UV does not always go 0 to 1. | |
paramLengthU = oNurbs.getShape().minMaxRangeU.get() | |
numberOfFollicles = 20 | |
for i in range(numberOfFollicles): | |
uPos = (i/float(numberOfFollicles-1)) * paramLengthU[1] | |
pin_to_surface('nurbsPlane1', uPos=uPos, vPos=0.5) | |
# Make a locator and place it somewhere near your surface | |
oLoc = pm.spaceLocator(n='positionLocator') | |
# If you specify sourceObj in the function, it will place the follicle | |
# as close as possible to your sourceObj. It can be any transform. | |
# You can use this feature to place follicles near the joints of a rig, for example. | |
pin_to_surface(pm.PyNode('nurbsPlane1'), sourceObj=oLoc) |
import pymel.core as pm | |
''' | |
Author: Chris Lesage, https://rigmarolestudio.com | |
A script snippet for pinning an object to a nurbs surface in Autodesk Maya. | |
This results in a surface pin, much like a follicle, but I've found | |
follicles to be less stable, sometimes flipping since Maya 2018 or so. Details in this thread: | |
https://tech-artists.org/t/flipping-follicles-in-ribbon-ik/11022/10?u=clesage | |
sourceObj is an optional parameter. If you pass a PyNode object, it will place the "follicle" | |
as close to the object as possible. Otherwise, you can specify U and V coordinates. | |
''' | |
def pin_to_surface(oNurbs, sourceObj=None, uPos=0.5, vPos=0.5): | |
""" | |
This function replaces what I used to use follicles for. | |
It pins an object to a surface's UV coordinates. | |
In rare circumstances follicles can flip and jitter. This seems to solve that. | |
1. oNurbs is the surface you want to pin to. | |
Pass a PyNode transform, NurbsSurface or valid string name. | |
2. sourceObj is an optional reference transform. If specified the UV coordinates | |
will be placed as close as possible. Otherwise, specify U and V coordinates. | |
Pass a PyNode transform, shape node or valid string name. | |
3. uPos and vPos can be specified, and default to 0.5 | |
""" | |
#TODO: Can I support polygons? | |
# Parse whether it is a nurbsSurface shape or transform | |
if type(oNurbs) == str and pm.objExists(oNurbs): | |
oNurbs = pm.PyNode(oNurbs) | |
if type(oNurbs) == pm.nodetypes.Transform: | |
pass | |
elif type(oNurbs) == pm.nodetypes.NurbsSurface: | |
oNurbs = oNurbs.getTransform() | |
elif type(oNurbs) == list: | |
pm.warning('Specify a NurbsSurface, not a list.') | |
return False | |
else: | |
pm.warning('Invalid surface object specified.') | |
return False | |
pointOnSurface = pm.createNode('pointOnSurfaceInfo') | |
oNurbs.getShape().worldSpace.connect(pointOnSurface.inputSurface) | |
# follicles remap from 0-1, but closestPointOnSurface must take minMaxRangeV into account | |
paramLengthU = oNurbs.getShape().minMaxRangeU.get() | |
paramLengthV = oNurbs.getShape().minMaxRangeV.get() | |
if sourceObj: | |
# Place the follicle at the position of the sourceObj | |
# Otherwise use the UV coordinates passed in the function | |
if isinstance(sourceObj, str) and pm.objExists(sourceObj): | |
sourceObj = pm.PyNode(sourceObj) | |
if isinstance(sourceObj, pm.nodetypes.Transform): | |
pass | |
elif isinstance(sourceObj, pm.nodetypes.Shape): | |
sourceObj = sourceObj.getTransform() | |
elif type(sourceObj) == list: | |
pm.warning('sourceObj should be a transform, not a list.') | |
return False | |
else: | |
pm.warning('Invalid sourceObj specified.') | |
return False | |
oNode = pm.createNode('closestPointOnSurface', n='ZZZTEMP') | |
oNurbs.worldSpace.connect(oNode.inputSurface, force=True) | |
oNode.inPosition.set(sourceObj.getTranslation(space='world')) | |
uPos = oNode.parameterU.get() | |
vPos = oNode.parameterV.get() | |
pm.delete(oNode) | |
pName = '{}_foll#'.format(oNurbs.name()) | |
result = pm.spaceLocator(n=pName).getShape() | |
result.addAttr('parameterU', at='double', keyable=True, dv=uPos) | |
result.addAttr('parameterV', at='double', keyable=True, dv=vPos) | |
# set min and max ranges for the follicle along the UV limits. | |
result.parameterU.setMin(paramLengthU[0]) | |
result.parameterV.setMin(paramLengthV[0]) | |
result.parameterU.setMax(paramLengthU[1]) | |
result.parameterV.setMax(paramLengthV[1]) | |
result.parameterU.connect(pointOnSurface.parameterU) | |
result.parameterV.connect(pointOnSurface.parameterV) | |
# Compose a 4x4 matrix | |
mtx = pm.createNode('fourByFourMatrix') | |
outMatrix = pm.createNode('decomposeMatrix') | |
mtx.output.connect(outMatrix.inputMatrix) | |
outMatrix.outputTranslate.connect(result.getTransform().translate) | |
outMatrix.outputRotate.connect(result.getTransform().rotate) | |
''' | |
Thanks to kiaran at https://forums.cgsociety.org/t/rotations-by-surface-normal/1228039/4 | |
# Normalize these vectors | |
[tanu.x, tanu.y, tanu.z, 0] | |
[norm.x, norm.y, norm.z, 0] | |
[tanv.x, tanv.y, tanv.z, 0] | |
# World space position | |
[pos.x, pos.y, pos.z, 1] | |
''' | |
pointOnSurface.normalizedTangentUX.connect(mtx.in00) | |
pointOnSurface.normalizedTangentUY.connect(mtx.in01) | |
pointOnSurface.normalizedTangentUZ.connect(mtx.in02) | |
mtx.in03.set(0) | |
pointOnSurface.normalizedNormalX.connect(mtx.in10) | |
pointOnSurface.normalizedNormalY.connect(mtx.in11) | |
pointOnSurface.normalizedNormalZ.connect(mtx.in12) | |
mtx.in13.set(0) | |
pointOnSurface.normalizedTangentVX.connect(mtx.in20) | |
pointOnSurface.normalizedTangentVY.connect(mtx.in21) | |
pointOnSurface.normalizedTangentVZ.connect(mtx.in22) | |
mtx.in23.set(0) | |
pointOnSurface.positionX.connect(mtx.in30) | |
pointOnSurface.positionY.connect(mtx.in31) | |
pointOnSurface.positionZ.connect(mtx.in32) | |
mtx.in33.set(1) | |
return result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment