Calling Maxscript Functions from DotNet

May 11, 2009 by     9 Comments    Posted under: DotNet, Technical Research

I thought I’d draw attention to a few enhancements that Autodesk has made with the new release in the hope that it will help you with your scripts. The private/public methods in struct declaration in MXS was a welcome addition, but I was hoping for a few extras in the dotnet area too. The main one to note is –

dotNet.setLifetimeControl {<dotNetObject> | <dotNetClass>} {#mxs | #dotnet}

This addresses the problem with assigning event handlers to dotnet objects in max -They would be removed as soon as Max triggered a garbage collect. I’m pleased about this – I had resorted to inherting controls that contained the handler’s functionality in order to use them in Max, this means that now it is possible to avoid the problem of non-functioning controls. It was something that I (and probably many others) had reported as a bug, and despite the fact that I’m sure it would have been noticed anyway, It is good that you feel that the bug submission system does actually get read by somebody. Good job Autodesk! Have a biscuit!

I don’t tend to read The Area a great deal, but I stumbled upon a blog by the Audodesk SDK chap Chris Diggins. He wrote that there are two really great addtions to the dotnet toolkit this release – the most important being the ability to invoke maxscript from within a Dotnet assembly.

Max automatically loads an assembly called ManagedServices.dll, and in this release two classes have been added –

  • MaxscriptSDK
  • PathSDK

PathSDK is the equivalent of the old getdir #scripts method in MXS, so is useful when you want to tie a path to a Max install but you dont want to hardcode it, but MaxscriptSDK is a great addition potentially to anyone thinking about tinkering with 3dsMax gubbins from dotnet.


This method allows you to pass a Maxscript command from an assembly as a string. In Chris D’s article he mentioned that he’d written a script manager tool in C#. Since I thought i’d look into this I thought i’d have a go at writing one in VB. (I hope you don’t mind Chris!)

This is what i came up with – It’s a custom user control which has an inherited treeview and some radio buttons tied to the new PathSDK members. The thing to remember with radiobuttons in dotnet is that they have a Flat style property, so can be changed from the default radiobutton look. This means you can build a sort of flat tabpanel very easily that contains the same sort of functionality as a normal one, but with a more attactive look. The treeview has the search functionality built in, and also has an embedded imagelist so that each script type can have a different icon. You’ll notice the encrypted scripts have a little padlock on them.

Running a maxscript from the utility is easy with the new methods – just passing a FileIn method was the ticket – Note the VB special characters – these are so that you can pass a string within a string for the filename –

ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("FileIn " & Chr(34) & e.DirectoryName & Chr(34))

This is all very straightforward. However, when it came to handling Macroscripts, I needed to do something else, as there are two ways of running a macro –

  • <category_string> <name_string>
  • <macro_id_integer>

This would need some info passed back to the assembly from max – It seemed that the best way to do it was to grab the macro id as an integer and run it via that. Fortunately, there are a couple of methods to allow you to pass values from maxscript back into your dotnet assembly.

  • .ExecuteColorMaxscriptQuery()
  • .ExecuteFloatMaxscriptQuery()
  • .ExecuteIntMaxscriptQuery()
  • .ExecuteStringMaxscriptQuery()
  • .ExecuteBooleanMaxscriptQuery()

Since the macroID is an integer, the ExecuteIntMaxscriptQuery is the method we want. However, when sending the fileIn call via the MaxscriptSDK method, It wasn’t returing the MacroID integer, and needed a bit more coaxing to get the value back into the assembly. Using the MaxscriptSDK methods, clicking the tree registers a Maxscript function that takes the script path and returns the result of a FileIn call (which does in fact give the macroID, which is what we want. Once that is back into the dotnet assembly, you can run it via the method listed above. While this isn’t tricky, getting this syntax correct (including cases) seemded to be crucial in getting it working. Although it seemed a little unnecessary, the function is set to undefined afterwards to avoid having an unnecessary global floating around. So the full eventhandler in VB was as follows –

Private Sub DirectoryTree_DirectorySelected(ByVal sender As System.Object, ByVal e As MXSDotNet.DirectorySelectedEventArgs) Handles MXSDirectoryTree.DirectorySelected

Dim fileinfo As New DirectoryInfo(e.DirectoryName)

If Not fileinfo.Attributes = FileAttributes.Directory Then

If ((Control.ModifierKeys And Keys.Control) = Keys.Control) Then
 Select Case fileinfo.Extension
 Case Is = ".mse"
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("messageBox " & Chr(34) & "This is an encrypted script, so you won't be able to edit it" & Chr(34) & "Title:" & Chr(34) & "Maxscript messagebox called via DotNet" & Chr(34))
 Case Else
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("edit " & Chr(34) & e.DirectoryName & Chr(34))
 End Select


Select Case fileinfo.Extension
 Case Is = ".ms"
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("FileIn " & Chr(34) & e.DirectoryName & Chr(34))
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("Print " & Chr(34) & "MaxScript Run Via DotNet - " & e.DirectoryName & Chr(34))
 Case Is = ".mse"
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("FileIn " & Chr(34) & e.DirectoryName & Chr(34))
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("Print " & Chr(34) & "Encrypted MaxScript Run Via DotNet - " & e.DirectoryName & Chr(34))
 Case Is = ".mcr"
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("Fn GenerateMCRint Val = (Return (FileIn Val))")
 Dim macroID As Integer = ManagedServices.MaxscriptSDK.ExecuteIntMaxscriptQuery("GenerateMCRint " & Chr(34) & e.DirectoryName & Chr(34))
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand(" " & macroID.ToString)
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("GenerateMCRint = undefined")
 Case Else
 End Select

End If

End If

You might notice the ability to CTRL click the tree to open the script for editing, but the nice thing is that it’s all handled in the assembly. You can download the control at the end of the article. The nice thing about this is the code in max –

dotnet.loadassembly "C:Program FilesAutodesk3ds Max 2010ScriptsLoneRobotClassLibMXSDotNet"
rollout MXSviaDotNet "MXSviaDotNet" width:419 height:548
 dotNetControl MaxTV "MXSDotNet.MXSBot" pos:[0,0] width:420 height:546
 createdialog MXSviaDotNet

Minimal, eh? While this is a basic example of what this class can do, I’m really excited about the potential use of this new stuff in the DotNet arsenal.

Grabbing the UI Color from max as the assembly loads

One thing that these new methods can allow you to do is probe max for the UI colors and set these in your custom control. Normally you would have to set this in MXS in the open handler of the dialog, and there was usually a slight pause as it would do so. Using the following VB code implemented a method to grab this color and build a dotnetcolor from the r,g,b float values. Incidentaly, there looks like a method for color retrieval in this new class, but I’m buggered if i can get it to work. I’ve posted on Chris Diggin’s Blog on the Area to see if he can help out.

ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("maxBackColor = colorMan.getColor #background")
 ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand("maxForeColor = colorMan.getColor #text")
Dim br As Single = ManagedServices.MaxscriptSDK.ExecuteFloatMaxscriptQuery("(maxBackColor[1] * 255.0f)")
 Dim bg As Single = ManagedServices.MaxscriptSDK.ExecuteFloatMaxscriptQuery("(maxBackColor[2] * 255.0f)")
 Dim bb As Single = ManagedServices.MaxscriptSDK.ExecuteFloatMaxscriptQuery("(maxBackColor[3] * 255.0f)")
 Dim Backcolor As Drawing.Color = Drawing.Color.FromArgb(CInt(br), CInt(bg), CInt(bb))
 Dim fr As Single = ManagedServices.MaxscriptSDK.ExecuteFloatMaxscriptQuery("(maxForeColor[1] * 255.0f)")
 Dim fg As Single = ManagedServices.MaxscriptSDK.ExecuteFloatMaxscriptQuery("(maxForeColor[2] * 255.0f)")
 Dim fb As Single = ManagedServices.MaxscriptSDK.ExecuteFloatMaxscriptQuery("(maxForeColor[3] * 255.0f)")
 Dim Forecolor As Drawing.Color = Drawing.Color.FromArgb(CInt(fr), CInt(fg), CInt(fb))
 Me.MXSDirectoryTree.ProjectPath = ManagedServices.PathSDK.GetDirectoryPath(ManagedServices.PathSDK.DirectoryID.Scripts)
Me.ForeColor = Forecolor
 Me.BackColor = Backcolor

What this does is automatically set the ui colors of the control without interaction, much like how the maxform class does it.


In a previous post, I worked out a method for using maxscript calls to the colorman interface from the dotnetassembly to get the max UI colours. After this post, the talented Yannick Puech contacted me and pointed me to the CUIUpdater class in managedservices. I knew max must have had something like this, but hadn’t figured it out until now. Thanks a million, Yannick!

Using CUIUpdater

You can’t create an instance of CUIUpdater via dotnetobject. You have to call the getinstance function instead.

dotnetcolorman = (dotnetclass "Managedservices.cuiupdater").getinstance()
showmethods dotnetcolorman
--   .GetButtonDarkShadow()
--   .GetButtonLightShadow()
--   .GetButtonPressedColor()
--   .GetColor aColorIndex
--   .GetControlColor()
--   .GetEditControlColor()
--   .[static]GetInstance()
--   .GetMaxColor id
--   .GetTextColor()

As you can see, there are some useful things in there to call from an assembly.

-- dotNetObject:System.Drawing.Color
-- this is the colour of the interface
-- dotNetObject:System.Drawing.Color
-- the is colour of the text (if you hadn’t already guessed)

Dark UI –

Light UI -Automatically!

download script