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.