Loops in Geometry Nodes [Proposal]

Loops are one of the most requested features for geometry nodes. They allow executing the same nodes an dynamic number of times. This proposal shows how loops could work in geometry nodes.

The first observation is that there are actually two kinds of loops that are of interest for us:

  • A serial loop allows executing a node multiple times whereby an iteration can take the result of the previous iteration as input. This is essentially the same as creating a chain of nodes that contains the same node multiple times.
  • A parallel loop also allows executing a node multiple times. However, this time the output of one node is not passed into the next node. Instead, all nodes can be executed at the same time and all results are joined in the end.

The sections below describe how both types of loops could look like in geometry nodes. Since the serial loop is more generic, the corresponding node is just called Loop in this proposal. The parallel loop is a bit more specialized, and since we are in geometry nodes, we generally want to do something in parallel for various geometry elements. Therefore, the corresponding node is called Geometry Loop. We might have a more generic Parallel Loop node in the future, but that is not really required right now and would make common use cases harder to achieve. Therefore this proposal focusses on a solution for geometry processing.

Loop

A design for a serial loop has to provide an answer to how the user specifies the following things:

  • How many iterations are run?
  • Which inputs are used by the first iteration?
  • Which data is output by the last iteratiton?
  • Which data is feed back into the next iteration?
  • Which nodes run in each iteration?
  • Optional: How to access the current iteration index?
    • This is optional, because one could just manually increase an index by one on every iteration.

Since a serial loop is very generic it should not be required to have any geometry to use it. This would theoretically also allow us to use the same loop construct in other node systems in the future.

This proposal solves this by introducing a new Loop node. This node is similar to a Group node, but instead of executing the referenced node group only once, it can run the node group a dynamic number of times. To create a new loop, one just has to add a Loop like any other node. It will look something like so:
image

It has a reference to a node group and an Iterations input. By default it might reference no node group. Instead a default node group would be created when first hitting tab to enter the group. On the inside there is just an empty Group Input and Group Output node by default. We could also have Ctrl+G like behavior to create a loop, but that could be added later.

One of the simplest (and least useful) loops one could build is one that just adds ten to a value in every iteration. In this setup the output of the Loop node would be 50.

Here we also see an interesting thing, the Loop node knows that that the output should be passed back into the group in the next iteration. In the current design this mapping between input and output sockets is just based on the name. We could think about some more explicit mapping, but currently I think just using the name is good enough, the most straight forward and easy to understand. The handling of name collisions needs some consideration, but a simple solution would be to just show a warning in that case and to not execute the loop until the problem is resolved.

We can also note that the initial inputs to the node group are provided from the outside.

Below is a more interesting example that actually modifies a geometry multiple times. This example also shows that there can be an input that does not have a corresponding output socket. In this case the same value is passed into the loop in every iteration.

The opposite is possible as well. There are can be an output that does not have a corresponding input. In this case, this output is only computed in the last iteration. When the number of iterations is zero outputs that correspond to inputs will just be passed through (same as when the Loop node is muted). Other outputs will get a default value, which is usually zero or an empty geometry.

A new Iteration node can provide easy access to the index of the current iteration. This node can only be used in Loops. Note that it does output a single value and not a field like the normal Index node.
image

Generally, the number of iterations in this kind of loop should be relatively low. This is to avoid unnecessary slow computions, because the individual iterations cannot be executed in parallel.

Geometry Loop

The new Geometry Loop node allows creating a loop where all iterations can run in parallel. This allows for much higher iteration counts in practice.

A design for a parallel loop has similar but slightly different requirements compared to the serial loop design. It has to answer the following questions.

  • How many iterations are run?
  • What data is passed into each iteration?
  • What data comes out of each iteration?
  • How are the outputs from each iteration reduced/joined to get values that can be further processed after the loop?
  • Which nodes run in each iteration?

Similar to the Loop node, the Geometry Loop node can just be added through the Add menu. It also references a node group that one can tab into. Instead of an Iterations input, it has two inputs by default: Geometry and Selection. It also has a Geometry output that outputs nothing by default (this is technically optional, but the node does not make sense without a geometry output).
image

Additionally, the node has a dropdown menu to choose a mode from (this is missing in the mockups). In the menu one can choose a domain (e.g. points, edges, …) but possibly also other modes like instance references, which would run the node once for every different instance.

The number of elements in the geometry, the mode and the selection together determine how often the referenced node group will be executed.

Like the Loop node, the Geometry Loop node also needs additional nodes to flourish. The naming is of course still up to debate.

  • An Element Geometry node that gives access to the geometry that corresponds to the current execution(e.g. a single point or face).
    • In theory we could also retrieve this from the Group Input node, but that would result in having different behavior for different inputs which I’d like to avoid.
    • Depending on the selected mode, this will output a different kind of geometry
  • An Evaluate at Element node that evaluates a provided field in the geometry context of the current element (e.g. the current point or face).
    • It would also have a data type dropdown menu.
    • Note how it has a field as input but outputs a single value.

image

The image below shows a slightly more complex example. It creates a circle mesh on every point of a grid. Whereby the number of vertices in each circle is randomized.

Let’s look at a couple of different aspects of how the Geometry Loop node works based on this example.

  • The node group in the loop outputs a different mesh for every element.
  • The geometry outputs from each element are joined into the final mesh output.
  • Even though the Vertices and Radius input do not support fields inside of the node group, they do support fields on the outside.
    • The Geometry Loop node evaluates these fields and passes single values into the node group for each element.
    • If the node group has an input that supports a field, that will also be exposed as a field on the outside. In this case the field will not be evaluated automatically. It will just be passed into the node group without changes.
  • The combination of the Position and Evaluate at Element node give access to the position of each grid vertex in the loop.
    • The same could be achieved by exposing the Translation from the Transform node as an input and passing the Position in from the outside.

The following image shows an example that points on each face individually. All points on the same face will get the same color. Note that this is not the most efficient way to achieve that (instead one could use an Attribute Capture node to capture the color on the face domain before scattering points).

The example shows a couple more aspects of the Geometry Loop node:

  • Not only the output geometries from each element are joined, but also the output fields.
    • All output fields are captured on the individual output geometries and then they are joined.
    • The domain where the attribute is captured is determined by the Attribute Domain set for the group outputs that already exists for the modifier panel.
    • Even though the node group outputs a single color for each element, the final output of the Geometry Loop node is a color field. This field references an anonymous attribute on the Points geometry.
  • The Element Geometry node outputs a mesh that contains a single face.

Some more misc aspects that I don’t have specific examples for yet:

  • The selection input to the Geometry Loop node allows only running the loop for all the selected elements.
  • The node group can output multiple geometries.
    • This will result in multiple Geometry outputs in the Geometry Loop node.
    • All output fields will be captured on all output geometries. In the future we can do some better static analysis of the node tree to determine which fields do not have to be captured for which geometries.

Conclusion

Both of these nodes make geometry nodes significantly more powerful. Sadly, I didn’t got to creating a prototype yet. Depending on the feedback, we will either update the proposal, create a prototype are start with the real implementation directly. The initial focus will probably be on the Geometry Loop node, because that solves more common use-cases.

When providing feedback, I recommend you try to create mockups for node trees you’d like to build. All of the mockups in this proposal have been made with Blender node groups without changing the source code.

FAQ

This section contains my current answers to frequently asked questions.

Can we put loops into the top-level node tree to avoid the need for node groups?

Yes, I think I’d prefer that as well, but there are some caveats.

  • To make this work, we need some new kind of frame node that contains the entire loop. Without that we’d get:
    • Even more different execution contexts in the same node tree.
    • Even more rules for what kind of links are valid (e.g. you couldn’t just link from the inside of the loop to stuff that’s outside).
    • Possibly ambiguous behavior with nested loops.
  • Something similar to “old-style” node groups could work (see screenshots here).
  • Implementing loops this way will be signifantly more complex.
    • This is not really an argument against having loops directly in the top-level node tree, but it may impact how we get there.
    • Loops as node groups I could see ready for Blender 3.1. Loops that require very new ui functionality not so much, but would have to look into it more.
    • Versioning code for converting one kind of loop to another should be relatively simple.
  • If we use this approach, it should be used for both kinds of loops.

More mockups in that area are welcome, but they should take the following notes into account:

  • There should be some kind of frame around all the nodes inside the loop.
  • The general ui solution shown in the mockup should be applicable to both kinds of loops.
  • The original proposal contains some questions that a loop design should answer. Make sure the mockup or additional text answers these questions.

Can we have an Entry/Break node in the loop?

Yes. I actually intended to mention that in the original proposal, but forgot about it. We can have both nodes.

If the loop contains an Entry/Break node, can the iterations input be removed?

In theory yes, in practice no. Removing the iterations input would make it very easy to accidentally create infinite loops. Even worse, since that would make the entire system turing complete, there is no general way to detect if there is an infinite loop (see halting problem). Always having a max number of iterations solves that. The user can of course choose to set the number of iterations to a very high number, but that would be a very explicit choice.

Why not extend the concept of fields to “Geometry Fields”?

In the past we thought about and discussed expanding the field concept to include geometries. It’s certainly possible and it would provide a bit more flexibility, but that flexibility does not come for free:

  • It adds even more (nested) evaluation contexts to a node group (similar to top-level loops without a “frame”). The current fields did that as well, but provided significant usability improvements for common use cases. Geometry fields would provide significantly less to no usability improvements.
  • Already existing tooling for the current field system would become worse, because the distinction between sockets that support fields and those that don’t goes away. That makes it much harder to produce useful and correct warnings.
  • The same flexibility could theoretically be added later. We could investigate if we can somehow pass node groups around that are invoked somewhere else. I have some ideas for how that can work, but have to put more thought into it. Right now it’s not important enough.

Can sequential loop and normal groups be combined?

I think think building a group as an asset and building a loop are two fairly distinct things. Combining them does not help imo and may make things more confusing. Also, changing how the Group node works may be problematic, because we want to use the same groups in different node tree types, and it’s not likely that all node tree types will support loops. This would also make it harder to convert node-group-style loops into groupless-style loops if we ever wanted to do that.

51 Likes

This idea looks very useful to me.

However I don’t really like the ‘Loop’ vs ‘Geometry Loop’ names. Though both are internally implemented using loops the effects are very different…I’m not sure what would be better though:

Maybe ‘Repeat’ (for Loop) and ‘Multiple’ (for GeoLoop)?

But even with the original names I could get used to those quite fast I’d guess :slight_smile: .

I like the name “Repeat”. The other one not so much, but maybe someone comes up with an even better name.

1 Like

‘Spawn’ :smiley: (not really serious, it’s difficult to find a good name for it).

Extremely useful.

It could also be not necessarily inside a group node, but a start loop (iterations) node, end loop node respectively on a surface level, so you don’t get a UX of a potential loop-ception by having them as groups.

The start and end loop nodes could also have ability for custom attributes/fields to be looped too.

In this case, you can do linear and parallel loops with the same nodes, and not need to ship multiple groups in a project. If you want them as a group, you can group them.

Reason being… having another group node system as loops, the custom node groups can get huge since they are listed from all editors in the API.
image

But, exciting! Can’t wait to see this in action.

1 Like

Cool proposal! I like the fact that the main body of the loop is in a group. That should make the node tree really clean :slight_smile:
“Geometry Loop” could maybe be named “Repeat on Geometry”.

3 Likes

Really glad to see this come through as a proposal!

Serial loops are always fun but the parallel loops are where the more general workflow gains are.
In Sverchok pretty much all nodes already allow for working on a bunch of things in parallel but this is something I’ve often missed in Geo Nodes.

I think as we move further into mesh operations, these parallel loops will be more and more important as we want to be simultaneously modelling potentially hundreds of disparate objects.

I’d definitely recommend a soft limit on the serial loop iterations. Nothing like dragging a slider and crashing to desktop.
I would think that the loop out node should automatically generate the same sockets as the loop in has. It should be fairly clear to the user that they will need connecting up for the loop to function.
Having current loop index is really useful for creating a changing random seed per loop.
Would also be good to have access to the total loop number as well.

While group nodes gain sockets from the inside, it seems like it would be useful to be able to generate sockets from the outside of a loop but plugging to a new socket.

Might also be useful to have a break socket on the loop out so that you can use conditions to terminate the loop before the max iteration value is reached.

For the Evaluate at Element, can’t you already just use a Transfer Attribute set to index and just plug an specific integer into the index?

I think we do need a better way to differentiate between different objects. Right now everything just gets appended to the same list but how do I isolate the Nth object in a list of 100k objects with arbitrary numbers of vertices? That’s a broader problem than loops but it’ll be more apparent when it suddenly becomes easier to generate huge data sets.

For naming, I’d err towards Serial Loop and Parallel Loop. I’m a fan of calling things by what they are.
(please no Mountain node bs)

10 Likes

Itterate Over Geometry

1 Like

I like Jacques implementation and idea of having one loop node that you can hop into and get out back for readability, it isolates your mind to understand loop. I’m not fan of LoopStart/End nodes. For example In houdini for me it makes it harder to read. Maybe we could separate nodegroups from loopgroups in file?

4 Likes

Didn’t read everything and currently only have very little time, but I wanted to ask why going through loop nodes? With the current approach of geometry nodes wouldn’t it be more consequent to think about geometric fields? A loop node feels like the attribute processor :wink:

1 Like

Hm I think you might be mixing things up here. The attribute processor was meant to be a group that does some processing on an attribute - once. The loop node is very different. It allows you to dynamically chain node groups as many times as you want. It doesn’t have to do something on an attribute, it could repeatedly execute a geometry node for example.

It could also be not necessarily inside a group node, but a start loop (iterations) node, end loop node respectively on a surface level, so you don’t get a UX of a potential loop-ception by having them as groups.

Personally, I don’t see that working suuper well, at least not without more ui. It may work better with the “old-style-node-group-design” (see screenshots here). I think it would be a bit messy if we’d get even more execution contexts in the root level of the node tree. Also you’d quickly run into problems with ambiguity. E.g. when there are nested loops, does the Iteration node correspond to the inner or outer loop? Both can be useful.

Reason being… having another group node system as loops, the custom node groups can get huge since they are listed from all editors in the API.

That’s more a problem with the list than with having more node groups.

Sounds reasonable!

Soft limit for sure, hehe. I’ve also been looking a bit into cancelable operations in Blender. I may be able to build a prototype for that. Have to do some more testing.

I would think that the loop out node should automatically generate the same sockets as the loop in has.

Hmm, that could work. Guess that’s behavior that we can figure out in more detail once we have something working. I could imagine that sometimes you have things things that are only input or only outputs. In that case it seems unnecessary to add more sockets that just won’t be used. To be fair, having the same sockets as inputs and outputs would eliminate of mapping sockets by name, which is nice.

While group nodes gain sockets from the inside, it seems like it would be useful to be able to generate sockets from the outside of a loop but plugging to a new socket.

That should work, but may not be in the initial version for implementation detail reasons, will see.

Actually forgot to mention that in the proposal. Yes, having some kind of Break node with a boolean input should work.

Depends. That could work when just iterating over mesh elements or so. May be more tricky in different modes of the loop (e.g. instance reference mode, or whatever we come up with in the future). Also it’s of course more cumbersome to use the Transfer Attribute node for that, because you’d have to link up more things. You also need access to the original geometry, which is not necessarily bad, but if we can avoid it, even better.

I don’t have a definite answer for that, because it depends a lot on the actual use case. However, I think better instancing support (especially instance attributes and the ability to investigate nested instances) will eliminate the problem in many cases.

Hm, thought about that before. “Serial Loop” seems reasonable, although I think right now I prefer “Repeat”. “Parallel Loop” is a bit too generic to me given that the node is specialized for geometry.

We did think about and discuss expanding the field concept to include geometries before. It’s certainly possible and it would provide a bit more flexibility, but that flexibility does not come for free:

  • It adds even more (nested) evaluation contexts to a node group. The current fields did that as well, but provided significant usability improvements for common use cases. Geometry fields would provide significantly less to no usability improvements.
  • Tooling for the current field system would become worse, because the distinction between sockets that support fields and those that don’t goes away, making it much harder to produce useful and correct warnings.
  • The same flexibility could theoretically be added later by investigating if we could somehow pass node groups around that are invoked somewhere else. I have some ideas for how that can work, but have to put more thought into it. Right now it’s not important enough.
4 Likes

No, I’m not mixing things up. I’m completely aware of this. The attribute processor had a subnet, that worked on a single attribute. The geometry loop, how it was called, has a subnet, that works on a single geometry. It’s just the domain of the field, that changes. Attribute processor used the incoming geometry as the domain. A loop uses an interval of values for the domain.

Re: Naming.

I believe both types of loops already have names in the programming world.

Loop: this is like recursion – every iteration takes the output of the previous one.

Geometry loop: this is like a map-reduce – every element (point/face) is mapped to some output, then this list of values (e.g. geometry points) are reduced to a single value (e.g. geometry)

So the loops could be called “Recursion loop” and “Map-reduce loop” if the terms are not too technical.

1 Like

Nice!

indeed, more visual distinctions between loop groups and regular groups would be nice for ex:

cade8ca896ccb52650f599806849afc5119f3842

  • change color? not the same green as nodegroups
  • new dedicated icon, not using the nodegroup icon
  • visual indication once inside the special group, perhaps using the new loop icon in the nested “breadcrumps” UI

i suppose these special loop inputs would be in their own “iteration” category in the add menu only available within such special group?

7 Likes

It’s not really like recursion, it’s just a for loop.

These are too technical imo.

Yup, these things can be figured out in detail when the general concept stands. Thanks for the mockup though.

Yes.

4 Likes

Feels like this proposal falls a little short, only considering static loops (ie the number of iterations is known beforehand) seems limiting, I always imagined implementing loops with “special” nodegroup where input sockets and output sockets would be identical except for an “Exit” output socket being 0 or 1 would either rerun the loop or exit the loop.

It’s simple elegant and answers all your questions you posed for a serial loop, a simple do-nothing loop would look like this

Socket Default Value
Iteraiton 1

While calculating the Nth Fibonacci would be something like

Socket Default Value
Iteraiton 1
FibonacciN 10
F0 0
F1 1
11 Likes

Though using node groups for creating loops is something that come first into mind and is most easy to implement I have concerns that in the end we will get something what is not very comfortable to use. To translate such proposal into a traditional programming language would meant to replace for loops with functions. Let’s try to translate first example into Python language.

# this part is in the node group
@vectorized
def my_best_name(value, loop_index):
    return value + 10

# this is in top tree
iterations = 5
value = 0
my_best_name(iterations, value)

And now have a look how it usually looks

iterations = 5
value = 0
for loop_index in range(iterations):
    value += 10

I think it’s clearly visible that creating a function here is redundant and readability is suffering. Also it makes it even worse that user usually won’t be able to see function code and code in a tree simultaneously. Looks like the proposal takes the direction toward functional languages which are not for the human beings.))

Also node groups as functions means that they can be reused what is different concept than loops.

Another argument. I read somewhere in documentation or somewhere else about another quite popular software which name should not be named that using node groups for solving loops problem was also initial solution. But the time showed that it was not very comfortable in use and they switched to another (current) solution where loop is determined directly in top tree.

It would be nice if it will be possible to test both approaches to compare.

1 Like

Screenshot 2021-11-12 at 17.47.57
I haven’t got any crits on the idea but I think the yellow arrows could maybe look a bit tidier. How about a little icon with a number like this? Obvs circular arrow could expand with more numerals.

1 Like