Particle systems have been a popular topic for TouchDesigner users for a while now, and rightfully so! The simulated physics they can produce generate some amazing visual effects. Up until recently, the only option to generate particles via the Particle SOP was to work with SOPs connected to the Particle Source input. With a recent update, TouchDesigner has added the ability to generate particles via Python (and modify their attributes), which opens up a world of new possibilities for triggering and modifying particle-based effects.
In this article, we’ll dive into the newly added createParticles function and create a cool cyclic particle effect that puts the functionality to use.
Updates to TouchDesigner’s Particle SOP: Generate Particles with Python
First, let’s take a look at the new functionality! Although a somewhat small update focused mainly on a hotfix for the Render TOP, TouchDesigner build 2022.29850 also came with an unsuspecting and super useful feature addition for the Particle SOP!
In the release notes below, a new method has been added to generate particles via Python script without advancing the simulation.
Along with that, the method returns a list of the points it generates, which can then have their attributes modified directly. This is a really exciting addition, allowing much easier manipulation of specific sets of particles as they’re generated. Later in this post, we’ll look at modifying the position, velocity, and color of each particle via Python code!
Getting Started: Basic Rendering Pipeline and Post Effects
Since we’ve got a lot to cover in this post, we’re going to cut right to the chase and get started! Recreate the following basic render pipeline and post effects seen in the image below. The settings for each operator are listed below the image.
Note: any of the following operators with no settings listed are to be left with their default settings. Likewise, any parameters that do not appear below should also be left at their default setting.
- Circle SOP
Radius: 0.05 0.05
- Null SOP
- Constant SOP
- Geometry COMP
- Render TOP
Pixel Format: 16-bit fixed (RGBA)
- Camera COMP
- feedback TOX (from Palette -> Image Filters)
- Constant TOP
Color: 0 0 0
Pixel Format: 8-bit fixed (RGBA)
- Null TOP
With the above operators added and parameters modified, the basic rendering pipeline and post effects are complete! Next, we’ll move on to adding particles.
Particles and Instancing Set Up
Well, you probably expected this one! Of course, if we’re going to create a particle based effect, we’re going to need to add particles. For the particle source, we’re going to add something a little different than usual. Start by adding an Add SOP, and turn the Add Point parameter On. This will add a single point at (0,0,0).
Then, add a Point SOP and connect the Add SOP to its input. We’re going to use the Add SOP to add a color attribute to the point. Change the Color parameter from Keep Color to Add Color. Later on, we’ll be modifying the color of each particle, so it’s important to make sure that we add the color attribute now.
With that complete, let’s add the star of the show: the Particle SOP. Add a Particle SOP to the network and connect the Point SOP to its first input. Then turn Remove Unused Points On and Birth to 0. Most importantly, the Birth setting will disable the Particle SOP from generating particles on its own.
Now, add a Null SOP and rename it to “null_instances”. For our final step in the SOP section of the network, we’ll use null_instances for setting up instancing in the Geometry COMP.
After setting Instancing to On in geo1, drag null_instances onto the Default Instance OP parameter. On the same page, set Translate X, Y and Z, to P(0), P(1), and P(2) respectively. On the Instance 2 page in the color section (near the bottom), set R, G and B to Cd(0), Cd(1) and Cd(2).
That’s it! Now we can move on to using Python to generate particles.
Generate Particles with Python
For this example, we’re going to keep things simple. You can use many things to create trigger signals in TouchDesigner, but one that is incredibly useful for testing (and demonstrating) functionality is the Keyboard In CHOP. Let’s add one to the network. Then, connect the output to a Null CHOP.
Right click on the output of the Null CHOP, and add a CHOP Execute DAT. In the parameter window, turn Value Change Off and Off to On On. Open the DAT’s code in an external code editor by hitting Ctrl + E (Cmd + E on Mac).
Now it’s time to add some Python code! Copy and paste the following code into your code editor.
# colorsys used for HSV to RGB conversion function import colorsys count = 0 def onOffToOn(channel, sampleIndex, val, prev): # use global variable count to enable incrementing global count # Set increment amount inc = 10 n = op('particle1').createParticles(1) for point in n: # Set point velocity using count point.v = [math.cos(math.radians(count)), math.sin(math.radians(count)), 0] # Use hsv_to_rgb to cycle through hues color = colorsys.hsv_to_rgb(math.radians(count / inc) % 1.0, 1.0, 1.0) # Set point color point.Cd = [color, color, color, 1.0] # Increment counter count += inc return
Get Our 7 Core TouchDesigner Templates, FREE
We’re making our 7 core project file templates available – for free.
These templates shed light into the most useful and sometimes obtuse features of TouchDesigner.
They’re designed to be immediately applicable for the complete TouchDesigner beginner, while also providing inspiration for the advanced user.
Generating Particles with Python: Code Overview
Let’s take a look at the code.
As noted at the top, the colorsys library is imported for the HSV to RGB conversion function that is used later. A count variable is set up to allow incrementing between triggers.
# colorsys used for HSV to RGB conversion function import colorsys count = 0
Next, we enter the onOffToOn function. We grab count as a global variable, and then set up a variable named inc, which will be used to increment count. Try adjusting inc to higher and lower values. It will change the amount of rotation between steps.
def onOffToOn(channel, sampleIndex, val, prev): # use global variable count to enable incrementing global count # Set increment amount inc = 10
After that, we use the new createParticles() function. The function has only one argument, which is used to set the number of particles that are generated. Here, it’s set to generate just one particle, but feel free to experiment with larger values. At this point, it’s also important to note that createParticles() returns a list of the particles it generates, which is why we’re storing it in a variable n.
n = op('particle1').createParticles(1)
Since n is a list, we can iterate through it with a for loop, and modify the points that it contains directly. The code is set up to work with multiple points as well. The first modification we’ll make to the point is to change its velocity, by modifying the attribute v. We use sine & cosine functions for the x and y velocity values to generate in the circular shifting we’re looking for.
for point in n: # Set point velocity using count point.v = [math.cos(math.radians(count)), math.sin(math.radians(count)), 0]
Next, we add a new variable color to store color values into. Here, we use the hsv_to_rgb function of the colorsys library to allow us to cycle through hues based on the current count value. The additional math applied to the count value ensures that the value will fall into the correct range for the function.
# Use hsv_to_rgb to cycle through hues color = colorsys.hsv_to_rgb(math.radians(count / inc) % 1.0, 1.0, 1.0)
We then set the point’s color via the Cd attribute. To make sure that the color is always opaque, we use the first three indices of the color variable along with a constant value of 1.0 for the alpha. After that, the counter is incremented and our code is complete!
# Set point color point.Cd = [color, color, color, 1.0] # Increment counter count += inc return
Now, if you hit the 1 key on your keyboard, you should start to see particles generate onscreen and move in the cool cyclical fashion seen below. Every time you hit the 1 key, a new particle should generate with a slightly shifted color and path of movement from the previous particle.
Although this effect just scratches the surface of what’s possible with this new functionality, hopefully it has gotten you excited to experiment further with generating particles via Python! Some simple places to start include different triggering options (from audio, MIDI, sensors, etc.) or additional post effects. But don’t stop there, either — the Python code we worked on has plenty of opportunities for changes as well. Even increasing the amount of particles generated can lead to cool results. Happy experimenting!