I have put together a script that creates the following; A start and end curve shape and a linear curve between. Now what I'm wanting to do is to duplicate and transform the starting curve shape along the path (as depicted by the image), and perform a loft (preferred as would prob give the cleanest result), or alternatively, loft between the two existing curve shapes, and then deform the loft geometry to the curve. For the latter I have tried;
pm.deformer((loftShape, path), type='curveWarp', name='curveWarp#')
without success. The locators are points calculated to generate the correct bezier curve given different distances/ starting angles. I would have thought the hard work was done, but I'm having trouble with this seemingly simple last step.
Below is a method I put together to query curve info:
def getClosestCV(x, curves, tolerance=0.0):
'''Find the closest control vertex between the given vertices, CVs, or objects and each of the given curves.
:Parameters:
x (str)(obj)(list) = Polygon vertices, control vertices, objects, or points given as (x,y,z) tuples.
curves (str)(obj)(list) = The reference object in which to find the closest CV for each vertex in the list of given vertices.
tolerance (int)(float) = Maximum search distance. Default is 0.0, which turns off the tolerance flag.
:Return:
(dict) closest vertex/cv pairs (one pair for each given curve) ex. {<vertex from set1>:<vertex from set2>}.
ex. vertices = Init.getComponents(objects, 'vertices')
closestVerts = getClosestCV(curve0, curves)
'''
pm.undoInfo(openChunk=True)
x = pm.ls(x, flatten=1) #assure x arg is a list (if given as str or single object).
npcNode = pm.ls(pm.createNode('nearestPointOnCurve'))[0] #create a nearestPointOnCurve node.
result={}
for curve in pm.ls(curves):
pm.connectAttr(curve.worldSpace, npcNode.inputCurve, force=1) #Connect the curve's worldSpace geometry to the npc node.
for i in x:
if not isinstance(i, (tuple, list, set)):
pos = pm.pointPosition(i)
else:
pos = i
pm.setAttr(npcNode.inPosition, pos)
distance = Init.getDistanceBetweenTwoPoints(pos, pm.getAttr(npcNode.position))
p = pm.getAttr(npcNode.parameter)
if not tolerance:
result[i] = p
elif distance < tolerance:
result[i] = p
pm.delete(npcNode)
pm.undoInfo(closeChunk=True)
return result
def getCvInfo(c, returnType='cv', filter_=[]):
'''Get a dict containing CV's of the given curve(s) and their corresponding point positions (based on Maya's pointOnCurve command).
:Parameters:
- c (str)(obj)(list) = Curves or CVs to get CV info from.
- returnType (str) = The desired returned values. Default is 'cv'.
valid values are:
'cv' = Return a list of all CV's for the given curves.
'count' = Return an integer representing the total number of cvs for each of the curves given.
'parameter', 'position', 'index', 'localPosition', 'tangent', 'normalizedTangent', 'normal', 'normalizedNormal', 'curvatureRadius', 'curvatureCenter'
= Return a dict with CV's as keys and the returnType as their corresponding values.
ex. {NurbsCurveCV(u'polyToCurveShape7.cv[5]'): [-12.186520865542082, 15.260936896515751, -369.6159740743584]}
- filter_ (str)(obj)(list) = Value(s) to filter for in the returned results.
:Return:
(dict)(list)(int) dependant on returnType.
ex. cv_tan = getCvInfo(curve.cv[0:2],'tangent') #get CV tangents for cvs 0-2.
ex. cvParam = getCvInfo(curve, 'parameters') #get the curves CVs and their corresponding U parameter values.
ex. filtered = getCvInfo(<curve>, 'normal', <normal>) #filter results for those that match the given value.
'''
result={}
for curve in pm.ls(c):
if '.cv' in str(curve): #if CV given.
cvs = curve
curve = pm.listRelatives(cvs, parent=1)
else: #if curve(s) given
cvs = curve.cv
parameters = Init.getClosestCV(cvs, curve) #use getClosestCV to get the parameter location for each of the curves CVs.
for cv, p in parameters.items():
if returnType is 'position': # Get cv position
v = pm.pointOnCurve(curve, parameter=p, position=True)
elif returnType is 'localPosition':
v = pm.getAttr(cv) # local cv position
elif returnType is 'tangent': # Get cv tangent
v = pm.pointOnCurve(curve, parameter=p, tangent=True)
elif returnType is 'normalizedTangent':
v = pm.pointOnCurve(curve, parameter=p, normalizedTangent=True)
elif returnType is 'normal': # Get cv normal
v = pm.pointOnCurve(curve, parameter=p, normal=True)
elif returnType is 'normalizedNormal':
v = pm.pointOnCurve(curve, parameter=p, normalizedNormal=True) #Returns the (x,y,z) normalized normal of curve1 at parameter 0.5.
elif returnType is 'curvatureRadius': # Get cv curvature
v = pm.pointOnCurve(curve, parameter=p, curvatureRadius=True) #Returns the curvature radius of curve1 at parameter 0.5.
elif returnType is 'curvatureCenter':
v = pm.pointOnCurve(curve, parameter=p, curvatureCenter=True)
elif returnType is 'parameter': # Return the CVs parameter.
v = p
elif returnType is 'count': # total number of cv's for the curve.
result[curve] = len(Init.getCvInfo(curve))
break
elif returnType is 'index': # index of the cv
s = str(cv)
v = int(s[s.index('[')+1:s.index(']')])
else:
v = None
result[cv] = v
if returnType is 'cv':
result = result.keys()
if filter_:
if not isinstance(filter_, (tuple, set, list)):
filter_ = list(filter_)
try:
result = {k:v for k,v in result.items() if any((v in filter_, v==filter_))}
except AttributeError:
result = [i for i in result if any((i in filter_, i==filter_))]
if len(result) is 1:
try:
result = result.values()[0]
except AttributeError, TypeError:
result = result[0]
return result
I ultimately decided to use the built-in MASH plugin for this. Perhaps this will be of help to someone in the future.
def duplicateAlongCurve(path, start, count=6, geometry='Instancer'):
'''Duplicate objects along a given curve using MASH.
:Parameters:
path (obj) = The curve to use as a path.
start () = Starting object.
count (int) = The number of duplicated objects. (point count on the MASH network)
geometry (str) = Particle instancer or mesh instancer (Repro node). (valid: 'Mesh' (default), 'Instancer')
:Return:
(list) The duplicated objects in order of start to end.
'''
pm.undoInfo(openChunk=1)
#create a MASH network
import MASH.api as mapi
mashNW = mapi.Network()
mashNW.MTcreateNetwork(start, geometry=geometry, hideOnCreate=False) #MASH_tools module (derived from 'createNetwork')
curveNode = pm.ls(mashNW.addNode('MASH_Curve').name)[0]
pm.connectAttr(path.worldSpace[0], curveNode.inCurves[0], force=1)
pm.setAttr(curveNode.stopAtEnd, 1) #0=off, 1=on
pm.setAttr(curveNode.clipStart, 0)
pm.setAttr(curveNode.clipEnd, 1)
pm.setAttr(curveNode.timeStep, 1)
pm.setAttr(curveNode.curveLengthAffectsSpeed, 1)
distNode = pm.ls(mashNW.distribute)[0]
pm.setAttr(distNode.pointCount, count)
pm.setAttr(distNode.amplitudeX, 0)
instNode = pm.ls(mashNW.instancer)[0]
baked_curves = mashNW.MTbakeInstancer(instNode) #MASH_tools module (derived from 'MASHbakeInstancer')
result=[start]
for curve in reversed(baked_curves):
result.append(curve)
pm.delete(mashNW.waiter.name()) #delete the MASH network.
pm.undoInfo(closeChunk=1)
return result