Abort UI Python Simulation

I’m working on a blender based python simulation - the core of the simulation is a loop similar to fluid baking.

I can’t figure out how to ABORT the simulation if the user presses ESC or something similar.

I’ve googled it and searched around… can’t find the answer I’m looking for…

Can anybody help?

Try using a modal operator:

Thanks for the suggestion about modal operator… I looked at the documentation and set up this simple script to run a simulation of a cube bouncing back and forth on X axis. It uses a timer to keep things moving. The keypress event works great to abort the simulation.

import bpy


class ModalOperator(bpy.types.Operator):
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"
    
    x_pos=0
    x_limit=2
    direction_forward=True
    _timer=None
    cube_object=None

    def __init__(self):
        print("Start")

    def __del__(self):
        print("End")
        
    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)

    def execute(self, context):

        print(context.object.location.x)
        
        wm = context.window_manager        
        self._timer = wm.event_timer_add(0.1, window=context.window)
        wm.modal_handler_add(self)
        
        return {'FINISHED'}

    def modal(self, context, event):

        if event.type in {'RIGHTMOUSE', 'ESC'}:  # Cancel
            self.cancel(context)
            return {'CANCELLED'}
        elif event.type == 'TIMER':
            if self.direction_forward==True:
                self.x_pos=self.x_pos+0.1
                
                if self.x_pos>=self.x_limit:
                    self.direction_forward=False
            else:
                self.x_pos=self.x_pos-0.1
                
                if self.x_pos<=-self.x_limit:
                    self.direction_forward=True
                    
            print(self.x_pos)
            self.cube_object.location.x=self.x_pos

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        
        cube_name="simcube"
        
        # find object by name to reuse if possible
        self.cube_object = bpy.data.objects.get(cube_name)
        if self.cube_object is None:
        
            if self.cube_object==None:
                bpy.ops.mesh.primitive_cube_add(size=2.0, 
                    enter_editmode=False, 
                    location=( self.x_pos, 0, 0))
                                
            self.cube_object=bpy.context.view_layer.objects.active
            self.cube_object.name=cube_name

        self.execute(context)


        return {'RUNNING_MODAL'}


bpy.utils.register_class(ModalOperator)

# test call
bpy.ops.object.modal_operator('INVOKE_DEFAULT')

My problem is - the frame rate is limited by the timer…

How do I make it so it goes as fast as the processor can handle - not using a timer just poling for ‘ESC’ keypress event similar to fluid baking… The number of FPS when fluid baking is not limited by a timer interval, but limited by how fast your CPU can process the simulation…

Ideally I would be able to poll for keypress events while running a tight loop inside the execute function

You can change these lines


# run at 60 fps
self._timer = wm.event_timer_add(1.0/60.0, window=context.window)

# do not block viewport
def modal(self, context, event):
	...
	# return {'RUNNING_MODAL'} # this blocks viewport
	return {'PASS_THROUGH'}

Hi - thanks for the reply - this solution is still timer based. Speeding up the timer to 60fps won’t help because some frames will be need longer than 60fps and some will be less… It’s for baking a simulation which has a variable frame rate unlike a video game. Like a fluid simulation the first few frames will be fast because there isn’t much happening - and as the simulation progresses it will get way slower because the scene is much more complicated.

The objective is to maximize CPU utilization while showing some view port updates with the ability to abort.

In C++ UI programming - one way to do this would be to use a custom event ID and modal function would generate a new event each loop - event based rather than time based. Another way would be a separate thread(s) for the simulation and the worker thread sends update events to the main UI thread.

If is so, you can consider looking at this example.

https://github.com/bulletphysics/bullet3/blob/master/examples/HelloWorld/HelloWorld.cpp`

In this line dynamicsWorld->stepSimulation(1.f / 60.f, 10); you can see that you can pass a delta time in the physics system. Putting this in a straight loop, is the same as running the sim “offline”.

The modal operator in Blender in this case is independent of the physics system as you say, only doing a heartbeat update once per second just to see progress or control the running state as you mention.

However the physics simulator must run in a thread so the intense CPU loop won’t block the GUI. It has been mentioned that Threading is unsupported (it works great actually - but is only a matter to cause random crashes) so it means that alternatively you will have to choose Queues instead.

https://devtalk.blender.org/t/multithreading-support-please/13038/17
https://docs.blender.org/api/current/info_gotcha.html