3ds Max maxscript import and export issues, and solutions (with simulating user clicking on buttons) 匯入與匯出問題與解決方法(模擬使用者按下按鈕)

In 3ds Max, users can import and export via UI. In batching process, we utilize scripting to largely reduce users' interactions with calling "importFile" and "exportFile". These two function calls provides "#noPrompt" option for "silently" processing. However, this "#noPrompt" mode is a bit different to "prompt" mode, "functionally". We more focus on export in this article, and you can try the same solution on importing.

Maxscript export and issues

In maxscript, we can export a model via this function call.

exportFile srcFName selectedOnly:true

For no-prompt mode, we can add "#noPormpt" parameter in this function call.

exportFile srcFName #noPormpt selectedOnly:true

However, in no-prompt mode, the exported file (especially in obj format) may have some problems, the most critical one is "vertex index change". (Can we say this is a bug in maxscript?)

To prevent this problem, we should give up the silent no-prompt mode export, and switch back to prompt mode export.

However, the prompt mode brings users pop-up windows, which is annoying and time-consuming during batch process. Accordingly, we should "simulate" button clicking onto the pop-up windows to keep the batch process running down.

Monitor UI pop-up Dialog

Thanks GOD, maxscript provides UIAccessor and DialogMonitorOPS APIs to access and monitor UI components.

Let's start from the Dialog Monitor, which is used to monitor dialog status (such as pop-up).

DialogMonitorOPS.RegisterNotification fnProcessNotification id:functionID

Where user defined functionID, #hook for example, is used to identify fnProcessNotification when register and un-register. fnProcessNotification is the user defined function to deal with the notification.

We can call

DialogMonitorOPS.Enabled = boolean

to enable/disable Dialog Monitor. To un-register a Dialog Monitor, just call

DialogMonitorOPS.RegisterNotification id:functionID

where functionID is the registered ID. If not disable and un-registered while script finished, the registered function still keeps monitoring in the background.

Simulate button clicking

We can use DialogMonitorOPS.GetWindowHandle() to get the pop-up window handler, and then use UIAccessor.GetWindowDllFileName to identify the dll file of the pop-up window and check whether the pop-up is exactly the one we wanna hook. If yes, we can use UIAccessor.PressButtonByName to simulate button clicking. Here is the sample code.

global WindowHandle	/* global variable to store window handle */

fn dmnotification = 
(
	global WindowHandle = DialogMonitorOPS.GetWindowHandle()

	strtmp = ((UIAccessor.GetWindowDllFileName WindowHandle) as string)
	print strtmp
	if findstring strtmp "gw_objio.dle" != undefined then
	(
		strtmp = ((UIAccessor.GetWindowText WindowHandle) as string)
		if findstring strtmp "OBJ Export Options" != undefined then
		(
			UIAccessor.PressButtonByName WindowHandle "Export"
		)
	)
	else if findstring strtmp "3dsmax.exe" != undefined then
	(
		strtmp = ((UIAccessor.GetWindowText WindowHandle) as string)
		if findstring strtmp "Import Name Conflict" != undefined then
		(
			UIAccessor.PressButtonByName WindowHandle "OK"
		)
	)
)

This sample code deals with export and import conflict. When these dialogues pop up, maxscript "clicks" the specific button via UIAccessor API.

Deal with "delayed" button

However, some buttons on pop-up dialog/window is not enabled as the dialog popping-up. For example, there is no "Done" button on the exporting dialog. Instead, after clicking the "Export" button and finishing the exporting task, 3ds Max changes the "Export" button to the "-= DONE =-" button. I.E. the "-= DONE =-" button is not enabled/shown at the beginning of the exporting dialog popping-up. If we use the similar method in preceding section, we can not successfully "click" the "-= DONE =-" button after finishing exporting task. A timer component is what we need to make the batch runs smoothly.

timer clock "testClock" interval:1000 active:false on clock tick do
(
	global WindowHandle	/* use global variable */

	b = UIAccessor.PressButtonByName WindowHandle "-= DONE =-"	/* try to press "-= DONE =-" button */
	
	/* if pressed, stop timer */
	if b == true then
	(
		clock.active = false
		print "clock stop"
	)
)


global WindowHandle	/* global variable to store window handle */

fn dmnotification = 
(
	global WindowHandle = DialogMonitorOPS.GetWindowHandle()

	strtmp = ((UIAccessor.GetWindowDllFileName WindowHandle) as string)
	print strtmp
	if findstring strtmp "gw_objio.dle" != undefined then
	(
		strtmp = ((UIAccessor.GetWindowText WindowHandle) as string)
		if findstring strtmp "OBJ Export Options" != undefined then
		(
			UIAccessor.PressButtonByName WindowHandle "Export"
		)
		else
		(
			allControls.clock.active = true	/* activate timer */
		)
	)
	else if findstring strtmp "3dsmax.exe" != undefined then
	(
		strtmp = ((UIAccessor.GetWindowText WindowHandle) as string)
		if findstring strtmp "Import Name Conflict" != undefined then
		(
			UIAccessor.PressButtonByName WindowHandle "OK"
		)
	)
)