Colour Variation with the Instancer and Arnold
Using Arnold, it’s extremely easy to add colour variations to particle/ MASH instances. This works in a similar way to the MASH Colour node, which is only available when using MASH in Repro mode (when the output is a mesh). When you’re working with instances, the job of colour variations is down to the renderer.
Here’s how the workflow works in Arnold:
You should now see colour variation across your spheres when you render the scene (remember to add a light!).


Initial State with Visibility
When using Initial State transforms in MASH, it’s possible to also inherit the visibility of those transforms and get them into the MASH network using this simple Python script.
Just a note, this only works with the Transform mode of Initial State (see screenshot).
Script below, I appreciate it could be shorter, but I’m trying to make it obvious what’s happening 😉

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import openMASH import MASH.api as mapi import maya.cmds as cmds #initialise the MASH network data md = openMASH.MASHData(thisNode) #get the MASH Network API object waiter = mapi.getWaiterFromNode(thisNode) network = mapi.Network(waiter) #and this gets the number of objects in the network count = md.count() #get the initial state connections initialStateConns = cmds.listConnections(network.distribute+'.initialStateMatrix') #for every point for i in range(count): #is there an initial state for it? if i < len(initialStateConns): #is this initial state node visible? if cmds.getAttr(initialStateConns[i]+'.visibility'): md.outVisibility[i]=1 else: md.outVisibility[i]=0 #tell MASH to write the network data md.setData() |
Adding Colour to Volume Noise in Maya

Suppose, for whatever reason, you want colourised noise in Maya; there are some 3d shaders that are colour based, like Crater, or Mountain, but if you want a colour version of Volume noise for example, there’s no support for that out the box, so in order to achieve this, you need to remap tones of grey into colour. To do this you simply add a remapColor node.
Here’s an example of how a finished setup would look (this is the setup for the above image):


Using Velocity in MASH

MASH measures the velocity of all the points travelling in a network. To see the speed of your objects you can add a Points node and set the mode to Velocity, you will then see each point’s velocity printed in the viewport:

So how about a practical use for this, well, the Colour node has a build in ‘Use Velocity’ checkbox, which will use an object’s velocity as a multiplier for the colour, so slow objects are dark, and fast objects are bright.
But if you want to move on to something more complex, you need to add the Python node, rest assured, this couldn’t be easier.
The following script will scale objects according to their speed, it was used to produce the above animation.
- Add a Python node
- Copy and paste the script into it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import openMASH #initialise the MASH network data md = openMASH.MASHData(thisNode) #and this gets the number of objects in the network count = md.count() # scale the points depending on the velocity magnitude for i in range(count): md.outScale[i].x = md.velocity[i] md.outScale[i].y = md.velocity[i] md.outScale[i].z = md.velocity[i] #tell MASH to write the network data md.setData() |
The available channels are:
- md.velocity: A list of translational magnitudes (the speed of the point’s movement)
- md.angularVelocity: The same thing but for rotation.
- md.velocityVec: A tuple containing the magnitudes for each axis (x, y, z)
- md.angularVelocityVec: The same thing but for rotation.
Their corresponding output arrays (though I don’t know why you’d want to set these!) are:
- md.outVelocity
- md.outAngularVelocity
- md.outVelocityVec
- md.outAngularVelocityVec
The Papers Behind The World Node

The World Node has three distinct types of distribution.
- Clusters – where points are placed in clusters around other points.
- Map based – where points are distributed according to a density in a map.
- Terrestrial ecosystems – where an ecosystem is simulated.
I’ve been asked a number of times which paper I based the terrestrial ecosystems simulations on, so this post will look into that. The truth is that I read around 20 papers when creating this node (23 to be exact!), but one in particular kicked it all off.
Where it started
It all started when Andrew Camenisch, of Mudbox fame, sent me this paper: Guided Ecological Simulation For Artistic Editing. It’s a great document that talks about the implementation of cutting edge ecosystem simulation algorithms. The algorithms mentioned in that paper can be found here: Ground Cover and Vegetation in Level Editors. This really is an excellent, clear and complete paper which can be summarised, haphazardly, like so:
- Put some seeds on the ground.
- These seeds grow up over time.
- At a certain age they start dropping their own seeds.
- These seeds may or may not survive depending on where they land/ what resources are available.
- The surviving seeds grow up, and at a certain point drop their own seeds.
- Plants can die if they are starved of resources.
- Plants die when they get too old.
This is a major over simplification, but you get the picture. I decided to start doing some experiments on implementing these ideas — done on my commute to work of course — and these produced some really cool results, so I decided to just go ahead and turn it into a fully fledged MASH node.
Additions
In order to provide artistic control over the simulation, and to make it production worthy, it needed a few extra controls, these included:
- Avoidance meshes and curves.
- Id maps – for dictating what type of seed was planted where.
- Maps for soil conditions (such as moisture and soil quality).
- Mesh normal sampling for slope awareness (some genotypes might not like ground that is too steep).
- Sparcity controls.
- Sun/ shade controls.
On top of those there were a few additions that resulted in more realistic outcomes, including using sigmoidal curves for the growth rate and various quadratic curves for resilience and other genotype characteristics.
Almost everything in the above list is optional, meaning you can go from an empty scene to a basic forest in about 30 seconds. All the advanced stuff needs turning on.
Faster, faster, faster
There was however a problem; once you enabled all these things, everything started taking a bit more time then I deemed acceptable – and once I’d optimised all the additional features as much as possible, there was only one place to go, the algorithm itself. So I started experimenting with which bits of the simulation I could approximate, or what, given certain circumstances, could be skipped. The result is what we’ve ended up with in the shipping product, which gives a result of virtually the same quality and is about 2.5x faster then the original algorithm, so while it isn’t an exact implementation of the above paper, you shouldn’t really notice the difference.
Additional reading
To anyone who’s interested in this topic, content in these papers also resulted in code additions/ changes:
Effects of size, competition and altitude on tree growth
David A. Coomes and Robert B. Allen
Extended Competition Rules for Interacting Plants
Monssef Alsweis
Generating Spatial Distributions for Multilevel Models of Plant Communities
Brendan Lane and Przemyslaw Prusinkiewicz



Setting MASH Point positions with JSON
A while ago I was asked by how you can set MASH points using JSON – a fairly specific request, but it’s pretty easy because the MASH Placer node uses a JSON dictionary to store it’s painted points, so we can tap into that to set the positions (and scale, and rotation and Id).
The JSON data is just some nonsense I made up, you can obviously use whatever you’d like here and make this as complex or simple as you’d like.
An alternative approach to this would be using the Python node and translating Python to MASH points in there, the disadvantage of this though is that the Python node recalculates every frame, which is unnecessary for this task (note: you can disconnect the Python node from the Time node to stop this behaviour so as is often the case with MASH, there’s more then one way to do this).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import json import MASH.api as mapi import maya.api.OpenMaya as nom import maya.cmds as cmds # Dummy JSON data from your file JSON = [{"assetName": "CoolAssetName", "matrix": [0.8345780997697541, 0.0, -0.5508896399322704, 386.43025219775876, 0.0, 1.0, 0.0, 1.7867262738341623, 0.5508896399322704, 0.0, 0.8345780997697541, 31148.033345175758, 0.0, 0.0, 0.0, 1.0], "filePath": "path/to/asset/asset.ma"}] # dummy shape for the MASH network dummyShape = cmds.polyCube(h=10) # make a new MASH network mashNetwork = mapi.Network() mashNetwork.createNetwork() # zero points from MASH cmds.setAttr(mashNetwork.distribute+".pointCount", 0) # add a Placer node (this accepts JSON to specify MASH point positions) placerNode = mashNetwork.addNode("MASH_Placer") finalJSON = {"positions":[], "rotations":[],"scale":[], "id":[]} # loop through the imported JSON data for entry in JSON: # nom.MMatrix requires 4 lists of 4 values, rather then just 16 entries as in C++ one = entry["matrix"][0:4] two = entry["matrix"][4:8] three = entry["matrix"][8:12] four = entry["matrix"][12:16] matrixList = [one, two, three, four] matrix = nom.MMatrix([one, two, three, four]) # create a transformation matrix tm = nom.MTransformationMatrix(matrix) # get the transform values position = tm.translation(nom.MSpace.kWorld) qrotation = tm.rotation(nom.MSpace.kWorld) rotation = qrotation.asEulerRotation() rotation *= 57.3 # convert to degrees scale = tm.scale(nom.MSpace.kWorld) # write these values to the JSON finalJSON["positions"].append([position.x, position.y, position.z]) finalJSON["rotations"].append([rotation.x, rotation.y, rotation.z]) finalJSON["scale"].append(scale) finalJSON["id"].append(0) #set the final JSON cmds.setAttr(placerNode.name+".paintJson", json.dumps(finalJSON), type='string') # you should now have a cube, at the origin, slightly rotated. |
MASH & Python & meshes – Part 2: Colour Sets
Following on from part 1, in this example we take things a small step further. Instead of just sampling the mesh for the closest normal to the MASH point, we sample the mesh for the closest point and normal, this call has the added benefit of also returning the closest face to the MASH point. Once we have the closest face we can use the MItMeshPolygon class to give us colour information about that face. I appreciate MFnMesh can also give us the information we want here, I’m just showing one of many ways of doing this.
This is a fairly simplistic example of using colour sets, meshes of course can contain multiple colour sets, and these can be set via methods on MFnMesh so with a little extra work (left to the reader) you can read multiple colour sets if you wish.
This script also includes the normal sampling from the previous script, and as before, you can just copy and paste this Python script onto the Python node and hit the run button, make sure your MASH network has a mesh based distribution, otherwise there will be no mesh to sample.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import openMASH import MASH.api as mapi import maya.api.OpenMaya as om import math # gets a dag path from an object name def getDagPath(nodeName): sel = om.MSelectionList() sel.add(nodeName) return sel.getDagPath(0) # initialise the MASH network data for the Python node md = openMASH.MASHData(thisNode) # get the number of objects in the network count = md.count() # get the MASH network API object waiter = mapi.getWaiterFromNode(thisNode) network = mapi.Network(waiter) # get the input mesh on the distribute node meshName = cmds.listConnections(network.distribute+".inputMesh", sh=True)[0] # get a mesh function set for the mesh on the distribute node meshPath = getDagPath(meshName) meshFn = om.MFnMesh(meshPath) faceIt = om.MItMeshPolygon(meshPath) # we're comparing with up comparisonVector = om.MVector(0,1,0) for i in range(count): # get the closest normal to the point - note, this is SLOW pt = om.MPoint(md.position[i].x, md.position[i].y, md.position[i].z) ptNmlFid = meshFn.getClosestPointAndNormal(pt, om.MSpace.kWorld) normal = ptNmlFid[1] faceId = ptNmlFid[2] # compare angles and turn off points where needed deltaAngle = math.degrees(normal.angle(comparisonVector)) # set the face iterator to the correct face number faceIt.setIndex(faceId) # check the face has colour if faceIt.hasColor(): # get the colour color = faceIt.getColor() # do something with that information if color.r == 1: md.outId[i] = 0 # this isn't needed, 0 is default, but here for clarity if color.g == 1: md.outId[i] = 1 if deltaAngle > 20: md.outVisibility[i] = 0 #tell MASH to write the network data md.setData() |
MASH & Python & meshes – Part 1: Normals
There are many reasons you might want to access mesh information in a MASH network, we’ll take a look at two of those reasons here.
- In part 1 we’ll look at turning points off based on mesh normals.
- In part 2 we’ll look at setting point Ids based on mesh vertex colours.
For the mesh normals, the process is fairly easy, we ask the MASH API to give us the Distribute node of the network, we then ask the Distribute node for the mesh you’re distributing on, once we have that mesh, we query the closest normal to the MASH point. The end goal here is to say ‘if the mesh normal is pointing more then 20 degrees away from up, turn the point off’. In the above images this has the effect of turning the trees off on steep slopes.
Check out the script. You can copy and paste this onto any Python node (make sure your MASH network has a mesh distribution, otherwise there will be no normals to sample!).
The World node has this ‘slope’ filtering built in, but this kind of technique is useful if you aren’t using that node (for whatever reason, e.g. you want to have a completely manual placement via the Placer node, or you want a more random scattering via the Distribute node).
If performance is important to you, I highly recommend investigating the MMeshIntersector class and using that instead of MFnMesh’s closest normal command as the mesh intersector is a lot faster at this task (it takes a bit more setup which would confuse the example, so I haven’t used it here).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import openMASH import MASH.api as mapi import maya.api.OpenMaya as om import math # gets a dag path from an object name def getDagPath(nodeName): sel = om.MSelectionList() sel.add(nodeName) return sel.getDagPath(0) # initialise the MASH network data for the Python node md = openMASH.MASHData(thisNode) # get the number of objects in the network count = md.count() # get the MASH network API object waiter = mapi.getWaiterFromNode(thisNode) network = mapi.Network(waiter) # get the input mesh on the distribute node meshName = cmds.listConnections(network.distribute+".inputMesh", sh=True)[0] # get a mesh function set for the mesh on the distribute node meshPath = getDagPath(meshName) meshFn = om.MFnMesh(meshPath) # we're comparing with up comparisonVector = om.MVector(0,1,0) for i in range(count): # get the closest normal to the point - note, this is SLOW pt = om.MPoint(md.position[i].x, md.position[i].y, md.position[i].z) normal = meshFn.getClosestNormal(pt, om.MSpace.kWorld)[0] # compare angles and turn off points where needed deltaAngle = math.degrees(normal.angle(comparisonVector)) # the angle is in degrees so the result is # "if the difference between this normal and 'up' is more then 20 degrees, turn the point off" if deltaAngle > 20: md.outVisibility[i] = 0 #tell MASH to write the network data md.setData() |
Accessing MASH Point Data with the Maya API
Good news, MASH only uses Maya’s default data types, so all MASH data can be accessed via normal Maya APIs.
Ordinarily this isn’t needed as MASH provides two built in ways to extract data:
- For the non scripter there’s the Breakout node, which can feed MASH data into float/vector attributes in Maya via connections.
- The Python node gives users access to the raw data which they can extract and manipulate in any way they like.
However, for the performance conscious, or for those wanting to write their own MASH nodes or export MASH data in some custom/ game engine format, it might help to know a few things about Maya’s data types and APIs, so here are some simple recipes to get you going. In the following example we’ll get a MASH node (a Waiter), get it’s output data, and print a list of all the channels it contains along with the MASH point positions.
Before we get started, please remember, data getting/setting in Maya should only be done with connections otherwise Parallel Evaluation performance will suffer, so what I’m about to show you should never be run in an expression node.
MASH Data can only be extracted with either the CAPI or PyAPI 1.0. The PyAPI 2.0 isn’t finished yet and thus can’t be used with MASH.
Step 1: Get the MObject for the MASH node.
1 2 3 4 5 6 7 8 9 10 |
import maya.OpenMaya as om # Create an empty selection list sel = om.MSelectionList() # Add the waiter (named "MASH") to the selection list sel.add("MASH") # Create an empty MObject mashNode = om.MObject() # Get the Waiter's node object, and save it to the empty MObject sel.getDependNode( 0, mashNode ) |
Step 2: Get a list of channel data and print it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Attach a function set to the MASH node mashNodeFn = om.MFnDependencyNode(mashNode) # Find the attribte that we want pointsAttribute = mashNodeFn.attribute("inputPoints") # Get the attribute plug pointsPlug = om.MPlug(mashNode, pointsAttribute) # Get the plug data handleData = pointsPlug.asMDataHandle().data(); # Attach an array attribute function set to the handle data inputPointsData = om.MFnArrayAttrsData(handleData) # Get the names of every channel in the data channels = inputPointsData.list() # Print them print channels |
Step 3: Get a specific channel. In C++ getting the channel data type is trivial, however I’ve never managed to do this in Python (it seems the data type enum get’s mangled when the Python API is auto-generated), so a bit of educated guessing on your part may be needed.
A. Double channel eg. Visibility.
1 2 3 4 5 |
# Get the visibility double data and return a copy of it # WARNING NO ERROR CHECHING doubleList = inputPointsData.getDoubleData("visibility")[:] # Print it print doubleList |
B. Vector array, e.g. Position.
First we need a function to copy the MVectorArray into a Python list:
1 2 3 4 5 6 7 8 9 10 |
def channelToList(channel): if channel == None or channel.length() == 0: return if channel.__class__.__name__ == "MVectorArray": listOfVectors = [] for i in range (0, channel.length(), 1): vec = (channel[i].x, channel[i].y, channel[i].z) listOfVectors.append(vec) return listOfVectors |
Now we can use that to get the data:
1 2 3 4 5 6 |
# Get the dynamic array data dynamicArray = inputPointsData.getVectorData("position") # Copy it to a list of tuples listOfVectors = channelToList(dynamicArray) # Print it print listOfVectors |
And you’re done. In C++, everything’s easier, you would check the channel data type first like so and then just copy the array:
1 2 3 4 5 |
MVectorArray position_pp; if (inputPointsData.checkArrayExist("position", dataType) && (dataType == MFnArrayAttrsData::kVectorArray)) { position_pp = inputPointsData.getVectorData("position"); } |
Any questions let me know.
MASH API Examples
Maya 2017 Update 3 includes a new API to help quickly create MASH networks with Python. Below are 4 examples that will hopefully get you started. To use them, copy and paste the Python into the Script Editor, and then run the script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
import MASH.api as mapi import maya.cmds as cmds #new file cmds.file(force=True, new=True) #get the geometry type geometryType = cmds.optionVar( q='mOGT' ) #set the MASH geometry type to Repro cmds.optionVar( iv=('mOGT', 2)) #move the camera cmds.setAttr('persp.translateX', 50) cmds.setAttr('persp.translateY', 48) cmds.setAttr('persp.translateZ', 50) #create a poly cube pcube = cmds.polyCube(w=1.0, h=1.0, d=1.0) #create a new MASH network mashNetwork = mapi.Network() mashNetwork.createNetwork("OffsetMapNetwork") cmds.flushIdleQueue() cmds.setAttr(mashNetwork.distribute + '.arrangement', 6) cmds.setAttr(mashNetwork.distribute + '.gridAmplitudeX', 30) cmds.setAttr(mashNetwork.distribute + '.gridAmplitudeY', 30) cmds.setAttr(mashNetwork.distribute + '.gridAmplitudeZ', 30) cmds.setAttr(mashNetwork.distribute + '.gridx', 30) cmds.setAttr(mashNetwork.distribute + '.gridy', 30) cmds.setAttr(mashNetwork.distribute + '.gridz', 30) signalNode = mashNetwork.addNode("MASH_Signal") cmds.setAttr(signalNode.name + '.positionX', 0) cmds.setAttr(signalNode.name + '.positionY', 0) cmds.setAttr(signalNode.name + '.positionZ', 0) cmds.setAttr(signalNode.name + '.rotationX', 360) cmds.setAttr(signalNode.name + '.rotationY', 360) cmds.setAttr(signalNode.name + '.rotationZ', 360) cmds.setAttr(signalNode.name + '.scaleX', 5) cmds.setAttr(signalNode.name + '.timeScale', 0.2) signalNode.addFalloff() falloffTransform = cmds.listConnections( signalNode.name+".strengthPP" )[0] cmds.setAttr(falloffTransform + '.translateX', 15.7) cmds.setAttr(falloffTransform + '.translateY', 14) cmds.setAttr(falloffTransform + '.scaleX', 25) cmds.setAttr(falloffTransform + '.scaleY', 25) cmds.setAttr(falloffTransform + '.scaleZ', 25) colorNode = mashNetwork.addNode("MASH_Color") cmds.setAttr(colorNode.name + '.color', 1.0,0.188,0.192 , type='double3') cmds.setAttr(colorNode.name + '.enableBackgroundColor', 1) cmds.setAttr(colorNode.name + '.backgroundColor', 0.086,0.247,0.282 , type='double3') signalFalloff = cmds.listConnections( signalNode.name+".strengthPP", sh=True )[0] cmds.connectAttr(signalFalloff+'.falloffOut', colorNode.name+'.strengthPP[0]') colorNodeHotSpot = mashNetwork.addNode("MASH_Color") cmds.setAttr(colorNodeHotSpot.name + '.blendMode', 2) cmds.setAttr(colorNodeHotSpot.name + '.color', 0.626,0.626,0.238 , type='double3') colorNodeHotSpot.addFalloff() hotspotFalloff = cmds.listConnections( colorNodeHotSpot.name+".strengthPP", sh=True )[0] cmds.setAttr(hotspotFalloff + '.innerRadius', 0) falloffTransform = cmds.listConnections( colorNodeHotSpot.name+".strengthPP" )[0] cmds.setAttr(falloffTransform + '.translateX', 15.7) cmds.setAttr(falloffTransform + '.translateY', 14) cmds.setAttr(falloffTransform + '.scaleX', 15) cmds.setAttr(falloffTransform + '.scaleY', 15) cmds.setAttr(falloffTransform + '.scaleZ', 15) #Creating Repro networks puts tasks in the idle queue, flush this so we can playblast cmds.flushIdleQueue() #playblast cmds.playbackOptions( animationEndTime='5sec') cmds.playblast(format="qt", viewer=True ) #restore geometry type cmds.optionVar( iv=('mOGT', geometryType)) |
Morphing from a Torus to a Sphere using a Volume Axis Field for Turbulence. This project is pretty cool as it shows how to use Flight to do something completely unrelated to flocking, it’s essentially being hijacked and turned into a particle system (the align/cohere/separate/ gravitate strengths are all set to 0). The ability to plug a particle field into Flight is also new in Update 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
import MASH.api as mapi import maya.cmds as cmds #new file cmds.file(force=True, new=True) #get the geometry type geometryType = cmds.optionVar( q='mOGT' ) #set the MASH geometry type to Instance cmds.optionVar( iv=('mOGT', 1)) cmds.setAttr( "persp.translateX", 0) cmds.setAttr( "persp.translateY", 30) cmds.setAttr( "persp.translateZ", 65) cmds.setAttr( "persp.rotateX", -25) cmds.setAttr( "persp.rotateY", 0) cmds.setAttr( "persp.rotateZ", 0) ptorus = ptorus = cmds.polyTorus(r=12, sr=2, sa=50) psphere = cmds.polySphere(r=10) #create a poly cube pcube = cmds.polyCube(w=0.1, h=0.1, d=0.1) # Distribute points on a Torus torusNetwork = mapi.Network() torusNetwork.createNetwork("TorusNetwork") shape = cmds.listRelatives(ptorus[0], s=True)[0] torusNetwork.meshDistribute(shape, 1) cmds.setAttr( torusNetwork.distribute+".pointCount", 10000) cmds.select(pcube[0]) # Distribute points on a Sphere sphereNetwork = mapi.Network() sphereNetwork.createNetwork("SphereNetwork") shape = cmds.listRelatives(psphere[0], s=True)[0] sphereNetwork.meshDistribute(shape, 1) cmds.setAttr( sphereNetwork.distribute+".pointCount", 10000) cmds.select(pcube[0]) # Create the Morphing network mergeNetwork = mapi.Network() mergeNetwork.createNetwork("MorphNetwork") cmds.setAttr( mergeNetwork.distribute+".amplitudeX", 0) cmds.setAttr( mergeNetwork.distribute+".pointCount", 10000) #add a World node flightNode = mergeNetwork.addNode("MASH_Flight") #set some attributes on the world node using the .name of the node instance cmds.setAttr( flightNode.name+".displayType", 6) cmds.setAttr( flightNode.name+".seperationStrength", 0) cmds.setAttr( flightNode.name+".alignmentStrength", 0) cmds.setAttr( flightNode.name+".cohesionStrength", 0) cmds.setAttr( flightNode.name+".gravitateStrength", 0) cmds.connectAttr(torusNetwork.waiter+".outputPoints", flightNode.name+".initialState") #create turbulence field vaxis = cmds.volumeAxis( pos=(0, 0, 0), magnitude=0.5, afc=0.0, afx=0.0, arx=0.0, alx=0.0, drs=0.0, trb=0.01, dtr=0.15 )[0] cmds.setAttr( vaxis+".scaleX", 20) cmds.setAttr( vaxis+".scaleY", 20) cmds.setAttr( vaxis+".scaleZ", 20) cmds.connectAttr(vaxis+".message", flightNode.name+".fields[0]") mergeNode = mergeNetwork.addNode("MASH_Merge") cmds.connectAttr(sphereNetwork.waiter+".outputPoints", mergeNode.name+".altInputPoints") cmds.setKeyframe( mergeNode.name, attribute='randEnvelope', t=['1.3sec'], v=1.0 ) cmds.setKeyframe( mergeNode.name, attribute='randEnvelope', t=['2.3sec'], v=0.0 ) springNode = mergeNetwork.addNode("MASH_Spring") cmds.hide(torusNetwork.instancer) cmds.hide(sphereNetwork.instancer) cmds.hide(ptorus[0]) cmds.hide(psphere[0]) #playblast cmds.playbackOptions( animationEndTime='3sec') cmds.playblast(p=100, format="qt", viewer=True ) #restore geometry type cmds.optionVar( iv=('mOGT', geometryType)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
import maya.cmds as cmds import MASH.api as mapi #new file cmds.file(force=True, new=True) #move the camera cmds.setAttr('persp.translateX', 50) cmds.setAttr('persp.translateY', 35) cmds.setAttr('persp.translateZ', 50) #get the geometry type geometryType = cmds.optionVar( q='mOGT' ) #set the MASH geometry type to Repro cmds.optionVar( iv=('mOGT', 2)) #create a poly cube ptorus = cmds.polyTorus(r=20, sr=2, sh=60, sx=400) pcube = cmds.polyCube(w=0.4, h=1.0, d=0.4) #create a new MASH network mashNetwork = mapi.Network() mashNetwork.createNetwork("OffsetMapNetwork") shape = cmds.listRelatives(ptorus[0], s=True)[0] mashNetwork.meshDistribute(shape, 4) cmds.setAttr(mashNetwork.distribute + '.floodMesh', 1) #add a Offset node offsetNode = mashNetwork.addNode("MASH_Offset") cmds.setAttr(offsetNode.name + '.positionOffsetY', 5) cmds.setAttr(offsetNode.name + '.scaleOffset1', 5) cmds.setAttr(offsetNode.name + '.transformationSpace', 2) # add a shader and connect it noiseShader = cmds.shadingNode('noise', asTexture=True) place2dTexture = cmds.shadingNode('place2dTexture', asUtility=True) cmds.connectAttr(place2dTexture+'.outUV', noiseShader+'.uv') cmds.connectAttr(noiseShader+'.outColor', offsetNode.name+'.mColour') cmds.setAttr(noiseShader + '.noiseType', 2) #set come keyframes cmds.setKeyframe( noiseShader, attribute='time', t=['0sec'], v=0.0 ) cmds.setKeyframe( noiseShader, attribute='time', t=['3sec'], v=2.0 ) # add a colour node colorNode = mashNetwork.addNode("MASH_Color") cmds.setAttr(colorNode.name + '.color', 0.859,0.31,0.251 , type='double3') cmds.setAttr(colorNode.name + '.enableBackgroundColor', 1) cmds.setAttr(colorNode.name + '.backgroundColor', 0.239,0.663,0.576 , type='double3') # add a falloff to the colour node colorNode.addFalloff() colourFalloff = cmds.listConnections( colorNode.name+".strengthPP", sh=True )[0] cmds.connectAttr(shape+'.worldMesh[0]', colourFalloff+'.shapeIn') cmds.setAttr(colourFalloff + '.falloffShape', 6) cmds.setAttr(colourFalloff + '.searchRadius', 4) cmds.setAttr(colourFalloff + '.innerRadius', 0) #Creating Repro networks puts tasks in the idle queue, flush this so we can playblast cmds.flushIdleQueue() #playblast cmds.playbackOptions( animationEndTime='3sec') cmds.playblast(format="qt", viewer=True ) #restore geometry type cmds.optionVar( iv=('mOGT', geometryType)) |
Parquet Flooring. There are many ways to do this kind of thing with MASH (I can think of at least 8), this method uses the Symmetry and Replicator nodes. It’s not really that useful, but it might inspire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import MASH.api as mapi import maya.cmds as cmds #new file cmds.file(force=True, new=True) #move the camera cmds.setAttr('persp.translateX', 40) cmds.setAttr('persp.translateY', 25) cmds.setAttr('persp.translateZ', 25) #create a poly cube pcube = cmds.polyCube(w=1.0, h=1.0, d=1.0) #create a new MASH network mashNetwork = mapi.Network() mashNetwork.createNetwork("FlooringNetwork") cmds.setAttr(mashNetwork.distribute + '.pointCount', 15) #add a World node offsetNode = mashNetwork.addNode("MASH_Offset") cmds.setAttr(offsetNode.name + '.rotateOffsetY', 45) cmds.setAttr(offsetNode.name + '.scaleOffset0', 2) symmNode = mashNetwork.addNode("MASH_Symmetry") cmds.setAttr(symmNode.name + '.axisOfSymmetry', 5) mirror = cmds.listConnections(symmNode.name + '.offsetPosition', d=True)[0] cmds.setAttr(mirror + '.translateX', 10.357) cmds.setAttr(mirror + '.translateZ', 2.129) repNode = mashNetwork.addNode("MASH_Replicator") cmds.setAttr(repNode.name + '.replicants', 4) cmds.setAttr(repNode.name + '.offsetPositionZ', -4.258) randNode = mashNetwork.addNode("MASH_Random") cmds.setAttr(randNode.name + '.positionX', 0.0) cmds.setAttr(randNode.name + '.positionY', 0.05) cmds.setAttr(randNode.name + '.positionZ', 0.0) cmds.setAttr(randNode.name + '.rotationX', 4) cmds.setAttr(randNode.name + '.rotationZ', 4) |