A wrangle is a few lines of code that run on the GPU for every particle (Particle Wrangle) or every voxel of a gas
grid (Field Wrangle). The code is short Python with @ attributes, in the manner of Houdini’s VEX:
# a swirl that fades with height, as an acceleration
chf('freq', 4.0) makes a slider named freq on the node, starting at 4. Everything a wrangle reads or writes about a
particle is an @attribute; everything else is an ordinary variable.
Every substep, before the pressure solve: may write @v and @force
Particle Wrangle
Substep
Every physics substep: forces, damping, velocity limits
Field Wrangle
Substep
Every substep, after the pressure solve: scalars only
Particle Wrangle
Post-Step
Once per frame after the substeps: colour, damage, filtering
Field Wrangle
Post-Step
Once per frame after the substeps: scalars only
Particle Wrangle
Emission
On newborn particles at spawn time: initial state (Atom and Hydro; Impact spawns nothing)
All engines work in the solver cube: positions run from 0 to 1 on every axis, Z is up, and velocities are in cube
units per second. A wrangle that needs the world position of something reads it from a slider (chv('center', 0.5, 0.5, 0.0)), not from the scene.
Variables take any name, including i, dt, temp or clamp; they cannot start with two underscores. A variable
has to be assigned before it is read.
Control flow: if / elif / else, conditional expressions (a if c else b), and / or / not, and for
loops over range(...) with written-out whole numbers and at most 256 iterations. break and continue
work inside a for loop only. There is no while, no function definition, no return, no dict and no string except
the name of a slider.
Vectors: set(x, y, z) (or a list or tuple, [x, y, z]) builds one; v.x v.y v.z v.w (or .r .g .b .a) and swizzles such as v.xy read and write
components. Arithmetic between vectors, or between a vector and a number, applies to every component.
Sliders: chf('name') (a number), chv('name') (a vector) and chc('name') (a colour) put a control on the node.
A default can follow the name: chf('radius', 0.3), chv('center', 0.5, 0.5, 0.0) (one number fills x, y and z),
chc('tint', 1.0, 0.5, 0.2) (alpha 1) or with a fourth number for alpha. Defaults must be written-out numbers. A
slider starts at its default; what you set by hand is kept when the code changes. Two uses of one name must agree in
kind, and the first default wins.
Operators and functions: + - * / **, comparisons, and the functions and ti. names listed below. Anything not
listed is refused with a message that names it and, when there is a close match, suggests it.
Mistakes are found when the code is checked (a moment after you stop typing, or with Recompile): the node shows the
message and the line, with a button that opens the code at that line. The GPU kernel itself is built by the worker when
the simulation starts or the code changes; its build time, or the reason it was left out, is shown on the node.
An attribute the engine does not have is an error (“@damage is not available on HYDRO”), not a silent no-op. The names in
the also column are accepted as well.
Temperature above the ambient air, in units of 1000 K (1 is a burning flame)
all stages
@t, @temperature
@fuel
Float
Unburned fuel
all stages
@flame
Float
Flame (the reaction front)
all stages
@burn
Float
Reaction rate (read only)
read only
@P
Vector3
Voxel centre in the solver cube (read only)
read only
@p, @pos, @position
@v
Vector3
Velocity: write it in the Forces stage only (the pressure solve keeps it divergence-free)
Forces stage only
@vel, @velocity
@force
Vector3
Acceleration to add, in cube edges per second squared: Forces stage only
Forces stage only
@accel
A Field Wrangle may write @v and @force in the Forces stage only. They act before the pressure solve, which keeps the
flow divergence-free; the scalars can be written in every stage.
Run order. The wrangles of a stage run in order: first those wired to the solver (through the emitter and detail
inputs, or downstream of Simulation State), then the others left to right by node position; a node’s Run Order number overrides that (lowest first, equal
numbers keep the order above). A wrangle that is not connected to anything still runs, on the whole simulation: mute it
to switch it off.
Dead particles are skipped by every Substep and Post-Step wrangle. Write @active = 0 to kill a particle: wrangles
after yours in the same stage skip it. The Emission stage runs on newborn particles, which are not alive yet.
Fusing. The Particle Wrangles of one stage run as one GPU kernel: the particle is read once, each snippet runs on
the values the one before left, and what changed is written once. The result is the same as running them one by one
(the colour is rounded the way the solver stores it between snippets); it is faster, most for small and medium
scenes. Each snippet keeps its own variables and sliders, so two nodes can both use chf('strength'). Set
BLUX_WRANGLE_FUSE=0 in the worker’s environment to turn it off.
Live editing. While a simulation plays or steps, a changed slider reaches the worker with the next step, and changed
code is swapped in without resetting the simulation (the GPU kernel is rebuilt once, a fraction of a second to a few
seconds with heavy noise). A wrangle the worker cannot build is left out and reported on the node; the others and the
simulation carry on.
Randomness and noise use an integer hash, so they do not depend on the GPU or the backend: the same point gives
the same value (to about one millionth). rand(@ptnum) is a stable random number per particle; add an offset
(rand(@ptnum + 1000)) for a second independent one.
Safety. The code is checked against an allowlist before it reaches the worker, and the worker checks it again: only
the constructs, functions, attributes and ti. names listed here exist, so a wrangle can do arithmetic on the
attributes above and nothing else.
A preset is applied from the node’s Presets… menu and starts with working slider values. Presets that need an
attribute some engine lacks are greyed out on it.