mmMaxControls : .NET Controls with a 3dsmax Style

Lately I’ve seen several posts on the CGTalk MAXScript forum asking how one could make the standard .Net WinForms controls look like the standard 3dsmax UI elements.

Towards this end, I’ve written a collection of some commonly used controls that imitate the 3dsmax look and feel to the pixel, thus maintaining uniformity in your scripted or compiled interface, but giving you the high level of control that DotNet offers.

The package is called mmMaxControls, and is freely available for personal or commercial use. It targets version 2.0 of the DotNet framework, which should make is backwards compatible many versions (the earliest I’ve tested is 3dsmax 2009, theoretically it may work on as far back as 3dsmax 9).

General Features:

  • Look exactly like 3dsmax controls.
  • Control colors update when 3dsmax colors update.
  • No keyboard focus issues that plague standard DotNet controls in 3dsmax (EnableAccelerators is automatically managed by the library).
  • All the customization options of DotNet controls.
  • Can be used in any DotNet language. e.g. if you are developing a 3dsmax plugin in c# without referencing the 3dsmax SDK (for example, for versions earlier than 3dsmax 2012SAP), this is your best bet for native-looking 3dsmax controls.
  • Can be used in both a MAXScript Rollout as a DotNetControl or in a WinForms Form.

Download mmMaxControls v1.02 (12k)

The library is fully documented: Online | Zipped HTML.

Below is the code used for creating the MAXScript rollout at the top of this post.

dotnet.loadAssembly @"C:\mmMaxControls.dll"
try(destroyDialog mmMaxControls)catch()
rollout mmMaxControls "mmMaxControls" width:235
	groupBox grpBtn "Button" width:225 height:55 pos:[5,5]
	dotNetControl mmBtn1 "mmMaxControls.Button" width:62 height:25 pos:[15,25]
	dotNetControl mmBtn2 "mmMaxControls.Button" width:60 height:25 pos:[80,25]
	dotNetControl mmBtn3 "mmMaxControls.Button" width:25 height:25 pos:[143,25]
	dotNetControl mmBtn4 "mmMaxControls.Button" width:50 height:25 pos:[171,25]
	groupBox grpChk "CheckButton" width:225 height:55 pos:[5,65]
	dotNetControl mmChk1 "mmMaxControls.CheckButton" width:25 height:25 pos:[15,85]
	dotNetControl mmChk2 "mmMaxControls.CheckButton" width:70 height:25 pos:[45,85]
	dotNetControl mmChk3 "mmMaxControls.CheckButton" width:72 height:25 pos:[120,85]
	dotNetControl mmChk4 "mmMaxControls.CheckButton" width:25 height:25 pos:[195,85]
	groupBox grpFlyBtn "FlyoutButton" width:110 height:60 pos:[5,125]
	dotNetControl mmFlyBtn "mmMaxControls.FlyoutButton" width:32 height:32 pos:[43,145]
	groupBox grpFlyChk "FlyoutCheckButton" width:110 height:60 pos:[120,125]
	dotNetControl mmFlyChk "mmMaxControls.FlyoutCheckButton" width:32 height:32 pos:[157,145]
	groupBox grpDrop "DropDownList" width:225 height:50 pos:[5,190]
	dotNetControl mmDrop "mmMaxControls.DropDownList" width:200 height:21 pos:[15,210]
	groupBox grpTxt "TextBox" width:225 height:120 pos:[5,245]
	dotNetControl mmText1 "mmMaxControls.TextBox" width:200 height:21 pos:[15,265]
	dotNetControl mmText2 "mmMaxControls.TextBox" width:200 height:63 pos:[15,290]
	groupBox grpSpn "Spinner" width:225 height:65 pos:[5,370]
	label mmSpn1Lab "That's draggable" pos:[15,390]
	dotNetControl mmSpn1 "mmMaxControls.Spinner" width:80 height:16 pos:[140,390]
	label mmSpn2Lab "And screen-wrapping" pos:[15,412]
	dotNetControl mmSpn2 "mmMaxControls.Spinner" width:80 height:16 pos:[140,412]
	on mmMaxControls open do
		local logoImg = dotnetObject "System.Drawing.Bitmap" @"C:\logo.png"
		local blueImg = dotnetObject "System.Drawing.Bitmap" @"C:\logoBlue.png"
		local flyoutStrip = dotnetObject "System.Drawing.Bitmap" @"C:\imageStrip.png"
		mmBtn1.text = "DotNet"
		mmBtn1.image = logoImg
		mmBtn1.imageAlign = mmBtn1.imageAlign.MiddleLeft
		mmBtn1.TextAlign = mmBtn1.textAlign.MiddleRight
		mmBtn2.text = "Controls"
		mmBtn3.image = logoImg
		mmBtn3.ShowFocusFrame = off
		mmBtn4.text = "With"
		mmBtn4.FrameOnMouseOverOnly = on
		mmChk1.text = "A"
		mmChk2.text = "3dsmax"
		mmChk2.image = blueImg
		mmChk2.imageAlign = mmChk1.imageAlign.MiddleLeft
		mmChk2.TextAlign = mmChk1.textAlign.MiddleRight
		mmChk2.ShowFocusFrame = off
		mmChk3.text = "Look"
		mmChk3.ShowFocusFrame = off
		mmChk3.FrameOnMouseOverOnly = on
		mmChk4.image = blueImg
		mmChk4.ShowFocusFrame = off
		mmChk4.FrameOnMouseOverOnly = on
		mmText2.multiLine = on
		mmSpn1.minimum = 0.1
		mmSpn1.maximum = 1
		mmSpn1.increment = .001
		mmspn1.value = mmspn1.defaultValue = 0.5
		mmSpn1.decimalPlaces = 3
		mmSpn2.increment = 1
		mmSpn2.maximum = 10000
		mmSpn2.value = mmSpn2.defaultValue = 100
		mmSpn2.decimalPlaces = 0
		mmDrop.items.addRange #("A", "DropDownList", "control", "that", "inherits", "from", "Combobox")
		mmDrop.selectedIndex = 0
		mmFlyBtn.setImageStrip flyoutStrip 4
		mmFlyBtn.toolTips = #("Easily", "Set", "Button", "Tooltips")
		mmFlyChk.setImageStrip flyoutStrip 4
		mmFlyChk.toolTips = #("Same", "Thing", "But", "Checkable")
		mmFlyChk.flyoutTime = 100
createDialog mmMaxControls

These are the bitmaps used in the example code:

Update: v1.01

I’ve updated the library to v1.01, fixing a few bugs and adding a few small methods.

  • Spinner: Backspace key was not working.
  • Spinner: now behaves correctly for resetting and cancelling a spin operation.
  • Spinner: now goes faster/slower with control/alt buttons pressed, as native 3dsmax spinner.
  • Spinner: text area would not accept the enter key when used as a DotNetControl.
  • Spinner: now shows the correct mouse cursor when dragging.
  • Spinner: ‘dragging’ behavior now only begins when the mouse has left the control area.
  • Spinner: Added .Clear() method for setting the control in an indeterminate state.
  • Spinner: Added events for ButtonDown and ButtonUp, similar to the native 3dsmax events.
  • Button: Contents are now corrently moved 1 pixel to the right and down when pressed.
  • CheckButtons: Frame is now always shown when button is checked, even if FrameOnMouseOverOnly is on.
  • Flyout controls: Opening the flyout no longer steals focus from the host dialog.
  • Flyout controls: Added .ShowFlyout() method for programmatically opening the flyout, e.g. – when you want another button to open the flyout.

Thanks to DenisT from CGTalk for catching most of these bugs.

Another Update: v1.02

I’ve fixed some more bugs and inaccuracies:

  • Spinner: Very minor change of point of transitioning from click to drag behavior, now completely matches 3dsmax.
  • CheckButton: Now shows checked background on mouse down, instead of mouse up.
  • FlyoutButton and FlyoutCheckButton: Flyout items are now correctly shifted 1 pixel right and down on mouse over.
  • FlyoutButton: ItemSelected is now also triggered when the button is clicked without the flyout opening.

mmTransformTypein: A hotkey-compatible TTI launcher

VFB+ user and architect Maxim Borisov has recently suggested a script to help make his transform-intensive work easier:

“Very often I have to move, rotate or scale my objects for some exact numbers of units, and I hate to move mouse pointer to Transform Type-in window, select the slot were to put numbers and move mouse back to objects on other side of my second screen… Idea is to get the Offset part of Transform Type-In dialog near by the mouse pointer or by hitting 4th or 5th button on mouse, or by some keyboard shortcut.”

This tiny MacroScript does exactly this. When executed, it opens the 3dsmax Transform Type-In dialog exactly where the mouse cursor is and sets keyboard focus to spinners. This allows rapid manipulation of scene objects for those users who rely on a fast-paced scene workflow.

edit (01/19/15) – the tool now takes the current axis constraints into consideration and focuses the relevant text field in the dialog.

Rigging a Sprite Sheet in 3dsmax

A user recently asked on CGTalk:

i want to use max to create something like this :

basically i want to have a controller like this one :

but instead of it controlling morph targets , i want it to control a texture on a plane object
so it will cycle or load different images for set values in the controller position.

This article will demostrate a method of hooking up a sprite sheet to a two-dimensional joystick controller.

This will be our sprite sheet for the example:

It may not be much to look at, but for testing purposes it will be the easiest way of determining if we’re getting the right image or not.

You can download the initial and final scene files here:
Download Sprite Sheet Rigging Scene Files (3dsmax 2009)

As a starting point, we have a teapot and a joystick controller. Notice that both the joystick frame and the joystick controller itself have their pivot set to their bottom left corner. This will make it easier to extract the normalized value from the controller. The joystick controller is also parented to the frame.
As you might expect, the joystick has a limit controller applied to it’s X and Y axes to keep it within the frame.

The teapot has a material applied with our sprite sheet loaded as a texture.

First we must scale our UV space. We do this so we get a UV space in the range of 0 to 1 for each sprite. This has the advantage of decoupling the object’s UV mapping from the sprite sheet, allowing us to think in normal terms when mapping our object.
So, if we have 7 sprites on the X axis and 3 sprites on the Y axis, our UV space, centered at [0,0], will range from -3 to 3 on the X and -1 to 1 on the Y.

To scale our current UV space we simply divide the U and V tiling parameters by the number of sprites in each axis.

U tiling: 1.0 / 7 = 0.142857
V tiling: 1.0 / 3 = 0.333333

Now we must write code that will snap the position of the joystick controller to the correct values. We will add float script controllers to the U offset and V offset parameters of our texture.
This is most easily done from track view.
Select the tracks in track view and from the controller menu choose ‘Assign…‘. Select ‘Float Script‘ as the controller type.

First we must create variables for the relevant joystick position controller, and a constant value for the number of sprites in this axis.
Create a variable named xCtrl and use ‘Attach Controller‘ to connect it to the Float Limit controller of the joystick’s X position.
Then create a variable named NumSpritesX and use ‘Assign Constant…‘ to assign it a value of 7.

This is the complete code for the U offset controller:

local normalizedX = xCtrl.value / XCtrl.upper_limit
local xIndex = (normalizedX * NumSpritesX - 0.001) as integer + 1
NumSpritesX / 2. + 0.5 - xIndex

The first line normalizes the position of the joystick by dividing it by the upper limit of the limit controller. because the pivots are aligned as mentioned before, and the joystick is parented to the frame, we get a normalized value in the range of 0.0 to 1.0.
We then use this value to determine the index of the sprite we need to retrieve.
We do this by multiplying the normalized position by the number of sprites on the X axis (in this case, 7) and casting to an integer. This cast is what provides the ‘snap’ behavior. Otherwise we would get a smooth movement across the UV space, which we are trying to avoid.
We subtract -0.001 from the value before casting to integer so that a normalized position of 1.0 (when the controller is farthest to the right) will not loop back to the first sprite. As the result we get is 0-based, we add 1 to convert it to 1-based.
The final line converts the index of the sprite to its valid U offset value. Because our U space is centered at [0,0] and ranges from -3 to 3, we must subtract the index from half of the total number of sprites, and add 0.5 so we end up on the correct boundary.

Do exactly the same for the V offset parameter.

Now everything should be hooked up. You can move the joystick around and the correct sprite should appear on the teapot.

If we want to change the number of sprites in the sprite sheet, all we need to do is:

  • Change the U and V tiling parameters on the bitmap texture.
  • Change the NumSpritesX and NumSpritesY variables in the script controllers of the U and V offset parameters.

Everything else will adjust automatically. The frame can also be resized to whatever we want, as long as the pivot alignment is maintained and the limits are updated.

In a proper production environment, or just to take it a step further, we could also put a custom attribute on the joystick controller with spinners for inputting the dimensions of the sprite sheet, making the rig more animator friendly. The U and V tiling paremeters as well as the variables in the script controllers would then be linked to these custom attribute parameters.

mmRegions: A Handly Little Script for Render Region Management

Lately I’ve been seeing several requests on forums for a way to easily ‘save’ coordinates of regions in a 3dsmax scene. mmRegions is a small free script which does just that. It can store the current render region, restore previously saved regions, and even batch render several regions with one button.

All coordinates are saved as percentages, so they scale linearly with the image size. The regions can also be given a descriptive name are are visualized inside the tool. They are saved automatically inside the scene.

To use it:

  • Install the script by dragging mmRegions.mcr into a viewport.
  • Assign it to a toolbar or a keyboard shortcut from the Customize User Interface dialog. It will be found under the Monotone Minimal category.
  • Open the tool.
  • Set a render region as you normally would.
  • Press the Store Region button (first button from the left) to save the coordinates of the region.
  • Press the Restore Region button (third button from the left) to restore the saved region to the current render region of 3dsmax.
  • To batch-render several regions, use the Batch Render button (last button from the left).

UPDATE: Fixed a bug which would cause the Mental Ray VFB controls window to go blank after batch-rendering multiple regions. Thanks to Spacefrog for the report!

UPDATE #2: Updated to v1.1! New features:

  • Preview window now correctly matches render aspect
  • Now works with V-Ray Frame Buffer regions. If the V-Ray VFB region mode is enabled, creating a new region, restoring a region or batch-rendering will affect that instead of the standard 3dsmax render region.
  • Added spinners for editing the dimensions of the currently selected region.
  • Added option to export/import region data. Data can be exported either as plain text, or as Adobe After Effects keyframe data.
  • Added a confirmation window when ‘delete region’ is pressed.

Download mmRegions