Docs

Channels and attributes

P, N, uv, Cd and the rest, per domain, plus the @attributes you name yourself and the mesh-wide constants.

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