Advanced Tips

There is alot of information in the Maxscript help file but when it comes to dotNet it would be nice with some more info.

Creating a dictionary object in maxscript: Tip Found here

--this creates a dictionary that takes a string key and string value
mySuperDictionary = dotnetobject "System.Collections.Generic.Dictionary`2[System.String,System.String]" 

it = mySuperDictionary.GetEnumerator()
while it.MoveNext() do
 (
   format "Key: % Value: %\n" it.current.key it.current.value
 )

Here is an excellent guide on how to use enums in maxscript.

/*
Using DotNet Enums in MaxScript
With any custom class objects,
it's highly likely Enums will be used,
as they are a way of organizing custom
properties into easily recognizable names,
rather like structs in MXS have members
to describe elements so that you don't
have to resort to array elements with
confusing index numbers.

To use an enum in 3DSMax, you use a plus symbol
in between the class string and the enum type.
therefore, in the maxbutton class,
to get the enums used you would type the following
*/

(dotnetclass "LoneRobot.Controls.MaxButton+CustomIcons").NewDocument

(dotnetclass "LoneRobot.Controls.MaxButton+LayoutStyles").Top
(dotnetclass "LoneRobot.Controls.MaxButton+ColorPresets").MaxUIDark

--you would then use this to set the custom properties in the class.

 

This tip came from the AREA
When you run a script that’s doing a lot of work 3ds Max tends to show ‘Not Responding’ in the title bar and won’t print progress messages in the Listener either. Luckily there is the windows.processPostedMessages() function that can help!

Call the function just before you print the output to the listener:

for i=1 to 100 do
(
 s=timeStamp()

 -- put your code here

 e=timeStamp()
 windows.processPostedMessages()
 format "%s\n" ((e-s)/1000.0)
)

A brilliant tip from Klaas Nienhuis on how to use C# await inside maxscript. He also shows a clever method of adding c# code to maxscript by compiling it directly in memory

http://www.klaasnienhuis.nl/2016/07/asyncawait-in-maxscript/

(
	--Klaas Nienhuis, 2016
	clearListener()
	
	--this .net class sets up async downloads
	local strAssembly = \
"
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace Downloader
{
    public class Download
    {
        public static async Task DownloadFileAsync(string remoteUrl, string localUrl)
        {
            using (HttpClient client = new HttpClient())
			{
				using (HttpResponseMessage responseMessage = await client.GetAsync(remoteUrl).ConfigureAwait(false))
				{
                    if (responseMessage.IsSuccessStatusCode)
                    {
						var byteArray = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
						using (FileStream filestream = new FileStream(localUrl, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize:4096, useAsync:true))
						{
							await filestream.WriteAsync(byteArray, 0, byteArray.Length);
						}
					}
				}
			}
        }

        public static async Task DownloadMultipleFilesAsync(string[] remoteUrl, string[] localUrl)
        {
            List allTasks = new List();
            for (int n = 0; n < remoteUrl.Length; n++)
            {
                allTasks.Add(DownloadFileAsync(remoteUrl[n], localUrl[n]));
            }
            await Task.WhenAll(allTasks).ConfigureAwait(false);
        }
    }
}
"

	function fn_loadClass strAssembly =
	(
		/*
		Description
			Loads a .net class from a string
		Arguments
		Return
			compilerResults which can be instanciated
		*/

		local csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		local compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		compilerParams.ReferencedAssemblies.AddRange #("System.Net.Http.dll","System.dll", "System.Management.dll")
		compilerParams.GenerateInMemory = on
		local compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(strAssembly)
	)
		
	local compilerResults = fn_loadClass strAssembly
	
	local Download = compilerResults.CompiledAssembly.CreateInstance "Downloader.Download"

	--set up a folder to receive the downloaded files
	local  localFolder = (dotnetClass "system.IO.Path").Combine (pathConfig.removePathLeaf (getSourceFileName())) "output"
	makeDir localFolder
	
	local arrUrl = #()
	local arrFilePath = #()
	for n = 1 to 10 do
	(
		append arrUrl @"http://a.tile.openstreetmap.org/8/131/84.png"; --we'll download this file
		append arrFilePath ((dotnetClass "system.IO.Path").Combine localFolder (n as string + ".png"))
	)
	local theTask = Download.DownloadMultipleFilesAsync arrUrl arrFilePath
		
	--we need to wait for the async download task to finish, otherwise we might get in trouble with the rest of the script. If we don't
	--wait, we're counting on the files to be downloaded while they might not be there yet.
	theTask.Wait()
		
	local arrFile = getFiles (localFolder + @"\*.png")
	format "Downloaded % files\r\n" arrFile.count
)

A smart way of getting rid of annoying warning boxes, that does not dissapear with the #noprompt or quiet:true..

http://forums.cgsociety.org/archive/index.php?t-680291.html

global batch_file
global f_error

fn handleBox = 
(
local windowHandle = DialogMonitorOPS.GetWindowHandle()
if (windowHandle != 0) then 
(
local title = UIAccessor.GetWindowText WindowHandle
format "\tWindow Title: %\n" title to:f_error
format "\tWindow Body:\n" to:f_error
local children = UIAccessor.getChildWindows windowHandle
for child in children do
(
format "\t\t%\n" (UIAccessor.getWindowText child) to:f_error
)
--local error = UIAccessor.GetWindowDllDescription WindowHandle
UIAccessor.PressDefaultButton()
true
)
else ( false )
)

rollout BatchRollout "Batch Export" width:341 height:158
(
button btn15 "Source" pos:[13,13] width:96 height:24
button btn17 "Export" pos:[10,110] width:323 height:35
edittext edt11 "" pos:[121,13] width:206 height:24

fn getFilesRecurse dir =
(

direc = GetDirectories (edt11.text + "\\*")
for d in direc do
(
join direc (GetDirectories (d+ "\\*"))
)

append direc (edt11.text + "\\") -- Need to include the original top level directory

maxfiles = #()
for de in direc do join maxfiles (getFiles (de + "*.max"))

------------------
DialogMonitorOPS.unRegisterNotification id:#hello
-----------------

for f in maxfiles do
(
DialogMonitorOPS.RegisterNotification handleBox id:#hello
DialogMonitorOPS.Enabled = true

f_error = stringStream ""
if (loadMaxFile f == true) then
(
if (filePos f_error != 0) then (
format "%\n" f to:batch_file
format "\tERRORS/WARNINGS WHILE LOADING:\n" to:batch_file
format "%\n" (f_error as string) to:batch_file
)
fname = getFilenameFile(f) + ".bin"
exportFile fname #noprompt
continue
)
else
(
format "%\n" f to:batch_file
format "\tFAILED TO LOAD:\n" to:batch_file
format "%\n" (f_error as string) to:batch_file
)
)	
)

on btn15 pressed do
(
source_dir = getSavePath()
edt11.text = (source_dir as string)
)
on btn17 pressed do
(
batch_file = createFile (edt11.text + "\\batchLog.txt")
getFilesRecurse edt11.text
close batch_file
DialogMonitorOPS.unRegisterNotification id:#hello
DialogMonitorOPS.Enabled = false
) 
)
createDialog BatchRollout 341 158


Which gives output (example):

c:\zort\\fail.max
FAILED TO LOAD:
Window Title: 3ds Max
Window Body:
OK

File Open Failed: c:\zort\fail.max


c:\zort\\grass11.max
ERRORS/WARNINGS WHILE LOADING:
Window Title: Missing External Files
Window Body:
Continue
Browse
Don't Display This Message at Render Time


Note that this doesn't actually list the missing external files; that specific dialog could be detected (check for the title) and the missing external files be gathered and added to the error string using some of the other 3ds Max functionality, though..

Edit: in fact, with newish 3ds Max versions, just use the "missingExtFilesAction:" and similar keywords, passing a by-reference variable that will get an array of the missing files (there's others for XRefs, etc.). Much easie :)

Edit 2: Then again, that requires quiet mode - which supresses any of those other dialogs.