SOLVED Animating the Status Spin with Render Progress

Hello!
I'm rendering an image in my plugin with documents.RenderDocument() and using StatusSetSpin().

With the code below, I'm using RenderDocument()'s prog parameter function to update the Status progress bar. Currently this code shows the spinner, but the render progress isn't given until after the rendering has completed which is unexpected. With the progress_type RENDERPROGRESSTYPE_DURINGRENDERING, I'd expect the function to execute during rendering rather than afterwards. Perhaps it is, but the UI gets frozen during rendering. With longer renders (like with the Zombie - Toon Rig scene from the Content Browser), the StatusBar doesn't get cleared with the code I have below. Here are my questions:

  • If this is working as intended, is there a way to animate the progress bar while the document is rendering?
  • How do I guarantee that c4d.StatusClear() is going to get called after the progress updates?
import c4d
from c4d import documents

def updateProgressBar(p, progress_type):
    if progress_type == c4d.RENDERPROGRESSTYPE_BEFORERENDERING:
        print("Before Rendering")
        c4d.StatusSetText('Initializing render...') 
        c4d.StatusSetSpin()
    elif progress_type == c4d.RENDERPROGRESSTYPE_DURINGRENDERING:
        print(p)
        c4d.StatusSetText('Rendering...') 
        c4d.StatusSetSpin()
    elif progress_type == c4d.RENDERPROGRESSTYPE_AFTERRENDERING:
        print("After Rendering")
        c4d.StatusClear()
        c4d.EventAdd()
        
def main(doc):
    rd = doc.GetActiveRenderData().GetData()
    bmp = c4d.bitmaps.BaseBitmap()
    bmp.Init(int(rd[c4d.RDATA_XRES]), int(rd[c4d.RDATA_YRES]), 24)
    documents.RenderDocument(doc, rd, bmp, c4d.RENDERFLAGS_EXTERNAL, prog=updateProgressBar)
    c4d.StatusClear()
    c4d.StatusSetText('Render complete.')
    c4d.EventAdd()

if __name__=='__main__':
    main(doc)

Thank you!

Hi @blastframe,

thank you for reaching out to us. What you are trying to do is unfortunately not supported by us.

Inside your callback function updateProgressBar you are inside the render thread or in other words not in the main thread. Which makes all gui operations of limits. I actually just recently asked @m_magalhaes what Maxon's stance is on the status bar functionalities being considered GUI operations or not, because I always wondered myself. Other than other GUI operations the status bar methods do not test if they are on the main thread. So you can bend the rules a bit with them. So it's kinda nice to have here an example which proves that they are subject to the same limitations although they do not enforce them.

To solve your problem you have two options:

  1. Use the c4d.documents.BatchRender type as it has the status bar functionality built in.
  2. Try to wiggle your way around RenderDocument and make it work anyways. This is not supported by us (at least In Python).

You have here two problems at play for option two. The threading scope and its GUI limitations of the callback function and the blocking nature of RenderDocument. RenderDocument accepts a th argument with which you can pass in a BaseThread to be used as the render thread. Unfortunately it actually only accepts a BaseThread and not a C4DThread, which makes a customly designed thread type solution impossible for us here. So the only way I see which COULD work (with the emphasis it being a possibility and not a guarantee):

You need three threads:

  • the main thread,
  • an execution thread (which basically just encapsulates a RenderDocument call),
  • and a render thread created internally by RenderDocument (you won't need to populate th).

You also will need a cross-thread communication object, I will call signal object. More on that later.

Then you do the following on the main thread:

  1. Instantiate a signal object S.
  2. Instantiate and start an execution thread E.
  3. While E is running run a loop:
    a.Check the signal object and operate with it the status bar.

In parallel runs the execution thread which internally runs the render thread:

  1. Your callback is being called in the render thread.
  2. You write some render information into the signal object S.

Which COULD work. There is however the problem that the Cinema 4D SDK does not expose its cross-thread communication objects to Python and the one's provided by CPython like semaphore and lock are not supported. So you would have to write one yourself. Which is not really possible (at least in a safe way, due to the fact that Cinema's threading internals are not public knowledge). So there is not really a solution here for you from an official Maxon point of view (besides form using BatchRender). But since only a one-way communication is here necessary, you might be able to do it anyways 😉 I hope you understand that this option clearly falls out of the scope of support.

Cheers,
Ferdinand

@ferdinand Thank you, Ferdinand for the thorough answer and ideas for workarounds. I don't quite have my head around how to use threading in Cinema 4D yet but you've inspired me to look into it. Thank you!