Last active
March 9, 2016 20:30
-
-
Save danbradham/09c26a3899c384af4970 to your computer and use it in GitHub Desktop.
Maya nParticle API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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