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

    RenderDocument/PythonCallBack : how to display progress prints during the render

    Cinema 4D SDK
    python s26 windows
    2
    4
    722
    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
      bioquet
      last edited by

      hello,
      when I run from script manager the maxon script below to launch a render and display the progress in the console, all the prints of line 44 in "def PythonCallBack"

      print("ProgressHook called [{0} / p: {1}]".format(text, progress * 100.0))
      

      are not displayed as the render progresses but all at once at the end of the render.
      What should I do to see these prints during the render ?

      Thanks in advance

      Here is the link to the script :
      https://developers.maxon.net/docs/py/26_107/modules/c4d.documents/index.html?highlight=renderdocument#c4d.documents.RenderDocument"

      And the maxon script :

      """
      Copyright: MAXON Computer GmbH
      Author: Maxime Adam
      
      Description:
          - Render the current document with a progress hook to get notified about the current rendering progress.
      
      Class/method highlighted:
          - c4d.bitmaps.MultipassBitmap
          - c4d.documents.RenderDocument()
      
      """
      import c4d
      
      
      def PythonCallBack(progress, progress_type):
          """Function passed in RenderDocument. It will be called automatically by Cinema 4D with the current render progress.
      
          Args:
              progress (float): The percent of the progress for the current step
              progress_type (c4d.RENDERPROGRESSTYPE): The Main part of the current rendering step
          """
          text = str()
      
          if progress_type == c4d.RENDERPROGRESSTYPE_BEFORERENDERING:
              text = "Before Rendering"
      
          elif progress_type == c4d.RENDERPROGRESSTYPE_DURINGRENDERING:
              text = "During Rendering"
      
          elif progress_type == c4d.RENDERPROGRESSTYPE_AFTERRENDERING:
              text = "After Rendering"
      
          elif progress_type == c4d.RENDERPROGRESSTYPE_GLOBALILLUMINATION:
              text = "GI"
      
          elif progress_type == c4d.RENDERPROGRESSTYPE_QUICK_PREVIEW:
              text = "Quick Preview"
      
          elif progress_type == c4d.RENDERPROGRESSTYPE_AMBIENTOCCLUSION:
              text = "AO"
      
          # Prints to the console the current progress
          print("ProgressHook called [{0} / p: {1}]".format(text, progress * 100.0))
      
      
      def PythonWriteCallBack(mode, bmp, fn, mainImage, frame, renderTime, streamnum, streamname):
          """Function passed in RenderDocument.
          It will be called automatically by Cinema 4D when the file rendered file should be saved.
      
          Args:
              mode (c4d.WRITEMODE): The write mode.
              bmp (c4d.bitmaps.BaseBitmap): The bitmap written to.
              fn (str): The path where the file should be saved.
              mainImage (bool): True for main image, otherwise False.
              frame (int): The frame number.
              renderTime (int): The bitmap frame time.
              streamnum (int): The stream number.
              streamname (streamname: str): The stream name.
          """
          text = str()
      
          if mode == c4d.WRITEMODE_STANDARD:
              text = "Standard"
      
          elif mode == c4d.WRITEMODE_ASSEMBLE_MOVIE:
              text = "Assemble Movie"
      
          elif mode == c4d.WRITEMODE_ASSEMBLE_SINGLEIMAGE:
              text = "Assemble single image"
      
          print("ProgressWriteHook called [{0} / p: {1}]".format(text, renderTime))
      
      
      def main():
          # Retrieves the current active render settings
          rd = doc.GetActiveRenderData()
      
          # Creates a Multi Pass Bitmaps that will store the render result
          bmp = c4d.bitmaps.MultipassBitmap(int(rd[c4d.RDATA_XRES]), int(rd[c4d.RDATA_YRES]), c4d.COLORMODE_RGB)
          if bmp is None:
              raise RuntimeError("Failed to create the bitmap.")
      
          # Adds an alpha channel
          bmp.AddChannel(True, True)
      
          # Renders the document
          if c4d.documents.RenderDocument(doc, rd.GetData(), bmp, c4d.RENDERFLAGS_EXTERNAL, prog=PythonCallBack,
                                          wprog=PythonWriteCallBack) != c4d.RENDERRESULT_OK:
              raise RuntimeError("Failed to render the temporary document.")
      
          # Displays the render in the Picture Viewer
          c4d.bitmaps.ShowBitmap(bmp)
      
      
      if __name__ == "__main__":
          main()
      
      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @bioquet
        last edited by ferdinand

        Hello @bioquet,

        Welcome to the Maxon developers forum and its community, it is great to have you with us!

        Getting Started

        Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.

        • Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
        • Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
        • Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.

        It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.

        About your First Question

        The problem with what you are trying to do, is that there are multiple things which are blocking in this context. See my code example below for details.

        Cheers,
        Ferdinand

        Result

        fa645c41-52b9-45d7-8430-471f03132e77-image.png

        Code

        """Demonstrates how to render a document with RenderDocument while showing the progress in the UI.
        
        Okay, to do what you want to do, we have to overcome two hurdles:
        
        * Script Manager scripts are blocking. I.e., nothing can happen in the UI while the script is
          running. We can sidestep this with an asynchronous dialog but PLEASE DO NOT DO THIS IN PRODUCTION
          CODE as this results in a dangling dialog. Use a CommandData plugin instead.
        * The RenderDocument function is blocking. We can overcome this by running the rendering in a
          separate thread. The rendering already runs in a separate thread but we push here the render handling
          into a separate thread.
        """
        
        import c4d
        import mxutils
        
        doc: c4d.documents.BaseDocument # The active document.
        
        class HandleRenderThread(c4d.threading.C4DThread):
            """Handles a #RenderDocument call in a separate thread.
        
            We need this to make the #RenderDocument call non-blocking.
            """
            MAP_RENDER_PROGRESS_TYPE: dict[int, str] = {
                c4d.RENDERPROGRESSTYPE_BEFORERENDERING: "Before Rendering",
                c4d.RENDERPROGRESSTYPE_DURINGRENDERING: "During Rendering",
                c4d.RENDERPROGRESSTYPE_AFTERRENDERING: "After Rendering",
                c4d.RENDERPROGRESSTYPE_GLOBALILLUMINATION: "GI",
                c4d.RENDERPROGRESSTYPE_QUICK_PREVIEW: "Quick Preview",
                c4d.RENDERPROGRESSTYPE_AMBIENTOCCLUSION: "AO"
            }
        
            def __init__(self, doc: c4d.documents.BaseDocument) -> None:
                """Initializes a new instance with the document to render.
                """
                # The document to render, the result, and a semaphore like string to signal the progress.
                self._doc: c4d.documents.BaseDocument = mxutils.CheckType(doc, c4d.documents.BaseDocument)
                self._res: c4d.bitmaps.BaseBitmap | Exception | None = None
                self._msg: str = ""
            
            def Message(self, progress: float, progress_type: int) -> None:
                """Called when the rendering progress changes.
        
                Writes to the _msg attribute to signal the progress to the main thread.
                """
                self._msg = f"Progress: {progress * 100:.2f}% " \
                            f"({self.MAP_RENDER_PROGRESS_TYPE.get(progress_type, 'Unknown')})"
        
            def Main(self) -> None:
                """Called when the thread is started.
                """
                # Get the render data, create a multipass bitmap, add an alpha channel, and 
                # render the document.
                doc: c4d.documents.BaseDocument = self._doc
                rData: c4d.documents.RenderData = doc.GetActiveRenderData()
        
                bmp: c4d.bitmaps.BaseBitmap = mxutils.CheckType(
                    c4d.bitmaps.MultipassBitmap(
                        int(rData[c4d.RDATA_XRES]), 
                        int(rData[c4d.RDATA_YRES]), 
                        c4d.COLORMODE_RGB
                    ))
                bmp.AddChannel(True, True)
        
                res: int = c4d.documents.RenderDocument(
                    doc, rData.GetDataInstance(), bmp, c4d.RENDERFLAGS_EXTERNAL, prog=self.Message)
                self._res = (bmp 
                             if res == c4d.RENDERRESULT_OK else 
                             RuntimeError("Failed to render the document."))
        
        class RenderDialog(c4d.gui.GeDialog):
            """Realizes a dialog with a button to start a rendering.
        
            We need this to make the script manager script non-blocking. Or more precisely, to escape
            the execution context of the script manager script (which is also why this is a bad idea for
            production code).
            """
            ID_BTN_RENDER: int = 1000
        
            def CreateLayout(self) -> bool:
                """Called to let the dialog populate its UI with gadgets.
                """
                self.SetTitle("Render Dialog")
                self.AddButton(self.ID_BTN_RENDER, c4d.BFH_CENTER, name="Render")
                return True
        
            def Command(self, cid: int, msg: c4d.BaseContainer) -> bool:
                """Called when the user presses a gadget.
                """
                # The user pressed the render button. We start the rendering in a separate 
                # thread. And then keep updating the UI and status bar with the progress.
                if cid == self.ID_BTN_RENDER:
                    thread: HandleRenderThread = HandleRenderThread(doc)
                    thread.Start()
        
                    # We must print to the status bar, as the console will not update until the
                    # rendering is done.
                    while thread.IsRunning():
                        c4d.StatusSetText(thread._msg)
        
                    c4d.StatusClear()
                    thread.End()
                    if isinstance(thread._res, Exception):
                        raise thread._res
                    
                    bmp: c4d.bitmaps.BaseBitmap = thread._res
                    c4d.bitmaps.ShowBitmap(bmp)
                return True
        
        if __name__ == "__main__":
            # We open a dialog to start the rendering. This is a dangling dialog because it is asynchronous.
            # This means it will persist beyond the lifetime of the script manager script module. Which is
            # why you should not do this in production code.
            dlg: RenderDialog = RenderDialog()
            dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=100)
        

        MAXON SDK Specialist
        developers.maxon.net

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

          Hello,
          Thank you very much for your quick reply.
          It works perfectly now.
          I just removed the "mxutils" which was not compatible with my s26 version

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

            Oh,

            my bad, I did overlook that you flagged this as S26. Yes, mxutils is a 2024+ feature. I used it here to carry out type checks, e.g., that a document or bitmap are not null/none. The code will run without them, but it will fail more gracefully with these checks.

            You could replace these calls with manual checks:

            bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.MultipassBitmap(
                int(rData[c4d.RDATA_XRES]), 
                int(rData[c4d.RDATA_YRES]), 
                c4d.COLORMODE_RGB))
            if bmp is None: # or more precise: if not isinstance(bmp, c4d.bitmaps.BaseBitmap) ...
                raise MemoryError("Failed to allocate bitmap.")
            

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

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