A new year, a new adventure
Please visit https://renderstacks.com/
/* VFX Artist | 3D Generalist | TD */
A new year, a new adventure
Please visit https://renderstacks.com/
I have been using jpg sequence for my Make Preview output. It is allways easier and more flexible to deal with image sequence than avi, mov or mp4.
The problem is Make Preview windows is the one of the old window which doesn’t have full exposure to Mmaxscript. 3dsMax dev added more argument for createPreview method in 3dsMax 2020. But, unfortunately some of option in the Make Preview dialog is still not available for Maxscript.
This allow you to emulate user interaction with UI like clicking button, choosing dropdown items and pressing Enter with Maxscript.
If you don’t want all these, Download the final template.
Let’s start with very simple script.
fn setMakePreview = ( local WindowHandle = DialogMonitorOPS.GetWindowHandle() local WindowTitle = (UIAccessor.GetWindowText WindowHandle) if WindowTitle == "Make Preview" then ( print "Hello" ) True ) DialogMonitorOPS.enabled = true DialogMonitorOPS.RegisterNotification setMakePreview id:#setMakePreview max preview DialogMonitorOPS.unRegisterNotification id:#setMakePreview DialogMonitorOPS.enabled = false
DialogMonitorOPS.enabled = true
First, you need to turn on DialogMonitorOPS.so 3dsMax can monitor any UI. Of course, you don’t want to turn on this all the time. So, after our job is done, we will turn off.
DialogMonitorOPS.RegisterNotification setMakePreview id:#setMakePreview
Then, resister your function to run(setMakePreview) and give id. Again, after our job is done, make sure to unresister.
This runs Make Preview
DialogMonitorOPS.enabled = true
DialogMonitorOPS.RegisterNotification setMakePreview id:#setMakePreview
Unresister setMakePreview and turn off DialogMonitorOPS
Now let’s see the setMakePreview fucntion. This function will run all the time while DialogMonitorOPS is running.
The most important thing to know is that this function need to return true at the end. I forgot why. But, you MUST do it. So, just do it.
local WindowHandle = DialogMonitorOPS.GetWindowHandle()
How would you let Maxscript know which UI you want to control? We will use window handle or hwnd which is a unique id of each UI element. The above line will give is the handle of window which DialogMonitorOPS detected.
local WindowTitle = (UIAccessor.GetWindowText WindowHandle)
Then, this above line will give us the title of dialog.
if WindowTitle == “Make Preview” then (
DialogMonitorOPS will check if the dialog is “Make Preview” dialog. If so, it will print Hello.
From now on I’ll only show setMakePreview function.
fn setMakePreview = ( local WindowHandle = DialogMonitorOPS.GetWindowHandle() local WIndowTitle = (UIAccessor.GetWindowText WindowHandle) if WindowTitle == "Make Preview" then ( for i in (windows.getChildrenHWND WindowHandle) do (format "%\n" i) UIAccessor.PressButtonByName WindowHandle "File..." ) True )
I removed print “Hello” and added UIAccessor.PressButtonByName WindowHandle “File…”. As you can read, this will find a button named “File…” and press it for you.
for i in (windows.getChildrenHWND WindowHandle) do (format “%\n” i)
What does this do? It just printed a bunch of things in Maxscript listener. This is how we sees what kinds of UI element is in the current dialog and fid a way to access each UI element. As I said in the begining, we use windows handle to specify UI element. This line will print out the information of all children of the dialog with given handle, Make Preview dialog. it gives us an array for each UI element. The important ones are first(hwnd of child), forth(UI type) and fifth( displayed text).
fn setMakePreview = ( local WindowHandle = DialogMonitorOPS.GetWindowHandle() local WindowTitle = (UIAccessor.GetWindowText WindowHandle) if WindowTitle == "Make Preview" then ( UIAccessor.PressButtonByName WindowHandle "File..." ) if WindowTitle == "Create Animated Sequence File..." then ( -- Set cusom output path local edits = for i in (windows.getChildrenHWND WindowHandle) where i == "Edit" collect i uiaccessor.setwindowtext edits @"c:\temp\test_.jpg" UIAccessor.PressButtonByName WindowHandle "&Save" ) True )
Because we pressed “File…” button. A new dialog pops up, “Create Animated Sequence File…”. In this dialog, we need to these.
To set custom output path, we need to know hwnd of path input UI. But, if you check fifth item of array. Text input doesn’t have name! What should I do? Other information we have is type of control on fourth item. The type UI you can input text is “Edit”. So, I collected hwnd of “Edit”s. Fortunately 3dsMax seems collecting UI info in the same order from top to bottom. So, let’s try on the first one. You can use uiaccessor.setwindowtext to set value on Spinner of Edit. If you want to use own naming convention. Replace @”c:\temp\test_.jpg” with own function or variable.
uiaccessor.setwindowtext edits @”c:\temp\test_.jpg”
The, press “Save” button.
UIAccessor.PressButtonByName WindowHandle “&Save”
Wait? why the name iis “&Save”. How do I know I need &? I also don’t know where & come from. But, I know “Save” did not work. So, I printed out all child UI elem data and checked the names.
Did it work? Maybe or Maybe not. Because 3dsmax remembers the format you used last time, if it was not jpg, Make Preview window will automatically switch to the format. So, we need to choose jpg from format dropdown. Now this is real fun!
fn setMakePreview = ( local WindowHandle = DialogMonitorOPS.GetWindowHandle() local WindowTitle = (UIAccessor.GetWindowText WindowHandle) if WindowTitle == "Make Preview" then ( UIAccessor.PressButtonByName WindowHandle "File..." ) if WindowTitle == "Create Animated Sequence File..." then ( local edits = for i in (windows.getChildrenHWND WindowHandle) where i == "Edit" collect i uiaccessor.setwindowtext edits @"c:\temp\reallyanothertest_.jpg" local comboboxes = for i in (windows.getChildrenHWND WindowHandle) where i == "ComboBox" collect i local filetypeHwnd = comboboxes local CB_SHOWDROPDOWN = 0x014F local CB_SETCURSEL = 0x014E local WM_LBUTTONDOWN = 0x0201 local WM_LBUTTONUP = 0x0202 windows.sendMessage filetypeHwnd CB_SHOWDROPDOWN 1 0 -- Open combobox dropdown windows.sendMessage filetypeHwnd CB_SETCURSEL 7 0 -- Select 7th item windows.sendMessage filetypeHwnd WM_LBUTTONDOWN 0 -1 -- Press left mouse button windows.sendMessage filetypeHwnd WM_LBUTTONUP 0 -1 -- Raise left mouse button windows.sendMessage filetypeHwnd CB_SHOWDROPDOWN 0 0 -- Close dropdown UIAccessor.PressButtonByName WindowHandle "&Save" ) True )
I guess you already have figured out what this does. Yes, it collect hwnd of all comboboxes.Them 3rd one was the Save As Type dropdown.
local comboboxes = for i in (windows.getChildrenHWND WindowHandle) where i == “ComboBox” collect i
local filetypeHwnd = comboboxes
All cool. Butn thet the heck is the next lines?
windows.sendMessage Sends a Win32 message to the HWND specified in the first argument. This is how you emulate UI interaction programmatically.
I commented on the code what each lines does. But, you may think how am I suppose to know all the secret code?
CGTalk maxscript forum has a lot of answers for common operations. You can also google windows message reference like this.
Now since you set jpg as a new format, JPEG Image Control windows pops up. This one is easy. We can just press OK button like this.
if WIndowTitle == “JPEG Image Control” then ( UIAccessor.PressButtonByName WindowHandle “OK” )
Since checkbox text usually doesn’t change, we can search the string pattern of fifth item to find hwnd. This is function to get hwnd using UI name. Then you can BM_SETCHECK window message to check the checkbox. if the first argument is 1, the chebox will be checked. If it is 0, the checkbox will be unchecked.
fn getChildHwndByName parent_hwnd childUIname = ( local child_hwnd = 0 for i in (windows.getChildrenHWND parent_hwnd) where matchPattern i pattern:childUIname do (child_hwnd = i) child_hwnd ) local frameNumHwnd = (getChildHwndByName WindowHandle "Frame Numbers" ) windows.sendMessage frameNumHwnd BM_SETCHECK 1 0
If you want to automatically run image sequence player like PDPlayer or RAMPlayer or resister to Shotgun, simple add the code after max preview.
Here is the cleaned final template code. If you don’t want to read all this, start from this.
Download the final template.
This is made in 3dsMax 2019. Other version might not work with this if there is UI difference.
3dsMax 2020 has some nice improvement for Make Preview.
3dsMax 2020 also has the bug fix for “User defined” Per-view preset missing issue. This issue is related to the permission. If you are still on older version. Make sure to open the permission for folders under 3dsMax root to be able to choose “User Defined” Per-view preset in Make Preview. Or, upgrade to 2020.
One of the new feature of 3dsMax 2020.1 is the new Hot Key Editor plus Hot Keys and underlying system.
The new Hot Key editor is cool. But, the more important change is the way of how the customized hot keys are stored and loaded. When you save and load hot keys in the past, 3dsMax had saved and loaded the entire hot key assignment. Because of this save/load mechanism, any newly added hot keys by 3dsMax dev would have lost when you load the hot keys from previous version. It was not possible to have a studio0wide custom hot keys since the hot keys would have gone when an artist load their own hot keys.
To solve this kinds of issues and make UI customization upgrade-safe, the new override based hot key customization engine is developed. Now 3dsMax stores only the changed hot key assignments in the file when users customize their hot keys. Then 3dsMax will override only the changed keys when the file is loaded.
This will allow users to keep the changed they made while still receiving updates from the global changes. Also you can deploy multiple level of hot key customization. For example, you can have a studio-wide hot keys on top of 3dsmax default hot key while artist still can have own hot keys if they want.
Another change is the new hot keys.Yes, some of hot keys have been changed. This new hot key assignment is the fruit of the community effort of 3dsMax and beta users. There has been many feedback and discussion for the best hot keys on the beta. Special thanks to Sergio Santos for the great contribution. 3dsMax put a nice documentation with map images to show the complete list of changes like this. Please visit HERE for all images.
But, I know there are always ones who doesn’t want to change their 20 years old hot keys. For them, here is a hot key files to go back the legacy hot keys. Download it and load in the Hotkey Editor.
If you have had customized hot key in pre-2019 version, this is the step to move to new hot key system.
1) Generate KBDX file using the maxscript command actionMan.saveKeyboardFile “C:\TEMP\LegacyDefaultUI-2019.kbdx”.
If you specify a KBDX extension, it will convert the entire active hotkey set to the legacy format through the old code. If you specify HSX, it will output in the new format and only contain user customizations.
Or Download this file.
2) Swap it out with the one in your UI_ln/CUI folder (rename the old one to keep a backup). This will use the new hotkey defaults as a reference point when doing the migration, and will treat every difference as a user customization, reaching the same result as if you remapped every single difference back to how it was in 2020-
One of the biggest changes was Improved MCG Package Installation Experience. Let me just norrow the words from dev.
“In previous versions of MCG, the package installation process of an .mcg file involved the automatic extraction of its contained .maxtool and .maxcompound files into the user’s 3ds Max /Max Creation Graph/Tools/Downloads directory. A consequence of this installation method was that common compounds would often conflict with each other, resulting in duplication messages.
In MCG 2018, we’ve simplified the package installation process to make it much more robust. You can now install a .mcg file by dragging it into the viewport. All installed packages now reside in the user’s 3ds Max 2018/Max Creation Graph/Packages directory, and are evaluated as standalone .mcg files. No more file extraction, no more conflicts, no more problems.”
Basically, 3dsMax will consume .mcg package file directly and use the compound in that package first to avoid compound version conflict. Now .mcg file act much like a plugin dll file.
MCG Editor > File > Package Tool Graph… will allow you to package the current tool graph and all compound in a .mcg file.
It means just copying .mcg file into MCG package folder, and drag and dropping .mcg file into 3dsMax viewport will do that f or you. The MCG packages folder is in your user folder/Autodesk. 2018/2019 shares the same structure. But, 2020 MCG package folder is a slightly different.
C:\Users\[username]\Autodesk\3ds Max 2018\Max Creation Graph\Packages
C:\Users\[username]\Autodesk\3ds Max 2019\Max Creation Graph\Packages
C:\Users\[username]\Autodesk\3ds Max 2020\User Tools\Max Creation Graph\Packages
Before I tal about network deployment. I need to mention about this file first. The mCG is implemented with dotnet and Maxscript. The engine is dotnet and UI and communicaion with 3dsMax portion id Maxscript. This means we can actually see the source of many MCG functions which are in C:\Program Files\Autodesk\3ds Max 2020\scripts\Startup\ProceduralContent.ms file. If you dissect this file, you can learn a lot about how MCG is working.
By default, 3dsMax uses the above MCG folders. But, you can also have own custom path for MCGs. RegisterCustomGraphPaths function in ProceduralContentOps struct in ProceduralContent.ms manages how to set the path.
By default, it is set to use 3dsMax.ini file, C:\Users\[username]\AppData\Local\Autodesk\3dsMax\2020 – 64bit\ENU\3dsMax.ini. You can type getMaxiniFIle() in Maxscript Listener to get your 3dsMax.ini file path
You can add MCG Compound Directories, MCG Tools Directories, MCG Package Directories sections and add path like this.
[MCG Tools Directories] tools_dir=C:\path\to\my\tools other_tools=C:\path\to\other\tools [MCG Compound Directories] common_compounds=C:\path\to\my\compounds experimental_compounds=C:\path\to\experimental\compounds [MCG Package Directories] networkPackages=\\path\to\network\packages
But, what if you do not want to use 3dsMax.ini. The one way of using own .ini file for MCG path would be modifying ProceduralContent.ms. Open the file,C:\Program Files\Autodesk\3ds Max 2020\scripts\Startup\ProceduralContent.ms, search “getMAXIniFile()”. Then, replace with whatever path you want.
fn RegisterCustomGraphPaths = ( local iniFile = getMAXIniFile() local settings = dotNetClass "Viper3dsMaxBridge.Settings"
But, then you have to modify on all workstations and render node. That might be too much. The next methods is taking the function from ProceduralContent.ms and make own script.
If you check the code, you can see all the functionality is coming from Viper3dsMaxBridge.Main dotnet class. So, I check what kinds of methods it has with showMethods command. CompileGraphsByFolder is what we need. There are a lot more methods. But, I revmoed not to scare you.
bridge = dotNetClass "Viper3dsMaxBridge.Main" dotNetClass:Viper3dsMaxBridge.Main showMethods bridge .[static]CompileGraphsByFolder <System.String>folder ....
Now, this is the final maxscript you can use
local viperbridge = dotNetClass "Viper3dsMaxBridge.Main" -- Just in case if Viper3dsMaxBridge.dll has not been loaded yet if viperbridge == undefined then ( local bridgePath = (symbolicPaths.getPathValue "$max") + @"\Viper3dsMaxBridge.dll" dotNet.loadAssembly bridgePath returnPassFail:true viperbridge = dotNetClass "Viper3dsMaxBridge.Main" ) viperbridge.ReloadOperators() viperbridge.CompileGraphsByFolder @"D:\myfolder1\" viperbridge.CompileGraphsByFolder @"E:\myfolder2\
Put this in a .ms file like myMCGload.ms. Then, throw in one of your network shared plugin folder. I’m sure you probably already have a plugin folder for a free plugins. Any .ms fil in plugin folder will be automatically runs when 3dsMax start.
I hope this post is helpful for MCGers.
3dsMax developer has changed their delivery model to continuous delivery. Instead of delivering a feature at one release, now a feature will be delivered continuously until all the planned feature is finished. The automatic OSL > HLSL conversion for viewport was the one of them. It has been improved in every PU since tisinception. Now almost all OSL shader will be automatically converted to HLSL including 3rd party OSL shaders.
Also, the viewport playback performance of animated OSL map has been greatly improved.
This is the viewport playback of the sample file for my OSL shader pack1 in 3dsMax 2019/2020.
Since I posted the Alembic improvement of 3dsMax 2019 release, each PU has been added more and more improvements continuously. Let’s check what has been added.
3dsMax 2019 introduced the export of per object propery. But, it was only compatible between 3dsMax. With the PU3 update, you can export/import per object properties via .userProperties and .arbGeomParams. This allows a greater compatibility between 3dsMax and Maya/Houdini.
Here is an example of Alembic file exported to Maya.
Here is some details.
This is added in PU1. This allows to browse the content of the alembic object even without opening the alembic file. Now PU3 allow you to open the Alembic Inspector for the already imported alembic files. Use the Alembic Inspector button in the Alembic container object(the root Alembic object with Alembic logo icon),
Alembic Inspector is also accessible via Maxscript. Link.
Maya is very picky about reading the multi UV and vertex color in Alembic file. To send multi UV(UV channel 2+) to Maya, you need to choose UV for Extra Channels type. Also vertex color data from Maya will be imported as a proper vertex color channel. Before PU3, the vertex color channel was imported as an UV 2+ channel.
Support for instances allows files to be much smaller while maintaining complexity and can dramatically improve export speed. PU2.
Alembic library has been updated to 1.7,5 in PU2.
Alembic Transform controller playback is more than 2x faster in PU3. Also Source and Object browse buttons are added for Alembic Transform controller.
Before PU3, None if mMaterial ID was exported.
Before PU3, it was caching from frame 0 all the time.
Thanks for 3dsMax team for continuous effort to improve Alembic support!
Since it is introduced in 3dsMax 2019, OSL Map has been comtinuously improved in every release.
There has been many updated on performance, OSL editor useability and viewport display.
The most important improvement among all is the viewport display.
Now 3dsMax viewpot can display almost all shippinig OSL and many 3rd party OSL shaders properly even as 3D procedural map.
How canit even support random 3rd party OSL shader?
The 3dsMax rendering team has developed automatic OSL > HLSL converter instead of making HLSL shader for each OSL shader.
The OSL shaders in the following images are all 3rd party OSL as 3D procedural map.
It is oddly satisfying to see all the 3D shaderes in the viewport exactly as renders.
And,,, a little bird told me even more stuff might come in the future. 🙂
3dsMax 2019.1 has been release with many improved features.
Many new features has been added to Alembic/OSL/Fluid/Arnold. Python and Project got some improvement. Plus 94 fixes.
One of the item among “Bringing your ideas to life in 3ds Max 2019.1 Update” was “Attaching large amounts of meshes is up to 7 times faster”.
So, I decided to test if it is true. The enhancement was done for Collapse utility. I think 3dsMax dev choose to enhance this because this is the only attach which keeps explicit normals.
I test total 4 files. I open the file and attach all geometries.
The result is… drum roll…
Obj:5616 Verts:99,686 Faces: 165,505
2019 : 3.335s
2018 : 32.724
9.81 times faster
Obj:19913 Verts:2,264,540 Faces: 3,265,430
2019 : 11.2s
2018 : 2236s
199.35 times faster
Obj:2429 Verts:2,766,360 Faces: 3,850.480
2019 : 9.5s
2018 : 546s
57.23 times faster
Obj:29596 Verts:9,065,430 Faces: 7,722,170
2019 : 274s
2018 : 19980s
72.95 times faster
It is actually far better than 7 times!
One of the hidden gem of the new 3dsMax 2019 feature is the Custom Scene File Data Streams. This allows uses attach custom strings to your max and access from outside of 3dsmax. It is not only eliminate the need of companion external file for such data but also opens up accesing of .max file related data to any program or language.
For more details, check the help.
To show the power of this feature, I made a sample implementation of this feature. csMergeBy script. It allows users to merge object from other .max file by Layer, SelectionSet, ObjectID and Material name.
There are 2 files in the zip file. The first one is csMergeBySave.ms which registers post save callback to save the scene data as xml and write to Custom Scene File Data Stream. You need to drop this into your startup script folder or one of plugins folder to run this script automatically everytime when you launch 3dsMax.
For exmaple, C:\Users\[username]\AppData\Local\Autodesk\3dsMax\2018 – 64bit\ENU\scripts\startup
This script would increase file saving a littlt bit. When I tested it, it took 0.1 sec for for 1,000 objects. BUT, I also have an extreme case of 29,769 objects which add 4.5 sec for saving. Considering the file saving itself would take some time, It was not noticable. But, I still want to give you heads up for the possible saving time increase.
The second file is csMergeBy.ms. This is actually a macro script. Just drag and drop this file into iewport. It will make csTools > csMergeBy action. This is the merge dialog.
How to use is simple.
2. Choose “Merge By” method from dropdownlist. You can use…
Layer – with hierarchy
Layer – no hierarchy
3. Choose items in the left list. You can CTRL+click to select multiple items. CTRL+click also deselect item.
As you choose item in the left list. Preview object list will be updated.
By default, all objects in the preview will be selected. But, you can only select the objects you want.
If you have a lot of objects(1000+), it might take a while to update the object list. Then you cah turn off Preview object names chekbox to not to show the object list. Then all objects in the selected items will be merged.
4. Then press Merge.
5. All megered object will be in “JustMerged” selection set for your convinience.
While I was beta testing 3dsMax 2019, I made a few OSL maps. Nothing spectacular. Most of them are just “artist-friendly” pre-made setups to save some nodes.
This is a renderable version of my superClayMode script. Plug into the UVW slot of Bitmap Lookup.
You can conver to a vector value of other color space to RGB.
You can convert a color from RGB to other color space. Output value is vector. after you process it, you can convert back to RGB with Color By Colorspace
It generates random integer index by number or color input. When you need to randomize something by Object ID, Materaal ID, Node Name and Node Handle, this map will be very handy. Of course you can also do the same thing by assembing a few other maps. I just made as one map for your convinience.
You can generate sine wave of time. It supports amplitude, period, phase value and square wave option. Check my sample animation. This map is very handy when you need to make some cyclic map animation.
!!! You DON”T need to connect Frame Number input. It already has float expression controller assigned. !!!
You can get normal value of pixel in various spaces.
Falloff map OSL version. It supports Perpendicular/Parallel and Towards/Away.
Legacy Composite map OSL version. It supports all bklending mode in legacy Composite map. I was trying to support all Photoshop mode. But, I gave up on Vivid Light and Linear Light.
Here is a QC image that shows this map and legacy Composite map are producing the same result.
1. Make “OSL” subfolder in any plugin folder
2. Unzip and copy *.osl files into the folder
Here is the shader tree for the animation. I used the distance from local center and random sin wave as the light intensity. No keyframes al all. The max file is included in the shader pack .zip file.