Scene Definition File Basics
Mantaflow scene definition files are written in Python, and responsible for creating scene geometry and implementing the main simulation loop by calling simulation operators and plugins. The core part of a scene file looks much like the pseudo-code usually given in research papers on fluid simulation. You can however also use the powerful scripting abilities of python to integrate render dispatch, load parameters from files and so on, directly in the scene file.
Hello Plume
Let's first have a look at our "Hello World" example simpleplume.py, which simulates a simple rising plume using a Stam-type solver. It can be found in the scenes subdirectory. If you haven't run it yet, play around with it in the GUI for a bit, to get an idea what the script will achieve. Below is a preview what it should look like. Note that the GUI typically only displays a 2D slice of the data (you can move it with the +/- keys), so what you see below is the middle section of the smoke. By default, the first vector grid of the scene is displayed with green lines. For the example below, that's the flow velocity. In the UI, click on the "?" button to see all keys for changing the display.
We will dissect this file in the following. The scene code of the python file starts with:
res = 64
gs = vec3(res,1.5*res,res)
s = Solver(name='main', gridSize = gs)
s.timestep = 1.0
First, a solver object is created. Solver objects are the parent object for grids, particle systems and most other simulation objects. It requires gridSize as a parameter, for which we use the custom vec3 datatype. Most functions expecting vec3 will also accept a python tuple or sequence of 3 numbers. When creating an mantaflow object, you can always specify a name parameter, which is used in debug output and in the GUI. Specifying it is optional, if it is omitted, mantaflow will default to the variable name. Finally, the simulation timestep is set.
flags = s.create(FlagGrid)
vel = s.create(MACGrid)
density = s.create(RealGrid)
pressure = s.create(RealGrid)
Next, the solver object is used to create grids. In the same way, any other object can be created with the solver s as a parent. The flag grid stores cell type (Fluid/Obstacle/Air).
flags.initDomain()
flags.fillGrid()
source = s.create(Cylinder, center=gs*vec3(0.5,0.1,0.5), radius=res*0.14, z=gs*vec3(0, 0.02, 0))
These statement initialize the scene geometry. initDomain creates an empty box with solid boundaries. As most plugins expect the outmost cells of a simulation to be obstacles, this should always be used. fillGrid then marks all inner cells as fluid. Finally, a cylinder shape is created, which will be used later as an inflow for smoke.
for t in range(200):
source.applyToGrid(grid=density, value=1.0)
advectSemiLagrange(flags=flags, vel=vel, grid=density, order=2)
advectSemiLagrange(flags=flags, vel=vel, grid=vel, order=2)
This is now the first part of the simulation loop. The cylinder shape created above is projected to grid, and all cells within are assigned a smoke density of 1. Then, the density and velocity grid are advected using second-order semi-Lagrangian advection, i.e. MacCormack advection.
setWallBcs(flags=flags, vel=vel)
addBuoyancy(density=density, vel=vel, gravity=vec3(0,-6e-4,0), flags=flags)
solvePressure(flags=flags, vel=vel, pressure=pressure)
setWallBcs(flags=flags, vel=vel)
s.step()
Finally, the boundary conditions at the obstacle are re-set, buoyant forces are added to the velocity grid and the pressure projection is applied. The step function now tells the solver that one iteration is complete, which is important for the GUI and timing functions.
Once you understand how this works, try to modify the scene file. Add e.g. an obstacle sphere to the flow, or change solver precision. Most of the plugin functions used here have a lot of further options, which can be looked up in the plugin section of the developer documentation, or the respective source files.
Plugins and Data Types
While reading the last section, you may have wondered where all the magic functions and data types came from. These are Python extension defined in the C++ framework, and are imported using the import manta statement. It is super-easy to roll you own plugins. A quick description on how to expose functionality to scene files in the frames can be found here. A complete list of all plugins and classes is available in the developer documentation. As an easy start, go check how the addBuoyancy function from above is implemented in extforces.cpp.