Animation loop in Python

Hello, I’m trying to generate an animation within a Python component without using the Timer component.
My environment is Windows, Rhino8, and Python 3.

This Python code runs, but nothing is shown during the computation.

Does anyone have suggestions?

Thanks!

import Rhino.Geometry as rg
mesh=rg.Mesh()
def main():
    for t in range(20):
        #update mesh
        preview_event.set()
    exit_event.set()
exit_event = threading.Event()
preview_event = threading.Event()
class MyComponent(Grasshopper.Kernel.GH_ScriptInstance):
    def RunScript():
         #initialize mesh()
  
        thread=threading.Thread(target=main,args=())        
        thread.start()
        counter=0
        while(not exit_event.is_set()):
            time.sleep(1)
            Rhino.RhinoApp.Wait()
            counter=counter+1
            if(preview_event.is_set()):
                ghenv.Component.ExpirePreview(True)
                print("preview")
                preview_event.clear()
                if(counter>5000):
                    print("timeout")
                    break
          
    def DrawViewportWires(self, args):
          args.Display.DrawMeshWires(mesh,System.Drawing.Color.Red,2)
  
    def DrawViewportMeshes(self, args):
          args.Display.DrawMeshWires(mesh,System.Drawing.Color.Red,2)
  

I usually expire the component itself iteratively (i.e. similar to the Timer component, but within the code itself). Here’s a basic example with a counter, but the same method applies for geometry/animation stuff (i.e. replace the count persistent/static mutable variable with your dynamic system/geometry):

Edit: I’m not actually sure if this all works with the new CPython script editor, but might be worth a go.

1 Like

Hi,

I’m not trying to debug this code in detail, but what I find strange about this code is that it does not look thread-safe, nor does it seem right.

So you are calling ExpirePreview, but not on the UI thread. Could work, but could also simply do nothing. Depends on the implementation. Rather the later, because Grasshopper is not thread-safe.
Other than that, what would happen if you call RunScript again? It looks like you just create another thread.
Now I don’t know how the CPython scripts are working in between. But can you guarantee that the code is not always interpreted on the execution? Does it reinterpret it on each script component execution? Because then you would need to persist the thread instance somewhere else and prevent it from executing more than once. IronPython might differ here from CPython.

Seems like it does, here’s a quick modification where we iteratively add random points to a polyline:


250505_GHPython_CounterPolyline_00.gh (11.8 KB)

Almost the same code worked perfectly with Rhino7 and the old C# node.
The code does not work with Rhino 8 and the new C# node.
I ported it to Python 3 and it does not work either.
So, it seems like this is not a Python-related issue. Rather, it seems like something was changed between Rhino 7 and Rhino 8…

sure, its always someone else…

If anybody is interested in generating an animation loop without relying on the timer component, here is the code. It works well with the old C# node that comes with Rhino 7.

The code finds a circular mesh based on an initial quad mesh and a reference smooth surface.

circular mesh.gh (73.0 KB)

If anyone from McNeel is watching, it would be great if the root of the problem could be identified and fixed so that this animation loop without a timer will work again with the new C#/Python 3 component. :folded_hands: :folded_hands: :folded_hands:

I don’t know if you missed it, but FWIW the generic timer/looping method I mentioned above does work with the new CPython 3 scripting component in Rhino 8:

Thanks! But I want to trigger preview only, instead of refreshing everything..
I tried replacing ExpireSolution with ExpirePreview but had no luck.

1 Like

Oh, wait, a zombie strategy worked.

I created a task that runs on a different thread and let the code continue without awaiting the task to finish. As the result, the code finsihses immediately after the task is started. The task floats in the air as a zombie task and keeps fireing the expirepreview event. Sounds terrible, but it seems that this strategy works…

There are many issues to resolve though. (How to prevent multiple zombie tasks to be created, how to capture the end of the task and safely output the resulted geometry, etc.)