16
$\begingroup$

I have a script, which creates multiple objects through a loop. However, the script freezes the viewport until it is done. Instead, I would like every object to be seen in the viewport one by one as it is created.

In its simplest terms:

import bpy
import random
for i in range(1000):
    XYZcoord = (random.random()*100, random.random()*100, random.random()*100)
    bpy.ops.mesh.primitive_uv_sphere_add(location=XYZcoord)
    # Update the viewport here.

Is there a way to update the viewport after every iteration of the loop to see every newly added object as they are created? Or do I need to use a different method?

$\endgroup$
2
  • 3
    $\begingroup$ bpy.ops.wm.redraw_timer but read this first $\endgroup$
    – Chebhou
    Apr 20, 2015 at 2:09
  • $\begingroup$ if you use bmesh you can update and control the object creation and the show in the 3d view $\endgroup$
    – yhoyo
    Apr 20, 2015 at 3:32

3 Answers 3

16
$\begingroup$

Text Editor > Python > Templates > Operator Modal Timer

I quickly cobbled together a small edit to that template, which I think does what you want. Run it from Text Editor, read the code you don't understand slowly and follow the flow of control carefully. There is no need to manually redraw.

import bpy
import random

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

    limits : bpy.props.IntProperty(default=0) #not 'limits ='
    _timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'} or self.limits > 30:
            self.limits = 0
            self.cancel(context)
            return {'FINISHED'}

        if event.type == 'TIMER':
            XYZcoord = (random.random()*100, random.random()*100, random.random()*100)
            bpy.ops.mesh.primitive_uv_sphere_add(location=XYZcoord)
            self.limits += 1

        return {'PASS_THROUGH'}

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

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


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()
$\endgroup$
1
  • $\begingroup$ I try to run the operator like bpy.ops.wm.modal_timer_operator().limits=1 but I get the message limits attribute not existing. what I'm doing wrong? $\endgroup$ Aug 3, 2021 at 13:51
22
$\begingroup$

The UI (including viewport ) can be updated during the script execution but it is advised against it in the Blender API documentation for these reasons :

Tools that lock Blender in a loop and redraw are highly discouraged since they conflict with Blenders ability to run multiple operators at once and update different parts of the interface as the tool runs.

so to update the UI during execution you can use wm.redraw_timer() :

import bpy
import random
for i in range(1000):
    XYZcoord = (random.random()*100, random.random()*100, random.random()*100)
    bpy.ops.mesh.primitive_uv_sphere_add(location=XYZcoord)
    # Update the viewport here.
    bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

a good alternative has been suggested by @Zeffii which is the modal operator triggered by timer

$\endgroup$
1
  • 2
    $\begingroup$ Thank you! This hack works great for a quick solution and will be sufficient in many cases. However, I have accepted the answer by @zeffii, as it probably will suit more people. $\endgroup$
    – dwitvliet
    Apr 21, 2015 at 2:50
2
$\begingroup$

bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) used to work in older versions of Blender, but as of Blender 3.X it doesn't do the trick any more. Through trial and error I found a sequence of operations that does the trick as of Blender 3.1.2, packaged into a simple function:

def Update3DViewPorts():
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            area.tag_redraw()
    bpy.ops.wm.save_mainfile()
$\endgroup$
1
  • $\begingroup$ I can't reproduce the problem you found in Blender 3.X. On Blender 3.2 in a simple script bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) worked perfectly (though admittedly it was very simple, just moving the camera around). However, it did update the interface as expected. $\endgroup$ Jul 1 at 23:19

You must log in to answer this question.

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