I don’t think we have plans for 3dsmaxbatch integration, but it should be possible to write a custom plugin for your specific case.
I am not sure how hard it would be.
I don’t think we have plans for 3dsmaxbatch integration, but it should be possible to write a custom plugin for your specific case.
I am not sure how hard it would be.
I am going to give it a try when I have a minute, will let you know how it goes…
Progress report:
Created a basic 3dsmaxbatch plugin based on the 3dsCmd plugin implementation, removed a ton of code related to DBR etc.
Created a basic 3dsMaxBatch Submission Workflow in SMTD to submit a job using the currently loaded scene and a custom script (MAXScript or Python) to run over it. I will make the scene optional in the future, and maybe allow external file(s) to be picked for processing instead of the current scene content.
Submitted a simple scene with a teapot on a plane, one light, one animated camera, Scanline Renderer, 101 frames.
Set the render output in the render scene dialog.
Used a super simple job script that reads the current Start and End Frames of the Deadline Task via the arguments dictionary maxOps.mxsCmdLineArgs
, sets the render time to them, and calls the current renderer:
rendTimeType=3
opts = maxOps.mxsCmdLineArgs
rendStart=(opts[#startFrame]) as integer
rendEnd=(opts[#endFrame]) as integer
max quick render
Partial log from the first Task:
This is of course just a simple proof of concept. I will try some more complex stuff like creating and modifying the scene content, saving the scene out, setting the render parameters and output via the script, etc.
I also need to figure out which SMTD parameters make sense to support and which ones to hide.
When I have something usable, I will post it here…
Fun in the sun - since the script is currently not included as an auxiliary file but is referenced via the Job Metadata (will make this optional in the future), I could modify it while the job was running for some colorful results:
rendTimeType=3
opts = maxOps.mxsCmdLineArgs
rendStart=(opts[#startFrame]) as integer
rendEnd=(opts[#endFrame]) as integer
theMaterial = standardMaterial diffusecolor:(random black white)
for o in geometry where classof o != TargetObject do o.material = theMaterial
max quick render
A small modification to render each object with a unique random color on each frame:
rendTimeType=3
opts = maxOps.mxsCmdLineArgs
rendStart=(opts[#startFrame]) as integer
rendEnd=(opts[#endFrame]) as integer
for o in geometry where classof o != TargetObject do
(
theMaterial = standardMaterial diffusecolor:(random black white)
o.material = theMaterial
)
max quick render
I then added the line
saveMaxFile (getFilenamePath rendOutputFilename + getFileNameFile maxFileName + rendStart as string + ".max")
to the end of the script and it saved the modified scene with the random colors for each frame. I need to convert the frame number to a padded value eventually, but in principle it seems to work well.
I made some further changes - the script is now always submitted as auxiliary file with the job to avoid unintended modifications to the output as shown in my previous post. One would have to edit the auxiliary file copy to do that.
On the other hand, the scene file submission respects the SMTD settings for submitting the scene with the job as auxiliary file, or referencing a custom, global, or original path network location via the SceneFile=
key value.
I also added 3 options for scene submission - all working well. Default is submitting the current scene, optionally you can select a file from disk, or no file at all. In the last case, the submitted script must construct the scene procedurally starting from nothing.
I also added support for most relevant properties in the SMTD UI.
Here is a WIP UI from the SMTD Workflow:
Ok, here is a super early Alpha WIP use-at-your-own-risk version of the 3dsmaxbatch plugin:
3dsmaxbatch_20210507.zip (14.3 KB) - REMOVED. See here for a new version.
Installation
SMTDWorkflow_3dsmaxbatch.ms
from the ZIP into your (Repo)\submission\3dsmax\Main\Workflows\
folder.3dsmaxbatch
folder from the ZIP into your (Repo)\plugins\
folder.UI:
The Batch Script File is mandatory. This is the script that will be run by 3dsmaxbatch.exe. It can be MAXScript or Python, and can do anything that can be done in 3ds Max. It will be sent as the first auxiliary file of the Job, so it can be located on a local drive or network share - either way a copy of it will be sent to the Repository.
The Scene File is optional. Radio buttons let you select one of the following options:
Submit Current Scene will save a copy of the scene according to the setting of the Assets tab > Scene and Asset Files rollout - either copied as the second auxiliary file with the job, or will be referenced via SceneFile=
in the Plugin params.
Submit External .MAX File will use a scene file from disk. It will also respect the Assets tab > Scene and Asset Files rollout settings and will be either copied as the second auxiliary file with the job, or will be referenced via SceneFile=
in the Plugin params.
No Scene - Create In Script will launch an empty 3dsMax scene. No scene file will be sent with the Job. It is up to you to generate content using the script.
In all 3 cases, the script can save the modified scene to any network path as part of the job.
Listener Log Path - This is optional and lets you specify a folder where each Task will write the script Listener Log file. The name of the log will look like mxslog_(jobName)_(TaskID).log
, for example “mxslog_3dsmaxbatchtest_1.log” if the Job was called “3dsmaxbatchtest” and this was Task 1. If you re-render the same Task multiple times, old logs will be overwritten. If you don’t specify a path, the Listener output will not be captured. You can use print() and format() calls in your script to write to the Listener.
They can be accessed from the Job’s script using maxOps.mxsCmdLineArgs[#CustomStringN]
and maxOps.mxsCmdLineArgs[#CustomValueN]
where N is an integer from 0 to 9. For example if you enter “Bobo was here” in the Custom String 0 field and submit a job, your script can do print maxOps.mxsCmdLineArgs[#CustomString0]
and you will see “Bobo was here” in the Listener Log.
The strings and values are exposed to the Job Properties > 3dsmaxbatch Settings panel and can be changed after submission to modify how the script behaves.
The rest of the SMTD rollouts have been reduced to only the controls that make sense.
I am sure there are a lot of bugs in the current version. Please report them in this thread!
Dang… you are still a machine.
Thanks a lot!!!
How would I access all these from MXS?
I assume there is a new method for 3damaxbatch job?
also I think we should have this exposed.
-i arg
The 3ds Max config file (.ini). Defaults to the per-user default config file (3dsmax.ini).
-p arg
The 3ds Max Plugin config file (.ini). Defaults to the per-user default plugin config file (Plugin.UserSettings.ini)
And a way to pass mxs value with method.
-mxsValue arg
MAXScript Value Parameter option, where arg = <key>:<value>.
<value> will be accessible as a MAXScript value in <script_file> through the maxOps.mxsCmdLineArgs dictionary using <key> as the dictionary key.
The <value> parameter must be enclosed in quotation marks.
Not sure what you are asking. The extra values and strings I exposed use the -mxsValue and -mxsString flags to pass data to the job, and you then use themaxOps.mxsCmdLineArgs[]
dictionary to access by name.
For example, if you enter “bobo was here” in the Custom String 1, then in the job script you do
str1 = maxOps.mxsCmdLineArgs[#CustomString1]
print str1
and the Listener log (if enabled) will show “bobo was here” as expected.
The same with values, except that they end up being MXS values and not just strings (but they are passed as strings via the job metadata). I added some precautions to the UI to ensure only valid MAXScript values are passed, but there are cases I am not handling. For example, if you select an object in the scene and type in the Custom Value 1 field $
, the value will be set to something like
$Teapot:Teapot001 @ [-1.355936,-1.807916,0.000000]
This will of course have no meaning once passed through the submission to the job script. (I might add extra tests to avoid node values from being entered). You could pass the $.inode.handle
instead, and resolve on the job script side using
maxOps.getNodeByHandle (maxOps.mxsCmdLineArgs[#CustomValue1] )
Or pass the $.name
and resolve using getNodeByName()
on the script side…
But if I enter $.pos
in the Workflow’s UI, it will expand to [-1.35594,-1.80792,0]
and that will travel through the metadata and be read as a Point3 value on the Job Script’s side. I tried in my test script
print maxOps.mxsCmdLineArgs[#CustomValue1].x
and it printed -1.35594
in the Listener Log.
I hear you about the custom plugin and config files. The regular 3dsmax plugin handles those within the custom plugin code, here I just need to expose two more text fields and pass the data to the job.
Now that I think about it, I could make both the name and the value customizable. So you would have two fields - one defining the key name, and one defining the value. Then, you could enter “MyVar1” in the first field, and $.pos in the value field, and then on the script side you would use maxOps.mxsCmdLineArgs[#MyVar1].x
instead of indexing by #CustomValue1
. I thought that using numbered slots with static and well-defined names would be easier, but it is not as flexible as naming your slots to make the script read better…
I can default all slot names to the current CustomValueN and CustomStringN patterns, so it would even be backwards compatible with the first iteration of the Workflow.
Something like this
Perfect.
I was asking how can I send 3dsMaxbatch job with SMTDSettings struct.
Here is an example of loading and calling the SMTD Workflow outside of the SMTD UI. You must have loaded the SubmitMaxToDeadline_Functions.ms before that.
(
global SMTDWorkflow_3dsMaxBatch
local the3dsmaxbathWorkflowFile = @"C:\DeadlineRepository\submission\3dsmax\Main\Workflows\SMTDWorkflow_3dsmaxbatch.ms"
fileIn the3dsmaxbathWorkflowFile --change the code above to detect your Repo, or hard-code to your Repo path
SMTDWorkflow_3dsMaxBatch.restoreSettings() --load settings from the .MAX file, if any
if not doesFileExist SMTDWorkflow_3dsMaxBatch.BatchScriptFile do SMTDWorkflow_3dsMaxBatch.BatchScriptFile = @"path\to\script.ms"
if not doesFileExist SMTDWorkflow_3dsMaxBatch.SceneFile do SMTDWorkflow_3dsMaxBatch.SceneFile = @"path\to\maxscene.max" --if you want an external file
if not doesFileExist SMTDWorkflow_3dsMaxBatch.ListenerLog do SMTDWorkflow_3dsMaxBatch.ListenerLog = @"path\to\listenerlogs.log" -- only if you want it
SMTDWorkflow_3dsMaxBatch.SubmitSceneMode = 2 --1 for current scene, 2 for external file, 3 for no scene
SMTDWorkflow_3dsMaxBatch.CustomString0Name = "BoboString"
SMTDWorkflow_3dsMaxBatch.CustomString0 = "Bobo was here"
--you can modify all 10 custom strings
SMTDWorkflow_3dsMaxBatch.CustomValue0Name = "ObjectPos"
SMTDWorkflow_3dsMaxBatch.CustomValue0 = [100.0,200.0,0.0]
--you can modify all 10 custom values
local canSubmitResult = SMTDWorkflow_3dsMaxBatch.canSubmit() --check if submission is allowed
format "%\n" canSubmitResult[2] --print the result of the test
if canSubmitResult[1] == true do --if submission is allowed, call the submit function
SMTDWorkflow_3dsMaxBatch.SubmitJob()
canSubmitResult[1]
)
This code will work with the next version I am working on right now. The same principles apply to the first version I dropped, except the CustomStringXName and CustomValueXName properties do not exist there.
The script will use some of the SMTDSettings like Pools, Groups, Priority, etc. Basically anything that is shown in the SMTD UI. You can find a full list by looking at the SubmitJob function - I write my own Job files there, so it is a very small subset of SMTDSettings that are being reused. You can set them in your own code before calling the submit function.
Here is WIP v2 from May 11, 2021:
3dsmaxbatch_20210511.zip (16.9 KB)
Changes:
Added Key Names for custom Strings and Values.
Added alternative 3dsmax.ini and plugin.ini support - both sent as auxiliary files with the job.
The Plugin Info will contain two keys pointing at the argument indices, e.g.
AlternativeConfigFileArgumentIndex=3
AlternativePluginFileArgumentIndex=2
These cannot be edited, they are for internal use only. They won’t be included if the respective files are not included.
The 3dsMax Settings panel exposes both the Key Name and Key Value - see screenshot in a previous post.
Added buttons on top of the Custom Strings and Values lists to reset all Keys and all Values.
Added the ability to reset the file names of the Script, Scene, Listener Log, and Alt. Config files by right-clicking the […] button. Added tooltips to inform the user about the new option.
Key Name validation will only allow lower and Capital case letters and numbers, all extra symbols will be replaced with underscore, e.g. entering “Object Pos.” will be changed to “Object_Pos_”
If the same Key Name is entered twice, an index with underscore prefix will be added, e.g. if you enter “ObjectPos” and then again “ObjectPos” in the next custom value field, the new entry will change to “ObjectPos_2”.
Updated all 3dsMaxBatch rollouts to show info when collapsed.
Fixed some bugs.
So… in theory, if we have a custom MXS submit code that using Deadline command line utility, I can just point to this plugin type and and supply Batch Script File path?
If you set Plugin=3dsmaxbatch
in the Job Info Params and provide the script file as first auxiliary file of the deadlinecommand call, then I would say the rest of your existing submission code for the 3dsCmd plugin should work.
But I have not tested that theory. Let me know how it goes.
@Bobo
so… working on an in-house 3dsmaxbatch plugin based on your plugin.
I found out DeadlineTuil.SetMessae is not working.
Could you confirm?
It works in my commandline-based plugin:
def RenderTasks( self ):
# Render frame(s) of job using CmdController class
self.Plugin.SetStatusMessage ('Rendering 3dsMaxBatch Tasks')
self.RenderFrame()
I copied this. But, setting progrses is not working.
self.AddStdoutHandlerCallback( ".*Progress: (\d+)%.*" ).HandleCallback += self.HandleProgress
def HandleProgress( self ):
progress = float( self.GetRegexMatch(1) )
self.SetProgress( progress )
One more question. I want to bypass some plugins “Failed to initialize” error. I added an popup handler. It doesn’t do anything…
I found out this is still nor included in installer. Please include this.