Stopping a cycle of renders
-
On 21/01/2016 at 12:12, xxxxxxxx wrote:
I'm creating a plugin that opens a GeDialog (it is a DLG_TYPE_MODAL dialog).
In it, I will perform a series of renders of the current scene (with c4d.documents.RenderDocument).
My dialog has a UserArea that is updated after each finished render, to show a thumbnail of the render.
The rendering action is activated by a button in the dialog, so the rendering code is inside the Command method.
I would like to provide the user with a way to abort the render.
I even have a button for that (called "Abort" of course ).
But when the rendering code is being executed inside the Command method, I can't read the "Abort" button, because the Command method is already running my code and can't catch the clicks on any button.
So, how can I create a cycle that still renders the frames but can be aborted through the click of a button? -
On 22/01/2016 at 06:18, xxxxxxxx wrote:
Hi Rui,
To be able to control such a render queue you need to use a thread so that Cinema GUI can respond to events while rendering.
The Python API provides the C4DThread class to create your own thread.
Such a thread can be stopped with C4DThread.End(False)
And the RenderDocument() calls should be done from the implementation of C4DThread.Main()If you need code to implement this I can post some.
-
On 22/01/2016 at 07:45, xxxxxxxx wrote:
Thank you, Yannick.
I already had spotted that but in the documentation, it stated that inside the threading functions it is forbidden to:Add an Event.
Make any changes to materials.
Change the structure of objects attached to the scene.
Change parameters of elements attached to the scene (allowed, but not recommended except for tags).
Call a Draw function.
Perform any GUI functionality. (e.g. displaying messages, opening dialogs etc.)
During drawing to do any file operations. (During execution it is allowed.)
Create undos.My problem is with the "Call a Draw function" because, to update the thumbnails, I refresh the UserArea with render_display.Redraw()
And, inside the DrawMsg method of the UserArea, I have several Draw functions.
Will it work? -
On 22/01/2016 at 08:30, xxxxxxxx wrote:
Originally posted by xxxxxxxx
My problem is with the "Call a Draw function" because, to update the thumbnails, I refresh the UserArea with render_display.Redraw()
And, inside the DrawMsg method of the UserArea, I have several Draw functions.
Will it work?"Draw function" in this sentence means any ObjectData.Draw() function.
The custom thread should only render and store the rendered bitmaps so that they are accessible from the dialog.
It shouldn't change the active document nor do any GUI operation (forbidden).Do all the GUI operations in the GeDialog and GeUserArea classes.
Use C4DThread.Start() to start the render thread from the dialog.
And from GeDialog.Timer() call C4DThread.IsRunning() to check if the started thread has finished its processing. -
On 22/01/2016 at 08:51, xxxxxxxx wrote:
Thank you, Yannick.
I will re-arrange my code to try that. -
On 22/01/2016 at 13:06, xxxxxxxx wrote:
It is not working
From inside the thread I call the Redraw method of the UserArea and it doesn't get refreshed. I placed a print command in the first line of the DrawMsg method of the UserArea and it never gets printed when called from my UserThread.
However, it gets printed when the dialog is first displayed. -
On 22/01/2016 at 13:21, xxxxxxxx wrote:
Are you passing True to the Redraw() method? (from_thread parameter)
Otherwise you can use EventAdd() or SpecialEventAdd() with your plugin
ID and update the user area in that case.Btw, in case you didn't know, you must pass self.Get() to the
RenderDocument()'s bt argument and implement C4DThread.TestDBreak()
to actually be able to stop the render process. -
On 22/01/2016 at 13:25, xxxxxxxx wrote:
Oops, didn't knew that. Thank you. I will try it.
-
On 22/01/2016 at 13:46, xxxxxxxx wrote:
@Yannick.
This is a perfect example of something that should be included in the example files. Or maybe on the Maxon blog.
Because most people will not have any prior experience with custom threads.-ScottA
-
On 22/01/2016 at 13:54, xxxxxxxx wrote:
The refresh is still not working
My thread starts like this:
class UserThread(C4DThread) :
thumbnail = RenderDisplay() # my UserArea
def Main(self) :
# my code...and inside my thread, I have:
print "Redraw"
self.thumbnail.Redraw(True)
c4d.SpecialEventAdd(PLUGIN_ID)The start of my UserArea draw method is:
def DrawMsg(self, x1, y1, x2, y2, msg_ref) :
print "Inside UserArea"
The "Redraw" gets printed.
But the "Inside UserArea" is not. -
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?