# How to show to the user a progression in a script?

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?

• Oct 7 '13 at 12:56

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()

• 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? Oct 11 '13 at 13:53
• @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. Mar 12 '14 at 20:04
• 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. Sep 15 '20 at 10:16
• 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:

• 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:

## 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)
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

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())

#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

• Or via modal timer operator. blender.stackexchange.com/a/47398/15543 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
– DB3D
Jul 27 '21 at 23:44

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

• Two of your links are dead. Can you elaborate with some code? Apr 20 '15 at 0:34