3
$\begingroup$

Blender 4.0 introduced Repeat Zones, however they represent a for loop without a way to break early. This means a while loop can't be implemented in Geonodes as: [not using Python as an example as idiomatically it doesn't provide a traditional for loop]

for (i=0; i<MAX; i++) {
    // usual body of a while loop
    if (condition) break;
}

What you can do instead is:

for (i=0; i<MAX; i++) {
    if (condition) continue;
    // usual body of a while loop
}

This however has a very significant performance problem: it doesn't scale; if only a small number of iterations is needed, the loop may be no-oping basically through its entire lifetime, e.g. if the condition is met at the 2nd execution of the loop's body, and MAX equals a million, the loop will be "idle" (no-op) 99.9999% of the steps! In such case the execution will unnecessarily take at least 8 additional seconds! [I benchmarked using a simple test]

Even if you assign MAX adequately to the same order of magnitude as the actual Iterations needed, you can waste 7 seconds if you guess a million and it finishes early after a little over 100k iterations.

Here's one example where the number of Iterations is unknown: Refining material assignment in Blender geometry nodes based on neighboring faces

So until proper while loops are implemented, I'm looking for the best workaround and posting my proposition below.

$\endgroup$
2
  • $\begingroup$ Couldn't we for all problems find the scale of iterations needed to solve it (except NP-hard problems I think or something similar) ? Even if I know it means sometimes really hard math problems. Also in Blender 4.1.1, the Timings overlay in the editor doesn't take into account empty loops (1M iterations took 2s for me but displays <0.1ms). Super interesting topic and workaround ! $\endgroup$
    – Lutzi
    Commented 2 days ago
  • 1
    $\begingroup$ @Lutzi yes, in general Timings lie, don't trust them. I think for the example given, figuring out the number of iterations needed is pretty much applying the algorithm - what you can do is figure some obvious truths e.g. the Iterations don't need to be higher than the number of faces. That is unless you unmute the node in "Loop Body". $\endgroup$ Commented 2 days ago

1 Answer 1

3
$\begingroup$

This is a workaround I came up with:

  • You can see it's just a fragment - you can keep adding repeat loops following the pattern of the 2 outermost repeat loops visible on the screenshot.
  • The purpose of the blue, innermost "Optimization Inner Loop" is to execute the "Loop Body" more than one time before making a conditional check - it's useful only if the conditional check is actually expensive.
  • The red "Conditional Loop" is the one loop that executes the "Loop Test".

Here's a full example with a shape of 10×10×10×10×10 = 100'000 iterations:

Loop Body:

Mat Fix:

Loop Test:

Code equivalent:

# ...
def loop_test(before, after):
    # count red faces
    a = sum(1 for f in before.faces if f.material_index==1)
    b = sum(1 for f in after.faces if f.material_index==1)
    return a == b


def loop_body(geometry):
    for f in geometry.faces:
        if matfix(f, 'grow'):
            f.material_index = 1
#    for f in geometry.faces:
#        if matfix(f, 'shrink'):
#            f.material_index = 0


done = False
for _ in range(10):
    if not done:
        for _ in range(10):
            if not done:
                for _ in range(10):
                    if not done:
                        for _ in range(10): # conditional loop
                            if not done:
                                after = geometry
                                for _ in range(10):
                                    after = loop_body(after)
                                done = loop_test(geometry, after)
                                geometry = after

It's supposed to grow the single red face on its column to completely fill this column:

Simple benchmark:

from bpy import context as C, data as D
import time

start = time.time()
mod = D.objects['Plane'].modifiers['Benchmark']
for i in range(10):
    mod.show_viewport = False
    mod.show_viewport = True
    C.view_layer.update()
print((time.time() - start)/10)

Competitor:

Iterations actually needed: 5000 (4999 faces in a loop will turn red, then one additional Iteration is needed to discover the algorithm finished), so the estimation here is 20× too big.

Nested Repeat Zones: 6.94 s

Competitor: 12.69 s

And without a check for those wondering, I got 135 s for the simplest approach (I dared to test only once though):

$\endgroup$
1
  • 1
    $\begingroup$ This is an ingenious and highly useful solution that proves to be exceptionally handy in a variety of situations. It serves as an excellent interim measure until we receive a native while loop node. $\endgroup$
    – crucchi
    Commented yesterday

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .