Use the progress bar in a Python script

I have written a python script that exports thousands of objects to ai files and it takes a few minutes. I’d like to use the progress bar in my script, but I can’t quite seem to get it to work. Would anyone have a small example script of how I can show progress of a python script? Thanks.

Hi @dustin,

Give this a whirl:

import Rhino
import scriptcontext as sc
import System

def test_progressbar():
    sn = sc.doc.RuntimeSerialNumber
    min = 0
    max = 100
    percent = False
    Rhino.UI.StatusBar.ShowProgressMeter(sn, min, max, "Starting", True, percent)
    for i in range(min, max + 1):
        Rhino.RhinoApp.Wait()
        System.Threading.Thread.Sleep(100)
        if i == 10:
            Rhino.UI.StatusBar.UpdateProgressMeter("Calculating", i, True)
        elif i == 50:
            Rhino.UI.StatusBar.UpdateProgressMeter("Processing", i, True)
        elif i == 90:
            Rhino.UI.StatusBar.UpdateProgressMeter("Finishing", i, True)
        else:
            Rhino.UI.StatusBar.UpdateProgressMeter(i, True)
    System.Threading.Thread.Sleep(750) #ms
    Rhino.UI.StatusBar.HideProgressMeter(sn)

if __name__ == "__main__":
    test_progressbar()

– Dale

4 Likes

I will post my entire script here, maybe someone can tell me why this doesn’t seem to work.
It does not error out, but I just don’t see any progress bar. This is too much info, but I think it might provide a clue. Thanks.

import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import re
import os
from fractions import Fraction

_nsre = re.compile('([0-9]+)')
def natural_sort_key(s):
    return [int(text) if text.isdigit() else text.lower()
        for text in re.split(_nsre, s)]   

def test():
    categories = {}

    max = 0
    ids = rs.AllObjects()
    for id in ids:
        if rs.IsText(id):
            text = rs.TextObjectText(id)
            idx = text.find("-")
            if idx != -1:
                name = text[:idx-1]
                rs.ObjectName(id, name)
                cat = rs.GetUserText(id, 'Cat')
                subcat = rs.GetUserText(id, 'SubCat')
                max = max + 1
                if not cat:
                    rs.MessageBox(text + ' does not have a Cat tag.')
                if not subcat:
                    subcat = '---'
                if cat:
                    if not categories.has_key(cat):
                        categories[cat] = {}
                    if not categories[cat].has_key(subcat):
                        categories[cat][subcat] = [id]
                    else:
                        categories[cat][subcat].append(id)

    Rhino.UI.StatusBar.ShowProgressMeter(sc.doc.RuntimeSerialNumber, 0, max, "Processing", True, True)

    count = 0
    csv_txt = 'Category,Subcategory,Order,Filename,Profile Name,Height,Width\n';    

    rs.EnableRedraw(True)
    export_root = rs.WorkingFolder() + "/exports"

    # loop over each category
    for cat in categories:
        for subcat in categories[cat]:
            ids = categories[cat][subcat]
            if not ids: return

            dict = {}
            distances = []
   
            names = set([rs.ObjectName(id) for id in ids])
            for name in names:
                # parse the text object text to get the long dimension then sort by that number
                ids = rs.ObjectsByName(name)
                if not ids: continue
                if len(ids) != 2: continue
                bb = rs.BoundingBox(ids)
                for id in ids:
                    if rs.IsText(id):
                        text = rs.TextObjectText(id).splitlines()[0]
                        # get the last element in the split array, don't read the last character either
                        widthText = text.split("x ")[-1][:-1]
                        dist = float(sum(Fraction(s) for s in widthText.split()))
                if dict.has_key(dist):
                    temp = dict[dist]
                    temp = temp + "," + name
                else:
                    temp = name
                dict[dist] = temp
    
            partList = sorted(dict.keys())

            # for now, we just prompt the user for a category and subcategory name, later we can put these in the attributes on each text object instead
            export_folder = export_root

            units = rs.UnitSystemName()
            for item in partList:
                test = dict[item]
                names = dict[item].split(",")
                names.sort(key=natural_sort_key)
                for name in names:
                    ids = rs.ObjectsByName(name)
                    fileName = str(count) + "_" + name + ".ai"
                    if not ids: continue
                    if len(ids) != 2: continue
                    bb = rs.BoundingBox(ids)
                    vecDir  = Rhino.Geometry.Point3d(0,0,0) - bb[0]
                    rs.MoveObjects(ids, vecDir)
                    rs.UnselectAllObjects()
                    rs.SelectObjects(ids)

                    if not os.path.exists(export_folder):
                        os.makedirs(export_folder)
                    rs.Command("-Export " + chr(34) + export_folder + "/" + fileName  + chr(34) + " PreserveUnits=Yes  AIScale=1  Unit=" + units + "  RhinoScale=1  Enter")
                    vecDir.Reverse()
                    rs.MoveObjects(ids, vecDir)
                    count = count + 1
                    w = h = 0
                    # we need to check the 2 objects to find out which is the text label
                    for id in ids:
                        if rs.IsText(id):
                            # we found the text so let's break out the width and height values
                            text = rs.TextObjectText(id)
                            idx = text.find(" - ")
                            if idx != -1:
                                dimension_str = text[idx+3:]
                                w = dimension_str.split(" x ")[1][:-1]
                                h = dimension_str.split(" x ")[0][:-1]
                    csv_txt += cat + ',' + subcat + ',' + str(count) + ',"' + fileName + '","' + name + '",' + str(h) + ',' + str(w) + '\n'

            Rhino.UI.StatusBar.UpdateProgressMeter("Processing", count, True)
#     Rhino.UI.StatusBar.UpdateProgressMeter(count, True)

    rs.EnableRedraw(True)

    Rhino.UI.StatusBar.HideProgressMeter(sc.doc.RuntimeSerialNumber)

    # write out the CSV string to the target CSV file
    file = open(export_root + "/indesign_data.csv", "w") 
    file.write(csv_txt)
    file.close()

if __name__ == "__main__":
    test()
1 Like

@dustin This is a long script and is supposed to pull data from a rhino model that I don’t have. Would you mind sharing the model so I can run the script?

Here are some changes I made to make it work but I do not have a model to test:

import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import re
import os
import os.path as op
from fractions import Fraction

_nsre = re.compile('([0-9]+)')
def natural_sort_key(s):
    return [int(text) if text.isdigit() else text.lower()
        for text in re.split(_nsre, s)]   

def test():
    categories = {}

    max = 0
    ids = rs.AllObjects()
    for obj_id in ids:
        if rs.IsText(obj_id):
            text = rs.TextObjectText(obj_id)
            idx = text.find("-")
            if idx != -1:
                name = text[:idx-1]
                rs.ObjectName(obj_id, name)
                cat = rs.GetUserText(obj_id, 'Cat')
                subcat = rs.GetUserText(obj_id, 'SubCat')
                max = max + 1
                if not cat:
                    rs.MessageBox(text + ' does not have a Cat tag.')
                if not subcat:
                    subcat = '---'
                if cat:
                    if cat not in categories:
                        categories[cat] = {}
                    if subcat not in categories[cat]:
                        categories[cat][subcat] = [obj_id]
                    else:
                        categories[cat][subcat].append(obj_id)

    Rhino.UI.StatusBar.ShowProgressMeter(sc.doc.RuntimeSerialNumber, 0, max, "Processing", True, True)

    count = 0
    csv_txt = 'Category,Subcategory,Order,Filename,Profile Name,Height,Width\n';    

    rs.EnableRedraw(False)
    export_dir = op.dirname(__file__)
    export_root = op.join(export_dir, "exports")
    if not op.exists(export_root):
        os.makedirs(export_root)

    # loop over each category
    for cat in categories:
        for subcat in categories[cat]:
            ids = categories[cat][subcat]
            if not ids: return

            dict = {}
            distances = []
   
            names = set([rs.ObjectName(obj_id) for obj_id in ids])
            for name in names:
                # parse the text object text to get the long dimension then sort by that number
                ids = rs.ObjectsByName(name)
                if not ids: continue
                if len(ids) != 2: continue
                bb = rs.BoundingBox(ids)
                for obj_id in ids:
                    if rs.IsText(obj_id):
                        text = rs.TextObjectText(obj_id).splitlines()[0]
                        # get the last element in the split array, don't read the last character either
                        widthText = text.split("x ")[-1][:-1]
                        dist = float(sum(Fraction(s) for s in widthText.split()))
                if dict.has_key(dist):
                    temp = dict[dist]
                    temp = temp + "," + name
                else:
                    temp = name
                dict[dist] = temp
    
            partList = sorted(dict.keys())

            # for now, we just prompt the user for a category and subcategory name, later we can put these in the attributes on each text object instead
            export_folder = export_root

            units = rs.UnitSystemName()
            for item in partList:
                test = dict[item]
                names = dict[item].split(",")
                names.sort(key=natural_sort_key)
                for name in names:
                    ids = rs.ObjectsByName(name)
                    fileName = str(count) + "_" + name + ".ai"
                    if not ids: continue
                    if len(ids) != 2: continue
                    bb = rs.BoundingBox(ids)
                    vecDir  = Rhino.Geometry.Point3d(0,0,0) - bb[0]
                    rs.MoveObjects(ids, vecDir)
                    rs.UnselectAllObjects()
                    rs.SelectObjects(ids)

                    if not os.path.exists(export_folder):
                        os.makedirs(export_folder)
                    rs.Command("-Export " + chr(34) + export_folder + "/" + fileName  + chr(34) + " PreserveUnits=Yes  AIScale=1  Unit=" + units + "  RhinoScale=1  Enter")
                    vecDir.Reverse()
                    rs.MoveObjects(ids, vecDir)
                    count = count + 1
                    w = h = 0
                    # we need to check the 2 objects to find out which is the text label
                    for obj_id in ids:
                        if rs.IsText(obj_id):
                            # we found the text so let's break out the width and height values
                            text = rs.TextObjectText(obj_id)
                            idx = text.find(" - ")
                            if idx != -1:
                                dimension_str = text[idx+3:]
                                w = dimension_str.split(" x ")[1][:-1]
                                h = dimension_str.split(" x ")[0][:-1]
                    csv_txt += cat + ',' + subcat + ',' + str(count) + ',"' + fileName + '","' + name + '",' + str(h) + ',' + str(w) + '\n'

            Rhino.UI.StatusBar.UpdateProgressMeter("Processing", count, True)
    Rhino.UI.StatusBar.UpdateProgressMeter(count, True)

    rs.EnableRedraw(True)

    Rhino.UI.StatusBar.HideProgressMeter(sc.doc.RuntimeSerialNumber)

    # write out the CSV string to the target CSV file
    with open(op.join(export_root, "indesign_data.csv"), "w") as f:
        f.write(csv_txt)

if __name__ == "__main__":
    test()

:warning: See my changes in this diff view

This is how you can use the progress bar very simply in a for loop:

import rhinoscriptsyntax as rs
from Rhino import RhinoApp

MAX = 1000
rs.StatusBarProgressMeterShow("Progress", 0, MAX)

for i in range(0, MAX):
    rs.StatusBarProgressMeterUpdate(i)
    RhinoApp.Wait()

rs.StatusBarProgressMeterHide()
1 Like

I added a page in the develope rdocs on Asynchronous Execution that has a section on reporting progress as well.

2 Likes