Viewport not updating when looping in python script.

Hi guys,
I have a bit of a problem. I have a script that advances the frame number, does some stuff (like a project from view in the viewport, baking a texture, etc). The script works well when I run it many times manually, but when I put a loop in the script, the viewport isn’t updating. So my question is, how can I simply force the view to update in my script. I googled it already and found suggestions like using the frame_change handers, but that seems like doing it the wrong way around to me. Any other suggestions?
Thanks!

Frame Change Handlers do work. Play the timeline to execute your loop.

But there is also a mechanism called a Modal Timer. An example script resides under the Templates menu of the text editor.
You could put the inner loop code inside the timer event.

Remember, python for Blender is a single core operation. Your code runs, then the system updates itself.

Thans for your quick answer. It makes sense to me now. If I use the frame change handlers, I would need to make sure that the timeline plays only once and doesn’t loop. How can I do that?

You can check Scene.frame_current in a frame_change handler and abort the playback if the frame number is equal to frame_end, but a modal timer is much more appropriate - the user could interfere with the playback, but he can’t if you use a modal operator that returns RUNNING_MODAL in its modal() method:

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
    frame_current = 0
    frame_prev = 0

    def modal(self, context, event):
        if (event.type in {'RIGHTMOUSE', 'ESC'} or
               self.frame_current > context.scene.frame_end):
            return self.cancel(context)

        if event.type == 'TIMER':
            context.scene.frame_set(self.frame_current)
            self.frame_current += 1
            
            # some action
            context.object.location.z += 0.1

        return {'RUNNING_MODAL'}

    def execute(self, context):
        wm = context.window_manager
        sce = context.scene
        
        self.frame_prev = sce.frame_current
        self.frame_current = sce.frame_start
        
        self._timer = wm.event_timer_add(0.3, context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)
        print(self.frame_prev)
        context.scene.frame_set(self.frame_prev)
        return {'FINISHED'}

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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()


Sorry I didn’t come back to this in such a while. Just an update. I finally had some time to look at this, but I guess I’m not doing it right, as I get a ton of crashes. I will just have to be a bit more careful and check some more stuff. My script at the moment is really sloppy. I don’t do any error checking. I guess I will now have to. I will be back when I figure out why it is crashing exactly.

Ah! I figured it out already. I actually did two things wrong

  1. I had the indentation level of my own code wrong, so that ran every time modal() was called instead of every time event.type was “TIMER”
  2. After I fixed that, it still crashed, but that problem stopped when I increased the delay in event_timer_add from 0.3 seconds to 5.0 seconds.
    Apparently, something goes very wrong when the routine gets called when the previous call didn’t finish yet. Is there a standard way to check this, or should
    I just write a flag somewhere to check whether the last call is finished.

Thanks for all the help, I’m really getting somewhere now!

AFAIK, modal() is not called again until a previous call returned? But could be wrong… maybe an issue to set scene_frame in a modal timer op… Can you provide a test script?

My script is not a secret, so I can just post the actual thing. I can’t figure out how to post code while maintainging the indentation levels, so they are screwed up. Sorry for that.

It is not very flexible at the moment, it assume you have an object called Walls which has a mesh Cube.002.
Most importantly, it is textured with a movie using project from view, the corresponding UVmap is called projected
in the script. It is also textured with a blank image using an unwrapping. The scripts than runs through the timeline, bakes the projected texture to the unwrapped one for each time and saves the unwrapped texture to a file. I am open to all comments. I know that I’m an absolute ass when it comes to checking error messages and such. This is because my python scripts usually do much more trivial things, like open same data and make a plot. But I would really like to learn how one should do these things!

script work when there are two texture in the uv image editor. One is the movie and

it is associated with a uvmap that has been projected from view. The other one is

an empty texture associated with a uvmap that has been regularly unwrapped. This last

one is the one we will bake to.

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
     frame_current = 0
     frame_prev = 0

     def modal(self, context, event):
             if (event.type in {'RIGHTMOUSE', 'ESC'} or
                        self.frame_current > context.scene.frame_end):
                     return self.cancel(context)

            if event.type == 'TIMER':
        context.scene.frame_set(self.frame_current)
        self.frame_current += 1
        
        
       print("frame number is now:",bpy.data.scenes["Scene"].frame_current)


                    bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
                    object_wall=bpy.context.scene.objects["Walls"]
                    bpy.context.scene.objects.active=object_wall

                   uvmap_projected=bpy.data.meshes["Cube.002"].uv_textures["projected"]
                   uvmap_unwrapped=bpy.data.meshes["Cube.002"].uv_textures["unwrapped"]

                   # set active texture to projected and project from view
                  bpy.data.meshes["Cube.002"].uv_textures.active=uvmap_projected

                  # to get the right context for project_from_view, we first need to find the 3D_view
                  filtered=next(filter(lambda x:x.type=="VIEW_3D",bpy.context.screen.areas))

                  # now we need to find the window region
                  window_region=next(filter(lambda x:x.type=="WINDOW",filtered.regions))
                  override=bpy.context.copy()
                  override['area']=filtered
                  override['region']=window_region
                  override['edit_object']=bpy.context.edit_object
                  bpy.ops.uv.project_from_view(override,orthographic=False, camera_bounds=True,correct_aspect=False,        clip_to_bounds=False, scale_to_bounds=False)
        
                  bpy.data.meshes["Cube.002"].uv_textures.active=uvmap_unwrapped  
                  bpy.ops.object.bake_image()
                  bpy.ops.object.mode_set(mode='OBJECT') 

                  filename='//automated_wall_textures_1/wall_texture_baked_frame_'+'{:03d}'.format(bpy.data.scenes["Scene"].frame_current)
                  print(filename)

                  bpy.data.images['wall_texture'].filepath_raw = filename
                  bpy.data.images['wall_texture'].file_format = 'PNG'
                  bpy.data.images['wall_texture'].save()

              return {'RUNNING_MODAL'}

def execute(self, context):
     wm = context.window_manager
             sce = context.scene
    
             self.frame_prev = sce.frame_current
             self.frame_current = sce.frame_start
    
             self._timer = wm.event_timer_add(5.0, context.window)
             wm.modal_handler_add(self)
             return {'RUNNING_MODAL'}

     def cancel(self, context):
     wm = context.window_manager
     wm.event_timer_remove(self._timer)
     print(self.frame_prev)
     context.scene.frame_set(self.frame_prev)
             return {'FINISHED'}

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

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

if name == “main”:
register()

     # test call
bpy.ops.wm.modal_timer_operator()

I think the amount of work you are doing inside the timer event exceeds the timer interval, thus recursion. I get a ‘Circular Reference to texture stack error’ when I run your script.

Do you get that error with the script as is, or only when you shorten the timer interval? With me, the script runs without error like this, but crashes without error messages on the console when I shorten the timer interval. The circular reference to texture stack error are an error I get when I don’t set up the textures like the scripts expects them.