Context: per-element values

One node can feed thousands of copies, and each copy can carry its own number.

A single node does not hold one value. When it feeds many elements, many points, many duplicates, many loop passes, a value can resolve differently for each one.

Per-row attributes

Most things you make are collections: a bag of elements (points, shapes, copies) that travel together. Every element in that bag carries its own labelled values, called attributes, written with an @ in front of the name.

Two you will meet constantly:

@id is not one number. It is a different number on every element, so referencing @id downstream asks what this element's id is and gets a different answer per element. An attribute is a column in a spreadsheet: the node is the whole sheet, @id is the column, and each row reads its own cell.

Two more read with the same @ sigil but are not stored columns: @ptnum is the element's ordinal in whatever is flowing through, and @numpt is how many elements there are. They are always available, where a column has to have been written by something upstream.

A selection is a column too, not a thing kept off to one side. Picking elements with a Select node writes a named attribute across the whole set, @selected unless you rename it in Group Name, holding 1 on what you picked and 0 on everything else. Give it a Radius above 0 and the picked elements still read 1 while their neighbours fall off towards 0 over that distance, in world units, on the Falloff curve you choose. Nodes downstream read that column as a mask, so a soft selection and a hand-authored weight are the same thing to them, and the column can be inspected, edited or written by an Expression like any other.

Worked example: shade 200 points by their position in the set

  1. Add a Points node, pick Circle as its Geometry so the points have a region to fill, and set Count to 200.

  2. Add an The Expression language node and write a per-element rule:

    float t = float(@ptnum) / float(@numpt - 1);
    @Cd = {t, t, t, 1.0};
  3. Every point reads its own ordinal, divides by the count, and takes a brightness from 0 on the first point to 1 on the last. The result is a ramp across all 200 points.

The Expression node ran once per point, and each run saw that point's own ordinal. There is no loop. You wrote the rule for one element and it was applied across the whole bag. For a colour sweep rather than greys, write t to an attribute and feed the points into a Color Map, which looks the colour up through a gradient.

Copies

The Iterator node stamps copies of whatever you feed it, in a line, a grid, a circle, along a path, or onto existing points. Each copy knows which copy it is.

While it builds the copies, the Iterator writes a set of per-copy columns:

Scaling each copy a little more than the last is reading @index and turning it into a scale. Fading copies evenly across the run is reading @normalized. The relationship is authored once and every copy fills in its own number.

A layout node can carry its own columns too. A Grid writes @index_x / @index_y / @index_z, the column, row and layer of each lattice point, so a grid can be varied by axis rather than by flat ordinal.

The per-copy inputs on the Iterator take three shapes, not two. Rotation, scale, time offset and variant accept a single value shared by every copy, a list where each copy reads its own entry, or a Curve, evaluated at the copy's @normalized ordinal so the whole run eases from one end to the other. Position offset and anchor take a value or a list.

Loops

A loop gives many passes, one after another, instead of many elements side by side. Each pass has its own context.

Loop context arrives on a wire, not as an attribute on the data. The loop's ordinals become @index / @normalized / @total columns only when the For End node is set to Merge Collection.

When something downstream knows which point, copy or pass it is, it is reading a per-element value: @id, @index, or @ptnum.

A value only varies per element if it actually is a per-element attribute. A plain number typed into a parameter is one value shared by everything. To get variety, read an attribute (@id, @index, @ptnum) or feed the Iterator a list instead of a single value. Reading a column nothing upstream wrote fails the cook by name rather than reading zero, so a wrong attribute name shows up as an error and not as a black result. Use attr_or(default, "name") where the column is genuinely optional.

See also