10
$\begingroup$

By default a long running script will lock Blender, and using threads isn't currently well supported.

How can a Python script run, (that has access to the Scene data bpy.data.*) for long time, in a way that allows the user to cancel? (for example).


Note: sub-processes are fine, but require data to be passed between processes,
see this similar question for the sub-process case.

$\endgroup$
6
$\begingroup$

For a process like baking a number of frequencies from an audio file, I use the modal timer operator. Eg If I have a long audio file and it takes 5mins to bake one frequency out of a set of 16, I bake one then have a small wait time to allow the handler to catch an escape key which throws up a menu to cancel (and clean up) or continue, rather than have to wait 16x5 or kill blender. It doesn't however let me cancel amid a bake.

For convenience I have used the BakeSoundPanel Panel class to store data on the status of the bake.

enter image description here

class SD_ContinueBakeOperator(Operator):
    """Continue Baking"""
    bl_idname = "sounddrivers.continue_baking"
    bl_label = "Continue"


    def execute(self, context):
        BakeSoundPanel.wait = 2
        BakeSoundPanel.cancel_baking = False
        return {'FINISHED'}

class SD_CancelBakeOperator(Operator):
    """Cancel Baking"""
    bl_idname = "sounddrivers.cancel_baking"
    bl_label = "Cancel"


    def execute(self, context):
        BakeSoundPanel.cancel_baking = True
        return {'FINISHED'}

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

    def modal(self, context, event):

        def confirm_cancel(self, context):
            layout = self.layout
            layout.operator("sounddrivers.cancel_baking")
            layout.operator("sounddrivers.continue_baking")

        if BakeSoundPanel.cancel_baking:
            self.clean()
            return self.cancel(context)

        if event.type == 'ESC' or not BakeSoundPanel.baking:
            context.window_manager.popup_menu(confirm_cancel, title="Baking", icon='SOUND')
            BakeSoundPanel.wait = 1000000
            return {'PASS_THROUGH'}
            self.clean()
            return self.cancel(context)

        if BakeSoundPanel.wait > 0:
            BakeSoundPanel.wait -= 1
            return {'PASS_THROUGH'}

        if event.type == 'TIMER':
            BakeSoundPanel.baking = True
            # bake a sound, when finished set self.wait to some "wait time"
            # to enable a cancel

        return {'PASS_THROUGH'}
$\endgroup$
1
$\begingroup$

You can try an approach that is similar to how Javascript works on Web pages. Web pages have no processes (there are web workers, but lets not talk about them).

You can try and split your script's execution into small portions (quants). Each quant can be executed by a timer. When all your quants are executed, you stop the timer. For example:

def my_handler(scene): 
    val = scene.objects['Cube'].location.x      
    scene.objects['Cube'].location.x = val + 0.1 
def register(): 
    bpy.app.handlers.frame_change_post.append(my_handler) 
def unregister(): 
    bpy.app.handlers.frame_change_post.remove(my_handler)

This script will make your Cube fly away, after you call "register()". The UI won't be blocked because the Quant in my_handler does not take much time to execute, and its execution is bound to the frame_change timer that is controlled by Blender.

$\endgroup$
  • $\begingroup$ This assumes my_handler is short running and can incrementally perform a task. There is still the problem that the function may take a long time to complete. $\endgroup$ – ideasman42 Feb 18 '16 at 12:18
  • $\begingroup$ I didn't know of Fibres, possibly this could be its own answer? $\endgroup$ – ideasman42 Feb 18 '16 at 12:37
  • $\begingroup$ Fibers, also known as greenlets, can provide you means to organize your code into tasks that can be scheduled by your own code (that is, paused, switched and resumed). I think this link has some better explanation: aigamedev.com/open/article/round-robin-multi-tasking $\endgroup$ – Dmitry Pryadkin Feb 18 '16 at 12:43
  • $\begingroup$ I'm familiar with greenlets but as far as I know the current implementations don't work when Python is embedded inside another application, Did you manage to use these successfully with Blender? $\endgroup$ – ideasman42 Feb 24 '16 at 10:44
  • $\begingroup$ I personally never tried them in Blender's Python. In the nearest feature I'm going to try. $\endgroup$ – Dmitry Pryadkin Feb 24 '16 at 11:51
0
$\begingroup$

spawn a process, then hand the data required to it, however if it's operating on anything blender is using things could get ugly, best to pass all the data to the remote process, (I was thinking of pickle myself to pass a mesh from bge to bpy)

but you may know more about using the python networking then me,

once the operation is completed, replace the data in blender with the data generated by the sub process?

I think Agoose could help here.

$\endgroup$

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.