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.

ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand(MXSstring)

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 –

  • macros.run <category_string> <name_string>
  • macros.run <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

Else

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("macros.run " & 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.

UPDATE –

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()
--dotNetObject:ManagedServices.CuiUpdater
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.

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

Dark UI –

Light UI -Automatically!

download script

9 Comments + Add Comment

  • avatar

    crap, just realized how old this is… sorry, probably old redundant information

  • avatar

    if i understand you right, to debug properly in visual studio,
    1. Start visual studio as administrator (if UAC is enabled)
    2. You can either
    2a. load your solution, right click and hit add existing project, and select the 3dsmax.exe file.
    2b. or you can choose open project, and select the 3dsmax.exe file, then add a new dll project
    3. set the 3dsmax.exe project as your startup project
    4. in your dll project, set the build output path to where they need to be for 3ds max to load them (/bin/assemblies i think?)
    5. make sure copy local is set to false on all your max references
    6. hit f5 and debug!

    if you have issues with the debugger, you can also right click and choose properties on the 3dsmax.exe project and tamper with things in there to get it to work B) learned this from kean on debugging autocad when visual studio 2010 came out. hope that helps!

  • avatar

    Hi, I create plugin for 3D max with Visual basic 6 and need call maxxscript function. ¿this is possible with Visual Basic 6 an ManagedServices.dll?

  • avatar

    Hi Darren,

    Debugging the managedservices .dll in VS is a bit of a dark art for me currently also – I have the same issue. From what I can work out, the assembly can only be run within 3dsMax in order for it to work. As I dont know VS very well, there might be a way of linking the build event with max somehow. I just havent figured it out. For me it means coding blind and debugging by testing in Max each time. Not ideal. You can set VS up to copy the compliled dll into your max testing directory via a DOS command as a post build event to make this quicker. If anyone can find out how to do this, or if it is possible then please let me know!

  • avatar

    Thanks for posting this. I am running into an error when trying to reference the ManagedServices.dll in Max2010 directory. The error I receive is:

    {“Could not load file or assembly ‘ManagedServices, Version=1.0.3559.5146, Culture=neutral, PublicKeyToken=null’ or one of its dependencies. The system cannot find the file specified.”:”ManagedServices, Version=1.0.3559.5146, Culture=neutral, PublicKeyToken=null”}

    I have the latest hotfix for Max 2010 and am running VS2008 and .NET 3.5. Intellisense in .NET sees the methods fine but causes this error when I try to run the code.

    Thanks for your help.

  • avatar

    hi james, are you using 2010? if so have you tried using the “@” symbol in front of the string that you pass from the dotnet assembly? it will ignore any escape characters! cheers Pete

  • avatar

    Looks like there’s an issue with escape characters in the file path. So “\t:” is being treated as a tab. On some of my scripts, since they’re in a folder that starts with a “t”, it’s just printing out a message instead of running it…
    “MaxScript Run Via DotNet – c:\main ools\max\functions\addDataChannel.ms”

  • avatar

    Hi Dave, Thanks for posting – I’d forgotten to add the download to the end. Should be all good now.

  • avatar

    Hey Pete!

    Looks like some nice script browsing panels you got going there. Anything downloadable for the masses!?

    😀

Got anything to say? Go ahead and leave a comment!

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>