• 1/2

Dynamically Compiling and Running C# Scripts for 3ds Max

Posted by Christopher Diggins, 26 October 2012 3:55 pm

[EDIT: Nov. 12, 2012 I've updated the editor to fix a bug in the editor and include the source code]

For impatient 3ds Max developers the ability to compile and run C# programs from within 3ds Max without having to restart it can be very useful. Since I am about as impatient as they come I developed a simple plug-in for 3ds Max that allows me to edit and run C# programs as if they were scripts.

You can download the C# script editor plug-in and source code here. To install it copy the plug-in DLL file (and optionaly the PDB debug database) into the "binassemblies" sub-folder of your 3ds Max 2013 installation.  This should work on both 32-bit and 64-bit flavors of 3ds Max.

Next you will have to "unblock" the DLL (at least for Windows 7, I'm not sure if it is required for Vista). Right click on the file in Windows Explorer after copying it to the "binassemblies" folder and press the unblock window.

When you restart 3ds Max, you will have a new user action in the category ".NET Plug-ins" that you will need to associate with a shortcut key or menu item. The plug-in should work for both 32-bits and 64-bit flavors of 3ds Max, but won't work on 3ds Max 2012 or earlier.

I've also included a sample program called "DemoScript_BentCylinder.cs" that you should be able to run from the editor.

script editor screenshot

Implementation Details

The following code compiles the source code of one or more files each passed as a string.

/// Looks for and executes a public static function named Main by searching all types in the assembly.
/// If more than one Main function is found or no Main function is found an exception is thrown.
public static void RunMain(Assembly asm, params string[] args)
{
    MethodInfo main = null;
    foreach (var t in asm.GetTypes())
        foreach (var mi in t.GetMethods(BindingFlags.Public | BindingFlags.Static))
            if (mi.Name == "Main")
                if (main != null)
                    throw new Exception("Multiple Main methods found");
                else
                    main = mi;
    if (main == null)
        throw new Exception("No static public Main method found in assembly");
    main.Invoke(null, new object[] { args });
}

/// Compiles source code given the language provider (e.g. CSharpCodeProvider or VBCodeProvider).
/// The named assemblies are referenced. The contents of each source code file is passed as a
/// separate "input" string.  Returns the generated assembly if successful or a list of errors.
public static CompilerResults Compile(CodeDomProvider provider, IEnumerable<string> assemblies, params string[] input)
{
    var param = new System.CodeDom.Compiler.CompilerParameters();
    param.GenerateInMemory = true;
    foreach (var asm in assemblies)
        param.ReferencedAssemblies.Add(asm);
    return provider.CompileAssemblyFromSource(param, input);
}

If compilation is successful the generated assembly is returned as part of the CompilerResults. Once you have the assembly you can invoke the Main entry point as long as it is a public static method of any public type using the following bit of reflection magic:

        public static void RunMain(Assembly asm, params string[] args)
        {
            MethodInfo main = null;
            foreach (var t in asm.GetTypes())
                foreach (var mi in t.GetMethods(BindingFlags.Public | BindingFlags.Static))
                    if (mi.Name == "Main")
                        main = mi;
            if (main == null)
                throw new Exception("No static public Main method found in assembly");
            main.Invoke(null, new object[] { args });
        }

The rest of the code in the plug-in is pretty unremarkable but. I'd love to hear if this tool (or some derivation) finds its way into your toolbox.

11 Comments

Hirazi Blue

Posted 30 October 2012 10:14 am

Would this be (relatively) easy to port to other apps (say for instance Softimage, my own "weapon of choice" or is the code too Max-specific for that?

Hirazi Blue

Posted 30 October 2012 12:40 pm

Forget my previous comment. It was pointed out to me, Softimage has had something similar all along.

Christopher Diggins

Posted 30 October 2012 2:19 pm

Hello Hirazi, yes should be able to easily adapt the code to any .NET enabled application including Softimage. Softimage does make it easy to load/unload C# plug-ins written using Visual Studio but this tool would allow you to side-step several steps.

Hirazi Blue

Posted 30 October 2012 7:33 pm

Hi Christopher,
That's what I thought too, but then someone (or actually two people) pointed me to the following page in the Softimage SDK documentation that actually does seem to do something very similar: http://bit.ly/YjW5fB

Ainur

Posted 31 October 2012 11:06 pm

U Da Man, Chris!
Keep up the impatience!

Can this plugin be adapted for IronPython?

I really like the idea of using the python language to build tools, especially with UI designers like the WPF/XAML stuff in VS...

I tried to compile a simple IronPython script with a simple XAML ui, and the assembly build worked, but 3dsmax would not accept the generated assembly...

Christopher Diggins

Posted 2 November 2012 6:54 pm

@Hirazi Thanks for educating me about that. We are looking into making this information more discoverable in the SDK documentation.

@Ainur I'm pretty sure that the code could be adapted to support IronPython. I wrote a similar tool for 3ds Max 2012 SAP. So you can use the source code from both. I have never tried to create an IronPython assembly, so I don't know what could be causing the problem.

Caspar Philippo

Posted 9 November 2012 10:28 am

Hi, This is great development. SDK was always a step too far for us, hopefully this will help to bring it a big step closer.

Is it also possible to compile a 'standalone' plugin with this method?

Jeff_Hanna

Posted 9 November 2012 4:15 pm

Is that zip file supposed to have the source code for the your editor? Your post makes it sound like it should. But the zip only contains a .DLL file, a .PDB file, and the BentCylinder.cs sample file.

Christopher Diggins

Posted 13 November 2012 3:49 am

@Jeff_Hanna I've updated the package to fix a bug in the editor (random tabs occuring) and to include the source.

sinushawa

Posted 15 January 2013 9:38 am

Hello and thanks for this tool.
I am having a problem with the del key not working in max viewport once the scriptEditor has been run and can't seem to find in the source code where it could come from, any idea?

Christopher Diggins

Posted 15 January 2013 5:36 pm

@sinushawa
The problem is probably related to the fact that main form derives from MaxForm. The problem is a conflict between handling key presses in Max and in the editor. I'm not sure how to resolve the issue, and I won't be able to investigate it in the short-term.

@caspar
Sorry for the long delay. No you can't compile a standalone plug-in at the current time.

Add Your Comment

You must be logged in to post a comment.

Please only report comments that are spam or abusive.