Waterline Real Flow Scripting

98
SCRIPTING WITH REALFLOW waterline magazine REALFLOW TUTORIALS BY THOMAS SCHLICK | NEXT LIMIT TECHNOLOGIES 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

description

Real Flow Scripting guide

Transcript of Waterline Real Flow Scripting

Page 1: Waterline Real Flow Scripting

Scripting with realFlow

waterline magazineRealFlow TuToRials

by Thomas schlick | NexT limiT TechNologies

Introduction to Python in RealFlowRealFlow's Scripting Editors

VariablesData Types

User InterfacesCoin Stacker ScriptParticle Swapping

Colour BlendingCrown Splashes

Export ManagementVelocities from Ocean Statistical Spectrum Waves

Page 2: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 2

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

RealFlow's Scripting Editors Batch Scripts Simulation Scripts Where to Add Simulation Scripts? Basic Workflows Node-Based Scripts

First Steps - Coin Stacker Coin Positions Adding Coins Changing Parameters Improvements and Suggestions

coNTeNTs

5

5

778

10121313161616171717181819192122232324

272728292930

3232343436

Page 3: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 3

First Steps - Particle Swapping Scene Setup Adding a Simulation Script Getting Emitters and Particles The Velocity Condition Randomizing the Threshold Improvements and Suggestions

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

A Batch Simulation Script Initial Variables The GUI Processing the Input Values

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

coNTeNTs

38383939414243

454546464748

53535656575858

61616263

666667676769707173737477

Page 4: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 4

Crown Splashes (continued) Accelerating Splashes and Tendrils Randomization Improvements and Suggestions Axis Setups

Export Path Manager Creating the GUI Evaluating the Input Changing the Export Path

Velocities from Ocean Statistical Spectrum Waves Definitions and Considerations Initial Velocities Velocity Calculations Improvements and Suggestions

coNTeNTs

79818586

88888990

9292939597

Page 5: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 5

welcome To waTeRliNe magaziNe!

Some of you might remember RF_magazine, a commercial special interest publication for RealFlow users. Although I had to discon-tinue 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 full-time 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 splash-es. And there is much more in my stash.

In the tradition of RF_magazine this issue deals with Python script-ing 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 feed-back is my motivation for creating the next issue.

Enough said! Enjoy this first issue of waterline magazine.

Happy reading,

Thomas Schlick

welcome To waTeRliNe magaziNe

Page 6: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 6

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 guar-antee that they will really work.

axis seTup aNd scale

RealFlow provides the possibility of defining which axis should be the scene’s 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!

Page 7: Waterline Real Flow Scripting

what iS Scripting?

Page 8: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 8

whaT is scRipTiNg

Scripting is the development of custom tailored applications – here inside of RealFlow. The script-ing language provides a set of statements and functions the programmer can use to solve a cer-tain 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 lan-guages 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.

RealFlow uses the Python language. Python is easy to understand, sup-ports all modern programming ap-proaches, and has an active developer community. It is also free, even for commercial applications, and you have access to hundreds of free ex-tensions – 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).

This API is the basis for the Software Development Kit (SDK). The SDK contains all the applica-tion-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

PY

VBA

MEL

JS

C++

whaT is scRipTiNg?

Page 9: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 9

When you expand the “Scripting Reference” tree you will see a long list of keywords. Most of them sound familiar, for example “Daemon”, “HY_Bub-bles”, or “Object”. Others, like “GUIFormDialog”, or “Vector”, are very ab-stract:

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 RealFlow‘s functions.

The SDK is your ticket to RealFlow‘s 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 in-side 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 re-quired. 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.

Scripted dust particles, emitted from a breaking object.

whaT is scRipTiNg?

Page 10: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 10

Most scripts also do not require higher mathematics and complex functions. In fact, the big ma-jority 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 RealFlow‘s Python script editors.• Paste the clipboard‘s 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 ;-)

whaT is scRipTiNg?

Page 11: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 11

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 script’s 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 2else: statement 3loop: statement 4 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.

whaT is scRipTiNg?

Page 12: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 12

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 don’t care about highly-opti-mized 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 automati-cally 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 script’s execution time. This also means that it is not possible to declare variables inside a comment, perform calculations, assemble vectors, etc. No matter what’s 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 script’s last line is a comment you will receive a syntax error.

whaT is scRipTiNg?

Page 13: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 13

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 place-holder 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 variable’s 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 variable’s value is 0 or if it is empty at the beginning of the script.

NamiNg

Let’s 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 = 27Name = "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 diffi-cult to differentiate them within a more complex script – as shown here:

A = 27B = "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 = 27Name = "Claudia"SecondName = "Beatrice"Weight = 56

whaT is scRipTiNg?

Page 14: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 14

It is important that a variable‘s 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 variable’s 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 RealFlow’s SDK commands cannot be used a variable names.

When you perform a calculation or reassign the variable somewhere else in the script the old val-ue will be overwritten:

Name = "Claudia"Name = "Lydia"

If the variable’s value is printed the output will be “Lydia”.

whaT is scRipTiNg?

Page 15: Waterline Real Flow Scripting

Data typeS

Page 16: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 16

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 = 27Name = "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 = 1100insuranceNumberLydia = 2200insuranceNumberClaudia + insuranceNumberLydia = 3300

daTa Types

Page 17: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 17

lisTs

Scalars can only store a single value, but we also need structures for multiple, maybe even mil-lions 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 list’s elements are always enclosed be-tween 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 entriesIndex 0 1 2 3 4 5

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 meth-od 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).

daTa Types

Page 18: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 18

Always mind the leading tab. Without it you will get a syntax error and the script will not be exe-cuted):

fruitList = ["peach","apple","apricot","pineapple","orange","strawberry","mango"]

for element in fruitList: … print the element’s 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 list‘s number of elements – also called length – and com-pare 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 polygon‘s ver-tices, but they are also often used for pairs of values, or complete data sets. Imagine you want to store a particle‘s 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)]

daTa Types

Page 19: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 19

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:

daTa Types

Page 20: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 20

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.5velocityY = 1.6velocityZ = 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:

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.

X

Y

< x0,y0>

lenght =

mag

nitude

x0

y0

daTa Types

Page 21: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 21

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.0velocityMagnitude = 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 + vector2Subtraction vector1 – vector2Division vector1 / vector2

Vector multiplication has to be performed manually, because the * operator is reserved for anoth-er function – the dot product (see table next page):

vector1 * vector2 = (x1 * x2, y1 * y2, z1 * z2)

daTa Types

Page 22: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 22

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 brack-ets 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()velocityY = velocityVector.getZ()velocityZ = velocityVector.getZ()

A different notation is:

velocityX = velocityVector.xvelocityY = velocityVector.yvelocityZ = 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 ele-ment, 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)

daTa Types

Page 23: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 23

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 wheth-er 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. In-stead 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 = 32Subtraction - 64 - 30 = 34Multiplication * 10 * 12 = 120Division / 80 / 10 = 8Exponentation ** 12 ** 2 = 144Modulus % 28 % 7 = 0String 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 val-ue, and the increment is added again:

+= value = value + increment-= value = value - increment*= value = value * increment/= Value = value / increment

daTa Types

Page 24: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 24

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 particleelse: stop particle emission

daTa Types

Page 25: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 25

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

daTa Types

Page 26: Waterline Real Flow Scripting

realFlow'SScripting eDitorS

Page 27: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 27

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 through-out 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 com-mands.

RealFlow's scRipTiNg ediToRs

Page 28: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 28

simulaTioN scRipTs

As the headline indicates, these scripts are created to influence simulations. One of the best-known 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.

RealFlow's scRipTiNg ediToRs

Page 29: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 29

You can also see a “Master” tab with several entries and functions. This tab is only there for com-patibility 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 dur-ing simulation.

When your script is executed under “StepsPre” or “StepsPost” it will be executed once per sub-step. RealFlow uses adaptive substeps, so in the worst case, it might happen that the script is exe-cuted 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 possibleto load them with “Add Script From File”.

The checkboxes allow you to activate and deactivate a script on demand, and scripts can be shift-ed from one event to another with drag and drop.

RealFlow's scRipTiNg ediToRs

Page 30: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 30

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 daemoNs shelF > scRipTed > Node paRams > scRipTed > ediT

Scripted RealWave deformer Realwave Node > RighT-click > add wave > scRipTed

Scripted standard particle fluid emiTTeR Node > Node paRams > paRTicles > Type > scRipT

RealFlow's scRipTiNg ediToRs

Page 31: Waterline Real Flow Scripting

FirSt StepS

coin Stacker

Page 32: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 32

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 varia-ble 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 = 7coinHeight = 1.0coinDiameter = 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.

FiRsT sTeps – coiN sTackeR

Page 33: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 33

An illustration will help us to find this offset:

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.50.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

1.01.0

2.0

3.0

4.0

5.0

6.0

7.0

6.5

5.5

4.5

3.5

2.5

1.5

0.5

FiRsT sTeps – coiN sTackeR

Page 34: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 34

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 intro-duced 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 pro-vides 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)

FiRsT sTeps – coiN sTackeR

Page 35: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 35

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 = 7coinHeight = 1.0coinDiameter = 1.5

for coinNumber in range(0, numberOfCoins): coin = scene.addCylinder(50,1) offset = coinNumber * coinHeight + coinHeight / 2

positionVector = Vector.new(0, offset, 0) 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.

FiRsT sTeps – coiN sTackeR

Page 36: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 36

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.

FiRsT sTeps – coiN sTackeR

Page 37: Waterline Real Flow Scripting

FirSt StepS

particle Swapping

Page 38: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 38

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.

FiRsT sTeps – paRTicle swappiNg

Page 39: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 39

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 emit-ters 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.

FiRsT sTeps – paRTicle swappiNg

Page 40: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 40

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()getVorticitygetAge()

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.

FiRsT sTeps – paRTicle swappiNg

Page 41: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 41

So, the correct version is

< pseudo code >

for particle in particleList: particleVelocity = particle.getVelocity() particleVelocityMagnitude = particleVelocity.module() if (particleVelocityMagnitude >= velocityThreshold): … 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.5waterEmitter = scene.get_PB_Emitter("Circle01")foamEmitter = scene.get_PB_Emitter("Container01")particleList = waterEmitter.getParticles()

for particle in particleList: particleVelocity = particle.getVelocity() particleVelocityMagnitude = particleVelocity.module()

if (particleVelocityMagnitude >= velocityThreshold): 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” val-ues, and thresholds. If you want to separate water and foam visually just assign another colour the “Container01” node:

coNTaiNeR01 > Node paRams > Node > coloR

FiRsT sTeps – paRTicle swappiNg

Page 42: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 42

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.5waterEmitter = scene.get_PB_Emitter("Circle01")foamEmitter = scene.get_PB_Emitter("Container01")particleList = waterEmitter.getParticles()

for particle in particleList: particleVelocity = particle.getVelocity() particleVelocityMagnitude = particleVelocity.module() velocityVariation = random.uniform(-0.25, 0.25)

if (particleVelocityMagnitude >= velocityThreshold + velocityVariation): particlePosition = particle.getPosition() particleId = particle.getId()

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

FiRsT sTeps – paRTicle swappiNg

Page 43: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 43

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 addi-tions 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.

FiRsT sTeps – paRTicle swappiNg

Page 44: Waterline Real Flow Scripting

FirSt StepS

colour BlenDing

Page 45: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 45

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 tempera-ture 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 rele-vant 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 informa-tion 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 over-written with the next frame – the calculated value will be lost. But, how can we solve this prob-lem? The solution is particle swapping. We need a modified version of our last script and intro-duce a third emitter (“Container01”).

FiRsT sTeps – colouR bleNdiNg

Page 46: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 46

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()

FiRsT sTeps – colouR bleNdiNg

Page 47: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 47

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 orig-inal 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 setTempera-ture(1.0):

< py >

for particle1 in particleList1: particle1Position = particle1.getPosition() particle1Velocity = particle1.getVelocity() particle1Id = 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 other-wise 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).

FiRsT sTeps – colouR bleNdiNg

Page 48: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 48

< py >

for particle3 in particleList3: temperatureCurrent = 0 temperatureAverage = 0 neighbourParticleList = particle3.getNeighbors(0.1) temperatureCurrent = 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 … calculate an average temperature … set the new 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 val-ues 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

FiRsT sTeps – colouR bleNdiNg

Page 49: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 49

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.

FiRsT sTeps – colouR bleNdiNg

Page 50: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 50

< py >

searchRadius = 0.1

emitter1 = scene.get_PB_Emitter("Circle01")emitter2 = scene.get_PB_Emitter("Circle02")emitter3 = 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)

FiRsT sTeps – colouR bleNdiNg

Page 51: Waterline Real Flow Scripting

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.

Page 52: Waterline Real Flow Scripting

cuStom-tailoreD

uSer interFaceS

Page 53: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 53

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 fash-ionable 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. De-fining 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, be-casue they will not be fixed anymore, but user-controlable:

numberOfCoins = 7coinHeight = 1.0coinDiameter = 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 dia-logue.

cusTom-TailoRed useR iNTeRFaces

Page 54: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 54

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 state-ment 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 mo-ment where the actual variable names will be used, as you will see on the following page.

cusTom-TailoRed useR iNTeRFaces

Page 55: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 55

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 = 7coinHeight = 1.0coinDiameter = 1.5guiForm = GUIFormDialog.new()

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") coinHeight = guiForm.getFieldValue("Coin height") coinDiameter = guiForm.getFieldValue("Coin diameter")

for coinNumber in range(0, numberOfCoins): coin = scene.addCylinder(50,1) offset = coinNumber * coinHeight + coinHeight / 2 positionVector = Vector.new(0, offset, 0) scaleVector = Vector.new(coinDiameter, coinHeight, coinDiameter)

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

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

cusTom-TailoRed useR iNTeRFaces

Page 56: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 56

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.

cusTom-TailoRed useR iNTeRFaces

Page 57: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 57

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 simula-tion 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 fol-lowing 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

cusTom-TailoRed useR iNTeRFaces

Page 58: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 58

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)

cusTom-TailoRed useR iNTeRFaces

Page 59: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 59

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)

cusTom-TailoRed useR iNTeRFaces

Page 60: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 60

< 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] foamEmitterName = emitterNameList[foamEmitterIndex] scene.setGlobalVariableValue("waterEmitterName", waterEmitterName) scene.setGlobalVariableValue("foamEmitterName", foamEmitterName) scene.setGlobalVariableValue("velocityThreshold", velocityThreshold)

< py | framespre >

waterEmitterName = scene.getGlobalVariableValue("waterEmitterName") foamEmitterName = scene.getGlobalVariableValue("foamEmitterName") velocityThreshold = scene.getGlobalVariableValue("velocityThreshold") waterEmitter = scene.get_PB_Emitter(waterEmitterName) foamEmitter = scene.get_PB_Emitter(foamEmitterName)

particleList = waterEmitter.getParticles()

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

cusTom-TailoRed useR iNTeRFaces

Page 61: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 61

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 differ-ent 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 man-ually. 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 var-iables: 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

cusTom-TailoRed useR iNTeRFaces

Page 62: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 62

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 sim-ple 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 com-bine 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)

cusTom-TailoRed useR iNTeRFaces

Page 63: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 63

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).

guiscusTom-TailoRed useR iNTeRFaces

Page 64: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 64

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.

cusTom-TailoRed useR iNTeRFaces

Page 65: Waterline Real Flow Scripting

crown SplaSheS

Page 66: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 66

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 pub-lication 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 under-stand 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 pro-ject, 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 parametriza-tion, 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.

cRowN splashes

Page 67: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 67

FoRce oR velociTy?

We also have to make a fundamental decision now about whether we want to use forces or ve-locities 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 any-where 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.

cRowN splashes

Page 68: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 68

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 acceler-ate them separately, while the remaining particles should not be moved.

The initial variables are:

< py | batch >

splashRadius = 0.5splashWidth = 0.05splashParticles = []helper = scene.getObject("Null01")hPos = helper.getParameter("Position")hPosH = Vector.new(hPos.getX(), 0, hPos.getZ())emitter = scene.get_PB_Emitter("Sphere01")particleList = emitter.getParticles()

splash radius

splash width

< 0,0 >

cRowN splashes

Page 69: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 69

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 = particle.getPosition() pPosH = Vector.new(pPos.getX(), 0, pPos.getZ()) p_hDistance = pPosH.distance(hPosH)

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)

cRowN splashes

Page 70: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 70

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 par-ticles 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 = 0stopFrame = 20

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

cRowN splashes

Page 71: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 71

In the "FramesPre" part of the script, the startFrame and stopFrame variables will be read:

< py | framespre >

splashParticles = scene.getGlobalVariableValue("splashParticles")curFrame = scene.getCurrentFrame()startFrame = scene.getGlobalVariableValue("startFrame")stopFrame = 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 struc-ture. 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 vectorvelDir.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)

cRowN splashes

Page 72: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 72

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 velocityvelV = 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 di-rection vectors. It will substitute the current code:

< py | framespre >

splashParticles = scene.getGlobalVariableValue("splashParticles")velH = scene.getGlobalVariableValue("velH")velV = scene.getGlobalVariableValue("velV")hPosH = scene.getGlobalVariableValue()curFrame = scene.getCurrentFrame("hPosH")startFrame = scene.getGlobalVariableValue("startFrame")stopFrame = 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 individual-ly we are able to control the crown's width. Our test uses a "Sheeter" daemon to fill the occur-ring holes, and shows the typical shape. We can proceed with the next task: the creation of the splash's tendrils.

cRowN splashes

Page 73: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 73

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 ad-vanced 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 even-ly 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.

cRowN splashes

Page 74: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 74

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

360 ° / 8 = 45 °

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 de-pendent 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.

X

Z

r

α

cRowN splashes

Page 75: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 75

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

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.mathradians = degPerTendril * math.pi / 180.0

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

X

Z

r = 1

α

cos (α)

sin

)

cRowN splashes

Page 76: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 76

The coordinates from the previous page are just for a single degPerTendril angle, but we neednumOfTendrils angles. A loop will solve this:

for i in range(0, numOfTendrils): radians = i * degPerTendril * math.pi * 180.0 xCoord = math.cos(radians) * splashRadius + hPos.getX() zCoord = 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 positionstartFrame = 0 stopFrame = 20 velH = 1.7 velV = 2.0splashRadius = 0.5 splashWidth = 0.05 numOfTendrils = 8tendrilSeeds = [] splashParticles = [] helper = scene.getObject("Null01") hPos = helper.getParameter("Position") hPosH = Vector.new(hPos.getX(), 0, hPos.getZ()) emitter = scene.get_PB_Emitter("Sphere01") particleList = emitter.getParticles()

# Find the ring/splash particles and add them to splashParticles for particle in particleList: pPos = particle.getPosition() pPosH = Vector.new(pPos.getX(), 0, pPos.getZ()) p_hDistance = pPosH.distance(hPosH) if (p_hDistance >= splashRadius - splashWidth / 2 and p_hDistance <= splashRadius + splashWidth / 2): splashParticles.append(particle)

cRowN splashes

Page 77: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 77

# Get the positions of the tendril seedsfor i in range(0, numTendrils): radians = i * degPerTendril * math.pi / 180.0 xCoord = math.cos(radians) * splashRadius + hPos.getX() zCoord = math.sin(radians) * splashRadius + hPos.getZ() tPosH = 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 varia-ble:

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.

splashWidthsearchRadius

cRowN splashes

Page 78: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 78

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.05tendrilIds = []

# 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 in-teresting. 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.

cRowN splashes

Page 79: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 79

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 spla-hes. 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() if (splashId in tendrilIds): factor = 1.25 else : factor = 0.75

In the next step, the factor has to be multiplied with the user-defined velocites and the XZ com-ponents 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.

cRowN splashes

Page 80: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 80

Append the following code to the previous part of the "FramesPre" script:

< py | framespre >

sPosH = splash.getPosition() velDir = sPosH - hPosH velDir.normalize() velVecX = velH * velDir.getX() * factor velVecY = velV * factor velVecZ = velH * velDir.getZ() * 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 cur-rent 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.

cRowN splashes

Page 81: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 81

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 artifi-cial for many applications. We need a more random appearance. Adding some noise is not diffi-cult, 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 cal-culated 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.2velVariation = []

# 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

cRowN splashes

Page 82: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 82

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

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 definitionsvelVariation = 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] else : factor = 0.75 + velVariation[counter]

# Do the velocity part here, assemble the vector, etc. # 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 im-provements, but the most important goal with this project was to show you concepts.

velVariation contains random values between -0.2 and 0.2

-0.13 0.07 0.12 0.18 -0.02 0.13 -0.17 0.11 0.16 -0.04 -0.10-0.05

0 1 2 3 4 5 6 7 9 10 118

0 1 2 3 4 5 6 7 9 10 118

Value

counter

Index

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

cRowN splashes

Page 83: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 83

< py | batch >

import math, random

# Define all initial variables, get the particles, and the helper object's position startFrame = 0 stopFrame = 15 velH = 1.7velV = 2.0splashRadius = 0.5 splashWidth = 0.05 searchRadius = 0.05 numOfTendrils = 8 velRange = 0.5 degPerTendril = 360.0 / numOfTendrils tendrilSeeds = [] tendrilIds = [] splashParticles = [] velVariation = [] helper = scene.getObject("Null01") hPos = helper.getParameter("Position") hPosH = Vector.new(hPos.getX(), 0, hPos.getZ()) emitter = scene.get_PB_Emitter("Sphere01") particleList = emitter.getParticles()

# Find the ring/splash particles and add them to splashParticlesfor particle in particleList: pPos = particle.getPosition() pPosH = Vector.new(pPos.getX(), 0, pPos.getZ()) p_hDistance = pPosH.distance(hPosH) 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 xCoord = math.cos(radians) * splashRadius + hPos.getX() zCoord = math.sin(radians) * splashRadius + hPos.getZ() tPosH = Vector.new(xCoord, 0.0, zCoord) tendrilSeeds.append(tPosH)

cRowN splashes

Page 84: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 84

# Get the positions of the tendril seedsfor 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)

# 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() velH = scene.getGlobalVariableValue("velH") velV = scene.getGlobalVariableValue("velV")hPosH = scene.getGlobalVariableValue("hPosH") splashParticles = scene.getGlobalVariableValue("splashParticles") tendrilIds = scene.getGlobalVariableValue("tendrilIds") startFrame = scene.getGlobalVariableValue("startFrame") stopFrame = scene.getGlobalVariableValue("stopFrame") velVariation = scene.getGlobalVariableValue("velVariation") counter = 0 if (curFrame >= startFrame and curFrame <= stopFrame): for splash in splashParticles: if (splash.getId() in tendrilIds): factor = 1.25 + velVariation[counter] else : factor = 0.75 + velVariation[counter]

cRowN splashes

Page 85: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 85

sPosH = splash.getPosition() velDir = sPosH - hPosH velDir.normalize() velVecX = velH * velDir.getX() * factor velVecY = velV * factor velVecZ = velH * velDir.getZ() * 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 in-

crease 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.

cRowN splashes

Page 86: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 86

axis seTups

So far, all scripts have been developed for a Y-based axis setup, but this can be considered a spe-cial 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.5velocityVector = 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)else: velocityVector = Vector.new(0.0, verticalVelocity, 0.0)

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

cRowN splashes

Page 87: Waterline Real Flow Scripting

export manager

Page 88: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 88

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 work-flow.

With the following batch script (F10) it is possible to specify new export paths for a scene's stand-ard 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:

expoRT paTh maNageR

Page 89: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 89

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] emitterName = emitter.getName() fieldName = emitterName+" export path" 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 al-most 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)

expoRT paTh maNageR

Page 90: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 90

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] emitterName = emitter.getName() fieldName = emitterName+" export path" 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.

expoRT paTh maNageR

Page 91: Waterline Real Flow Scripting

VelocitieS From ocean StatiStical Spectrum waVeS

Page 92: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 92

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 dis-tance, 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)

velociTies FRom oceaN sTaTisTical specTRum waves

Page 93: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 93

iNiTial velociTies

The most obvious idea is to calculate the velocities and transfer them to the RealWave node's par-ticle 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 parti-cle 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 “Con-tainer01” 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 swap-ping algorithm in a slightly modified form. Add a new script to the “SimulationPre” branch of the “Simulation Flow” panel.

velociTies FRom oceaN sTaTisTical specTRum waves

Page 94: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 94

Here is the first part of our script:

< py | simulationpre >

rwSurface = scene.getRealwave()rwVertices = rwSurface.getVertices()emitter = scene.get_PB_Emitter("Container01")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 parti-cles will be increased with every start of the simulation. With scene.enablePaint(False) the cre-ation 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 chal-lenge 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 definitionsvPositionsOld = []

# Append the blue lines to the exsiting code:for vertex in rwVertices: vPos = vertex.getPosition() emitter.addParticle(vPos, nullVec) vPositionsOld.append(vPos)

scene.setGlobalVariableValue("vPositionsOld", vPositionsOld)scene.enablePaint( True )

velociTies FRom oceaN sTaTisTical specTRum waves

Page 95: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 95

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")rwSurface = scene.getRealwave()rwVertices = rwSurface.getVertices()dT = 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 definitionscounter = 0

for vertex in rwVertices: vPos = vertex.getPosition() posDiff = vPos - vPositionsOld[counter] ... counter += 1

velociTies FRom oceaN sTaTisTical specTRum waves

Page 96: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 96

It is time to apply our velocity formula. posDiff is a vector and therefore we have to treat its el-ements 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")rwSurface = scene.getRealwave()rwVertices = rwSurface.getVertices()dT = 1.0 / scene.getFps()counter = 0

emitter.removeAllParticles()

for vertex in rwVertices: vPos = vertex.getPosition() posDiff = vPos - vPositionsOld[counter] vel = Vector.new(posDiff.getX() / dT, posDiff.getY() / dT, posDiff.getZ() / dT) 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 no-tation 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)

velociTies FRom oceaN sTaTisTical specTRum waves

Page 97: Waterline Real Flow Scripting

waterline magazine | Scripting with RealFlow 97

We save the current positions and assign them as the old ones – the previous values will be over-written 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")

velociTies FRom oceaN sTaTisTical specTRum waves

Page 98: Waterline Real Flow Scripting

waterline magazineScripting with RealFlow

© 2015 by Thomas Schlick | Next Limit Technologies

Commercial distribution is prohibited. Translations only with permission.