Docs

Run mode: Detail

The mode that runs once for the whole mesh, and the only one that may add or remove geometry.

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.

Run once: Detail

Run Mode = Detail.

What it is

Set Run Mode to Detail and the code runs exactly once for the whole mesh, single threaded, instead of once per vertex/triangle/texel - elemnum is 0 and numelem is 1. This is what makes side-effecting writes (landmarks, setoutput, setpointpos) safe: there is only ever one run, so there is nothing for two elements to race. The subject Mesh parameter is optional, exactly like Texel: undeclared or unwired, the code still runs; wired, it passes through (or is rewritten when setpointpos is used) and doubles as the "Mesh" donor slot.

clearlandmarks(); addlandmark(pointpos("Mesh", 0), pointpos("Mesh", 0) + pointnormal("Mesh", 0))

Not available

Everything that describes a current mesh element is a positioned compile error here, because Detail has none: P, N, uv, Cd, wmap(...) writes, mask, area, ptnum/numpt/primnum/numprim, @attributes (read one with pointattr("Mesh", "name", i) or surfattr("Mesh", "name", p) instead), foreach over neighbours, and neighbourcount()/neighbour()/pointnormal()/isboundary() (the no-argument form). pointpos(i) and isboundary(p) are legal here so foreach-over-points can read the working copy.

P, N, Cd, wmap(...) writes, mask, @name - all errors

Parameters (bare reads and ch()/chf()/chv()/chi()/chb()), detail constants (need the subject Mesh parameter wired), every donor-mesh function and every texture function - their pins are independent of the run-over, same as in Texel. Every landmark read function above. setoutput() and setpointpos() below. foreach (i in points()). addpoint()/addprim()/removeprim()/removepoint() and foreach (t in prims()) - see the Topology creation/deletion section below.

float t = pointattr("Mesh", "thickness", 0)

setpointpos(i, v)

Write vertex i's position on the Detail working copy. Later pointpos(i) / pointpos("Mesh", i) in the same run see the write - that is what makes foreach (i in points()) { setpointpos(i, ...) } work. Statement-OK. Detail only (Vertex/Triangle are parallel and refuse it). After a successful run, dirty positions are applied to the Mesh output.

foreach (i in points()) { setpointpos(i, pointpos(i) + vec3(0,0,1)) }

clearlandmarks()

Empties the working landmark set's pairs. Splines are untouched. Usable as a bare statement (its return value, if any, is simply discarded).

clearlandmarks()

addlandmark(src, dst)

Appends a pair to the working set and returns its new index. A cap of 16,777,216 pairs makes this a no-op (a counted LimitGuarded guard, not an error) past that point.

float i = addlandmark(pointpos("Mesh", 0), pointpos("Mesh", 0) + pointnormal("Mesh", 0) * 2)

setlandmarksrc(i, v)

Overwrites pair i's source position. Out-of-range i is a counted no-op.

setlandmarksrc(0, pointpos("Mesh", 0))

setlandmarkdst(i, v)

Overwrites pair i's target position. Same out-of-range convention.

setlandmarkdst(0, pointpos("Mesh", 0) + pointnormal("Mesh", 0))

removelandmark(i)

Removes pair i, preserving the order of the rest. Same out-of-range convention.

removelandmark(numlandmarks("Guide") - 1)

Working set

The working set starts as a copy of the first wired Landmarks parameter (Parameters-list order) - its splines carry through unchanged - or empty if none is wired. After a successful run it publishes on this node's primary Landmarks output if one exists (see primary on the Parameters page) - Create pins from code (or the pin editor) adds one when the expression writes landmarks; switching Run Mode does not. The default landmark viewport rendering picks it up automatically. The output rebuilds its map from the flat pair list; since a map keys on the source position, two pairs that end up with the same source position collapse to one - the last write for that position wins, so order your addlandmark/setlandmarksrc calls with that in mind.

setoutput("Name", value)

Writes a Parameters-list value output ("User Outputs", Float/Vector/Int/Bool) by name - Detail mode's counterpart to a named WeightMap output write (wmap("Name") = ...) in the vertex/corner domain. The value must match the declared type: scalar for Float/Int/Bool (Int truncates toward zero, Bool becomes 0/1 by !=0, the same rules chi()/chb() use on the read side), vector for Vector. Writing a name that is not declared is a compile error listing the declared outputs. Multiple writes: the last one wins. Usable as a bare statement.

setoutput("Count", numlandmarks("Guide")); setoutput("Centroid", centroid())

Unwritten outputs

After a successful run, every declared output publishes its pin's own default value if the expression never wrote it - and the node reports a warning naming which ones. Nothing is ever left stale from a previous run.

Topology creation/deletion

Detail Run Mode only.

What it is

addpoint()/addprim()/removeprim()/removepoint() edit the Detail working copy's topology - not just a position or an attribute - immediately, inside the same run. Vertex/Triangle/Corner refuse all four with a positioned compile error: those run-overs are parallel, so a new element's id would depend on what every other element did and could not be the same from one run to the next.

n = addpoint(P + N * 2)

addpoint(v)

Appends a new vertex at v and returns its new id. A cap of 100,000,000 new points per run (counted from the pre-run mesh, not the mesh's own size) makes this a no-op (a counted LimitGuarded guard, not an error) past that point - the addlandmark() precedent. Statement-OK.

float n = addpoint(pointpos(0) + vec3(0, 0, 1))

addprim(a, b, c)

Appends a new triangle over three existing vertex ids and returns its new id, or -1. Any corner that does not name a live vertex, or two corners naming the same vertex, is a counted OutOfRangeIndex and adds nothing. A triangle that would be non-manifold (an edge already shared by two faces) or that already exists is a counted TopologyRefused and adds nothing. A cap of 100,000,000 new triangles per run is a counted LimitGuarded, the addpoint() twin. No group argument - a new triangle always lands in TriangleGroups' default group; assign a real one downstream if you use polygroups. Statement-OK.

float t = addprim(a, b, c)

Winding

The new triangle's face normal follows normalize(cross(Pc - Pa, Pb - Pa)) - the same left-handed convention every other triangle in this engine uses. Wind it like the triangle you copied the corners from; if the result renders inside-out, swap b and c.

float t = addprim(a, c, b) // flipped on purpose

Overlay fill

A freshly appended triangle's UV/normal/colour elements are unset by the engine (every downstream bake silently skips an unset triangle), so addprim() fills them itself: each corner reuses that vertex's first existing overlay element if it has one, or gets a fresh neutral one otherwise (UV zero; normal = the new triangle's own face normal; tangent/bitangent layers, if present, zero - they need a real downstream tangent recompute; colour white).

removeprim(t) / removeprim(t, keep_points)

Removes triangle t; returns 1, or 0 if t already was not a triangle (a legitimate "nothing to remove" answer, the removedelta() precedent - not counted). The one-argument form also removes any corner left referencing no other triangle (which is also what a "blast a region" script's usual intent); keep_points != 0 leaves those corners in place as free points, which round-trip through the Mesh output pin same as any other isolated vertex. Statement-OK.

removeprim(t); removeprim(t2, 1)

removepoint(i)

Removes vertex i and every triangle still touching it; returns 1, or 0 if i already was not a vertex (same "nothing to remove", not counted). No keep_prims form exists: a mesh cannot hold a triangle that references a dangling vertex, so removing the vertex necessarily removes its triangles too. Statement-OK.

removepoint(i)

clearmesh()

Empties every point and triangle from the working copy, one at a time - not the same as an empty mesh from Mesh.Clear(): every overlay's layout (UV/normal layer count, colour layer presence) survives with zero elements in it, so a from-scratch addpoint()/addprim() pass right after this call still has overlays to fill. Cleared points/triangles are counted into the same totals removepoint()/removeprim() report. Has no per-element form (unlike every other function in this section, it refuses with its own message rather than suggesting a foreach loop). Statement-OK.

clearmesh(); n = addpoint(vec3(0,0,0))

foreach (t in prims()) { ... }

Iterate every live triangle id, Detail only - the required companion to removeprim()/addprim(): without it, a removal script has nothing but a raw for (t = 0; t < maxprims(); ...) loop over a ceiling that keeps moving if the body itself adds triangles. The element set is snapshotted once at loop entry, exactly like foreach (i in points()) - a body that adds or removes triangles does not retroactively grow or shrink the current loop. No mesh-pin argument: a donor mesh is never mutated, so there is nothing to iterate there.

foreach (t in prims()) { if (primarea(t) < 0.01) { removeprim(t) } }

Live counts

npoints()/nprims()/maxpoints()/maxprims()'s self forms (a bare call, or an explicit "Mesh" pin name) read the current count/ceiling after any addpoint()/addprim()/removeprim()/removepoint() earlier in the same run - what makes removeprim(t); setoutput("Count", nprims()) tell the truth.

setoutput("PointCount", npoints())

Ids do not survive publish

A vertex/triangle id returned by addpoint()/addprim() (or read via npoints()/pointpos(i)/etc.) is only meaningful for this run. A mesh leaving any pin is always compacted if it was not already, which renumbers every id - so an id you saved into a User Output and expect to feed into a later node's own point-index math will not necessarily name the same element anymore. An isolated point (removeprim's keep_points, or any point with no triangle) is not discarded by that compaction - it round-trips through the Mesh output pin's own orphan-vertex block, just potentially under a different id.

Interactions

Deltas: if this run wrote a Delta entry (setdelta) and also changed the topology and the mesh was not already compact, the node posts a warning - the delta's key no longer names the same vertex once the mesh is renumbered on publish. Skin: points added by addpoint() carry no skin weights - a note recommends Transfer Weights or Bind Skin downstream when the mesh has a skin-weights attribute.