RF_magazine Scripting With RealFlow - Introduction

became one of the most important parts of the package. On the other .... Of course, the manual is an obvious source for ... existing Python language by hundreds of new statements ...... script skips four particles, because with the fifth particle.
41MB taille 3 téléchargements 437 vues
RF_magazine Scripting With RealFlow - Introduction REALFLOW USER MAGAZINE FLUID DYNAMICS :: PHYSICAL SIMULATION :: SCRIPTING :: CONNECTIVITY :: REALWAVE

Basics Of Python Scripting With RealFlow Learn how to use Python inside RealFlow

First Steps Accessing particles and changing parameters

Creating GUIs Write out your own user interfaces

The Foam Project Learn, how to create foam and spray and many more ...

RF_magazine

1

Editorial Welcome To RF_magazine This issue deals with scripting. With RF4, Next Limit introduced a Python interface. Remembering the first reactions in the “old” official forum, scripting wasn't very popular at that time. But in the meantime, scripting became one of the most important parts of the package. On the other hand, many users are still afraid of using this mighty tool. When 3d artists think about scripting and programming, many still have the impression that you need a deep understanding of maths. This edition wants to show that you can achieve stunning effects with little effort. Python scripting in RealFlow is your friend, because it makes life so much easier in many ways. With just a few lines of basic code it’s possible to automate many processes.

Finally, I'd like to say thanks to the Next Limit team for certifying this magazine and useful comments and critics. Sincerely yours, Thomas

Thomas Schlick Nuremberg/Germany [email protected]

This magazine is certified by Next Limit:

The principles are fairly the same with each script. RF_magazine uncovers these techniques with lots of examples and projects. With this issue you can discover the advantages and power of scripting. Next Limit chose Python, because it's an easy to learn and wide spread language. In many terms, Python almost reads as spoken English. This feature makes Python well suited for beginners. With growing knowledge and experience, you'll get a deep insight into Python and a better understanding of RealFlow. Altering parameters with a self-written program is completely different from changing values within in a Node Params window. But there's one thing, even the best book or tutorial can't do for you: Reading carefully and trying to understand what's going on in a particular script. The basics, like Data Types or simple Vector maths are very important and can't be left out. Programming is also learning by doing! So, don't hesitate and dive into scripting with RealFlow. After a few hours you'll be able to write your own first script. With coming issues of RF_magazine, scripting will always be an important part.

Scripting With RealFlow

RF_magazine

2

Editorial Welcome To RF_magazine

Page 1

The Power Of Scripting A First Glance What Is Scripting The Advantage Of Scripting Python And RealFlow Where To Start?

Page 4 Page 4 Page 4 Page 5 Page 5

The Basics Of Python Scripting In RealFlow Variables - First Steps With Scalars Other Types Of Variables Creating Lists Creating Dictionaries Appending Values Operators Data Types Vectors Calculating With Vectors Notation And Syntax

Page 6 Page 7 Page 8 Page 8 Page 9 Page 9 Page 11 Page 13 Page 13 Page 16

Getting Started Setting The Preferences The Scripting Windows Scripted Objects

Page 18 Page 19 Page 20

Your First Steps Calling Emitters And Objects Accessing Particles Building Vectors If & Else

Page 22 Page 23 Page 25 Page 27

Changing Attributes With Get & Set Introduction How To Use Get & Set Working With Particles Conclusion

Page 29 Page 29 Page 34 Page 36

Contents

RF_magazine

3

Creating Graphical User Interfaces What’s A Graphical User Interface The Structure Of RealFlow GUIs File And Node Pickers

Page 37 Page 37 Page 40

The Foam Project Project Overview The Nature Of Foam And Spray Creating Foam Controlling Values Through Objects Collision Based Foam Creation Optimizing The Script Threshold Parameters Credits

Page 41 Page 41 Page 42 Page 46 Page 47 Page 49 Page 50 Page 50

Fun With Freeze Project Overview Freezing Time And Particles Circular Particle Freezing Relaxing Particles With Freeze Inverting The Effect Summary

Page 52 Page 52 Page 53 Page 55 Page 55 Page 56

Examples And Ideas Overview Image Based Emitter Speed Boxed Daemons Exporting Position Data To AfterEffects®

Page 57 Page 57 Page 58 Page 59

Appendix Reserved and Forbidden Words About The Scene Files Legal Notes

Page 64 Page 65 Page 65

Contents

RF_magazine

4

The Power of Scripting A First Glance Since version 4, scripting is an important part of RealFlow. Many well known studios asked for this feature and Next Limit met this demand. The implementation of a scripting interface was one of the major leaps in the development of RealFlow and took almost two years. Unfortunately there are still many users around, who want to explore the power of scripting, but often don't know where to start. Of course, the manual is an obvious source for information regarding scripting. Real Flow’s online help already contains a extensive introduction to scripting. There, a lot of fundamental questions and ideas behind scripting are explained. Anyhow I met many people in search of a compendium or a comprehensive tutorial, covering the entire range from beginning to intermediate or even advanced scripting. At realflowforum.com I already published a series of free tutorials for beginners, and I had positive feedback. This feedback encouraged me to deal with scripting in this issue.

What Is Scripting? Scripting is the process of developing programs for custom tailored applications. In our case, inside of RealFlow. For scripting, a programming language is needed. This language provides a set of statements and functions the programmer can use, to write his own scripts. The main difference between scripting and, e.g. C or Java programming is the way, the code is treated: Scripts are normally interpreted. You all know this from web browsers. JavaScript and PHP are very popular examples. 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 directly processed.

Unlike scripts, programs like RealFlow are compiled. This means, the code will be preprocessed and converted into a completely different format. Compiling has some great advantages and the most obvious are speed and visibility. A compiled program is much faster than an interpreted script, because the compiler optimizes the code for certain hardware needs. Simultaneously the program is converted and the content becomes invisible. This helps to protect your development. Today, there are already some compilers available for scripting languages.

The Advantage Of Scripting I was talking about the benefits of compiled programs. Now, you'll surely ask, why does RealFlow use a scripting language and not C/C++ or Java? With scripting languages you don't have to deal with compiling. This sometimes can be a very heavy task to solve and you need certain compilers for different operating systems and hardware requirements. With the available scripting languages you don't have to care about all these things. You just start coding, execute the script and the interpreter does everything else. Many people also say that it's easier to start with a scripting language, but in my personal opinion, it's also a matter of what you want to do. Both, interpreted and compiled languages have clear benefits and disadvantages. Modern scripting languages are very powerful tools with hundreds of extensions and libraries, mighty built-in functions and sophisticated methods, like object orientation. Often used scripting languages are Perl, Python, JavaScript, VBScript, PHP, Tcl and also AppleScript. During the first years of the WWW, Perl was the main language for online applications. After the rising of PHP and Python, Perl lost its importance, but still has a vivid and strong community.

Scripting With RealFlow

RF_magazine

5

The Power of Scripting Python And RealFlow Now, Python is an integral part of RealFlow, but there is much more: The Next Limit developers extended the existing Python language by hundreds of new statements and functions. You can now directly access almost any object and attribute of RealFlow via Python scripting. This fantastic development opens the door to an infinite variety of possibilities. RealFlow comes together with a complete version of Python and the user doesn't have to struggle with installation directories and libraries. With the installation of RealFlow you get the complete Python package, ready to start.

Another misunderstanding is that you have to be a maths genius to develop good programs. Of course a basic knowledge of mathematics is useful, but not mandatory. If you just want to automate some processes, you won't need very much mathematics. A very good starting point for each programming novice is to create a simple example with a great feeling of success. Since RealFlow is a 3D application with a high visual impact, it's rather easy to find those examples. No matter what you're doing, it will directly influence the objects on your screen.

Some of the main advantages of Python are: • Free license, even for commercial use • Common and wide spread language • Perfect for beginners • OS independent (Unix, Linux, OS X, Windows etc.) • Hundreds of online resources, books and forums are available • Standard scripting language for many applications • Code is platform independent and easy to exchange Fig 1. Script affected particles of an emitter

Python scripting in RealFlow lets you directly address object, daemon, and emitter parameters and alter them.

Where To Start? One of the most important questions for beginners is where to start. Many users are doubtful and insecure when they think about writing their own scripts. One very common prejudice is that scripts always have to be complicated and highly sophisticated “artworks”. Just discard this attitude and think of it as a normal language. Not every sentence has to be a poem, mostly it's better to speak in a clear and easy way.

Maybe you've already started to learn a programming language? Then you also might have seen the famous “Hello World” program. This is always a nice start, but for our purposes it's not suitable. We're after completely different things. Before we can kick off, it's indispensable to learn the basics of Syntax and Notation to understand the principles of how Python works.

Scripting With RealFlow

RF_magazine

6

The Basics of Python Scripting in RealFlow Variables - First Steps With Scalars Variables are the key to everything, independent of the used programming language. A Variable could be considered as a placeholder. You can fill this placeholder with any content you like. In programming languages, Variables have to be declared. This means that you have to introduce a name for your Variable and assign a value to it. Imagine the following easy example. We got some information from a person: 25, female, Claudia. Without setting these information into a logical context, we can't capitalise on these data. I'm sure you already started bringing these information into an order: Name

= "Claudia"

Age

= 25

Gender = "female"

Now these attributes start making sense. You assigned certain properties to Claudia and you already declared three Variables. The names of these Variables are Name, Age and Gender, their properties (or values) are Claudia, 25 and female. You can also see, that it's very important to choose strong and meaningful names for your Variables. In this case we declare our Variables like this: A = "Claudia" B = 25 C = "female"

Formally that's absolutely correct, but the information isn't very clear, especially when there are similar values or properties: A = "Claudia" B = 25 C = "Beatrice" D = 56

After a while, you'd certainly have no clue what the Variable names are standing for. With Variables declared like this, it's no problem: First_Name

= "Claudia"

Age

= 25

Second_Name

= "Beatrice"

Weight

= 56

It's very important that Variables always share the same name over the entire script, because Python and other languages are case sensitive. In a script Gender is not the same as gender. Variables can be declared wherever they're needed, but it's a good idea to find common places for your Variables and arrange them clearly. Never use special characters for Variables names. The range of allowed characters is a-z, A-Z, 0-9 and the underscore. Variables must start with a character, not with a number. There are also some reserved and forbidden words (see page 64). These names must not be used for Varialbe definitionVariables are a very flexible facility. You can change their values within the script and you can assign numbers, characters, and strings. But each of the Variables above can only carry one value. This type is called a Scalar. Scalars are certainly the most used Variable type with each programming language. We need them everywhere and they are easy to use and flexible. By defining a new value for an existing Variable, the old value will be substituted: Name = "Claudia" Name = "Lydia"

If we had a script to print out the variables’ content, the result would be Lydia. Only the very last value will be used. An important convention is that strings and characters have to be written in quotation marks. With numbers this is not necessary. If you're writing numbers within quotation marks, they´ll be treated as a string.

Scripting With RealFlow

RF_magazine

7

The Basics of Python Scripting in RealFlow Other Types Of Variables As you've learned, a Scalar can only carry one value, but there also are Variables, you can fill with two or more values. Python programmers differentiate between two types: Lists and Dictionaries. A List is something, we certainly need very often with Python and RealFlow. You can consider a List as a cupboard with drawers. Each value has it's own drawer, and they're all arranged in a fixed order. The numeration of these drawers starts with 0 (”Zero”). Python also starts the numeration of Lists with 0. Let's assume that each drawer contains coloured marbles: 0 = red, 1 = blue, 2 = green, 3 = yellow, 4 = purple By opening drawer number 2, you'll have access to the green marbles, opening number 0, will lead to red ones.

In difference to Lists, it's not necessary to call the entries via a fixed index. In Dictionaries we use search keys. A very good example for a Dictionary is a private phone or address book, in which we often see pairs of values, e.g. a name and a phone number. These pairs are stuck together and we can't identify one without the other: Claudia Lydia Agnes Susan

: 54358 : 43663 : 65586 : 94343

Imagine the phone book from your cell phone. By entering a name, the software prints out the according phone number. That's exactly the way, a Dictionary is working. It doesn't matter, where the pair entries are located within the book, because they'll always be identified via a search key. In RealFlow Dictionaries are rarely used, but nevertheless there might be some cases, where you need them. In RealFlow, Dictionaries can be useful in conjunction with GUIs (see page 37). You can store a number of names or values, which will be called with a certain input or selection. By nesting a List inside a Dictionary, it’s even possible to store complete parameter sets for all kinds of RealFlow objects. Python knows some special functions to read out List elements from particular positions. In RealFlow, Lists are often needed, since object or particle groups are always stored within this Variable Type.

Fig 2. A List can be used to arrange values and entries

The List provides a container with a fixed sequence. Whenever you want to have purple marbles, you have to call position number five. But wouldn't it be nice to have a method to arrange the marbles the way we want? The solution is called Dictionary. In a Dictionary, there's no sequence and all contents could be disordered.

Now, you know all three types of Variables for storing values in Python and respectively in RealFlow: Scalar, List and Dictionary. For Lists and Dictionaries you have to obey the same rules as for Scalars. The Variables names should be meaningful and special characters are forbidden. Always use strong names and comment your Variables and code segments. A comment is introduced with a # character. Just one final note: A List is sometimes called Array, and a Dictionary is also known as Hash or Associative Array.

Scripting With RealFlow

RF_magazine

8

The Basics of Python Scripting in RealFlow Creating Lists Creating a List structure in Python is an easy task. An empty List is written as:

The next method uses the stored indices. Since each value has a fixed position within the List, it's possible to address a colour using this index. Remember that List indices always start with 0.

my_list = []

colours = ["red","blue","green","yellow","purple"]

If you want to define the values directly with the creation of the List, the format is:

print selection

selection = colours[2]

Result: green

my_list = [value1,value2,value3, ...,value n]

For better understanding, I'd like to create a List with the colours of our marbles from page 7:

Another very important function with Lists is to find the total number of elements. This is also called the Length of a List. You can get it with:

colours = ["red","blue","green","yellow","purple"]

colours = ["red","blue","green","yellow","purple"]

As you can see, all colour names are between quotation marks. You don't need these marks for numbers:

print number_of_entries

number_of_entries = len(colours)

Result: 5

diameter = [2.0, 2.5, 3.0, 3.5, 4.0, 4.5]

In Python it's easy to find elements, stored in a List:

Creating Dictionaries

favourite_colour = "yellow"

You've already learned that elements in a Dictionary don't need a specific order to find them. A Dictionary uses key/value pairs to search through the content. An empty Dictionary can be created by typing

colours = ["red","blue","green","yellow","purple"] if (favorite_colour in colours): print "Your favourite colour is in stock." else: print "Sorry. Your favourite colour is not available." Result: Your favourite colour is in stock.

In this short example, I introduced a Scalar Variable, named favourite_colour. The value is yellow. Now, the in statement goes through the List in search of yellow. If yellow has been found, the script writes out a success message. If the value of favourite_colour (e.g. brown) isn´t part of colours, the result would be Sorry. Your favourite colour is not available.

my_dictionary = { }

Of course you can directly start entering values and performing a query: phonebook = { "Claudia":54358, "Lydia":43663, "Agnes":65586, "Susan":94343 } print phonebook["Agnes"] Result: 65586

Isn't that easy? You don't have to worry about indices, numeration or positions.

Scripting With RealFlow

RF_magazine

9

The Basics of Python Scripting in RealFlow Analogue to Lists, it's also possible to get the length of a Dictionary. The function calls all key-value pairs and stores the result in a predefined Scalar:

It's also possible to use Scalars and add their values to the Dictionary: new_name

phonebook = { "Claudia":54358, "Lydia":43663, "Agnes":65586, "Susan":94343 }

= "Helen"

new_number = 22986 phonebook[new_name] = new_number

number_of_entries = len(phonebook) print number_of_entries

For the moment this should be enough. You'll learn more about these data types in connection with scripts.

Result: 4

Appending Values

Operators

Now you know how to create Lists and Dictionaries manually and how to read out specific values. In RealFlow we often have to deal with thousands of particles. Collecting their data and writing them to a List by hand won't be a good solution. For this purpose, Python provides a special function called append. The append function adds values to the end of a List. This is important, since a List is working with index numbers. There are also functions to insert values at certain positions, but they're rarely used with RealFlow. In most cases you extend a List simply by appending a new value. This is the syntax for Lists:

Operators are one of the most important things in Python. With an Operator it's possible to perform basic arithmetic calculations and comparisons. And that's truly a bless. Without Operators, you wouldn't be able to compare particle velocities or positions against a Scalar and there would be no way to multiply or subtract values. You can find Operators anywhere in a script and Python knows a lot of them. The first group contains all arithmetical Operators:

colours = ["red","blue","green","yellow","purple"] new_colour = "orange" colours.append(new_colour)

Adding a value to Dictionary is slightly different. We don't need a fixed order and therefore the position of the new element isn't important. An entry of a Dictionary always consists of two elements: The key and its value. It's very important to define which one will serve as a key, respectively as a value. The pairs always have to stick together, but they can be located anywhere within the Dictionary: phonebook = { "Claudia":54358, "Lydia":43663,

+ * / ** % + *

Addition Subtraction Multiplication Division Exponentiation Modulus String concatenation String repetition

15 + 17 = 32 64 - 30 = 34 10 * 12 = 120 80 / 10 = 8 12 ** 2 = 144 28 % 7 = 0 “John” + “Doe” = JohnDoe “Hi” * 3 = HiHiHi

A very nice Operator is Modulus, but it needs some explanation. In general, Modulus tells you, if there's a remainder after performing a calculation or not. With Modulus it's possible to find out whether a number is even or not, for example. If the result is different from 0, the number is uneven. A result of 0 indicates an even number.

"Agnes":65586, "Susan":94343 } phonebook["Helen"] = 22986

The String Operators are rarely used with RealFlow and more important for text processing applications.

Scripting With RealFlow

RF_magazine

10

The Basics of Python Scripting in RealFlow The next group contains Operators for comparisons: < > = == !=

Less than Greater than Less than or equal Greater than or equal Equal Not equal

Equality is tested with “ ==”. The single “=” is just used for assignment, e.g. in Variable names:

We're testing, if the value of the current velocity is greater than the maximum velocity. This value is a threshold. Whenever a particle's velocity is greater than this limit, it’s just cut down to 7.5. If this condition is not fulfilled, the script calculates with the current value, stored in current_velocity. In this example, the condition is true, because current_velocity is actually greater than max_velocity. Another example using the equality Operator: max_y_position = 3.5

Name = “Claudia”

current_y_position = 2.9

The result you get with Comparison Operators is true or false. These are the two possibilities and there's nothing between:

if (current_y_position == max_y_position): attractor_force = 25 else: attractor_force = 10

velocity_a = 5.0 velocity_b = 7.3 if (velocity_a < velocity_b): print "This is true."

By swapping the Variables, the result would be false. In RealFlow you normally won’t calculate with true or false. It's just a means for making decisions, what should happen with your values or parameters. It can be considered as a crossover, where you also have two or more ways to go. Let's say, you want to limit the velocity of a certain particle: max_velocity = 7.5

At the moment, the observed particle reaches the max_y_position value, attractor force will be 25. Here, current_y_position is less than 3.5, and the condition is false. The assigned value for attractor_force is 10. A third group consists of so called Boolean Operators. The keywords are: and or not You´ll mostly need these Operators when you have to check more than one condition.

current_velocity = 8.2 if (cur_vel >= threshold_1 or cur_vel max_velocity): new_velocity = max_velocity else:

gravity_force = 9.81 else: gravity_force = 0

new_velocity = current_velocity

What happens here? The current velocity of a certain particle has been determined as 8.2. With an Operator

In this case, we´re checking, whether the current velocity lies between two threshold values. If the condition is true, the gravity force will be activated.

Scripting With RealFlow

RF_magazine

11

The Basics of Python Scripting in RealFlow The last class I'm talking about here, are Augmented Operators. This is a very special class and mostly used for abbreviations. In some cases, it's necessary to write expressions like: while (particles): total_mass = total_mass + particle_mass

In this example, the script loops through all particles and sums up their individual masses to get a total mass value. This calculation could also be written as:

There are two subtypes of Integers in Python. The first one is called Plain Integer, or just Integer. The abbreviation of this type is known as int. This type is not endless and there's a maximum range. For the sake of calculation performance its necessary to differentiate, because Plain Integers are processed much faster. In 99% of all cases, the Plain Integer type will serve your needs. These are the specifications: int (Integer or Plain Integer) −2,147,483,648 to +2,147,483,648

while (particles): total_mass += particle_mass

This range is also called 32-bit precision and it's the minimum for all modern operating systems. Most operating systems also support 64-bit precision. The valid range for this type is:

The most common Operators are +=

-=

/=

*=

**=

%= −9,223,372,036,854,77,.808 to +9,223,372,036,854,775,808

There are some more Operators in Python, but they're not often used with RealFlow. By knowing the discussed classes, you already have the tools to create your own conditional expressions and basic calculations.

long (Long Integer) In Python this subtype has infinite precision, but the effective length of your number depends on the amount of RAM in your computer.

Data Types Coders have to distinguish between various Data Types. At school you probably already met most of them and with RealFlow you will need these types again. The most common format is surely the Integer type. Integers are wide spread and we use them in our daily life without thinking about them. Integers are numbers like:

The next, very important type is called Float. You've already seen Floats (also called floating point numbers) on the left. They were introduced as decimal fractures (1.8543). The most significant attribute of Floats is the decimal point. It's possible to calculate with decimal places of different length. Of course it's allowed to combine Integers and Floats together. Valid expressions would be:

-5, 2, 100, -4335, 757, 423843, -3289988, 45, 234, -775647 The set of Integers is infinite, it can be positive or negative and it doesn't contain fractures like 3.645, -1.3333 or 12.7. Fractured elements aren't members of the Integer family. Also fractures, apparently consisting of two Integers aren't valid elements: ¼ or ¾. Though the result of such a division can be an Integer!

75.543 / 8.4 + 1.3 * 67.55 5.8564 * 6 + 23.98 Floats can also be negative or positive. The length or precision of Floats strongly depends on your computer and operating system, but most likely you won't encounter any problems. The token in Python is simply float.

Scripting With RealFlow

RF_magazine

12

The Basics of Python Scripting in RealFlow The last Data Type, I'm talking about, is called Complex. In RealFlow this type will be rarely used and it's only mentioned for completeness. Complex numbers consist of two parts: A real and an imaginary part. The Complex type is therefore always written as a pair of numbers. The real number is just an Integer or a Float type, while the imaginary part must own a j character:

The used Data Types in this example are: levels width height

= int = float = float

e.g. 10 e.g. 2.5 e.g. 3.75

(3 + 6j) Even if the imaginary part is Zero, you have to write this value in conjunction with j: (12 + 0j) With Complex numbers it's possible to perform calculations as usual: (2 + 4j) * (8 + 3j) or (11 + 1j) * (7 + 5j) Fig 3. A tower based upon integer and float values

In Python, this special Data Type can be introduced with a complex statement. Now you've heard about int, long, float and complex, but you surely have no idea, how to use these types. One main application for Data Types are Variables, of course. The good thing is that you don't have to introduce each Variable with the appropriate index. Python automatically recognizes the correct type. But there are some other cases, where you have to determine, which type you'd like to use. One of these cases are GUIs - Graphical User Interfaces. You'll read about GUIs later, starting on p. 37, but I'd like to introduce some basic information about the usage of Data Types:

Another often used exercise is the conversion of Data Types. It's possible to translate a Float number into an Integer and vice versa. You could also translate a Long type into a Float number. But with all these transformations, you have to keep in mind that you're probably losing precision. Converting a Float type number into an Integer truncates the decimal places: int(3.256) = 3 int(9.999) = 9

This conversion only takes the Integer part of the original number, without caring about rounding. The same can be observed when converting Long to Float.

With GUIs it's often allowed and wanted, to enter custom values for initializing a calculation. Let's assume, your goal is to build a tower made of cubes. Within the GUI you can type in values for the number of stacks, the width and the height of a stack. This number has to be an Integer, because you can't build a tower with 5.278 floors. The width and height instead, can measure 2.5 or 7.84 units.

Scripting With RealFlow

RF_magazine

13

The Basics of Python Scripting in RealFlow Vectors

In RealFlow we're dealing with 3D space and the representation of a Vector needs a third coordinate:

Another Data Type are Vectors. Vectors are not a built-in element of Python, but there are some languages, supporting Vectors as a Data Type. The Python extension of RealFlow, for example, knows Vectors and they're fully integrated. Since they're not part of Python's standard installation, I´d call them a Pseudo Data Type here. In RealFlow, Vectors are one of the most important elements and this is another reason, why I chose to discuss them separately.

a = (x, y, z) or a = (1.0, 1.0, 1.0) If you're interested in real life examples or the mathematical background, I'd suggest to have a look at physics books or the internet, e.g. Wikipedia.

Calculating With Vectors

A Vector could be considered as an arrow, pointing into a certain direction. To describe a Vector's direction, we need at least two dimensions: x and y. By drawing a Vector into a coordinate system, we get the main properties: Direction and Magnitude. The Direction tells you, where the Vector is pointing at, while the Magnitude is the length of the arrow. y

It's not possible to calculate with Vectors directly. The most common calculation type with Vectors is Addition. For performing Vector calculations, you have to use either the individual elements x, y, z or its Magnitude. The coordinates of a Vector are called Scalars. You already heard about Scalars at the very beginning of this chapter. a = (5.0, 2.5, 4.2) b = (1.7, 3.9, 8.1) In this case we're looking for c = a + b. Maybe it's just

x

c = (ax + bx, ay + by, az + bz) c = (5.0 + 1.7, 2.5 + 3.9, 4.2 + 8.1) c = (6.7, 6.4, 12.3) This really is the answer, and it’s analogue with Vector Subtraction c = a - b:

Fig 4. Different vectors pointing at various directions

Vector coordinates can consist of Integers and Floats. The graphical illustration of a Vector might help to get an understanding, but it's not suitable for calculations. Therefore we have to find a notation. There are some established forms to represent a Vector, but according to RealFlow's notation, a Vector in RF_magazine is always written as: a= (x, y) or a = (1.0, 1.0)

c = (ax - bx, ay - by, az - bz) c = (5.0 - 1.7, 2.5 - 3.9, 4.2 - 8.1) c = (3.3, -1.4, -3.9) As you can see here, a Vector can also point into negative directions. The coordinates of a Vector are Scalars. For this reason it's possible to multiply the Vector components with another Scalar. It's likely that you will use this method very often, especially with forces.

Scripting With RealFlow

RF_magazine

14

The Basics of Python Scripting in RealFlow Multiplication with a Scalar is calculated this way: a = (4.8, 2.9, 1.4) s = 2.5 b=s*a b = (s * ax, s * ay, s * az) b = (2.5 * 4.8, 2.5 * 2.9, 2.5 * 1.4) b = (12, 7.25, 3.5) Another very important calculation rule is the Scalar Product. Don't mix it up with last rule, the Multiplication with a Scalar. These operations are completely different! The result of a Scalar Product is a Scalar and it can be considered as the multiplication of two Vectors: a = (1.7, 7.3, 3.3) b = (2.4, 0.7, 5.1) c=a*b c = ax * bx + ay * by + az * bz c = 1.7 * 2.4 + 7.3 * 0.7 + 3.3 * 5.1 c = 4.08 + 5.11 + 18.83 c = 26.02 A special form of the Scalar Product is the square of a Vector: c = a2 c = ax2 + ay2 +az2 Extracting the square root from this term, yields to a Vector's Magnitude (often called Norm or Length). | c | = ax2 + ay2 +az2 As you can see clearly here, the result of this operation is a Scalar, not a Vector. The lines around c indicate that its a Norm. In RealFlow you won't have to worry about this rule, because there's a built-in function for calculating the Scalar Product. In RealFlow it's called Dot Product, represented by an asterisk character ( * ).

The next rule, I'm talking about, is named Cross Product. The result of a Cross Product is also a Vector. The most common way to calculate a Cross Product is done by multiplying the components of two Vectors: a = (2.2, 6.1, 1.0) b = (0.7, 0.4, 3.8) c=axb cx = ay * bz - az * by cy = az * bx - ax * bz cz = ax * by - ay * bx cx = 6.1 * 3.8 - 1.0 * 0.4 cy = 1.0 * 0.7 - 2.2 * 3.8 cz = 2.2 * 0.4 - 6.1 * 0.7 cx = 23.18 - 0.40 = 22.78 cy = 0.70 - 8.36 = -7.66 cz = 0.88 - 4.27 = - 3.39 c = (cx, cy, cz) c = (22.78, -7.66, -3.39) This operation is also implemented in RealFlow by default and you don't have to calculate this manually. Many physical values can be determined with the Cross Product, e.g. the Lorentz Force or the torisonal moment. There surely comes a time, when you start looking for special formulas to enhance your simulations. Then you'll certainly meet the Cross Product again. Many formulas, dealing with the simulation of natural, turbulent phenomena use the Cross Product to mimic the desired forces. The last rule has already been introduced: The Norm or Magnitude of a Vector. The result of this operation is a Scalar. You'll often use Magnitudes with forces. The resulting Scalar can be multiplied with a Vector again (see “Multiplication with a Scalar” above). The Magnitude is calculated using an ”old friend”: Pythagoras’ theorem. If you can't remember it, don't worry and turn over...

Scripting With RealFlow

RF_magazine

15

The Basics of Python Scripting in RealFlow Pythagoras´ theorem is normally used for triangle calculations. In Vector maths, where you also could represent a Vector as an arrow, it's possible to extract values by constructing triangles from the given Vectors.

y

pos c

x

a

For fluid, smoke and fire simulations, physicians introduced Vector Fields. In a Vector Field, a Vector is assigned to each point of the considered space. The idea is to create field of variable strength at different points. Very good examples are gravity or the magnetic field. In reality these depend on where you are and therefore have different values. Creating Vector Fields in RealFlow with Python is not trivial. For this reason I don’t want to dive into the depth of these fields.

b

Fig 5. The Magnitude c of a Vector

With Pythagoras´ theorem you're now able to get the Length, or Magnitude of a Vector: Pythagoras´ theorem for right-angled triangles: a2 + b2 = c2 You can get the value for c by extracting the square root:

Fig 6. 3D representation of a Vector Field with arrows

c = sqrt( a2 + b2) This term is also valid in three dimensional coordinate systems and for Vectors. The Magnitude of a Vector is always positive, because you have to square the values.

The maths behind Vector Fields is complicated and the main problem with RealFlow is that we need the previous and the current value to solve the equations. RealFlow doesn't provide functions to store values over time and substitutes the previous results.

a = (3.2, 2.8, 1.5) | a | = sqr(ax2 + ay2 +az2) | a ] = sqr(10.24 + 7.84 + 2.25) | a | = 4.51

A big variety of very interesting Vector Fields can be explored here with a Java Applet: http://www.falstad.com/vector3d/

Scripting With RealFlow

RF_magazine

16

The Basics of Python Scripting in RealFlow Notation and Syntax Now you're getting closer to scripting and the theoretical part lies almost behind you. In this chapter you'll learn how to organize a RealFlow script and what you have to attend. When you're writing Python scripts for RealFlow you have to follow certain rules. Without obeying these rules, you'll receive error messages and your program won't run. The best idea is, to avoid those errors from the very beginning. As I've seen in the unofficial RealFlow forum, the most common error is related to shifting and spacing. Python recognizes clauses, conditions and syntax groups automatically. The indicators are leading tabs and blanks. program start: statement 1 if ( condition ): statement 2 else: statement 3

to close all branches. Otherwise you'll receive an error. This an example of a code structure in Perl: sub function { statement 1; if ( condition ) { statement 2 } else { statement 3 } while loop { statement 4 if ( condition ) { statement 5 } else { statement 6 } statement 7 } # end while } # end sub function

Though all conditions, definitions and loops are embedded into branches, it’s still a very good idea to use tabs for better readability. With Python it's essential and especially with copied and pasted scripts, you have to be extremely careful. Scripts from forums, discussion boards or other sources often don't follow these rules for leading tabs, and this directly leads to problems.

while loop: statement 4 if ( condition ): statement 5 else: statement 6 statement 7

The example above is a typical structure of a Python script. For each new instruction set, there's got to be a new clause. The interpreter allows blanks as well as tabs, but you must never use them together. Always make a decision on one method for shifting. I'd recommend to use the Tab key. With this key you don't have to worry about the number of blanks and this makes life much easier. Other scripting languages, like Perl, often use brackets to separate statements and functions. The advantage is that you don't have to care about tabs, but you shouldn't forget

Python is a very flexible programming language. It allows many short forms and convoluted statements. For beginners it's often very hard to understand, what’s going on in scripts, made by Python pros. To grant access to everyone, the scripts from this issue use a step-by-step technique. Thus many scripts may be longer than necessary, but beginners can follow the code, too. I leave it to you, to find the shortest form for a script. With growing experience you'll surely learn how to contract statements effectively. Another issue is the spelling of Variables. Python and the RealFlow extension use certain words for introducing particular functions. These words can't be used for Variables. You can find a list of the currently reserved words on page 64. Another limitation with Variable names is the usage of the dot character. The dot character is used as an identifier for an object's properties, like: emitter.getPosition()

Scripting With RealFlow

RF_magazine

17

The Basics of Python Scripting in RealFlow The Syntax of a script can be seen as a grammar. Violating the rules leads to errors. There are many definitions and essays about Syntax and especially for beginners it’s not easy to understand its importance. In brief terms: Syntax is the logical structure of a script. This structure is not a fixed rule type, it's flexible and there's always more than one way to achieve a result. I think this should be enough about the nature of Syntax. If you need further information, just have a look at the internet. One of the most common error messages you'll receive in RealFlow is “Script Syntax error at line XX”. A Syntax error often has marginal reasons, like: • leading spaces are wrong • wrong order of statements • using Variables before assigning them • misspelling of Variable names • unmatched brackets • infinite loops • using improper operators • using reserved or forbidden words • using the wrong Data Type

Fig 7: RealFlow error message

Scripting With RealFlow

RF_magazine

18

Getting Started Setting the Preferences RealFlow provides a complete scripting environment, fully equipped with Syntax highlighting, a debugger (this is an “error detection facility”) and a set of predefined functions. To adjust this environment to your needs, I'd recommend to set the scripting preferences first:

use tabs instead of blanks or spaces to introduce new instructional blocks (also see p. 16). In conjunction with this setting, it's a good idea to use a so called monospace font. Glyphs from these fonts all have a the same fixed width, which is great for organizing a script. With monospace fonts it's possible to arrange Variable names like this:

realflow > Preferences > Script

pi

After the Script tab has been selected, a new panel appears, showing a variety of settings:

e

= 3.14

gravity = 9.81 = 2.718

ini_vel = 12

With fonts using variable widths, it's not possible to align names, numbers and other characters correctly: pi gravity e ini_vel

= 3.14 = 9.81 = 2.718 = 12

Very common monospace fonts are Monaco, which is used here for all scripting examples, and Courier. Both fonts should be installed on most computers by default.

Adjusting the Syntax Color is a matter of taste. You can choose any colour you want. With the predefined colours, a typical statement would look like this:

In RealFlow it's possible to choose a directory for your own scripts. These scripts can be filed in the toolbar of RealFlow and you have direct access to them, just by clicking on the appropriate icon. You can either use the default path or define another location anywhere on your harddisc(s). The Scripts Organizer file carries all information about the user scripts.

# Get the scene emitter particle_source = scene.getEmitter("Circle01")

Highlighting certain keywords of a script helps you to keep the code readable and it's easier to differentiate the individual elements. The Tab size determines, how many leading blanks are inserted to structure the Python code. You should always

The last three icons are customized and indicate some of my own scripts. Please note that it's only possible to use Batch Scripts (see p. 19) with the toolbar. Many free scripts already come with icons, ready to use with the toolbar. After you've made the settings, just click OK to use them as new defaults.

Scripting With RealFlow

RF_magazine

19

Getting Started The Scripting Windows The Scripting Windows are a true obstacle for many beginners, because they simply don't know which type to use. In RealFlow there are three basic types: Batch, Events and Custom. The Batch and Events Script windows can be called via the menu bar or by pressing F10, respectively F11: Layout > Batch Script Layout > Events Script

Batch Scripts Let's have a look at Batch Scripts first. What's a Batch script and what's it used for? In Batch Scripts you mostly define routines for repeating tasks. Good examples are: • Creating a brick wall • Simulating two or more scenes successively • Changing hundreds of values simultaneously • Creating a basic scene with default objects • Changing states from inactive to active • Adding constraints to multiple objects Have you ever built a brick wall in your 3D application? You have to set brick by brick, or use a copy and paste method. With scripting you can define rules, how many floors the wall should have and how many bricks you want to use. The arrangement will be done automatically while running the Batch Scripts. After this task has been completed, another Batch Scripts could alter the mass of each brick to get a more random effect, while destroying the wall with a bullet, for example. Whenever you need to change properties, create lots of similar objects, export values or add features, a Batch Scripts is the proper choice. The way of developing Batch Scripts is the same as coding Events or Custom Scripts. The main difference lies in the way, the script will be executed by RealFlow.

Fig. 8: The Batch Script window

Events Scripts The second type, Events Scripts is needed, whenever you want to influence particles or objects directly during the simulation process. With a Batch Script you don't need to start a simulation, with an Events Script it's crucial. RealFlow knows a couple of predefined events: • onSimulationBegin • onSimulationEnd • onSimulationStep • onSimulationFrame • onChangeToFrame onSimulationBegin is suited for applying initial parameters, You can also add some basic elements to a scene that will be used for simulation. After this initialization, the routine won't be called again. onSimulationEnd works analogue to onSimulationBegin. Maybe the most important event is onSimulationStep. Here, RealFlow applies calculations for each time step. With heavy maths, this may result in long simulation times. Please also remember that scripts only use one processor/core of a computer.

Scripting With RealFlow

RF_magazine

20

Getting Started Maybe the best example for a script using an onSimulationStep event is the well known foam script. This script will be discussed later as separate project, starting on page 41. With onSimulationFrame it's possible to make changes on certain frames. The actual frame serves as trigger to switch on or off certain properties of one or more objects. Also changes can be performed with each frame, e.g. animating an object or exporting custom bin files. The last type, onChangeToFrame is great for postprocessing. With this event, scripts can be applied while playing back a cached scene. It's rarely used, but nevertheless a powerful means for some applications.

Each function wears a pass statement. This statement tells RealFlow to simply jump over this function. By removing pass and replacing it through your own code, RealFlow starts executing the script. The best thing is that you're not limited to just one of the given functions. You can initialize a scene with onSimulationBegin, activate objects with onSimulationFrame and perform calculations using onSimulationStep. That's no problem, but before you'll be able to run a script, you have to check Active.

Scripted Objects Custom scripts are sectioned into a couple of subtypes and they're surely the most powerful means to customize your simulations. The three types are: • Scripted Daemon • Scripted Emitter • Scripted RealWave The Scripted Daemon is probably the most often used subtype. In simple terms, a Scripted Daemon applies a force to an object. For this purpose, the scripting window provides three predefined functions: • applyForceToEmitter( emitter ): • applyForceToBody( body ): • removeParticles( emitter ): It's necessary to distinguish between particles and (rigid) bodies. With a Scripted Daemon you can write your own gravity or attractor daemons, add friction to particles or objects, bound forces or define falloffs. As you're dealing with forces, represented as Vectors, some basic knowledge of applied mathematics is recommended.

Fig. 9: The Events Script window

After opening the Events Scripts window, you'll recognize that these functions are already implemented.

On page 36 and 58 you can also find two Scripted Daemons, showing, how Vector forces are implemented. The only limitation is that forces from Scripted Daemons seem to act a little bit different from the predefined Daemons in RealFlow.

Scripting With RealFlow

RF_magazine

21

Getting Started Another very powerful facility is the Scripted Emitter. With this type it's theoretically possible to write your own fluid solver, but we don't want to go that far. Maybe you're asking, why one should use a Scripted Emitter, though RealFlow already makes a wide range of predefined emitters available? The answer to this question lies deep within the way, RealFlow solves fluid dynamic equations. Maybe you've already tried to animate viscosity or density of a particle stream over time? If so, you might have encountered some serious crashes. The fluid becomes instable and the calculation suddenly aborts. With a Scripted Emitter you can avoid this behaviour and define ranges for the parameters from an emitter's Particles panel. This is truly a mighty feature! The Scripted Emitter is not an individual object you can choose from the emitter list. The Scripted Emitter is available for each type, like Circle, Sphere, Spline etc. All you have to do, is changing the type from Liquid to Custom:

The Custom type provides just one function to calculate the forces: computeInternalForces( emitter ) The last subtype is the Scripted RealWave object. Similar to the Scripted Emitter, this object is also part of the different RealWave types. You first have to add a new RealWave surface and then place a new deformer via Add Wave. Here's a screenshot of how you can access this feature:

Emitter > Node Params > Particles > Type > Custom After this change, a new button, named Edit appears. To open the related scripting window, simply hit this button.

Again, an Edit button appears, to open the scripting window. There, you'll also find just one function to update the vertices of the RealWave object: updateWave( vertices ) Please note, that only you can only alter the y direction of the vertices. Translations in x or z direction will be ignored. The best application for a Scripted RealWave is to import a sequence of greyscale images as displacement templates. This method is described in detail in RF_magazine Issue 01_2007 “Cinema 4D Special Edition”, page 40 et seqq. Of course it's possible to develop your own formulas to create new, not implemented wave types.

Fig. 10. Parameter rollout for the Custom Emitter type

Scripting With RealFlow

RF_magazine

22

Your First Steps Calling Emitters and Objects Now your persistence and patience will be rewarded finally, because you're about to make the first steps with Python and RealFlow!

meaningful name. As you've already learned, it's always a good idea to use significant names. Later we're developing a foam script. This type of script uses multiple emitters and we have to distinguish them by name: water = scene.getEmitter("Water")

Start with a Circle emitter by choosing:

foam

Edit > Add > Emitters > Circle > Square

Please note that the names of the emitters have to be identical with the names used in the Nodes window and the names must be written between quotation marks, otherwise you’ll get a Syntax Error.

The Node window now shows a new object called Circle01. To use this emitter within a script, it's necessary to address it and store it in a Variable. The Variable type, we're introducing here is a Scalar:

= scene.getEmitter("Foam")

emitter = scene.getEmitter("Circle01")

emitter is the name of the Variable, scene tells Python to look at the Nodes panel for an emitter named Circle01. The basic keyword in this statement is getEmitter to fetch the desired object. Now add a Torus object. To get this item, we use a very similar construction:

Another possibility would be to declare Variables with emitter names: water_emitter = "Water" foam_emitter

= "Foam"

water

= scene.getEmitter(water_emitter)

foam

= scene.getEmitter(foam_emitter)

object = scene.getObject("Torus01")

In this case, the Scalar variable is object and with the getObject keyword we have access to the Torus01 item from your basic scene. Of course, sometimes it's required to change an object's name. Let's say you want to change the name from Circle01 to Water. To identify the emitter again, you also have to alter the name in the script:

In this case we're using the Variables as a substitute for the real name from the Nodes window. To make Python understand that we want to use a Variable with getEmitter( ) and not a String (= sequence of characters), we don't write quotation marks. With objects and other items it's the same procedure: collission_object = scene.getObject("Torus") animated_daemon

= scene.getDaemon("MainAttractor")

emitter = scene.getEmitter("Water")

main_camera

= scene.getCamera("Camer01")

rope_constraint

= scene.getConstraint("Rope01")

The Variable name may also be subject to change. In many cases there's more than one emitter and you need a more

fluid_mesh

= scene.getMesh("Fluid")

wave_surface

= scene.getRealwave("Wave")

Scripting With RealFlow

RF_magazine

23

Your First Steps Accessing Particles Let's stay with emitters for a while. As you certainly know, RealFlow is a particle based software. Each emitter type spills out a number of particles, dependent on the Resolution parameter. Through Python you have direct access on each individual particle. But how is it possible to read out values, like Velocity or Position for a single particle? The basic idea is to gather all existent particles and loop through this amount. Fortunately RealFlow knows various methods to detect the total number of particles and provides direct access. A very common construction is to look for the first particle and then go through the total amount. As long as there are particles in the scene, the loop will be executed: fluid = scene.getEmitter("Circle01") particle = fluid.getFirstParticle() while (particle): do something here particle

On page 9 I was talking about the Modulus Operator %. This is exactly what you'll need for this purpose. With this example I'm also introducing a new principle - the if clause. You'll learn more about this later (see p. 27). The operation you have to perform is the following: skip

= 5

counter = 0 fluid = scene.getEmitter("Circle01") particle = fluid.getFirstParticle() while (particle): counter = counter + 1 if (counter % skip == 0): do something here particle = particle.getNextParticle()

In this example the script counts the total number of particles. With each particle the counter Variable will be increased by 1. There's also a short form available (please see page 11) : counter += 1

= particle.getNextParticle() The result is exactly the same value you can see under

What's happening here? First you have to identify the emitter Circle01. The statement fluid.getFirstParticle( ) means: Search for the first particle emitted from fluid, actually from Circle01. Now, the script starts looping through all existent particles. With this method it's possible to get information from each particle in your scene. While the loop is executed, you're able to perform calculations or check and compare values. To seize the next particle, the script replaces the current particle Variable with the ID of following particle. The keyword for this action is getNextParticle( ). In some cases it's recommended to skip particles for speeding up a simulation or for testing purposes.

Node Params > Statistics > Emitted Particles The Modulus Operator % tells you, whether the operation produces a remainder or not. Only if there's no remainder (in this case the result is exactly 0) the instructions below the if statement will be executed. This means that the script skips four particles, because with the fifth particle the condition is true. The higher the skip value, the more particles will be missed out. With a skip value of 1, the condition is always true and all particles will be included. Besides the while method, RealFlow also knows the for ... in and the for ... in range loop. The first one is also often used with particles and additionally with large numbers of objects. Amongst others, for ... in allows you to change object properties simultaneously.

Scripting With RealFlow

RF_magazine

24

Your First Steps The for ... in loop is often used with a defined number of particles. You have to collect the wanted data first, and then go through them one by one. Especially when it's necessary to gather colliding particles for foam generation, the for ... in method is applied. fluid = scene.getEmitter("Circle01") collided_particles = fluid.getParticlesColliding() for particle in collided_particles: do something here

As you can see from the code, you have to store the desired particles in the Variable collided_particles. RealFlow identifies those particles with getParticlesColliding. After the particles have been stored, the script can read out the Variable, calling each particle individually. With lots of particles, this method may become very RAM consuming, because RealFlow makes a copy of the particle set with all it's values and settings. This statement could also be read as: Perform a given calculation for each particle from the collided_particle set. The for ... in range method works similar to the for ... in loop. The main difference is that you can define a stop value for the loop. Let's say, you have a group of six spheres, but the operation should only affect the first three objects. spheres no_of_spheres stop start step

= = = = =

[S0,S1,S2,S3,S4,S5] len(spheres) no_of_spheres / 2 0 1

for object in range(start, stop, step): current_sphere = spheres[object]

do something here Whooo! What's going on here? Well, nothing special. I just used another Data Type. Do you remember Lists? If not, then please have a look at page 7 and 8 again. But don't worry, because everything's explained here, too. The first expression is a List, containing six entries - the spheres.

The Variables in this List are named S0 - S5. Since Lists always start with 0, it's better to adopt your names to this circumstance and avoid trouble. The spheres in the Nodes panel must wear exactly the same name. You only want the first three objects to be affected, so you have to find a stop value. To get the total number of List entries, it's necessary to determine the length. This value is stored in no_of_spheres. The List carries six entries and we need three spheres. So the stop value is: 6/2=3 Of course you simply could write stop = 3, but here I wanted to illustrate how to deal with Lists and their entries. And in some cases it might be useful to get start or stop values directly from a List. To read out the current_sphere, you're using the current value of the loop as a reference on the List containing the spheres: current_sphere = spheres[object]

Since we're starting with start = 0, in our example we'd get these expressions (actually we can't see this term, but this is the way it looks like internally): current_sphere = spheres[0]

( S0 )

current_sphere = spheres[2]

( S2 )

current_sphere = spheres[1]

( S1 )

After you've stored the sphere in a Variable, you'll be able to perform calculations. With current_sphere = spheres[3], the loop ends. I'd suggest to make a few tests with this method to get a better understanding. The step value indicates an increment. So with each step the loop adds 1 to the current_sphere Variable. The increment hasn't got to be 1 necessarily. Other Integer values are also allowed, but with different steps, you might have to change the stop value!

Scripting With RealFlow

RF_magazine

25

Your First Steps The List expression shows the names of all objects. On page 8, I explained that names have to written between quotation marks. In this case, the entries of spheres serve as Variables, used by RealFlow to identify the objects in the Nodes window. In RealFlow, quotation marks with Lists are a rarely used. spheres = [S0,S1,S2,S3,S4,S5]

Fig. 11: Velocity vectors in RealFlow

This notation would cause a Syntax error: spheres = ["S0","S1","S2","S3","S4","S5"]

The creation of loops is a crucial task, used with almost any Events or Custom Script (e.g. Daemon or RealWave). Even Batch Scripts often show loops, especially in conjunction with Lists.

Building Vectors Vectors are one of the most important Data Types within RealFlow. There's already been a detailed introduction on page 13 et. seqq. Here, you'll learn how to use Vectors in a script and how to extract a Vector's components. All RealFlow calculations are done in 3D space. The positions of a particle, emitter or object can be described with their x, y, and z coordinates. Starting at the origin of a scene's coordinate system, these three values can be considered as an address, where you can find the object. So it would be possible to draw an arrow from the origin to the object's position. This arrow is the graphical representation of a Vector. But not only positions can be written as Vectors. This is also possible for forces or velocities.

The mathematical representation of a Vector is: a = (x, y, z) In RealFlow it's possible to determine Vectors simply by writing a series of three values to a Variable. The complete Python statement for initializing a new Vector is: my_first_vector = Vector.new(x, y, z)

In RealFlow it's often required to extract the x, y, z values or further calculations, sometimes you may also just need the y or z component of a Vector. The elements of a Vector are called Scalars: position = (1.0, 3.3, 2.7)

Now we want to calculate new y and z positions for a single particle. But how could this be realised? There's got to be a way to split the Vector into it's elements. Indeed, RealFlow knows such a method: position_x = position.getX() position_y = position.getY() position_z = position.getZ()

Scripting With RealFlow

RF_magazine

26

Your First Steps In most cases you'll deconstruct a Vector, perform a calculation, and then build a new Vector using the “fresh” values. The process for performing this operation is finally always the same. Similar to the loop methods, you just have to remember a few fixed steps:

Events Script: ParticleNewPos.rfs fluid = scene.getEmitter("Circle01") particle = fluid.getFirstParticle()

In a second step, the script disassembles the x, y, and z components from the pos_g Vector. The task in this program is to alter only the y and z values, while the x component remains untouched. The new position values have to be calculated individually as well, and they're based upon the old pos_y and pos_z values. The new_pos_y, new_pos_z, and the unchanged pos_x values provide a basis for the new global position vector. The Vector.new statement puts everything together. Of course this Vector has to be stored again in a Variable (new_pos_g) for further use.

while (particle): # 1. Get global position data in Vector format pos_g = particle.getPosition() # 2. Get components from the position Vector

The last steps assigns the new_pos_g Vector to the particles using a setPosition instruction. You'll learn more about the get and set functions later. Here, just put up with the fact that these instructions are responsible for getting and setting positions.

pos_x = pos_g.getX() pos_y = pos_g.getY() pos_z = pos_g.getZ() # 3. Calculate the new coordinates individually new_pos_y = pos_y + 0.01 new_pos_z = pos_z + 0.01

The last line of the script simply calls the next particle. As you can see from the script, we’re affecting particle by particle. The higher the emitter’s resolution, the longer the calculation takes. Since Python scripts inside RealFlow are single-threated by default, you probably have to take longer simulation times into account. Especially with tight deadlines, this has to be considered.

# 4. Build a new Vector new_pos_g = Vector.new(pos_x, new_pos_y, new_pos_z) # 5. Use the new Vector for position change particle.setPosition(new_pos_g) particle = particle.getNextParticle()

This is already a complete script. To show you what's happening here, I want to deconstruct this little proggie. The first part uses a well known method to loop through the emitter's (Circle01) particles.

Fig. 12: The result from the Events Script ParticleNewPos.rfs

The first step is to get vectorized value, in this case the script reads out the global position Vector and stores it in the pos_g Variable.

The representation in RealFlow isn't very spectacular, but the script implements most of the principles, introduced so far.

Scripting With RealFlow

RF_magazine

27

Your First Steps If & Else

Another, equivalent notation would be:

When you're writing a script, it's often required to set certain conditions or alternative ways. Differentiations can be achieved with if and else statements. The Syntax of this function is rather simple and well known from your daily life: I'm sure you love new and fast computers, but money is always the obstacle for buying the latest killer machine. You have to calculate to get the maximum out of the available money. This example is well suited for an if-else construction:

total_money = availabe_money + grannies_gift

available_money = 2500

if (total_money >= computer_price):

computer_price

if (total_money < computer_price): can't buy computer else: go to computer store

You could also reverse the comparison:

go to computer store

= 3200

if (available_money < computer_price):

else can´t buy computer

can't buy computer else: go to computer store

In this case, the amount of available_money is less than computer price. The condition for this example is true, and you're not able to buy the new computer. But Variables can change over time and some new decisions can be made: available_money = 2500 computer_price

= 3200

grannies_gift

= 750

if (available_money + grannies_gift < computer_price): can't buy computer else: go to computer store

Here, Granny saved your day, because 2500 + 750 = 3250. As you can see, comparisons are not only limited to one Variable, it's also possible to perform calculations within the if clause.

As you can see from this example, if-else conditions are mostly connected with comparisons. There are also ways to compare more than one value: ram

= 200

my_money

= 250

birthday_gift = 210 if (my_money >= ram or birthday_gift >= ram): set max_particles to 50,000,000 else: set_max_particles to 100,000

It´s also possible to use an and statement. This is often needed for setting a certain limit: ram = 200 gpu = 350 if (ram = retail_price): if (location == zip):

sell computer immediatly

shipping_cost = 10 else:

elif (offer >= min_price and offer < retail_price):

shipping_cost = 25

contact customer for further negotation else: else:

don´t sell computer

don't sell computer

In this case, the customer will be contacted, because offer, lies between retail_price and min_price. elif statements mostly need a second criteria to complete a decision. Have a look at this example: offer

= 520

With nested if-else constructions, it's possible to assign case dependent values to a Variable. Having a look at the example above, you'll recognize that the computer has been sold (offer is greater than retail_price). The second clause is used to determine, whether the customer is located in your hometown or not. The shipping_cost value directly depends on the customer's location.

retail_price = 500 min_price

= 400

if (offer >= retail_price): sell computer immediatly elif (offer >= min_price): contact customer for further negotiation else: don’t sell computer

Scripting With RealFlow

RF_magazine

29

Changing Attributes With Get & Set Introduction You already know the basics of Python scripting in RealFlow. You're able to loop through an emitter's particles, you've heard about building Vectors and else-if clauses. The coming chapter is about getting and setting values directly from RealFlow. This is the quasi-core of RealFlow's Python extension. With the get and set statements, you have direct access to all properties of an emitter, object, daemon etc. The variety of get and set statements seems to be endless. So it's necessary to sort the instructions, to keep an overview. In this issue, we're dealing with the most common get and set directives. RealFlow also provides commands for getting and setting vertices, polygon faces, animation keys, image pixels and more.

many parameters and RealFlow objects. The next expression determines the Data Type you have to use with this particular instruction. The Data Type also specifies, how the argument will be treated: Particle getParticle( int ) This operation can only be used with particles and the input value has to be an Integer. Using it with a daemon and a string, like Gravity01, would cause an error. The manual sometimes gives more hints: Emitter [ ] getEmitters( ) The brackets [ ] tell you that the resulting Data Type will be a List. Do you remember? With a Scalar it's only possible to store one value, while Lists can carry two or more entries.

You've already met a few statements: getEmitter( ) getPosition( ) getFirstParticle( ) getNextParticle( ) These instructions had been used to find a scene emitter, to have access to individual particles or make use of a particle's Vector position data. With different get instructions, you'll receive different values and results, e.g. Integers, Vectors, Floats and Strings. The online manual shows a rather long index of different get and set commands. These commands are written in a particular format, like: Emitter getEmitter( string ) Object getObject( string ) The first word describes the allowed object type, like emitter, object (primitive), camera, mesh and so on. The second part is the specific get command, available for

All descriptions of the available commands are working the same way, as the examples above. With a little experience, you'll be able to forecast the used object and Data Type, but if you're not sure, you can always refer to the manual: • Help > Contents... (opens a new application) > Index • Help > Contents... > Contents > Scripting Reference The second source contains a more structured compilation, sorted by object types.

How To Use Get & Set The concept behind the get and set statements is simple, but very clever, because it's directly connected to RealFlow's user interface. The commands share exactly the same names you can see in the Nodes and Node Params windows. The main difference is the number and type of arguments, a get or set command needs. The commands are not limited to loops, they can be used anywhere.

Scripting With RealFlow

RF_magazine

30

Changing Attributes With Get & Set The Data Type you receive with the getParameter command can also be read out from the Node tab. Whenever you see a triplet of values, the result will be a Vector: Position Rotation Scale Pivot

0.0 0.0 1.0 0.0

0.0 0.0 1.0 0.0

0.0 0.0 1.0 0.0

Please note: The individual values of the Vector are Floats. Even Color is represented by three values for red, green and blue (RGB). The Data Types for Simulation and Dynamics are Strings. The fields can contain words or expressions: Simulation Dynamics

Active No

If WetDry Texture was set to Yes, we'd also have some Integer values, like @ resolution

256

The values for @ filter strength and @ ageing are Float types again. In this example, a Cone had been added to the scene. The parameter rollout above contains everything you need to access the object's properties. The most interesting parameters are certainly located within the Node tab. Here you can find basic attributes, like Position, Rotation or Scale. The online help describes the corresponding get commands as follows: any getParameter( any ) This notation tells us that we can use any object or Data Type with the getParameter command. This operation is not limited to primitives, emitters or cameras. Also the arguments can be an Integer, Float, Vector etc.

As already mentioned, it's crucial to know, which Data Type a get command “spits out”, because in the reverse process with setParameter, you need exactly the same Data Type again to alter the appropriate value. Let me show you an example: cone

= scene.getObject("Cone01")

pos_global = cone.getParameter("Position")

The result is a Vector, consisting of three Floats. After a calculation has been finished, you can build a new Vector. This new Vector must carry three Floats (Integers are allowed, too) again. You can't replace the Floats with Strings. This would cause a Syntax error: pos_new

= Vector.new("left", "up", "right")

Scripting With RealFlow

RF_magazine

31

Changing Attributes With Get & Set The next example is a complete script, ready to use within RealFlow. The program adds a value of 0.2 to the y position of a Cone with each new frame:

Events Script: ChangeConeYPos.rfs

Particles don't use the same get and set commands as physical objects in RealFlow. For an emitter or a mesh, it's possible to see values directly in the corresponding Node Params window. Position or Velocity values aren't displayed at all. They'll be used internally. For faster access and better distinction, the particle attributes don't use the Parameter keyword.

def onSimulationFrame(): cone

= scene.getObject("Cone01")

# 1. Get global position data in Vector format pos_g = cone.getParameter("Position")

And there's another difference: The getParameter command only takes one argument, named “Position”. The setParameter shows “Position” and new_pos_g. So, with setParameter you have to tell RealFlow which type of parameter you want to change (Position, Pivot, Roughness...) and the new value using the correct Data Type.

# 2. Get components from the position Vector pos_x = pos_g.getX() pos_y = pos_g.getY() pos_z = pos_g.getZ()

The changing values can be monitored in the Node Params window during the simulation. Just give it a try and watch the y component of the Position value.

# 3. Add 0.2 to the y component new_pos_y = pos_y + 0.2 # 4. Build a new Vector new_pos_g = Vector.new(pos_x, new_pos_y, pos_z) # 5. Use the new Vector for position change cone.setParameter("Position", new_pos_g)

This script is very similar to the Events Script on page 26 (ParticleNewPos.rfs). There, the script adds values to the y and z positions of all particles. Here, we're just using a single object. The process of getting, splitting and assembling the Vectors is finally the same. The main difference lies in the last statement: cone.setParameter("Position", new_pos_g)

With the particle method, it's just particle.setPosition(new_pos_g)

Fig. 13: The Cone performs a constant movement in y direction

Tip Don't copy and paste scripts to from the original source to RealFlow. Different fonts, character sets and spaces or blanks will cause errors almost any time. It's better to type scripts manually and think about what's happening.

Scripting With RealFlow

RF_magazine

32

Changing Attributes With Get & Set With getParameter and setParameter you can have lots of fun, since you can alter, change or turn off and on almost everything. Some tasks could also be solved manually and with easy means, but scripting is much more accurate. Especially with collisions it’s sometimes not easy to get exactly the moment of the first interaction between two objects or particles. With a Python script, the solution is just a few lines of code away. This is the initial situation:

RealFlow recognizes your selection and stores all items in a List object. So the correct expression is: items = scene.getSelectedNodes()

Just for remembrance: With a selection of the three objects Cone, Cube and Cylinder, the entries of items would be internally: items = [Cone,Cube,Cylinder]

The next step is to collect all objects, colliding with the Floor item. RealFlow also knows a function for this purpose. The Data Type created with this command is again a List, because there's more than one object to collide with Floor: floor

= scene.getObject("Floor")

colliding_objects = floor.getCollidingObjects()

Now we have to browse through the list of collided objects and deactivate the appropriate primitive: if (colliding_objects != [ ] ): for object in colliding_objects: object.setParameter("Simulation","Inactive")

Our goal is to deactivate the objects, after they've hit the floor. All items have different positions. Deactivating them manually could be an ungrateful job. This scene just uses three objects, but imagine 20, 30 or 60 items.

The if condition checks, whether the List colliding_objects is empty or not. The expression

According to the methods you have seen so far, we always called objects individually, but with more than three items, this could get rather annoying. To ease this task, RealFlow provides mighty tools for object selection. The operation, I'm going to use here is named:

means: “If the List colliding_objects is not (!= ) empty, then do something”. An empty List is written as [ ]. Another idea is to check the length of the List.

getSelectedNodes() With this command, you just select the objects you want to be affected by the script from the Nodes window.

if (colliding_objects != [ ] ):

If a collided object has been detected and written to colliding_objects, the Simulation parameter will be set to Inactive. In other words: As soon as an object hits the floor completely, it'll be set to Inactive and the enclosed fluid can be released. On the next page you'll find the listing for this script.

Scripting With RealFlow

RF_magazine

33

Changing Attributes With Get & Set Events Script: InactiveOnCollision.rfs def onSimulationBegin(): items = scene.getSelectedNodes() for object in items: object.setParameter("Simulation", "Active") object.setParameter("Dynamics", "Rigid body")

def onSimulationStep(): items = scene.getSelectedNodes() floor = scene.getObject("Floor") colliding_objects = floor.getCollidingObjects() if ( colliding_objects != [] ): for object in colliding_objects: state = object.getParameter("Simulation") if (state == "Inactive"): pass else: object.setParameter("Simulation", "Inactive") object.setParameter("Dynamics", "No")

The first function initializes the selected items and makes them active. This is useful in case of starting multiple simulation passes, because you don't have to switch back each time you want to simulate the scene. This program identifies all objects colliding with the floor item and writes them to a List Variable: colliding_objects = floor.getCollidingObjects()

The last if clause is just a little construction for checking, whether the Simulation state has been changed. So if the Simulation state is already set to Inactive, then the script simply does nothing.

Fig. 14: “Dissolved” objects after collision

The only limitation is that the objects have to collide completely with the floor item, before they'll be inactivated. With the first contact the items remain active! A more sophisticated approach is the use of an object’s faces or vertices. You can write the collided faces into a List. If the Length of this List is greater than 0, the object becomes inactive. Then, check with each step or frame whether the object’s state is active to speed up the script.

Scripting With RealFlow

RF_magazine

34

Changing Attributes With Get & Set Working With Particles Until now, you have learned a lot about getting and setting attributes from objects. As I've already mentioned, particles represent an own class and provide direct access to special values and characteristics. When you have to work with particles, it's always necessary to construct a loop function and call them individually. For this task, an emitter is required and you have to distinguish between emitter and particle based properties. The emitter attributes are directly displayed within the Node Params window:

are not meant to change, but they can be used for control structures. With Density or Pressure, for example, you're able to create foam, based on physical properties of a fluid. This is a very effective way to create believable spray. Here are a few important and often used instructions: getMass( ) getDensity( ) getPressure( ) getNormal( ) getNeighbors( ) These attributes occur nowhere within a RealFlow window, because they're just meant for internal calculations. Here's how to use them: emitter = scene.getEmitter("emitter_name_here") particle = emitter.getFirstParticle() while (particle): particle.getVelocity() particle.getPosition() particle.getMass() particle.getPressure() ...

Changing these values may cause instabilities and a crashing RealFlow application. You should alter these values carefully. With a Scripted Emitter it's safe to change these values. But here I want to talk about an emitter's output: Particles. While looping through an emitter's particles, you have access to a wide variety of attributes. The most common values will probably be these two commands:

particle = particle.getNextParticle()

In many cases it's important to get an idea of a value's dimension. Is it large or small, a Float or an Integer? Especially with if-clauses it's crucial to know the magnitude. RealFlow knows a function for printing out any kind of value: scene.message(str(variable_name))

getVelocity( ) getPosition( )

This statements directly prompts the appropriate value to RealFlow's Message window, but in some cases you might get results like this:

Velocity and Position can be changed directly by creating a new Vector after performing a calculation. Other values

RealFlow vector at

Scripting With RealFlow

RF_magazine

35

Changing Attributes With Get & Set A result like the one from page 34, is displayed when you try to print out the value of Lists, Vectors or Dictionaries. Whenever there's more than one value stored in a Variable, RealFlow displays the hexadecimal value. Only the individual components of these multi-value Variables can be printed out readable:

Events Script: StopOnMass.rfs def onSimulationStep(): total_mass

= 0

mass_threshold = 1000 emitter

= scene.getEmitter("Circle01")

vec_z = vector.getZ()

speed

= emitter.getParameter("Speed")

entry = list[2]

particle

= emitter.getFirstParticle()

scene.message(str(vec_z))

while (particle):

scene.message(str(entry))

mass

= particle.getMass()

total_mass = total_mass + mass

The str command forces Python to transform any value into a String. This is the only Data Type that can be used with the Message window. Of course it's possible to use more than one Variable within a str term. True Strings, defined by the user, have to be placed within quotation marks: vec_z = 3.5 entry = Cube04 scene.message(str("Values: "+vec_z+" / "+entry)) Result: Values: 3.5 / Cube04

With this little helper you can estimate dimensions and adopt values for forces or limits. Please note that RealFlow becomes significantly slower while printing out scene messages!

if (total_mass >= mass_threshold): emitter.setParameter("Speed", 0.0) particle = particle.getNextParticle()

The script again shows the basic concepts, e.g. gathering the scene and particle data or creating a loop structure There's also an if condition to trigger a particular event. If the total_mass Variable reaches a limit of 1000 (mass_threshold) the emitter speed is set to 0.0. In other words: The emitter stops creating new particles. The good thing with this script is that the mass of a particle depends on it's density. Setting Density to 100.0 in the Node params window, will roughly produce 10 times more particles than a standard Density of 1000:

A nice little application is a mass based stop function for an emitter. The following script simply reads out the Mass values from each particle and adds them up. If the total mass is greater than a given threshold, then the emitter stops pouring out new particles. Of course this script could also depend on Pressure or Density. As you can see, scripts don't have to be complicated to be effective. Even with easiest means it's possible to achieve good results. With a daemon based approach, it would be rather difficult to achieve a result like the one on the right.

Scripting With RealFlow

RF_magazine

36

Changing Attributes With Get & Set Another possible application for particle manipulation are Scripted Daemons. With daemons it's possible to apply external forces to particles. The following example applies a height dependent force to a particle.

Within the loop, the script calculates a height dependent gravity force:

The first step is to create a Scripted Daemon:

In this formula we met all the initial Variables again. As you can see, the script performs a division here and you have to take care that there’s no division with Zero. This may happen directly after the particles leave the emitter. To avoid a Zero division, I simply add a small value of 0.001. Due to the basic rules of calculation some brackets aren't absolutely necessary, but sometimes it can improve readability. The expression:

Edit > Add > Daemons > Scripted > Scripted You could leave the default name Scripted01 or assign a new one. In this example I decided to rename the daemon and call it HeightDaemon.

gravity = accel / ((pos_y * factor) + 0.001)

((pos_y * factor) + 0.01)

Scripted Daemon: HeightDaemon.rfs could also be written as def applyForceToEmitter(emitter): (pos_y * factor + 0.001) accel

= -9.81

factor

= 7.0

emitter

= scene.getEmitter("Square01")

particle = emitter.getFirstParticle() while (particle): pos_g

= particle.getPosition()

pos_y

= pos_g.getY()

gravity = accel / ((pos_y * factor) + 0.001) force

= Vector.new(0.0, gravity, 0.0)

particle.setExternalForce(force) particle =

because * and / always have higher priority. The next step is to construct the new Vector representing a force in negative y direction. With the Scripted Daemon specific command setExternalForce(force), the new force will be applied. Isn't that nice? With some easy and fully customizable steps it's possible to script your own daemon. Another idea is to make the gravity force velocity dependent.

particle.getNextParticle()

OK, let's go through this script. The first part initializes the needed variables, while accel is the acceleration of gravity, and factor is a value to prevent the particles from slowing down to fast. The smaller the value for factor, the stronger the final force. The while-loop goes through the particles from the Square01 emitter and checks the height permanently.

Conclusion The possibilities with RealFlow and Python scripting are almost endless. Examples, techniques or methods could fill another 10 or more magazines, but here I just wanted to give a first introduction into scripting. The following projects are just a selection of what's possible. They range from beginner to intermediate, but there will surely come an issue of RF_magazine, covering advanced scripting...

Scripting With RealFlow

RF_magazine

37

Creating Graphical User Interfaces What's A Graphical User Interface? A Graphical User Interface (GUI) is a kind of environment in software applications, providing user friendly tools for interaction. Each time you're starting a computer program, you can see a GUI. Palettes, buttons, toolbars, and value fields are elements of such interfaces. Even RealFlow provides a GUI - it consists of windows, parameter tabs and selection lists, filled with emitters, daemons or constraints. You've already read a lot about Variables, like Scalars, Lists or Vectors. Until now you had to define all Variables at the beginning of the script, within a function or a loop. What, if there was a possibility to let the user enter his own starting values? You wouldn't have to change the source code for altering some parameters. RealFlow and Python offer functions to create those GUIs. Of course they're not as complex as GUIs you know from software application, but you can open a window with custom tailored values. These values will be taken over by RealFlow and then used with your current script.

The Structure Of RealFlow GUIs GUIs can be used with Batch and Events scripts, but they're mostly needed with Batch scripts. The usage of GUIs within an Events Script is limited to the onSimulationBegin and onSimulationEnd functions. GUIs are constructed using a particular structure. This structure is always the same and only contents are changing. Another very important issue is the usage of Data Types. As with all Variables, the correct specification of the used Data Type is the premise for the execution of a script. While defining a Variable, the Data Type is often obvious from the way, you're writing it: number_of_objects = 23

In this case the value apparently is an Integer. With GUIs the situation is different, because you have to define exactly the Data Type, you're expecting from the user. So, it's indispensable to make up your mind about Data Types before you can start to build a GUI. But, with a little exercise you'll quickly get a feeling for these Variables. With RealFlow GUIs it's not only possible to create your own windows, you can also add custom error messages and use file or node pickers. The most common application surely is the form for custom and default values. The process of working with those GUIs is divided into two substeps: 1.. Creating the GUI with all fields and Variables you want to provide to the user 2. Transferring the custom entries into values, used by the script The very first step is to define a new Variable containing the GUI dialogue window: form = GUIFormDialog.new()

This simple statements initializes the window. The dialogue is also called a modal window. This type of window always comes to the fore, while the contents of the main application aren't accessible, as long as the modal window is open. Your next job is to define the user Variables. RealFlow knows statements for (almost) each Data Type: form.addIntField() form.addFloatField() form.addVectorField() form.addListField() form.addBoolField() form.addStringField()

The only Data Type missing here, is the Dictionary.

Scripting With RealFlow

RF_magazine

38

Creating Graphical User Interfaces Let's have a closer look at these add statements. Each statement expects a series of arguments, defining the basic information for the user: • The field's name • The default value addListField( ) also needs the List entries, e.g. for the particle type, like Dumb, Liquid, Gas, Elastic or Custom. Lists have to be defined, before they can be used with a GUI instruction. Another requirement is the usage of unique field names, you can't share the same name for two or more fields. To give you a first impression, of how GUIs are created, here's an example for setting up a basic scene emitter with a Batch Script:

The second step for a fully working GUI is the assignment of Variables, used by the script. It's necessary to tell the script, what should happen with the values, the user entered so far. A transformation like that is normally introduced with an if-clause: if (form.show() == GUI_DIALOG_ACCEPTED):

form = GUIFormDialog.new() e_types = ["Circle","Square","Sphere","Triangle"] p_types = ["Liquid","Dumb","Elastic","Gas","Custom"] form.addListField("Emitter", e_types, 0)

The term GUI_DIALOG_ACCEPTED is a fixed expression, determining that the user hit OK. The opposite of this term is GUI_DIALOG_REJECTED. Now, you have to transfer the values into Variables. The key instruction for that purpose is written this way:

form.addListField("Type", p_types, 0) form.addFloatField("Resolution", 1.0)

variable_name = form.getFieldValue()

form.addFloatField("Density", 1000.0) form.addFloatField("Viscosity", 3.0)

The getFieldValue( ) statement also needs an argument

form.addStringField("Name","BasicEmitter")

this is exactly the field's name.

As you can see from this example, the List Variables are used within the addListField( ) statement, also all nonvariables are written between quotation marks, both in the Lists and the argument brackets.

emitter_type

This script snippet won't work in RealFlow so far, because some functions are still missing. But there's a preview on the right, of how the resulting window will look later. You might notice that the field names aren't arranged in the order you defined within the GUI creation block. Unfortunately RealFlow sorts the fields alphabetically, but this aspect won't impact the GUIs functionality.

and

= form.getFieldValue("Emitter")

particle_type = form.getFieldValue("Type") resolution

= form.getFieldValue("Resolution")

density

= form.getFieldValue("Density")

viscosity

= form.getFieldValue("Viscosity")

name

= form.getFieldValue("Name")

Here it's obvious why it's not allowed to use shared field names, because the result would be ambiguous. Variables, created during this process can now be used like any other Variable before. Together, these instruction sets now open a new modal user window.

Scripting With RealFlow

RF_magazine

39

Creating Graphical User Interfaces Batch Script: CreateBasicEmitter.rfs # Create new modal window and define fields form

= GUIFormDialog.new()

e_types = ["Circle","Square","Sphere","Triangle"]

The first two parts have already been explained on p. 38, while the creation process of the emitter is just the usage of various get and set instructions. Instead of real names and numbers, like Circle, 1000.0 or 3.0, we’re just using Variables. For better understanding, I'd like to explain the way of a value through the entire script:

p_types = ["Liquid","Dumb","Elastic","Gas","Custom"] form.addListField("Emitter", e_types, 0) form.addListField("Type", p_types, 0) form.addFloatField("Resolution", 1.0) form.addFloatField("Density", 1000.0) form.addFloatField("Viscosity", 3.0)

1. Add a field, named Resolution with a default of 1.0 2. The user enters a value of 5.0 3. The scripts fetches the value 5.0 from the GUI window 4. The value 5.0 is assigned to the resolution variable 5. The value 5.0 is used for the setParameter statement 6. The script inserts 5.0 for Resolution in Node params

form.addStringField("Name","BasicEmitter") # Transform user values into variables

The List objects are bit tricky, but there shouldn't be anything new to you. The addListField( ) statement is not working with strings, but with indices. Instead of

if (form.show() == GUI_DIALOG_ACCEPTED): emitter_nr

= form.getFieldValue("Emitter")

form.addListField("Emitter", e_types, "Circle")

particle_nr = form.getFieldValue("Type") resolution

= form.getFieldValue("Resolution")

density

= form.getFieldValue("Density")

viscosity

= form.getFieldValue("Viscosity")

name

= form.getFieldValue("Name")

the argument needs an index for identifying the correct type from the e_types List. Since Lists always start with 0 by default, the term form.addListField("Emitter", e_types, 0)

# Get the emitter/particle type from the lists emitter_type

= e_types[emitter_nr]

gets the correct type for the GUI window. The problem is that the GUI also assigns 0 to this expression:

particle_type = p_types[particle_nr] emitter_nr

= form.getFieldValue("Emitter")

# Create emitter using the new variables emitter = scene.addEmitter(emitter_type)

We need a string for this expression, not a number, because there is no emitter type called 0:

emitter.setParameter("Type", particle_type) emitter.setParameter("Resolution", resolution)

emitter = scene.addEmitter(emitter_type)

emitter.setParameter("Density", density) emitter.setParameter("Viscosity", viscosity)

With this expression we get a String from the e_types list:

emitter.setName(name) e_types = ["Circle","Square","Sphere","Triangle"] emitter_type

= e_types[emitter_nr]

emitter_type

= e_types[0]

Scripting With RealFlow

RF_magazine

40

Creating Graphical User Interfaces Like looping through particles, the creation of GUIs always follows the same rules and needs practice.

File And Node Pickers The last section of this chapter is about opening files and selecting Nodes. RealFlow knows two instructions for this issue, both are pretty similar:

The Node Picker also opens a window, showing all entries from the Nodes window. From this window a multiple selection can be made and the chosen objects will be used for your script. The result of this operation is a List, where all names are stored. dialog = GUINodesPickerDialognew() nodes = dialog.show( TYPE_EMITTER | TYPE_DAEMON ) for node in nodes: scene.message(node.getName())

files = GUIFilePickerDialog.new() path

= files.show( FILE_PICKER_LOAD, "/Users/tsn/RF",

"*.jpg;*.tga;*.tif", "Load Images" )

The keyword in this term is FILE_PICKER_LOAD, for saving issues it'll be FILE_PICKER_SAVE. The next expression specifies the path to the appropriate directory you want to open, followed by the file extension, you're looking for. The last phrase indicates the window name. The file format can be extended to various other format. Imagine a series of pictures you want to use inside RealFlow. These images can be stored in JPG, TGA or TIF format, other formats aren't allowed: "*.jpg;*.tga;*.tif"

If you don't want to restrict the file type, just use the common wildcard notation "*.*"

This example is directly taken from RealFlow's online help and shows the construction of this method. With the TYPE expression, it's possible to allow only certain object types, used in RealFlow. In detail, these types are: TYPE_DAEMON TYPE_OBJECT TYPE_EMITTER TYPE_MESH TYPE_CONSTRAINT TYPE_REALWAVE ALL_TYPES The delimiter between the TYPE keywords is the so called pipe character, which can be found pressing AltGr+ > (Win) or Alt + 7 (OS X). I always recommend to experiment with these GUI types to find out, how things are working. Especially for beginners it's often very confusing, which Data Type to use and how to deal with List entries. You can start with simple scripts, based on the program from page 39 and then carry on with more difficult GUIs.

Scripting With RealFlow

RF_magazine

41

The Foam Project Project Overview The creation of foam and spray is one of the most wanted and discussed subjects with RealFlow. Meanwhile there are already some fantastic approaches available, creating different types of foam particles for various needs. Despite this fact, I'd like to start with foam creation, because it's a very good example for the implementation of the methods and concepts, you've learned so far. Over the next pages, I want to evolve and develop the foam script step by step, to show you exactly what happens.

1. The creation process can depend on pressure, age or velocity (breaking wave, stormy wind) 2. Spray can consist of small particles dispersed in air 3. Foam bubbles of different size normally accumulate to bigger structures 4. The dispersion of air in water causes a change of the specific weight - foam is not as dense as water 5. Spray and foam can reform to water and vice versa 6. Spray and foam consist of large particle amounts

The Nature Of Foam And Spray Foam consists of little gaseous blisters, separated through walls. To achieve the formation of walls, a tenside is needed to reduce the fluid's surface tension. Foam is a dispersion of air or gas in a medium, containing tensides. There are some natural foam producing processes, e.g. through alges or fermentation.

That's already a lot of information for our project. Now we have to find a way to implement these features into a RealFlow script. Python for RealFlow provides most of the physical properties, listed in the compilation above. The third topic is certainly the biggest problem, because it's not easy to calculate the internal forces between particles. We'd need a Scripted Emitter for this purpose and, of course the appropriate formulas.

Spray is of very similar nature, but a tenside is not necessarily needed. On ocean surfaces, spray normally occurs, when air and water are merging. A good example is a breaking wave. Also the dispersion of tiny waterdrops in air creates spray. Foam and spray aren't fixed states, they can shade off into each other. The white appearance of spray and foam is a result of light dispersion, where parts of the light spectrum are differently scattered. This effect is known as Rayleigh-Dispersion and describes the dispersion of electromagnetic waves on spherical particles. What does this all mean for the creation of spray with RealFlow? Considering the colour of foam, we can see that this is mainly a matter of texturing and rendering. Also the physical or chemical processes are hardly to achieve, but there are some properties, we can use for foam simulation.

Fig. 15: Spray from breaking waves

Scripting With RealFlow

RF_magazine

42

The Foam Project Creating Foam As you can see, the foam creation process may depend on various properties: Pressure, velocity, density and age. Secondary factors can be mass and the amount of neighbour particles or isolation time. Isolated particles won't always appear like foam or spray. Of course we need a particle source. To separate the water particles from the foam particles, it's recommended to use a second emitter, where we can store the generated spray. Now, the first challenge is clear: Declare a threshold value for the creation of foam and separate spray from water particles. The most obvious parameter seems to be a certain velocity. Velocity is a Vector and consists of a x, y, and z component, but it's not advisable to check the particle speed against these three values. Since each Vector has a certain length representing its strength, we better use this Magnitude (see p. 14, 15).

Each particle carries a specific ID. While looping through the particles, the ID is used to differentiate, whether it's a water or a spray particle.

Events Script: Foam_001.rfs def onSimulationStep(): threshold = 2.6 water

= scene.getEmitter("Water")

foam

= scene.getEmitter("Foam")

particle

= water.getFirstParticle()

while (particle): vel

= particle.getVelocity()

vel_mag = vel.module() pos

= particle.getPosition()

id

= particle.getId()

if (vel_mag > threshold): foam.addParticle(pos,vel)

Reminder Calculating the Magnitude of a Vector:

water.removeParticle(id) particle

= particle.getNextParticle()

vel = (3.2, 2.8, 1.5) | vel | = sqr(velx2 + vely2 +velz2) | vel | = sqr(10.24 + 7.84 + 2.25) | vel | = 4.51 RealFlow provides an easy way to extract the Magnitude without performing any additional calculations:

There are a few things to consider, when setting up the RealFlow scene. The emitters must share the same setting for Resolution. The foam emitter isn't meant to produce particles, it's just an empty container for the spray. So the Speed value for foam has got to be 0.0. The colour for water has been set to blue, for foam to white for better distinction.

vel.module( ) This function is simply added to the given vector and automatically returns the Magnitude. You can study the accurate usage in the first foam script on the right.

Fig. 16: The scene setup for Foam_001

Scripting With RealFlow

RF_magazine

43

The Foam Project Events Script: Foam_002.rfs def onSimulationStep(): threshold = 10000 water

= scene.getEmitter("Water")

foam

= scene.getEmitter("Foam")

particle

= water.getFirstParticle()

while (particle): pres = particle.getPressure()

Fig. 17: Velocity based creation of foam with Foam_001.rfs

vel

= particle.getVelocity()

pos

= particle.getPosition()

id

= particle.getId()

if (pres > threshold):

The Syntax of the script Foam_001.rfs should already look familiar to you. We've just used the concepts, discussed so far. The only new section is the following expression: if (vel_mag > threshold): foam.addParticle(pos,vel) water.removeParticle(id)

With the first statement, the script adds particles to the foam emitter, based on the current Position pos and Velocity vel from the water particle. In a second step, the same particles are removed from the water emitter, and the separation of foam and water has been carried out. All this only happens, if the Magnitude vel_mag of the Velocity Vector is greater than the threshold value. By having a look at Fig. 17, you can see that the spreading of the white foam particles is rather uniform. Maybe there are other properties leading to more random results. The next idea would certainly be Pressure. So, with the next script, we compare the Pressure value to a given threshold, instead of Velocity. Since Pressure is a Float type, there's no need for calculating the Magnitude again. But we still need the Velocity Vector for the addParticle( ) function.

foam.addParticle(pos,vel) water.removeParticle(id) particle = particle.getNextParticle()

The concept is pretty much the same, but the result significantly differs from Fig. 17. We get a more random look and the spray has a natural appearance.

Fig. 18: Pressure based creation of foam with Foam_002.rfs

Scripting With RealFlow

RF_magazine

44

The Foam Project The scripts Foam_003 and Foam_004 use Density and Mass for foam generation. I continue without the listings here, they can be found in the appropriate folder: The_Foam_Project > Scripts Instead of pres = particle.getPressure()

we're using these terms: dens = particle.getDensity() mass = particle.getMass()

Fig. 20: Mass based creation of foam with Foam_003.rfs

Here are the results from these approaches: Now, that we know, how to shift particles from one emitter to another, we can care about bringing foam particles back to water. For this purpose, Age will play an important role, but also Velocity and Pressure. If we could observe a single particle, we'd see that both Velocity and Pressure will decline with increasing time. RealFlow simulates these effects physically correct, so they're suited for our purposes.

Fig. 19: Density based creation of foam with Foam_002.rfs

The density method also yields to nice simulations with randomly spread foam particles. This simple approach can also be very effective and it will surely stand our needs. It can be a good alternative to the pressure method.

It's a good idea to use a third emitter, storing the new water particles. An extra colour helps to visualize the fresh particles. To avoid an uniform or artificial look, it would be great to have a random factor, we can add to the particle's age. Indeed there's a way to do so, by using Modules. A Module is an external extension to enhance Python's capabilities. There are lots of different Modules, but the most common with RealFlow are certainly random and math. While random is quite obvious, math provides us with functions, like sine, cosine, square root, hyperbolic sine and so on. The Syntax for importing Modules is: import module_name

The worst method is surely the mass idea. There's no change in the particle's mass over time and the result is that all particles are either foam or water. With all these tests, we found out the working ones for later usage.

For random the complete notation is: import random

Scripting With RealFlow

RF_magazine

45

The Foam Project Events Script: Foam_005.rfs def onSimulationStep(): import random threshold_pres = 5000 water = scene.getEmitter("Water") foam rewater

= scene.getEmitter("Foam") = scene.getEmitter("ReWater")

# Go through water particles particle_water = water.getFirstParticle() while (particle_water): pres_water = particle_water.getPressure() vel_water = particle_water.getVelocity() pos_water = particle_water.getPosition() id_water = particle_water.getId() if (pres_water > threshold_pres): foam.addParticle(pos_water,vel_water) water.removeParticle(id_water) particle_water = particle_water.getNextParticle() # Go through foam particles, check age and pressure particle_foam = foam.getFirstParticle() while (particle_foam): current_age = particle_foam.getAge() random_age = random.uniform(-0.1,0.1) threshold_age = random_age + 0.5 if (current_age >= threshold_age): pres_foam = particle_foam.getPressure() vel_foam = particle_foam.getVelocity() pos_foam = particle_foam.getPosition() id_foam = particle_foam.getId() if (pres_foam < threshold_pres): rewater.addParticle(pos_foam,vel_foam) foam.removeParticle(id_foam) particle_foam = particle_foam.getNextParticle()

Fig. 21: Result from Foam_005.rfs showing different states

The script is getting longer, but we're still using the same principles. The second loop simply turns around the conditions from the first one. We're just using the foam particles to convert them back into water. To get a realistic behaviour, we're introducing an Age dependency: current_age = particle_foam.getAge() random_age = random.uniform(-0.1,0.1) threshold_age = random_age + 0.5

The only new method here is the definition of the random_age Variable. The random.uniform(-0.1,01) expression tells Python to create a random Float number between -0.1 and 0.1. To define a threshold_age, the random_age value is added to 0.5. With this method, the particles turn back into water after 0.4 to 0.6 seconds, but only if the pres_foam value is less then 5000. The particles have to meet two criteria to transform into water again, one of them is semi-random. So, this script is also a very nice example for convoluted if-conditions (see page 28). By altering just a few values, we can achieve good control over the amount of foam created. Since the script already reads out Velocity, we can even add this parameter, e.g. by checking against Pressure and Velocity simulataneously.

Scripting With RealFlow

RF_magazine

46

The Foam Project Controlling Values Through Objects At the moment we have two values influencing the creation of foam and water: Pressure and Age. Each time we want to change these values, we'd have to open the Events Script window, and enter new parameters. Wouldn't it be nice to have another method for this task? One idea would be to control Pressure and Age directly via Null objects. With this method, the script works as if you had sliders to adjust values.

This is an effective method of control, easy to implement. Let's have a quick look at the Python code: • Get the Null objects. • Assign Variables and derive the y position (”Height”). • Use the current position together with a factor for the threshold_pres value. Pressure can be negative, so negative positions are also allowed.

What do we need for this function? First, we add two Nulls, called Control_Pressure and Control_Age:

• Age can't be negative and we have to multiply the resulting value with -1 to make it positive, if needed.

> Null Edit > Add > Objects > Null

• Perform a simple calculation to get a smaller value, e.g. with pos_y_ca = 1.0, the threshold_age value will be 0.5 + random_age

For controlling the values directly through the objects, we have to read out the Null's height or y positions. The positions will be stored into Variables, used to define the threshold parameters. The script doesn't care, whether you move the objects or enter values within the Node Params window. cp

= scene.getObject("Control_Pressure")

ca

= scene.getObject("Control_Age")

You can find the complete listing here: The_Foam_Project > Scripts > Foam_006.rfs To visualize the slider positions, I used small cubes here, instead of Nulls. Of course, you can also add other objects, but don't forget to make them invisible to the emitters!

pos_g_cp = cp.getParameter("Position") pos_y_cp = pos_g_cp.getY() pos_g_ca = ca.getParameter("Position") pos_y_ca = pos_g_ca.getY() threshold_pres = pos_y_cp * 2000 [ ... ] if (pos_y_ca < 0): threshold_age = -1 * (pos_y_ca / 2.0) + random_age else: threshold_age = (pos_y_ca / 2.0) + random_age

Fig. 22: Object driven threshold values

Scripting With RealFlow

RF_magazine

47

The Foam Project Collision Based Foam Creation Until now, we just had to care about emitters, no other elements were populating the scene. For simulations it's much more realistic to add objects for fluid-body interaction. This means that we have to create foam particles, based on collisions. Fortunately RealFlow provides several collision detection facilities to ease our job. The one, we're interested in, is this function: getParticlesColliding( ) The name indicates that this expression gathers all collided particles. The result has to be a List, containing these particles. For reading out Lists, the appropriate looping method is the for ... in approach (see p. 24). The fundamental instruction block for getting the wanted particles is written this way: emitter = scene.getEmitter("Water") collided_particles = emitter.getParticlesColliding()

EventScript: CollisionFoam_001.rfs def onSimulationStep(): threshold_pres = 5000 water

= scene.getEmitter("Water")

foam

= scene.getEmitter("Foam")

# Go through water particles collided_particles = water.getParticlesColliding() for single_particle in collided_particles: pres_water = single_particle.getPressure() vel_water

= single_particle.getVelocity()

pos_water

= single_particle.getPosition()

id_water

= single_particle.getId()

if (pres_water > threshold_pres): foam.addParticle(pos_water,vel_water) water.removeParticle(id_water)

for single_particle in collided_particles: perform checks and calculations...

Again, we're developing our foam script step by step. First, let's check the result with a very basic scene. Just add the two emitters Water and Foam and a collision object of your choice.

As you can see, there's not much difference between our first foam script and the one listed here. It's really just the method of looping through the emitter's particles, all the other functions remain untouched. This is an easy and effective way to call collided particles via Python, and existing scripts can be quickly adapted.

Scripting With RealFlow

RF_magazine

48

The Foam Project The last approach was Pressure dependent, but of course it's also possible to write a collision based script, using a particle's Velocity. Maybe, a combination of both parameters leads to more realistic results? Another topic is that foam is lighter than water. This behaviour makes foam particles floating to the surface. As we can see from the current results (Fig. 23), the foam is even located at the walls of our vessel. That's true for foam creation, but after a while, the foam should disappear from there. We already introduced an Age dependency. This method could be used here, too, along with some random values.

Until now, all emitters had the same parameters. With this script, we're starting to use different Density values for Water, Foam and ReWater. All these actions will help to achieve a better mixing of foam and water and keep the foam particles away from the bowl's bottom. The listing for this script can be found here: The_Foam_Project > Scripts > CollisionFoam_002.rfs Again, we're working with Nulls to adjust the threshold values for Pressure, Velocity, Age, and Height. All in all we now have a fully customizable and controllable foam script. Another advantage is that the calculations will be performed rather fast. Here you can find the main routine for controlling the amount of foam particles: # Define height dependent life span of a particle if (pos_foam_y >= threshold_height): threshold_age = (threshold_age + 0.85) + rand_value else: threshold_age = (threshold_age + rand_value) / 2.0

Fig. 23: Foam particles at the walls of the vessel

# Transform particles back to water if (current_age >= threshold_age):

What do we need for the next script?

id_foam = particle_foam.getId() rewater.addParticle(pos_foam_g,vel_foam)

• Velocity check (Magnitude) • Height check • Age check • Pressure check The foam particles on the top of the water surface should disappear slower than the ones created below the water line. We surely need a couple of if-conditions, but the more effects we're considering, the better the result will be. Of course this is also a matter of speed and some methods are simply not suited for tight production deadlines.

foam.removeParticle(id_foam)

With the first if-condition, we define the life span (threshold_age) dependent from the current height (pos_foam_y) of a particle. Particles below a particular height (threshold_height) will transform back to water quickly, other remain longer as foam. Based upon the resulting Age value, the particles are shifted to the ReWater emitter and behave like fluid again. Together with different densities, the result is realistic foam generation with slowly disappearing particles.

Scripting With RealFlow

RF_magazine

49

The Foam Project Optimizing The Script The last step is an optimization of the code and a function for faster calculation. As I already mentioned, this a guide for beginners, therefore I´d like to show methods. This also implicates that some statements in the script can be shortened. I don´t want to go into depth of fast Python programming, but there are a few possible enhancements. If you´re interested in these refinements, just open both CollisionFoam_002.rfs and CollisionFoam_003.rfs and compare the source code. You´ll see that some statements, defined in separate Variables before, have disappeared. Another improvement, especially for test simulations is to skip particles. This sounds familiar to you, doesn´t it? Of course it does, because I´ve been talking about this topic before (see p. 23). The Modulus Operator, written as a percent character %, is the key to success. This method is also an effective way to control the amount of particles in general. Here we go: First, we need a counter and an Integer, defining how many particles should be skipped. This Variables have to be initialized at the very beginning. def onSimulationStep(): import random counter

= 0

skip_value = 5 [ ... ]

Fig. 24: Image sequence produced with CollissionFoam_002.rfs

As you can see from the image sequence above, the first colliding particles are generating foam (white). After a short period, most of these particles recycle to water (red) again. Different Density values make the foam drift to the water surface. During their way, they're also transformed back to water. The blue colour indicates original particles from the emitter without any change of state.

Together with the first loop for foam generation, we introduce the Modulus operation. Modulus tells us, whether there´s a remainder or not, while dividing Integers (Floats are allowed, but may lead to weird results). If the remainder is exactly Zero, the condition is fulfilled and the particle can be transformed into foam. For all other counter values, the condition is false and the foam generation will not be performed.

Scripting With RealFlow

RF_magazine

50

The Foam Project # Go through water particles collided_particles = water.getParticlesColliding()

Random values also play a very important role, because they care for a natural look. Adjusting these parameters may also need some experimenting.

for single_particle in collided_particles: counter

+= 1

Credits

if (counter % skip_value == 0): pres_water = single_particle.getPressure() vel_water

= single_particle.getVelocity()

pos_water

= single_particle.getPosition()

random_vel = random.uniform(-0.15,0.15) if ((pres_water > threshold_pres) or (vel_water.module() > threshold_vel + random_vel)): foam.addParticle(pos_water,vel_water)

The creation of foam and spray has been widely discussed throughout forums and scripting resources. Similarities can't be excluded, though the programs in this edition of RF_magazine have been developed independent from other sources. Nevertheless I'd like to credit to all the smart people, who have published scripts so far: Beatriz Lorenzo, Robb Flynn, Richard Sutherland, and of course all other RealFlow enthusiasts who have written neat foam scripts.

water.removeParticle(single_particle.getId())

The last enhancement is pretty easy: Just switch the foam emitter type from Liquid to Dumb. This may also save lots of time. Of course, there are many ways to improve the foam script, but the basic aim was to show that you can already achieve sophisticated and stunning effects, just by applying simple methods. Surely you have to make up your mind, before you can start writing a script, but that's nothing unusual. Thorough preparation, a little patience and some extra time for optimization will lead to the desired results.

Threshold Parameters These values control the amount of foam in our scene. It may take a little time to adjust them, but with growing experience you'll find the settings for a specific scene much faster. The adjustment through Null objects is not necessarily needed - this method mainly has been introduced for convenience and for educational purposes. The thresholds normally have to be set individually for each new scene. It's also up to you to introduce new values and parameters for different results.

Scripting With RealFlow

RF_magazine

51

Scripting With RealFlow

RF_magazine

52

Fun with Freeze Project Overview The freeze function is a versatile and powerful feature that can be used for numerous applications. The first part offers some basic approaches with the freeze function. Later, we’ll develop a more complex script for freezing and melting a particular shape, consisting of particles. We'll use animated helpers objects to control the melting speed As always, the goal is to show you principles and methods, and encourage you to write your own scripts or extend the examples from RF_magazine.

Freezing Time and Particles The Bullet Time effect, known from the famous Matrix® trilogy has a very special appeal. In brief terms, the Bullet Time effect allows the artist to stop the motion of objects, characters or particles, while the camera/spectator is still moving. With RealFlow 3 there had been several more or less lavish methods to achieve this effect, but with RealFlow 4 and Python scripting it's a pure pleasure. The problem has always been forcing RealFlow to write out BIN files, while the particles stop moving. This can now be done with a few lines of code: Events Script: BulletTime_001.rfs

Maybe this script is the easiest application of freeze and unfreeze, but the results are stunning. The convenient issue with this script is that the emitter also stops spilling particles during the deadlock time. Let's have a quick look at the listing. The main part lies within the while loop. Here, we're defining a frame range, starting at 25 and ending with frame 60. The length of this range could be defined as long as needed. During this time span, the particles won't move. With frame 60 they'll be released and continue flowing. Another possibility is to setup a simple to GUI for user defined values: Events Script: BulletTime_002.rfs def onSimulationBegin(): form

= GUIFormDialog.new()

form.addIntField("Start Frame", 25) form.addIntField("Stop Frame", 60) # Transform user values into variables if (form.show() == GUI_DIALOG_ACCEPTED): start = form.getFieldValue("Start Frame") stop

= form.getFieldValue("Stop Frame")

def onSimulationStep():

def onSimulationStep(): emitter frame

= scene.getEmitter("Circle01") = scene.getCurrentFrame()

particle = emitter.getFirstParticle() while (particle): if (frame == 25):

particle.freeze()

emitter

= scene.getEmitter("Circle01")

frame

= scene.getCurrentFrame()

particle = emitter.getFirstParticle() while (particle): if (frame == start): particle.freeze()

if (frame == 60):

if (frame == stop):

particle = particle.getNextParticle()

particle = particle.getNextParticle()

particle.unfreeze()

particle.unfreeze()

Scripting With RealFlow

RF_magazine

53

Fun with Freeze Please note that the scripts introduced here only work for particles. With objects, like cubes, spheres or items from SD files, we need another method, as shown in RF_magazine “Cinema 4D Special Edition”, page 30. This issue is available on rf-magazine..com

The example scene is orientated in Cinema 4D/Lightwave style. This means, the y axis is in up orientation (YXZ). In our case, we only check for a 2D expansion of the particles and we're not interested in any height information.

z

Circular Particle Freezing Now, that we know how to use the freeze function, we could try to animate this effect. Our scene setup consists of just a few objects: An emitter and a cube, serving as a ground floor and some obstacles to make the simulation a bit more interesting.

pos r x

c

a

b

Fig. 25: The position of a particle in 2D space

The idea is to define a circle, shrinking towards the centre of the scene. The particles outside the borders of this circle will freeze, while the inner particles are still flowing. To control the speed of the desired effect and the radius of the circle, it's recommended to introduce a little helper object, e.g. a Null or a Cube object. We're just reading out the particle's position and the current x position of the helper object and compare these values. From the moment, the particles are outside the resulting value, they'll be frozen. To get the correct particle position, we need the Magnitude again.

The question is, how do we compare the current position pos of a particle with the radius r of our imaginary circle? The solution is Pythgoras' theorem for calculating a Vector's Magnitude (see p. 15). It makes no difference, whether we're extracting the Magnitude from a Vector in 2D or 3D space. That's our formula: c 2 = a2 + b2 c = (a2 + b2) After we received the value for c, which is a Scalar, we simply compare it against r and already got the condition we're after. In a circle, the radius remains constant at each point of the outline, so we don't need further checks.

Scripting With RealFlow

RF_magazine

54

Fun with Freeze With this knowledge we can write our little script, implementing the position check. The helper object, which is used to define the boundary, is animated. With each step, the new position is read out. To avoid troubles with positive or negative position values, we're simply taking the absolute value abs(radius), while the Magnitude is always positive. These two preconditions guarantee a hassle-free simulation. Events Script: CircularFreeze.rfs def onSimulationBegin(): emitter = scene.getEmitter("Circle01") emitter.setParameter("Speed", 2.0)

def onSimulationStep(): import math

Fig. 26: Particles outside the circle are frozen

The def onSimulationBegin( ) block is just for our convenience, to reset the scene to its initial values. Something you've already seen with the foam scripts is the import statement. Here, we're importing the math module, to provide Python with functions, like sine, cosine or square root.

emitter = scene.getEmitter("Circle01") helper

= scene.getObject("Helper")

h_pos_g = helper.getParameter("Position") radius

= h_pos_g.getX()

if (radius == 0.0):

emitter.setParameter("Speed", 0.0)

particle = emitter.getFirstParticle() while (particle):

To speed up the simulation, after the last particle has been frozen, we prevent the emitter from spitting out further particles. This condition is fulfilled when the helper object has reached the centre of the scene: if (radius == 0.0):

emitter.setParameter("Speed", 0.0)

The implementation of Pythagoras´ theorem can be found here:

p_pos_g = particle.getPosition()

p_pos_x = p_pos_g.getX()

p_pos_z = p_pos_g.getZ()

p_mag

if (p_mag >= abs(radius)):

As you can see from Fig. 26, the particles behave exactly as predicted. Particles outside the circle shaped boundary are darker, indicating lower speed.

p_pos_x = p_pos_g.getX() p_mag

= math.sqrt(p_pos_x ** 2 + p_pos_z ** 2)

particle.freeze()

particle = particle.getNextParticle()

p_pos_z = p_pos_g.getZ()

= math.sqrt(p_pos_x ** 2 + p_pos_z ** 2)

Of course this script could be extended, to check against elliptical, rectangular or other shapes.

Scripting With RealFlow

RF_magazine

55

Fun with Freeze Relaxing Particles with Freeze You've already learned a lot of things about the freeze statement. The next question surely is, whether it's possible to use freeze for fast relaxation of particles? Each time you want to generate a calm surface, you have to wait for ages, until the particles have settled. The freeze function seems to be a smart alternative to all known methods, but there's one decisive difference: With freeze you simply stop the motion of the particles at a certain point. The particles´ energy will be preserved. After unfreezing the particles again, the simulation goes on as if nothing has happened (as long as all environmental conditions don't change). So, the freeze function could be considered as an interruption of the current movement, while all parameters and values remain the same. The known relaxing methods really withdraw motion energy from the particles over time. The particles settle and the spaces between the “water molecules” become smaller. The volume of the particle cloud decreases. With an Initial State it's possible to use settled particles for further experiments. The freeze method is only suited for simulations, where you want to keep speed, impulse, and energy, or lock the particles at a particular state.

Inverting the Effect Until now we've only used Python to lock particles, but this is just half the truth, because RealFlow also knows the unfreeze method. The unfreeze function is great for partial dissolving of objects or characters. By defining boundaries, you're able to delete just portions of an object or let it disappear using a Wind daemon. For this purpose, again an animated helper object is needed. These helpers are more than a gadget, they allow us to have visible control over the settings.

Fig. 27: Object controlled dissolving of a rocket primitive

The script itself needs no witchcraft, we're just using the known principles and techniques. The main part again compares the current position of the helper object and releases the particles, if the given condition is fulfilled. In conjunction with an age function, the particles will disappear after a certain time. It's not possible to apply an Age daemon here, because it's simply too static. After a short while all particles would have been gone, even the frozen ones. To avoid this, we're introducing our own age function. The released particles (with a certain age) will be identified via their ID and then removed from the emitter. You've already met this principle with the foam script. There, we shifted the particles to the foam emitter and then removed them from the water source. Basically, this is pretty much the same method. The velocity of the helper object defines, how fast or slow the particles will be released. This surely may take some testing and adjusting, until you get the desired result. Together with some daemons you can achieve impressive effects and animations.

Scripting With RealFlow

RF_magazine

56

Fun with Freeze Events Script: ObjectDissolve.rfs def onSimulationBegin(): emitter = scene.getEmitter("Fill_Object01") rocket

= scene.getObject("Rocket01")

particle = emitter.getFirstParticle() while (particle): particle.freeze() particle = particle.getNextParticle() rocket.setParameter("Simulation", "Inactive")

def onSimulationStep(): import random age_threshold = 0.5 emitter

= scene.getEmitter("Fill_Object01")

helper

= scene.getObject("Helper")

h_pos_g

= helper.getParameter("Position")

h_pos_y

= h_pos_g.getY()

particle = emitter.getFirstParticle() while (particle): rand

=

random.uniform(-0.2, 0.2)

p_pos_g = particle.getPosition() p_pos_y = p_pos_g.getY() age

The onSimulationBegin function initializes our scene, including the freezing of all available particles from the Fill Object emitter. The second step is to make the Rocket inactive, to prevent the particles from colliding with the object. The unfreeze condition is a really simple if-clause, testing, whether the particles are located above the helpers y position (”height”) or not. Within this group of released particles, the age check is performed. By altering the age_threshold value you can increase/decrease the life span easily. If you don't want the particles to disappear, you can simply omit the age check!

Summary The idea behind this chapter was to show you the possibilities, you can get out from just two RealFlow functions. Also all scripts are very short and use only basic maths. With little efforts you can do stunning things. The only precondition is a little time to make up your mind before you start scripting. Please remember that there's always more than one way to solve a problem. The scripts, presented here are nothing more than one possible solution. Maybe you'll find a shorter, better or faster method, but that's not the point with this issue. The current edition of RF_magazine wants to show you that you don't have to build sophisticated and long-drawn-out scripts to achieve nice effects.

= particle.getAge()

if (p_pos_y > h_pos_y): particle.unfreeze() if (age >= age_threshold + rand): id = particle.getId() emitter.removeParticle(id) particle = particle.getNextParticle()

Scripting With RealFlow

RF_magazine

57

Examples and Ideas Overview Possibilities with scripting are numerous and there are solutions for many special issues and topics. To complete the previous chapters, here are some programs, showing you a variety of methods and principles, ready to use for your own work.

Events Script: ImageBasedSpeed.rfs def onSimulationFrame(): import math emitter

= scene.getEmitter("Circle01")

start

= 25

stop

= 125

Image Based Emitter Speed

range

= stop - start

frame

= scene.getCurrentFrame()

The usage of images and bitmap gradients is a wide spread technique for a dynamic control of parameters. Fractal height fields are the most common example. The greyscale or RGB intensity of a pixel is used to transform this value into height information. More sophisticated approaches use Displacement or Normal Maps to affect geometry. Something similar can be found in RealFlow's manual on PDF page 171 “08.2.Simulation Tasks: Create A Customized Wave Pattern”. There, an image sequence is loaded into RealFlow. The gathered intensities are transferred into vertex displacements to create an animated wave.

root_path

= scene.getRootPath()

Why shouldn't we use those values and intensities for other purposes? This example teaches you, how to access a TIFF image for controlling the speed of an emitter. The script isn't just going through the image, reading out some intensities. You can define a certain frame range, in which the values will be applied to the emitter. The image we're using is just plain TIFF image: 10 x 300 pixels with a gradient, ranging from pure white to pure black. Don't forget to store the images as RGB, not greyscale. The common notation for describing the size of an image is width x height. That's important, because we'll receive a List of values, representing these directions. In this example, we're using the height value, but this is just a matter of definition. The image format for this script shouldn't be compressed, since artefacts may falsify the values.

image_path = str(root_path)+"/templates/SpeedGrad.tif" template = Image.new() template.open(image_path) width

= template.size[0]

height

= template.size[1]

if ((frame >= start) and (frame New Composition • Make your settings, like NTSC, PAL, HDTV or any other format and specify the aspect ratios. • Layer > Color Plane • Make your settings • Add a position key to the Color Plane • Mark the key and copy it to the clipboard • Open a text program (WordPad, Word, TextEdit...) and paste the clipboard content into a new text file

Of course you can modify the position data in your text editor or Microsoft Excel® to your needs. The image below shows the RealFlow position data, transferred to a prerendered marble. The pink crosses indicate the keys, we can also see in the timeline.

The text file now shows the header, directly from AfterEffects®. This file serves as a template, you have to rebuild in RealFlow. The content should be similar to this figure:

Text files are well suited for manipulation, but RealFlow also knows functions to write out binary data. With this methods it’s possible to create your BIN or SD files, but this also requires knowledge about the file structure.

Scripting With RealFlow

RF_magazine

64

Appendix Reserved and Forbidden Words Python uses some words and expressions internally and they have a special meaning there. Thus it’s not allowed to name Variables as: and def exec if not return

assert del finally import or try

break elif for in pass while

class else from is print

continue except global lambda raise

These names should be avoided, though they aren't strictly forbidden, but may cause conflicts: array input write

close numeric zeros

data open

float range

int type

Maths related expressions should not be used. You must avoid them when the math library is imported: acos exp pi

asin fabs sin

atan floor sqrt

cos log tan

e log10

Also all keywords from RealFlow are forbidden. You can simply identify the RealFlow command, as they turn yellow, after they've been typed into one of the scripting windows. A complete overview is available from the online help’s scripting reference.

Scripting With RealFlow

RF_magazine

65

Appendix About The Scene Files RF_magazine contains scene files for each described tutorial and setup. All RealFlow scene files share a common scene scale of 0.01. In some cases, you have to rescale or recreate scenes to adjust them to your needs. The files are for demonstration purposes only and therefore not adopted to individual production settings. The files have been created with RealFlow version 4.3.8. It's possible that older versions of RealFlow can’t open the scene files. In this case, please update your version of RealFlow. The 4.3.8 update is free of charge and highly recommended.

Any violation of these terms will be prosecuted. All materials have been created and assembled with high diligence, though errors can't be totally excluded. RF_magazine can not be held responsible for computer or software crashes and loss of data. Use all materials, scripts, and files at your own risk.

rf-magazine.com The homepage is your source for all developments and updates regarding RF_magazine. There, you can find more releases and issues, as well as free tutorials:

Included scripts use a Cinema 4D/Lighwave 3D axis setup, with the y coordinate serving as height axis. If you’re working with other 3D applications, you probably have to change the height axis from y to z. The Cinema 4D files use absolute paths to texture, animation, and RealWave files. It’s unavoidable to recreate and adopt the paths to your local hard disk. To make things easier for you, each Cinema 4D directory carries a file called “_FilePathInfo.txt” containing the appropriate paths for simulation and texture data.

Cinema 4D

Scripting

Rigid Bodies

Choppy Waves

Attribute Maps

Legal Notes All materials, scripts, texts, files and images are copyrighted by RF_magazine, Thomas Schlick. It's forbidden to copy, share or provide the materials as a whole or as excerpts. The materials must not be shared in forums, file sharing, torrent servers or similar facilities. I takes several months of work to create a single issue of RF_magazine. Please be fair and respect copyrights. Thank you.

For more information and contents, please visit: The magazine and its materials must not be copied and given to third parties, neither free of charge nor against payment. The publication of citations, images, files or text excerpts is only allowed with written permission from RF_magazine, Thomas Schlick.

www.rf-magazine.com

Scripting With RealFlow

RF_magazine Copyright Thomas Schlick 2008 For updates, news and more please visit http://www.rf-magazine.com