Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Cancel Option for Progress Bar Dialog?

    Cinema 4D SDK
    2023
    2
    4
    804
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      bentraje
      last edited by

      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)
      
      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @bentraje
        last edited by ferdinand

        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 until opecnt 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 in MyDialog.Command and with MyDialog.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 a C4DThread all threading restrictions do apply.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 1
        • B
          bentraje
          last edited by

          Hi @ferdinand

          RE: limited at the moment.
          No worries. Take your time

          Thanks 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?

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @bentraje
            last edited by ferdinand

            Hey @bentraje,

            I would recommend using the example I posted above, only that you replace the MessageData instance with a GeDialog 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 an object 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 declared MyThread.SendMessage and yet try to call it. As always, we also cannot debug your code for you.

            Cheers,
            Ferdinand

            Code:
            ⚠ 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()
            

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • First post
              Last post