Browsing"Rigging"

SliderMan – Does everything that a slider can

Mar 8, 2015 by     3 Comments    Posted under: 3dsMax, Characters, Maxscript, pipeline, Rigging, User Controls

After many years of rigging in 3dsMax I have to admit I’ve not been a fan of viewport sliders and joysticks. Coming from animation,  I always felt that too much clutter from rigs gets in the way of the character silhouette that you’re supposed to be honing. So I’ve always been a custom attribute squirrel, hiding stuff away in the command panel.

However I was recently rigging some organic tree-like structures, and the rig and the the mesh were such that I felt it was needed to have some form of feedback about the state of the controls twinned with the animation mesh. My rigging sense was tingling – It made me realise that I didn’t have a toolset for making these myself. That’s fine, its not hard to make them, but I thought I’d give myself a couple of train rides to and from work to block something out quickly that I could give back to the community.

So SliderMan is the outcome.

UI

There’s a few options, horizontal, vertical with flipped and centre positioned slider layouts, as well as the traditional joystick setup.

They are resizable (If the option is selected) and there’s some rudimentary alignment tools and a group option to draw a border around them. To resize, just change the width and length properties on the slider border. A joystick has to be square on creation, as the script makes a decision on whether it is a horizontal, vertical or joystick slider on the rectangle dimensions. But the joystick can be changed after creation to a rectangle if needed.

bevel

There’s also a parameter block so you can wire anything you like to them quickly and easily. The values are normalized between 0 and 1 for the default slider, or -1 and 1 for a centered slider.

SliderWire

 

A joystick has two parameters for each direction

JoyWire

That’s pretty much it!

To Use – Just make a rectangle shape that you want to use, or let the script create one for you. There’s a couple of point helpers to handle the resizing and limits, so you can  pop them on a hidden layer in your rig.

Enjoy!

download script

 

 

Adding mirror functionality to a custom rig

Jan 18, 2011 by     No Comments    Posted under: 3dsMax, Characters, DotNet, Maxscript, Rigging, User Controls

On our last production, I had to find a quick way of implementing a system that would allow me to set up basic mirror functions on any custom rig. Specifically, I needed a toolset that would standardise the main functions of the rig like selection tools for the current side of the body, mirror select, copy, paste and IK/FK switching.

I accomplished this with a simple custom attribute that allowed me to store the nodes of each half of the body as weak references. If you don’t already know, weak references are the golden goose of character rigging. They allow you to store arbitrary nodes within the character so that you can retrieve them at any stage without having to select the node itself.

RigGUI

In the video below, you can see how I set up a full mirror/rig selection system on a puppetshop rig. Puppetshop is great because it already has a load of good stuff already built in, so the GUI I add in the video (which is a custom dotnet interface) can simply expose functions from the puppetshop API.

The selector always knows what the opposite side of the rig is because you store each node’s opposite in the parameter block of the attribute.

So what it does is allows me to set up this feature on any rig within a minute or so. Because you can store the node assignments to XML, it is easy to mirror the nodes onto the other side of the rig for even faster setup.

I hope this video gives some insight into how automated setups can save a wealth of time in production.

If you want to try a max script that uses weak references on a rig, please read my previous post –

http://lonerobot.net/?p=158

I talk about them in a bit more detail and show you how you can control rig visibility using them.

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

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.

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
Get
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
Get
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 –
e.Button
e.Clicks
e.Delta
e.Location
e.X
e.Y

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
Get
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

Appendix

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
)