loadMaxFile with 3dsmaxbatch will not show missing files & XRef

I am working on a Jenkins build pipeline, one stage involves exporting max file from 3DS Max (we use 2019.3) with our in-house Maxscript tools.

At first, I ran the 3DS Max task by running a Python script through 3dsmaxbatch.exe. For a while, it seems to work pretty well. Then, we noticed that some times, the task will claim a file is exported successfully, but the output files aren’t generated. If we try to run the Python manually in a standard 3DS Max instance, it will show that the max file has missing dependencies, which we would like the Pipeline to be informed of.

The TA who wrote the Maxscript pointed out that the crux might be where loadMaxFile is called. So as a Unit Test, I created a Maxscript wrapper class that executes loadMaxFile:

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();
)

Then I have another Python script that 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 "[ITListenerLog]\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,

[ITListenerLog] Run Maxscript Cmd with pymxs => fileIn “c:\projects\nighthawk\GTG5_AUTOBUILD_JENKINS_3DSMAX_EXPORT\testing\unitLoadMaxFileFunc.ms” quiet:true
[ITListenerLog] Run Maxscript Cmd with pymxs => UnitTest
[ITListenerLog] rt.r_modProb is #Struct:UnitTestClass(load:; Public)
[ITListenerLog] Run Maxscript Cmd with pymxs => r_mxs = UnitTest.Load(“Q:/static/golf/courses/2019/bealscreek/bealscreek03.max”) quiet:true useUnitsInFile:true

[ITListenerLog] r_mxs =

[ITListenerLog] r_mxs is class of:

[ITListenerLog] 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\ground\terrain__2.0.fx”, “Q:\static\golf\shaders\cg\standard_specular\normalmap__cloud_detail_fade_1.0.fx”, “Q:\static\golf\shaders\cg\ground\terrain__1.0.fx”, “Q:\static\golf\shaders\cg\ground\alphablend__nmap_1.0.fx”, “Q:\static\golf\shaders\cg\animated\ocean__3.0_vertshift.fx”, “Q:\static\golf\shaders\cg\ui\basic__color.fx”, “Q:\static\golf\shaders\cg\engine\engine__cutaway_1.0.fx”, “Q:\static\golf\shaders\cg\animated\pond__reflect_1.0.fx”, “Q:\static\golf\shaders\cg\engine\engine__vegetation_1.0.fx”, “Q:\static\golf\shaders\cg\standard\lambert__1.0.fx”, “Q:\static\golf\shaders\cg\standard\lambert__harmonic_1.0.fx”, “Q:\static\golf\shaders\cg\skinned\skin__lambert_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.

[ITListenerLog] Run Maxscript Cmd with pymxs => fileIn “c:\projects\nighthawk\GTG5_AUTOBUILD_JENKINS_3DSMAX_EXPORT\testing\unitLoadMaxFileFunc.ms” quiet:true
[ITListenerLog] Run Maxscript Cmd with pymxs => UnitTest
[ITListenerLog] rt.r_modProb is #Struct:UnitTestClass(load:; Public)
[ITListenerLog] Run Maxscript Cmd with pymxs => r_mxs = UnitTest.Load(“Q:/static/golf/courses/2019/bealscreek/bealscreek03.max”) quiet:true useUnitsInFile:true
[ITListenerLog] r_mxs =
[ITListenerLog] r_mxs is class of:
[ITListenerLog] 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; we previously had an application that sets up 3DS Max as a server that communicates with clients through TCP sockets, but the source code for the server code is mostly lost; the only thing I can be for sure is that it’s a .Net dll.

I’m still going back-and-forth with Autodesk regarding 3dsmaxbatch’s behavior with loadMaxFile. I mostly work with C/C++ and Python and am less familiar with .Net and Maxscript. If I really need to switch to the server/client model, what’s the best way for me to approach this problem?

Thank you and stay well,
Ingrid

Yeah sometimes things in 3dsmax don’t work as they should and you will just need to deal with it. Try to find an alternate solution to your problem. I’m not sure about the whole server/client model you’re talking about, i’m having trouble understanding how that would fit into your pipeline or what the goal of it would be.
The crux of your problem seems to be that the loadmaxfile function is not checking for missing files when using 3dsmaxbatch, try using the getMAXFileAssetMetadata function to check through the file before loading it. You’ll need to write your own recursive function to check through all scene assets but it might actually be faster than using loadmaxfile as it doesn’t require you to actually open the file.

http://help.autodesk.com/view/3DSMAX/2019/ENU/?guid=GUID-2AF2AFE0-1B22-4DA2-B0F9-CA3ADABCB6C6

1 Like