Is there a way to stop a for loop by now with <esc>?

I am looking for a way to catch keyboard events to stop a loop and I am not really finding something solid. Only that it seems a common problem.

I am looking for something like this:


import bpy
import time

for x in range(10):
    print(x)
    time.sleep(1)    
#    if keyboardevent == 'ESC':
#        break
    

Ideas or solutions anyone?
tia.

modal operator.

elif event.type == 'ESC':
            return {'CANCELLED'}

edit: look for the template ‘operator modal’ in the text-editor.

Hm. I don’t understand how to connect those two :confused:
I have two problems with this I can’t come by:

How do I implement the modal operator in this?
Why is the button not showing when I run the script, only when I “update” the tools panel by panning/clicking whatever?

I seem so incompatible with Blenders API and Python…


import bpy
import time

class FOO_Panel(bpy.types.Panel):
    bl_label = "FOO"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
      
    def draw(self, context):
        layout = self.layout    
        column = layout.column(align=True)
        column.operator("op.foo")


class OBJECT_OT_FOO_Button(bpy.types.Operator):
    bl_idname = "op.foo"
    bl_label = "Foo 10s now!"
            
    def execute(self, context):
        
        for x in range(10):
            print(x)
            time.sleep(1) 

        return{'FINISHED'}
                          
classes = [
    FOO_Panel,
    OBJECT_OT_FOO_Button
    ]

def register():
    for c in classes:
        bpy.utils.register_class(c)

def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)

if __name__ == "__main__":
    register()

Here is what your code was doing but as a modal timer operator, using the code from the template. Notice how blender is still usable while the modal timer op is running, whereas using the sleep in your example it is locked until the op finishes.


import bpy


class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None
    count = 0

    def modal(self, context, event):
        if event.type == 'ESC' or self.count &gt; 9:
            self.count = 0
            return self.cancel(context)

        if event.type == 'TIMER':
            # change theme color, silly!
            print(self.count)
            self.count += 1
        return {'PASS_THROUGH'}

    def execute(self, context):
        context.window_manager.modal_handler_add(self)
        self._timer = context.window_manager.event_timer_add(1, context.window)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}


class FOO_Panel(bpy.types.Panel):
    bl_label = "FOO"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
      
    @classmethod
    def poll(self, context):
        return True
    
    def draw(self, context):
        layout = self.layout    
        column = layout.column(align=True)
        column.operator("wm.modal_timer_operator")


def register():
    bpy.utils.register_class(FOO_Panel)
    bpy.utils.register_class(ModalTimerOperator)


def unregister():
    bpy.utils.unregister_class(FOO_Panel)
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
    register()



Not sure why the button doesn’t show up immediately.

I don’t really want to use the UI. I need the possibility, to break free from the for loop.
The for loop is supposed to hold a long calculation, and several in a row and I need a way to exit that calculation, ideally by pressing ESC :wink:

All I get from making the OBJECT_OT_FOO_Button modal is weired stuff, the for not working anymore, only mousemove is cought…
I’ll play a bit more, just thought someone can just fill in the missing parts in my code so I see what’s going on.
The API wiki isn’t exactly… very explainatory… they throw you a code sniplet and good luck :smiley:

I put the modal timer op and your code together in previous post in an edit after your next post. (distracted by the TV) You can use the event.type = "TIMER’ part to emulate your for loop and use operator properties, like self.count in this instance, to keep where you are in the loop.

Mousemove makes me think you have used the Modal Operator template, instead of Modal Timer Operator.

This subforum should support syntax highlighting :smiley:

Anyways, a truckload of thanks. After looking at the modal timer I had a similar idea, I was already guessing that the for-loop would be a problem.
I am not sure I completely understand that code, but I’ll dig into it.

I wonder how one would realize abortable nested loops… another thing to experiment. :wink:

@batFINGER: Post #4
That’s what i thought.

@arexma: what for do you need that code exactly?

For a small tool for DAUs, for a sort of a custom render button.
It has to render a small series of images, with changing parameters and it’s a lot easier to let the parameters run in a loop than animate them (don’t ask). And as the users are eyecandy-non3dcg-people I have to drag all the functions into one toolpanel to the top… most stuff is redundant. (you don’t want to know either)

So the rendering has to run in loops, but obviously it should be abortable.
The render itself is already by design, the loop was not.

But I am already thinking of doing it with keyframes and animation rendering - anyways, whatever method I’ll use I learned something again :slight_smile:

I can’t think clearly anymore today, with this hour I spent 12 hours straight reading the API and doing good old trail’n’error :stuck_out_tongue:

I can’t think clearly anymore today, with this hour I spent 12 hours straight reading the API and doing good old trail’n’error :stuck_out_tongue:

been there. gute nacht! :wink:

btw: bist du zufällig aus wien? überleg mir schon länger ein blender-user-group-meeting in wien zu machen…

Graz, but I’d be in vienna in no time by car or bike.
There is/was a smaller group in Graz, but I don’t even know if they’re active anymore.

Also to stop a script you can hit ctrl-C in the console… Very handy if you forget to increment a counter in a while loop.

just found: http://code.activestate.com/recipes/203830/

maybe u can use it somehow.

Yeh, I grew up with MSDOS and Debian 1.0 :smiley:
ctrl-c is the only thing that’s been consistant in all those years, and needed it a lot of times today already :stuck_out_tongue:
But my DAUs are ideally not going to see the console.

@nikookin: I am not certain this’ll work, it needs tty which isn’t present in Windows - hence the name “unix way” :slight_smile:
…but it’s an intresting and dirty hack

I wonder how one would realize abortable nested loops… another thing to experiment.

not iterative but recursive modal operator (shouldn’t be hard do to it iterative):

import bpy

bpy.data.scenes['Scene']['rec'] = 0

class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    def modal(self, context, event):
        if event.type == 'LEFTMOUSE':
            if event.value == 'RELEASE':
                bpy.data.scenes['Scene']['rec'] = bpy.data.scenes['Scene']['rec'] + 1
                print(bpy.data.scenes['Scene']['rec'])
                bpy.ops.object.modal_operator('INVOKE_DEFAULT')
            return {'RUNNING_MODAL'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            if event.value == 'RELEASE':
                bpy.data.scenes['Scene']['rec'] = bpy.data.scenes['Scene']['rec'] - 1
                print(bpy.data.scenes['Scene']['rec'])
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        if context.object:
            context.window_manager.modal_handler_add(self)

            return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)


if __name__ == "__main__":
    register()

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

I am still having problems with this.

  1. It seems that the modal operator only updates on an event.
  2. I am having problems initializing the loop parameters.

What I want to have is a triple nested “for-loop”, which is running by itself but abortable with ‘ESC’


stop = False
for u in range(u_range):
  if stop : break
  for v in range(v_range):
    if stop : break
    for w in range(w_range):
      if event == 'ESC' : stop = True
      if stop : break
      print(u)
      print(v)
      print(w)

For what I get, if I do this with a modal operator the loops can’t be done with “for”, they have to be done with “u += 1”
The problem is, I have to assign u=0 somewhere, and can’t do it inside the modal operatior obviously.
The nesting is the next problem, unless I use ridiculously complex “if-conditions” I’d have to call a modal operator for each “loop-variable” inside each modal operator.

I’ve got to bpy.ops.render.render(write_still=True) inside that triplenested loop.
Does anyone know before I try if the ops handles event?
Or if i can make an event-catcher in render_pre or render_post and use it to abort the loop?

Just thought I would say thanks. Never learned to stop a never ending loop in the terminal.

I would just quit terminal. LOL

Could you not just throw an exception and catch it?

Leaves the question how to constantly watch the ESC key from within a triple nested for loop in Blender to throw anything - which is the actual problem.

I got it somewhat working now. The current problem is how to replace a triple nested for loop, which can also be double nested or a single loop, depending on what’s tured on with a, so to say, while loop with modulo operations, as the modal operator is very similar to a while loop.

Not so trivial.

I think I have mentioned this before. If you make your script an actual render engine you can do what you want, press ESC while rendering. The problem is you are trying to do this inside a Tools context. This may sound blunt, but the Tools context is not worth spending time on programming.

What you are looking for is test_break.

Even if you sub-render within the render event you still should be able to evaluate test_break after each loop iteration.