Docs

Language and control flow

Statements, operators, types, loops and your own functions.

Part of the Wrangle reference. Everything on this page goes in the node's Code field, and runs once per element of the current Run Over mode.

Language

Syntax What it does Example
statements stmt; stmt; ... Statements run in order, separated by ;. Comments: // or # to end of line. P.z += 1; wmap("Weights") = P.z
assignment target = expr Assign a channel, a named attribute, or a local variable (created on first assignment). Compound forms: += -= *= /=. Locals that are vectors also support .x/.y/.z (or .r/.g/.b) writes. d = P.x * 2; v.z = 0
declaration float/vector/quaternion/matrix3/matrix name = expr Optional typed declaration of a local. Without a keyword the type is inferred from the first assignment. matrix3 R = lookat(P, target, vec3(0,0,1))
bare expression expr A statement that is just an expression, with no assignment, is always a compile error - it computes a value and throws it away, which is nearly always a typo. Assign it, e.g. to a Weight Map output (wmap("Name") = ...), a channel, or a local. wmap("Weights") = P.z * 0.5
vectors vec3(x, y, z) A vector is three scalars. vec3() costs zero instructions. Component access .x/.y/.z (or .r/.g/.b) works on any vector expression. wmap("Weights") = (P - nearpoint("Reference", P)).z
quaternion / matrix quat(x,y,z,w) / matrix3(c0,c1,c2) / matrix Quaternion is xyzw (4 slots). matrix3 is three column vectors (9 slots, column-major). matrix is matrix4 (16 slots, column-major with last row 0001). quat/matrix3 constructors emit no ops. quaternion q = quaternion(vec3(0,0,1), 90)
operators + - * / % ^ Arithmetic. Vectors support v+v, v-v, vv (per-component), v/v (per-component), vs, sv, v/s, -v. Also m3m3, m4m4, m3v (=vtransform), m4v (=ptransform), qq (=qmul). % and ^ are scalar only. Two vectors with * is not a dot or cross - use dot()/cross() for those. wmap("Weights") = 2 ^ 3 ^ 2
comparison < <= > >= Scalar comparisons; result is 1 or 0. wmap("Weights") = P.z > 10
equality == != Scalar equality; result is 1 or 0. Binds less tightly than < <= > >=, the way C and HLSL do, so a == b < c reads as a == (b < c). Comparing two floats that came out of arithmetic is rarely what you want; compare a difference against a tolerance instead. wmap("Weights") = frac(ptnum / 2) == 0
logic && || ! ?: Short-circuit logic and the conditional. Both ?: arms must be the same type (scalar or vector). wmap("Weights") = P.z > 0 ? 1 : 0
constants pi, e Folded at compile time; not assignable. wmap("Weights") = sin(pi / 2)

Case

Names are case sensitive: channels, variables, functions and keywords all are, so n is an ordinary variable while N is the normal channel, and p is free while P is the position. The two exceptions are component letters (P.X is P.x) and attribute names (@Height and @height are one attribute, because attribute names are case-insensitive).

float n = 0; N = normalize(N)

Strings - "text"

A string literal. Argument only, consumed entirely at compile time: it names a parameter for ch()/chf()/chv()/chi()/chb(), or names a pin, bone or attribute for the donor-mesh, texture, landmark, weight-map, ramp, transform and delta functions further down this reference - never just a ch() parameter, and never a value you can store in a variable, combine with an operator, or assign. No escape sequences, and the closing quote must be on the same line.

wmap("Weights") = ch("radius")

Control flow

Syntax What it does Example
if (cond) { ... } Run the block when cond is non-zero. Braces are required, so there is no dangling-else to guess about. The condition must be a scalar. if (P.z > 0) { wmap("Weights") = 1 }
if (...) { ... } else { ... } The other arm. else if (...) { ... } chains as deeply as you like. if (mask > 0.5) { P.z += 1 } else { P.z -= 1 }
while (cond) { ... } Repeat the block while cond is non-zero. while (d > 1) { d *= 0.5; n += 1 }
for (init; cond; step) { ... } C-style. Any of the three parts may be empty; for (;;) loops until break. There is no ++ - write i += 1. for (i = 0; i < 8; i += 1) { s += rand(i) }
break; Leave the innermost loop. for (i = 0; i < 99; i += 1) { if (i > 4) { break } }
continue; Skip to the next iteration (to the step of a for). foreach (nb in neighbours(ptnum)) { if (nb == ptnum) { continue } }
block scope { ... } A block's locals vanish at its closing brace, so two sibling loops may both declare i. A bare expression is not allowed inside a block, for the same reason it is not allowed beside other statements. for (i = 0; i < 3; i += 1) { float t = i * 0.5; s += t }

foreach (name in neighbours(p)) { ... }

Iterate the one-ring of point p. name is a fresh local holding each neighbour point ID, scoped to the block. Vertex or Corner domain. neighbors is accepted too. Also: foreach (t in neighbourprims(primnum)) walks edge-adjacent triangles (Triangle or Corner domain); foreach (i in points()) / foreach (i in points("Pin")) iterates live vertex ids; foreach (i in nearpoints("Pin", p, r)) iterates donor vertex ids within r of p (Vertex, Corner or Detail - see the Donor mesh pins page for the full nearpoints entry). Self points() is Vertex, Corner or Detail; points("Pin") is any run-over.

foreach (nb in neighbours(ptnum)) { sum += pointpos(nb) }

Step budget

Every element may dispatch at least 268 million instructions - more automatically, with no settings visit needed, once a bound mesh, texture or landmark set is large (Settings > Scripting overrides the number by hand the moment you change it there, up to 2 billion). Exceeding it aborts the whole run with an error naming the element - the mesh passes through unchanged rather than half-written. A loop-free expression can never reach it, whatever the limits are set to.

while (1) { } // error, does not hang

Unlimited mode

Settings > Scripting's Unlimited step budget turns the step budget off entirely and replaces it with a seconds-based run time limit (Unlimited run time limit (seconds)) for a loop whose iteration count genuinely depends on runtime data. Exceeding the time limit aborts the run the same way the step budget does; a long unlimited run can also be stopped early from its own progress bar's Cancel button. A time limit of 0 means no time limit at all - with the step budget off too, a loop that never ends will run for ever and the app has to be closed from Task Manager.

while (1) { } // runs until the time limit or Cancel, in unlimited mode

User functions

function name(type p0, type p1, ...) { ... return expr; }

Define a helper at the top of the expression (not inside if/for/foreach/while or another function). The body is inlined at every call site at compile time - there is no call stack, and a function cannot call itself. The body cannot see the caller's locals, only its own parameters plus channels and node parameters, so names the caller used may be reused inside the function. Parameter types are float, vector, quaternion, matrix3 or matrix. All returns in one function must share a type.

function falloff(float d, float r) { return saturate(1 - d / r); } P.z += falloff(length(P), 25)

return expr;

Leave an inlined function body with a value. Only legal inside a function.

return saturate(1 - d / r)

Call - name(args)

Call a previously defined function. Argument count and types must match the definition. Arguments are evaluated in the caller; the body cannot see the caller's locals.

wmap("Weights") = falloff(length(P), 25)