Skip to content

Instantly share code, notes, and snippets.

@danbradham
Last active March 9, 2016 20:30
Show Gist options
  • Save danbradham/09c26a3899c384af4970 to your computer and use it in GitHub Desktop.
Save danbradham/09c26a3899c384af4970 to your computer and use it in GitHub Desktop.
Maya nParticle API
import functools
import maya.cmds as cmds
import maya.mel as mel
connect_dynamic = functools.partial(cmds.connectDynamic, delete=False)
disconnect_dynamic = functools.partial(cmds.connectDynamic, delete=True)
def chunk(fn):
def graceful(*args, **kwargs):
value = None
cmds.undoInfo(openChunk=True)
try:
value = fn(*args, **kwargs)
except:
cmds.undoInfo(closeChunk=True)
cmds.undo()
raise Exception
cmds.undoInfo(closeChunk=True)
return value
return graceful
def compatible_types():
return [cls.typ for cls in nObject.__subclasses__()]
def is_compatible(node):
node_type = cmds.nodeType(node)
return node_type in [cls.typ for cls in nObject.__subclasses__()]
class nucleus(object):
pass
class nObject(object):
'''Base class for nDynamic objects. Allows easy connection of nParticle
shapes to fields, colliders, emitters, nucleus.
:method connect: Creates a dynamic connection, shares signature with
cmds.connectDynamic(delete=False)
:method disconnect: Breaks a dynamic connection, shares a signature with
cmds.connectDynamic(delete=True)
:property name: Gets and sets the nParticles name.
:property fields: Returns a list of all connected fields
:property nucleus: Get and set nucleus object for nParticle shape
:methods get and set: Get and set arbitrary attributes on the nParticle
shape
:classmethod find: A generator yielding nBase instances accepts a name
argument supporting wildcards to narrow the returned nObjects.'''
typ = None
def __init__(self, name=None):
if not name:
name = cmds.ls(sl=True)[0]
self._name = name
if is_compatible(name):
self.shape = self.name
self._name = cmds.listRelatives(self.shape, parent=True)[0]
else:
self.shape = cmds.listRelatives(self.name, shapes=True)[0]
if not is_compatible(self.shape):
raise TypeError(
"{0} does not appear to be a compatible type({0})".format(
self.name,
", ".join(compatible_types())))
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
self._name = cmds.rename(self._name, new_name)
self.shape = cmds.listRelatives(
self._name, shapes=True, type="nParticle")[0]
def get(self, attr, default=None):
value = cmds.getAttr("{0}.{1}".format(self.shape, attr))
if value:
return value
if default:
return default
raise AttributeError("Attribute does not exist.")
def set(self, attr, *args, **kwargs):
cmds.setAttr("{0}.{1}".format(self.shape, attr), *args, **kwargs)
def connect(self, *args, **kwargs):
'''Has the same signature as cmds.connectDynamic(delete=False)'''
connect_dynamic(self.name, *args, **kwargs)
return self
def disconnect(self, *args, **kwargs):
'''Has the same signature as cmds.connectDynamic(delete=True)'''
disconnect_dynamic(self.name, *args, **kwargs)
return self
def set_initial_state(self):
'''Reimplement in subclasses'''
def clear_initial_state(self):
'''Reimplement in subclasses'''
@property
def is_dynamic(self):
return cmds.getAttr("{0}.isDynamic".format(self.shape))
@is_dynamic.setter
def is_dynamic(self, value):
cmds.setAttr("{0}.isDynamic".format(self.shape), value)
@property
def fields(self):
return cmds.listConnections("{0}.fieldData".format(self.shape))
@property
def nucleus(self):
return cmds.listConnections("{0}.startFrame".format(self.shape))[0]
@nucleus.setter
def nucleus(self, name):
cons = cmds.listConnections("{0}.outputObjects".format(name))
num_cons = 0 if not cons else len(cons)
cmds.connectAttr(
"{0}.outputObjects[{1}]".format(name, num_cons),
"{0}.nextState".format(self.shape),
force=True)
cmds.connectAttr(
"{0}.startFrame".format(name),
"{0}.startFrame".format(self.shape),
force=True)
@classmethod
def find(cls, name="*"):
nObjects = cmds.ls(name, type=cls.typ)
for n in nObjects:
yield cls(name=n)
class nCloth(nObject):
typ = "nCloth"
class nParticle(nObject):
'''A wrapper for nParticle nodes.
:method set_initial_state: Set initial state of nParticle.
:method clear_initial_state: Clear initial state of nParticle.
'''
typ = "nParticle"
def set_initial_state(self):
cmds.saveInitialState(self.shape)
def clear_initial_state(self):
mel.eval('clearParticleStartState("{0}")'.format(self.name))
@classmethod
@chunk
def fill(cls, *nodes, **kwargs):
'''Fill the selected nodes with nParticles.
returns an nParticle object'''
resolution = kwargs.get("resolution",
kwargs.get("rs", 10))
close_packing = kwargs.get("close_packing",
kwargs.get("closePacking",
kwargs.get("cp", False)))
density = kwargs.get("density",
kwargs.get("particleDensity",
kwargs.get("pd", 1.0)))
double_walled = kwargs.get("double_walled",
kwargs.get("doubleWalled",
kwargs.get("dw", False)))
if not nodes:
nodes = cmds.ls(sl=True, long=True)
before_nparticles = set(cmds.ls(long=True, type="nParticle"))
try:
cmds.select(nodes, replace=True)
cmds.particleFill(
resolution=resolution,
closePacking=close_packing,
particleDensity=density,
doubleWalled=double_walled,
maxX=1,
maxY=1,
maxZ=1,
minX=0,
minY=0,
minZ=0)
except: # particleFill always raises a Maya Command Error, go figure.
pass
p = list(
set(cmds.ls(long=True, type="nParticle")) - before_nparticles)[0]
return cls(name=p)
@classmethod
@chunk
def ring(cls, num_points, r):
'''Create a particle ring!
:param num_points: Number of points in the ring
:param r: Radius of the particle ring.
Returns the name of the nParticle
'''
step_size = 1.0/num_points
two_pi = 2 * math.pi
points = []
for i in xrange(num_points):
x = math.sin((i * step_size) * two_pi) * r
y = math.cos((i * step_size) * two_pi) * r
points.append((x, y, 0))
np = cmds.nParticle(p=points)
return np
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment