New DotNet control for download – HitchHiker

Sep 27, 2009 by     6 Comments    Posted under: DotNet, User Controls

I’m proud to announce the release of a new DotNet control for 3dsMax called HitchHiker. If anyone has read the related earlier post here they will know a little bit about the background of the control.

This release has some significant improvements in terms of the simplicity of integration.

What’s more, it’s credit crunch busting, global downturn reducingly FREE!

Click the HitchHiker tab at the top of the site to find out more information and get the download link to the control.


Shameless Plug Alert

Sep 15, 2009 by     No Comments    Posted under: DotNet

Bit of a shameless plug, but there has been a short article placed on Ken Pimental’s blog page on The Area. This has information about some of the DotNet character systems I have put in place in the animation pipeline at Impossible TV.

Read it here – Ken Pimental’s Blog


Supressing 3dsMax hotkeys in composite control assemblies

Sep 15, 2009 by     No Comments    Posted under: 3dsMax, DotNet, Maxscript, Tips and Tricks

The way to use a dotnet textbox on a max rollout is to use the gotfocus and lostfocus events. This allows you to know when the textbox has been highlighted for text entry, and use the enableaccelerators command to temporailly suspend the max hotkeys.

rollout tbtest “MXS Dotnet texbox” width:227 height:23
dotnetcontrol textboxctrl “TextBox” pos:[1,1] width:225 height:16

on textboxctrl gotfocus sender arg do enableacellerators = false
on textboxctrl lostfocus sender arg do enableacellerators = true
createdialog tbtest

In my previous article, I detailed a method that can automatically set the backcolor of a custom control using the managedservices.dll . A similar principle could be applied to the textbox. If you were building a custom control, you could override the gotfocus and lostfocus methods and use the managedservices dll to call the maxscript function. Then you have a control that has this functionality built in. It’s really simple, you just write a control that inherits the textbox and add the functionality you need. The VB code is below –

Public Class MaxTextBox
Inherits System.Windows.Forms.TextBox

Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
End Sub

Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
End Sub
End Class

Again, the solution is simple to implement in maxscript, so this would be perfectly okay. However, if you wanted to build a composite control that contained a textbox, you would be stuck with the same problem. The code above would be all you would need to add. You would use the maxtextbox inside a composite control, If you added this class to your solution in visual studio, you could drag the new inherited control and add 3dsMax functionality natively to the assembly.

I should mention that this method is only available in 3dsMax 2010

FloatSpinner – A custom control to bypass 3dsmax value casting issues

James Haywood of Bungie Studios recently pointed out an issue on CGTalk regarding the 3ds maxscript/dotnet conversion functions. Notably it was to do with a decimal from dot net being cast into and integer in 3ds max. This could present a problem if you are using a numericupdown control in max (This is the spinner equivalent in Dot net)

James noticed that any decimal values were automatically being cast into the nearest integer. If you check the MXS help, it does indeed support this fact.

From the list, it seemed that what was needed in order for this to function in 3dsmax properly was a spinner control that returned a dotnet single as it’s event handler rather than the current decimal. This is possible. You may have read the other articles about control inheritance and custom events. The new control will be using the same principles.

Control Inheritance

It’s the one form of inheritance that the treasury isn’t thinking of taxing people on. We inherit the numericupdown control, as it already contains the functionality we need in our control, (like how we use extends in MXS). The numericupdown becomes the base class of the control.In VB you refer to this by typing MyBase. This is similar to the way you can use delegate in a scripted plugin.

Once this is done, we have to override the properties we need to change. In VB, the rough syntax for setting up a property that stores any form of information within the assembly is as follows –

private _QuantumTheory As String
<Category(“Data”), Description(“Something that nobody understands”)> _
Public Property QuantumTheory() As String
Return _QuantumTheory
End Get
Set(ByVal value As String)
_QuantumTheory = value
End Set
End Property

You’ll see that the variable is stored within a private member which is the same, except for an underscore before it. Properties are basically a function that has a get and set block. Get, in this case is called when you type something like control.QuantumTheory. It simply returns whatever is stored within the private variable _QuantumTheory. Set is called when you type something like control.QuantumTheory = “unproven”. This is the basic layout for a property the only one simpler than this is a readonly property, that do not have a set part. However, you can put whatever you like within the set part to perform error checking and options for the user. For example, you might want to check a supplied value isn’t outside the maximum or minimum range of the control, like on a spinner. Without some error checking here, you would get an exception thrown if a user tried to set this property. So you could in fact extend the to property to perform logical comparisons on the value submitted.

Another thing you can do is add information about the property. Before the property declaration, you can provide a category and description of the property. This can help organise things better, and viewing the control in Visual studio will show all categories together and provide information about the property and it’s usage. This isn’t always necessary with properties like value, and backcolor, but some properties might need clarifying. I’ll add that I’m not brilliant at doing this myself – Most of the time if I make a control, it’s just for something I’m doing so I miss this bit out if I’m in a rush. (This has been done on the XML panel download assembly though)

The properties that need to be changed are the ones that previously returned a decimal as it’s return type. You’ll see on the property declaration above that all elements are declared with the AS operator. This determines that the property returns whatever type specified. In the case of the floatspinner, here is the value property –

Shadows Property Value() As Single
Return _Value
End Get
Set(ByVal value As Single)
MyBase.Value = value
_Value = Math.Round(CSng(MyBase.Value), MyBase.DecimalPlaces)
End Set
End Property

It is declared as shadows in VB as you are using a property that exists on the base class that you want to replace. I don’t know if this is the correct or proper way to do this in VB but is the only way I have found so far. The thing to note this time is that the property returns a Single. This will mean that when this property is retrieved in 3dsmax, it will be in the correct data type for max to cast into a float, rather than the decimal to integer conversion it currently performs.

Be a DotNet Pirate – Use an Event Arrrrg

This isn’t the only thing that needs to be done. Once the properties have been overridden, the control needs to provide an event for the data to be given to 3dsmax. I have talked in the past about custom event arguments, and they are a great way to make you controls perform tasks where you get exactly the information you need out of the control, in the format ready to be used without further casting or conversion. This is recapping other posts, but is really important if you want to start to develop your own controls for max. Custom Event arguments are classes themselves, so also have properties. It is these properties that allow you to query the eventargs for the information that you want. So in the standard mouse event args, there are stored properties for that particular mouse movement that allow you to get information about the pointer –

System.Windows.Forms.MouseEventArgs Properties –

We are going to write a simple eventarg that returns a value as a single in the form of an event property. This way, we know that 3dsMax will get the correct data structure we need.

Public Class LoneRobotValueChangedEventArgs
Inherits EventArgs
Private _Value As Single
Public Property Value() As Single
Return _Value
End Get
Set(ByVal value As Single)
_Value = value
End Set
End Property
Public Sub New(ByVal Value As Single)
Me.Value = Value
End Sub
End Class

This is pretty much it, all we need to add to the custom control is the connection between the valuechanged event in the numericupdown, and our new event class. We do this by overriding that, and calling our new event class. firstly, we override the event we want to alter –

Public Shadows Event ValueChanged(ByVal sender As Object, ByVal e As LoneRobotValueChangedEventArgs)

We have changed it so that it now accepts a lonerobotValueChangedEventArgs instead of a SystemValueChangedEventArgs

Protected Overrides Sub OnValueChanged(ByVal e As System.EventArgs)
Me.Value = Math.Round(CSng(MyBase.Value), MyBase.DecimalPlaces)
RaiseEvent ValueChanged(Me, New LoneRobotValueChangedEventArgs(Math.Round(CSng(MyBase.Value), MyBase.DecimalPlaces)))
End Sub

You’ll see that when we raise the event, we do so by instantiating a new lonerobotValueChangedEventArgs object. The sender (i.e the control) is the first argument, and a second argument which is a number that is set into the event property “value”. Now that has been performs, you can retrieve this value in max as a single and therefore get the float value that you need.

Download the source for this below. As I mentioned before in the post, I haven’t had a great deal of time to write this properly, perhaps someone could add properties if needed and post the results.

download script


I also mentioned in the forum that I had seen a class in the managed services dll called maxspinner.

I couldn’t instantiate this so I asked over at the SDKblog on the Area. This was the response that I got –

Regarding your .NET Questions.

First off, a disclaimer – if a class doesn’t have specific documentation (in this class, it’s excluded) then we don’t officially support its usage.

That said, those classes are public (because they need to be used across Assembly boundaries) and we can’t really prevent anyone from at least trying. 🙂

MaxSpinner is a wrapper class that takes the Spinner custom control that we expose in Win32 and provides a managed interface so that it can be used in an interop solution. This means that we take care of instantiating the class, but the client would need to grab the handle, using MaxNativeControl::HostWindowHandle, and embed it in an interop solution (such as System.Windows.Interop.HwndHost in WPF.)

So there you are, that clears that up. MaxSpinner is not for us mortals.

Follow Up –

DotNet Supremo Yannick Peuch has managed to clear up this issue with a conversion before Max casts to and integer. Thanks for sharing this Yannick!

rollout upDownRolloutTest “NumericUpDown Control Test” width:220 height:45
dotNetControl upDownCtrl “System.Windows.Forms.NumericUpDown” pos:[10,10] width:50 height:25
dotNetControl btnGetValue “System.Windows.Forms.Button” pos:[90,10] width:100 height:21
on upDownRolloutTest open do
upDownCtrl.DecimalPlaces = 1
upDownCtrl.Increment = 0.1
upDownCtrl.Value = 1.0
upDownCtrl.Minimum = 0.0
upDownCtrl.Maximum = 10.0
btnGetValue.Text = “Get Value”
btnGetValue.FlatStyle = (dotNetClass “System.Windows.Forms.FlatStyle”).System
on btnGetValue MouseClick do
— Get the value auto converted by MAXScript
fValue = upDownCtrl.Value
format “Wrong Value : %n” fValue
— Get the decimal value as a .NET object
dValue = getProperty upDownCtrl #value asDotNetObject:true
— Convert it to single object value then auto converted to float value by MAXScript
fValue = (dotNetClass “System.Decimal”).ToSingle dValue
format “Good Value : %n” fValue
createDialog upDownRolloutTest

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