Particle Shifting Using a GUI

RealFlow provides a very easy and reliable method for transferring particles from one emitter to another. The combination of a "Container" emitter and "Filter" daemon can be used for this purpose. "Container" and "Filter" are, of course, a convenient alternative, because you can achieve stunning effects without a single line of Python code. Nevertheless it is sometimes necessary to do this within a Python script. Particle shifting can be considered as one of the core concepts with scripting, as it is a versatile and often used method. Particle shifting can be based on several particle properties, like velocity, pressure, age, collision and many more.

 

Name

GUIParticleShift.rfs

Type

Batch and Simulation Events

Description

This script finds particles meeting a certain given criteria and shifts them to a second emitter. Target and source emitters can be meshed and finally rendered separately. Most of the initial settings are  made via a custom GUI.

 

What the script should do:

  • Initialize colours and get all exisiting emitters
  • Create the GUI
  • Assign the variables
  • Determine the source and target emitters, make the settings
  • Define the trigger condition, e.g a velocity above a given value with a random tolerance
  • Loop through the particles, identify the ones with the desired pressure
  • Shift the particles from the source to the target emitter
  • Delete the appropriate particles from the source to avoid stability problems

 

The graphical user interface will query a couple of parameters: source and target emitter, property (age, pressure, etc), trigger value, colour for the target emitter. These values are then stored in variables and finally used to feed the particle shift routine. Additionally the emitters are equalised to prevent stability problems. The GUI part should be executed as a batch script.

The very first step is to define a range of different colours which will be used to dye the target emitter’s particles – source particles keep their default colour. Additionally, the script creates a list from all currently available emitters in your scene. This list is then used to generate a selection in the GUI. The code looks like this:

emitterList = []

colourList = ["Red","Orange","Purple","White","Yellow"]
rgbList    = ((200,0,25),(255,150,0),(180,0,180),(255,255,255),(255,225,0))
emitters   = scene.get_PB_Emitters()

for emitter in emitters:
    emitterList.append(emitter.getName())

 

Except for the rgbList() statement, everything should already be familiar to you. The notation of the rgbList() list entries is different, because each element consists of three values enclosed in parenthesis. This is an exciting feature, because you can nest lists inside a list. The three values here are also called tuples. They are necessary because the RGB model always needs three values to define a certain colour. Since the tuples are lists, it is possible to call the individual elements through their indices, too. That is the complete set of instructions for assembling a colour:

colour   = guiForm.getFieldValue("Colour")
e_colour = rgbList[colour]
rgb      = Vector.new(e_colour[0], e_colour[1], e_colour[2])

 

The selection from the GUI is translated into a variable called colour. Since all selections and choices are translated into numbers, you can directly extract the chosen colour from rgbList[]. The result of this operation is again a list object, containing the three values for R, G and B. Each element of this list has a fixed position and can be accessed via the index. The individual entries are then used to create the colour “vector”. The next part is just basic GUI programming:

guiForm = GUIFormDialog.new()

guiForm.addListField("Source Emitter", emitterList, 0)
guiForm.addListField("Target Emitter", emitterList, 1)
guiForm.addListField("Colour", colourList, 3)
guiForm.addFloatField("Threshold Value", 2.0)
guiForm.addFloatField("Tolerance", 1.0)
guiForm.addFloatField("Speed", 2.0)

if (guiForm.show() == GUI_DIALOG_ACCEPTED):
    emitter1  = guiForm.getFieldValue("Source Emitter")
    emitter2  = guiForm.getFieldValue("Target Emitter")
    colour    = guiForm.getFieldValue("Colour")
    threshold = guiForm.getFieldValue("Threshold Value")
    tolerance = guiForm.getFieldValue("Tolerance")
    speed     = guiForm.getFieldValue("Speed")

 

Finally the emitters are equalized with getParameter() and setParameter() to avoid problems. The most important physical settings are read from the source emitter and then transferred to the target:

# Get the source emitter's physical attributes and set speed to the given value

source       = scene.get_PB_Emitter(emitterList[emitter1])
s_resolution = source.getParameter("Resolution")

source.setParameter("Speed", speed)
source.setParameter("Color", Vector.new(128,128,128))


# Attach colour, parameters and set Speed/Volume to 0.0

e_colour = rgbList[colour]
rgb      = Vector.new(e_colour[0], e_colour[1], e_colour[2])
target   = scene.getEmitter(emitterList[emitter2])

target.setParameter("Color", rgb)
target.setParameter("Resolution", s_resolution)
target.setParameter("Speed", 0.0)
target.setParameter("Volume", 0.0)

 

All the variables defined so far are local. This means that they cannot be transferred to other parts of the script, for example from batch to simulation events. Nevertheless some of the values have to be transferred to the simulation events section. For this purpose, global variables must be introduced, and you first have to find out which values are shared. For "threshold" and "tolerance" it is pretty straight forward, but for the emitters it is slightly different. With emitterList[emitter1] and emitterList[emitter2] it is possible to find out the particular names, for example “Circle01” and “Circle01”. These names are stored in global variables and transferred to the simulation events section.

# Define the global variables
 
scene.setGlobalVariableValue("source", emitterList[emitter1])
scene.setGlobalVariableValue("target", emitterList[emitter2])
scene.setGlobalVariableValue("threshold", threshold)
scene.setGlobalVariableValue("tolerance", tolerance)

# Reset and start the simulation automatically, abort with ESC!
scene.reset()
scene.simulate(0,200)

 

This initial part is finished with the assignment of the global variables. The second task is to find the particles meeting the previously entered velocity threshold. “tolerance” is a random value that will be added or subtracted to get a more natural look. Since “tolerance” works in both directions, only half of the original value will be used. Everything is located under simulation events:

Simulation > Steps > StepsPre > Right click > Add script...


For this script a tolerance value has to be determined. With a given "tolerance" value of 0.5, for example, the final number lies between -0.25 and +0.25:

rndValue = random.uniform(-tolerance/2, tolerance/2)

  

The result is a float number that can be added to the entered "threshold" and then compared against the current particle’s velocity:

if (particle.getVelocity().module() >= threshold + rndValue):
    add the particle to the target emitter
    delete the particle from the source emitter

 

The process of shifting particles between emitters should be familiar to you by now, because this technique has already been introduced and discussed in the chapter “Shifting Particles”. The only new thing here is the call of global values from the batch script section. With these values it is possible to identify the source and target emitter, and make use of "threshold" and "tolerance". Type this part to

Simulation Events > Scene > ScenePre

 

import random
 

# Get the global values from batch script

source_name = scene.getGlobalVariableValue("source")
target_name = scene.getGlobalVariableValue("target")
threshold   = scene.getGlobalVariableValue("threshold")
tolerance   = scene.getGlobalVariableValue("tolerance")
rndValue    = random.uniform(-tolerance / 2, tolerance / 2)

source      = scene.get_PB_Emitter(source_name)
target      = scene.get_PB_Emitter(target_name)
 

# Loop through the particles, compare velocity and shift them to target

particle = source.getFirstParticle()
while (particle):
    if (particle.getVelocity().module() >= threshold + rndValue):
        pos = particle.getPosition()
        vel = particle.getVelocity()
        pid = particle.getId()

        target.addParticle(pos,vel)
        source.removeParticle(pid)

    particle = particle.getNextParticle()

 

If you added the GUI part to a batch window, the simulation starts automatically. This program uses many of the concepts you have read about so far. As you can see from this example it is not only important to be careful with your indents and leading tabs, but also to consider readability. The entire script is grouped and subdivided into functional blocks. This helps you to keep a clear view about the used variables and values. It is also recommended to insert comments about what is currently going on.