Cancel Option for Progress Bar Dialog?
-
Hi,
I'm using the illustration code in this thread.
It works.But the problem is that it freezes viewport interaction. I can't cancel the progress bar midway the process. It has to be completed.
Is there a way around this?
import c4d from c4d import gui import time class TestDialog(gui.GeDialog): PROGRESSBAR = 1001 ID_BTN_01 = 1002 ID_BTN_02 = 1003 def __init__(self): self.progress = 0 self.prim_list = [c4d.Ocube, c4d.Osphere, c4d.Ocylinder, c4d.Oplane, c4d.Otorus, c4d.Opyramid] self.prim_length = len(self.prim_list) def StopProgress(self): self.progress = 0 progressMsg = c4d.BaseContainer(c4d.BFM_SETSTATUSBAR) progressMsg.SetBool(c4d.BFM_STATUSBAR_PROGRESSON, False) self.SendMessage(self.PROGRESSBAR, progressMsg) def CreateLayout(self): self.SetTitle("ProgressBar Example") self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT|c4d.BFV_TOP, cols=0, rows=1) self.AddCustomGui(self.PROGRESSBAR, c4d.CUSTOMGUI_PROGRESSBAR, "", c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT, 0, 0) self.GroupEnd() self.GroupBegin(id = 1005, flags=c4d.BFH_SCALEFIT|c4d.BFV_TOP, cols=2, rows=1) self.AddButton(self.ID_BTN_01, c4d.BFH_SCALEFIT, 100, 15, name="Create Primitives") self.GroupEnd() return True def Command(self, id, msg): if (id == self.ID_BTN_01): #do something short opecnt = self.prim_length; for prim in self.prim_list: obj = c4d.BaseObject(prim) doc.InsertObject(obj) c4d.EventAdd() for x in range(opecnt): self.progress += 1 progressMsg = c4d.BaseContainer(c4d.BFM_SETSTATUSBAR) progressMsg[c4d.BFM_STATUSBAR_PROGRESSON] = True progressMsg[c4d.BFM_STATUSBAR_PROGRESS] = self.progress/opecnt self.SendMessage(self.PROGRESSBAR, progressMsg) time.sleep(1) self.StopProgress() return True def AskClose(self): self.StopProgress() return False if __name__=='__main__': dialog = TestDialog() dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=0, defaulth=100, defaultw=400)
-
Hello @bentraje,
Thank you for reaching out to us. As announced here, Maxon is currently conducting a company meeting. Please understand that our capability to answer questions is therefore limited at the moment.
Your code has blocking behavior because you designed/wrote it so. It is specifically these lines:
for x in range(opecnt): self.progress += 1 progressMsg = c4d.BaseContainer(c4d.BFM_SETSTATUSBAR) progressMsg[c4d.BFM_STATUSBAR_PROGRESSON] = True progressMsg[c4d.BFM_STATUSBAR_PROGRESS] = self.progress/opecnt self.SendMessage(self.PROGRESSBAR, progressMsg) time.sleep(1) self.StopProgress()
This happens inside
Command
which is executed on the main thread. Your loop therefore blocks the main thread, nothing can happen untilopecnt
seconds have elapsed.When you want the action
ID_BTN_01
to be non-blocking you will have to implement it as a C4DThread. The idea is then to start the thread inMyDialog.Command
and withMyDialog.Timer
regularly poll the thread for its finish status while updating for example a status bar. In this thread I recently demonstrated the general pattern of threading in the sense of making something non-blocking for the main thread.Please note that you can NOT update/modify the GUI from within a thread; you must defer GUI updates to the main thread. For example by checking your thread in MyDialog.Timer
. Inside aC4DThread
all threading restrictions do apply.Cheers,
Ferdinand -
Hi @ferdinand
RE: limited at the moment.
No worries. Take your timeThanks for the response and the reference post.
I managed to implement the thread but I'm stuck on using it along side the GeDialog.Specifically this part.
self.SendMessage(self.PROGRESSBAR, progressMsg)
since this is now inside the Thread class rather than Dialog class.
This gives me an error of
AttributeError: 'MyThread' object has no attribute 'SendMessage'
How should I refactor it?
I tried doing the following code base on this thread (https://developers.maxon.net/forum/topic/12310/best-plugin-type-for-background-thread-processing/14)
self.dlg = weakref.ref(dlg)
but it gives me an error
AttributeError: 'weakref' object has no attribute 'SendMessage'
How should I go about it?
-
Hey @bentraje,
I would recommend using the example I posted above, only that you replace the
MessageData
instance with aGeDialog
instance. The other thread looks overly specific, a bit overengineered with the decorator, to be a good basic example.I do not have the time to write a full working example right now, but in pseudo code it would look as shown at the end of the posting. I cannot make much out of random error messages you give me. An
AttributeError
means that anobject
does not have an attribute, be it a field/property (myObject._data, myObject.Data
) or a function (myObject.SendMessage
), so it means that you have not declaredMyThread.SendMessage
and yet try to call it. As always, we also cannot debug your code for you.Cheers,
FerdinandCode:
This is untested 'pseudo-code', I wrote this 'blind'. It demonstrates a pattern and is not meant to be executable code."""Provides an example for a thread executing multiple tasks and expressing the execution state to the outside world. """ import c4d import typing class WorkerThread (c4d.threading.C4DThread): """Wraps the execution of multiple tasks expressed by a set of data in a thread. The thread exposes the total amount of tasks, already done tasks, and their results to outside observers. """ def __init__(self, data: typing.Collection) -> None: """Initializes the worker. """ self._data : typing.Collection = data # Stuff to do. self._results: list[any] = [] # Results of stuff that has been done. self._taskCount: int = len(data) # Number of things to do in total. self._finishedTaskCount: int = 0 # Number of things that already have been done. def Main(self) -> None: """Carries out the tasks. """ for item in self._data: self._results.append(self.Compute(item)) # this takes a long time to do. self._finishedTaskCount += 1 def Compute(self, *args) -> any: """Represents a computation step. """ return 0 # Read-only properties to access the data of the thread from another thread. You could also just # let the other thread directly access members (thread._results) or also use fancier things like # locks/semaphores to make this "nicer". This is a question of taste, read-only properties are # somewhat a middle ground. @property def Results(self) -> tuple[any]: return tuple(self._results) @property def TaskCount(self) -> int: return self._taskCount @property def FinishedTaskCount(self) -> int: return self._finishedTaskCount class MyTaskDialog (c4d.gui.GeDialog): """Realizes a dialog that runs a thread wrapping multiple tasks. """ ID_NEW_THREAD: int = 1000 # Id of a gadget which invokes adding a new thread. def __init__(self) -> None: """ """ # The worker thread of the dialog, could also be multiple threads as in the other example, # I kept it simple here. self._workerThread: WorkerThread | None = None super().__init__() def Command(self, mid: int, msg: c4d.BaseContainer) -> bool: """Called by Cinema 4D on GUI interactions. """ # Something with ID_NEW_THREAD has been pressed, we start try to start a new thread with the # dummy data [1, 2, 3]. if mid == MyTaskDialog.ID_NEW_THREAD and not self.StartWorkerThread([1, 2, 3]): # Do something on failure. pass return super().Command(mid, msg) def StartWorkerThread(self, data: typing.Collection, evalFrequency: int = 250) -> bool: """Starts a new worker thread and sets the evaluation frequency. """ # There is already an ongoing thread. if isinstance(self._workerThread, WorkerThread): return False # Create and start the new thread. self._workerThread = WorkerThread(data) self._workerThread.Start() self.SetTimer(max(100, evalFrequency)) return True def Timer(self, msg: c4d.BaseContainer) -> None: """Called by Cinema 4D for each timer tick. """ # Should never happen and (most) GUI functions do this own their own, more a formality. if not c4d.threading.GeIsMainThreadAndNoDrawThread(): return t: WorkerThread = self._workerThread # The thread is still running, just update the UI with the status of the thread. if t.IsRunning(): c4d.StatusSetSpin() c4d.StatusSetText(f"Running tasks: {t.FinishedTaskCount}/{t.TaskCount}") # The thread has finished, do something with the result, shut off the timer, and clear out # the UI. else: results: any = t.Results print(results) t.End() self._workerThread = None self.SetTimer(0) c4d.StatusClear()