[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!
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:
- Extrude a random face of each node
- Randomly rotate each node
- 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
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.
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.
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
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.
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!
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()
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!