We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 0
RF_magazine
REALFLOW USER MAGAZINE
FLUID DYNAMICS :: PHYSICAL SIMULATION :: SCRIPTING :: CONNECTIVITY :: REALWAVE ISSUE 02_2007 RF_magazine is brought to you by Liquidlight.tv www.liquidlight.tv Scripting with RealFlow - Introduction 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 Welcome to RF_magazine 02/2007 This issue deals with scripting. With RF4, Next Limit introduced a Python interface. Remembering the first reactions in the old official forum, scripting wasnt 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 people think about scripting and programming, many of them still have the impression that you need a deep understanding of maths. This edition wants to show - or even proof - that you can achieve stunning effects with little effort. Python scripting in RealFlow is your friend, not an enemy, because it makes life so much easier in many ways. 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 its an easy to learn and wide spread language. In many terms, Python almost reads as spoken English. This feature makes Python suited for beginners. With growing knowledge and experience, youll get a deep insight into Python and a better under- standing of RealFlow. Altering parameters with a self- written program is completely different from changing values within in a Node Params window. But theres one thing, even the best book or tutorial cant do for you: Reading carefully and trying to understand whats going on in a particular script. The basics, like Data Types or simple Vector maths are very important and cant be left out. Programming is also learning by doing! So, dont hesitate and dive into scripting with RealFlow. After a few hours youll be able to write your own first script. Sincerely yours, Thomas Thomas Schlick (tsn) Liquidlight.tv Nuremberg/Germany www.liquidlight.tv This magazine has been certified by Next Limit: Editorial 02_2007 RF_magazine 2 Editorial Welcome to RF_magazine Page 1 The Power of Scripting A First Glance Page 4 What is Scripting Page 4 The Advantage of Scripting Page 4 Python and RealFlow Page 5 Where to Start? Page 5 The Basics of Python Scripting in RealFlow Variables - First Steps with Scalars Page 6 Other Types of Variables Page 7 Creating Lists Page 8 Creating Dictionaries Page 8 Appending Values Page 9 Operators Page 9 Data Types Page 11 Vectors Page 13 Calculating with Vectors Page 13 Notation and Syntax Page 16 Getting Started Setting the Preferences Page 18 The Scripting Windows Page 19 Scripted Objects Page 20 Your First Steps Calling Emitters and Objects Page 22 Accessing Particles Page 23 Building Vectors Page 25 If & Else Page 27 Changing Attributes with Get & Set Introduction Page 29 How to Use Get & Set Page 29 Working with Particles Page 34 Conclusion Page 36 Contents 02_2007 RF_magazine 3 Creating Graphical User Interfaces Whats a Graphical User Interface Page 37 The Structure of RealFlow GUIs Page 37 File and Node Pickers Page 49 The Foam Project Project Overview Page 41 The Nature of Foam and Spray Page 41 Creating Foam Page 42 Controlling Values Through Objects Page 46 Collision Based Foam Creation Page 47 Optimizing the Script Page 49 Threshold Parameters Page 50 Credits Page 50 Fun with Freeze Project Overview Page 52 Freezing Time and Particles Page 52 Circular Particle Freezing Page 53 Relaxing Particles with Freeze Page 55 Inverting the Effect Page 55 Summary Page 56 Examples and Ideas Overview Page 57 Image Based Emitter Speed Page 57 Boxed Daemons Page 58 Exporting Position Data to AfterEffects Page 59 Appendix Reserved and Forbidden Words Page 64 Notes Issue 01_2008 Page 65 Legal Notes Page 65 Contents 02_2007 RF_magazine 4 Scripting with RealFlow 02_2007 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 therere still many users around, who want to explore the power of scripting, but often dont know where to start. Of course, the manual is an obvious source for information regarding scripting. Fusion CI Studio co-founder Dr. Mark Stasiuk did a very good job, as he explains lots of fundamental questions and ideas behind scripting. 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 program- ming 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 compil- ers available for scripting languages. The Advantage of Scripting I was talking about the benefits of compiled programs. Now, youll surely ask, why does RealFlow use a scripting language and not C/C++ or Java? With scripting languages you dont 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 dont 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 its easier to start with a scripting language, but in my personal opinion, its 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 orienta- tion. Often used scripting languages are Perl, Python, Java- Script, 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. The Power of Scripting RF_magazine 5 Scripting with RealFlow 02_2007 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 doesnt have to struggle with instal- lation directories and libraries. With the installation of RealFlow you get the complete Python package, ready to start. 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 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 making 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 its better to speak in a clear and easy way. Another misunderstanding is that you have to be a maths genius to write good programs. Of course a basic knowl- edge of mathematics is useful, but not mandatory. If you just want to automate some processes, you wont 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, its rather easy to find those examples. No matter what youre doing, it will directly influence the objects on your screen. Maybe youve 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 its not suitable. Were after completely different things. Before we can kick off, its indispensable to learn the basics of Syntax and Notation to understand the principles of how Python works. The Power of Scripting Fig 1. Script affected particles of an emitter RF_magazine 6 Scripting with RealFlow 02_2007 Variables - First Steps with Scalars Variables are the key to everything, independent of the used programming language. A Variable could be consid- ered 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 infor- mation from a person: 25, female, Claudia. Without setting these information into a logical context, we cant capital- ise on these data. Im 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 its 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 thats absolutely correct, but the information isnt very clear, especially when there are similar values or properties: A = "Claudia" B = 25 C = "Beatrice" D = 56 After a while, youd certainly have no clue what the Variable names are standing for. With Variables declared like this, its no problem: First_Name = "Claudia" Age = 25 Second_Name = "Beatrice" Weight = 56 Its 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 theyre needed, but its 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. Variables 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. 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 youre writing numbers within quota- tion marks, theyll be treated as a string.
The Basics of Python Scripting in RealFlow RF_magazine 7 Scripting with RealFlow 02_2007 Other Types of Variables As youve 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 its own drawer, and theyre all arranged in a fixed order. The numeration of these drawers starts with 0 (Zero). Python also starts the numeration of Lists with 0. Lets assume that each drawer contains coloured marbles: 0 = red, 1 = blue, 2 = green, 3 = yellow, 4 = purple By opening drawer number 2, youll have access to the green marbles, opening number 0, will lead to red ones. The List provides a container with a fixed sequence. Whenever you want to have purple marbles, you have to call position number five. But wouldnt it be nice to have a method to arrange the marbles the way we want? The solution is called Dictionary. In a Dictionary, theres no sequence and all contents could be disordered. In difference to Lists, its 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 cant identify one without the other: Claudia : 54358 Lydia : 43663 Agnes : 65586 Susan : 94343 Imagine the phone book from your cell phone. By entering a name, the software prints out the according phone number. Thats exactly the way, a Dictionary is working. It doesnt matter, where the entries are located within the book, because theyll always be identified via a search key. In RealFlow Dictionaries is rarely used, but nevertheless there might be some cases, where you need them. 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 forbid- den. Just one final note: a List is sometimes called an Array, and a Dictionary is also known as Hash or Associative Array.
Fig 2. A List can be used to arrange values and entries The Basics of Python Scripting in RealFlow RF_magazine 8 Scripting with RealFlow 02_2007 Creating Lists Creating a List structure in Python is an easy task. An empty List is written as: my_list = [] If you want to define the values directly with the creation of the List, the format is: my_list = [value1,value2,value3, ...,value n] For better understanding, Id like to create a List with the colours of our marbles from page 7: colours = ["red","blue","green","yellow","purple"] As you can see, all colour names are between quotation marks. You dont need these marks for numbers: diameter = [2.0, 2.5, 3.0, 3.5, 4.0, 4.5] In Python its easy to find elements, stored in a List: favourite_colour = "yellow" 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) isnt part of colours, the result would be Sorry. Your favourite colour is not available. The next method uses the stored indices. Since each value has a fixed position within the List, its possible to address a colour using this index. Remember that List indices always start with 0. colours = ["red","blue","green","yellow","purple"] selection = colours[2] print selection Result: green 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"] number_of_entries = len(colours) print number_of_entries Result: 5 Creating Dictionaries Youve already learned that elements in a Dictionary dont 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 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 Isnt that easy? You dont have to worry about indices, numeration or positions. The Basics of Python Scripting in RealFlow RF_magazine 9 Scripting with RealFlow 02_2007 Analogue to Lists, its also possible to get the length of a Dictionary. The function calls all key-value pairs and stores the result in a predefined Scalar: phonebook = { "Claudia":54358, "Lydia":43663, "Agnes":65586, "Susan":94343} number_of_entries = len(phonebook) print number_of_entries Result: 4 Appending Values Now you know how to create Lists and Dictionaries manu- ally 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 wont 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 theyre rarely used with RealFlow. In most cases you extend a List by simply appending a new value. This is the syntax for Lists: colours = ["red","blue","green","yellow","purple"] new_colour = "orange" colours.append(new_colour) Adding a value to Dictionary is slightly different. We dont need a fixed order and therefore the position of the new element isnt important. An entry of a Dictionary always consists of two elements: The key and its value. Its very important to define which one will serve as a key, respec- tively as a value.
phonebook = { "Claudia":54358, "Lydia":43663, "Agnes":65586, "Susan":94343} phonebook["Helen"] = 22986 Its also possible to use Scalars and add their values to the Dictionary: new_name = "Helen" new_number = 22986 phonebook[new_name] = new_number For the moment this should be enough. Youll learn more about these data types in connection with scripts. Operators Operators are one of the most important things in Python. With an Operator its possible to perform basic arithmetic calculations and comparisons. And thats truly a bless. Without Operators, you wouldnt be able to compare particle velocities or positions against a Scalar and thered been no way to multiply or substract values. You can find Operators anywhere in a script and Python knows a lot of them. The first group contains all arithmetical Operators: + Addition 15 + 17 = 32 - Substraction 64 - 30 = 34 * Multiplication 10 * 12 = 120 / Division 80 / 10 = 8 ** Exponentiation 12 ** 2 = 144 % Modulus 28 % 7 = 0 + String concatenation John + Doe = JohnDoe * String repetition Hi * 3 = HiHiHi A very nice Operator is Modulus, but it needs some expla- nation. In general, Modulus tells you, if theres a remain- der after performing a calculation or not. With Modulus its 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. The String Operators are rarely used with RealFlow and more important for text processing applications. The Basics of Python Scripting in RealFlow RF_magazine 10 Scripting with RealFlow 02_2007 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: Name = Claudia The result you get with Comparison Operators is true or false. These are the two possibilities and theres nothing between: 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 wont calculate with true or false. Its just a means for making decisions, what should happen with your values or parameters. It can be consid- ered as a crossover, where you also have two or more ways to go. Lets say, you want to limit the velocity of a parti- cle: max_velocity = 7.5 current_velocity = 8.2 if (current_velocity > max_velocity): new_velocity = max_velocity else: new_velocity = current_velocity What happens here? The current velocity of a certain particle has been determined as 8.2. With an Operator were testing, if the value of the current velocity is greater than the maximum velocity. This value is a threshold. Whenever a particles velocity is greater than this limit, its 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 current_y_position = 2.9 if (current_y_position == max_y_position): attractor_force = 25 else: attractor_force = 10 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 Youll mostly need these Operators when you have to check more than one condition. if (cur_vel >= threshold_1 or cur_vel <= threshold_2): gravity_force = 9.81 else: gravity_force = 0 In this case, were checking, whether the current velocity lies between two threshold values. If the condition is true, the gravity force will be activated. The Basics of Python Scripting in RealFlow RF_magazine 11 Scripting with RealFlow 02_2007 The last class Im talking about here, are Augmented Operators. This is a very special class and mostly used for abbreviations. In some cases, its 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: while (particles): total_mass += particle_mass The most common Operators are += -= /= *= **= %= There are some more Operators in Python, but theyre not often used with RealFlow. By knowing the discussed classes, you already have the tools to create your own conditional expressions and basic calculations. 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: -5, 2, 100, -4335, 757, 423843, -3289988, 45, 234, -775647 The set of Integers is infinite, it can be positive or negative and it doesnt contain fractures like 3.645, -1.3333 or 12.7. Fractured elements arent members of the Integer family. Also fractures, apparently consisting of two Integers arent valid elements: or . Though the result of such a division can be an Integer! 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 theres 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 This range is also called 32-bit precision and its the minimum for all modern operating systems. Most operat- ing systems also support 64-bit precision. The valid range for this type is: 9,223,372,036,854,77,.808 to +9,223,372,036,854,775,808 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. The next, very important type is called Float. Youve 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. Its possible to calculate with decimal places of different length. Of course its allowed to combine Integers and Floats together. Valid expressions would be: 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 wont encounter any problems. The token in Python is simply float. The Basics of Python Scripting in RealFlow RF_magazine 12 Scripting with RealFlow 02_2007 The last Data Type, Im talking about, is called Complex. In RealFlow this type will be rarely used and its only men- tioned 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 imagi- nary part must own a j character: (3 + 6j) Even if the imaginary part is Zero, you have to write this value in conjunction with j: (12 + 0j) With Complex numbers its possible to perform calcula- tions as usual: (2 + 4j) * (8 + 3j) or (11 + 1j) * (7 + 5j) In Python, this special Data Type can be introduced with a complex statement. Now youve 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 dont 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 youd like to use. One of these cases are GUIs - Graphical User Interfaces. Youll read about GUIs later, starting on p. 37, but Id like to introduce some basic information about the usage of Data Types: With GUIs its often allowed and wanted, to enter custom values for initializing a calculation. Lets 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 cant build a tower with 5.278 floors. The width and height instead, can measure 2.5 or 7.84 units. The used Data Types in this example are: levels = int e.g. 10 width = float e.g. 2.5 height = float e.g. 3.75 Another often used exercise is the conversion of Data Types. Its 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 youre 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. The Basics of Python Scripting in RealFlow Fig 3. A tower based upon integer and float values RF_magazine 13 Scripting with RealFlow 02_2007 Vectors 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 theyre fully integrated. Since theyre not part of Pythons standard installation, Id 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 Vector could be considered as an arrow, pointing into a certain direction. To describe a Vectors 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. Vector coordinates can consist of Integers and Floats. The graphical illustration of a Vector might help to get an understanding, but its not suitable for calculations. Therefore we have to find a notation. Therere some established forms to represent a Vector, but according to RealFlows notation, a Vector in RF_magazine is always written as: a= (x, y) or a = (1.0, 1.0) In RealFlow were dealing with 3D space and the represen- tation of a Vector needs a third coordinate: a = (x, y, z) or a = (1.0, 1.0, 1.0) If yourre interested in real life examples or the math- ematical background, Id suggest to have a look at physics books or the internet, e.g. Wikipedia. Calculating with Vectors Its 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 were looking for c = a + b. Maybe its just c = (a x + b x , a y + b y , a z + b z ) 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 its analogue with Vector Substraction c = a - b: c = (a x - b x , a y - b y , a z - b z ) 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 its possible to multiply the Vector components with another Scalar. Its likely that you will use this method very often, especially with forces. The Basics of Python Scripting in RealFlow Fig 4. Different vectors pointing at various directions y x RF_magazine 14 Scripting with RealFlow 02_2007 Multiplication with a Scalar is calculated this way: a = (4.8, 2.9, 1.4) s = 2.5 b = s * a b = (s * a x , s * a y , s * a z ) 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. Dont 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 = a x * b x + a y * b y + a z * b z 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 = a 2 c = a x 2 + a y 2 +a z 2 Extracting the square root from this term, yields to a Vectors Magnitude (often called Norm or Length). | c | = a x 2 + a y 2 +a z 2 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 wont have to worry about this rule, because theres a built-in function for calculating the Scalar Product. In RealFlow its called Dot Product, repre- sented by an asterisk character ( * ). The next rule, Im 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 = a x b c x = a y * b z - a z * b y c y = a z * b x - a x * b z c z = a x * b y - a y * b x c x = 6.1 * 3.8 - 1.0 * 0.4 c y = 1.0 * 0.7 - 2.2 * 3.8 c z = 2.2 * 0.4 - 6.1 * 0.7 c x = 23.18 - 0.40 = 22.78 c y = 0.70 - 8.36 = -7.66 c z = 0.88 - 4.27 = - 3.39 c = (c x , c y , c z ) c = (22.78, -7.66, -3.39) This operation is also implemented in RealFlow by default and you dont 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 youll certainly meet the Cross Product again. Many formulas, dealing with the simulation of natural, turbulent phenom- ena 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. Youll 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 cant remember it, dont worry and turn over... The Basics of Python Scripting in RealFlow RF_magazine 15 Scripting with RealFlow 02_2007 Pythagoras theorem is normally used for triangle calcula- tions. In Vector maths, where you also could represent a Vector as an arrow, its possible to extract values by constructing triangles from the given Vectors. With Pythagoras theorem youre now able to get the Length, or Magnitude of a Vector: Pythagoras theorem for right-angled triangles: a 2 + b 2 = c 2 You can get the value for c by extracting the square root: c = sqrt( a 2 + b 2 ) 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. a = (3.2, 2.8, 1.5) | a |= sqr(a x 2 + a y 2 +a z 2 ) | a ] = sqr(10.24 + 7.84 + 2.25) | a | = 4.51 For fluid, smoke and fire simulations, physicians intro- duced 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 dont want to dive into the depth of these fields. 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 doesnt provide functions to store values over time and substitutes the previous results. A big variety of very interesting Vector Fields can be explored here with a Java Applet: http://www.falstad.com/vector3d/ The Basics of Python Scripting in RealFlow Fig 5. The Magnitude of a Vector Fig 6. 3D representation of a Vector Field with arrows x y a b c pos RF_magazine 16 Scripting with RealFlow 02_2007 Notation and Syntax Now youre getting closer to scripting and the theoretical part lies almost behind you. In this chapter youll learn how to organize a RealFlow script and what you have to attend. When youre writing Python scripts for RealFlow you have to follow certain rules. Without obeying these rules, youll receive error messages and your program wont run. The best idea is, to avoid those errors from the very beginning. As Ive seen in the unofficial RealFlow forum, the most common error is related to shifting and spacing. Python recognizes clauses, conditions and syntax groups auto- matically. The indicators are leading tabs and blanks. program start: statement 1 if ( condition ): statement 2 else: statement 3 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, theres 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. Id recommend to use the Tab key. With this key you dont 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 dont have to care about tabs, but you shouldnt forget to close all branches. Otherwise youll 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, its still a very good idea to use tabs for better readability. With Python its essential and especially with copied and pasted scripts, you have to be extremely careful. Scripts from forums, discussion boards or other sources often dont follow these rules for leading tabs, and this directly leads to problems. Python is a very flexible programming language. It allows many short forms and convoluted statements. For begin- ners its often very hard to understand, whats 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 youll 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 cant 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 objects properties, like: emitter.getPosition() The Basics of Python Scripting in RealFlow RF_magazine 17 Scripting with RealFlow 02_2007 The Syntax of a script can be seen as a grammar. Violating the rules leads to errors. Therere many definitions and essays about Syntax and especially for beginners its 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, its flexible and theres 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 youll 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 The Basics of Python Scripting in RealFlow Fig 7: RealFlow error message RF_magazine 18 Scripting with RealFlow 02_2007 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 func- tions. To adjust this environment to your needs, Id recommend to set the scripting preferences first: realflow > Preferences > Script After the Script tab has been selected, a new panel appears, showing a variety of settings: 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: # Get the scene emitter particle_source = scene.getEmitter("Circle01") Highlighting certain keywords of a script helps you to keep the code readable and its easier to differentiate the individual elements. The Tab size determines, how many leading blanks are inserted to structure the Python code. You should always use tabs instead of blanks or spaces to introduce new instructional blocks (also see p. 16). In conjunction with this setting, its 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 mono- space fonts its possible to arrange Variable names like this: pi = 3.14 gravity = 9.81 e = 2.718 ini_vel = 12 With fonts using variable widths, its not possible to align names, numbers and other characters correctly: pi = 3.14 gravity = 9.81 e = 2.718 ini_vel = 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. In RealFlow its 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. The last three icons are customized and indicate some of my own scripts. Please note that its 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 youve made the settings, just click OK to use them as new defaults. Getting Started RF_magazine 19 Scripting with RealFlow 02_2007 The Scripting Windows The Scripting Windows are a true obstacle for many beginners, because they simply dont 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 Lets have a look at Batch Scripts first. Whats a Batch script and whats 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. 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 dont need to start a simulation, with an Events Script its crucial. RealFlow knows a couple of predefined events: onSimulationBegin onSimulationEnd onSimulationStep onSimulationFrame onChangeToFrame onSimulationBegin is suited for applying initial param- eters, You can also add some basic elements to a scene that will be used for simulation. After this initialization, the routine wont be called again. onSimulationEnd works analogue to onSimulationBegin. Maybe the most impor- tant 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. Fig. 8: The Batch Script window Getting Started RF_magazine 20 Scripting with RealFlow 02_2007 Maybe the best example for a script using an onSimula- tionStep event is the well known foam script. This script will be discussed later as separate project, starting on page 41. With onSimulationFrame its 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 post- processing. With this event, scripts can be applied while playing back a cached scene. Its rarely used, but neverthe- less a powerful means for some applications. After opening the Events Scripts window, youll recognize that these functions are already implemented. 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 youre 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. Thats no problem, but before youll be able to run a script, you have to check Active. Scripted Objects Custom scripts are sectioned into a couple of subtypes and theyre 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 ): Its 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 youre dealing with forces, represented as Vectors, some basic knowledge of applied mathematics is recommended. 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. Fig. 9: The Events Script window Getting Started RF_magazine 21 Scripting with RealFlow 02_2007 Another very powerful facility is the Scripted Emitter. With this type its theoretically possible to write your own fluid solver, but we dont want to go that far. Maybe youre 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 youve 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 param- eters from an emitters Particles panel. This is truly a mighty feature! The Scripted Emitter is not an indivdual 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: 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. 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. Heres a screenshot of how you can access this feature: Again, an Edit button appears, to open the scripting window. There, youll also find just one function to update the vertices of the RealWave object: updateWave( vertices ) Please note, that only its only possible to 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 greyscale images as displacement templates. This technique is described in detail in RF_magazine Issue 01_2007 Cinema 4D Special Edition, page 40 et seqq. Of course its possible to develop your own formulas to create new, not implemented wave types. Fig. 10. Parameter rollout for the Custom Emitter type Getting Started RF_magazine 22 Scripting with RealFlow 02_2007 Calling Emitters and Objects Now your persistence and patience will be rewarded finally, because youre about to make the first steps with Python and RealFlow! Start with a Circle emitter by choosing: Edit > Add > Emitters > Circle > Square The Node window now shows a new object called Circle01. To use this emitter within a script, its necessary to address it and store it in a Variable. The Variable type, were introducing here is a Scalar: 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: 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 its required to change an objects name. Lets 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: emitter = scene.getEmitter("Water") The Variable name may also be subject to change. In many cases theres more than one emitter and you need a more meaningful name. As youve already learned, its always a good idea to use significant names. Later were developing a foam script. This type of script uses multiple emitters and we have to distinguish them by name: water = scene.getEmitter("Water") foam = scene.getEmitter("Foam") 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. 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) In this case were 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 getEmit- ter( ) and not a String (= sequence of characters), we dont write quotation marks. With objects and other items its the same procedure: collission_object = scene.getObject("Torus") animated_daemon = scene.getDaemon("MainAttractor") main_camera = scene.getCamera("Camer01") rope_constraint = scene.getConstraint("Rope01") fluid_mesh = scene.getMesh("Fluid") Your First Steps RF_magazine 23 Scripting with RealFlow 02_2007 Accessing Particles Lets 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 Resolu- tion 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 therere particles in the scene, the loop will be executed: fluid = scene.getEmitter("Circle01") particle = fluid.getFirstParticle() while (particle): do something here particle = particle.getNextParticle() Whats 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 parti- cles. With this method its possible to get information from each particle in your scene. While the loop is executed, youre 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 its recommended to skip particles for speeding up a simulation or for testing purposes. On page 9 I was talking about the Modulus Operator %. This is exactly what youll need for this purpose. With this example Im also introducing a new principle - the if clause. Youll 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. Theres also a short form available (please see page 11) : counter += 1 The result is exactly the same value you can see under Node Params > Statistics > Emitted Particles The Modulus Operator % tells you, whether the operation produces a remainder or not. Only if theres 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. Your First Steps RF_magazine 24 Scripting with RealFlow 02_2007 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 its necessary to gather colliding particles for foam genera- tion, 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 getParticlesCollid- ing. 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 its 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. Lets say, you have a group of six spheres, but the operation should only affect the first three objects. spheres = [S0,S1,S2,S3,S4,S5] no_of_spheres = len(spheres) stop = no_of_spheres / 2 start = 0 step = 1 for object in range(start, stop, step): current_sphere = spheres[object] do something here Whooo! Whats 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 dont worry, because everythings 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, its 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, its 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, youre using the current value of the loop as a reference on the List containing the spheres: current_sphere = spheres[object] Since were starting with start = 0, in our example wed get these expressions (actually we cant see this term, but this is the way it looks like internally): current_sphere = spheres[0] ( S0 ) current_sphere = spheres[1] ( S1 ) current_sphere = spheres[2] ( S2 ) After youve stored the sphere in a Variable, youll be able to perform calculations. With current_sphere = spheres[3], the loop ends. Id 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 hasnt got to be 1 necessarily. Other Integer values are also allowed, but with different steps, you might have to change the stop value! Your First Steps RF_magazine 25 Scripting with RealFlow 02_2007 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] 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 conjunc- tion with Lists. Building Vectors Vectors are one of the most important Data Types within RealFlow. Theres already been a detailed introduction on page 13 et. seqq. Here, youll learn how to use Vectors in a script and how to extract a Vectors components. All RealFlow calculations are done in 3D space. The posi- tions of a particle, emitter or object can be described with their x, y, and z coordinates. Starting at the origin of a scenes coordinate system, these three values can be considered as an address, where you can find the object. So itd be possible to draw an arrow from the origin to the objects position. This arrow is the graphical representa- tion 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 its 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 its 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? Theres got to be a way to split the Vector into its elements. Indeed, RealFlow knows such a method: position_x = position.getX() position_y = position.getY() position_z = position.getZ() Fig. 11: Velocity vectors in RealFlow Your First Steps RF_magazine 26 Scripting with RealFlow 02_2007 In most cases youll 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() while (particle): # 1. Get global position data in Vector format pos_g = particle.getPosition() # 2. Get components from the position Vector 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 # 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 whats happening here, I want to deconstruct this little proggie. The first part uses a well known method to loop through the emitters (Circle01) particles. 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. 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 theyre 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. The last steps assigns the new_pos_g Vector to the particles using a setPosition instruction. Youll 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. The last line of the script simply calls the next particle. The representation in RealFlow isnt very spectacular, but the script implements most of the principles, introduced so far. Fig. 12: The result from the Events Script ParticleNewPos.rfs Your First Steps RF_magazine 27 Scripting with RealFlow 02_2007 If & Else When youre writing a script, its 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: Im 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: available_money = 2500 computer_price = 3200 if (available_money < computer_price): cant 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 youre 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): cant 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, its also possible to perform calculations within the if clause. Another, equivalent notation would be: total_money = availabe_money + grannies_gift if (total_money < computer_price): cant buy computer else: go to computer store You could also reverse the comparison: if (total_money >= computer_price): go to computer store else cant buy computer 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 Its also possible to use an and statement. This is often needed for setting a certain limit: ram = 200 gpu = 350 if (ram <= 250 and gpu <= 400): buy both articles else: buy only graphics board Your First Steps RF_magazine 28 Scripting with RealFlow 02_2007 Another effective method for multiple queries is the elif statement. The elif expression is an abbreviation and stands for else if. You can use (almost) as many elif state- ments as you want to: if mandatory once elif optional mutliple else optional once This time you want to sell your old computer. The decision chain could be like this: offer = 475 retail_price = 500 min_price = 400 if (offer >= retail_price): sell computer immediatly elif (offer >= min_price and offer < retail_price): contact customer for further negotation else: dont 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 retail_price = 500 min_price = 400 if (offer >= retail_price): sell computer immediatly elif (offer >= min_price): contact customer for further negotiation else: dont sell computer The result from this example is ambiguous. The first criteria is fulfilled, since 520 is greater than 500, but the second condition is also true, because 520 is greater than 400, too. To avoid such an inconsistency, you have to introduce a second requirement, like in the first example on the left. In Python (and all other programming languages) its allowed to construct convoluted if-else-conditions: offer = 540 retail_price = 500 zip = 90482 if (offer >= retail_price): if (location == zip): shipping_cost = 10 else: shipping_cost = 25 else: dont sell computer With nested if-else constructions, its possible to assign case dependent values to a Variable. Having a look at the example above, youll 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 customers location. Your First Steps RF_magazine 29 Scripting with RealFlow 02_2007 Introduction You already know the basics of Python scripting in RealFlow. Youre able to loop through an emitters parti- cles, youve 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 RealFlows Python extension. With the get and set state- ments, 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 its necessary to sort the instructions, to keep an overview. In this issue, were 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. Youve 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 particles Vector position data. With different get instruc- tions, youll 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 many parameters and RealFlow objects. The next expres- sion 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 its only possible to store one value, while Lists can carry two or more entries. All descriptions of the available commands are working the same way, as the examples above. With a little experi- ence, youll be able to forecast the used object and Data Type, but if youre 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 compila- tion, sorted by object types. How to Use Get & Set The concept behind the get and set statements is simple, but very clever, because its directly connected to RealFlows 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. Changing Attributes with Get & Set RF_magazine 30 Scripting with RealFlow 02_2007 In this example, a Cone had been added to the scene. The parameter rollout above contains everything you need to access the objects 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. The Data Type you receive with the getParameter command can also be read out from the Node tab. When- ever you see a triplet of values, the result will be a Vector: Position 0.0 0.0 0.0 Rotation 0.0 0.0 0.0 Scale 1.0 1.0 1.0 Pivot 0.0 0.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 Dynam- ics are Strings. The fields can contain words or expressions: Simulation Active Dynamics No If WetDry Texture was set to Yes, wed also have some Integer values, like @ resolution 256 The values for @ filter strength and @ ageing are Float types again. As already mentioned, its 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 cant replace the Floats with Strings. This would cause a Syntax error: pos_new = Vector.new("left", "up", "right") Changing Attributes with Get & Set RF_magazine 31 Scripting with RealFlow 02_2007 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 def onSimulationFrame(): cone = scene.getObject("Cone01") # 1. Get global position data in Vector format pos_g = cone.getParameter("Position") # 2. Get components from the position Vector pos_x = pos_g.getX() pos_y = pos_g.getY() pos_z = pos_g.getZ() # 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, were just using a single object. The process of getting, splitting and assem- bling the Vectors is finally the same. The main difference lies in the last statement: cone.setParameter("Position", new_pos_g) With the particle method, its just particle.setPosition(new_pos_g) Particles dont use the same get and set commands as physical objects in RealFlow. For an emitter or a mesh, its possible to see values directly in the corresponding Node Params window. Position or Velocity values arent displayed at all. Theyll be used internally. For faster access and better distinction, the particle attributes dont use the Parameter keyword. And theres 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, Rough- ness...) and the new value using the correct Data Type. 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. Tip Dont 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. Its better to type scripts manually and think about whats happening. Fig. 13: The Cone performs a constant movement in y direction Changing Attributes with Get & Set RF_magazine 32 Scripting with RealFlow 02_2007 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 its 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: Our goal is to deactivate the objects, after theyve 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. 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, Im going to use here is named: getSelectedNodes() With this command, you just select the objects you want to be affected by the script from the Nodes window. 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 theres 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") The if condition checks, whether the List colliding_objects is empty or not. The expression if (colliding_objects != []): 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. 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, itll be set to Inactive and the enclosed fluid can be released. On the next page youll find the listing for this script. Changing Attributes with Get & Set RF_magazine 33 Scripting with RealFlow 02_2007 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 dont 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. The only limitation is that the objects have to collide completely with the floor item, before theyll be inacti- vated. With the first contact the items remain active! Fig. 14: Dissolved objects after collision Changing Attributes with Get & Set RF_magazine 34 Scripting with RealFlow 02_2007 Working with Particles Until now, you have learned a lot about getting and setting attributes from objects. As Ive already mentioned, particles represent an own class and provide direct access to special values and characteristics. When you have to work with particles, its always neces- sary to construct a loop function and call them individu- ally. 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: Changing these values may cause instabilities and a crashing RealFlow application. You should alter these values carefully. With a Scripted Emitter its safe to change these values. But here I want to talk about an emitters output: Particles. While looping through an emitters particles, you have access to a wide variety of attributes. The most common values will probably be these two commands: getVelocity( ) getPosition( ) Velocity and Position can be changed directly by creating a new Vector after performing a calculation. Other values are not meant to change, but they can be used for control structures. With Density or Pressure, for example, youre 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 theyre just meant for internal calcula- tions. Heres how to use them: emitter = scene.getEmitter("emitter_name_here") particle = emitter.getFirstParticle() while (particle): particle.getVelocity() particle.getPosition() particle.getMass() particle.getPressure() ... particle = particle.getNextParticle() In many cases its important to get an idea of a values dimension. Is it large or small, a Float or an Integer? Especially with if-clauses its crucial to know the magni- tude. RealFlow knows a function for printing out any kind of value: scene.message(str(variable_name)) This statements directly prompts the appropriate value to RealFlows Message window, but in some cases you might get results like this: RealFlow vector at <0xdee2> Changing Attributes with Get & Set RF_magazine 35 Scripting with RealFlow 02_2007 A result like the one from page 34, is displayed when you try to print out the value of Lists, Vectors or Dictionaries. Whenever theres 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: vec_z = vector.getZ() entry = list[2] scene.message(str(vec_z)) scene.message(str(entry)) 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 its 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! 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 dont have to be complicated to be effective. Even with easiest means its possible to achieve good results. With a daemon based approach, itd be rather difficult to achieve a result like the one on the right. Events Script: StopOnMass.rfs def onSimulationStep():
while (particle): mass = particle.getMass() total_mass = total_mass + mass 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 Theres 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 its density. Setting Density to 100.0 in the Node params window, will roughly produce 10x more particles than a standard Density of 1000: Changing Attributes with Get & Set RF_magazine 36 Scripting with RealFlow 02_2007 Another possible application for particle manipulation are Scripted Daemons. With daemons its possible to apply external forces to particles. The following example applies a height dependent force to a particle. The first step is to create a Scripted Daemon: 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. Scripted Daemon: HeightDaemon.rfs def applyForceToEmitter(emitter): 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 = particle.getNextParticle() OK, lets 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 perma- nently. Within the loop, the script calculates a height dependent gravity force: gravity = accel / ((pos_y * factor) + 0.001) 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 theres 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 arent absolutely necessary, but sometimes it can improve readability. The expression: ((pos_y * factor) + 0.01) could also be written as (pos_y * factor + 0.001) 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. Isnt that nice? With some easy and fully customizable steps its possible to script your own daemon. Another idea is to make the gravity force velocity dependent. 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 whats possible. They range from beginner to intermediate, but therell surely come an issue of RF_magazine, covering advanced scripting... Changing Attributes with Get & Set RF_magazine 37 Scripting with RealFlow 02_2007 Whats 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 youre 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, param- eter tabs and selection lists, filled with emitters, daemons or constraints. Youve 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 wouldnt have to change the source code for altering some parameters. RealFlow and Python offer functions to create those GUIs. Of course theyre 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 theyre 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, youre 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, youre expecting from the user. So, its indispensable to make up your mind about Data Types before you can start to build a GUI. But with a little exercise youll quickly get a feeling for these Variables. With RealFlow GUIs its 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 arent 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. Creating Graphical User Interfaces RF_magazine 38 Scripting with RealFlow 02_2007 Lets have a closer look at these add statements. Each statement expects a series of arguments, defining the basic information for the user: The fields 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 cant share the same name for two or more fields. To give you a first impression, of how GUIs are created, heres an example for setting up a basic scene emitter with a Batch Script: form = GUIFormDialog.new() e_types = ["Circle","Square","Sphere","Triangle"] 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) form.addStringField("Name","BasicEmitter") As you can see from this example, the List Variables are used within the addListField( ) statement, also all non- variables are written between quotation marks, both in the Lists and the argument brackets. This script snippet wont work in RealFlow so far, because some functions are still missing. But theres a preview on the right, of how the resulting window will look later. You might notice that the field names arent arranged in the order you defined within the GUI creation block. Unfortu- nately RealFlow sorts the fields alphabetically, but this aspect wont impact the GUIs functionality. The second step for a fully working GUI is the assignment of Variables, used by the script. Its 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): 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: variable_name = form.getFieldValue() The getFieldValue( ) statement also needs an argument and this is exactly the fields name. emitter_type = 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 its obvious why its 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. Creating Graphical User Interfaces RF_magazine Scripting with RealFlow 02_2007 Batch Script: CreateBasicEmitter.rfs # Create new modal window and define fields form = GUIFormDialog.new() e_types = ["Circle","Square","Sphere","Triangle"] 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) form.addStringField("Name","BasicEmitter") # Transform user values into variables if (form.show() == GUI_DIALOG_ACCEPTED): emitter_nr = form.getFieldValue("Emitter") particle_nr = form.getFieldValue("Type") resolution = form.getFieldValue("Resolution") density = form.getFieldValue("Density") viscosity = form.getFieldValue("Viscosity") name = form.getFieldValue("Name") # Get the emitter/particle type from the lists emitter_type = e_types[emitter_nr] particle_type = p_types[particle_nr] # Create emitter using the new variables emitter = scene.addEmitter(emitter_type) emitter.setParameter("Type", particle_type) emitter.setParameter("Resolution", resolution) emitter.setParameter("Density", density) emitter.setParameter("Viscosity", viscosity) emitter.setName(name) 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, were just using Variables. For better understanding, Id like to explain the way of a value through the entire script: 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 The List objects are bit tricky, but there shouldnt be anything new to you. The addListField( ) statement is not working with strings, but with indices. Instead of form.addListField("Emitter", e_types, "Circle") 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) gets the correct type for the GUI window. The problem is that the GUI also assigns 0 to this expression: emitter_nr = form.getFieldValue("Emitter") We need a string for this expression, not a number, because there is no emitter type called 0: emitter = scene.addEmitter(emitter_type) With this expression we get a String from the e_types list: e_types = ["Circle","Square","Sphere","Triangle"] emitter_type = e_types[emitter_nr] emitter_type = e_types[0] 39 Creating Graphical User Interfaces RF_magazine Scripting with RealFlow 02_2007 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: 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 itll be FILE_PICKER_SAVE. The next expression specifies the path to the appropriate directory you want to open, followed by the file extension, youre 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 arent allowed: "*.jpg;*.tga;*.tif" If you dont want to restrict the file type, just use the common wildcard notation "*.*" 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()) This example is directly taken from RealFlows online help and shows the construction of this method. With the TYPE expression, its 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 begin- ners its 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. 40 Creating Graphical User Interfaces RF_magazine 41 Scripting with RealFlow 02_2007 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, Id like to start with foam creation, because its a very good example for the implementation of the methods and concepts, youve 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. 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 fluids 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. Spray is of very similar nature, but a tenside is not neces- sarily 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 arent 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 differ- ently 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 therere some properties, we can use for simulating foam. 1. The creation process may 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 lighter than water 5. Spray and foam can reform to water and vice versa 6. Spray and foam consist of large particle amounts Thats 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 its not easy to calculate the internal forces between particles. Wed need a Scripted Emitter for this purpose and, of course the appropriate formulas. The Foam Project Fig. 15: Spray from breaking waves RF_magazine 42 Scripting with RealFlow 02_2007 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 neigh- bour particles or isolation time. Isolated particles wont always appear like foam or spray. Of course we need a particle source. To separate the water particles from the foam particles, its 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 its 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). Reminder Calculating the Magnitude of a Vector: vel = (3.2, 2.8, 1.5) | vel |= sqr(vel x 2 + vel y 2 +vel z 2 ) | 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: 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. Each particle carries a specific ID. While looping through the particles, the ID is used to differentiate, whether its 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) water.removeParticle(id) particle = particle.getNextParticle() Therere a few things to consider, when setting up the RealFlow scene. The emitters must share the same setting for Resolution. The foam emitter isnt meant to produce particles, its 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. Fig. 16: The scene setup for Foam_001 The Foam Project RF_magazine 43 Scripting with RealFlow 02_2007 The Syntax of the script Foam_001.rfs should already look familiar to you. Weve 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, theres no need for calculating the Magnitude again. But we still need the Velocity Vector for the addParticle( ) function. 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() vel = particle.getVelocity() pos = particle.getPosition() id = particle.getId() if (pres > threshold):
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. 17: Velocity based creation of foam with Foam_001.rfs Fig. 18: Pressure based creation of foam with Foam_002.rfs The Foam Project RF_magazine 44 Scripting with RealFlow 02_2007 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() were using these terms: dens = particle.getDensity() mass = particle.getMass() Here are the results from these approaches: The density method also yields to nice simulations with randomly spread foam particles, but from this simple approach its not apparent, whether this parameter will stand our needs for transforming foam back to water. The worst method is surely the mass idea. Theres no change in the particles 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. 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, wed see that both Velocity and Pressure will decline with increasing time. RealFlow simulates these effects physically correct, so theyre suited for our purposes. Its 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 particles age. Indeed theres a way to do so, by using Modules. A Module is an external extension to enhance Pythons capabilities. Therere 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 For random the complete notation is: import random Fig. 19: Density based creation of foam with Foam_002.rfs Fig. 20: Mass based creation of foam with Foam_003.rfs The Foam Project RF_magazine 45 Scripting with RealFlow 02_2007 Events Script: Foam_005.rfs def onSimulationStep(): import random
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() The script is getting longer, but were still using the same principles. The second loop simply turns around the conditions from the first one. Were just using the foam particles to convert them back into water. To get a realistic behaviour, were 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 criterias 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). Fig. 21: Result from Foam_005.rfs showing different states The Foam Project RF_magazine 46 Scripting with RealFlow 02_2007 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, wed have to open the Events Script window, and enter new parameters. Wouldnt 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. What do we need for this function? First, we add two Nulls, called Control_Pressure and Control_Age: > Null Edit > Add > Objects > Null For controlling the values directly through the objects, we have to read out the Nulls height or y positions. The positions will be stored into Variables, used to define the threshold parameters. The script doesnt care, whether you move the objects or enter values within the Node Params window. cp = scene.getObject("Control_Pressure") ca = scene.getObject("Control_Age") 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 This is an effective method of control, easy to implement. Lets 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. Age cant be negative and we have to multiply the resulting value with -1 to make it positive, if needed. 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 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 dont forget to make them invisible to the emitters! Fig. 22: Object driven threshold values The Foam Project RF_magazine 47 Scripting with RealFlow 02_2007 Collision Based Foam Creation Until now, we just had to care about emitters, no other elements were populating the scene. For simulations its much more realistic to add objects for fluid-body interac- tion. 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, were 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() for single_particle in collided_particles: perform checks and calculations... Again, were developing our foam script step by step. First, lets check the result with a very basic scene. Just add the two emitters Water and Foam and a collision object of your choice. 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) As you can see, theres not much difference between our first foam script and the one listed here. Its really just the method of looping through the emitters 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. The Foam Project RF_magazine 48 Scripting with RealFlow 02_2007 The last approach was Pressure dependent, but of course its also possible to write a collision based script, using a particles Velocity. Maybe, a combination of both param- eters 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. Thats 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. What do we need for the next script? 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 were 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. Until now, all emitters had the same parameters. With this script, were 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 bowls bottom. The listing for this script can be found here: The_Foam_Project > Scripts > CollisionFoam_002.rfs Again, were 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 # Transform particles back to water if (current_age >= threshold_age): id_foam = particle_foam.getId() rewater.addParticle(pos_foam_g,vel_foam) foam.removeParticle(id_foam) With the first if-condition, we define the life span (threshold_age) dependet 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 ReWa- ter emitter and behave like fluid again. Together with different densities, the result is realistic foam generation with slowly disappearing particles. Fig. 23: Foam particles at the walls of the vessel The Foam Project RF_magazine 49 Scripting with RealFlow 02_2007 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, theyre also transformed back to water. The blue colour indicates original particles from the emitter without any change of state. 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 Id like to show methods. This also implicates that some statements in the script can be shortened. I dont want to go into depth of fast Python programming, but there are a few possible enhancements. If youre interested in these refinements, just open both CollisionFoam_002.rfs and CollisionFoam_003.rfs and compare the source code. Youll 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, doesnt it? Of course it does, because Ive 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 [ ... ] Together with the first loop for foam generation, we introduce the Modulus operation. Modulus tells us, whether theres 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. Fig. 24: Image sequence produced with CollissionFoam_002.rfs The Foam Project RF_magazine 50 Scripting with RealFlow 02_2007 # Go through water particles collided_particles = water.getParticlesColliding() for single_particle in collided_particles: counter += 1 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) 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 therere 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 apply- ing simple methods. Of course, you have to make up your mind, before you can start writing a script, but thats 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 youll find the settings for a specific scene much faster. The adjustment through Null objects is not necessarily needed - this method mainly has been intro- duced for convenience and for educational purposes. The thresholds normally have to be set individually for each new scene. Its also up to you to introduce new values and parameters for different results. Random values also play a very important role, because they care for a natural look. Adjusting these parameters may also need some experimenting. Credits The creation of foam and spray has been widely discussed throughout forums and scripting resources. Similarities cant be excluded, though the programs in this edition of RF_magazine have been developed independent from other sources. Nevertheless Id 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. The Foam Project RF_magazine 51 Scripting with RealFlow 02_2007 RF_magazine 52 Scripting with RealFlow 02_2007 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, well develop a more complex script for freezing and melting a particular shape, consisting of particles. Well 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 thered been several more or less lavish methods to achieve this effect, but with RealFlow 4 and Python scripting its a pure pleasure. The problems always been to make RealFlow writing out BIN files, while the particles stop moving. This could be done with a few lines of code: Events Script: BulletTime_001.rfs def onSimulationStep(): emitter = scene.getEmitter("Circle01") frame = scene.getCurrentFrame() particle = emitter.getFirstParticle() while (particle): if (frame == 25): particle.freeze() if (frame == 60): particle.unfreeze() particle = particle.getNextParticle() 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. Lets have a quick look at the listing. The main part lies within the while loop. Here were 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 wont move. With frame 60 theyll 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(): emitter = scene.getEmitter("Circle01") frame = scene.getCurrentFrame() particle = emitter.getFirstParticle() while (particle): if (frame == start): particle.freeze() if (frame == stop): particle.unfreeze() particle = particle.getNextParticle() Fun with Freeze RF_magazine 53 Scripting with RealFlow 02_2007 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, like the one from RF_magazine Issue 01/2007 Cinema 4D Special Edition, page 30. 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. 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, its recommended to introduce a little helper object, e.g. a Null or a Cube object. Were just reading out the particles position and the current x position of the helper object and compare these values. From the moment, the particles are outside the resulting value, theyll be frozen. To get the correct particle position, we need the Magnitude again. 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 were not interested in any height information. 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 Vectors Magnitude (see p. 15). It makes no difference, whether were extracting the Magnitude from a Vector in 2D or 3D space. Thats our formula: c 2 = a 2 + b 2 c = (a 2 + b 2 ) After we received the value for c, which is a Scalar, we simply compare it against r and already got the condition were after. In a circle, the radius remains constant at each point of the outline, so we dont need further checks. r x z a b c pos Fig. 25: The position of a particle in 2D space Fun with Freeze RF_magazine 54 Scripting with RealFlow 02_2007 With this knowledge we can write our little script, imple- menting 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, were 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 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): p_pos_g = particle.getPosition() p_pos_x = p_pos_g.getX() p_pos_z = p_pos_g.getZ() p_mag = math.sqrt(p_pos_x ** 2 + p_pos_z ** 2) if (p_mag >= abs(radius)): particle.freeze() particle = particle.getNextParticle() The def onSimulationBegin( ) block is just for our conven- ience, to reset the scene to its initial values. Something youve already seen with the foam scripts is the import statement. Here, were importing the math module, to provide Python with functions, like sine, cosine or square root. 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_x = p_pos_g.getX() p_pos_z = p_pos_g.getZ() p_mag = math.sqrt(p_pos_x ** 2 + p_pos_z ** 2) As you can see from Fig. 26, the particles behave exactly as predicted. Particles outside the circle shaped boundary are darker, indicating lower speed. Of course this script could be extended, to check against elliptical, rectangular or other shapes. Fig. 26: Particles outside the circle are frozen Fun with Freeze RF_magazine 55 Scripting with RealFlow 02_2007 Relaxing Particles with Freeze Youve already learned a lot of things about the freeze statement. The next question surely is, whether its 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 theres 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 environmen- tal conditions dont 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 its 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 weve 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, youre 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. The script itself needs no witchcraft, were 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. Its not possible to apply an Age daemon here, because its simply too static. After a short while all particles would have been gone, even the frozen ones. To avoid this, were introducing our own age function. The released particles (with a certain age) will be identi- fied via their ID and then removed from the emitter. Youve 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. Fig. 27: Object controlled dissolving of a rocket primitive Fun with Freeze RF_magazine 56 Scripting with RealFlow 02_2007 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 = 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() 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 dont want the particles to disappear, you can simply omit the age check! Summary The idea behind this chapter was to show you the possi- bilities, 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 theres always more than one way to solve a problem. The scripts, presented here are nothing more than one possible solution. Maybe youll find a shorter, better or faster method, but thats not the point with this issue. The current edition of RF_magazine wants to show you that you dont have to build sophisticated and long-drawn-out scripts to achieve nice effects. Fun with Freeze RF_magazine 57 Scripting with RealFlow 02_2007 Overview Possibilities with scripting are numerous and therere 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. Image Based Emitter Speed 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 trans- form this value into height information. More sophisti- cated approaches use Displacement or Normal Maps to affect geometry. Something similar can be found in RealFlows 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. Why shouldnt 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 isnt 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 were using is just plain TIFF image: 10 x 300 pixels with a gradient, ranging from pure white to pure black. Dont forget to store the images as RGB, not greys- cale. The common notation for describing the size of an image is width x height. Thats important, because well receive a List of values, representing these directions. In this example, were using the height value, but this is just a matter of definition. The image format for this script shouldnt be compressed, since artefacts may falsify the values. Events Script: ImageBasedSpeed.rfs def onSimulationFrame(): import math emitter = scene.getEmitter("Circle01") start = 25 stop = 125 range = stop - start frame = scene.getCurrentFrame() root_path = scene.getRootPath() 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 <= stop)): step_width = math.ceil(height / range) step = (frame - start) * step_width pixel_pos = template.getPixel(1, step) intensity = pixel_pos[1] speed = intensity / 100.00 emitter.setParameter("Speed", speed) if (frame == stop): emitter.setParameter("Speed", 0.0) template.close() The first part defines the needed Variables, including the path, where the desired image has been stored. Its important to differentiate between upper and lower cases. The bitmap SpeedGrad.tif has been placed inside a direc- tory of the project folder. We can get the project folder easily be using scene.getRootPath( ). Wherever your project is located, this statement will find it. After weve defined the bitmap path, the image has to be opened inside RealFlow. Examples and Ideas RF_magazine 58 Scripting with RealFlow 02_2007 The opening routine can be considered as a fixed construc- tion. The script reads the image and creates a List for its properties. The size statement gets the dimension. The Data Type we get with size is an Integer. Using width and height, defines the exact position of a pixel within a bitmap. These positions are always Integers, because theres no pixel address, like 1.25.
A 8-bit gradient from white to black delivers values between 255 and 0. An emitter speed of, e.g. 193 is rather strong, therefore we have to cut down the intensity. The emitter.setParameter(Speed, value) expects a Float Data Type, so we have to convert the intensity Integer into a Float value. This is done by dividing the pixel strength through 100.00. Using this expression speed = intensity / 100 emitter.setParameter("Speed", speed) would directly lead to an error: Script error: Bad type argument for parameter Speed The frame range has been introduced to adopt the avail- able pixels to a certain simulation length. Of course, itd be possible, to adjust the image size to the frame range. Another new function is math.ceil(step_width) The ceil function is a member of the math module and rounds up the result to the next higher Integer: math.ceil(3.06) = 4 math.ceil(2.88) = 3 The opposite of math.ceil is math.floor. The last statements are just for safety reasons and to free memory: Its always a good idea to close an image, when we dont need it anymore. Boxed Daemons This type of daemon is rather useful, because you can specify a bounding object to affect the particles or bodies. The clue with this script is that it uses a RealFlow primitive to define the bounding box. The most obvious shapes are cubes and spheres. The main task is to use relative coordi- nates: We have to calculate the dimensions of the box, dependent from its position in our 3D world. For simple objects, this is not too difficult. The boundaries of a box are given through the surfaces between the vertices. If we know the position of the vertices, we also know the dimensions. The Node panel offers all information we need: Scale and Position. We first calculate the dimension in x direction. Fortu- nately, a box is symmetric object, and by halving the box, we get the distance of the vertices from the centre: dim_x_min = position_x + scale_x / 2 dim_x_max = position_x - scale_x / 2 Fig. 28: Example parameters of a daemons bounding box Examples and Ideas RF_magazine 59 Scripting with RealFlow 02_2007 The formula from page 58 can also be used for getting the dimensions in y and z direction: cube = scene.getObject("Cube01")
position_g = cube.getParameter("Position") position_x = position_g.getX() position_y = position_g.getY() position_z = position_g.getZ() size_g = cube.getParameter("Scale") size_x = size_g.getX() size_y = size_g.getY() size_z = size_g.getZ() dim_x_max = position_x + size_x / 2 dim_x_min = position_x - size_x / 2 dim_y_max = position_y + size_y / 2 dim_y_min = position_y - size_y / 2 dim_z_max = position_z + size_z / 2 dim_z_min = position_z - size_z / 2 The next step is to compare the dimension values against the absolute position of the particles. For this purpose we take the dim results and build new Vectors. This method is much more reliable than just using the blank values, we receive from the calculation above. On the left you can see the results from our bounded daemon. I used a freeze function to visualize the effect of the script. The complete listing can be found here: Examples_And_Ideas > BoxedDaemon > BoxedDaemon.rfs Credits The script is based on a program by A. Tena from Next Limit and shows you methods, how to extend or modify existing scripts. Its called Freeze Daemon and its avail- able from the Next Limit scripting homepage: http://www.nextlimit.com/nlscript/ranking_scripts.php?id=1 Exporting Position Data to AfterEffects In some cases its necessary to use keyframed position data with other applications. The RealFlow plug-ins are designed for this purpose, but theres no possibility to export these keys to other programs, e.g. AfterEffects. If the desired application supports text files, then we have an easy-to-use data format for exchanging RealFlow data. In this example, were tracking the motion of a rigid body object, moved by a daemons force. The position data will be written as keys and exported into a text file simultane- ously. Its not mandatory to write the keyframes within RealFlow, but its a good opportunity to demonstrate this function. Our first scene setup is pretty straightforward: Just a Torus object, a Gravity Daemon and a Cube, serving as a ground plane. The idea is to record the bouncing motion of the Torus and transfer it to AfterEffects. The first part creates a plain text file and writes out a header for AfterEffects. Such a header is used by applica- tions to recognize and initialize the data inside the file. The next block generates keys for each frame and appends the data to the text file. The last section closes our data file and writes out a footer. Examples and Ideas RF_magazine 60 Scripting with RealFlow 02_2007 Writing Keys to the Curve Editor Before were starting with text file operations, well have a look at RealFlows class of instructions for the Curve Editor. We want the position changes of the Torus object stored as keys. This is called position baking. The advan- tage with baked values is that they can be used many times - even in other scenes. Were translating the auto- matically calculated position data into a graphical repre- sentation. First, we have to specify the object, we want to use - here its the Torus01 item from the Nodes window. Just by calling the related curve, RealFlow adds a new curve to the Curve Editor (so many curves...). The Key.new( ) statement adds a new key to this curve. The last step is to add values to the keys, in particular its Position and Time. Were doing this for the y direction first: def onSimulationFrame(): current_time = scene.getCurrentTime() torus = scene.getObject("Torus01") curve_y = torus.getParameterCurve("Position Y") pos_g = torus.getParameter("Position") pos_y = pos_g.getY() # Add a new key only for the y position newKey_y = Key.new() newKey_y.time = current_time newKey_y.value = pos_y newKey_y.type = KEY_TYPE_TCB curve_y.addKey(newKey_y) We now have to repeat the key generation process for the missing x and z values to get a full representation of the movement. After the simulation has been performed, well get a result similar to the one on the right. Creating a Text File Python provides several methods to handle text files. This feature is one of Pythons fundamental functions. Plain text files are often used with web applications for tempo- rary or persistent storage. In RealFlow, text files can be used to save and read values. To create a file, Python needs to know several basic things: Path and Directory File name Access type: read r , write w or append a The keyword for the creation of a new file is open( ). Lets say we want to store the file PositionData.txt into the projects objects folder: path = scene.getRootPath() file = open(str(path)+"/objects/PositionData.txt", "r") file.close() By executing these lines of code, Python writes an empty file into the scene folder. To fill the file, we also need some content. For this purpose we use the write( ) statement. The text, you want to print to the file has to be written within the brackets, just like an argument: path = scene.getRootPath() file = open(str(path)+"/objects/PositionData.txt", "r") file.write("My first text file") file.close() Examples and Ideas RF_magazine 61 Scripting with RealFlow 02_2007 Now, weve got almost everything to write out the header for our AfterEffects exchange file. Theres one thing left: The so called Field Delimiter. This character separates fields from each other, making them readable for other applica- tions. In our case, the Field Delimiter is a tab character. Python knows several fixed expressions for writing tabs, blanks or carriage returns. For our script we need: \t = tab \n = new line These combinations can be used directly within a text, because Python searches through strings for expressions like \t or \n. So the source code for the header is: def onSimulationBegin(): path = scene.getRootPath() file = open(str(path)+"/objects/PositionData.txt", "w") file.write("Adobe After Effects 6.5 Keyframe Data\n\n") file.write("\tUnits Per Second\t25.00\n") file.write("\tSource Width\t100\n") file.write("\tSource Height\t100\n") file.write("\tSource Pixel Aspect Ratio\t1\n") file.write("\tComp Pixel Aspect Ratio\t1\n\n") file.write("\tFrame\tX pixels\tY pixels\tZ pixels\n") file.write("Position\n") file.close() The header section shouldnt be modified, otherwise AfterEffects may have serious trouble reading your keys. This is the content directly from our freshly created file: Writing Position Data to a Text File Now we want to write the Position and Frame data to the PositionData.txt file. Youve already learned how to treat values and Variables within RealFlow scripts. This concept is used again here, together with the write( ) statement. Just one thing: We want to append the gathered values. With the following expression, the file would be created newly each frame and overwritten: file = open(str(path)+"/AnimationData.txt", "w") Instead of w, were just using a, for append: file = open(str(path)+"/AnimationData.txt", "a") The notation for the data part is written this way: file.write("\t"+str(frame)+"\t"+str(pos_x)+"\t"+ str(pos_y)+"\t"+str(pos_z)+"\n") The very last task is to print out a footer for AfterEffects. This footer helps the application to recognize the end of the data section. Again, weve got to open the file in append mode: def onSimulationEnd(): path = scene.getRootPath() file = open(str(path)+"/AnimationData.txt", "a") file.write("\n\n\nEnd of Keyframe Data") file.close() You can have a look at the entire listing here: Examples_and_Ideas/Scripts/ExportPositionData_001.rfs On page 62 youll see a screenshot from the file, gener- ated with the current script. Therere still several issues, we have to solve to write out an adequate file: The format of the Float numbers is not suitable for AfterEffects. Examples and Ideas RF_magazine 62 Scripting with RealFlow 02_2007 The values of X pixels, Y pixels and Z pixels weve got here cant be used within AfterEffects. We have to find a way to format them. Also the values are way too small. Trans- ferring the result to AfterEffects would result in a hardly visible motion. First, well cut down the pixel values. Python uses a special notation for this purpose. A pretty reasonable result would be to leave two digits after the decimal point. This can be written as: x_form = "%.2f" % pos_x y_form = "%.2f" % pos_y z_form = "%.2f" % pos_z The most striking at this expression surely is the percent character, which has already been introduced with the Modulus operation. Here, the character has a completely different meaning. The script takes the original pos_x value and uses the complete value left from the decimal point (%), and then the first two digits (2f) right from the point. We can simply repeat this for pos_y and pos_z. Please note that its not possible to calculate with these formatted values. Operations have to be transformed before the formatting process. The Float values are treated mathematically correct and a rounding is performed, as you can see on the right. The output is almost perfectly suited for the export. For printing these values, we of course have to use the new Variables x_form, y_form and z_form: file.write("\t"+str(frame)+"\t"+str(x_form)+"\t"+ str(y_form)+"\t"+str(z_form)+"\n") You can find the complete script here: Ideas_and_Examples/Scripts/ExportPositionData_002.rfs By multiplying the original position values with 100, we get useful results, suited for 3D scenes in AfterEffects. But in many cases we only need the y value to get the effect, were after. The last example extracts this value and prints out a fixed x coordinate, while the z value will be left blank. Were using a new scene with this script. The Torus object will be replaced with a Sphere item. The x coordinate is just the half of our composition size. With NTSC 640 x 480 its 320, with HDTV 1280 x 720 its 640. Here, were using PAL and 384. For the y coordinate we can write: factor = 100 offset = 382 ae_pos_y = -pos_y * factor + offset form_ae_pos_y = "%.2f" % ae_pos_y Examples and Ideas RF_magazine 63 Scripting with RealFlow 02_2007 The offset is empirical and helps us to find the right initial position. Its up to you, where you want to set the starting point. Also we have to use -pos_y, because we want the object to fall downwards in AfterEffects. The next chapter tells you, how to use the data within the program. You can find the complete script here: Ideas_and_Examples/Scripts/ExportPositionData_003.rfs After Effects Specifications The header is a bit different for each scene, you create with AfterEffects. Just open a new composition and add the element you want to use with the RealFlow data, e.g. a Color Plane: Composition > 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 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: After the RealFlow file has been created, open it in a text program and follow these instructions: Copy the entire content from the file to the clipboard Mark the desired object in AfterEffects Paste the clipboard to the AE object If therere any errors, AfterEffects prints out a message, but if everything went right, you can immediately see the keyframes and the motion curve in AfterEffects timeline: 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 pre- rendered marble. The pink crosses indicate the keys, we can also see in the timeline. Examples and Ideas RF_magazine 64 Scripting with RealFlow 02_2007 Reserved and Forbidden Words Python uses some words and expressions internally and they have a special meaning there. Thus its not allowed to name Variables as: and assert break class continue def del elif else except exec finally for from global if import in is lambda not or pass print raise return try while These names should be avoided, though they arent strictly forbidden, but may cause conflicts: array close data float int input numeric open range type write zeros Maths related expressions should not be used. You must avoid them when the math library is imported: acos asin atan cos e exp fabs floor log log10 pi sin sqrt tan Also all keywords from RealFlow are forbidden. You can simply identify the RealFlow command, as they turn yellow, after theyve been typed into one of the scripting windows. A complete overview is available from the online helps scripting reference. Appendix RF_magazine 65 Issue 1_2008: Rigid Body Dynamics The scheduled release date of Issue 1_2008 is January, 28th 2008 For updates and contents, please visit http://www.rf-magazine.com Please note: The topic may be subject to change. Further information and contents will be available from http://www.rf-magazine.com Legal Notes All materials, texts, files and images are copyrighted to Liquidlight.tv, Thomas Schlick. Its forbidden to copy, share or provide the materials as a whole or as excerpts. The materials must not be shared in forums, file sharing or torrent servers or similar facilities. All images, texts and materials are property of Liquidlight.tv, Thomas Schlick and must not be copied or modified. 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 Liquidlight.tv, Thomas Schlick. All violations of these terms will be prosecuted. Notes 02_2007 RF_magazine Copyright Liquidlight.tv 2007 For updates, news and more please visit http://www.rf-magazine.com