Sorry it took so long. The .NET based control library for 3dsmax has now been recompiled to work with 3dsmax 2017.
Download mmMaxControls v1.10 (13k)
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).
Download mmMaxControls v1.02 (12k)
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:
I’ve updated the library to v1.01, fixing a few bugs and adding a few small methods.
Thanks to DenisT from CGTalk for catching most of these bugs.
I’ve fixed some more bugs and inaccuracies:
VFB+ user and architect Maxim Borisov has recently suggested a script to help make his transform-intensive work easier:
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.
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.
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.
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:
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.
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:
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: