Browsing"3dsMax"

You can tell by the way I walk I’m a woman’s man

Nov 2, 2010 by     1 Comment     Posted under: 3dsMax, Maxscript, Rigging, Technical Research

zfOn the last few projects I have used a system that I developed as part of Lonerobot R&D. It’s a walk system called Zimmerframe that is targeted at optimising animation production. The reason I decided to develop something like this was simple – when working as an animator on production, there were many times, especially when I was up against the clock on my weekly allocation that I wanted to eliminate the time it took setting up characters into position in a shot. This was so I could get on with actually animating the performance!

My logic was that there are a multitude of what I call ‘bread-and-butter’ entry and exit shots where characters need to walk in or out at the start and end of shot. On a production, you want to have the walk standardized as much as possible so it struck me that if you stored the distance information of a walk cycle to XML, you could automate the translation part of the animation depending on how many steps you are taking. On a walk it’s pretty much set in stone if you want to avoid foot slip and I also wanted a more precise way of calculating these walk around curved motion paths too.

This was certainly not designed to remove any of the inherent character from the walk cycle – These can be animated however the animator originally wished, except that the actual cycle must be on linear keys to provide a constant speed to the step cycle.

My solution involved adding a helper object – the zimmerframe dummy which stored the XML preset walk data in a parameter block.

Zimmerframe has a small collection of extras to augment it’s operation on this production – A walk cycle maker, A walk browser featuring a custom XML dotnet interface, and footlocker, a simple but useful way of fixing any foot slip around tight corners.

The key to this system is it’s simplicity. However, by combining different walk and run cycles you can build a complex movement animation in a very short amount of time – leaving you to spend the time making the acting good!

Featured here are some screen grabs I made to help the animators on our last production use the system. They are in quicktime mp4 format. I hope it goes some way to explaining how the system works and the ease by which it fits into the max UI and program flow.

1 – Setting up Macros

2 – Setting up a linear run

3 – Removing the helper

4 – Setting up a curved walk

5 – Creating a new walk cycle

6 – Correcting foot slip around corners

HitchHiker Update v4.4

Oct 9, 2010 by     29 Comments    Posted under: 3dsMax, DotNet, User Controls

hh-new

I’ve been so swamped with work this last year, my planned update to Hitchhiker had to be stalled. It’s a shame really, as I have had a working version of the updated assembly for a while but no time to update the help files to show how to use it. However I’ve been pleased with the progress made in it’s place – I’ve been able to write RigStudio, Digit and Character Silo which I’m really proud of. Hopefully I will get a bit of breathing space to allow me to concentrate on next generation 3dsMax UI stuff, namely learning WPF.

There are a few updates – mainly from great feedback I’ve had from the community. I am glad people are using HitchHiker in any way to augment their Asset Browsing scripts. With the new additions that I’ve made, I am hoping this will be even better.

Please let me know if you like Hitchhiker and use it for anything useful. You feedback is critical as my time tends to be limited. So if i feel people are using the control I can spend my time improving it. I’d love to spend all my time refining this control but there are so many other project that I have to work on! But feedback on any level will always motivate me to fix and improve!

Recent Updates/Fixes –

v4.4 – 19th January 2011

Added the .buttonborder <boolean> property. If you set this to false, Hitchhiker will not render the black border around each control. If used with the .gap <integer> property set to 0, Hitchhiker will render like a seamless section of tiles.

buttonborder

Instantiating HitchHiker

I had to have a re-organisation of my namespaces as they were getting confusing. Since HitchHiker isn’t really anything to do with characters, i decided to change the default namespace to lonerobot.ui.controls. If you have any scripts that were written with the previous release of this assembly, you can just do a search and replace of ‘character’ for ‘controls’

make sure that you call dotnet.loadassembly <hitchhiker dll location> in your script before using.

if using on a dotnetform or maxform – dotNetobject “lonerobot.ui.controls.hitchhiker”

if using on a maxrollout – dotNetcontrol “lonerobot.ui.controls.hitchhiker” width:<int> height:<int>

please refer to the hitchhiker tab at the top of this site for more detailed help, all these are still valid

Additions to Version 4.4 of HitchHiker

Max file support –

Hitchiker will now support the searching of the Maxfile type. Not only this, you can render an associated thumbnail directly from the assembly to be displayed with the file when previewing it in HitchHiker.

Additional populate methods –

Hitchhiker was always designed to operate on a folder level, whatever was inside with the designated filetype was displayed. Now you have two options.

populate (by directory string)

populate (by file array)

This means you can now pass an array of filepath strings to be displayed in the hitchhiker window.

This can also be utilised with the enhanced search parameters –  PopulateFilterSearch

Hitchhiker will now allow you to display files that start with, end with or contain certain characters. So if you are doing a recursive search population of many folders, you can tell it to only display those with a particular character’s name for example.

Other options is that you could prefix or suffix a filename “Approved” or “Unapproved” and allow the showing of approved files only.

Fast Cache Display Mode

Hitchhiker is reasonably fast in realtime mode, but (like most asset browsers) can slow down when loading multiple hi-resolution images inside folders. Hitchhiker has a display state that  can be set to cache – meaning the thumbnails are stored locally on the user’s machine, meaning that the slowdown only exists the first time the populate method is called. Subsequent calls result in the control being populated very fast!

hh.cache =  (dotnetclass “lonerobot.ui.controls.hitchhiker+cachestate”).cache

Store Favourites of your commonly used directories

Hitchhiker can now add locations that you use to populate from to save specifying the folder each time

Fully integrated toolbar

Hitchhiker can be run without the toolbar and it’s behaviour controlled entirely by script, but setting the toolbar to true allows you to do everything from the menu system, set thumbnail sizes, search parameters etc, meaning it really is a pretty comprehensive asset browser straight out of the box.

A few caveats I have noticed about this release –

Occasionally the keyboard focus can be stolen by HitchHiker if you close the dialog. Make sure you call the housekeeping function in your close handler, as this will make sure max gets it’s shortcuts back.

Feel free to download at the link below. If you find any issues with using this version of Hitchhiker, please let me know and I’ll do my best to fix it. I would ordinarily like to test a release more than this one has been – But didn’t want to delay the release this to the community any longer. I hope it helps you make great utilities with minimum effort.

download

Flexible Animation System for Fingers and Hands

Oct 8, 2010 by     No Comments    Posted under: 3dsMax, Characters, DotNet, User Controls

digitui

For many years as a character animator I have wanted a system to aid with the animation of fingers. It’s one of the things that tend to be done as an afterthought when pushed for time on a production, but there is no doubting that some of the nicest animation will always have really great hand gestures. The balance is trying to find the balance between time and ease of animation. If you are really pushed for time, it would be far better that you could store hand poses that you use most of the time, with a view to having a robust toolset to allow you to refine them afterwards.

I recently wrote a system called Digit that goes some way towards addressing this issue in production. The key to this system working well was to provide a UI that was intuitive to the way people currently work in max, and making it minimal and simple enough that it didn’t need too much understanding. This article will hopefully explain the process that I undertook in planning and executing this system.

Planning the control layout

Digit-Roughs

I usually start with some sketches – i have a technical journal that I write ideas and loops into. This over the years (and much to my chagrin) has replaced my sketchbook but it does give me a kind of technical reference for everything. I will scribble UI ideas down before I code anything, as it helps me to plan in advance potential layout conflicts. It is by no means final but it gets me thinking about how everything is going to interact. The thing to remember is that it’s not something you’ll proudly display, but it helps to organise your mind and avoid any UI layout problems along the line. UI design is a hard thing to do, generally people will tell you when it is wrong in a heartbeat and not even notice if it is right. It’s one of those thankless tasks that only you can appreciate.

With Digit I felt I could deviate from the look of the max UI – firstly, square controls were simply not going to cut it, and I wanted to allow control to adjust the layout when running in either a portrait or landscape mode.  The FlowLayoutPanel is built for this task and I’ve used it extensively for my controls up to now. The layout logic was pretty simple, I didn’t want to separate the controls too much, what it gave me was two main panels – the selection interface, and the slider controls.

The critical thing for me in terms of the functionality of the system was that it was as intuitive as possible, and mimicked a lot of the same methods as the native max UI. This was mainly the use of CTRL and ALT to add and remove from selection, and the double-click hierarchy selection. My other consideration was to try to keep the dialog as minimal as possible. I had setup finger controls in the past and found, due to the nature of wanting them to be able to control individual fingers, that they can become large slider interfaces. I thought that this time i would be able to use less and still have the same level of control. Here is what I came up with in the end, they are the same UserControl, they have a hand property, Left and Right which decides what UI colour they get.

Colour profiles

Selection based curl slider

I did this by having a two stage approach, working on a specific selection or on the entire hand. If no finger element was selected, the curl slider affected the entire hand. If a particular finger had been selected, then the curl would only operate on that finger. The same went for resetting the finger position. This worked well, you could quickly set up different poses based on others.

Splay and Thumb position and rotation were different. You don’t want to splay individual fingers, it looks odd so this acts on the entire finger rig. And individual thumb position and rotation are also necessary. However the thumb curl operates on the global curl slider.

The +Thumb button was something that came from the evolution of the control and testing it on some real characters. Sometimes, you didn’t want to move the thumb but still move the fingers. This checkbutton allows you to affect the thumb curl and splay with the other fingers – as before, it is based on the selection, so you have to have no part selected for this to work.

help

Its all put in place with a bit of simple matrix rotation. You can prerotate a matrix according to an angle, so each time the slider moves, it is calling a function that supplies the current transform and returns a new transform matrix that is prerotated by the angle from the slider.

Medial,Distal and Proximal Selectors

The three selectors to the left of the fingers are to select across the first, second and third bones. I thought this gives an extra control if the automatic blended rotation of the fingers isn’t quite enough to get the hand pose you are after.

Pose Storage and Mirroring Transforms

I have been using XML for a while now to store any sort of pose when I use a reference transform monitor to store a node. Most of my facial animation systems and rig setups use these. Once the directory is specified, you can store the local transform of each node and that’s all you need. I use an XMLLayoutPanel to handle the files. Its a dotnet user control I released a while ago that handles all the layout and even lets you specify a custom thumbnail. Check my previous blog post if you want to have a go at using it.

When you apply it back on the hand later, you multiply the current finger’s parent transform with the XML preset’s local transform and you have applied your stored preset. Because you extract the local transform, the pose is the same, even when applied to a different hand position as you are making a new transform matrix from the current parent’s position. When you want to mirror the transform to the other hand, that is a little more involved.

You start by reading the local transform of the finger nodes. Then, you Mirror the transform matrices of all bones, by creating a mirrored transform around a common parent (like the character’s root). However the other arm is not necessarily in the same position. So, you need to create a mirrored transform of the hand too. Once you have this, you can get the mirrored bones local transform matrix. This becomes the one you use, as you can then multiply this mirrored local transform onto the current location of the parent (i.e. the opposite hand) With this you can paste a hand pose from one arm to the other, regardless of the arm position. I was grateful for all of the brilliant examples on the CGTalk board to help with this, specifically from Paul Neale.

Setup

It is possible to set up a character with this complete hand animation system without any Maxscript. This works on Bipeds, CATrigs, Puppetshop rigs and custom bone rigs, so hopefully it will be useful for many projects to come.

Loving LINQ is easy because it’s beautiful

Oct 7, 2010 by     6 Comments    Posted under: 3dsMax, Characters, DotNet, Technical Research, User Controls

One thing is certain, XML is prolific. I wanted to research the most efficient way of using this versatile language in future systems I develop for 3dsmax animation pipelines.

One of my latest research projects is looking at various options for storing information about characters in a project. There are many different types of data that is useful to be able to pull up, from node information to walk cycle data. What i haven’t had before is a unified method for storage and retrieval. I have been using XML in my character tools for some years quite successfully, from Lipsync storage to Walk Cycles.

I’m sure most studios out there are using some form of database, whether it be for asset tracking etc, and I’m sure that this is a perfect solution. However for this problem, after looking at SQL server configurations and methods I found a different approach that could span my need for database-like data handling and the transparency of storing to XML.

XML Verbosity

I’ll hold my hat up, part of this might be because I went to art school and didn’t pay enough attention in Maths, but there was something distinctly hit and miss about using XML within a dotnet assembly. The Document Object Model (DOM) felt quite cumbersome and wasn’t like the elegant OOP approach I was looking for. What was a transparent user experience with intellisense in visual studio, using XML on an object level became a clunky affair. With my last post about RigStudio, my dynamic character selection framework, I integrated a custom XML serlializer to take the guesswork out of the XML parsing and creation.

The Microsoft Language Integrated Query framework (or LINQ) is one of the more recent introductions to the dotnet framework and allows XML data to finally be treated like data. You can now perform queries and operations on the XML tree in a proper object orientated approach. If you go the whole hog, you can also build an XML schema from an existing XML template and have intellisense support for the XML document. Most importantly, the Linq XML classes are enumerable classes, meaning you can iterate then easily, meaning you can extract, merge and join portions of the whole document tree into other branches and documents.

Also available to the VB programmer are XML Literals. Basically, this means you can begin typing a variable directly into an xml tree – so your code actually resembles an XML document and allows you to integrate variables into the tree dynamically, so you can loop object collections to build complex XML documents completely via code.

Xelement

As you can see, you can format it exactly like an XML document directly from the variable declaration. To add a variable into the document, you can use :

<%= your variable here %>

This allows one to pass another XElement at this point to nest more complex trees. This is certainly far simpler to set up than a custom serliazation class.

Where LINQ fits in with 3dsMax

In terms of max, you can’t really perform the query commands that are the really useful part of LINQ (Well not to my initial research). The main class you will want to use with LINQ to XML is the XElement. In raw dotnetclass form within 3dsmax, you are using it in a similar way to how XML was previously treated – i.e. the DOM model. However, providing a class library that utilises the LINQ query methods could be worthwhile. Visual Studio is a mature development environment, and you are adding something to max. In terms of the deployment, I have no problem adding a dll to the max startup. I met with the 3dsMax design team a while back through work and they assured me dotnet is going to be with max for a long time to come.

Where the XElement class is useful, is that it can encompass many different XML files, or a single branch within a particular file. If you had a selection of XML documents that stored the scene nodes of a different scenes,You could use a LINQ query to get the filenames where a specific object resided. But my purposes, whilst similar was to get this working within a character pipeline.

My idea was to create a type of XML Database that I called Silo. This would be setup in Max using managedservices.MaxsciptSDK functions to pass object names into the assembly that could then be written to XML. It could also integrate RigStudio into the same file. What this means is there is a front end that can quickly store node data about a character rig which can then be queried, giving access potentially to any data applicable to the characters within a particular project, whether this would entail node data, mirroring information, Lipsync, Walk Cycle footstep length – the possibilities are endless.

Most of the time, animators want to be able to control visibility of characters at various points, some times you want to see the controls for speed, sometimes the mesh for previews. Having different layers for each of these types is fine, but when you have a lot of characters, it can mean navigating the layer manager is more involved. Wouldn’t it be great to just store these relationships in a datasource and keep the entire character on one layer? In fact, you could have ALL characters on a single layer if you wanted, the database could then handle all node interaction.

The front end on my prototype looks like this, I’m still trying to work out the best layout at the moment, as it feels a little thrown on to me. However it’s enough to quickly setup a multiple character database that can be automatically bound to my Rigselector control. Each tab allows me to store selected elements of the rig into the various sections.

SiloConsole

Here’s a useful class that you can use within a dotnet assembly – I needed a way of getting the selected object names into the dotnet assembly in order to save them to XML

Public Class MaxOps
    Public Function GetCurrentSelection(ByVal SingleNode As Boolean) As List(Of String)
        Dim NodeList As New List(Of String)
        Dim Selectioncount As Integer = ManagedServices.MaxscriptSDK.ExecuteIntMaxscriptQuery("Selection.count")
        If SingleNode Then
            If Selectioncount = 1 Then
                NodeList.Add(ManagedServices.MaxscriptSDK.ExecuteStringMaxscriptQuery("Selection[1].name"))
                Return NodeList
            Else
                Return Nothing
            End If
        ElseIf Selectioncount > 0 Then
            For i As Integer = 1 To Selectioncount
                NodeList.Add(ManagedServices.MaxscriptSDK.ExecuteStringMaxscriptQuery("Selection[" & i.ToString & "].name"))
            Next
            Return NodeList
        Else
            Return Nothing
        End If
    End Function
End Class

The final tab updates RigStudio for Silo compatibility. You can now build a rig selector directly from silo, or import a previous version.

One option with Silo is you can specify a species for the character, so that you can control visibility of different types of characters. For example you can use LINQ to combine types of queries. A literal translation would be to ask –

“Unhide all animation control nodes that reside in the animal species”

With LINQ, this command would look a bit like this –

query

You can see the use of XML style parentheses in the query. These are known as axis constraints that return the xml nodes of ANY character with the same branch name. This means you are using the XML nodes like objects.

Another thing to remember, is that within max a system.array with be cast into a max array type. So if you are using dotnet lists and specialized.collections within the assembly, that is fine, but in order to avoid extra code in maxscript it’s best to make sure the function has the appropriate return type.

Hooking up the XML Database

dbclass

This is a breakdown of the SiloDatabase class – This uses LINQ to consolidate an XML file into a queryable dotnetobject in 3dsMax. As you can see, there are many methods, all of which can be hardwired into other assemblies, as you would only usually be running a single instance of this class. This means I can bind other assemblies to use the database without any maxscript interaction. This is always my goal, maximum flexibility with minimal deployment. The deployment for this whole database pipeline within a project? a couple of lines in max startup. It grows with the project and all the selection logic is built into the controls, not the deployment code, so can evolve and improve as the project goes on. The core of the database object isn’t really a database of course, its a collection of XML files. But when max instantiates the Silo Database dotnetobject, it appears and acts like one because of the LINQ query methods.

Stuff the revolution, I’m thinking about a Character Selection Framework

Jun 1, 2010 by     10 Comments    Posted under: 3dsMax, Characters, DotNet, Rigging, Technical Research, User Controls

 

One of the most time consuming things always seems to be building pesky UI’s in Maxscript. The ever reliable Visual Maxscript editor, whilst having the reputation of Marmite, has been compromised by a spectacular bug in the last few releases that prints the last few characters of the previous line onto the next one, thus scrambling your code.

rollout MXSMashup "Untitled" width:169 height:171
     (
         button btn1 "Button" pos:[7,8] width:70 height:39
     39
         bitmap bmp1 "Bitmap" pos:[97,25] width:52 height:84
    84
        checkbutton ckb1 "CheckButton" pos:[28,138] width:132 height:20
    20
        combobox cbx1 "ComboBox" pos:[16,91] width:55 height:1
    1
        edittext edt1 "" pos:[14,60] width:55 height:20
    20
    )

All this means you end up manually  tweaking positions and sizes till the end of time, for a script  that is essentially doing something very simple. It’s always faster to have a small dialog with a picture of the rig with fast access to individual bones to remove the necessity of picking them in the viewport. However, generating these things up can usually take more time than we have to give in production. I thought it would be an interesting focus to my research to see how easy it would be to implement a system that allowed even a non-scripter to build specialised dialogs for selection, ultimately a task that you end up doing a billion times with a rig.

The IDE driven usercontrol approach

Now Visual Studio has a pretty nifty IDE, and you can layout windows forms pretty darned fast with it. It would be rather handy to have something similar but centred around 3dsMax, albeit with a basic layout toolset. The obvious approach (in terms of linking the dotnet framework to 3dsMax objects) was binding controls to the names of scene nodes. Okay, maybe scene node names might not be the most robust of solutions but it is fine for this sort of thing – but if someone starts renaming rigs just for fun, you’re going to be screwed on a bigger level than just the selection tools.

Look at this lot –

all dialogs

With Rig Studio, you can do this without any coding in no time at all. Here’s how –

A Runtime Design Surface

Have a look at the video below to see an example of how RigStudio sets up a basic control dialog and then stores it to be read by a user end interface.

You build up an interface by drag and drop, and there are preset sizes and shapes defined for speed. You can size these up to whatever you like. There are options to set the forecolor and background color, and to position text blocks to illustrate the different areas of the dialog.

You might recognise the control on the right of the UI – It’s a propertygrid and is a great little control for exactly this type of thing, as it takes a dotnetobject and displays an area so that you can adjust the properties easily. You can use this out of the box, but I have customised it to only display the properties that you need to adjust. I have also written a custom UITypeEditor class to handle the control list drop down method for choosing hierarchies.

TypeEditor

This is not a default behaviour of the propertygrid control. the Child property on a rigcontrol object is a string denoting the name of the next node in the hierarchy. In order to give better design-time support, I am asking the UITypeEditor  to take an array of controls from the design surface and build a sorted listbox from the results. It needs to be sorted as the controls could have been created in any order.

Custom Toolstrip Renderers

Alignment-Options_Dotnet Alignment-Options

Keen-eyed UI aficionados will notice the similarity of the drop down menu on the left to 3ds Max’s menu system, and not the default dotnet toolstrip renderer (which is on the right). Its a subtle difference, but this is due to the use of a custom toolstrip renderer. Autodesk has provided this as part of the MaxCustomControls namespace.

MaxToolStripSystemRenderer is a custom renderer and is assigned by simply giving an instance of it to the renderer property. So you can implement this into Maxscript pretty easilly too, if you want everything to look like it is part of the same application.

-- VB
Me.CtrlMenu.Renderer = New MaxCustomControls.MaxToolStripSystemRenderer()
-- MXS
CtrlMenu.Renderer = dotnetobject "MaxCustomControls.MaxToolStripSystemRenderer"

What might annoy you about the toolstrip, especially if you run the UI from the dark side of the force, is the border that windows paints around the toolstrip menu. This is just plain annoying if you are trying to keep a nice minimal looking UI.

toolstrip

It’s probably not so noticeable on a light UI scheme, but it would be great to get rid of it. With some poking around on some VB forums, I found this solution. There is a protected method called OnRenderToolstripBorder. All that you need to do is to override this and tell it to do nothing, as you don’t want the border. The great thing about class inheritance is that we can inherit the custom renderer class made by Autodesk, implement the functionality we need, and have a new class to use. By only overriding the methods that we want to change, we keep all the other existing render bits. It is actually a pretty short entry to have a version of the Autodesk Toolstrip renderer without the white border line –

Public Class BorderlessMaxToolStripRenderer
Inherits MaxCustomControls.MaxToolStripSystemRenderer
	Protected Overrides Sub OnRenderToolStripBorder _
				(ByVal e As System.Windows.Forms.ToolStripRenderEventArgs)
 	'Do nothing
 	End Sub
End Class

So now you can have the great functionality of the toolstrip without having an incongruous looking UI. It would be pretty easy to dynamically compile this class as part of a maxscript, so that you dont have to distribute an assembly too.

Toolstrip-CustomRenderer

Weirdly, you don’t get this border with a menustrip, which is fine for most things, but it’s not got the same choice of controls. Its a shame i didnt realise this until I had gone down this route!

Design-TIme Alignment Functions

Controls have a left, right, top and bottom property which returns their position relating to their parent container. This is a local position and unrelated to screen position. This makes the majority of options in the alignment menu relatively easy. Where it becomes more complicated is control spacing. Controls can be added in any order, so you need a method of deciding where a control is in dialog position terms. If you are performing a spacing arrangement, you need a method that can take an array of controls and return them sorted into order by their location property. As you can space vertically or horizionally, you’ll need a way of tells the method the direction that you want to sort them. It is precisiely this sort of logic that the Icomparer can handle, sorting abstract classes according to  defined properties.

Fortunately, the dotNet framework has some powerful sorting method called Icomparer for doing this job.

Public Enum AlignmentCompareOptions
    Vertical
    Horizontal
End Enum

Friend Class PointComparer
    Implements IComparer(Of Drawing.Point)
    Dim _CompareDirection As AlignmentCompareOptions

    Public Sub New(ByVal Direction As AlignmentCompareOptions)
        _CompareDirection = Direction
    End Sub
    Public Function Compare(ByVal x As Drawing.Point, ByVal y As Drawing.Point) As Integer Implements IComparer(Of System.Drawing.Point).Compare
        Dim pointX As Point = DirectCast(x, Point)
        Dim pointY As Point = DirectCast(y, Point)

        Select Case _CompareDirection
            Case AlignmentCompareOptions.Vertical
                Select Case True
                    Case pointX.Y > pointY.Y
                        Return 1
                    Case pointX.Y < pointY.Y
                        Return -1
                    Case pointX.Y = pointY.Y
                        Return 0
                End Select
            Case AlignmentCompareOptions.Horizontal
                Select Case True
                    Case pointX.X > pointY.X
                        Return 1
                    Case pointX.X < pointY.X
                        Return -1
                    Case pointX.X = pointY.X
                        Return 0
                End Select
        End Select

    End Function

End Class

In order to use this class, you add the controls to a SortedDictionary, one of the classes that you can supply an Icomparer to the constructor. You’ll see this SortedDictionary takes a point value, and a control value. As these are added the Sorted dictionary automatically sorts them into the order they are according to the direction (vertical or horizontal)

Dim CompareControlList As New SortedDictionary(Of Point, Control)(New PointComparer(Direction))

The Icomparer sorts it by returning an integer that relates to the evaluated expression within the IComparer, 1 if pointA is greater than pointB, –1 if B is greater than A, and 0 if they are the same.

The conclusion to all this is that you can use the results to calculate the difference between the first and last control instances, deduct the combined height or width of the controls and divide by the number of gaps to get the distance needed to space the controls evenly.

XML Control Serialization

This was a new thing for me to get my head around, and in doing so I genuinely believe I have only scratched the surface on the sort of things that you can do. In my case, XML serialization provided an elegant solution to this problem –

XMLfile.ChildNodes.ItemOf(i).ChildNodes.ItemOf(i).ChildNodes.ItemOf(i).InnerText

Sometimes, negotiating an XML tree manually becomes difficult to track, but in this case, you explicitly know what type of information you are dealing with. XML serialization allows you to deconstruct dotnetobjects, with the idea that you can use the serializer’s logic to re-assemble them at another time, perhaps even to send data to a compliant application on a different computer.

To use the example of, say a bog-standard button, we could serialize this but we have to address a couple of issues first. XML needs a specific data type to store the information. You couldn’t just pass it an enum style like “borderstyle.fixedsingle” as a string and expect it to know what to do with it. Also, there are a sh*t load of properties that we have on a button, many of which don’t particularly represent anything to do with the visual state. You would be serializing a lot of useless information into the XML file, making the file larger than it needs to be.

Writing a Custom Serialization class

I ended up by writing a class specifically to handle the serialization of the design surface to XML. It actually consists of three classes, the RigControlSerializer is where the action happens. It stores all the dialog information necessary to recreate the size and shape of the stored data. RigControl and RigControlText are subclasses that allow the serializer to loop through the controls on the design surface and store them into an array list. So the serializer property controlist actually has an array of the RigControl class, not the actual control itself, but is perfect as it is all the information that the deserializer needs to recreate the control faithfully on another surface.

Custom Serializer Classes

The great thing about writing a serializer class is that each property becomes a branch in the xml file automatically,  and subsequent paths are made as each property is serialized in order. So an entire dialog can be written out to XML as follows –

Private Function SerializeDesignSurfacetoXML(ByVal XMLFilename As String) As Boolean

        Try
            Dim SavedDialog As RigControlSerializer = New RigControlSerializer()

            With SavedDialog
                .DialogSize = DesignSurface.Size
                .DialogBackcolor = (CType(DesignSurface.BackColor, Color)).ToArgb
                .ControlList = CreateRigControlCopyArray()
                .TextLabelList = CreateTextLabelCopyArray()
                .BackgroundImage = CType(Me.DesignSurface.BackgroundImage, System.Drawing.Bitmap)
                .CharacterName = Me.CharacterName
            End With

            Dim writer As New XmlSerializer(GetType(RigControlSerializer))
            Dim file As New StreamWriter(XMLFilename)
            writer.Serialize(file, SavedDialog)
            file.Close()
            writer = Nothing
            Return True

        Catch ex As Exception
            Return False
            MessageBox.Show(ex.InnerException.ToString)
        End Try
    End Function

That, for me is a pretty straightforward way of converting a whole bunch of dotnet objects into an XML file!

As you control the headings of the XML branches via the class, its pretty easy to see what is going on in the XML file itself. As much as it seems like more work, I think it is a quite elegant method.

xml

One other useful thing to crop up with this is Binary Serialization. You use the same serializer class to dump all the control positions to a temporary .dat file. Why is this useful? You’ve just used the serializer to introduce an undo buffer, in case the control alignment didn’t happen as you expected, or as I do pick the wrong direction to align.

The RigSelector User Control

RigSelectorVS

The ultimate payoff to the IDE approach is providing a simple way to rebuild the selection dialog from the XML data. The RigSelector usercontrol is just a composite control that only really features a panel. The key is having a method to deserialize the XML to populate the panel with the stored node hierarchy.

We have reused the BorderlessAutodeskToolstripRenderer that we used earlier for the button.

It can resize the parent form to the dimensions of the stored dialog control.

RigSelectorClass

The rest is done by adding the handlers when deserializing the control to fire the selection within max. This perhaps the simplest part. To fire Maxscript code from within a dotnetclass you use –

>ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand(<<MXS_String>>)

Lastly, my best piece of advice about implementing ANY form of custom dotnet control class in 3dsMax.

Don’t be shy about calling enable accelerators

to pass focus back to your max shortcuts!

As much as the maxform control is supposed to handle all this stuff, using a custom class can put a spanner in the works, and there’s nothing worse than realising that you’ve just knocked out your hotkeys.

ManagedServices.AppSDK.EnableAccelerators()

I call this after every button click, which might be overkill but it’s better than losing focus to your dotnetcontrol and being powerless to get them back without a restart or frenzied maxscript call. Any one wanting to do this sort of thing should really look into ManagedServices.dll, there is some great stuff in there.

Polar-DesignSurface

I hope this article has been of interest! Comments are always welcome!

Rig Studio Update!

I recently added automatic rig generation to Rig Studio – This speeds things up considerably.

I have also added a new control – A layer control. This allows you to store a name string of a corresponding layer. This has hide and unhide functionality build in, so you can control character rig and mesh visibility without having to open the layer manager.