11
$\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$
  • 3
    $\begingroup$ bpy.ops.wm.redraw_timer but read this first $\endgroup$ – Chebhou Apr 20 '15 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 '15 at 3:32
11
$\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)
    _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$
13
$\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$
  • 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$ – Banana Apr 21 '15 at 2:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.