Stopping a cycle of renders



  • On 23/01/2016 at 09:41, xxxxxxxx wrote:

    This is getting frustrating :-(
    My user thread prints the "Redraw".
    Both the print and the Redraw call are the result of a conditional expression, so if the "Redraw" is printed, then the Redraw call is also executed.
    But the "Inside UserArea" print, at the start of the code that draws the content of the user area is not printed, so it seems that the Redraw function is never executed from within the user thread.
    However, it is executed the first time the dialog opens.
    Why is it not working from within the user thread?!?!



  • On 23/01/2016 at 09:46, xxxxxxxx wrote:

    I'm not even implementing the abort verification yet.
    I want to make it render first, but that is still not working.



  • On 23/01/2016 at 12:13, xxxxxxxx wrote:

    I don't know exactly what you're trying to do. But it seems to me that the modal dialog is making this much harder to do.
    What is the reason you're using a modal dialog?

    If you use an async dialog. You should not need to use any custom threads stuff. Because you can click a button any time you wish.
    And you should be able to stop the rendering very easily by using this method:
    c4d.threading.GeStopBackgroundThreads(0, c4d.BACKGROUNDHANDLERFLAGS_RENDEREXTERNAL)

    I think...I've never actually tried it.

    -ScottA



  • On 23/01/2016 at 14:17, xxxxxxxx wrote:

    I'm using a modal dialog because I'm performing a lot of renderings of the active scene (that I can't predict if it is a very large one) and I was using the RenderDocument command with RENDERFLAGS_NODOCUMENTCLONE to save on RAM.
    So, I wanted to make sure it was impossible for the user to modify the scene while the plugin was performing the renders.



  • On 23/01/2016 at 17:06, xxxxxxxx wrote:

    As a test. I wrote a very simple modal GeDilaog plugin that executes and stops the the rendering with two buttons. And the buttons start and stop the rendering fine without using any custom threads.
    But I noticed that even the picture viewer would not update. Even though the rendering is still running in the background.
    But once the dialog is closed. The picture viewer suddenly updates and writes the rendered images to it that were stored in memory.

    I don't see how using a custom thread can get around this problem.
    AFAIK. As long as your modal dialog is open. You cannot update any other other gui.
    Whether that gui is the picture viewer, or your UA, or whatever....
    Modal dialogs are evil that way.

    Perhaps Yannick can explain further what he was talking about on Monday?
    Because it doesn't seem possible from where I'm sitting.

    -ScottA



  • On 23/01/2016 at 17:29, xxxxxxxx wrote:

    Well, when I had my rendering cycle running in the main thread, inside the Command method, it updated the UserArea fine.
    It just doesn't update now that is on a user thread.
    I may have to test with a non-modal dialog, then
    I still would love to hear from Yannick, though.
    Thank you for testing out, Scott.



  • On 24/01/2016 at 15:00, xxxxxxxx wrote:

    Hey Rui,
    I'm just sitting here watching football and writing some code. And I might have written something that might help you.
    This is a GeDialog plugin with a UserArea.
    The custom thread renders the scene and the UA displays it.
    The trick to getting the UA to Redraw is the use of the Timer() method in the GeDialog class while the renderer is running.

    I think you just wanted to kill a single frame render?
    I'm rending a sequence of frames. So you can see the image update in the UA easier.
    So that part you'll need to change.

    Put some object in the scene and key frame it from 0-20 so it rotates. Just so you can see the result in the UA.

    import c4d, os, time, threading  
    from c4d import plugins, utils, gui, bitmaps, documents   
      
    PLUGIN_ID=1000010 #<-------------- Testing id ONLY!!!!!!!   
      
    rImage = bitmaps.BaseBitmap()  
      
      
    class UserArea(c4d.gui.GeUserArea) :  
        
      def __init__(self, bmp) :  
          super(UserArea, self).__init__()  
          self.bmp = rImage  
            
      def GetMinSize(self) :  
          x = 100  
          y = 100  
          return x,y         
        
      def DrawMsg(self, x1, y1, x2, y2, msg) :  
          self.SetClippingRegion(x1, y1, x2, y2)  
          self.DrawSetPen(c4d.COLOR_BG)  
          self.DrawRectangle(x1, y1, x2, y2)  
            
          #Draw the bitmap rendered in the custom thread below   
          if self.bmp:  
              w, h = self.bmp.GetSize()  
              self.DrawBitmap(self.bmp, x1, y1, w, h, x1, y1, w, h,c4d.BMP_NORMAL | c4d.BMP_ALLOWALPHA)  
      
                
                
                
    #This custom thread will render the scene to a bitmap variable  
    #The UserArea will display this bitmap  
    #NOTE: I set it to render 20 frames just as a test..change as desired  
    class MyThread(c4d.threading.C4DThread) :  
      
      ua = UserArea(None)  
      
      def Main(self) :  
         
          doc = c4d.documents.GetActiveDocument()  
          rd = doc.GetActiveRenderData().GetData()  
          rd[c4d.RDATA_ALPHACHANNEL]= True   
          rd[c4d.RDATA_XRES]=100  
          rd[c4d.RDATA_YRES]=100  
          xres = int(round(rd[c4d.RDATA_XRES]))  
          yres = int(round(rd[c4d.RDATA_YRES]))  
                
          fps = doc.GetFps()  
          rd[c4d.RDATA_FRAMESEQUENCE] = 3  
          startFrame = rd[c4d.RDATA_FRAMEFROM]= c4d.BaseTime(0, fps)  
          stopFrame = rd[c4d.RDATA_FRAMETO]= c4d.BaseTime(20, fps)              
      
          rImage.Init(xres, yres, depth=24)  
          res = documents.RenderDocument(doc, rd, rImage, c4d.RENDERFLAGS_EXTERNAL)   
            
                
                
    #enums  
    USER_AREA = 1000  
    BTN_START = 1001  
    BTN_STOP = 1002  
      
    class MyDialog_Gui(gui.GeDialog) :  
      
      ua = UserArea(None)  
      thread = MyThread()  
      
      def CreateLayout(self) :  
       
          self.SetTitle("Start/Stop Rendering Example")          
            
          self.AddUserArea(USER_AREA, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, c4d.BFV_SCALEFIT)  
          self.AttachUserArea(self.ua, USER_AREA)          
            
          self.GroupBegin(0, c4d.BFH_SCALEFIT|c4d.BFH_SCALEFIT, 3, 1, "Master Group",0) #The master group  
          self.GroupBorder(c4d.BORDER_BLACK)  
          self.GroupBorderSpace(10, 20, 10, 10)    #left, top, right, bottom      
      
          ############# Start Rendering Button ##################################  
          bc1 = c4d.BaseContainer()  
          self.GroupBegin(0, c4d.BFH_SCALEFIT|c4d.BFH_SCALEFIT, 0, 0, "Start Button",0)   
          self.GroupBorder(c4d.BORDER_BLACK)   
          self.AddButton(BTN_START, c4d.BFH_SCALE|c4d.BFV_SCALE, 130, 30, "Start")          
          self.GroupEnd()      
      
          ############## Stop Rendering Button ############################  
          bc2 = c4d.BaseContainer()  
          self.GroupBegin(0, c4d.BFH_SCALEFIT|c4d.BFH_SCALEFIT, 0, 0, "Stop Button",0)   
          self.GroupBorder(c4d.BORDER_BLACK)  
          self.AddButton(BTN_STOP, c4d.BFH_SCALE|c4d.BFV_SCALE, 130, 30, "Stop")  
          self.GroupEnd()     #The end of the button's group  
            
          self.GroupEnd()        #The end of the master group holding the two button groups  
      
          return True   
      
      def InitValues(self) :  
          self.SetTimer(300);  
          return True  
            
      def Timer(self, msg) :  
        
          #Since we can't update the USER_AREA manually while rendering  
          #We must ReDraw it constantly before we start rendering  
          #Only do this ReDrawing when we are actually rendering!!!    
          if self.thread.IsRunning() : self.ua.Redraw()  
        
      def Command(self, id, msg) :       
      
          if id == BTN_START:  
              print "Start Pressed"              
              self.thread.Start()          #Start the custom rendering thread         
       
      
          if id == BTN_STOP:  
              self.thread.End()            #Stop the thread  
              c4d.CallCommand(430000731)   #Stop Rendering  
              print "Stop Button Pressed"             
                           
          c4d.EventAdd()  
          return True  
            
       
    class myDialog_Main(plugins.CommandData) :  
      dialog = None  
        
      def Execute(self, doc) :      
      
          if self.dialog is None:  
              self.dialog = MyDialog_Gui()  
          return self.dialog.Open(dlgtype=c4d.DLG_TYPE_MODAL, pluginid=PLUGIN_ID, defaultw=200, defaulth=150, xpos=-1, ypos=-1)  
            
      def RestoreLayout(self, sec_ref) :  
          if self.dialog is None:  
              self.dialog = MyDialog_Gui()  
          return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)  
       
      
    if __name__ == "__main__":  
     path, fn = os.path.split(__file__)  
     bmp = bitmaps.BaseBitmap()  
     bmp.InitWith(os.path.join(path, "res/icons/", "None"))  
     plugins.RegisterCommandPlugin(PLUGIN_ID, "Render To UA",0,None,"", myDialog_Main())
    

    This is still fairly crude. It doesn't quite stop & restart the thread properly when using it on multiple frames like I'm doing.
    But it does stop the rendering while the dialog is open. And it does Redraw the UA.
    If I was only rendering one frame rather than several frames. I think this would be close to good enough.

    Maybe Yannick will have some pointers on how to stop and start the thread better.

    -ScottA



  • On 24/01/2016 at 16:39, xxxxxxxx wrote:

    This is my version. You can pass the C4DThread to RenderDocument() which will then use it to check if
    rendering should continue. Calling the End() method of the thread will then abort the rendering immediately.

    # Copyright (c) 2016  Niklas Rosenstein
    #
    # Permission is hereby granted, free of charge, to any person obtaining a copy
    # of this software and associated documentation files (the "Software"), to deal
    # in the Software without restriction, including without limitation the rights
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    # copies of the Software, and to permit persons to whom the Software is
    # furnished to do so, subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be included in
    # all copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    # THE SOFTWARE.
      
    import c4d
      
      
    class RenderThread(c4d.threading.C4DThread) :
        
        def __init__(self, doc, bmp, renderflags = None) :
            super(RenderThread, self).__init__()
            if renderflags is None:
                renderflags = (
                    c4d.RENDERFLAGS_NODOCUMENTCLONE |
                    c4d.RENDERFLAGS_SHOWERRORS)
            self.doc = doc
            self.bmp = bmp
            self.renderflags = renderflags
      
        def Main(self) :
            rdata = self.doc.GetActiveRenderData().GetData()
            rdata[c4d.RDATA_XRES] = self.bmp.GetBw()
            rdata[c4d.RDATA_YRES] = self.bmp.GetBh()
            c4d.documents.RenderDocument(self.doc, rdata, self.bmp, self.renderflags, self.Get())
      
      
    class RenderArea(c4d.gui.GeUserArea) :
        
        def __init__(self) :
            super(RenderArea, self).__init__()
            self.bmp = None
        
        def DrawMsg(self, x1, y1, x2, y2, msg) :
            self.DrawSetPen(c4d.COLOR_BG)
            self.DrawRectangle(x1, y1, x2, y2)
            if self.bmp:
                self.DrawBitmap(self.bmp, 0, 0, self.GetWidth(),
                    self.GetHeight(), 0, 0, self.bmp.GetBw(),
                    self.bmp.GetBh(), c4d.BMP_NORMAL)
      
      
    class RenderDialog(c4d.gui.GeDialog) :
        
        def CreateLayout(self) :
            self.ua = RenderArea()
            self.thread = None
            self.AddUserArea(1000, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 70)
            self.AttachUserArea(self.ua, 1000)
            self.AddButton(1001, c4d.BFH_SCALEFIT, name="Start")
            return True
      
        def Command(self, param, bc) :
            if param == 1001:
                if not self.thread or not self.thread.IsRunning() :
                    doc = c4d.documents.GetActiveDocument()
                    width, height = self.ua.GetWidth(), self.ua.GetHeight()
                    bmp = c4d.bitmaps.BaseBitmap()
                    bmp.Init(width, height)
                    self.thread = RenderThread(doc, bmp)
                    self.thread.Start()
                    self.SetString(1001, "Stop")
                    self.SetTimer(250)
                    self.ua.bmp = bmp
                    return True
                else:
                    self._Stop()
                    return True
            return False
      
        def Timer(self, bc) :
            if not self.thread or not self.thread.IsRunning() :
                self._Stop()
            self.ua.Redraw()
        
        def _Stop(self) :
            self.thread.End(True)
            self.SetString(1001, "Start")
            self.SetTimer(0)
      
      
    if __name__ == '__main__':
        RenderDialog().Open(c4d.DLG_TYPE_MODAL_RESIZEABLE)
    


  • On 25/01/2016 at 01:58, xxxxxxxx wrote:

    Thank you, both
    I will take a look at both the listing and I guess I will make this work, now.
    Once again, thank you much.



  • On 25/01/2016 at 04:37, xxxxxxxx wrote:

    Well, it kind of works.
    I used the Scott method with some additional information from Niklas method.
    It does render and it does update.
    However, when I press the "Abort"button, I get a spinning wheel and Cinema4D hangs :-(
    Also, I need to perform some calculations on each rendered bitmap, after it gets rendered.
    So, instead of rendering a sequence of images, I need to create a cycle that renders from frame A to frame B, performing some operations on the bitmap after being rendered, before saving it.
    Can this be done?



  • On 25/01/2016 at 05:49, xxxxxxxx wrote:

    Ok, solved the hanging problem by ending the thread with self.thread.End(False) instead of just self.thread.End()

    Now, Still haven't solved the processing of the bitmaps after each render.



  • On 25/01/2016 at 07:28, xxxxxxxx wrote:

    Actually, it is not really solved
    If I abort and press the render button again, it hangs.
    Damn!!!



  • On 25/01/2016 at 08:34, xxxxxxxx wrote:

    Something to keep in mind.

    I know very little about threads. I never use them.
    When I was playing around with this yesterday. I tried using the python threading instead of the C4D threading. And I looked around the internet about how to stop threads. And I was surprised to learn that there is no such thing as stopping a thread. Stopping a thread apparently is a very, very bad idea.
    There are hacks to do it. But it's generally a bad idea.

    In the C4D thread class. We have the thread.end(True/False) method to stop them.
    Setting the param to True (or keeping it empty) will try to stop the thread immediately.
    Setting the param to False will try to stop the thread. But it won't stop until what it's working on has finished.
    But based on my research. I would say that Maxon knows that "stopping" a thread is a very bad idea. So I'm guessing that their end() method does it softly and safely. And not abruptly.

    So it sounds to me that you are trying to work on the image before the RenderDocument() method has finished running.
    I think you need to worry more about if the RenderDocument() method has finished before attempting to do something with the images. Rather than if the thread has stopped.

    Keep at it. I think you'll get it working eventually.

    -ScottA



  • On 25/01/2016 at 09:31, xxxxxxxx wrote:

    I'm stubborn ;-)
    I'm still trying and I will get there, eventually :-)
    Do you know how can I check if the RenderDocument finished?



  • On 25/01/2016 at 10:17, xxxxxxxx wrote:

    Nope.
    I spent a little bit of time yesterday trying to find a python method that checks if a file is being written to or not as a way to check if the RenderDocument() was writing to a file. And I didn't find anything worth while.
    I mostly saw people posted things like checking the time files were written as a way to tell if they were being written to or not.

    I'm sure there's a better way. I only spent about 15mins. looking around.
    I was too caught up in watching the football games.🙂

    -ScottA

    *Edit-  I don't know if you can rely on the return from RenderDocument() in cases where you're rendering multiple frames. Or calling it many times in a thread. It does have a return that can be checked. But I don't know if it can be used in your situation or not.

       res = documents.RenderDocument(doc, rd, bmp, c4d.RENDERFLAGS_EXTERNAL)  
      if res == c4d.RENDERRESULT_OK: print" We've finished"
    


  • On 25/01/2016 at 10:50, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    In the C4D thread class. We have the thread.end(True/False) method to stop them.
    Setting the param to True (or keeping it empty) will try to stop the thread immediately.
    Setting the param to False will try to stop the thread. But it won't stop until what it's working on has finished.
    But based on my research. I would say that Maxon knows that "stopping" a thread is a very bad idea. So I'm guessing that their end() method does it softly and safely. And not abruptly.

    Not quite. The End() method does the same in "stopping" the thread independent from whether you
    pass True or False to it. All it does is set a flag in the thread object that will cause TestBreak() to return
    false. The RenderDocument() function will call that method to check if it should continue to process or
    not. There's no real "stopping" happening in terms of what you might think of relating to processor 
    (where you can just kill a process with the Task Manager [Win] or Activity Monitor [Mac]).

    This is in fact the way you "stop" a thread: By telling it to stop. Note that the example below should
    just outline  the way these two components are implemented. You can not  pass a Python Thread to
    the RenderDocument function, it must be a C4DThread.

    from threading import Thread
      
    def MyRenderDocument(doc, bmp, thread) :
      ''' Sample implementation of c4d.documents.RenderDocument(). '''
      
      while not thread.TestBreak() :
        # continue to render ...
      
    class MyC4DThread(Thread) :
      ''' Sample implementation of C4DThread.End(). '''
      
      def __init__(self) :  
        super(MyC4DThread, self).__init__()
        self.stopped = threading.Event()
      
      def End(self, wait) :
        self.stopped.set()
        if wait:
          self.join()
      
      def TestBreak(self) :
        return self.stopped.is_set()
      
      def run(self) :
        MyRenderDocument(doc, self)
    


  • On 25/01/2016 at 10:54, xxxxxxxx wrote:

    @Rui If you want to render multiple frames, I think you should call RenderDocument() for each frame.
    Afaik there is no way to receive some sort of callback when a frame finished. You know that RenderDocument()
    finished when the function returned (ie. all code that you place after the RenderDocument() code is
    executed after it finished).

    @Scott Afaik RenderDocument() does not do the "Save" part for you, so you would have to save it
    by yourself after the rendering.

    TL;DR if you're still having problems with C4D hanging, you have to make sure RenderDocument()
    finishes. You should use the C4DThread.TestBreak() interaction with RenderDocument() to stop the
    function (and with it the thread) rather than the way Scott did it in his example using
    RENDERFLAGS_EXTERNAL +  CallCommand() to stop the rendering.



  • On 25/01/2016 at 11:22, xxxxxxxx wrote:

    The script Niklas posted does seem to stop the rendering Rui. Although I never tested it by trying to do anything with the resulting image.

    I would not use my example at all. It was just a wild guess on my part.

    -ScottA



  • On 25/01/2016 at 14:05, xxxxxxxx wrote:

    I will have to save the bitmap myself, I know that :-)
    And, to make sure the render finishes, for each frame, my abort button will only set a variable to True and that variable will be tested after the RenderDocument.
    So, the render will stop after the frame has finished render, if the user pressed the Abort button, in the meanwhile.
    Well, at least that is what I'm going to try to do :-)



  • On 25/01/2016 at 15:30, xxxxxxxx wrote:

    And you're sure you don't want the user to be able to stop the render process mid-render, eg. if the
    frame would still take 5 hours to render?


Log in to reply