RenderDocument/PythonCallBack : how to display progress prints during the render
-
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()
-
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,
FerdinandResult
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)
-
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 -
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