Sort by Hue, I should Coco

Nov 26, 2008 by     No Comments    Posted under: 3dsMax, DotNet, User Controls

Here is an update to the color chart article. Since custom UI controls are kinda my thang, I decided to make one for this. The above left is with the colors sorted by Hue (More about this below). On the right is the previous arrangement where they are sorted by name. The Hue sort isn’t perfect, but it does at least group similar colors together which makes it easier to pick rather than the random sort by name on the right. You pick colors generally by Hue, not by their name.

The advantages of building a custom control are a simpler maxscript implementation. As you can see below this is the code for building the ColorChart in 3ds Max –

dotnet.LoadAssembly "C:LoneRobotclasslibMaxColorControl.dll"
rollout ColorChart "LoneRobot MaxColorChart" width:340 height:610
dotNetControl LRColorChart "LoneRobot.UI.MaxColorControl" pos:[0,0] width:338 height:610
createdialog colorchart

Now that is minimal code! πŸ˜‰

I was looking into using an IComparer interface for performing a Hue based color sort and found this article –

Thanks goes to Ruud van Eeghem for this posting.

Rather than writing one from scratch, I liked his GDI+ method of generating the color swatches, and since it was in C# I converted it to VB so that I could use it. Once that was done I added a few extra properties and methods to the control, namely taking the picked pixel color and checking to see if it was a ‘named’ dotnet color. Once I had this I made a composite control to extend the functionality and provide the clipboard method seen before. Bingo!

Update –

After a nights’ sleep I realized I wanted a more compact version. The control still displays the color name, but not the full maxscript string. The full string is still copied to the clipboard. You could register a dockable window for this. The new control is embedded into the same assembly, just change the dotnetcontrol declaration to “LoneRobot.UI.MaxColorControlMini”

download script

Building a DotNet Colour Chart (Not for Elephants)

Nov 24, 2008 by     No Comments    Posted under: DotNet, Imaging, Maxscript

If you’re anything like me then you have a mind like a sieve. So much so, I can barely remember the names of the dotnet colours, let alone get their pesky American spelling right. It’s colour alright???!!! πŸ™‚

Anyhoo, despite this, and not knowing what “GoldenRod” is, I decided to make a script that built an interface by retrieving the colors via the enumeration class. I’ve talked about enums last article but this introduces another way of using them. This certainly isn’t the only way to do something like this, but it was the way I worked out so it’ll do for me. The DotNetControl is a FlowLayoutPanel, which is really useful for building dynamic interfaces, (I use one in my HitchHiker control) as you can pass it an array of controls to build it.

It’s quite easy to get the brightness of the colour when creating the labels. Having a cutoff where if the color has a brightness value of less than 0.3 will change the forecolor to a light color. I’ve changed the code to reflect this. Also, i’ve split the array into two, to make the differentiation between systemcolors and named colors. (System colors are the colors used on the current windows color scheme)

Update –

  • Added copying to clipboard of dotnet string
  • Added DotNet color dialog to pick color
  • Added RGB swatches

To Do –

I am going to see if Reflection can be used to make this script faster. At the moment it takes a few seconds I cant work out if it is setting up the button array or the color property retrieval.

I will also try sorting the colors by Hue (Thanks to Dave Stewart for the suggestion) It will be an interesting test to see if an IComparer class can work in 3dsMax.

download script

Using Inheritance to build a custom control

Nov 19, 2008 by     No Comments    Posted under: DotNet, Maxscript, User Controls

One of the really good things about writing your own controls in Visual Studio is the ability to not actually write one. This is great because you don’t have to do any of the hard work, you just pick the control that is closest to the functionality you want, then inherit the class. This is much the same if as using extends in a scripted plugin. What this does is gives you a fully featured version of the control and allows you to build extra properties and methods for the bits you want to add, or override the existing properties to do something flash.

In this case, i’m not doing this at all, but I am just tailoring the button control to be integrated into 3dsMax in a more direct way. If you use a dotnetcontrol constructor to implement a dotnet button onto a MXS form, you will get a default instance of a windows.forms.button, which you then have to alter in the rollout’s load event. This isn’t problem really, but i wanted to see if it was possible to build a class that returned a button in the correct visual state from the word go, leaving very few, if any, things to do in the load event. The first property I wanted to implement was to return a flat button with a background color the same as 3dsMax’s default UI color. The other things I experimented with was the idea of using embedded resources to hold common UI icons, rather like the examples on the GDI+ article i wrote a while ago. I also wanted a one line method to align the text and image around in a logical way.

It doesn’t avoid the issue of having to perform some UI stuff in the rollout open handler, but it does most of what you need for you. You also avoid the flicker of the controls as they redraw themselves with the correct background color. If you still want to build MXS rollout rather than dedicated Dotnet rollouts this could be a reasonable solution. (Don’t forget, if you build a dotnet form with the MaxForm class in 3dsMax, the controls will take on the correct UI color automatically.)

Using DotNet Enums in MaxScript

With any custom class objects, it’s highly likely Enums will be used, as they are a way of organizing custom properties into easily recognizable names, rather like structs in MXS have members to describe elements so that you don’t have to resort to array elements with confusing index numbers.

To use an enum in 3DSMax, you use a plus symbol in between the class string and the enum type. therefore, in the maxbutton class, to get the enums used you would type the following –

(dotnetclass "LoneRobot.Controls.MaxButton+CustomIcons").NewDocument
(dotnetclass "LoneRobot.Controls.MaxButton+LayoutStyles").Top
(dotnetclass "LoneRobot.Controls.MaxButton+ColorPresets").MaxUIDark

you would then use this to set the custom properties in the class.

Here is the full class diagram of the Maxbutton Control –

I hope this article has been of interest to you. Please download the class library and try it for yourself. there is a basic MXS script that builds the rollout at the top of the page. You can explore trying the different enums for yourself!

download script

GDI+ Methods – The DotNet Paint event

Oct 28, 2008 by     2 Comments    Posted under: 3dsMax, DotNet, Imaging

In order to use GDI+ to draw vector based shapes, outlines and objects in DotNet, one option available is to use the paint event.

I have been working on some GDI+ functions that can dynamically generate icons without the need for bitmaps. The idea of this came from a post on CGTalk some time ago by Dave Stewart who previewed a similar idea based around MXS and setpixel methods if i remember rightly. I wanted to write a dotnet equivalent. I am trying to include the sort of icons you use on dialogs for generic actions like arrows, numbers, pickbuttons etc.

One nice thing is using translatetransform() within the graphics surface, that way you can draw the icon in one position and then pass an angle in the function and it draws it pointing the correct direction. this simplifies things greatly and means you have one function for all arrow icons.

With the same thing in mind, the plus icon also becomes the cross icon with translatetransform, as does the square when it becomes the diamond. The numbers adjusts the font size to the largest possible according to the number of digits.

Each paint event function passes an outline colour and a fill colour along with the graphics surface so you can adapt the icons to whatever scheme you like, including the current 3dsmax one.

There are a few options with a GDI+ drawing surface when it comes to how you perform the drawing itself. I decided to opt for the graphicspath object method as you are able to treat it as a closed entity. This means you can fill it with colors, stroke the outline and apply linestyles and edge joins. Constructing the shape from individual lines always treats them as individual lines and not the overall shape. In order to make the corners sharp on a graphicspath object, one option in this heavilly overloaded function is to pass a byte array. I’m still not sure why this is neccesary, except for the fact that it doesn’t work properly without it. I think it must be something to do with how the corners are rendered.

I set up a loop to build the dotnet arrays in max, remember that dotnet arrays start at 0, not at 1 like in Maxscript.

-- specify an array of points

PlusGDIArray = dotNetObject "System.drawing.point[]" 12

-- and a byte array of the same size

ByteArray = dotNetObject "System.byte[]" 12

-- store the points you want to place in the array

local pointloop = #((pt1 = dotNetObject PointClass 8 2),
(pt2= dotNetObject PointClass 8 8),
(pt3= dotNetObject PointClass 2 8),
(pt4 = dotNetObject PointClass 2 16),
(pt5 = dotNetObject PointClass 8 16),
(pt6 = dotNetObject PointClass 8 22),
(pt7= dotNetObject PointClass 16 22),
(pt8 = dotNetObject PointClass 16 16),
(pt9 = dotNetObject PointClass 22 16),
(pt10 = dotNetObject PointClass 22 8),
(pt11 = dotNetObject PointClass 16 8),
(pt12 = dotNetObject PointClass 16 2))

-- and apply them into the dotnetarray
for i = 1 to pointloop.count do PlusGDIArray.setvalue pointloop[i] (i-1)

--repeat for the byte array
local byteloop = #((Byte1 = dotNetObject ByteClass 0),
(Byte2= dotNetObject ByteClass 1),
(Byte3= dotNetObject ByteClass 1),
(Byte4 = dotNetObject ByteClass 1),
(Byte5 = dotNetObject ByteClass 1),
(Byte6 = dotNetObject ByteClass 1),
(Byte7= dotNetObject ByteClass 1),
(Byte8 = dotNetObject ByteClass 1),
(Byte9 = dotNetObject ByteClass 1),
(Byte10 = dotNetObject ByteClass 1),
(Byte11 = dotNetObject ByteClass 1),
(Byte12 = dotNetObject ByteClass 129))
for i = 1 to byteloop.count do ByteArray.setvalue Byteloop[i] (i-1)
-- finally add both arrays to a graphicspath object
ClosedPath = dotnetobject "System.Drawing.Drawing2D.GraphicsPath" PlusGDIArray ByteArray

download script

Delving into the DotNet SDK – A timechange callback via Dotnet

Oct 28, 2008 by     No Comments    Posted under: 3dsMax, DotNet, Technical Research

A quick look into the dotnet SDK shows a few useful classes that one can use. Over time as I discover what some are for I will hopefully be able to document some of them. One class that stuck out to me was the ManagedServices.AnimationFrameChangeListener class. After a quick inspection of the methods and properties I was able to work out how to enable this and get it talking to max – namely calling a function when the timeslider was scrubbed back and forth.

The example is just to show it working, it updates a label control on the dialog with the current frame. However, knowing that this is possible from dotnet is interesting, as in the future it might be possible to use it to bind max controllers to dotnet objects and controls. Whether it is any better or worse that the current timechange callback that MXS implements is something I’m yet to figure out. The way i see it is that it’s been put there for a reason, I’m sure a use can be found!

global DotNetTimeChangeCallback

rollout DotNetTimeCallback "DotNet Time Callback" width:194 height:70

fn testcallback =
DotNetTimeCallback.framelbl.text = "Current Frame - " + currenttime as string

button btnaddCB"Add Callback" pos:[3,3] width:92 height:40
button btnremoveCB "Remove Callback" pos:[99,3] width:92 height:40
dotnetcontrol framelbl "label" pos:[4,46] width:186 height:21

on DotNetTimeCallback open do
framelbl.borderstyle = (dotNetClass "System.Windows.Forms.BorderStyle").fixedsingle
framelbl.backcolor = (dotnetclass "System.Drawing.Color").slategray
framelbl.forecolor = (dotnetclass "System.Drawing.Color").black
framelbl.textalign = (dotnetclass "System.Drawing.ContentAlignment").middlecenter
framelbl.text = "Current Frame - " + currenttime as string

on btnaddCB pressed do
if DotNetTimeChangeCallback == undefined do
DotNetTimeChangeCallback= dotnetobject "ManagedServices.AnimationFrameChangeListener"
dotNet.addEventHandler DotNetTimeChangeCallback "AnimationFrameChanged" testcallback
framelbl.backcolor = (dotnetclass "System.Drawing.Color").orangered
framelbl.forecolor = (dotnetclass "System.Drawing.Color").yellow

on btnremoveCB pressed do
dotNet.removeEventHandler DotNetTimeChangeCallback "AnimationFrameChanged" testcallback
DotNetTimeChangeCallback = nothing
framelbl.backcolor = (dotnetclass "System.Drawing.Color").slategray
framelbl.forecolor = (dotnetclass "System.Drawing.Color").black


createdialog DotNetTimeCallback