AWS Thinkbox Discussion Forums

Submitting single frame with multiple tasks

Hi

is it possible to submit a single Frame 3ds Max scene with multiple tasks? - I would like to have a scene sampled with different random patterns. Instead of submitting multiple jobs, I would like to only submit once.

Is this possible. If not with the custom submitter, maybe via maxscript/python code?

Thanks in advance

This can be definitely done with a MAXScript job. A MAXScript job can call the render() function as needed, if needed, or do other processing of the scene.

However, keep in mind that by default 3ds Max is run in a non-UI, no-license network mode, so some scene operations might not be supported because there are no viewports, command panel, etc. In these cases, it would be possible to enable Workstation Mode so all MAXScript functionality could be employed. A 3ds Max license would be consumed.

For a simple example of a Script Job that does something else in each Task, look at the \\DeadlineRepository10\submission\3dsmax\Main\Workflows\MAXScriptJob_SimulateFumeFX.ms script used by the FumeFX Workflow that ships with SMTD. In this simple case, we get the current frame as an index, and then simulate the Nth object of the list of FumeFX simulation objects submitted with the job.

The processing code looks like this:

du.SetTitle "FumeFX Simulation" --set the job title 
du.LogMessage ">Starting FumeFX Simulation..." --output a message to the log
local st = timestamp() --get the current system time

local theFumeFX = execute (du.GetJobInfoEntry "FumeFXToSimulate")
local theIndex = du.CurrentFrame
if theIndex < 1 or theIndex > theFumeFX.count do du.FailRender "Index Out Of Range!"
local theObject = getNodeByName theFumeFX[theIndex]
du.LogMessage (">Simulating Object [" + theFumeFX[theIndex] + "]")
du.LogMessage (">Output Path [" + theObject.GetPath #default + "]")
theObject.BackBurnerSim = true
theObject.RunSimulation 0	

You might have to pass some info to the Job during submission, and then read that info in the Job Script to determine what to do in each Task…

Thanks for this suggestion. I will try this today and report :slight_smile:

it all works like it should! thank you very much bobo - you always helped me a lot over the years. much appreciated!

another thing now came up. people still want to see the render progress from Corona. It is easy for me to optain these information using the maxscript corona functions - BUT I need to call them regularly during rendertime to set the job title with the DeadlineUtil functions. the only way I could think of was creating a small dialoge with a timer. as I am not rendering with the Corona Frame Buffer beeing shown this is also a good place to put a button to stop the rendering prematurely with the rest of the code and the files saving still possible - so in a emergency szenario the user can access the blade via RDP and stop the rendering and still does not loose the process…

this all seems to work fine when testing locally, however I have the feeling that for some reason Deadline is not allowing for dialoges to be created - or is it the Max Slave mode it is all running in?

however - how else could I at least optain the render progress info (without any button etc) to be sent back to Deadline?

ok. i narrowed it down now.

while not able to see the actual rollout beeing created it is still possible to set a timer there and get code executed regularly in the background while rendering with corona on the farm. the issue is though, that the CoronaRenderer.CoronaFp.getStatistic function will always return 0 when max is in slave/nonUI mode. it is at least defined however. still not enough for me as i want render progress information.

If a Corona Render Job is submitted regularly the Render Progress information is sent to Deadline. So there needs to be another way to get this information. So how did you Deadline guys got it working?

You can look at the Corona stand-alone integration plugin under

<DeadlineRepository10>\plugins\Corona\Corona.py

A Deadline plugin registers a callback function which performs pattern matching on the standard output of the application being rendered.

In the case of 3ds Max running Corona, the 3dsmax.py script is capturing the progress in a similar way, but it is communicating with 3ds Max in a different way that you cannot easily replicate. Deadline uses a MAXScript Plugin called lightning.dlx loaded when 3ds Max starts to communicate with the application via the MAX API. You can find the function LightingFunctionHandler() in the 3dsmax.py and see how it is trying to RegEx the progress of the renderer. That function is being called from a function PollUntilComplete() which is polling the application to see when it is done rendering.

Basically what you are trying to do, but running as part of the integration plugin.

In your case, your MAXScript code is what is actually running, and the integration plugin is sitting and waiting to receive a “true” return value to know the script is done. So you are one level deeper, and you can communicate with the Lightning plugin via the DeadlineUtil interface, but it is hard to talk to a process that was launched via MAXScript’s render() command while it is running. In fact, in older versions of 3ds Max MAXScript would block the main Max thread and would not let you do anything until the function it launched has exited.

It would be interesting to know if running with Force Workstation Mode on (3ds Max with UI and license) will behave differently when running your script. If that works but network mode breaks it, then the problem is somewhere between MAXScript and Corona knowing you are in network mode…

thanks for this hint. i could now verify that it is not the slave mode that makes the corona functions return 0… it is the render() function itself. if i load in my rollout with timer in a max workstation session locally it can fetch the render status information when I hit the render button in the render scene dialog, but it is unable to do so if i just call the render function…

the problem is now, that If I would use “max quick render” to actually render an image, the code will not wait until rendering is finished. it will just execute the rest straight away. I could setup corona in the way it freezes max during render - but this also freezes my timer as well :crazy_face:

so the render() function seems to work on a different level and works fine as long as i dont want to get any information during the render… I guess I will have to live with that…

In Krakatoa, I used max quick render to perform Partitioning, with two callbacks registered to detect the start and end of the frame rendering:

		callbacks.removeScripts id:#Krakatoa_TP_Partitioning
		callbacks.addScript #preRenderFrame "Krakatoa_GUI_Partition.preRenderFrameFunction()"  id:#Krakatoa_TP_Partitioning
		callbacks.addScript #postRenderFrame "Krakatoa_GUI_Partition.postRenderFrameFunction (callbacks.notificationParam())"  id:#Krakatoa_TP_Partitioning

I was using this mainly to get a timestamp() and measure the time in milliseconds to process a frame.

You could do something like that before calling max quick render, and loop inside your Deadline MAXScript code until the second function notifies you that the frame was finished, at which point you could do another loop with another max quick render, until you are done with all iterations…

That “wait” loop could be set to exit when a variable initially set to False by the #preRenderFrame callback is set to True by the #postRenderFrame callback, or after a certain max. frame duration timeout has passed (to avoid getting stuck in a “forever” loop if the callback never gets called).

I will defenitely test this, thanks a lot for your suggestion!

One thing though, If I loop as long as the rendering has been finished maxscript will try hard to get the one core it is running on maxed out at 100% capacity the whole time. This will bring down the render speed for the actual job the slave has to do. I will have to see how to minimize this effect with some wait commands etc…

You can pause within the loop via sleep() to avoid high CPU usage on one thread. Maybe pause for 5 seconds, check the variable, repeat…

http://help.autodesk.com/view/MAXDEV/2021/ENU/?guid=GUID-F7862930-9F54-43E3-A767-AF6B67702539

Sorry if I am spamming here…

I have to share some of my code I guess. my code runs without producing an error. If I dont have a callback installed it seems to just rush through the end of the script and set the task as finished.

if I place in the callback loop it seems to be stuck forever at 2%cpu while not rendering a single pixel.

the same code runs perfectly fine locally though.

so at the beginning I am declaring the render process to be true

global DL_is_currently_rendering = true
callbacks.removeScripts id:#DL_Render_Start_End
callbacks.addScript #postRenderFrame "DL_is_currently_rendering = false"  id:#DL_Render_Start_End

then comes the usual deadlineUtil stuff, then i am defining my listener rollout:

rollout dl_render_monitor_fl "render progress monitor"(
	timer tim interval:1000
	button stop_rend "stop current rendering (saving progress)"
	label curr_statz "Deadline Render Status:"
	local tim_c = 0
	
	fn upd_status=(
		
		if classof renderers.current as string == "CoronaRenderer" then(
		
			num_passes = (CoronaRenderer.CoronaFp.getStatistic 0) as integer
			time_rema = (((CoronaRenderer.CoronaFp.getStatistic 8)/1000.0)/60.0) as float
			time_used = (((CoronaRenderer.CoronaFp.getStatistic 5)/1000.0)/60.0) as float
			tot_passes = renderers.current.progressive_passLimit
			progress = (time_used/((time_used+time_rema)*1.0))*100.0
			status_ = ((num_passes as string) + "/" + (tot_passes as string) + " Passes done, " + (time_rema as string) + " minutes remaining")
			
			
			deadlineUtil_Median.SetTitle(status_)
			deadlineUtil_Median.SetProgress(progress)
			
			
			curr_statz.caption = "Render status: "+status_

		)
	)
	
	
	on tim tick do(
		upd_status()
	)
	
	on dl_render_monitor_fl open do(
		upd_status()
	)
	
	on stop_rend pressed do(
		CoronaRenderer.CoronaFp.stopRender()
	)
	
)

I do some preadaptations to the scene that are executed fine (as i can see in my log)
and then:

deadlineUtil_Median.SetTitle "Median Passbunch Job - RENDERING" --set the job title 
slidertime = rendStart

createdialog dl_render_monitor_fl 350 48 20 20
max quick render

while DL_is_currently_rendering do(
	sleep 5
)

destroydialog dl_render_monitor_fl

so as far as I see it, it would call the listener dialog (wich it does, I see it appearing on the blades) and immediately after start the rendering process - wich seems, it does not. The listener dialog is also frozen, you can not hit the stop button.

what I see and get is the status beeing reported to deadline, but this never gets updated, it stays at 0% all the time… most likely because it is indeed not rendering (CPU 2%) or because the listener dialog is frozen and the timer does not tick properly

like i said, this all works fine locally.

when I declare

global DL_is_currently_rendering = false

from the beginning and add another callback for renderstart It will just jump over the code and quickly finish without anything beeing rendered.

The same principle code:

global DL_is_currently_rendering = false


rollout dl_render_monitor_fl "render progress monitor"(
	timer tim interval:1000
	button stop_rend "stop current rendering (saving progress)"
	label curr_statz "Deadline Render Status:"
	local tim_c = 0
	
	fn upd_status=(
		
		if classof renderers.current as string == "CoronaRenderer" then(
			if DL_is_currently_rendering then(
				num_passes = (CoronaRenderer.CoronaFp.getStatistic 0) as integer
				time_rema = (((CoronaRenderer.CoronaFp.getStatistic 8)/1000.0)/60.0) as float
				time_used = (((CoronaRenderer.CoronaFp.getStatistic 5)/1000.0)/60.0) as float
				tot_passes = renderers.current.progressive_passLimit
				progress = (time_used/((time_used+time_rema)*1.0))*100.0
				status_ = ((num_passes as string) + "/" + (tot_passes as string) + " Passes done, " + (time_rema as string) + " minutes remaining")
				
				
				--deadlineUtil_Median.SetTitle(status_)
				--deadlineUtil_Median.SetProgress(progress)
				
				curr_statz.caption = "Render status: "+status_
				print status_
			)else(
				curr_statz.caption = "Render status: not rendering"
			)
		)
	)
	
	
	on tim tick do(
		upd_status()
	)
	
	on dl_render_monitor_fl open do(
		upd_status()
	)
	
	on stop_rend pressed do(
		CoronaRenderer.CoronaFp.stopRender()
	)
	
)



createdialog dl_render_monitor_fl 350 48 20 20

callbacks.removeScripts id:#DL_Render_Start_End
callbacks.addScript #preRenderFrame "DL_is_currently_rendering = true"  id:#DL_Render_Start_End
callbacks.addScript #postRenderFrame "DL_is_currently_rendering = false"  id:#DL_Render_Start_End


max quick render


while DL_is_currently_rendering do(
	sleep 5
)


destroydialog dl_render_monitor_fl

so this above - will work locally as intended. I have no clue why it behaves so differently on the farm. It seems that the callbacks do not work there

EDIT: also the callbacks do not work when defined to be persistent before submitting the job

Is your Worker running in Force Workstation mode, or network render mode? Does it make any difference?

network render mode. even if it would work fine in workstation mode, it would not be a suitable solution for us though. However I can still test it next week.

so in workstation mode it works as it should

unfortunately - as i said, this would not be an option for us as we would need 40+ extra max licenses just for the sake of seeing the render progress this way…

Privacy | Site terms | Cookie preferences