[Closed] Controlling dynamics.dlo through MAXscript
I can’t find how to do this through MAXscript:
In my scene, I have set up several different dynamics simulations. I want to make a script that automatically runs (“solves”) the first simulation, then when it’s over runs the second, and so on until they are all done. By “Dynamics simulations” I mean the Dynamics scripted tool in the utilities panel (dynamics.dlo), not Reactor.
I have no idea how to interact with a plugin like dynamics.dlo through MAXscript. In this project, my simulations are pretty hefty and slow, and I have many to do, so I want to run them all automatically, and not have to wait until one is over to manually launch the next.
Thanks in advance!
it doesn’t have an option to do this? how quaint…
oh well... here we go with some more of that crazy windows messages code, at your own peril.
-- open the Dynamics utility in the Utilities panel
utilityPanel.openUtility Dynamics
WM_COMMAND = 0x111 -- Windows Message: Command
CB_GETCOUNT = 0x0146 -- ComboBox message: Get number of items
CB_SETCURSEL = 0x014E -- ComboBox message: Set current selection
CBN_SELENDOK = 9 -- ComboBox notification: Selection ended, OK
maxHWND = windows.getMaxHWND() -- handle to 3ds Max
maxChildren = windows.getChildrenHWND maxHWND -- all of 3ds Max's children elements
-- a function to get the Simulations dropdown from the Dynamics rollout
fn getDynamicsDropdown = (
foundPanel = false
-- loop over all the children
for child in maxChildren do (
-- if we find the Dynamics rollout
if ((child[4] == "RollupPanelTitle") AND (child[5] == "Dynamics")) then ( foundPanel = true )
if (foundPanel) then (
-- then we can return the very first 'ComboBox' (the dropdownlist) we find
if (child[4] == "ComboBox") then ( return child )
)
)
undefined
)
-- get the Simulations dropdown
dynamicsDropdown = getDynamicsDropdown()
-- get its handle
dynamicsDropdown_handle = dynamicsDropdown[1]
-- get its UI resource ID
dynamicsDropdown_id = UIAccessor.getWindowResourceID dynamicsDropdown_handle
-- get the dialog it resides in (the 'body' of the Dynamics rollout)
dynamicsDialog_handle = dynamicsDropdown[2]
-- Now we've got everything we need...
-- This is where you would put your code to, for example, determine which simulations should be run.
-- In the following, it will simply loop over all of the simulations.
numSolutions = windows.sendmessage dynamicsDropdown_handle CB_GETCOUNT 0 0
for i = 1 to numSolutions do (
-- try to change the dropdown selection to the 'i'th simulation
-- if that fails (its result is -1), then exit the loop.
if ((windows.sendMessage dynamicsDropdown_handle CB_SETCURSEL (i-1) 0) == -1) then ( exit )
-- note that we only changed the selection internally. Max doesn't know this until we send a notification as well.
windows.sendMessage dynamicsDialog_handle WM_COMMAND ((bit.shift CBN_SELENDOK 16) + dynamicsDropdown_id) dynamicsDropdown_handle
-- The 'i'th simulation should now be properly selected, so let's hit the button labeled "Solve"
UIAccessor.pressButtonByName dynamicsDialog_handle "Solve"
)
-- The End.
Edit 1: numSolutions
Edit 2: this forum’s
handling with respect to indentation is nnnngh-worthy. Re-pasted to clear that up.
just curious: why would anybody use the old dynamics over reactor? The old dynamics are from R4 ish…
Thank you very much, ZeBoxx2! Worked like a charm. The script is full of MAXScript concepts I had never before even got close to, so I will study it in depth. I’m sure to learn quite some new tricks thanks to this piece of code.
EDIT: Been looking at that “crazy windows messages code”. I can’t find documentation on it in the MAXScript help. Where can I find information about this kind of scripting “kung fu”? in the SDK?
I had never used the old dynamics before. I finally chose it for this specific case, after some tries, for the following reasons:
-
In this case, I have about 30 simulations in one scene, each with hundreds of objects. I don’t need a very precise/realistic simulation, just need the thousands of objects to follow a couple of rough forces and timings. I found out that I had my first rough simulations much faster with the old dynamics (with no collisions) than with reactor, so I could run more iterations to find the space warp values I needed to get the desired result.
-
With the old dynamics solver, I can use a few more kinds of forces than with reactor (where I only have one gravity and reactor winds to mold my objects’ behaviour).
Excellent
Fwiw, I agree with the others that there’s -probably- better ways to do this; but if the old dynamics works for you – shrug
Nay
Bits to look up are… Max Script Help file
- Windows Structure (note that ‘SendMessage’ is not documented, but it’s in one of the examples)
- Interfaces > Core Interfaces > UIAccessor
- Interfaces > Core Interfaces > DialogMonitorOPS (not used here, but it can be very useful)
The Windows structure chapter will tell you about the ‘children’ bit and what values it returns. The information there is fairly important as you get some key information in each element – the handle to the control, the handle to the parent of the control (typically the dialog it’s in), the (mostly useless for us) ancestor handle, the type of control and the label on that control.
Two notes:
- UIAccessor has a .sendMessage as well; it’s less useful than the Windows one as it only returns a boolean value.
- Neither of the sendMessages allow you to specify any values other than integers – so no changing labels, selecting by string value, etc, I’m afraid. You’d have to use an external application.
Next is MSDN’s listing of controls:
-
http://msdn.microsoft.com/en-us/library/bb773169(VS.85).aspx
In each of the controls listed there, there will be a branch with “Reference”. Expand that, and you should get more branches with, among other, “Messages” and “Notifications”. These last two are what you can work with in the context of “sendMessage” stuff.
Clicking on either “Messages” or “Notifications” will tell you how it tends to be used.
So, find the control you’re interested in, then find the messages/notifications and deduce whether it’s what you need.
A simple example is clicking a button; Button Control reference > Messages > BM_CLICK.
http://msdn.microsoft.com/en-us/library/bb775985(VS.85).aspx
That topic will tell you that what it does is send a mouseDown and a mouseUp for the button, and automatically notifies the parent that you just clicked something.
It also tells you how to use it:
lResult = SendMessage(
// returns LRESULT in lResult
(HWND) hWndControl,
// handle to destination control
(UINT) BM_CLICK,
// message ID
(WPARAM) wParam,
// = 0; not used, must be zero
(LPARAM) lParam
// = 0; not used, must be zero
);
You can literally condense this to a windows.sendmessage command:
result = windows.sendMessage hWndControl BM_CLICK wParam lParam
The hWndControl is that “the handle to the control” bit. This is some funky number, typically with a P at the end.
BM_CLICK is a constant. MSDN uses these constants everywhere. You can find the value of that constant in “winuser.h”.
http://www.woodmann.com/fravia/sources/WINUSER.H (one of many sources – try Google)
If it’s not in there, it’s probably some more obscure control/message/notification… in that case, google for “define thething” where “thething” is the message. You’ll likely run into some piece of source code that defines it and you can grab the value from there.
In our case, BM_CLICK -is- in winuser.h and defined as “0x00F5”. That’s a hexadecimal value, you can use that in MaxScript just fine. E.g. “BM_CLICK = 0x00F5”.
wParam and lParam are the two parameters you pass along with the message. For BM_CLICK, there’s not a whole lot extra information to be passed along, so they are zero.
So the code ends up as, for example:
result = windows.sendMessage 4713283P 0x00F5 0 0
or, if you defined BM_CLICK (recommended as ‘0x00F5’ isn’t going to ring a bell too easily):
result = windows.sendMessage 4713283P BM_CLICK 0 0
Now let’s take a more difficult example, namely that of CBN_SELENDOK that I used in the script for you:
http://msdn.microsoft.com/en-us/library/bb775825(VS.85).aspx
It states in its lead-in:
You could look at the WM_COMMAND message’s documentation, but the basic information needed is presented on the CBD_SELENDOK page as well:
So from that, we know that we should be using something like:
WM_COMMAND = 0x0111 -- from winuser.h
result = windows.sendMessage parent_handle WM_COMMAND wParam control_handle
As per the MSDN page, it states that the “parent windows” should be receiving the message. The parent_handle is from the “handle to the parent of the control (typically the dialog it’s in)” bit mentioned near the beginning of this post.
The only value that may appear confusing is the wParam bit, not yet filled in. wParam and lParam are 32bit values. When they say “low order”, they simply mean the last 16bits in a value, and “high order” means the first 16bits.
E.g.:
H H H H H H H H H H H H H H H H l l l l l l l l l l l l l l l l
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
So in our case, the value of the ‘notification message’, CBN_SELENDOK, is (again from winuser.h) 9. The value ‘9’, however, is currently in low order.
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
( 2^0 + 2^3 = 9 )
So we need to shift its bits 16 to the left to put it in the high order.
bit.shift 9 16
589824 -- result value
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
( 2^16 + 2^19 = 589824 )
Then we add on top of that the control’s identifier value. Basically it’s a unique number within a dialog that refers to that particular control. If you have any ‘resource hacker’ type application, you can find this value by checking RT_DIALOG\1033\107.rc inside dynamics.dlo. It says something like:
CONTROL "", 1051, COMBOBOX, 0x50210042, 4, 19, 102, 71
You may completely ignore that, however, as the “UIAccessor.getWindowResourceID” method will get it (the value ‘1051’) for us much more conveniently.
So the end-result code ends up looking like:
result = windows.sendMessage parent_handle WM_COMMAND ((bit.shift CBN_SELENDOK 16) + control_id) control_handle
I’ve skimmed over several bits (literally). If there’s anything in particular unclear, just let me know.
I agree, the old solver doesn’t work worth a $#!^, Use reactor or flex for better solutions.
PFlow would have been perfect, but I didn’t find a quick way (quicker than the dynamics thing) to do the following with PFlow:
My objects are fragments of buildings. So, at the beginning of the shot each specific fragment must be placed in one specific location, so as to make the building whole and unscathed. Then, during the simulation, they progressively move to break the building apart. If there’s a quick way to do that with PFlow, I missed it.