Notifications
Clear all

[Closed] pausing maxscript execution without UI lock

Hi,

I am looking for a solution to pause and continue maxscript code execution with a user input (like a modal dialog provides) but without locking the max UI (which a modal dialog does).

so basically i have:

fn1()

waitWithoutUILock() //wait for userinput without locking the rest of the UI

fn2()

there would be a workaround with a for loop which would execute fn1 in the first iteration and fn2 in the 2nd iteration – but unfortunately i have a bunch of recursive functions which would all need to be changed and would probably more buggy and less elegant…

it would be simpler if i have thread control over the maxscript thread so i could simply block this one for a while, is there maybe a .net solution to start mxs from a different thread?

or something else?

thanks in advance for any advice!

28 Replies
1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

AFAIK you can’t pause an iteration without locking the UI in MaxScript.
Perhaps a different approach to the problem would work for what you need.

The following script will create 3 boxes and then process the selection as follow:

  1. Extrude a random face of each node
  2. Randomly rotate each node
  3. Assign a random wirecolor to each node

It is a very rough script just to “illustrate” the idea.
The script does not really pause, but I believe it gives the kind of interaction you are looking for:

(
 
 try(destroydialog ::RO_TEST)catch()
 
 rollout RO_TEST "" width:136 height:96
 (
 	
 	button bt_getNodes "Get Nodes" pos:[16,16] width:104 height:32
 	button bt_process  "Process"   pos:[16,48] width:104 height:32 enabled:false
 	
 	local nodes = #()
 	local n = 1
 	
 	fn ExtrudeNodeFace node =
 	(
 		format "extruding: % 
" node.name
 		polyop.extrudeFaces node (random 1 node.numfaces) 12.5
 	)
 	
 	fn RotateNode node =
 	(
 		format "rotating : % 
" node.name
 		rotate node (eulerangles (random 0 360) (random 0 360) (random 0 360))
 	)
 	
 	fn PaintNode node =
 	(
 		format "painting : % 

" node.name
 		node.wirecolor = random black white
 	)
 	
 	fn ProcessNodes node n =
 	(
 		select node
 		case n of
 		(
 			1: ExtrudeNodeFace node
 			2: RotateNode node
 			3: (PaintNode node; clearselection())
 		)
 	)
 	
 	on bt_getNodes pressed do
 	(
 		nodes = selection as array
 		n = 1
 		if nodes.count == 0 then return messagebox "There is no selection"
 			else bt_process.enabled = true
 	)
 	
 	on bt_process pressed do
 	(
 		ProcessNodes nodes[1] n
 		n += 1
 		if n > 3 do
 		(
 			n = 1
 			deleteitem nodes 1
 		)
 		if nodes.count == 0 then bt_process.enabled = false
 	)
 	
 )
 
 delete objects
 for j = 1 to 180 by 60 do converttopoly (box pos:[j, 0, 0] wirecolor:gray)
 max select all
 
 createdialog RO_TEST
 clearlistener()
 	
 )

yes, there is. you can run mxs code in a different thread. but… if this code does do nothing with max scene and max built-in ui… it might look like a code works for these two cases too, but it’s not safe. sooner or later the code will crash.

hi dennis,

I tried a .net backgroundworker in the meantime which is executing


ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand(“Box()”);

Thread.Sleep(10000);

in the work event.

but, as you already mentioned, it crashes during the 10 second sleep time…

unfortunately I want to manipulate the scene in my code

 lo1

Lately I’ve been toying with the idea of invoking scene-manipulating code from a background thread using max’s own timer ui element.

The trick is that the timer is internally implemented using Windows messages to the max window, and thus always executes on the UI thread.

See the following code, can you make it crash?

(
	fn CurrentThreadID =
	(
		(dotnetClass "System.Threading.Thread").CurrentThread.ManagedThreadID
	)

	fn sceneManipulatingAction =
	(	
		format "Invoking action on thread: %
" (CurrentThreadID())
		box pos:(mapScreenToCP mouse.pos)
	)

	rollout threadSyncRol "Thread Sync"
	(
		local action	
		local sync
		
		timer tim active:off interval:1
			
		on tim tick do
		(
			tim.active = off
			action()
			sync.Set()
		)
		
		fn invokeAction actionFn syncEvent =
		(		
			action = actionFn
			sync = syncEvent
			tim.active = on
		)
	)

	createDialog threadSyncRol pos:[-2000,-2000]

	fn bgThread s e =
	(
		format "Starting BG work on thread: %
" (CurrentThreadID())
		local syncEvent = dotNetObject "System.Threading.AutoResetEvent" false
		for i = 1 to 50 do
		(
			::threadSyncRol.invokeAction sceneManipulatingAction syncEvent
			syncEvent.WaitOne()
			sleep 0.1
		)
		::threadSyncRol.invokeAction (fn f = destroyDialog threadSyncRol) syncEvent
		syncEvent.WaitOne()
		syncEvent.Close()
		
	)
	
	format "UI thread ID: %
" (CurrentThreadID())
	bgw = dotNetObject "System.ComponentModel.BackgroundWorker"
	dotnet.addEventHandler bgw "DoWork" bgThread

	bgw.runWorkerAsync()
)

This method won’t help you create 10000 boxes faster than you could create them in one thread, but it does seem to enable you to keep the UI unlocked while executing scene-altering code in a background thread.

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

where is the place in the code to add anything that modifiers the scene?

 lo1

Inside sceneManipulatingAction obviously

2 Replies
(@denist)
Joined: 11 months ago

Posts: 0

why do you think it works in a different thread? it modifies scene in the main thread. that’s why there is no any benefits and why it doesn’t crash.

 lo1
(@lo1)
Joined: 11 months ago

Posts: 0

That’s exactly what I said, it modifies the scene in the UI thread but it can be called from a background thread, which means the UI doesn’t have to be locked.

i’m probably missing something. it definitely locks UI at the time of any scene modification.
it’s easy to see by changing the action:

	fn sceneManipulatingAction =
	(	
		format "Invoking action on thread: %
" (CurrentThreadID())
		arr = for k=1 to 1000000 collect (random -1e9 1e9)
		sort arr
		free arr
		--box pos:(mapScreenToCP mouse.pos)
	)

for example

2 Replies
 lo1
(@lo1)
Joined: 11 months ago

Posts: 0

Yes, of course. The idea is to do the long calculations in the bg thread and only the scene-specific manipulations in a thread invocation.

(@denist)
Joined: 11 months ago

Posts: 0

you can probably use .net form timer as well. the only thing is it has to be global i guess.

Hi lo,

I tried the code (I guess the ‘::’ was not intendet) and it’s definitvely an interesting solution. Unfortunately it’s not working for me that i can see…

I oversimplified my mxs code to:

f1()

waitWithoutUILock()

f2()

which is not correct since it’s more:

f1=(
print “do something”
waitWithoutUILock()
f2 “1” “2”
)

f2 p1 p2=
(
print “do something”
waitWithoutUILock()
f3 param
)

My need is to pause during the execution of f1 before f2 is called to wait for user input and let the user explorer the scene after f1 was nearly completed. Then whenever the user decides to continue, f2 is executed.

Your solution is a good option if my call of f2 is not inside the f1 but for me that’s necessary. And sometimes I have to stop multiple times in the f1 so not only f2 is called, there might be f3 f4 and so on.

but thanks anyway!

 lo1

Hi

you could use this structure:

rollout threadSyncRol "Thread Sync"
(
	local action, sync
	timer tim active:off interval:1
		
	on tim tick do
	(
		tim.active = off
		action()
		sync.Set()
	)
	
	fn invokeAction actionFn =
	(		
		sync = dotNetObject "System.Threading.AutoResetEvent" false
		action = actionFn
		tim.active = on
		sync.WaitOne()
		sync.Close()
	)
)
createDialog threadSyncRol pos:[-2000,-2000]

fn WaitForUserApproval =
(
	global approveRol
    rollout approveRol "Press Ok to Continue"
    (
        local syncEvent
        button btnOk "Ok"

        on btnOk pressed do
        (
            syncEvent.set()
            destroyDialog approveRol
        )
    )  
    threadSyncRol.invokeAction (fn f = createDialog approveRol)
	local sync = dotNetObject "System.Threading.AutoResetEvent" false
	approveRol.syncEvent = sync
	sync.WaitOne()    
)

fn f2 =
(
	fn f2OnUiThread =
	(
		print "f2"
		--all your scene related work		
	)
	threadSyncRol.InvokeAction f2OnUiThread
	WaitForUserApproval()
	print "finished f2"
)

fn f1 s e =
(	
	fn f1OnUiThread =
	(
		print "f1"
		--all your scene related work		
	)
	threadSyncRol.InvokeAction f1OnUiThread
	WaitForUserApproval()
	f2()
	--clean up
	threadSyncRol.invokeAction (fn f = destroyDialog threadSyncRol)	
)

bgw = dotNetObject "System.ComponentModel.BackgroundWorker"
dotnet.addEventHandler bgw "DoWork" f1
bgw.RunWorkerAsync()
 lo1

When I think about it, you don’t need threads at all.
You can just do this:


fn WaitForApprovalAndThen action =
(
	rollout approveRol "Press Ok to Continue"
    (
        local action
        button btnOk "Ok"

        on btnOk pressed do
        (
	    destroyDialog approveRol
            action()            
        )
    )  
    createDialog approveRol
	approveRol.action = action
)
	

fn f2 =
(
	print "f2"
)

fn f1 =
(
	print "f1"
	WaitForApprovalAndThen f2
)

f1()

again to much simplification from my side… my f1 contains a loop…

fn process =
(
–manipulate scene
a = box()

    for k in children do
    (                
    	k.process()
        WaitForApprovalAndThen f2 k
     )

    -- undo scene manipulation
    delete box()

)

maybe a bit more explanation from my side would be useful:

I have a existing node based scene manipulation system where every node manipulates the scene. after the current scene manipulation system (like creating a box the children are processed. after the children processing the operation should be undone – e.g deleting the box. now to check if each function does what it was supposed to I want to pause the code and then continue. a modal dialog is nearly perfect since it’s pausing the code but the user can’t rotate the viewport and so on, but at the end I’m probably going to live with that for the moment…

even if i would store a function list as arrays as a callstack in the rollout and execute them one after another all my call parameters would have to be stored in the rollout too which is hard to implement for a complex existing code.

but i’m sure your technique might be very useful for other cases!

Page 1 / 2