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.
Channels: vertex domain
| Syntax | What it does | Example |
|---|---|---|
P vec3, read/write |
Vertex position, in cm. Also @P. | P.z += 5 |
N vec3, read/write |
Vertex normal (mean of split-normal elements; writes patch each element and are not re-normalised). Also @N. | N = normalize(N) |
Cd vec3 rgb, read/write |
Vertex colour. Bare Cd is rgb as a vector; alpha is Cd.a (scalar). Writes broadcast across colour elements at the vertex. Also @Cd. | Cd = vec3(uv.x, uv.y, 0) |
Cd.a scalar, read/write |
Vertex colour alpha. | Cd.a = 1 |
uv scalar components, read/write |
Primary UV as uv.x/uv.y (aliases uv.u/uv.v). Two components, so uv is not a vec3 - read the components. | uv.x = 1 - uv.x |
wmap (write) wmap("Name") = expr |
Writes the named WeightMap output parameter (also += -= *= /=) - Name resolves the primary WeightMap output when only one is declared, see primary on the Parameters page. See Weight maps for the read side and the shared-name rule. |
wmap("Weights") = fit(P.z, 0, 100, 0, 1) |
mask scalar, read |
Per-vertex value of the primary WeightMap input parameter (the mask - typically named Mask; see primary on the Parameters page). Compile error while none is declared and wired. Also @mask. |
wmap("Weights") = mask * P.z |
ptnum / elemnum scalar, read |
This vertex's id. Also @ptnum. | wmap("Weights") = rand(ptnum) |
numpt / numelem scalar, read |
Vertex count. Also @numpt. | wmap("Weights") = ptnum / numpt |
| conditional writes | A channel assignment inside an if or a loop only touches the elements whose run actually reached it. Everything else keeps what it had, so writing N or uv on part of a mesh leaves the hard edges and UV seams elsewhere exactly as they were. |
if (P.z > 0) { N.x = 0 } |
Channels: triangle domain
| Syntax | What it does | Example |
|---|---|---|
P vec3, read |
Triangle centroid. Writes are rejected: a centroid write has no single meaning. | @sz = length(P) |
N vec3, read |
Face normal. | @up = dot(N, vec3(0,0,1)) |
Cd, uv read |
Mean of the three corners. Read only here - corner elements are shared with neighbouring triangles. | @u = uv.x |
area scalar, read |
Triangle area in cm^2. | @big = area > 10 |
primnum / numprim scalar, read |
Triangle id / triangle count. | @odd = primnum % 2 |
@name per type |
Named attributes are the only writable thing per triangle. | @flat = 1 - dot(N, vec3(0,0,1)) |
Channels: corner domain
| Syntax | What it does | Example |
|---|---|---|
P vec3, read |
Owning vertex's position (the vertex this corner belongs to). Write is a compile error - several corners share one vertex, so a P write has no single meaning here; use Run Per = Vertex. | @dist = length(P - centroid()) |
Cd vec3 rgb, read/write |
This corner's exact colour element. Same split-on-disagreement rule as N. | Cd = vec3(uv.x, uv.y, 0) |
Cd.a scalar, read/write |
This corner's exact colour alpha. | Cd.a = 1 |
uv scalar components, read/write |
This corner's exact uv element (uv.x/uv.y, aliases uv.u/uv.v). Same split rule as N. | uv.x = 1 - uv.x |
wmap (write) wmap("Name") = expr |
Writes the named WeightMap output parameter (also += -= *= /=), promoted to the per-vertex mean over corners that actually wrote it - a vertex none of whose corners wrote it keeps 0. | wmap("Weights") = 1 |
mask scalar, read |
The owning vertex's weight from the primary WeightMap input parameter (the mask), not a per-corner value - all corners of a vertex share its fate. Compile error while none is declared and wired. | wmap("Weights") = mask * P.z |
elemnum scalar, read |
This corner's id: cornerid = 3*tid + c (c in 0..2), ascending per live triangle. | wmap("Weights") = elemnum % 3 |
numelem scalar, read |
3 * TriangleCount, pre-Restrict. | wmap("Weights") = elemnum / numelem |
cornerindex scalar, read |
This corner's own slot in its triangle, 0..2 - the c in cornerid = 3*tid+c. Corner domain only. |
wmap("Weights") = cornerindex |
ptnum / numpt scalar, read |
Owning vertex id / mesh vertex count - the same registers Vertex mode's ptnum/numpt use, seeded from the corner's owning vertex here. | float w = wmap("Falloff", ptnum) |
primnum / numprim scalar, read |
Owning triangle id / mesh triangle count - the same registers Triangle mode's primnum/numprim use. | wmap("Weights") = materialid("Pin", primnum) |
area scalar, read |
The owning triangle's area in cm^2, exactly like Triangle mode's own area. | @big = area > 10 |
N - vec3, read/write
This corner's exact normal element - never a mean, unlike Vertex mode. A write that disagrees with another corner sharing the same element splits it; writes that agree with every other corner (or with the unwritten ones' existing value) never split anything. See the Run modes: Texel and Corner page for the full rule and why it is never undone by a merge.
N = normalize(N)
UV layers
Non-primary UV channels - uvlayer(0) is uv, see the Channels sections above.
uvlayer(n) / uvlayer(n).x / uvlayer(n).y (aliases .u/.v)
UV channel n as vec3(u,v,0) (read) or per-component (.x/.y, read and write). n must be a constant whole number - it selects a register at compile time, the same constraint relax()'s iteration count has. uvlayer(0) is uv (same registers, no separate storage). Reads are legal in Vertex (per-vertex mean, same convention as uv) and Triangle (3-corner mean); writes are vertex only (Triangle refuses for the same shared-corner reason uv itself does there). Reading/writing a channel that does not exist is a compile error naming how many the mesh has; writing one with Create Missing Attributes on creates it as an all-zero atlas (a loud run note - Pack/Bake will happily succeed on it; lay it out with Generate UVs). Corner mode: deferred - only uvlayer(0) (the primary uv channel) is available per-corner for now; a non-primary layer there is a positioned compile error.
uvlayer(1).x = 1 - uvlayer(1).x
numuvlayers() / numuvlayers("Pin")
UV channel count, self or a named donor (0 without any UV layout - unlike numpolygrouplayers() there is no "layer 0 always exists" concept here). Scalar, every run-over.
wmap("Weights") = numuvlayers() > 1
Attributes
@name - read/write
A tagged named attribute. Scalar for Float; a Vector 3 attribute reads/writes as a vector; other sizes go per component (@a.x .. @a.w / .r .g .b .a). Cross-domain access is a compile error naming the fix (Promote Attribute). In Corner mode a name is readable from either domain (a vertex attribute at ptnum, a triangle attribute at primnum) - if the same name exists in both domains that is a positioned compile error naming the collision, not a silent pick; writes are refused outright in Corner (promotion policy deferred - write from Vertex or Triangle mode instead).
@height = P.z
Creation - write, opt-in
With Create Missing Attributes on, assigning an absent name creates it: Float from a scalar, Vector 3 from a vector. Off, the same line is an error listing what exists. Never available in Corner mode - see the row above.
@heat = saturate(P.z / 100)
Name clash
@name means the named attribute whenever the mesh carries one, and only falls back to the built-in channel when it does not. So on a mesh that really has an attribute called P, @P reads that attribute while the bare P still reads the position. Leave the @ off when you mean the channel.
wmap("Weights") = P.z
getattrib(name) / setattrib(name, value)
Read/write the same named attribute @name does, through identical rules (creation, domain, Corner read-only/write-refused), but with the name resolved at compile time from a string literal or chs("Param") instead of the @ sigil - handy when the name itself comes from a String parameter. No .component form (use @name.x = ... for that) and no @P/@Cd/@ptnum channel fallback - a getattrib()/setattrib() name always means an attribute. setattrib is usable as a bare statement.
setattrib(chs("TargetAttr"), getattrib(chs("TargetAttr")) + 1)
Detail constants
Mesh Wrangle node only; compile error in a Custom Force expression.
| Syntax | What it does | Example |
|---|---|---|
bboxmin() |
The mesh's axis-aligned bounding box minimum corner, in cm. One value for the whole evaluation, not per element. | wmap("Weights") = P.z - bboxmin().z |
bboxmax() |
The bounding box maximum corner. | wmap("Weights") = (bboxmax().z - P.z) / bboxsize().z |
bboxsize() |
bboxmax() - bboxmin(), the box's extent along each axis. | wmap("Weights") = (P.x - bboxmin().x) / bboxsize().x |
centroid() |
The average of the mesh's vertex positions. Not the bounding-box centre and not a volume-weighted centre of mass - always defined, even on an open mesh. | wmap("Weights") = length(P - centroid()) |
surfacearea() |
Total triangle area in cm^2, the same quantity area sums per triangle. |
@rel = area / surfacearea() |
volume() |
Enclosed volume in cm^3 via the divergence theorem. Only meaningful on a closed (watertight) mesh; an open mesh still returns a defined number rather than a guard. | wmap("Weights") = volume() > 1000000 |