Tuesday, January 1, 2019

Undocumented 3ds Max SDK - MaxPlus and .NET Assemblies

3ds Max has an official .NET SDK which has some documentation and examples, but it is only the tip of the iceberg.

I happen to know that there are quite a few .NET DLLs (aka managed DLLs) that contain various APIs that can be found in the 3ds Max executable directory.

Hint: the 3ds Max executable can be found usually in %programfiles%\Autodesk\3ds Max 2019, or you can use the environment variable: ADSK_3DSMAX_x64_2019. If you ever forget, like me you can find it by typing in set in the command line. You can also type in %ADSK_3DSMAX_x64_2019% directly in Windows explorer to navigate to the folder.

For example the Max Creation Graph (MCG) is written entirely in .NET. and uses an unsupported .NET version of the MaxPlus API ( MaxPlusDotNet.dll) which is a C++ wrapper around the 3ds Max API exposed to Python.

So while it is undocumented and not supported, we do know the MaxPlus .NET API has being used internally extensively since 3ds Max 2016, and is probably not going away anytime soon (unless the 3ds Max team pulls the plug on MCG, or rewrites it).

This is not really a secret I am sharing: it is possible to load .NET assemblies programmatically using a number of tools (e.g. the C# Object Browser in Visual Studio) and even using your own code.

The .NET Assemblies

Below is a lightly edited list of all the .NET DLLs found by a simple tool I wrote. You can reference any of these DLLs in a Visual C# project, and view the contents using the Visual Studio Object Browser.

The Tool

Here is the source code for the tool I wrote for iterating over all DLL files in the 3ds Max directory and outputting whether or not is it a .NET DLL.

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace _3dsMaxSamples
{
    public class ReflectOverDlls
    {
        public static void OutputDllInfo(string file)
        {
            try
            {
                var asm = Assembly.ReflectionOnlyLoadFrom(file);
                Console.WriteLine($"File {file}");
            }
            catch (Exception e)
            {
                // This happens when we know that the API is not a .NET API
                //Console.WriteLine(e.Message); 
            }
        }

        public static void OutputTypes(Assembly asm)
        {
            foreach (var t in asm.GetTypes().OrderBy(t => t.FullName))
            {
                var kind = t.IsInterface ? "interface" : "class";
                Console.WriteLine($"  {kind} {t.FullName}");
                foreach (var c in t.GetConstructors())
                    Console.WriteLine($"    constructor {c}");
                foreach (var m in t.GetMethods())
                    Console.WriteLine($"    method {m}");
                foreach (var p in t.GetProperties())
                    Console.WriteLine($"    property {p}");
                foreach (var f in t.GetFields())
                    Console.WriteLine($"    field {f}");
            }
        }

        public static void Main()
        {
            var maxFolder = Environment.GetEnvironmentVariable("ADSK_3DSMAX_x64_2019");
            foreach (var f in Directory.GetFiles(maxFolder, "*.dll", SearchOption.AllDirectories)
                .OrderBy(f => f)
                .AsParallel())
            {
                OutputDllInfo(f);
            }
        }
    }
}