Sie sind auf Seite 1von 98

waterline magazine

RealFlow Tutorials
by Thomas Schlick | Next Limit Technologies

Scripting

with

RealFlow

Introduction to Python in RealFlow


RealFlow's Scripting Editors
Variables
Data Types
User Interfaces
Coin Stacker Script
Particle Swapping
Colour Blending
Crown Splashes
Export Management
Velocities from Ocean Statistical Spectrum Waves

Contents

Welcome to waterline magazine

Annoations

What Is Scripting?

The Software Development Kit (SDK)

Getting Started

Syntax, Syntax Errors, Indents
Comments
Variables
Naming

Data Types

Integers and Real Numbers
Strings
Lists

Accessing List Elements

Looping Through List Elements

Appending List Elements

Counting List Elements
Dictionaries
Vectors

Vector Maths

Disassembling and Assembling Vectors
Booleans
Operators
Conditions

7
7
8
10
12
13
13
16
16
16
17
17
17
18
18
19
19
21
22
23
23
24

RealFlow's Scripting Editors



Batch Scripts

Simulation Scripts

Where to Add Simulation Scripts?

Basic Workflows

Node-Based Scripts

27
27
28
29
29
30

First Steps - Coin Stacker



Coin Positions

Adding Coins

Changing Parameters

Improvements and Suggestions

32
32
34
34
36

waterline magazine | Scripting with RealFlow

Contents

First Steps - Particle Swapping



Scene Setup

Adding a Simulation Script

Getting Emitters and Particles

The Velocity Condition

Randomizing the Threshold

Improvements and Suggestions

38
38
39
39
41
42
43

First Steps - Colour Blending



Analysing the Workflow

Scene Setup

Initial Variables and Settings

Getting a Particle's Neighbours

Temperature Calculations

Custom-Tailored User Interfaces

Coin Stacker GUI

Other Field Types

Particle Swapping GUI

Local and Global Variables

GUIs and Lists

Translating References Into Names

45
45
46
46
47
48

A Batch Simulation Script



Initial Variables

The GUI

Processing the Input Values

61
61
62
63

Crown Splashes

What the Script Should Do

Force or Velocity?

Scene Setup

Defining a Ring

Accelerating the Ring Particles

Start and End Time

Creating a Splash-Like Shape

Tendril Creation

Finding the Tendril Positions

Circle Maths

Getting the Tendril Particles

66
66
67
67
67
69
70
71
73
73
74
77

waterline magazine | Scripting with RealFlow

53
53
56
56
57
58
58

Contents

Crown Splashes (continued)



Accelerating Splashes and Tendrils
Randomization

Improvements and Suggestions

Axis Setups

79
81
85
86

Export Path Manager



Creating the GUI

Evaluating the Input

Changing the Export Path

88
88
89
90

Velocities from Ocean Statistical Spectrum Waves



Definitions and Considerations

Initial Velocities

Velocity Calculations

Improvements and Suggestions

92
92
93
95
97

waterline magazine | Scripting with RealFlow

Welcome to Waterline Magazine

Welcome to waterline magazine!


Some of you might remember RF_magazine, a commercial special
interest publication for RealFlow users. Although I had to discontinue this magazine I never really gave up the idea of renewing it
one day. Now I am happy to present the first issue. Over the last
years I also started several attempts with plugin development, a
blog, and several free tutorials, but all this did not really satisfy me.
The creation of plugins takes a lot of time, and it is actually a fulltime job. Time, I simply did not have besides my work at Next Limit
Technologies. Anyway, some of these projects and developments
have found their way into this eZine, for example the crown splashes. And there is much more in my stash.
In the tradition of RF_magazine this issue deals with Python scripting inside RealFlow and it is not just a copy of the old scripts and
articles it is completely new.
This magazine is free, but it took me countless hours to complete it. Therefore I love to hear from
you! Just drop me a line if you think this magazine is a good addition for your work. You can also
contact me if you have suggestions for upcoming issues and tutorial, or if you just want to say
hello. I am always happy to get in touch with talented artists from all over the world. Your feedback is my motivation for creating the next issue.
Enough said! Enjoy this first issue of waterline magazine.
Happy reading,

Thomas Schlick

waterline magazine | Scripting with RealFlow

Welcome to Waterline Magazine

Annotations
All tutorials and scripts in this issue have been created with the latest version of RealFlow 2015.
Some of the scripts and simulations can also be used with previous versions, but there is no guarantee that they will really work.

Axis Setup and Scale


RealFlow provides the possibility of defining which axis should be the scenes vertical axis. For
some 3D programs it is Y, for others it is Z. In this magazine, a Y setup is used by default, and for
Z-based setups you have to swap Y and Z values.
An example:

Let's say there is a scale value with three elements, e.g. (1.0,5.0,1.0) a so-called vector.
This means that the object's length and width are 1 m each, while height is 5 m.
With a Z-based setup, the vector must be changed to (1.0,1.0,5.0).
This notation is also interesting for scripts, because the magazine's programs are not axes-aware.
You have to convert all XYZ vectors like position, velocity, force, or scale.
The default scale, used in this magazine, is 1.0.

Sharing waterline magazine


There are just three golden rules please be fair and repsect them:

Do not share the magazine, but provide a link to waterline.tv instead.


If you use the magazine's techniques and tutorials do not sell them as your own ideas.
Do not translate this magazine without my permission.
Thank you!

waterline magazine | Scripting with RealFlow

What Is Scripting?

What Is Scripting?

What Is Scripting
Scripting is the development of custom tailored applications here inside of RealFlow. The scripting language provides a set of statements and functions the programmer can use to solve a certain task.
Scripts are normally interpreted. This means that all instructions are processed at runtime, and
translated into a code the computer can understand You know this from web browsers where the
code (this is the sequence of statements in your program) is often directly visible to the user. At
the moment a visitor calls a webpage, the code is sent to the interpreter and processed. With languages like C++ the code is preprocessed and converted into machine code. A compiled program
is much faster than a script, because the compiler optimizes the code for certain hardware needs.

PY

JS

VBA

C++

RealFlow uses the Python language.


Python is easy to understand, supports all modern programming approaches, and has an active developer
community. It is also free, even for
commercial applications, and you
have access to hundreds of free extensions so-called libraries for
almost any need.

The Software Development Kit


When you download the Python
bundle from the official website, and
install it you will not find a single
RealFlow-related command. Python is
simply not aware of RealFlow and this
also applies to other applications with
Python support. A program requires an extension that will make Python understand these specific
commands. This interface is known as API (Application Programming Interface).

MEL

This API is the basis for the Software Development Kit (SDK). The SDK contains all the application-specific commands and instructions you need for working with Python inside a program.
In RealFlow the list of SDK commands can be viewed under

Help > Scripting Reference

waterline magazine | Scripting with RealFlow

What Is Scripting?

When you expand the Scripting Reference tree you will see a long list of
keywords. Most of them sound familiar, for example Daemon, HY_Bubbles, or Object. Others, like GUIFormDialog, or Vector, are very abstract:
These higher-ranking elements are also called classes. When you click on an
entry you will see a list with commands. The commands are members of the
currently viewed class. Every command provides access to one of RealFlows
functions.
The SDK is your ticket to RealFlows Python interface, and when you make
your first steps with scripting, you will use the reference very often. You will
also be amazed by the number of commands and how deep you can go inside RealFlow to change and influence simulations, nodes, and parameters.

Getting Started
These commands open
a whole new world
and you can literally
do everything, but the
question is how? How
can you do all these
fancy things you have
seen in simulations, or
recreate the stuff you have read about?
Many users have the strong conviction
that it is a heavy task to get started, learn
a new language, and convert ideas into
code. There are also rumours that a deep
knowledge of maths and physics is required. This is exactly the problem that
keeps most artists away from scripting and
programming.
All this is only partially true. Scripts do not
have to be sophisticated constructions with
thousands of lines of code.

waterline magazine | Scripting with RealFlow

Scripted dust particles, emitted from a


breaking object.

What Is Scripting?

Most scripts also do not require higher mathematics and complex functions. In fact, the big majority of scripts contains just a few lines, does not use any maths at all, and has been created to
execute repetitive tasks, and ease your daily life.
Think of scripting as a normal language. Not every sentence has to be a poem, but it is better
to speak in a clear and simple way instead. Another, very important, thing with scripting is the
feeling of success. This feeling is exactly what keeps you motivated and lets you look for more
advanced tasks. With every script you will get more routine, and self-confidence. Of course, there
will also be throwbacks, programs where you are not able to move forward, get stuck, or have to
start from scratch. But these problems will improve your knowledge not only about scripting,
but also about fundamental processes in RealFlow.
Anyway, the first steps are about the nuts and bolts, but I will keep things as easy as possible and
provide examples where ever they make sense.

Syntax, Syntax Errors, Indents


This is perhaps the most scary word for beginners, and the term syntax error is a very good
reason to stop reading, close Acrobat, and trash this magazine. But wait! In Python, many syntax
errors have a very simple reason.
Copy the first example below and start RealFlow:
< py >
for i in range(0, 3):
scene.message("This is a message from waterline magazine.")

Hit F10 to open one of RealFlows Python script editors.


Paste the clipboards content to the editor.
Script > Run.
Look at the Messages panel.

Congratulations! You have just produced your first syntax error and the praised feeling of success
is blown away already. I also want to welcome you to the colourful world of bug fixing ;-)

waterline magazine | Scripting with RealFlow

10

What Is Scripting?

Now, remove the script, copy/paste the code from the next example, and execute it with Run:
< py >
for i in range(0, 3):

scene.message("This is a message from waterline magazine.")

Take a look at the Messages panel, where you will see the scripts output:

The difference between both scripts are the indents and leading spaces, and this is exactly the
reason for the syntax error. Python requires leading spaces and scripts with wrong indents will
always fail. Especially with downloaded or copies scripts you have to be careful, and sometimes it
is necessary to recreate the indents. After every colon (:) you have to add a new indent:
< pseudo code >
if ( condition ):

statement 2

statement 3

statement 4

else:
loop:

if ( condition ):

statement 5
else:

statement 6

statement 7

Quotation marks are another very common source of syntax errors, because Python only accepts
a certain type. If the content between two quotation marks is displayed in red everything is Ok.
With missing brackets it is very similar. Complex commands sometimes have three or four nested
brackets. If one of these brackets is missing you will get a syntax error.

waterline magazine | Scripting with RealFlow

11

What Is Scripting?

Aside from syntax errors and debugging, syntax itself plays an important role, because it is the
logical structure of your program and its instructions. Every command has its own syntax and you
can find it in the SDK, but I will introduce the individual components later with examples.
There is not the one correct syntax. You can build and structure the code the way you want it to
be. There are many programmers out there who say that they really dont care about highly-optimized code as long as their scripts are running. Why not? It should be fun to write a program and
none of us is a professional coder. Of course, there are ways to accelerate a script when you take a
little time and think about it, but this feeling for the code is something that will come automatically with more practice, and also by analysing scripts from users.

Comments
Comments are one of the most important code elements, because they will help us to keep our
scripts readable and understandable even after months and years. A comment is a description
and you should add them where ever you can. They are ignored by the Python interpreter and
therefore they do not have influence on the scripts execution time. This also means that it is not
possible to declare variables inside a comment, perform calculations, assemble vectors, etc. No
matter whats inside a comment it will not be processed.
A single-line comment is introduced this way in RealFlow comments are printed in green:
# This is my first comment

It is possible to add an unlimited number of comments:


# This is my second comment,
# and it covers three lines,
# not more.

Another form are multi-line comments:


"""

This is my third comment.

It also covers three lines,

but this time it is a multi-line comment

"""

If the scripts last line is a comment you will receive a syntax error.

waterline magazine | Scripting with RealFlow

12

What Is Scripting?

Variables
Here is the next reason for headaches: variables. Fortunately, the concept behind variables is very
easy to understand. What make more users really desperate is the large number of different
variable types and the contents they can store. Basically, a variable is nothing more than a placeholder that can be filled with a value. Think of your garage as a variable. For the garage itself it
makes no difference whether you put a car, a bicycle, or your old furniture inside. But, of course it
makes a huge difference for you, and you will see it when you try to ride a cupboard instead of a
bike. With variables it is very similar, because the variables content determines its role and how it
will be treated in the script.
Variables have to be declared before they can be used within the program this is also valid if the
variables value is 0 or if it is empty at the beginning of the script.

Naming
Lets start with a simple example with three values: 27, female, Claudia. What we have to do is to
set the values into a logical context to make use of them:
Age

Name

= 27

= "Claudia"

Gender = "female"

Now, the attributes make sense and you have just declared three variables. You can also see that
meaningful names are very important. The following variable names are valid, but it is very difficult to differentiate them within a more complex script as shown here:
A = 27

B = "Claudia"

C = "Beatrice"
D = 56

Here it is difficult to find out what C and D actually stand for. Do they perhaps describe another
person or something else? Here is the solving:
Age

Name

= 27

= "Claudia"

SecondName = "Beatrice"
Weight

= 56

waterline magazine | Scripting with RealFlow

13

What Is Scripting?

It is important that a variables name does not change during the script. Even lower and upper
cases are differentiated. Age and age are not the same, but completely different variables.
A variables value, on the other hand, can change during the execution of the script, because
otherwise it is not possible to calculate with variables and get a result.
A few more notes on naming:

Allowed characters are A-Z, a-z, 0-9, and the underscore.


Variables must start with a number, not a character
Python and RealFlows SDK commands cannot be used a variable names.
When you perform a calculation or reassign the variable somewhere else in the script the old value will be overwritten:
Name = "Claudia"
Name = "Lydia"

If the variables value is printed the output will be Lydia.

waterline magazine | Scripting with RealFlow

14

Data Types

Data Types

Data Types
It is true that the following explanations are not exactly exciting. Discussions about integers, lists,
or strings are very theoretical, but they are absolutely essential. They are also important for the
creation of custom GUIs, where we can assign initial values.
In the previous chapters you have already used two data types: numbers and strings. A string can
be a single character, but also a word, a sentence, or complete text from a book. Numbers, on the
other hand, are subdivided into several classes like integers, reals, or complex numbers.

Integers and Real Numbers


When you take another look at the first example you will spot a difference in how the values are
written:
Age

Name

= 27

= "Claudia"

Gender = "female"

Age is treated as a number, or to be more precise: an integer, while Name and Gender are strings.

Integers are numbers like 64, 8524, -9610, or 7. When you perform calculations with integers
then the result will always be an integer. Real numbers are, for example, 3.1415, -0.758, 43.2, or
-416.0003. This type is also called floats. When two real numbers are combined you will get a real
number as well.

Strings
Strings are always enclosed within quotation marks, and this also applies to numbers, as shown in
the next example. Here, the numbers will be treated as strings, and one way to combine them is a
process called concatenation. The operator is a + sign and the result is a new string:
insuranceNumberClaudia = "1100"
insuranceNumberLydia

= "2200"

insuranceNumberClaudia + insuranceNumberLydia = "11002200"

If you want to treat these numbers as integers you have to remove the quotation marks:
insuranceNumberClaudia = 1100
insuranceNumberLydia

= 2200

insuranceNumberClaudia + insuranceNumberLydia = 3300

waterline magazine | Scripting with RealFlow

16

Data Types

Lists
Scalars can only store a single value, but we also need structures for multiple, maybe even millions of values. A good example are RealFlow particles. How can we access these huge amounts of
data? For this purpose Python provides so-called lists. A lists elements are always enclosed between brackets, and an empty list is declared as:
emptyList = []

Here is an example with numbers:


numberList = [10,20,30,40,50,60,70,80,90,100]

Strings are also supported, but they must be enclosed within quotation marks:
stringList = ["this","list","contains","six","string","entries"]

Accessing List Elements


An individual element can be accessed via an internal index. This index always start with 0. In
stringList we have seven elements, and the index ranges from 0 to 5:
Value

this

list

contains

six

string

entries

Index

Now we can use the index to access an element directly, e.g. contains:
listElement = stringList[2]

The result of this operation is a single value, but a list with just a single element will remain a list.

Looping Through List Elements


In the example above we have extracted a single element, but this is not a very efficient method with hundreds of thousands of entries. Therefore we need an instruction that will return the
individual elements automatically. The following code snippet is a loop and it is one of the most
fundamental actions with RealFlow and Python (see next page).

waterline magazine | Scripting with RealFlow

17

Data Types

Always mind the leading tab. Without it you will get a syntax error and the script will not be executed):
fruitList = ["peach","apple","apricot","pineapple","orange","strawberry","mango"]
for element in fruitList:

print the elements name

Appending List Elements


New elements are simply appended to an existing list as shown here:
fruitList = ["peach","apple","apricot","pineapple","orange","strawberry","mango"]
newFruit = "lemon"

fruitList.append(newFruit)
Result: fruitList = ["peach","apple","apricot","pineapple","orange","strawberry","mango","lemon"]

Counting List Elements


Another, often used, action is to count a lists number of elements also called length and compare the result against a threshold. This way it is possible to trigger events within the script. If we
want to know the length of the last version of fruitList we have to use the following command:
numberOfListElements = len(fruitList)
Result: 8

Python provides many other functions and commands in conjunction with lists, but they are not
really relevant for an introduction to scripting. It is, for example, possible to use lists within a list
and such an element is called a tuple. A typical application for tuples is a list with a polygons vertices, but they are also often used for pairs of values, or complete data sets. Imagine you want to
store a particles Id and connect it with attributes like age and mass:
tupleList = [(0,0.38,0.0004),(1,0.32,0.0004),(2,0.37,0.0004),(3,0.33,0.0004)]

waterline magazine | Scripting with RealFlow

18

Data Types

Dictionaries
This variable type is comparable to a phone book. In contrast to lists, dictionaries do not require
a specific order and there is no index for identifying an element. A dictionary uses key-value pairs
instead. Similar to lists, dictionaries are declared with brackets:
emptyDictionary = {}

Here is an example with names and phone numbers:


phoneBook = {"Claudia":54388,"Lydia":43891,"Agnes":75064,"Susan":97247}

The first entry of each pair is the key and it can be used to identify its associated value:
phoneNumber = phoneBook["Agnes"]
Result: 75064

As with lists, the number of key-value pairs is queried with the len command:
numberOfPairs = len(phoneBook)

And here is also how to add new key-value pairs:


phoneBook["Helen"] = 22972

Vectors
In 3D programs, vectors play a fundamental role: they are used to describe positions, dimensions,
velocities, rotations, but also directions, and many other attributes. In RealFlow, a vector is always
described through a trio of three values one for each spatial direction (XYZ). You can find some
of the most important RealFlow vectors under a scene element's Node panel:

waterline magazine | Scripting with RealFlow

19

Data Types

Velocity is a very good example for vectors, because we all have an idea of what it is. But, when
we think about velocity and speed we normally have something like 10.6 m/s in mind, not a vector
consisting of three values. Let me explain you the backgrounds and start with a velocity vector:
velocityVector = (6.5,1.6,8.2)

What we have a here are values for X, Y, and Z. In a variable-based notation, the vector can also
be written as:
velocityX = 6.5
velocityY = 1.6
velocityZ = 8.2
velocityVector = (velocityX, velocityY, velocityZ)

As you can see, the individual elements are single values so-called scalars:

The XYZ values are coordinates and determine a point in space.


Now draw a line from the 3D system's origin at 0,0,0 and the velocityVector's coordinates.
The direction of this line is indicated by an arrowhead.
Here is an illustration of an arbitrary 2D vector:

Y
< x0,y0>

x0
de

tu
ni

ag

t=

h
ng

le

X
y0

What we got now is an arrow, and this is the standard representation for vectors. The arrow
does not only show the vector's direction, but its length has a certain meaning. If you measure
the length you will roughly get 10.6.

waterline magazine | Scripting with RealFlow

20

Data Types

So, a velocity vector's length, also known as module or magnitude, is what we call speed in daily
life. Now you know what a vector's magnitude is, but you do not know how to get this value or
how to use it. The calculation of the magnitude is done with a built-in function inside RealFlow:
vectorMagnitude = vector.module()

What we get here is again a scalar and this value can be used for comparisons, e.g. for defining
thresholds. It is not possible to compare two vectors against each other, and you cannot compare
a scalar against a vector. The following example notations are not allowed:
if (velocityVector > positionVector)
if (velocityVector == 2.5)

We always need two scalars as the example below illustrates:


velocityVector = (6.5,1.6,8.2)
velocityThreshold = 5.0

velocityMagnitude = velocityVector.module()
if (velocityMagnitude >= velocityThreshold):

do something

Vector Maths
The headline really spells trouble, but fortunately RealFlow will do all the maths for us. We can
calculate with vectors in pretty much the same way as with numbers. The difference to numbers is
that the result is a new vector.
vector1 = (x1, y1, z1)
vector2 = (x2, y2, z2)

Addition

vector1 + vector2

Subtraction

vector1 vector2

Division

vector1 / vector2

Vector multiplication has to be performed manually, because the * operator is reserved for another function the dot product (see table next page):
vector1 * vector2 = (x1 * x2, y1 * y2, z1 * z2)

waterline magazine | Scripting with RealFlow

21

Data Types

There are also more complex functions available, and some of them only require one vector:
Cross product

vector1.cross(vector2)

Returns a vector that is perpendicular to vector1 and


vector2.

Dot product

vector1 * vector2

Returns a single value a scalar.

Distance

vector1.distance(vector2)

Returns a single value a scalar.

Normalization

vector1.normalize()

Returns a vector with a magnitude of 1.

Magnitude

vector1.module()

Returns the length of the vector.

Scaling

vector1.scale(factor)

Multiplies each component with the factor in brackets and returns a vector.

Disassembling and Assembling Vectors


In this last chapter about vector we want to take a closer look at how to disassemble a vector and
get its XYZ components, but also how to combine three values to finally get a new vector. Let's
stay with our already introduced velocityVector and extract the individual elements:
velocityVector = (6.5,1.6,8.2)
velocityX

= velocityVector.getX()

velocityZ

= velocityVector.getZ()

velocityY

= velocityVector.getZ()

A different notation is:


velocityX = velocityVector.x
velocityY = velocityVector.y
velocityZ = velocityVector.z

With these separated values we are able to perform any operation, swap components, or leave
certain elements untouched. We can do comparisons between another scalar and a single element, and so on. Once the components are defined they can be used to create an new vector:
newVelocityVector = Vector.new(velocityNewX, velocityNewY, velocityNewZ)

This notation is also often used to introduce a standard vector, e.g. a null vector or a reference
vector:
nullVector

= Vector.new(0,0,0)

referenceVector = Vector.new(0,1,0)

waterline magazine | Scripting with RealFlow

22

Data Types

Booleans
This is the last data type we have to discuss, and fortunately it will not take very long. With
Booleans we can differentiate between true and false. These states represent yes and no, and
they are used like a switch. A typical field of application is a script where the user is asked whether the scene objects' active rigid body property should be enabled or not.
Another common application is the creation of so-called flags. A flag indeed acts as a switch and
can be used to trigger events. RealFlow's Visibility option is another example for Booleans. Instead of the option's Yes and No states, we have to use true and false.

Operators
Operators are necessary to perform calculations and comparisons. Here are the most important
arithmetic operators:
Addition

15 + 17 = 32

Subtraction

64 - 30 = 34

Multiplication

10 * 12 = 120

Division

80 / 10 = 8

Exponentation

**

12 ** 2 = 144

Modulus

28 % 7 = 0

String concatenation

"John" + "Doe" = "JohnDoe"

String repetition

"Hi" * 3 = "HiHiHi"

There are also operators for incrementing an existing value. Here, the old/existing value is stored
and we can add a new value, for example. In the next cycle this result will be used as the old value, and the increment is added again:
+=

value = value + increment

-=

value = value - increment

*=

value = value * increment

/=

Value = value / increment

waterline magazine | Scripting with RealFlow

23

Data Types

A list with comparison operators:


Less than

<

Greater than

>

Less than or equal to

<=

Greater than or equal to

>=

Equal

==

Not equal

!=

Finally, there are Boolean operators:


and
or
and

Conditions
Conditions are comparable to decisions. In programs this decision is made by comparing one or
more values with an operator. A condition is introduced with an if statement:
if (particleVelocity > 5.0):

do something

It is also possible to compare against more than one value. This is often used to define ranges:
if (particleVelocity >= 5.0 and particleVelocity <= 10.0):

do something

Another way is to perform the comparison with the or operator:


if (particleVelocity <= 2.0 or particleVelocity >= 5.0):

do something

You can also define an alternative case or trigger another event with the else statement:
if (particleVelocity < 5.0):

accelerate particle

stop particle emission

else:

waterline magazine | Scripting with RealFlow

24

Data Types

Sometimes you have to check for an exact value:


if (currentTime == 2.5):

do something

Even calculations can be performed within in a condition:


if (particleVelocity > velocityThreshold + randomVariation):

do something

Comparisons with Boolean variables are also allowed quotation marks are not allowed here:
if (objectVisibility == True):

do something

waterline magazine | Scripting with RealFlow

25

RealFlow's
Scripting Editors

RealFlow's Scripting Editors

RealFlow's Scripting Editors


Editors? Python is a universal programming language and why do we need several editor types to
write scripts? Despite the universal approach the various applications with Python support have
to follow the given syntax. RealFlow is now exception, but it makes a huge difference when and
where a script is executed. Scripts can be run offline to make initial adjustments, create objects
and scene nodes, change parameters, or open a GUI. Other scripts are used to manipulate fluids
or the path of objects, and we can write our own daemons and RealWave deformers.
RealFlow provides different editors, but the syntax, rules, and conventions are the same throughout all editors.

Batch Scripts
This script type is mainly used for repetitive
tasks. Typical applications are:

Changing large amounts of parameters.


Creating GUIs for initial user settings.
Conversions, e.g. when animation curves
are translated into splines.
Building stacks, towers, or walls.
Creating and manipulating objects.
Setting up scenes with initial parameters.
Manipulating export resources.
Normalization of parameters.
And many more.
In order to write and run those scripts you
have to use RealFlow's Batch Script editor:

Layout > Batch Script (F10)


You can open and use as many batch script
editors as required. Batch scripts can also be
attached to RealFlow's shelves and even embedded to use them in the same way as built-in commands.

waterline magazine | Scripting with RealFlow

27

RealFlow's Scripting Editors

Simulation Scripts
As the headline indicates, these scripts are created to influence simulations. One of the bestknown applications with this script type is particle swapping to simulate foam, but that is by far
not everything. Here a few more suggestions:

Melting, deforming, freezing.


Foam creation on RealWave surfaces.
Wet-dry map creation.
Splash creation.
Particle tracking.
Emission of dust particles.
Colour blending between particles.

Simulation scripts are executed within RealFlow's Simulation Flow environment:

Layout > Simulation Flow (Ctrl/Cmd + F2)


Here you will find an events tree and scripts are attached to the tree's branches, e.g. FramesPre
or StepsPost:

Right-click on an event, e.g. "FramesPre", and choose Add Script.


RealFlow adds a new tab with a Python editor.

waterline magazine | Scripting with RealFlow

28

RealFlow's Scripting Editors

You can also see a Master tab with several entries and functions. This tab is only there for compatibility reasons with RealFlow 4. Many old scripts still use the Master tab's structure, but it is
really outdated. New scripts should always be applied to the events tree.

Where to Add Simulation Scripts


The most common question with simulation scripts is where to add them? With SimulationPre
and SimulationPost it is pretty clear: these sections will only be executed once at the beginning
and at the end of a simulation. SimulationPre is often used to initialize variables, add certain
properties to particles (e.g. temperature), or gather objects for later usage. Scripts, located under
FramesPre and FramesPost are executed once at the beginning or at the end of a frame during simulation.

When your script is executed under StepsPre or StepsPost it will be executed once per substep. RealFlow uses adaptive substeps, so in the worst case, it might happen that the script is executed several hundred times per frame. With large amounts of particles or complex calculations a
simulation will become very slow.
It is therefore a good idea to execute your scripts in the frames section whenever possible at
least for testing purposes. If the result does not satisfy your requirements you can shift the script
to StepsPre or StepsPost for increasing precision.

Basic Workflows
You can add as many scripts as required. Just right-click on the appropriate event, and choose
Add Script. If you have scripts stored on your hard disk or network drives it also also possible
to load them with Add Script From File.
The checkboxes allow you to activate and deactivate a script on demand, and scripts can be shifted from one event to another with drag and drop.

waterline magazine | Scripting with RealFlow

29

RealFlow's Scripting Editors

Node-Based Scripts
RealFlow provides the possibility of creating your own RealWave deformers, standard particle
fluid solvers, and custom daemons. Each node has its own scripting editor and there you will find
several predefined sections. These sections have names like def applyForceToBody( body ) or def
computeInternalForces( emitter ), and they tell you where you have to put your script in order
to influence a certain node type. If you want to write a

daemon for MultiBodies put your script under def applyForceToMultiBody( multibody ).
custom wave deformers use def updateWave( vertices, initPositions ).
etc.
The individual editors can be found here:
Scripted daemon
Scripted RealWave deformer
Scripted standard particle fluid

Daemons shelf > Scripted > Node Params > Scripted > Edit
RealWave node > right-click > Add wave > Scripted
Emitter node > Node Params > Particles > Type > Script

waterline magazine | Scripting with RealFlow

30

First Steps
Coin Stacker

First Steps Coin Stacker

Getting Started - Coin Stacker Script


Different programs have different axis setups: some applications use the Y axis as the vertical axis,
others have a Z setup. RealFlow provides functions for detecting the scene's axis setup, but these
methods will be addressed later. For now, please change RealFlow's axis setup to
File |RealFlow > Preferences... > General > Axis setup > YXZ (Lightwave, cinema 4D)
Enough theory! Let's start with something practical and write a batch script where we build a
tower made of coins. This appears to be a manageable task and we can go ahead with our variable definitions. In this first version we need the following information:

Number of coins
Coin's height (thickness)
Coin's diameter
Open the Batch Script editor with F10 and type:
< py >
numberOfCoins = 7
coinHeight

coinDiameter

= 1.0
= 1.5

These variables can be adjusted to our needs and changed freely, but for our basic considerations
it is better to have something catchy. Are you able to determine the variables' data types already?

Coin Positions
When a new object is added to a scene it is positioned at the scene's origin at < 0,0,0 >. When
cylinders are created they are placed at exactly the same position, but we want to stack the coins.
Therefore we have to find out the vertical position for each individual coin.
The first coin should be aligned with the scene's horizontal XZ plane (mind the axis setup!), and
the others will be stacked on top of this object. Our task is now to find a vertical offset here:
along the Y axis that will be applied to the coins.

waterline magazine | Scripting with RealFlow

32

First Steps Coin Stacker

An illustration will help us to find this offset:

7.0
6.5
6.0
5.5
5.0
4.5
4.0
3.5
3.0
2.5
2.0
1.5
1.0
0.5

1.0

From the illustration we can see that there is a common calculation rule:
offset = (coin number - 1) * 1.0 + 0.5

Let's check if this is formula is correct:


Coin #1

Coin #2

Coin #3

Coin #4

Coin #5

Coin #6

Coin #7

0 * 1.0 + 0.5

1 * 1.0 + 0.5

2 * 1.0 + 0.5

3 * 1.0 + 0.5

4 * 1.0 + 0.5

5 * 1.0 + 0.5

6 * 1.0 + 0.5

0.5

1.5

2.5

3.5

4.5

5.5

6.5

The vertical positios we get here are exactly the same as in the illustration. But what do the values
1.0 and 0.5 represent? They stand for coinHeight and coinHeight / 2. Now we have everything
for a variable-based formula:
offset = (coinNumber 1) * coinHeight + coinHeight / 2

waterline magazine | Scripting with RealFlow

33

First Steps Coin Stacker

Adding Coins
In the next steps, the coins are created. We will position the coins using our offset formula and we
also have to scale them accordingly. For this task a loop is added something that has been introduced in the chapter about lists. There, a loop was used to go through a lists individual elements.
Although we do not have a list here, the concept is the same. A loop is introduced this way:
for currentNumber in range(start, stop):

A good idea is to start the loop with 0, not 1, because this has advantages:

List indices always start at 0 and this makes it easier to find a specific elements.
Particle Ids also start with 0.
Therefore we can replace (coinNumber 1) through coinNumber.
< py >
for coinNumber in range(0, numberOfCoins):

coin = scene.addCylinder(50,1)

This code segment will add numberOfCoins cylinders. As you can see the cylinder object is stored in
a variable named coin. With each cycle of the loop coin will be overwritten and contains the next
cylinder. The good thing is that RealFlow does not just store the object, but we also have direct
access to all of its properties, like position and scale.
The numbers in brackets 50 and 1 are called arguments, and define the cylinder's facets and
number of height segments. This way it is possible to increase the cylinder's resolution and make
it smoother. 50 facets are a good value.

Changing Parameters
It is time to create the coins' position and scale vectors. This means that we have to replace the
cylinders' default parameter settings with our calculated values. For this purpose, RealFlow provides a very convenient method to access any parameter of an object. It is possible to read a value
and overwrite it. The commands are:
getParameter(name)

setParameter(name, value)

waterline magazine | Scripting with RealFlow

34

First Steps Coin Stacker

The name argument is just the parameter's name as it appears in a node's Node Params sections.
Since a parameter's name is a string it has to be enclosed between quotation marks. If you set a
value RealFlow does not only require the parameter's name, but also the new value itself. Here
you have to take care about the value's data type (integer, list, vector, etc.).
In this script, only the setParameter command is required and the new value is a position vector:

the horizontal components X and Z are 0


offset represents the current Y value.
We already know how to assemble a new vector:
positionVector = Vector.new(0, offset, 0)

It is also possible to assemble the scale vector, because we already have the required data:
scaleVector = Vector.new(coinDiameter, coinHeight, coinDiameter)

These two vectors will now substitute the current values. The order of the script's instructions is
important. In the first step, the object is created and then we start to modify its properties using
the calculated values. Here is the entire script:
< py >
numberOfCoins = 7
coinHeight

coinDiameter

= 1.0
= 1.5

for coinNumber in range(0, numberOfCoins):


coin

positionVector = Vector.new(0, offset, 0)

= scene.addCylinder(50,1)

offset = coinNumber * coinHeight + coinHeight / 2

scaleVector

= Vector.new(coinDiameter, coinHeight, coinDiameter)

coin.setParameter("Position", positionVector)
coin.setParameter("Scale", scaleVector)

Now you can enter different initial values and execute the script with Batch Editor > Script > Run.

waterline magazine | Scripting with RealFlow

35

First Steps Coin Stacker

Improvements and Suggestions


This is a very basic script and there is much room for improvements even for scripting novices.
Here are few suggestions:

Translate the script for a Z setup.


Make the tower stand on a ground cube.
Activate the coins' Active rigid body property.
Use cubes instead of cylinders.
Rotate each cube around the vertical axis using this formula: coinNumber * 10

The result of the Coin Stacker script in RealFlow's viewport.

waterline magazine | Scripting with RealFlow

36

First Steps
Particle Swapping

First Steps Particle Swapping

Particle Swapping
Although the Coin Stacker script contains just a few lines it already uses many of the concepts we
need for most of our scripting tasks:

Variable definitions
Loops
Create (or read out) scene nodes
Manipulate values
Write back the new values

With the following script we will make use of these concepts again. Particle swapping scripts have
been discussed in many other publications, and there are also lots of free scripts available, but it is
still a very good example for beginners.
Particle swapping is used to separate particles, for example if you want to simulate foam. The
idea is to measure one or more attributes of a fluid's particles, and compare them against a
threshold. As soon as an attribute's value is greater than the associated threshold, the particle
will be moved to a second emitter. Simultaneously, the particle will be removed from the source
emitter.
This is exactly the main principle behind the Filter daemon, but scripting gives us a few more
possibilities, e.g. for combining multiple thresholds in a single script.

Scene Setup
We need a standard particle emitter, e.g. Circle, and a Container node. Both emitters must
have exactly the same Resolution, otherwise the simulation will become unstable. Then, a glass
or bin is added to catch the fluid. A Vase node is perfectly suited, but we will add this node
with a script:
Press F10 for a batch script editor and enter the following line of code:
< py >
scene.addVase(75,1,2.0)

The idea is to create an object with a higher resolution than the default node, and we did the
same in the Coin Stacker script. Scale the vase if necessary.

waterline magazine | Scripting with RealFlow

38

First Steps Particle Swapping

Now we only need a Gravity and k Volume daemon to remove escaping particles. Close the
batch script window without saving.

Adding a Simulation Script


This time, our script will be added to the Simulation Flow window:

Press Ctrl/Cmd + F2 to open it, right-click on FramesPre, and choose Add Script.
You will see something like Script_embedded_407862257.
Right-click on this entry, choose Rename.
Enter Particle Swapping Script.

Now we can start with the script. The first task is to define initial variables, get access to the emitters and their particles, and to specify, which fluid attribute we want to use to filter the particles.
For this purpose, a threshold is also required. Standard particle fluids provide almost a dozen
channels, but velocity seems to be good for a first test. If necessary we can test against other
channels later as well, and introduce additional thresholds.

Getting Emitters and Particles


We start with the velocity threshold (the value can be changed at any time). Then, there are two
emitters, but we only have to gather the particles from Circle01, because Container01 will
receive the filtered particles. To do this, Container01 has to be present in our script as well.
Emitters are scene elements:
< py >
velocityThreshold = 7.5
waterEmitter

= scene.get_PB_Emitter("Circle01")

foamEmitter

= scene.get_PB_Emitter("Container01")

PB stands for particle-based, and if you change the emitters' names in the scene you also have
to change their names in the script.
What we will be doing now is to get the Circle01 emitter's particles and loop through them.
Within this loop, the particles' velocities are read and compared against the threshold.

waterline magazine | Scripting with RealFlow

39

First Steps Particle Swapping

The particles are stored in a list:


particleList = waterEmitter.getParticles()

As in the Coin Stacker script we create a loop where we have access to every particle. This time,
the script will not go through a user-defined range, but loop through particleList:
for particle in particleList:

get velocity and compare against velocityThreshold

The Velocity Condition


If we want to read out parameters from objects, daemons, emitters, and other scene nodes the
getParameter(name) command is used. All we have to do is to enter the parameter's name and
store it as a variable. With particles it is different, because attributes like velocity, vorticity, age,
etc. are not parameters, but channels, and they are accessed directly with appropriate commands:
getVelocity()
getVorticity
getAge()

The entire bandwidth of particle channels can be accessed this way something that also applies
to Hybrido and Dyverso (RF2015) particles; for object vertices, getVelocity() is available too.
A particle should be shifted if its speed is equal to or greater than velocityThreshold:
< pseudo code >
for particle in particleList:

particleVelocity = particle.getVelocity()

if (particleVelocity >= velocityThreshold):

shift particle to Container01

Did you spot the error in the code segment above? The condition will not work, but why? The
answer is that particleVelocity is a vector and it is not possible to compare vectors against real
numbers. We need the vector's magnitude for this job.

waterline magazine | Scripting with RealFlow

40

First Steps Particle Swapping

So, the correct version is


< pseudo code >
for particle in particleList:

particleVelocity = particle.getVelocity()

if (particleVelocityMagnitude >= velocityThreshold):

particleVelocityMagnitude = particleVelocity.module()

shift particle to Container01

I have added this bug as a reminder, to tell you once more how important it is to understand data
types. Finally, there has to be a function to identify the particle we want to remove. This can be
done via the particle's Id:
< py >
velocityThreshold = 7.5
waterEmitter

= scene.get_PB_Emitter("Circle01")

particleList

= waterEmitter.getParticles()

foamEmitter

= scene.get_PB_Emitter("Container01")

for particle in particleList:


particleVelocity

if (particleVelocityMagnitude >= velocityThreshold):

= particle.getVelocity()

particleVelocityMagnitude = particleVelocity.module()

particlePosition = particle.getPosition()

particleId

= particle.getId()

foamEmitter.addParticle(particlePosition, particleVelocity)
waterEmitter.removeParticle(particleId)

Our Particle Swapping script is ready now and can be tested with different emitter Speed values, and thresholds. If you want to separate water and foam visually just assign another colour
the Container01 node:

Container01 > Node Params > Node > Color

waterline magazine | Scripting with RealFlow

41

First Steps Particle Swapping

Randomizing the Threshold


Now we want to randomize the threshold. This process is a very important concept with scripting
and in most cases your results will look better and more natural. We only have to add a few lines
and the most important code segment is:
import random

This line has to be put at the beginning of the script. Python has no built-in functions for random
numbers, but some clever guy has written a so-called module or library to fix this issue. Python
comes bundled with several dozens of modules. With the import command, the module is loaded
once and then kept. random provides a long list of types, but in most cases you will only need:
random.randint(a, b)
random.uniform(a, b)

The random number will be something between a and b. You can use positive and negative values
for a and b. So, what we do is to create a new random number for every particle and add it to the
threshold.
< py >
import random
velocityThreshold = 7.5
waterEmitter

= scene.get_PB_Emitter("Circle01")

particleList

= waterEmitter.getParticles()

foamEmitter

= scene.get_PB_Emitter("Container01")

for particle in particleList:


particleVelocity

= particle.getVelocity()

velocityVariation

= random.uniform(-0.25, 0.25)

particleVelocityMagnitude = particleVelocity.module()

if (particleVelocityMagnitude >= velocityThreshold + velocityVariation):

particlePosition = particle.getPosition()
particleId

= particle.getId()

foamEmitter.addParticle(particlePosition, particleVelocity)
waterEmitter.removeParticle(particleId)

waterline magazine | Scripting with RealFlow

42

First Steps Particle Swapping

Left image: velocityThreshold = 6.0, no random variation.


Right image: velocityThreshold = 6.0, random variation between -1.0 and 4.0

Improvements and Suggestions


Particle Swapping can be seen as the core of a whole series of foam-creation scripts. Nice additions are:

Add pressure, density, and age filters.


Turn particles colliding with the vase into foam and shift them to Container01.
Synchronize the emitters' "Resolution" parameters automatically.

waterline magazine | Scripting with RealFlow

43

First Steps
Colour Blending

First Steps Colour Blending

Colour Blending
The next example will be more challenging. In this script we want to create a smooth colour
blending effect from mixing particles. The original idea has been developed by Karl Richter, a TD
from Portland/Oregon. On his Vimeo account he also shares results of this technique together
with some information about how the effect has been achieved. From this description, I recreated
the following script. Here is Karl's original text:
I made a script to set one fluid's temperature to 1 and the other to 0. By averaging the temperature of each particle with some of its neighbors, this creates smoothly mixing colors.
It seems as if there is not very much information, but in fact we have almost everything we need
to get a nice colour blending effect. The temperature channel can be used, because it is only relevant for gas particles:

Get two standard particle fluid emitters and their particles.


Loop through the particles.
Set the values of the temperature particle channels to 0 (emitter1) and 1 (emitter2).
Get a particle's neighbours.
Sum up the neighbour's temperatures and calculate an average.
Write back the average temperatures.

Analysing the Workflow


This is a good recipe although it is not 100% complete, because one important piece of information is missing. Before we start scripting it is necessary to find out where the problem lies.
Reading out the emitters, storing the particles in lists, and creating loops are standard actions we
know from the Particle Swapping script already. The difference is that we have to do this twice
this time. So, the crucial point must be in the loop:
We set temperature values (0 and 1), calculate new temperatures, and write them back to the
particles. This is done with every frame and the consequence is that this new value will be overwritten with the next frame the calculated value will be lost. But, how can we solve this problem? The solution is particle swapping. We need a modified version of our last script and introduce a third emitter (Container01).

waterline magazine | Scripting with RealFlow

45

First Steps Colour Blending

Scene Setup
The setup I have chosen here is almost the same as with the Particle Swapping script. The only
change is a second Circle emitter. I also rotated the Circle emitters a little. The Container01
node can be kept. Only the emitters are visible in this image:

Our script has to be added to the Simulation Flow window:

Open it with Ctrl/Cmd + F2, right-click on FramesPre, and choose Add Script.

Initial Variables and Settings


Of course we need access to the two emitters and its particles, because we have to loop through
them to do the temperature magic. And this time we really have to go through all emitters.
< py >
emitter1 = scene.get_PB_Emitter("Circle01")
emitter2 = scene.get_PB_Emitter("Circle02")

emitter3 = scene.get_PB_Emitter("Container01")
particleList1 = emitter1.getParticles()
particleList2 = emitter2.getParticles()

waterline magazine | Scripting with RealFlow

46

First Steps Colour Blending

We can loop through a list's elements with a simple expression here for particleList1:
for particle1 in particleList1:

do something here

The trick is to create a new particle with the position and velocity vectors from the circle emitter's
particles and store it. Then, the temperature value is applied to the new particle, and the original particle is removed from Circle01. Temperature is a particle channel, and we have direct
access to this attribute with setTemperature(float). We also know the temperatures already. So,
let's complete the first loop the second loop uses particleList2, particle2, and setTemperature(1.0):
< py >
for particle1 in particleList1:

particle1Position = particle1.getPosition()

particle1Id

particle1Velocity = particle1.getVelocity()
= particle1.getId()

newParticle = emitter3.addParticle(particle1Position, particle1Velocity)

newParticle.setTemperature(0.0)
emitter1.remove(particle1Id)

Getting a Particle's Neighbours


Maybe you have asked yourself why particleList3 has not been defined so far? The answer, why
we have to do this now is that we have to wait for the particle swapping event, because otherwise particleList3 will remain empty:
particleList3 = emitter3.getParticles()

And we are ready to loop through the new list. Before we proceed we should take a look at the
next steps. As written in the introduction (the recipe) we have to find particle3's neighbours
and get their temperature values. By default, these values are 0 and 1. So we need another two
variables, and these new variables have to be introduced within the loop, because they will be
reset with every new particle from the list (please go the next page for the code).

waterline magazine | Scripting with RealFlow

47

First Steps Colour Blending

< py >
for particle3 in particleList3:

temperatureCurrent

neighbourParticleList = particle3.getNeighbors(0.1)

temperatureAverage
temperatureCurrent

= 0
= 0

= particle3.getTemperature()

The argument in getNeighbors(0.1) is the function's search radius All neighbours within this radius
will be added to the list. With higher values the effect will turn out smoother, but simulation time
will increase as well 0.1 m is sufficient. It is possible to initialize this value as a variable.

Temperature Calculations
The structures of neighbourParticleList and particleList1/2 are exactly the same, but we need
one more indent:
< pseudo code >
for particle3 in particleList3:

... get neighbours and temperatures

for neighbourParticle in neighbourParticleList:

sum up all temperatures

set the new average temperature

calculate an average temperature

What we have here is a nested loop, and these loops can be very slow. Imagine a particle with 15
neighbours. The script has to process these 15 neighbours particles before it can proceed with the
next particle from the main list, find its neighbours, go through them again, and so on.
In order to calculate an average temperature we have to sum up the individual temperature values within the loop. This is a typical field of application for an increment operator. The average
temperature is the result of a simple calculation:
temperatureAverage = total temperature of all neighbor particles / number of neighbor particles

waterline magazine | Scripting with RealFlow

48

First Steps Colour Blending

Before the loop starts we also check if neighbourParticleList contains any particles at all:
< py >

if (len(neighbourParticleList) > 0):

for neighbour in neighbourParticleList:

temperatureCurrent += neighbour.getTemperature()

temperatureAverage

= temperatureCurrent / (len(neighbourParticleList) + 1)

Do you remember the chapter about lists, where we were talking about how to get the number
of list elements? The key was the len(list) command. The reason, why we add 1 to the number
of neighbour particles is that we also have to include the seed particle (= particle3).
Finally, the new temperature value will be written to the neighbour particles and the currently
processed particle3. In other words: we need another loop.
< py >
for

neighbour in neighbourParticleList:

neighbour.setTemperature(temperatureAverage)
particle3.setTemperature(temperatureAverage)

That's it! This is the complete script and it is a very nice example how we can achieve interesting
results with standard procedures and some basic maths. Finally we have to find a way to visualize
the result:

Make Circle01 and Circle02 invisible.


Container01 > Node Params > Display > Property > Temperature
The fluid's temperature channel can be mapped to particles meshes, and finally rendered.

waterline magazine | Scripting with RealFlow

49

First Steps Colour Blending

< py >
searchRadius

= 0.1

emitter1
emitter2
emitter3

= scene.get_PB_Emitter("Circle01")
= scene.get_PB_Emitter("Circle02")
= scene.get_PB_Emitter("Container01")

particleList1 = emitter1.getParticles()
particleList2 = emitter2.getParticles()
for particle1 in particleList1:

particle1Position = particle1.getPosition()

particle1Velocity = particle1.getVelocity()

particle1Id
= particle1.getId()

newParticle
= emitter3.addParticle(particle1Position, particle1Velocity)
newParticle.setTemperature(0.0)
emitter1.removeParticle(particle1Id)
for particle2 in particleList2:

particle2Position = particle2.getPosition()

particle2Velocity = particle2.getVelocity()

particle2Id
= particle2.getId()

newParticle
= emitter3.addParticle(particle2Position, particle2Velocity)
newParticle.setTemperature(1.0)
emitter2.removeParticle(particle2Id)
particleList3 = emitter3.getParticles()
for particle3 in particleList3:

temperatureCurrent
= 0

temperatureAverage
= 0

neighbourParticleList = particle3.getNeighbors(searchRadius)

temperatureCurrent
= particle3.getTemperature()
for neighbour in neighbourParticleList:

temperatureCurrent += neighbour.getTemperature()

if (len(neighbourParticleList) > 0):

temperatureAverage = temperatureCurrent / (len(neighbourParticleList) + 1)
for neighbour in neighbourParticleList:
neighbour.setTemperature(temperatureAverage)
particle3.setTemperature(temperatureAverage)

waterline magazine | Scripting with RealFlow

50

Particle view of the fluid with blended colours. The emitter's "Temperature" channel is active here.

Mesh with enabled "Temperature" channel in RealFlow's viewport.

Custom-Tailored
User Interfaces

Custom-Tailored User Interfaces

Custom-Tailored User Interfaces


So far we have defined our initial variables and values at the beginning of script. This is also the
place where they have to be changed they are hardcoded. A much more convenient and fashionable way is to create an input mask with parameters and default values a GUI. RealFlow's
Python SDK provides a set of commands for creating GUIs and processing the settings.
The best place for a GUI is a batch script that is attached to one of RealFlow's shelves. From there,
the input values are transferred to another script, and used for the simulation or a batch job. Defining GUI values requires knowledge about data types (reals, list, vectors, etc.), because an input
value's data type has to be determined before it can be processed. The procedure of creating GUIs
is always the same:

Define the GUI's input fields, names, and default values.


Wait for the user's input.
Read the input values from the fields and translate them into variables.
Transfer the variables and values to another script or different parts of a script.

Coin Stacker GUI


In the first example we want to create a GUI for the Coin Stacker batch script. There, we have
three variables one integer and two real numbers, also called floats, and we want to make them
user-definable. These are the variables and they do not have to be added to the new script, becasue they will not be fixed anymore, but user-controlable:
numberOfCoins = 7
coinHeight

= 1.0

coinDiameter

= 1.5

Our first action is to initialize the GUI with the following command:
< py >
guiForm = GUIFormDialog.new()

In the second step, the input fields, their names, and default values are added to the GUI dialogue.

waterline magazine | Scripting with RealFlow

53

Custom-Tailored User Interfaces

For this action, a fixed notation is required where the value's data type is included:
< py >
guiForm.addIntField("Number of coins", 7)

guiForm.addFloatField("Coin height", 1.0)

guiForm.addFloatField("Coin diameter", 1.5)

The strings between the quotation marks do not represent the variables' names, but the names of
the input fields, comparable to RealFlow's parameter names. These names have to be unique.
Here is a preview of our GUI in RealFlow 2015:

Once we have made our settings we can process the values. This part is introduced with the statement below. The if condition is often used to write out a message, for example when the GUI has
been closed with the Cancel button:
< pseudo code >
if (guiForm.show() == GUI_DIALOG_ACCEPTED):

read the field values and store them in variables (see below)
proceed with the script's functions

else:

scene.message("The script has been cancelled.")

Reading the field values always follows the same scheme. The data type does not play anymore
role here, because it has been introduced with the creation of the GUI already. This is also the moment where the actual variable names will be used, as you will see on the following page.

waterline magazine | Scripting with RealFlow

54

Custom-Tailored User Interfaces

Please double-check if the strings here are exactly the same as in the add...Field() commands,
because otherwise you will receive a syntax error:
< py >
if (guiForm.show() == GUI_DIALOG_ACCEPTED):

numberOfCoins = guiForm.getFieldValue("Number of coins")


coinHeight

= guiForm.getFieldValue("Coin height")

coinDiameter

= guiForm.getFieldValue("Coin diameter")

Now that we have completed the definition of variables we can add the rest of the Coin Stacker
script.
< py >
numberOfCoins = 7
coinHeight

= 1.0

guiForm

= GUIFormDialog.new()

coinDiameter

= 1.5

guiForm.addIntField("Number of coins", 7)

guiForm.addFloatField("Coin height", 1.0)

guiForm.addFloatField("Coin diameter", 1.5)

if (guiForm.show() == GUI_DIALOG_ACCEPTED):

numberOfCoins = guiForm.getFieldValue("Number of coins")

coinDiameter

coinHeight

= guiForm.getFieldValue("Coin height")

= guiForm.getFieldValue("Coin diameter")

for coinNumber in range(0, numberOfCoins):

coin

positionVector = Vector.new(0, offset, 0)

offset

scaleVector

= scene.addCylinder(50,1)

= coinNumber * coinHeight + coinHeight / 2


= Vector.new(coinDiameter, coinHeight, coinDiameter)

coin.setParameter("Position", positionVector)
coin.setParameter("Scale", scaleVector)
else:

scene.message("The script has been cancelled.")

waterline magazine | Scripting with RealFlow

55

Custom-Tailored User Interfaces

Other Field Types


So far we have been working with integers and float/reals, but there are more data types:
addIntField()

addFloatField()

addStringField()
addListField()

addVectorField()
addBoolField()

We also have fields for adding files, directories, or choosing scene nodes:
addFileField()

addDirectoryField()
addObjectField()

Particle Swapping GUI


In this example we want to create a GUI for the Particle Swapping simulation events script. The
GUI part will be a batch script (F10). Again, the input values are read out, and stored as variables,
but then they have to be transferred to the events script. The initialization of the GUI is the same
as before.
< py | batch >
guiForm = GUIFormDialog.new()
guiForm.addFloatField("Velocity threshold", 2.5)
if (guiForm.show() = GUI_DIALOG_ACCEPTED):

velocityThreshold = guiForm.getFieldValue("Velocity threshold")

else:

scene.message("The script has been cancelled.")

The GUI's velocityThreshold will replace the original definition we had at the beginning of the
simulation events script. This variable has to be removed, because it will simply overwrite the GUI
value. Now, we can start the simulation, but we receive an error telling us that velocityThreshold
is not defined. What went wrong here? It is obvious that the variable has not been transferred.

waterline magazine | Scripting with RealFlow

56

Custom-Tailored User Interfaces

Local and Global Variables


velocityThreshold has been declared as a so-called local variable. This means that it can only be
used locally within the GUI batch script. If we try to use velocityThreshold somewhere else we

will receive an error. We need a method to make the variable global and available to the simulation events script as well. For this purpose, the SDK provides a dedicated pair of functions:
setGlobalVariableValue(string, any)
getGlobalVariableValue(string)

Both commands are members of the scene class. The string is the variable's name as it appears
in the script, but set between quotation marks "velocityThreshold". any tells us that we can
declare any variable, or better: data type, as global. Our GUI script has to be extended by the following line:
< py | batch >
if (guiForm.show() = GUI_DIALOG_ACCEPTED):

velocityThreshold = guiForm.getFieldValue("Velocity threshold")

scene.setGlobalVariableValue("velocityThreshold", velocityThreshold)

You might ask why velocityThreshold appears twice in the global variable's argument?

In the first case it is treated as a string and it is the name of the variable.
In the second case we use the variable itself, and this variable stores a value.
The stored value is what we get from the user's input (2.5 by default).
Another, shorter notation might make the idea clearer:
scene.setGlobalVariableValue("velocityThreshold", guiForm.getFieldValue("Velocity threshold"))

Now we have to switch to the Particle Swapping script under Simulation Flow and add a new
line at the beginning of the script:
velocityThreshold = scene.getGlobalVariableValue("velocityThreshold")

What we have done is to read the global variable and its value, and make it local again:
velocityThreshold = get the global velocityThreshold variable's value = 2.5

waterline magazine | Scripting with RealFlow

57

Custom-Tailored User Interfaces

GUIs and Lists


In the last chapter of the GUI section I will show you how to work with lists, because they require
a certain notation as well. As you know, lists store multiple elements and each entry has its own
index starting with 0. So far we have been looping through a list's elements one by one, but did
not access a specific entry.
In this example we add another extension to the Particle Swapping GUI and events script, where
the user can choose which emitter is used for water and, which one for foam. Of course, this is
not really necessary with just two emitters one Circle and one Container but the script
illustrates the concepts. Instead of listing the scene's standard particle emitters manually, we are
going to use a convenient command where all emitters are added to a list automatically:
emitterList = scene.get_PB_Emitters()

The problem is that we do not have direct access to these emitters, because they are not stored
by their names, but as an internal structure a reference. We can see this when we print the list's
elements:
for emitter in emitterList:
scene.message(str(emitter))

The output will look like this:


<RealFlow PB Emitter object at 0x10fcfc258>
<RealFlow PB Emitter object at 0x10fcfc228>

Translating References Into Names


What we can do is to define a new empty list where the emitter's names will be stored:
< py | batch >
emitterNameList = []
emitterList

= scene.get_PB_Emitters()

for emitter in emitterList:



emitterName = emitter.getName()

emitterNameList.append(emitterName)

waterline magazine | Scripting with RealFlow

58

Custom-Tailored User Interfaces

Now, emitterNameList contains two strings, and can we can print them to the GUI dialogue:
< py >
guiForm = GUIFormDialog.new()
guiForm.addListField("Water", emitterNameList, 0)

guiForm.addListField("Foam", emitterNameList, 1)
guiForm.addFloatField("Velocity threshold", 2.5)

We can see that addListField(string, list, integer) takes three arguments:


1. Parameter name.
2. Name of the list with the emitters' names.
3. Position of the list elements 0 stands for Circle01, 1 represents Container01.
The problem is that addListField() does not return a name or a string, but a number. The name
you can see in the GUI is just the names list's entry at position 0 or 1. The number, on the other
hand, is reused to identify the appropriate name in emitterNameList:
< py >
if (guiForm.show() == GUI_DIALOG_ACCEPTED):

waterEmitterIndex = guiForm.getFieldValue("Water")
foamEmitterIndex

= guiForm.getFieldValue("Foam")

waterEmitterName

= emitterNameList[waterEmitterIndex]

foamEmitterName

= emitterNameList[foamEmitterIndex]

waterEmitterName and foamEmitterName can now be stored as global variables, and transferred to

the simulation script. There we use the names to finally get the emitters.
< py >
scene.setGlobalVariableValue("waterEmitterName", waterEmitterName)
scene.setGlobalVariableValue("foamEmitterName", foamEmitterName)

scene.setGlobalVariableValue("velocityThreshold", velocityThreshold)

waterline magazine | Scripting with RealFlow

59

Custom-Tailored User Interfaces

< py | batch >


emitterNameList = []
emitterList

= scene.get_PB_Emitters()

for emitter in emitterList:


emitterName = emitter.getName()

emitterNameList.append(emitterName)
guiForm = GUIFormDialog.new()
guiForm.addListField("Water", emitterNameList, 0)
guiForm.addListField("Foam", emitterNameList, 1)
guiForm.addFloatField("Velocity threshold", 2.5)
if (guiForm.show() == GUI_DIALOG_ACCEPTED):

waterEmitterIndex = guiForm.getFieldValue("Water")

foamEmitterIndex

= guiForm.getFieldValue("Foam")

velocityThreshold = guiForm.getFieldValue("Velocity threshold")

waterEmitterName = emitterNameList[waterEmitterIndex]

scene.setGlobalVariableValue("waterEmitterName", waterEmitterName)

scene.setGlobalVariableValue("velocityThreshold", velocityThreshold)

foamEmitterName

= emitterNameList[foamEmitterIndex]

scene.setGlobalVariableValue("foamEmitterName", foamEmitterName)

< py | framespre >


waterEmitterName
foamEmitterName

= scene.getGlobalVariableValue("waterEmitterName")
= scene.getGlobalVariableValue("foamEmitterName")

velocityThreshold = scene.getGlobalVariableValue("velocityThreshold")
waterEmitter

= scene.get_PB_Emitter(waterEmitterName)

particleList

= waterEmitter.getParticles()

foamEmitter

= scene.get_PB_Emitter(foamEmitterName)

# Add the rest of the Particle Swapping script here (loop)

waterline magazine | Scripting with RealFlow

60

Custom-Tailored User Interfaces

The GUI for the Particle Swapping script.

A Batch Simulation Script


When you start with a RealFlow simulation then you normally create several versions with different parameter settings, e.g. different viscosities, resolutions, object frictions, or daemon forces.
A very good idea is to prepare simulation files and let them run over night. The problem is that
RealFlow does not support batch simulating a new scene file has to be loaded and started manually. With a short script it is possible to change this. All we need for this script is a GUI and a few
loops. Here is the idea behind the script:

Create a GUI where we can specify the FLW scene files for the batch simulation.
Store the paths to the FLW files in a list.
Loop through the list, load and open the FLW files.
Start the simulations one by one.

The name indicates that we will be creating a batch script here. Open the Batch Script editor
with F10.

Initial Variables
When we take another look at the task list above then it seems as if we need just two initial variables: a list where the paths to the FLW files will be stored, and the number of GUI fields for the
FLW paths:
< py >
projectFiles

= []

numberOfFields = 10

waterline magazine | Scripting with RealFlow

61

Custom-Tailored User Interfaces

The GUI
The structure of the GUI is really straight-forward. All we need are 10 fields where we can specify
the FLW files' paths. A loop is a quick solution, but we have to consider a few things. With a simple loop all fields will have exactly the same name, and this is not allowed. We have to keep the
fields separated, because every field contains its own file path.
What we can do is to use the loop's current index and create a name out of it, e.g. Project file
01, Project file 02, , Project file 10. The leading 0 is certainly not necessary, but a nicely
formatted GUI does not only look better, it is also a good opportunity to show you how to combine strings.
The first field here is Project file 01 and therefore the loop will not start with 0 as usual, but
with 1:
< py >
guiForm = GUIFormDialog.new()
for fieldNumber in range(1, numberOfFields + 1):

Strings are always enclosed in quotation marks, but fieldNumber is an integer. We have to convert
the number into a string with
str(fieldNumber)

The first table in the chapter about operators contains an entry called string concatenation,
and this is exactly what we need here: it is a simple + operator. It is also necessary to differentiate
between numbers smaller than 10 and 10 (or greater):
< py >

if (fieldNumber < 10): fieldName = "Project file 0" + str(fieldNumber)

else: fieldName = "Project file " + str(fieldNumber)

Now, fieldNumber can be used as an argument for the add...Field() command:


guiForm.addFileField(fieldName)

waterline magazine | Scripting with RealFlow

62

Custom-Tailored User Interfaces


GUIs
An here is a preview of the GUI window in RealFlow 2015:

Processing the Input Values


We can now read the user's input and append the paths to projectFiles with another loop. Here
it is possible to reuse variable names, because the creation of the input fields is completed, and it
is safe to overwrite the existing values.
< py >
if (guiForm.show() == GUI_DIALOG_ACCEPTED):
for fieldNumber in range(1, numberOfFields + 1):
if (fieldNumber < 10):

fieldName = "Project file 0" + str(fieldNumber)


else:

fieldName = "Project file " + str(fieldNumber)


projectFiles.append(guiForm.getFieldValue(fieldName))

A third loop will go through the entries of the projectFiles list, load the FLW files, and start the
simulation. We also have to check if there are empty entries in projectFiles, because maybe we
want to simulate just 3 or 4 project files. This can be achieved with an "is not" operator:
if (currentProject != ""):

The line above is read as: If the content of currentProject is not empty. We also print a message
to give some information about the currently running simulation (see next page).

waterline magazine | Scripting with RealFlow

63

Custom-Tailored User Interfaces

Here is the final part of the script:


< py >
for

currentProject in projectFiles:

if (currentProject != ""):

scene.load(currentProject)
scene.simulate()

scene.message("Simulating " + currentProject + "")


else:

scene.message("Script cancelled by user.")

You should now be able to assemble the entire script from the previous explanations and code
snippets.

waterline magazine | Scripting with RealFlow

64

Crown Splashes

Crown Splashes

Crown Splashes
We have gathered a lot of scripting knowledge already and now it is time for a more complex
project. RealFlow 2015 provides a really cool new daemon called Crown. A viewport gizmo with
editable control points is used to determine the number of tendrils and draw the splash's shape.
The idea behind the new Crown daemon is a script I have written for RealFlow 2014. I wanted
to have a tool for the creation of paint-like splashes with tendrils, but of course my program did
not have all these nice viewport features. I have been adding other functions instead functions
which are missing with the daemon. The script's concept is also completely different and here I
present a downgraded version, because waterline magazine Scripting with RealFlow is a publication for beginners. Despite of the magazine's beginner's approach you should not be under
the illusion that the following scripts came out of thin air. It will take you some time to understand what happens. And the used formulas and methods are the result of thinking! Sometimes it
also requires time, a pencil, and paper to make sketches and to do some basic maths.
The creation of artificial and user-controllable splashes, on the other hand, is a fascinating project, and this is the reason why I chose to discuss it. Of course you can improve the script, add
more features, and change it to your needs.

What the Script Should Do


I always recommend defining a script's purpose and what you expect it to do. Then I try to break
down this goal into manageable parts and code segments, and start with the most fundamental
functions. Once I got the individual pieces it is much easier to implement things like parametrization, randomness, or a generally valid version, e.g. with location-independent functions.
Out task list includes the creation of crown splashes with a user-defined

base radius
width (thickness)
number of tendrils
velocity
randomization
start and end time
splash position.

From now on I will use shortened variable names, but you should still be able to recognize their
meaning.

waterline magazine | Scripting with RealFlow

66

Crown Splashes

Force or Velocity?
We also have to make a fundamental decision now about whether we want to use forces or velocities for accelerating the particles. The main difference lies in simulation speed. A force-based
approach uses a scripted daemon, and the appropriate functions are executed per simulation
step. This makes our daemon very accurate, but rather slow. A daemon can also be placed anywhere easily, deactivated on demand, and combined with other forces.
A velocity-driven method is added to the Simulation Flow panel's events tree, and we will get a
sufficient level of quality under FramesPre. This will make the script much faster and with very
large amounts of particles we will be able to create more versions in less time. Forces also evolve
slowly, while velocities act immediately.
Since time is always the most critical part in simulations we want to focus on the velocity-based
method.

Scene Setup
Our starting point is a shallow puddle of water. The fluid is calm and we have an initial state. In
this example a standard particle emitter is used. Dyverso fluids are also possible, but at the time
this magazine has been written, this solver type has not been fully supported by RealFlow's SDK.
But, it should be no problem to translate the scripts for Dyverso. The scene's Gravity daemon
can be disabled for the following simulations.

Defining a Ring
The entire script is mainly about finding and separating particles. Once they are stored we can
treat them individually. First of all we have to look for particles within a ring. These particles will
be the seeds for the splashes.

waterline magazine | Scripting with RealFlow

67

Crown Splashes

To find the appropriate candidates we have to check the particles' distances to the ring's centre.
This means that we need a reference point to determine the centre, and this is done with a helper
object a null. A null's default position is < 0,0,0 > (hPos) and we keep this position for now:

A particle is inside the specified ring if its distance from the centre is inside an area around a
circle with a given radius.
We only use the horizontal values, because a particle's height plays no role. This can be
achieved by setting a vector's horizontal component to 0 (see hPosH and pPosH variables).
Furthermore we have to store the particles from inside the ring, because we want to accelerate them separately, while the remaining particles should not be moved.

splash radius
< 0,0 >

splash width

The initial variables are:


< py | batch >
splashRadius
splashWidth

= 0.5

= 0.05

splashParticles = []
helper

= scene.getObject("Null01")

hPosH

= Vector.new(hPos.getX(), 0, hPos.getZ())

hPos

emitter

particleList

= helper.getParameter("Position")

= scene.get_PB_Emitter("Sphere01")
= emitter.getParticles()

waterline magazine | Scripting with RealFlow

68

Crown Splashes

In the following loop we will calculate a particle's distance from the helper object. If this distance
is inside the ring the particle is added to splashParticles:
< py | batch >
for particle in particleList:

pPos

p_hDistance = pPosH.distance(hPosH)

= particle.getPosition()

pPosH

= Vector.new(pPos.getX(), 0, pPos.getZ())

if (p_hDistance >= splashRadius - splashWidth / 2 and


p_hDistance <= splashRadius + splashWidth / 2):

splashParticles.append(particle)

scene.setGlobalVariableValue("splashParticles", splashParticles)

This is already a very good intermediate result, but where do we have to place this code segment?
It will be outsourced to a batch script, because it contains all initial variables we need for the GUI
and the definition of the ring is also a process that has to be finished before the simulation starts.

Accelerating the Ring Particles


The particles from inside the ring are now stored in splashParticles, and we can loop through
them. To do this we add a new script to the FramePre branch of the Simulation Flow events
tree. Of course, we first have to read the global variable:
splashParticles = scene.getGlobalVariableValue("splashParticles")

For our first test we just want to start with an acceleration along the positive vertical axis here it
is Y. If you work with a Z-based setup use the Z axis. Of course, a velocity value is needed as well:
< py | framespre >
splashParticles = scene.getGlobalVariableValue("splashParticles")

velV

= 1.0

for splash in splashParticles:



velVector = Vector.new(0.0, velV, 0.0)


splash.setVelocity(velVector)

waterline magazine | Scripting with RealFlow

69

Crown Splashes

The code can be shortened by combining the last two lines. Of course, the following notation is
valid for other variables as well:
< py | framespre >

splash.setVelocity(Vector.new(0.0, velV, 0.0))

Now, deactivate the Gravity daemon, set the last simulation frame to 50, and start a first test.

What we get is a ring-shaped structure and we can also observe that our definition of splash particles works as expected. Currently, the particles are attracted during the entire simulation range,
and there is no counteracting force, e.g. Gravity, because we want to see the pure result of our
script.

Start and End Time


Setting a time limit for the attraction is just a matter of a few lines of code. We define the start
and end frame variables and compare them against the scene's current simulation frame. As long
as the simulation is within the given range the particles will be affected. Since these parameters
will be part of our GUI they are added to the existing batch script and defined as global variables:
< py | batch >
startFrame = 0
stopFrame

= 20

scene.setGlobalVariableValue("startFrame", startFrame)
scene.setGlobalVariableValue("stopFrame", stopFrame)

waterline magazine | Scripting with RealFlow

70

Crown Splashes

In the "FramesPre" part of the script, the startFrame and stopFrame variables will be read:
< py | framespre >
splashParticles = scene.getGlobalVariableValue("splashParticles")
curFrame

startFrame
stopFrame

= scene.getCurrentFrame()

= scene.getGlobalVariableValue("startFrame")
= scene.getGlobalVariableValue("stopFrame")

if (curFrame >= startFrame and curFrame <= stopFrame):


for splash in splashParticles:

execute code from Accelerating the Ring Particles

Creating a Splash-Like Shape


Our next task is to give the fluid its typical shape, because by now we only have a cylindrical structure. Let's take another look at the definition of our velocity vector:
Vector.new(0.0, velV, 0.0)

There is just a vertical component, while the horizontal values are both 0. It seems as if we only
have to define X and Z values, right? Wrong! We have to consider the particles' positions and
motion directions, because some particles will receive negative velocities, while others require
positive values. But how can this be accomplished?
The keyword is vector normalization. When a vector is normalized you can also think of this
process as finding its direction. The normalized vector's components will help us to determine in
which direction a particle has to be accelerated. Although this is a very reliable method we are
facing one problem here: the usage of normalized vectors only works as long as our null helper is
located at in the scene's origin at < 0,0,0 >.
Fortunately, this can be solved easily by taking the helper object's position into consideration. We
need a velocity direction (velDir) from the splash's centre to its outside:
velDir = pPosH hPosH # particle position vector - helper position vector
velDir.normalize()

Since the helper's postion is used here it must be declared as a global variable as well. Again, the
horizontal components are enough (= hPosH)

waterline magazine | Scripting with RealFlow

71

Crown Splashes

We will also outsource the initial velocity definitions to the batch script, because later we want
them to be part of the GUI. Add these lines to the existing script:
< py | batch >
velH = 1.7 # The particles' initial vertical velocity

velV = 2.0 # The particles' initial horizontal velocity


scene.setGlobalVariableValue("velH", velH)
scene.setGlobalVariableValue("velV", velV)

scene.setGlobalVariableValue("hPosH", hPosH)

And here is the complete "FramesPre" part of our script so far, where we use the normalized direction vectors. It will substitute the current code:
< py | framespre >
splashParticles = scene.getGlobalVariableValue("splashParticles")
velH

= scene.getGlobalVariableValue("velH")

hPosH

= scene.getGlobalVariableValue()

velV

= scene.getGlobalVariableValue("velV")

curFrame

startFrame
stopFrame

= scene.getCurrentFrame("hPosH")

= scene.getGlobalVariableValue("startFrame")
= scene.getGlobalVariableValue("stopFrame")

if (curFrame >= startFrame and curFrame <= stopFrame):


for splash in splashParticles:

pPosH

= splash.getPosition()

velDir = pPosH - hPosH


velDir.normalize()

velVec = Vector.new(velH * velDir.getX(), velV, velH * velDir.getZ())

splash.setVelocity(velVec)

Now it is possible to place the helper object anywhere inside the puddle and the particles will be
accelerated correctly. Since we can accelerate the horizontal and vertical components individually we are able to control the crown's width. Our test uses a "Sheeter" daemon to fill the occurring holes, and shows the typical shape. We can proceed with the next task: the creation of the
splash's tendrils.

waterline magazine | Scripting with RealFlow

72

Crown Splashes

The script now creates the typical crown splash shape and is position-independent.

Tendril Creation
Currently, all particles receive the same velocity value, but for the creation of tendrils we have to
modify the velocities of specific particles and make them faster than the rest of the splash. The
definition of the splash's tendrils is definitely the most difficult part, and requires some more advanced techniques. We also have to consider simulation speed, so we should look for an economic
solution. Here are our subtasks:

Find the positions of the tendrils.


Save the tendril seed particles.
Accelerate the tendril seeds separately from the ring/splash particles.
The first two topics will be part of the batch script, while the acceleration happens inside the
FramesPre part.

Finding the Tendril Positions


Our zone of action is a ring. The ring's inner radius describes a circle and this parameter has been
defined already: splashRadius. What we need is a formula that will spread the tendril seeds evenly over this circle.
Attention:
The following illustrations show a top view of the splash ring and therefore the corresponding
axes are X and Z. If you have a Z-based setup, the axes are X and Y.

waterline magazine | Scripting with RealFlow

73

Crown Splashes

A full circle has 360 degrees. With, for example, eight tendrils, a tendril seed is placed every
360 / 8 = 45

r
X

Or in a common formula with degPerTendril and numTendrils as a new variables:


degPerTendril (here:

a)

= 360.0 / numTendrils

Circle Maths
What we need now are the X and Z coordinates of the points on the circle. Again, height plays no
role. We therefore have to find a method to describe the position of every point on a circle dependent on a given angle: degPerTendril.
Maybe you still remember your time at school and there you have been dealing with the so-called
unit circle a circle with a radius of 1. The interesting thing about this certain type of circle is that
it is used to illustrate sine and cosine functions. Just do a quick Internet search for:
sine unit circle
There you will find a detailed explanation. Here we just want to complete our illustration.

waterline magazine | Scripting with RealFlow

74

Crown Splashes

Any coordinate on a unit circle can be found with sine, cosine, and and angle (a):

r=1
cos ()

sin ()

With a = degPerTendril our coordinates are:


xCoord = cos(degPerTendril)
zCoord = sin(degPerTendril)

There is one thing we have to consider: instead of degrees the radians is required. The formula
for converting degrees into radians is:
radians = degPerTendril * Pi / 180.0

The two equations above are multiplied with splashRadius (r) to get a result for arbitrary circles.
And of course, we have to consider the splash's centre, indicated by the helper object. When we
work with mathematical functions a certain module is required:
import.math

radians = degPerTendril * math.pi / 180.0


xCoord = math.cos(radians) * splashRadius + hPos.getX()

zCoord = math.sin(radians) * splashRadius + hPos.getZ()

waterline magazine | Scripting with RealFlow

75

Crown Splashes

The coordinates from the previous page are just for a single degPerTendril angle, but we need
numOfTendrils angles. A loop will solve this:
for i in range(0, numOfTendrils):

radians = i * degPerTendril * math.pi * 180.0

zCoord

xCoord

= math.cos(radians) * splashRadius + hPos.getX()

= math.sin(radians) * splashRadius + hPos.getZ()

These positions will be added to a separate list (tendrilSeeds). Since the previous descriptions are
rather complex we want to take a look at the entire batch script so far. New code segments are
printed in blue:
< py | batch >
import math
# Define all initial variables, get the particles, and the helper object's position
startFrame

= 0

velH

= 1.7

stopFrame
velV

splashRadius
splashWidth

numOfTendrils
tendrilSeeds

= 20

= 2.0
= 0.5

= 0.05
= 8

= []

splashParticles = []
helper

= scene.getObject("Null01")

hPosH

= Vector.new(hPos.getX(), 0, hPos.getZ())

hPos

= helper.getParameter("Position")

emitter

particleList

= scene.get_PB_Emitter("Sphere01")
= emitter.getParticles()

# Find the ring/splash particles and add them to splashParticles


for particle in particleList:

pPos

p_hDistance = pPosH.distance(hPosH)

pPosH

= particle.getPosition()

= Vector.new(pPos.getX(), 0, pPos.getZ())

if (p_hDistance >= splashRadius - splashWidth / 2

and p_hDistance <= splashRadius + splashWidth / 2):

splashParticles.append(particle)

waterline magazine | Scripting with RealFlow

76

Crown Splashes

# Get the positions of the tendril seeds


for i in range(0, numTendrils):

radians = i * degPerTendril * math.pi / 180.0

zCoord

xCoord
tPosH

= math.cos(radians) * splashRadius + hPos.getX()


= math.sin(radians) * splashRadius + hPos.getZ()
= Vector.new(xCoord, 0.0, zCoord)

tendrilSeeds.append(tPosH)
# Define the global variables

scene.setGlobalVariableValue("startFrame", startFrame)
scene.setGlobalVariableValue("stopFrame", stopFrame)
scene.setGlobalVariableValue("velH", velH)
scene.setGlobalVariableValue("velV", velV)

scene.setGlobalVariableValue("hPosH", hPosH)

scene.setGlobalVariableValue("splashParticles", splashParticles)

Getting the Tendril Particles


The tPosH vector is basically the same as the hPosH vector we have been using for detecting the
ring particles. This means that we can calculate the distance between and tPosH and a particle's
position (pPosH) in order to find a tendril particle. It is the same principle that we have used for
finding the ring particles. It is, on the other hand, not very likely that there will be a particle at
the specified position, so it is better to search within a given radius the new searchRadius variable:
searchRadius = 0.05

In the next step we loop through all particles again, but also through tendrilSeeds to calculate
the distance between particles and seeds (= nested loop). We also need another list where we
can store the found particles. And we must not forget to add the individual tendril seed particles
to the splashParticles list, because searchRadius can be greater then splashWidth.

splashWidth
searchRadius

waterline magazine | Scripting with RealFlow

77

Crown Splashes

This is the last code segment of the batch script. I have added a few more annotations on the
script below:
< py | batch >
# Add these new variables to the initial variable definition
searchRadius = 0.05
tendrilIds

= []

# Add this nested loop directly before the definition of the global variables
for particle in particleList:

pPos

= particle.getPosition()

pPosH = Vector.new(pPos.getX(), 0, pPos.getZ())

for tPosH in tendrilSeeds:


p_sDist = pPosH.distance(tPosH)

if (p_sDist <= searchRadius):

tendrilIds.append(particle.getId())
splashParticles.append(particle)

scene.setGlobalVariableValue("tendrilIds", tendrilIds)

The if-condition defines the area in which we search for particles, but the last line is more interesting. Here, we do not store particles, but only their Ids. This trick will help us to avoid a
time-consuming nested loop at simulation time.

The eight big spots are the tendril positions (splashWidth = 0.05, searchRadius = 0.15).
A blossom-shaped splash appears during the simulation, but the tendrils are not yet created.

waterline magazine | Scripting with RealFlow

78

Crown Splashes

What we have scripted here is a very versatile approach. Imagine a spline, e.g. a spiral. In such a
case we can read out the horizontal coordinates from the spline's control points directly. There is
no need to search for these values with sine and cosine. Then, the control points are used as seeds
for pulling out tendrils at specific positions. With the knowledge from this chapter you should be
able to write such a script already!

Accelerating Splashes and Tendrils


A huge advantage with our current approach is that we have to look for the splash and tendril
particles only once in the batch part. All particles are defined before RealFlow starts to simulate
and this makes our script very fast. The result of all the previously executed actions are two lists:

splashParticles contains all particles inside the ring including the tendril particles.
tendrilIds contains the Ids of the particles around a tendril seed within searchRadius.

This differentiation makes it possible to accelerate the tendril particles separately from the splahes. For this purpose a factor is introduced. When the script detects the Id of a tendril particle a
factor of 1.25 is used, if it is a ring splash particles the factor is 0.75:
< py | framespre >
splashParticles = getGlobalVariableValue("splashParticles")
tendrilIds

= getGlobalVariableValue("tendrilIds")

if (curFrame >= startFrame and curFrame <= stopFrame):


for splash in splashParticles:

splashId = splash.getId()

else

if (splashId in tendrilIds): factor = 1.25

: factor = 0.75

In the next step, the factor has to be multiplied with the user-defined velocites and the XZ components of the normalized direction vector velDir.
To give you a complete view of this part I have added the last code segment to the next page.

waterline magazine | Scripting with RealFlow

79

Crown Splashes

Append the following code to the previous part of the "FramesPre" script:
< py | framespre >

sPosH

velDir

= splash.getPosition()
= sPosH - hPosH

velDir.normalize()

velVecX = velH * velDir.getX() * factor

velVecZ = velH * velDir.getZ() * factor

velVecY = velV * factor

velVec

= Vector.new(velVecX, velVecY, velVecZ)

splash.setVelocity(velVec)

The script's mode of operation is as follows: we just go through our splash/ring particles, get their
positions as usual, and normalize these vectors. We also get the Id and then we check if the current Id is stored in the tendrilIds list. If this is the case, the particle gets an extra push to make it
faster than the remaining splash particles. Here I am using fixed factor values, but of course you
can add this parameter to a GUI as well.
Below is a rendered version with 10 tendrils and "FPS Output" set to 100. The tendrils' droplets
are the result of a "Surface Tension" daemon.

waterline magazine | Scripting with RealFlow

80

Crown Splashes

Randomization
At the moment we have a perfect splash, where the distances between the tendrils is fixed, and
the accelrating forces are also the same for all particles. The resulting crown is nice, but too artificial for many applications. We need a more random appearance. Adding some noise is not difficult, and we did something very similar in the Particle Swapping script already, but here we have
to be careful:
If the random speed variation is added to the velocity vector velVec in the FramesPre script we
might end up with something chaotic, because the particles' speed will change every time the calculated velocity is applied. It is better to define a fixed initial random value for every particle and
apply it during the simulation.
We therefore have to start with the batch script, load the "random" module, and define the
range. We also need another list called velVariation. The number of entries in velVariation has
to be equal to the number of particles in splashParticles:
< py | batch >
import math, random
# Add these new variables to the initial variable definition
velRange

= 0.2

velVariation = []

# Add this loop directly before the definition of the global variables
for entry in splashParticles:

velVariation.append(random.uniform(-velRange, velRange))

scene.setGlobalVariableValue("velVariation", velVariation)

In the "FramesPre" section the required global variable is read as usual. What we also need is a
method to get a splash particle's associated random velocity from the velVariation list. To do
this, a counter is introduced:
counter = 0

waterline magazine | Scripting with RealFlow

81

Crown Splashes

Inside the loop, 1 is added to the counter with every new particle. This process is also called incrementation (see Operators). Now it is possible to use the counter as an index to get the list positions. Since the counter is updated with every step of the loop we can go through all list elements
automatically:

velVariation contains random values between -0.2 and 0.2


Value

-0.13

0.07

0.12

0.18

-0.02

-0.05

0.13

-0.17

0.11

0.16

-0.04

-0.10

Index

10

11

counter

10

11

A single element can be read out this way: rndVel = velVariation[counter]


The last question is where the random value should be used? After a few tests I found that the
most effective method is to add the random values to the factor variable. Here is the shortened
code:
< py | framespre >
# Add these variables to the other (global) variable definitions
velVariation = scene.getGlobalVariableVariation("velVariation")
counter

= 0

if (curFrame >= startFrame and curFrame <= stopFrame):


for splash in splashParticles:

if (splash.getId() in tendrilIds): factor = 1.25 + velVariation[counter]

# Do the velocity part here, assemble the vector, etc.

else

: factor = 0.75 + velVariation[counter]

# The last line in the loop is:

counter += 1

That's it! We are ready and our script contains all the features we have defined at the beginning
of the chapter. We are able to user-define and control crown splashes, we have found a very fast
approach, and avoided nested loops during simulation time. Of course, there is also room for improvements, but the most important goal with this project was to show you concepts.

waterline magazine | Scripting with RealFlow

82

Crown Splashes

< py | batch >


import math, random
# Define all initial variables, get the particles, and the helper object's position
startFrame

= 0

velH

= 1.7

stopFrame
velV

splashRadius
splashWidth

searchRadius

numOfTendrils
velRange

degPerTendril
tendrilSeeds
tendrilIds

= 15

= 2.0
= 0.5

= 0.05
= 0.05
= 8

= 0.5

= 360.0 / numOfTendrils
= []
= []

splashParticles = []
velVariation

= []

hPos

= helper.getParameter("Position")

helper
hPosH

emitter

particleList

= scene.getObject("Null01")

= Vector.new(hPos.getX(), 0, hPos.getZ())
= scene.get_PB_Emitter("Sphere01")
= emitter.getParticles()

# Find the ring/splash particles and add them to splashParticles


for particle in particleList:

pPos

p_hDistance = pPosH.distance(hPosH)

pPosH

= particle.getPosition()

= Vector.new(pPos.getX(), 0, pPos.getZ())

if (p_hDistance >= splashRadius - splashWidth / 2

and p_hDistance <= splashRadius + splashWidth / 2):

splashParticles.append(particle)
# Get the positions of the tendril seeds
for i in range(0, numOfTendrils):

radians = i * degPerTendril * math.pi / 180.0

zCoord

xCoord

= math.cos(radians) * splashRadius + hPos.getX()

tPosH

= Vector.new(xCoord, 0.0, zCoord)

= math.sin(radians) * splashRadius + hPos.getZ()

tendrilSeeds.append(tPosH)

waterline magazine | Scripting with RealFlow

83

Crown Splashes

# Get the positions of the tendril seeds


for particle in particleList:

pPos

for tPosH in tendrilSeeds:

pPosH

= particle.getPosition()

= Vector.new(pPos.getX(), 0, pPos.getZ())

p_sDist = pPosH.distance(tPosH)
if (p_sDist <= searchRadius):

tendrilIds.append(particle.getId())
splashParticles.append(particle)

# Create a unique random acceleration value for every splash particle


for entry in splashParticles:

velVariation.append(random.uniform(-velRange, velRange))

# Define the global variables

scene.setGlobalVariableValue("startFrame", startFrame)
scene.setGlobalVariableValue("stopFrame", stopFrame)
scene.setGlobalVariableValue("velH", velH)
scene.setGlobalVariableValue("velV", velV)

scene.setGlobalVariableValue("hPosH", hPosH)

scene.setGlobalVariableValue("splashParticles", splashParticles)
scene.setGlobalVariableValue("tendrilIds", tendrilIds)

scene.setGlobalVariableValue("velVariation", velVariation)

< py | framespre >


curFrame

= scene.getCurrentFrame()

velV

= scene.getGlobalVariableValue("velV")

velH

hPosH

= scene.getGlobalVariableValue("velH")
= scene.getGlobalVariableValue("hPosH")

splashParticles = scene.getGlobalVariableValue("splashParticles")
tendrilIds

= scene.getGlobalVariableValue("tendrilIds")

stopFrame

= scene.getGlobalVariableValue("stopFrame")

startFrame
velVariation
counter

= scene.getGlobalVariableValue("startFrame")
= scene.getGlobalVariableValue("velVariation")
= 0

if (curFrame >= startFrame and curFrame <= stopFrame):


for splash in splashParticles:

if (splash.getId() in tendrilIds): factor = 1.25 + velVariation[counter]


else

waterline magazine | Scripting with RealFlow

: factor = 0.75 + velVariation[counter]

84

Crown Splashes

sPosH

= splash.getPosition()

velDir = sPosH - hPosH

velDir.normalize()

velVecX = velH * velDir.getX() * factor

velVecZ = velH * velDir.getZ() * factor

velVecY = velV * factor

velVec = Vector.new(velVecX, velVecY, velVecZ)

splash.setVelocity(velVec)

counter += 1

Improvements and Suggestions


Possible additions are

The GUI
Randomize the tendril's angles.
Create propagating tendril patterns.
Translate FramesPre into a scripted daemon.
Particles below the helper object should not be affected.
Take the particles' vertical positions into account.
Use spline control point positions or object vertices as tendril seeds.
Add a time-dependent function for the velocity vector to start with very low values, and increase them over time.

You should now be able to implement the above suggestions and customize your crown splash
script. For some features (propagating tendrils, tendrils from spline control points, and vertices) it
is better to discard splashParticles, and to work with tendrilSeeds only. Otherwise you will get
strange results.

waterline magazine | Scripting with RealFlow

85

Crown Splashes

Axis Setups
So far, all scripts have been developed for a Y-based axis setup, but this can be considered a special case. If your scripts are for personal or internal use only then you do not have to care about
this issue too much, because you just have to swap Y and Z in your vector definitions:
verticalVelocity= 2.5
velocityVector = Vector.new(0, verticalVelocity, 0)

With a Z-based setup the vector is:


velocityVector = Vector.new(0, 0, verticalVelocity)

RealFlow also provides a method for detecting the scene's axis setup as shown in this batch script:
< py >
verticalVelocity = 2.5
axisSetup = scene.getAxisSetup()

if (axisSetup == AXIS_SETUP_ZXY):

velocityVector = Vector.new(0.0, 0.0, verticalVelocity)

velocityVector = Vector.new(0.0, verticalVelocity, 0.0)

else:

scene.message(str(velocityVector))

If your axis setup is ZXY with Z as the vertical axis the result is:
0.000000 0.000000 2.500000

For Y-based setups (YXZ, YZX) it is:


0.000000 2.500000 0.000000

waterline magazine | Scripting with RealFlow

86

Export Manager

Export Path Manager

Export Path Manager


RealFlow's Export Central dialogue provides lots of features, but it is sometimes cumbersome to
use. Especially the definition of export paths for storing different versions can be tedious, because
the paths have to be entered and edited manually. This is not exactly a quick and smooth workflow.
With the following batch script (F10) it is possible to specify new export paths for a scene's standard particle emitters by choosing a directory. The chosen folder will then be transferred to the
node's Export Central resources. One requirement is that the new path will only be applied to
active resources, e.g. to BIN and RPC, while inactive formats should not be changed.
Starting point for this helper is the Batch Simulation script, because we can find some useful
concepts here like the creation of GUI input fields within loops, or the detection of empty field
entries.

Creating the GUI


The GUI is the place where we can choose directories and its only content is the scene's list of
emitters. Therefore we have to collect all standard particle emitters first and loop through the
resulting list:
< py >
emitterList = scene.get_PB_Emitters()
guiForm

= GUIFormDialog.new()

for emitter in emitterList:


guiForm.addDirectoryField(emitter.getName()+" export path")

The last line of code is the most interesting, but it should be familiar to you already, because it is
what we call string concatenation. Here is a preview of the GUI with a few emitters:

waterline magazine | Scripting with RealFlow

88

Export Path Manager

Evaluating the Input


All we have to do is to click on the appropriate folder icons (RealFlow 2015 only) and go to the
desired directory. Then, a loop is added where we will read out the GUIs individual fields:
< py >
if (guiForm.show() == GUI_DIALOG_ACCEPTED):
for i in range(0, len(emitterList)):

emitter = emitterList[i]

fieldName = emitterName+" export path"

emitterName = emitter.getName()

exportPath = guiForm.getFieldValue(fieldName)

The i variable is used as an index to access the list entries, but this is also a common concept now.
Then, the fields' names are assembled, and finally read out and stored in exportPath. We are almost ready with this loop, but need to get an emitter's export resources:

By default, only Particle cache (.bin) is active, but maybe you also want to write RPC or ABC
files. The script has to detect which formats are enabled, because only these will be changed.
With this command we can check a node's export state the result is printed below:
exportResources = emitter.getAllExportResourceValues()
(1, 'Particle cache (.bin)', True)

(2, 'Particle proxy (.pxy)', False)

(6, 'Krakatoa Particle File Format (.prt)', False)


(3, 'Particle sequence (.pd)', False)

(4, 'Particle sequence (.asc)', False)


(5, 'Particle sequence (.pdc)', False)
(7, 'Alembic sequence (.abc)', False)

(9, 'Arnold Scene Source (.ass)', False)

waterline magazine | Scripting with RealFlow

89

Export Path Manager

Each entry has its own index ranging from 0 to 2. The attribute of interest is number 2, because
it tells us a whether a format is enabled (True) or nor (False). It is possible to directly access this
field with its index. The first number is also important, because we can use it to address a specific
export resource.

Changing the Export Path


Now we can check with the known != (is not) operator if a field is empty or not. Here is the
complete script. The new parts are printed in blue:
< py >
emitterList = scene.get_PB_Emitters()
guiForm

= GUIFormDialog.new()

for emitter in emitterList:


guiForm.addDirectoryField(emitter.getName()+" export path")

if (guiForm.show() == GUI_DIALOG_ACCEPTED):
for i in range(0, len(emitterList)):

emitter

= emitterList[i]

fieldName

= emitterName+" export path"

emitterName = emitter.getName()

exportPath

= guiForm.getFieldValue(fieldName)

if (exportPath != ""):

exportResources = emitter.getAllExportResourceValues()

for entry in exportResources:

if entry[2] == True:

emitter.setExportResourcePath(entry[0], exportPath)

Here we go through the entries of the node's export resources and address the relevant attributes
0 and 2. Finally, the content from exportPath is inserted.

waterline magazine | Scripting with RealFlow

90

Velocities From Ocean


Statistical Spectrum Waves

Velocities From Ocean Statistical


Spectrum Waves
Velocities From Ocean Statistical Spectrum Waves
Maybe you have already observed that ocean statistical spectrum (OSS) waves do not provide
any velocity information. With RealWave, velocities can be visualized by activating the surface's
Particle layer option. With deformers like Fractal or Control Points you can see RealFlow's
typical blue and white patterns, but with Ocean statistical spectrum and Gerstner all colours
remain the same during simulation.
So, if you want to create meshes from the particle layer the velocity channel will remain empty
and cannot be used for shading purposes. With the help of a simulation script it, on the other
hand, possible to calculate the vertices' velocities.

Definitions and Considerations


Velocity is the distance s that is covered within a certain time t, and given in metres per second:
velocity = s / t [m/s]

To be more precise, it is the change of position between two points in time. In physics, changes
are represented with a D symbol:
velocity =

Ds

Dt

Our script has to calculate the differences in position between two frames (f0, f1) to get the distance, but we have to be careful with the order:

Ds

= positionf1 - positionf0

This also applies to time, but here it is much easier, because the time span between two frames is
always the same it is 1 frame. What we have to do is to convert this fixed time step to seconds.
The length of the time span depends on the adjusted frame rate:

Dt

= 1.0 / FPS

Our complete formula is:


velocity = (positionf1 positionf0) / (1 / FPS)

waterline magazine | Scripting with RealFlow

92

Velocities From Ocean Statistical


Spectrum Waves
Initial Velocities
The most obvious idea is to calculate the velocities and transfer them to the RealWave node's particle layer. But, before we start we should take another look at this layer:

Zooming in reveals that the particles are not only located at the polygons' intersections, but also
in the triangles' centres. This makes it difficult to transfer the calculated velocities to the particle layer, because we do not have enough information from the vertices. It is possible to average
velocities from a triangle's vertices and use the values for the centre particles, but this approach is
too complex for our conceptual script.
We therefore go a different route and

introduce a Container node


create particles at the vertices' positions
add the calculated velocity to the particles.
This workflow can be seen as a particle swapping approach something we have done already.
Instead of particles from a different emitter we use vertices. It is important to unlink the Container01 node from the Relationship Editor's default hub, and set its particle type to Dumb.
In order to speed up the simulation, go to RealFlow's Simulation Options > General and set both
"MIN | MAX substeps" to 1. RealWave surfaces and dumb particles do not need more substeps.
Now we have a clear list of tasks and can proceed with the first part of the script: initial velocities.
The formula above tells us that we always need two frames, but the simulation starts at frame 0
and therefore the vertices' velocities are 0 as well. All we have to do is to apply our particle swapping algorithm in a slightly modified form. Add a new script to the SimulationPre branch of the
Simulation Flow panel.

waterline magazine | Scripting with RealFlow

93

Velocities From Ocean Statistical


Spectrum Waves
Here is the first part of our script:
< py | simulationpre >
rwSurface

= scene.getRealwave()

emitter

= scene.get_PB_Emitter("Container01")

rwVertices = rwSurface.getVertices()
nullVec

= Vector.new(0.0,0.0,0.0)

scene.enablePaint(False)

emitter.removeAllParticles()
for vertex in rwVertices:

vPos = vertex.getPosition()

emitter.addParticle(vPos, nullVec)

scene.enablePaint(True)

The removeAllParticles() statement clears the emitter, because otherwise the number of particles will be increased with every start of the simulation. With scene.enablePaint(False) the creation of the particles will be very fast. Since we want to see the result during simulation, we have
to reactivate RealFlow's paint function.
In terms of scripting, the calculation of the changes in position is the most difficult part. The challenge is to store and keep the RealWave's vertex positions from the previous frame and use them
in the current frame. A global variable is required for the positions:
< py | simulationpre >
# Add this line to the initial variable definitions
vPositionsOld = []

# Append the blue lines to the exsiting code:


for vertex in rwVertices:

vPos = vertex.getPosition()

vPositionsOld.append(vPos)

emitter.addParticle(vPos, nullVec)

scene.setGlobalVariableValue("vPositionsOld", vPositionsOld)

scene.enablePaint( True )

waterline magazine | Scripting with RealFlow

94

Velocities From Ocean Statistical


Spectrum Waves
Velocity Calculations
The global vPositionsOld can now be used in the FramesPre part of our script. We also need
an empty list where the current positions will be stored, the time step 1.0 / FPS, and some other
essentials:
< py | framespre >
vPositionsOld = scene.getGlobalVariableValue("vPositionsOld")
vPositionsCur = []
emitter

= scene.get_PB_Emitter("Container01")

rwVertices

= rwSurface.getVertices()

rwSurface
dT

= scene.getRealwave()

= 1.0 / scene.getFps()

emitter.removeAllParticles()

Then we start with another loop:


< py | framespre >
for vertex in rwVertices:

vPos = vertex.getPosition()

In the next step we subtract the vertex's old position from its current position. This is again done
by using the counter method from Crown Splashes.The counter is updated with every loop cycle
and used as an index to extract the elements of vPositionsOld:
< py | framespre >
# Add this line to the initial variable definitions
counter = 0

for vertex in rwVertices:



vPos

...

= vertex.getPosition()

posDiff = vPos -

vPositionsOld[counter]

counter += 1

waterline magazine | Scripting with RealFlow

95

Velocities From Ocean Statistical


Spectrum Waves
It is time to apply our velocity formula. posDiff is a vector and therefore we have to treat its elements individually, and assemble a new vector. All of the following processes happen inside the
loop:
< py | framespre >
vPositionsOld = scene.getGlobalVariableValue("vPositionsOld")
vPositionsCur = []
emitter

= scene.get_PB_Emitter("Container01")

rwVertices

= rwSurface.getVertices()

rwSurface
dT

counter

= scene.getRealwave()

= 1.0 / scene.getFps()
= 0

emitter.removeAllParticles()
for vertex in rwVertices:

vPos

= vertex.getPosition()

vel

= Vector.new(posDiff.getX() / dT, posDiff.getY() / dT, posDiff.getZ() / dT)

posDiff

= vPos -

vPositionsOld[counter]

particle = emitter.addParticle(vPos, vel)

particle.freeze()

vPositionsCur.append(vPos)

counter += 1

scene.setGlobalVariableValue("vPositionsOld", vPositionsCur)

The orange line does not only create a new particle, but stores it in a variable. You know this notation from the Colour Blending script! In the next line, the particle's position is frozen, because
we have applied a velocity and this means that the particle will be moving. Finally, we store the
current position.
The last line of the script uses a trick to save the current positions for the next frame, where they
will be treated as the old positions:
scene.setGlobalVariableValue("vPositionsOld", vPositionsCur)

waterline magazine | Scripting with RealFlow

96

Velocities From Ocean Statistical


Spectrum Waves
We save the current positions and assign them as the old ones the previous values will be overwritten and replaced. With the next frame, the global variable is read again, and so on.
And here is the result of our script: a view of RealFlow's OSS waves with velocities. We can mesh
these particles and include the velocity channel.

An interesting result can be achieved by disabling the emitter.removeAllParticles() statement.


Then you will see the trajectories of the vertices and it is, of course, possible to mesh them as well.
The result looks like an array of complex knots.

Improvements and Suggestions


Here are a few more ideas for the last script:

Use the velocity calculation for animated objects.


Use the velocity vectors for triggering the emission of foam.
Combine velocity with other attributes, e.g. height, and create complex thresholds.
Write a version where the RealWave's particle layer is used for transferring the velocities.
To get these particles you have to use the following statement:
emitter = scene.get_PB_Emitter("RealWave01")

waterline magazine | Scripting with RealFlow

97

waterline magazine
Scripting with RealFlow
2015 by Thomas Schlick | Next Limit Technologies
Commercial distribution is prohibited. Translations only with permission.

Das könnte Ihnen auch gefallen