Menu Close

Author: changsooeun

3dsMax tips #2 Custom Viewport Tooltip

When your cursor is hovering over an object in the viewport, you can see that a tooltip pops up and shows object name. 3dsMax 2017+ allow you to customize the viewport tooltip of the active viewport. You can not only show any data you want but also have a custom style using a subset of html tags.

This is a template code for a custom viewport tooltip.

global genTooltip
fn genTooltip = (
	local obj = callbacks.notificationParam() -- Getting the objects under cursor from callback
	local nodeName = obj.name
	local mtlName = (if obj.material == undefined then ("undefined") else (obj.material.name))
	local faceNum = try(obj.mesh.numfaces as string)catch("0")
	local vertsNum = try(obj.mesh.verts.count as string)catch("0")
	local tooltipText = "<u><b><font color=blue size=6>" + nodeName + "</b></font></u><br>"
	tooltipText += "<font  size=4>Layer : "	+ obj.layer.name + "</font><br>"
	tooltipText += "<font  size=4>Material : "	+ mtlName + "</font><br>"		
	tooltipText += "Verts Count: " + vertsNum + "<br>"	
	tooltipText += "Face Count: " + faceNum + ""	
	viewport.appendtooltip tooltipText
)
callbacks.removeScripts id:#MXSVIewportTooltip
callbacks.addScript #preViewportTooltip "genTooltip()" id:#MXSVIewportTooltip

Let’s see what’s happening here.

First, we made a global function, genTooltip , to assemble the tooltip text and add to viewport.
The first line local obj = callbacks.notificationParam() is how you get the the objects under cursor from callback. It is what it is. Don’t touch it. 🙂

Then we build the text for the tooltip, As you can see, you can use a subset of html tags. The devloper mentioned to check this document. http://doc.qt.io/qt-4.8/richtext-html-subset.html

The above code will make a bold(<b></b>) big blue(<font color=blue size=6></font>) object name with underline(<u></u>).
Adding <br> is like adding enter to make a new line.
Then, the size 4 layer name and the material name will be added.
Then, normal size verts count and face count will be added.

After you build the text to display,viewport.appendtooltip tooltipText will resister the text as tooltip.

Lastly, we call this genTooltip  function as #preViewportTooltip callback.
That’s the lase 2 line. If copy/paste the above code into Maxscript Editor and run this once with CTRL+E, it will last for the session.

If you are lazy like me, you can copy/paste the above code into notepad.
Then, save as “customViewportTooltip.ms” in the user startup script folder.
C:\Users\[username]\AppData\Local\Autodesk\3dsMax\2020 – 64bit\ENU\scripts\startup

OK. if you are lazy again, type this in the listener and press num pad Enter.
getdir #userstartupscripts

Now every time when you start max, the script will run automatically for you.

Bonus tip!

If you have a looooooooooooot of objects(I mean really a lot), the hit testing to detect which object is under cursor might take a little bit of time. If you want to save a few milisecond. You can turn off viewport tooltip from here.

If you like this post, you will like renderStacks, too! Let’s click here and check it out!

 

3dsMax tips #1 – 3dsMax.ini setting for file load/save speed up

As you know, 3dsMax searches a few folder to find your asset or map files. I personally do not set any of these search folder. Either having assets in a correct folder or not. But, I know some users rely on this behaviors and even having 100s(!) of search folders.

When you save a max file, 3dsMax will try to resolve all this path which means it will try to search all these folders until it finds the map. As you can imagine, it could take some time if you have a lot of maps and especially a lot of user folders.

3dsMax need to resolve path for 2 reasons. First, there is the asset meta data streams. Second, there is External Dependencies file list in Properties dialog.

You can turn off this process by adding this in 3dsMax.ini. The first one will disable resolving path for properties. Second one will disable for asset metadata stream. If you want to turn on set as “1”.

[Performance]
SaveAuxFilesInFileProperties=0
SaveAssetResolvedFileName=0

Or you can run this code and restart max.

setIniSetting (getMAXIniFile()) "Performance" "SaveAuxFilesInFileProperties" "0"
setIniSetting (getMAXIniFile()) "Performance" "SaveAssetResolvedFileName" "0"

Next! When 3dsMax open a max file, it will also try to revolve asset path to popup Missing External Files dialog. You can see it could take a while if you have a lot of maps with many broken or need-to-be-searched path on the network drive. Also 3dsMax will try to find every single file in IFL! One time I had a scene with many IFL sequences which total more than 20,000 images. You can imagine how painful was to load the file.

Good news! You can turn off this by setting this in 3dsMax.ini.

[Performance]
FindMissingMapsOnSceneLoad=0

Or you can run this code and restart max.

setIniSetting (getMAXIniFile()) "Performance" "FindMissingMapsOnSceneLoad" "0"

I can not remember exactly. But, I think these options are added in 3dsMax 2015.
Try it!

renderStacks can speed up your task, too! Let’s click here and check it out!

3dsMax 2021.1 Custom Default Parameter – and the manager!

Updated to 1.11! 11/29/2021

3dsMax 2021.1 has been released today. There are many fixes and nice improvement.
One of a new feature is the custom default parameter using Maxscript by DefaultParamInterface. Click here for details. This new feature allows you to have custom default for most areas of 3dsMax as long as it is a class.

The simplest  example would be the height segment of Cylinder. The current default is 5. Id you change it, whatever that value becomes the default for the session. Now you can set your own default  height segment using Maxscript DefaultParamInterface. It is not limited to object and modifiers since it is supported for all classes. You can also use this to set your own renderer default include 3rd party plugins.

The custom defaults will be saved in C:\Users\[username]\Autodesk\3ds Max 2021\User Settings\DefaultParameters.ini. Next time when you update to 3dsMax 2022. You just need to copy that file to the corresponding 3dsMax 2022 folder.

BUT, you would wonder… “why do I have to use Maxscript?”. I think setting the custom default using UI will come in the future. There was some prototype for UI in beta. But, it was removed from release because it doesn’t cover some cases.

So, that’s why I made this Custom Default Param Manager script. This script give you a UI to search a class and parameters and allow you to set default value.

Download 1.11

Download, unzip and drag and drop the script into the viewport. It will be nder csTools > CustomDefaultParamManager. Or simply type X > CustomDefaultParamManager.

This is the UI.

On the left, you can choose a class. On the right, you can choose a parameter name. Then, you will see the spinner of checkbox to input the new default.
Then, just press Set button.
Persistent checkbutton is on by default. To make the default for the next session, you need to check this. So, don’t turn off.
The last 3 buttons are to remove the custom default. You can clear custom default for selected parameter or restores to the factory default. You can also remove everything you set.

Have you heard about renderStacks? The smarter way to render? Let’s click here and check it out!

 

3dsMax 2021 OSL Advanced User Interface

3ds Max has many cool OSL shaders. It also has some under the hood improvement for OSL shader developers for better user experience.

For details, you can check Help document. Click here.

Here is some highlights.

  • Additional metadata
    widget type “null”
    metadata connectable
    metadata worldunits
  • Row Packing
  • Custom Widgets
    max:ramp0
    max:actionButton
  • Dynamic UI
    help of the “max:actionButton” shown above, a few helper scripts have been introduced that makes shaders dynamic – as in – the shader is actually modifying its own sourcecode!
    Thanks to this. Now 1-of-5 and 1-of-10 has been consolidated to 1-of-N.
  • Fully Custom UI via QT
    allows a custom layout to be provided in the form of a .ui fil
    You can design cool UI with QtDesigner!
    The following images are a few new OSL maps which is using new Qt UI.

Mini OSL tutorial #1 – Random map rotation per tile

Let me just borrow text from 3dsMax help. You guys should read manual all the time’ Three are many good in formation! I highlighted important aspect of 3dsMax OSL map for you!

Open shading language (OSL) is an open source shading language that is fairly simple to understand. It can be used in several different ways. You can use the OSL Map, which is an execution environment for OSL shaders inside of 3ds Max, and it works like any regular built-in 3ds Max map. There is also a category of pre-loaded OSL maps that you can easily use. In addition, you can use any OSL maps you download from the internet. Finally, you can creating a shader or map in OSL using our development tools. This is a much simpler method to create custom maps than developing the equivalent functionality as a 3ds Max C++ map.

OSL works in any renderer supporting the regular 3ds Max shading API (Scanline, vRay, Corona, etc.). It also works outside of renderers, anywhere in 3ds Max where a regular map is requested, such as in the Displacement modifier. It also works with renderers that support OSL natively, such as Arnold. In those cases, the execution environment inside the OSL map is not sued, instead, the OSL source code, the parameter values and shader bindings are sent to the renderer, which executes the OSL code. More renderers supporting OSL natively are appearing daily.

OSL uses “just-in-time” compilation and optimization of entire shade trees at once, as long as all the shaders in the shade tree are OSL shaders. You can mix OSL shaders and regular shaders, but the optimizations will suffer.

First of all, I really really want to make sure about this.

As I posted before, You don’t have to code to use OSL maps in 3dsMax.

3dsMax OSL is seamlessly integrated just like all other C++ maps. There is ZERO difference in terms of how to use and where you can use. Also if you chain OSL map together, 3dsMax combine them the entire OSL chain and make a single shader under the hood. Essentially Slate ME is acting as an OSL node editor for you. Even better 3dsMax 2021 ships with 123 build-in shaders to start with. At these point, almost all 3dsMax legacy map could be replace with OSL. This mini tutorial is a very good example of using Slate ME as an OSL node editor.

At a Stack post, we god a question.

In this tutorial, the blender guru is using a custom tool in order to randomize the uv’s rotation so we can’t see anymore the repetitive pattern on a large scale tiling texture. He says that, as far as he knows, this kind of tool doesn’t exist in any other 3d software because it involves maths tricks and vectors and nobody wants to deal with this.

Good news! you don’t need custom node for this. Master Zap let me know how to do this with built-in OSL node. I’m posting the master;s answer with my explanation so you can go further.

First, this is the graph. 4 nodes!

 

Let’s see one by one.

UVTransform : Tiling

This OSL map is like Coordinate rollout in other maps. It allows you to move, rotate and scale uv coordinate. I tiled uv coordinate here. So, I tilted here with Tiling parameters.

Tip! You can connect one UVTransform to many OSL maps whicn means you can control the coordinate of all those map at once.

Noise : Give random value per tile

This map generates a random 0-1 value per tile which will be used as rotation value later.

As the name says, it is an OSL version of Noise map. It has 6 types of noise in a map. We will use Cell type which makes random pixel bitmap patter.

Then, set Scale to 1.0 and Octave to 1. This makes the noise function generates one value per tile. If you increase Scale or Octave, it will essentially divide each tile.

Then, turn off Step Function to prevent blending.

Multiply : convert to degree

Multiply 360 so we can get rotation value between 0-360. As you can see, you don’t have to make a map for value B. You can just type in B parameters of the map.

UVTransform : randomize uv rot

This map rotates UV per tile. You don;t need to set any value here. Just connect UVTransform : Tiling to Input(UVW) which inherit tiling from UVTransform : Tiling map. Then, connect Multiply : convert to degree to Rotate.

Now you can connect this map to any maps UVW port.

Download random rotation per tile OSL setup max file

Do you like to work smart? Just like this map? Then, you will like renderStacks. Click here and check it out!

simpleMapBaker

3dsMax 2021 has been release. One of the new feature is the brand new BakingToTexture tool. This tool is written from scratch and the replacement of legacy Render to Texture tool. This is the first iteration of this tool and still in active development.

Along with this tool, now 3dsMax provide the full support of MikkT tangent space from baking to rendering , viewport and SDK.

simpleMapBaker is a simplified front-end of new BakingToTexture. It allow users to bake certain utility map with one click. Also it was used to test the maxscript exposure of BakingToTexture tool.

Download simpleMapBaker

You can bake the following maps.

  • Curvature
  • AO
  • Position
  • World Normal
  • Vertex Normal
  • Material IDs
  • Normal

simpleMapBaker utilize the new override map feature to render most maps at once. Also it uses Arnold’s Curvature and AO shader. Therefore, it will save render preset of the current render.Therefore, it will switch to Arnold for all maps other than Normal map. For normal map, it will switch to Scanline renderer(This is temporary workaround until Arnold normal baking is ready). After baking, the renderer will be reverted to back.

Select objects, and just turn on the map buttons to render and press the big Bake button.

You can set up parameters for some maps and output parameters that BakingToTexture supports.

After bake, it will show all baked maps in the dropdownlist at the bottom. If you select a map there, it will use viewport override for preview baked map.

For normal map, it is using the new MikkT tangent space.

If you want to preview the previously baked maps, select object and press Reload button. It will search the maps and show preview if it finds the map.

Installation
  1. Download simpleMapBaker zip file
  2. Unzip
  3. drag and drop .ms into a viewport
  4. csTools > simpleMapBaker

thanks!

renderStacks can also make your work a lot simpler. Click here and check it out!

 

 

UIAccessor mini tutorial – How to control Make Preview dialog

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.

But, that doesn’t mean you can not set Make Preview automatically. 3dsMax has the ultimate hack(?) for controlling any UI component. UIAccessor and DialogMonitorOPS.

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.

Skeleton code of DialogMonitorOPS  

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.

max preview

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 (
print “Hello”
)
True

DialogMonitorOPS will check if the dialog is “Make Preview” dialog. If so, it will print Hello.

Let’s set custom output path

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[4] == "Edit" collect i[1]
		uiaccessor.setwindowtext edits[1] @"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.

  1. Set custom output path
  2. Set Save as Type to jpg
  3. Press Save button

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[1] @”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[4] == "Edit" collect i[1]
		uiaccessor.setwindowtext edits[1] @"c:\temp\reallyanothertest_.jpg"	

		local comboboxes = for i in (windows.getChildrenHWND WindowHandle) where i[4] == "ComboBox" collect i[1]
		local filetypeHwnd = comboboxes[3] 
		
		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[4] == “ComboBox” collect i[1]
local filetypeHwnd = comboboxes[3]

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” )

How about other controls like checkbox?

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[5] pattern:childUIname do (child_hwnd = i[1])
    child_hwnd
)
local frameNumHwnd = (getChildHwndByName WindowHandle  "Frame Numbers" )
windows.sendMessage frameNumHwnd BM_SETCHECK 1 0

Runscript after Make Preview is done

If you want to automatically run image sequence player like PDPlayer or RAMPlayer or resister to Shotgun, simple add the code after max preview.

Final template code

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 Preview Enhancement

3dsMax 2020 has some nice improvement for Make Preview.

  • Much faster. 1.5 – 3x faster creation on local drives
  • Capture size greater than viewport dimensions supported
  • “Quality” setting accessible from Preview UI (Nitrous only)
  • Default preview filename based on current scene filename
  • 100% output resolution on by default
  • MXS snippet can be executed per frame for custom strings
  • Filename and MXS snippet values can be specified from MXS command line of CreatePreview()
  • After executing the preview, the time slider is returned to the original starting frame
  • “Play when done” accessible from Preview UI
  • If running from MXS command line, avoid dialog boxes, output to listener instead

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.

renderStacks – Render Pass Manager for 3dsMax.