26

I'm making an python script which create a lot of object and this could take a lot of time. My question is how to show to the user of my script, the "progression" of the script? For now I print an progress bar in the system console.

But you have to open the system console befor and (apparently) the system console only work with Windows.

Is there an other way to show the progression of my script with python like control the mouse percentage icon (the little icon that replace the mouse while baking) or another idea?

lucblender
  • 3,333
  • 3
  • 25
  • 36

8 Answers8

29
  • Progress of script can also be printed and updated with sys module to the console:

    import sys
    from time import sleep
    
    sys.stdout.write("Some job description: ")
    sys.stdout.flush()
    some_list = [0] * 100
    for idx, item in enumerate(some_list):
        msg = "item %i of %i" % (idx, len(some_list)-1)
        sys.stdout.write(msg + chr(8) * len(msg))
        sys.stdout.flush()
        sleep(0.02)
    
    sys.stdout.write("DONE" + " "*len(msg)+"\n")
    sys.stdout.flush()
    

    This can look like this:

    enter image description here

  • You can also copy paste this next function to display a progress bar:

    import time, sys
    
    def update_progress(job_title, progress):
        length = 20 # modify this to change the length
        block = int(round(length*progress))
        msg = "\r{0}: [{1}] {2}%".format(job_title, "#"*block + "-"*(length-block), round(progress*100, 2))
        if progress >= 1: msg += " DONE\r\n"
        sys.stdout.write(msg)
        sys.stdout.flush()
    
    # Test
    for i in range(100):
        time.sleep(0.1)
        update_progress("Some job", i/100.0)
    update_progress("Some job", 1)
    

    This results in:

    enter image description here

Jaroslav Jerryno Novotny
  • 51,077
  • 7
  • 129
  • 218
18

Currently the only (simple) way to do this is to show the progress with the mouse cursor.

import bpy
wm = bpy.context.window_manager

# progress from [0 - 1000]
tot = 1000
wm.progress_begin(0, tot)
for i in range(tot):
    wm.progress_update(i)
wm.progress_end()
ideasman42
  • 47,387
  • 10
  • 141
  • 223
  • Thank's for your answer. You say "the only (simple) way". Does this means that you know other (hard) ways to do it? And of course other than those indicated in other answer? – lucblender Oct 11 '13 at 13:53
  • 2
    @lucblender with python a lot of hacks are possible, like calling into operating system specific progress bars which display in the window list, or writing a modal operator that draws text in the 3d view with a timer, its possible but way overkill in almost all cases. – ideasman42 Mar 12 '14 at 20:04
  • 1
    FWIW, on Windows (tested with 2.83), if the script takes too long (about 5+ seconds), as the UI does not 'respond', the cursor is replaced by the standard Windows spinning cursor - thus hiding the progress info. – rotoglup Sep 15 '20 at 10:16
10

Research over this subject :

I have this but it seems that working in modal do not update the gui correctly between the tasks and it might freeze the interface anyway

so this should work in theory, but in practice it seems that there's few flaws because on how blender handle the freezing process. (that perhaps could be resolved by using bpy.app.timers ?)

This technique consist of segmenting your code into multiple function, that you store in a dictionary of operations, then a modal operator will loop over your operations while updating the progress bar depending on you steps achievement.

import bpy
import time

#Operation Dict #operation will need to be divided in multiple steps

sleep = 0

def _00(): print("f00") time.sleep(sleep)

def _01(): print("f01") time.sleep(sleep)

def _02(): print("f02") time.sleep(sleep)

def _03(): print("f03") time.sleep(sleep)

def _04(): print("f04") time.sleep(sleep)

Operations = { 0:_00, 1:_01, 2:_02, 3:_03, 4:_04,
}

#Modal Operator

class EXAMPLE_OT_modal_operator(bpy.types.Operator):

bl_idname = "example.modal_operator"
bl_label = "Modal Operator"

step : bpy.props.IntProperty()


def modal(self, context, event):

    global Operations
    max_step = len(Operations.keys())

    #Update Gui
    context.object.progress = self.step/max_step*100
    context.area.tag_redraw()
    context.area.tag_redraw()

    #Running Operations Steps In Order 
    if self.step < max_step:
        print(f"Operations[{self.step}]()")
        Operations[self.step]()
        self.step += 1
        return {'RUNNING_MODAL'}

    #No More steps, Restore and Done
    print("Finished")
    self.step = 0
    context.object.progress = 0
    return {'FINISHED'}


def invoke(self, context, event):
    print("Invoke")
    #self.execute(context)
    context.window_manager.modal_handler_add(self)
    return {'RUNNING_MODAL'}



#Panel

class EXAMPLE_PT_panel(bpy.types.Panel):

bl_idname      = "EXAMPLE_PT_panel"
bl_label       = "Example"
bl_category    = "Example"
bl_space_type  = "VIEW_3D"
bl_region_type = "UI"
bl_context     = "objectmode"

def draw(self, context):
    layout = self.layout
    obj = context.object

    if obj.progress: 
        layout.prop(bpy.context.object,"progress")
    else:
        ope = layout.row()
        ope.operator_context = "INVOKE_DEFAULT"
        ope.operator("example.modal_operator",text="Run Modal Operator")

    return 


#Registering Stuff...

bpy.types.Object.progress = bpy.props.FloatProperty( name="Progress", subtype="PERCENTAGE", soft_min=0, soft_max=100, precision=0, ) bpy.utils.register_class(EXAMPLE_PT_panel) bpy.utils.register_class(EXAMPLE_OT_modal_operator)

Here is another test, same principle but this time using delayed function hoping that the delay between the operations might be enough to let blender "breath" so we can get a glimpse of the interface. it seems that this attempt is also failing and that blender interface simply like to keep in freeze state even if is is not supposed to

  • note that this technique might be limiting as execution context within a delay function is highly limiting

Not sure if my research are of any help. I'll try to explore more solutions sooner or later


import bpy
import time
import functools

#Operation Dict #your operation work steps should divided into multiple function

sleep = 0

def _00(): print("f00") time.sleep(sleep)

def _01(): print("f01") time.sleep(sleep)

def _02(): print("f02") time.sleep(sleep)

def _03(): print("f03") time.sleep(sleep)

def _04(): print("f04") time.sleep(sleep)

#then you store then all in a dict like this Operations = { 0:_00, 1:_01, 2:_02, 3:_03, 4:_04,
}

#We need a function that refresh all areas #we need this because we can't access context.area from a timer function

def refresh_all_areas(): for wm in bpy.data.window_managers: for w in wm.windows: for area in w.screen.areas: area.tag_redraw()

#Then we run all steps of our Operation Dict in this operator #the goal of this operator is to run the function from timers #We need to run these from timers otherwise the interface might be locked and freeze when calculating

class EXAMPLE_OT_timer_operator(bpy.types.Operator):

bl_idname = "example.timer_operator"
bl_label = "Timer Operator"

interval : bpy.props.FloatProperty(default=0.1)

def execute(self, context):

    global Operations
    max_step = len(Operations.keys())

    for i,(step,operation) in enumerate(Operations.items()):
        i+=1

        def generate_delay_fct(stp,):
            """fct factory needed otherwise same function for all timer"""

            def fct():
                """delay is needed, so interface don't freeze and we have time to display progress bar"""

                print("execute_op_with_delay ->"+str(stp))
                Operations[stp]()

                #Update Gui
                bpy.context.object.progress = (stp/max_step)*100
                refresh_all_areas()
                return None 
            return fct

        bpy.app.timers.register( generate_delay_fct(step,), first_interval=self.interval*i,)
        continue 


    def last_step():
        """one last function is required in orter to restore the progress bar to 0 once all operations are done"""
        bpy.context.object.progress = 0
        refresh_all_areas()
        return None

    bpy.app.timers.register( last_step, first_interval=self.interval*(i+1),)

    return {'FINISHED'}

#Panel

class EXAMPLE_PT_panel(bpy.types.Panel):

bl_idname      = "EXAMPLE_PT_panel"
bl_label       = "Example"
bl_category    = "Example"
bl_space_type  = "VIEW_3D"
bl_region_type = "UI"
bl_context     = "objectmode"

def draw(self, context):
    layout = self.layout
    obj = context.object

    if obj.progress: 
        layout.prop(bpy.context.object,"progress")
    else:
        ope = layout.row()
        ope.operator_context = "INVOKE_DEFAULT"
        ope.operator("example.timer_operator",text="Run Timer Operator")

    return 


#Registering Stuff...

bpy.types.Object.progress = bpy.props.FloatProperty( name="Progress", subtype="PERCENTAGE", soft_min=0, soft_max=100, precision=0, )

bpy.utils.register_class(EXAMPLE_PT_panel) bpy.utils.register_class(EXAMPLE_OT_timer_operator)

Solution Found

enter image description here

It seems that the first prototype i did was ok, and the second idea of timers was a good lead, what i needed in the end is an hybrid solution between the two.

by using a timer such as event_timer_add(), we could check for this timer in our events, then have a guaranteed unfreeze at this moment. But that's not enough, we need to extend this timeframe by just a bit! that's why i added a counter timer_count that will ignore the first 10 modal loops

in the end this is working quite fine

import bpy
import time

#Operation Dict #operation will need to be divided in multiple steps

sleep = 1

def _00(): print("f00") time.sleep(sleep)

def _01(): print("f01") time.sleep(sleep)

def _02(): print("f02") time.sleep(sleep)

def _03(): print("f03") time.sleep(sleep)

def _04(): print("f04") time.sleep(sleep)

Operations = { "First Step":_00, "Second Step":_01, "Running Stuff":_02, "Wait a minute":_03, "There's a problem":_04,
"ah no it's ok":_04,
"we are done":_04,
}

#Modal Operator

class EXAMPLE_OT_modal_operator(bpy.types.Operator):

bl_idname = "example.modal_operator"
bl_label = "Modal Operator"

def __init__(self):

    self.step = 0
    self.timer = None
    self.done = False
    self.max_step = None

    self.timer_count = 0 #timer count, need to let a little bit of space between updates otherwise gui will not have time to update

def modal(self, context, event):

    global Operations

    #update progress bar
    if not self.done:
        print(f"Updating: {self.step+1}/{self.max_step}")
        #update progess bar
        context.object.progress = ((self.step+1)/(self.max_step))*100
        #update label
        context.object.progress_label = list(Operations.keys())[self.step]
        #send update signal
        context.area.tag_redraw()


    #by running a timer at the same time of our modal operator
    #we are guaranteed that update is done correctly in the interface

    if event.type == 'TIMER':

        #but wee need a little time off between timers to ensure that blender have time to breath, so we have updated inteface
        self.timer_count +=1
        if self.timer_count==10:
            self.timer_count=0

            if self.done:

                print("Finished")
                self.step = 0
                context.object.progress = 0
                context.window_manager.event_timer_remove(self.timer)
                context.area.tag_redraw()

                return {'FINISHED'}

            if self.step < self.max_step:

                #run step function
                list(Operations.values())[self.step]()

                self.step += 1
                if self.step==self.max_step:
                    self.done=True

                return {'RUNNING_MODAL'}

    return {'RUNNING_MODAL'}

def invoke(self, context, event):

    print("")
    print("Invoke")

    #terermine max step
    global Operations
    if self.max_step == None:
        self.max_step = len(Operations.keys())        

    context.window_manager.modal_handler_add(self)

    #run timer 
    self.timer = context.window_manager.event_timer_add(0.1, window=context.window)

    return {'RUNNING_MODAL'}



#Panel

class EXAMPLE_PT_panel(bpy.types.Panel):

bl_idname      = "EXAMPLE_PT_panel"
bl_label       = "Example"
bl_category    = "Example"
bl_space_type  = "VIEW_3D"
bl_region_type = "UI"
bl_context     = "objectmode"

def draw(self, context):
    layout = self.layout
    obj = context.object

    if obj.progress: 
        progress_bar = layout.row()
        progress_bar.prop(bpy.context.object,"progress")
        progress_lbl = layout.row()
        progress_lbl.active = False
        progress_lbl.label(text=bpy.context.object.progress_label)
    else:
        ope = layout.row()
        ope.operator_context = "INVOKE_DEFAULT"
        ope.operator("example.modal_operator",text="Run Modal Operator")

    return 


#Registering Stuff...

bpy.types.Object.progress = bpy.props.FloatProperty( name="Progress", subtype="PERCENTAGE",soft_min=0, soft_max=100, precision=0,) bpy.types.Object.progress_label = bpy.props.StringProperty()

bpy.utils.register_class(EXAMPLE_PT_panel) bpy.utils.register_class(EXAMPLE_OT_modal_operator)

Prop to sybren from the Grove3d, i did dig a little bit to discover his logic

Fox
  • 1,892
  • 18
  • 48
  • Or via modal timer operator. https://blender.stackexchange.com/a/47398/15543 – batFINGER Jul 27 '21 at 15:13
  • Nice! Thanks for posting, i suggest to make it more obvious and posting it as a solution to this thread. I did a lot of research and never stumble over this solution – Fox Jul 27 '21 at 23:44
3

Could use text to show progress bar anywhere you can have text in the UI:

import bpy
from functools import partial

def progress(area): if progress.i >= 100: area.header_text_set(None) return else: area.header_text_set('Loading ' + progress.bar + " " + str(progress.i) +"%") progress.i += 0.5 progress.bar = "█"int(progress.i)+"░"(100-int(progress.i)) return 0.01

progress.bar = "" progress.i = 0

bpy.app.timers.register(partial(progress, bpy.context.area))

Martynas Žiemys
  • 24,274
  • 2
  • 34
  • 77
3

Blender 4.0 now has a built-in progress bar.

Example usage of Blender progress bar

Usage:

layout.progress(factor = 0.5)
layout.progress(factor = 0.8, type = 'RING', text = "Updating")
  • Currently type can be "RING" or "BAR"
  • Factor is from 0.0 to 1.0

Blender 4.0 Changelog


More information:

Pull request with more info

Here is the Blender commit

Rooster
  • 131
  • 2
3

Solution:

I've found this two solution in the blenderartist forum:

  • Print a text in the 3D view with the blf library
  • Set progress information in the header area of a window via a modal timer
  • In 2.69 an update in the api lets the user change the cursor appearance so we could change the cursor to the "loading" cursor.

What doesn't work:

  • The solution of printing a progress bar in the console only work with windows.

This part of answer may contain errors

HalfKiloByte
  • 558
  • 3
  • 8
lucblender
  • 3,333
  • 3
  • 25
  • 36
2

Here's a minimal working example that displays a progress bar filling in the UI. I used a modal operator and a timer but it can be driven by any property. You'll need Version 4.0 and bpy.types.UILayout.progress. You're kind of obligated to use a modal operator since a regular operator's execution will block updating the UI until it finishes. Note autosave is disabled while a modal operator runs.

enter image description here

import bpy

class ModalTimerOperator(bpy.types.Operator): bl_idname = "wm.modal_timer_operator" bl_label = "Modal Timer Operator"

_timer = None

def modal(self, context, event):
    [a.tag_redraw() for a in context.screen.areas]
    if self._timer.time_duration > 3:
        context.window_manager.progress = 1
        return {'FINISHED'}
    context.window_manager.progress = self._timer.time_duration / 3
    return {'PASS_THROUGH'}

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


def progress_bar(self, context): row = self.layout.row() row.progress( factor=context.window_manager.progress, type="BAR", text="Operation in progress..." if context.window_manager.progress < 1 else "Operation Finished !" ) row.scale_x = 2

def register(): bpy.types.WindowManager.progress = bpy.props.FloatProperty() bpy.utils.register_class(ModalTimerOperator) bpy.types.TEXT_HT_header.append(progress_bar)

if name == "main": register() bpy.ops.wm.modal_timer_operator()

Here's a second technique using a non-modal operator. You'll notice your script takes significantly longer if you update the UI a lot. I suggest doing it sparsingly. Also the cursor flickers a lot, at least on windows.

import bpy

class TimerOperator(bpy.types.Operator): bl_idname = "wm.timer_operator" bl_label = "Timer Operator"

def execute(self, context):
    progress_bar.progress = 0
    for i in range(100):
        progress_bar.progress += 0.01
        bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
        bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
    return {'FINISHED'}


def progress_bar(self, context): row = self.layout.row() row.progress( factor=progress_bar.progress, type="BAR", text="Operation in progress..." if progress_bar.progress < 1 else "Operation Finished !" ) row.scale_x = 2

progress_bar.progress = 0

def register(): bpy.utils.register_class(TimerOperator) bpy.types.TEXT_HT_header.append(progress_bar)

if name == "main": register() bpy.ops.wm.timer_operator()

Gorgious
  • 30,723
  • 2
  • 44
  • 101
  • Is there a way to show progress while subprocess.run(command) is running? I mean there is no way for percentage. Just something like this since the progress depends, sometimes it can take very long so I need feedback to the user that it's in progress. Currently there is no way but looks as if it hangs. That's bad feedback :( – Harry McKenzie Jan 11 '24 at 12:05
  • 1
    @HarryMcKenzie You can, but you need to run it in a separate thread and use a queue to receive updates https://docs.blender.org/api/current/info_gotcha.html#strange-errors-when-using-the-threading-module – Gorgious Jan 11 '24 at 12:31
-1

How to show a progress bar and load the cpu in the background

demo featuring

  • Progress Bar!
  • Responsive GUI
  • Background Thread (long uninterrupted compute time allowed!)
  • Signals (user cancel-able pattern)
  • Memory Safety
  • Developer Comments
  • State Reset (user preferences respected)

tested on 2.82 manually :✔️:

# blender progress bar
# License: AGPL

import bpy import bpy.props

import time import threading

class ThreadExample: def init(self, context): self.thread = threading.Thread(target=self.main) self.tid = None

    # self.state is protected by this lock
    self.lock = threading.Lock()
    self.state = None

    # blender memory is protected by this lock
    self.context_lock = threading.Lock()
    self.context = context

def reset(self):
    self.state = {
        'tid': None,
        'state': None,
        'progress': None,
        'signal': None,
        'rc': None,
    }

def start(self):
    self.reset()
    self.thread.start()

def signal(self):
    &quot;&quot;&quot; react to user signals (0=RUN, 2=INTERRUPT, 15=EXIT)
    &quot;&quot;&quot;
    with self.lock:
        if self.state['signal'] != 0:
            self.state.update(dict(state='DONE', rc=1, signal=None))
    return False

def progress(self, part, total_parts):
    &quot;&quot;&quot; inform blender of our progress
    &quot;&quot;&quot;
    with self.lock:
        self.state.update(dict(state='RUN', progress=part/total_parts))

def main(self):
    &quot;&quot;&quot; your workload here
    &quot;&quot;&quot;
    # get local ro state copy
    state = dict()
    with self.lock:
        state.update(self.state)

    self.enter_()

    with self.context_lock:
        scene_objects = [i.name for i in self.context.scene.objects]
        scene_objects_len = len(scene_objects)

    for idx, obj_name in enumerate(scene_objects):
        # check for user abort regularly, the thread is atomic between self.signal() calls
        # if the user requests quit, blender will hang until we react
        if self.signal(): return

        with self.context_lock:
            # your workload here
            obj = self.context.scene.objects[obj_name]
            print(&quot;%r&quot; % ((self.state['tid'], obj),))

        # report progress
        self.progress(idx, scene_objects_len)
    self.exit_()

def join(self, *args):
    self.thread.join(*args)

def enter_(self):
    tid = threading.get_ident()
    with self.lock:
        self.state.update(dict(tid=tid, state='RUN', progress=0.0, signal=0))

def exit_(self):
    with self.lock:
        self.state.update(dict(state='DONE', progress=1.0))

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

singleton_p = [True]
dirty_timer = None
focus_reset = None

@classmethod
def poll(cls, context):
    return cls.singleton_p[0]

def _enter(self, context):
    bpy.context.window.cursor_set(&quot;WAIT&quot;)
    self.focus_reset = self.focus()
    self.singleton_p[0] = False
    bpy.context.window_manager.modal_handler_add(self)
    self.dirty_timer = bpy.app.timers.register(self.dirty_interval, first_interval=2.0)
    self.thread = ThreadExample(context)
    self.thread.context_lock.__enter__()
    self.thread.start()

def _exit(self):
    bpy.context.window.cursor_set(&quot;DEFAULT&quot;)
    for area, context in self.focus_reset:
        area.spaces.active.context = context
        self.focus_reset = None
    with self.thread.lock:
        self.thread.state['signal'] = 15
    self.singleton_p[0] = True
    if bpy.app.timers.is_registered(self.dirty_timer):
        bpy.app.timers.unregister(self.dirty_timer)

    self.thread.join()
    self.thread.reset()
    self._progress(self.thread.state)

def cancel_(self):
    &quot;&quot;&quot; request thread exit
    &quot;&quot;&quot;
    with self.thread.lock:
        self.thread.state['signal'] = 2

    self._exit()
    self.report({'WARNING'}, &quot;operation stopping ...&quot;)
    return {'CANCELLED'}

def finish_(self):
    &quot;&quot;&quot; thread finished
    &quot;&quot;&quot;
    self._exit()
    self.thread.state['progress'] = 1.0
    self._progress(self.thread.state)
    return {'FINISHED'}

def _progress(self, state):
    &quot;&quot;&quot; copy progress from thread to blender
    &quot;&quot;&quot;
    if state['progress'] is not None:
        bpy.context.scene['modal_progress'] = state['progress']
    else:
        del bpy.context.scene['modal_progress']

def dirty_interval(self):
    &quot;&quot;&quot; blender only redraws if something happened, the thread doesn't count
        so we artificially &quot;do something&quot; regularly, so the progress bar
        will get redrawn
    &quot;&quot;&quot;
    try:
        state = dict()
        with self.thread.lock:
            state.update(self.thread.state)

        self._progress(state)
        self.dirty()

        return 1.0/12 # fps

    except ReferenceError:
        # modal finished, &quot;self&quot; is deallocated
        pass

    return None

@staticmethod
def get_screens():
    screens = bpy.data.screens
    if bpy.context.screen:
        screens = [bpy.context.screen]
    return screens

@staticmethod
def dirty(areas={'PROPERTIES'}, regions={'WINDOW'}):
    &quot;&quot;&quot; progress bar is drawn as a scene custom property
    &quot;&quot;&quot;
    for screen in ModalOperator.get_screens():
        for area in screen.areas:
            for region in area.regions:
                if area.type in areas and region.type in regions:
                    region.tag_redraw()

def focus(context='SCENE'):
    &quot;&quot;&quot; try to make sure the progress bar is on screen
    &quot;&quot;&quot;
    ret = []
    for screen in ModalOperator.get_screens():
        for area in screen.areas:
            if area.type != 'PROPERTIES': continue

            ret.append((area, area.spaces.active.context))
            area.spaces.active.context = 'SCENE'
    return ret


def modal(self, context, event):
    # get read-only copy of thread state
    state = dict()
    with self.thread.lock:
        state.update(self.thread.state)

    # redraw progress indicator
    self.dirty()

    # check if user abort requested
    cancel = event.type in {'ESC'} #, 'RIGHTMOUSE'}
    if cancel:
        return self.cancel_()

    # check if thread finished
    if state['state'] == 'DONE':
        return self.finish_()

    assert self.thread.state['state'] == 'RUN', 'unhandled thread state %r' % self.thread.state['state']

    # report progress from thread to blender
    self._progress(state)

    # dedicated time for thread to read blender state
    self.thread.context_lock.__exit__()
    time.sleep(1/24)  # gui min 24 fps - threat read time
    self.thread.context_lock.__enter__()

    return {'RUNNING_MODAL'}

def invoke(self, context, event):
    self._enter(context)
    return {'RUNNING_MODAL'}

class ProgressPanel(bpy.types.Panel): """Progress Bar""" bl_label = "Progress Panel" bl_idname = "SCENE_PT_layout" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "scene"

# try to make sure the progress bar is on screen
bl_options = {'HIDE_HEADER'}

def draw(self, context):
    layout = self.layout

    if context.scene.modal_progress &lt; 1.0:
        text = &quot;Progress: %3.2f%% (ESC to cancel)&quot; % (context.scene.modal_progress*100.0)
    else:
        text = &quot;Progress: Done&quot;
    layout.label(text=text)

    row = layout.row()
    row.prop(context.scene, &quot;modal_progress&quot;)

def register(): bpy.utils.register_class(ModalOperator) bpy.utils.register_class(ProgressPanel) bpy.types.Scene.modal_progress = bpy.props.FloatProperty(name="Modal Progress", min=0.0, max=1.0, subtype='FACTOR')

def unregister(): bpy.utils.unregister_class(ModalOperator) bpy.utils.unregister_class(ProgressPanel) del bpy.types.Scene.modal_progress

if name == "main": register() # test call bpy.ops.object.modal_operator('INVOKE_DEFAULT')

Mirror opencode.net: https://www.opencode.net/-/snippets/3378 Mirror archive.org: https://web.archive.org/web/20230705035959/https://www.opencode.net/-/snippets/3378/raw/main/blender_progress_bar.py


well, the progress state/status reporting is working, drawing the progressbar is trickier, possible future work

  • create a progress window (seems impossible currently)
  • to ensure progress bar in on screen, or reset the current window to a specific predictable gui element (possible, but, too invasive)
  • drawing a progress bar, with import gpu overtop of some existing ui element
  • find a way to place gui elements more concretely (with many plugins installs, this progress could be pushed off screen)
ThorSummoner
  • 344
  • 3
  • 14
  • This doesn't work in 3.6.0 it just freezes until you press escape. Leaves a useless property in the Scene tab, Properties Editor... – Martynas Žiemys Jul 05 '23 at 06:39
  • Could simply use something like this – Martynas Žiemys Jul 05 '23 at 07:14
  • hmm, i just tried it on blender 3.6.0 (Linux) and has no issues, seemed to work as intended. you might be talking about blender's Modal mode where it doesn't let you intact with the UI while a modal operator is running, you can still however interrupt the background job by pressing escape, or bind more hotkeys if you like. i think that is what you are talking about @MartynasŽiemys - this method allows the ui to be updated/painted while the thread is running – ThorSummoner Jul 08 '23 at 21:56
  • hmm, i'll think on doing a non-modal version, that will break blender's undo feature, but some people might like to trade "blender being usable" for complicating their background blender<->thread edge cases/synchronization – ThorSummoner Jul 08 '23 at 22:00