Python script runs different under 3dsmaxbatch or 3dsmax.exe Quiet Mode

Python script runs different under 3dsmaxbatch or 3dsmax.exe Quiet Mode

gt-crunch-dev
Explorer Explorer
1,219 Views
2 Replies
Message 1 of 3

Python script runs different under 3dsmaxbatch or 3dsmax.exe Quiet Mode

gt-crunch-dev
Explorer
Explorer

Hello,

 

I have 2 questions regarding running 3DS Max from command line:

  1. Is there a way to have 3dsmaxbatch.exe run a script with Quiet Mode disabled?
  2. If running my Python script through 3dsmax.exe, are there ways for me to pass in customized parameters, as well as redirecting the outputs from the Listener Log and System Log?

 

Here’s the context of what I’m trying to do:

 

I am working on automating parts of the 3DS Max art asset pipeline, which involves my Python script parsing some text files and calling a function from a MaxScript written by another technical artist.

 

After first, I launched the Python script with 3dsmaxbatch.exe, which worked fine enough at first:

3dsmaxbatch.exe .\maxBatchTest_exportFunc.py -listenerlog <custom_listener_log > -v 5 -log <custom_sys_log> -mxsString someParam1:<my_parama_1> -mxsString someParam2:<my_parama_2>​

 

However, recently I noticed that the same script might run with different results based on how I launch 3DS Max to run my Python script.

 

To sum things up, running my Python script with 3dsmaxbatch.exe or 3dsmax.exe in Quiet mode (with “-silent”) will skip certain error checking function which would have run if the same script is ran with standard 3dsmax.exe or within 3ds Max’s “Scripts > Run Script…”.

 

As a unit test, if maxBatchTest_exportFunc.py is:

import MaxPlus
import sys, os

def main():
    strMaxCmd = "OurFileExportFunc(\"Q:/some/known/source_max_file.max\")(#some_flag)(\"N:/\")"

    r = MaxPlus.FPValue()
    bOK = None
    bOK = MaxPlus.Core.EvalMAXScript( strMaxCmd, r)
    print "[{0}] Type: {1} ==> {2}".format( bOK, r.Type, r.Get() )

if __name__ == '__main__':
    main()

Running this script through these commands:

3dsmaxbatch.exe maxBatchTest_exportFunc.py -listenerlog .\maxBatchTest_exportFunc_Listener.log

3dsmax.exe -u PythonHost maxBatchTest_exportFunc.py -silent

… will have results different from:

3dsmax.exe -u PythonHost maxBatchTest_exportFunc.py

For either cases, r is of Type 8 (String), and its content will include the result of the function, including its success and fail status. It’s from this that we notice that the former will run to completion as with a success status, while the other will fail due to error check routines.

 

This could be something on the Maxscript side. The TA who wrote the Maxscript mentioned that his script’s error checking routine uses values from certain dialogs and UI. Since Quiet mode suppresses UI elements, and 3dsmaxbatch’s purpose is more on running batch job and less for UI interaction, not having access to certain UI components might affect those routines.

 

However, before I get into a deeper discussion of how the Maxscript works, I also want to get those 2 questions out there to have a better understanding on how we can tackle this.

 

Thank you and stay well.

0 Likes
1,220 Views
2 Replies
Replies (2)
Message 2 of 3

attilaszabo
Alumni
Alumni

Hi @gt-crunch-dev ,

 

3dsmaxbatch.exe is meant to run in silent (quiet) mode, which means dialogs coded to respect that mode will not open.

The whole point of 3dsmaxbatch, is to allow for non-interactive processing or execution of tasks.

The function OurFileExportFunc() should not rely on data stored in a dialog. The data should be perhaps passed to this function as parameters. In fact, this would be a wise software design even when running your script in a non-silent mode 3ds Max.

 

I hope this helps,

Attila Szabo

Product Owner, 3ds Max

Autodesk

Attila Szabo
Product Owner, 3ds Max
Autodesk
0 Likes
Message 3 of 3

gt-crunch-dev
Explorer
Explorer

Just an update, but I managed to scratch up a Unit Test where the Python script makes Maxscript run the loadMaxFile function.

 

unitLoadMaxFileFunc.ms:

(
  global UnitTest
  struct UnitTestClass
  (
    fn Load filename useUnitsInFile:false quiet:true =
      (
        if SceneConverter != undefined do
        (
          SceneConverter.BackupOriginalFiles = false  -- how this turns itself on is beyond me, but its extremely annoying
        )

        local out
  			if DoesFileExist(filename) then
  			(
  				local missingFiles = #();
  				local missingXrefs = #();
  				local loaded =
  							try(
  								loadMaxFile filename missingExtFilesList:&missingFiles missingXRefsList:&missingXrefs missingExtFilesAction:#logmsg missingXRefsAction:#logmsg  useFileUnits:useUnitsInFile quiet:quiet
  							)
  							catch(format "*** % ***\n" (getCurrentException()) )

  				out = #(loaded, join missingFiles missingXrefs)
  			)
  			else
  			(
  				out = #(false, undefined)
  			)
  			/*return*/ out as string
      )
  )

  UnitTest = UnitTestClass();
)

 

The Python script uses pymxs to initiate UnitTestClass and load a max file,

maxBatchTest_Load.py:

import MaxPlus
from pymxs import runtime as rt
import sys, os
from pathlib2 import Path, PurePath, PurePosixPath

def ListenerPrint( strOutput ):
    print "[ListenerLog]\t" + strOutput

def ConvertBackSlashToLiteral( strOrgPath ):
    return strOrgPath.replace( '\\', "\\\\")

def main():
    strMaxCmd = None
    strLoadMaxFile = "Q:/some/path/to/myFile.max"

    # Import Unit Test module
    scriptDirPath = Path(os.getcwd())
    unitFuncFilePath = scriptDirPath / Path("unitLoadMaxFileFunc.ms")
    print(unitFuncFilePath)
    strMaxCmd = "fileIn \"{}\" quiet:true".format( ConvertBackSlashToLiteral( str(unitFuncFilePath) ) )
    ListenerPrint("Run Maxscript Cmd with pymxs => " + strMaxCmd)
    rt.execute( strMaxCmd )

    # Make sure UnitTest is loaded
    strMaxCmd = "UnitTest"
    ListenerPrint("Run Maxscript Cmd with pymxs => " + strMaxCmd)
    rt.execute( "r_modProb = " + strMaxCmd )
    ListenerPrint( "rt.r_modProb is {}".format( rt.classOf( rt.r_modProb ) ) )

    # Run the load test
    strMaxCmd = "r_mxs = UnitTest.Load(\"{}\") quiet:true useUnitsInFile:true".format( strLoadMaxFile )

    ListenerPrint("Run Maxscript Cmd with pymxs => " + strMaxCmd)
    rt.execute( strMaxCmd )
    ListenerPrint("r_mxs = ".format( rt.r_mxs ) )
    ListenerPrint("r_mxs is class of: ".format(rt.classOf( rt.r_mxs )))
    pyMxsStr = str( rt.r_mxs )
    ListenerPrint("pyMxsStr = " + pyMxsStr )

if __name__ == '__main__':
    main()

 

I’ve tried running the Python script by both 3dsmax.exe and 3dsmaxbatch.exe:

3dsmax.exe -u PythonHost maxBatchTest_Load.py 
3dsmaxbatch.exe maxBatchTest_Load.py -listenerlog .\maxBatchTest_Load_Listener.log
3dsmax.exe -u PythonHost maxBatchTest_Load.py -silent

 

Whereas the first method will show the max’s missing files in the Listener Log,

[ListenerLog] Run Maxscript Cmd with pymxs => fileIn “c:\<project_path>\unitLoadMaxFileFunc.ms” quiet:true
[ListenerLog] Run Maxscript Cmd with pymxs => UnitTest
[ListenerLog] rt.r_modProb is #Struct:UnitTestClass(load:; Public)
[ListenerLog] Run Maxscript Cmd with pymxs => r_mxs = UnitTest.Load(“Q:/<art_asset_path>/bealscreek03.max”) quiet:true useUnitsInFile:true

[ListenerLog] r_mxs =

[ListenerLog] r_mxs is class of:

[ListenerLog] pyMxsStr = #(true, #(“Q:\static\golf\shaders\cg\animated\river__vertexblend_3.0_reflect.fx”, “Q:\static\golf\shaders\cg\ground\alphablend__1.0.fx”, ....,
“Q:\static\golf\shaders\cg\camera_interaction\lambert__alpha_by_distance_1.0_projmap.fx”, “Q:\static\golf\shaders\cg\standard\lambert__cloud_1.0.fx”))

 

… the latter 2 will claim there are no missing files.

[ListenerLog] Run Maxscript Cmd with pymxs => fileIn “c:\projects\nighthawk\GTG5_AUTOBUILD_JENKINS_3DSMAX_EXPORT\testing\unitLoadMaxFileFunc.ms” quiet:true
[ListenerLog] Run Maxscript Cmd with pymxs => UnitTest
[ListenerLog] rt.r_modProb is #Struct:UnitTestClass(load:; Public)
[ListenerLog] Run Maxscript Cmd with pymxs => r_mxs = UnitTest.Load(“Q:/<art_asset_path>/bealscreek03.max”) quiet:true useUnitsInFile:true
[ListenerLog] r_mxs =
[ListenerLog] r_mxs is class of:
[ListenerLog] pyMxsStr = #(true, #())

 

So it looks like loadMaxFile isn’t playing nice with 3dsmaxbatch.exe. I’m not sure how else I can get around this, but we previously had an application that sets up 3DS Max as a server that communicates with clients through TCP sockets, a long, long time ago.

 

If I really need to switch to the server/client model, what’s the best way for me to approach this problem? I mostly work with C/C++ and Python and I'm less familiar with .Net and Maxscript, so this switch in method won't be trivial for me.

 

Besides, 3dsmaxbatch allows me to easily redirect listener logs and pass customized parameters, which is a plus for automation tasks. Is there truly no way to have 3dsmaxbatch.exe evoke loadMaxFile with the missing files properly addressed?

 

Thank you and stay well,
Ingrid

0 Likes