I’d posted this over a year ago on the autodesk forums, but never got an answer.
I’ve got a script that dumps out skin weights and then reloads them. The loading is the issue - if I run the loadEnvelope command by stepping through the script, it works. If I run it in a loop, it fails UNLESS I run the command twice in sequence.
So this fails:
skinOps.loadEnvelope obj.modifiers[#Skin] skobj
But this works:
skinOps.loadEnvelope obj.modifiers[#Skin] skobj
skinOps.loadEnvelope obj.modifiers[#Skin] skobj
I’ve tried adding sleep before and after the command, but no joy.
The script works with the double execution, but I’d rather not do this since it seems…dirty. Has anyone else done this?
Incidentally there is no way to bypass the load skin weights dialogue, but I found a usefull callback written by by BoBo for Anoon (which I’ve broken, so I’ve removed from the post. Will updates when I get that to work.)
I seem to remember running into the same problem a few years ago and eventually having to solve it with a deftly placed completeRedraw() either directly before or directly after skinOps.loadEnvelope, which is still as ugly as hell but not as bad as running it twice.
-- use a callback to press the buttons
-- I found this here: http://forums.cgsociety.org/showthread.php?t=485300
fn LSW_EnvelopeCallbackFunction =
(
WindowHandle = DialogMonitorOPS.GetWindowHandle()
theDialogName = UIAccessor.GetWindowText WindowHandle
if theDialogName != undefined and matchpattern theDialogName pattern:"*Load Envelopes*" do
UIAccessor.PressButtonByName WindowHandle "Match by Name"
if theDialogName != undefined and matchpattern theDialogName pattern:"*Load Envelopes*" do
UIAccessor.PressButtonByName WindowHandle "OK"
true
)
The looping skinweight loader (with some cleanup)
-- Load the skin weights back on.
-- Need to use completeRedraw() or the weights wont load when in a loop. Madness.
-- Fix was from here: http://tech-artists.org/forum/showthread.php?p=4034
-- Use a callback to press the buttons
-- I found this here: http://forums.cgsociety.org/showthread.php?t=485300
DialogMonitorOPS.RegisterNotification LSW_EnvelopeCallbackFunction ID:#ANoon_Envelopes
DialogMonitorOPS.Enabled = true
completeRedraw()
skinOps.loadEnvelope obj.modifiers[#Skin] skobj
DialogMonitorOPS.Enabled = false
DialogMonitorOPS.UnRegisterNotification ID:#ANoon_Envelopes
Here’s a better version I wrote some time ago, posted at CGTalk:
[source=“mxs”]fn setUICheckboxState hwnd state =
(
/*******************************************************************************
<DOC> toggle an UI checkbox’s checked state via UI messages/notifications
Arguments:
<int> hwnd: HWND of the control
<int> state: checked state. 1 = check, 0 = uncheck
<skin modifier> curObj: The selected skin modifier (must be in modify panel)
Return:
<ok> should check/uncheck the checkbox and have its change effected
*******************************************************************************/
local BN_CLICKED = 0 – clicky message ID
local BM_SETCHECK = 241 – checkbutton toggle message ID
local WM_COMMAND = 273 – windows command message
local parent = UIAccessor.getParentWindow hwnd
local id = UIAccessor.getWindowResourceID hwnd
windows.sendMessage hwnd BM_SETCHECK state 0
windows.sendMessage parent WM_COMMAND ((bit.shift BN_CLICKED 16) + id) hwnd
OK
),
fn confirmLoadEnvelopes removeIncomingPrefix:0 removeCurrentPrefix:0 =
(
/*******************************************************************************
<DOC> Manipulate the Load Envelopes dialog via the UI Accessor.
Arguments:
<int> removeIncomingPrefix: Corresponds to the “Remove Incoming Prefix” checkbox. 0 is false, 1 is true
<int> removeCurrentPrefix: Corresponds to the “Remove Current Prefix” checkbox. 0 is false, 1 is true
Return:
<bool> true (needed for DialogMonitorOps)
*******************************************************************************/
–local BM_SETCHECK = 241
local hwnd = dialogMonitorOps.getWindowHandle()
if (uiAccessor.getWindowText hwnd == “Load Envelopes”) then
(
local children = windows.getChildrenHWND hwnd
for child in children do
(
if (child[5] == “Remove Incoming Prefix”) then
(
setUICheckboxState child[1] 1
)
else if (child[5] == “Remove Current Prefix”) then
(
setUICheckboxState child[1] 1
)
)
fn loadEnvelope theSkin envFile removeIncomingPrefix:false removeCurrentPrefix:false =
(
/*******************************************************************************
<DOC> Load an .env file. There is no function for silently loading an .env, so this
is a UI Accessor workaround.
Arguments:
<skin modifier> theSkin: The selected skin modifier (must be in modify panel)
<string> envFile: Filename of the .env file.
Return:
<ok>
*******************************************************************************/
–determine which confirmLoadEnvelopes to use
local confirmFn = case of
(
(removeIncomingPrefix and removeCurrentPrefix):confTT
(removeIncomingPrefix and not removeCurrentPrefix):confTF
(not removeIncomingPrefix and removeCurrentPrefix):confFT
(not removeIncomingPrefix and not removeCurrentPrefix):confFF
)
Here’s a better version I wrote some time ago, posted at CGTalk:
fn setUICheckboxState hwnd state =
(
/*******************************************************************************
<DOC> toggle an UI checkbox's checked state via UI messages/notifications
Arguments:
<int> hwnd: HWND of the control
<int> state: checked state. 1 = check, 0 = uncheck
<skin modifier> curObj: The selected skin modifier (must be in modify panel)
Return:
<ok> should check/uncheck the checkbox and have its change effected
*******************************************************************************/
local BN_CLICKED = 0 -- clicky message ID
local BM_SETCHECK = 241 -- checkbutton toggle message ID
local WM_COMMAND = 273 -- windows command message
local parent = UIAccessor.getParentWindow hwnd
local id = UIAccessor.getWindowResourceID hwnd
windows.sendMessage hwnd BM_SETCHECK state 0
windows.sendMessage parent WM_COMMAND ((bit.shift BN_CLICKED 16) + id) hwnd
OK
),
fn confirmLoadEnvelopes removeIncomingPrefix:0 removeCurrentPrefix:0 =
(
/*******************************************************************************
<DOC> Manipulate the Load Envelopes dialog via the UI Accessor.
Arguments:
<int> removeIncomingPrefix: Corresponds to the "Remove Incoming Prefix" checkbox. 0 is false, 1 is true
<int> removeCurrentPrefix: Corresponds to the "Remove Current Prefix" checkbox. 0 is false, 1 is true
Return:
<bool> true (needed for DialogMonitorOps)
*******************************************************************************/
--local BM_SETCHECK = 241
local hwnd = dialogMonitorOps.getWindowHandle()
if (uiAccessor.getWindowText hwnd == "Load Envelopes") then
(
local children = windows.getChildrenHWND hwnd
for child in children do
(
if (child[5] == "Remove Incoming Prefix") then
(
setUICheckboxState child[1] 1
)
else if (child[5] == "Remove Current Prefix") then
(
setUICheckboxState child[1] 1
)
)
UIAccessor.PressButtonByName hwnd "Match by Name"
forceCompleteRedraw()
UIAccessor.PressButtonByName hwnd "OK"
--UIAccessor.PressDefaultButton()
)
true
),
fn confTT = (confirmLoadEnvelopes removeIncomingPrefix:1 removeCurrentPrefix:1),
fn confTF = (confirmLoadEnvelopes removeIncomingPrefix:1 removeCurrentPrefix:0),
fn confFT = (confirmLoadEnvelopes removeIncomingPrefix:0 removeCurrentPrefix:1),
fn confFF = (confirmLoadEnvelopes removeIncomingPrefix:0 removeCurrentPrefix:0),
fn loadEnvelope theSkin envFile removeIncomingPrefix:false removeCurrentPrefix:false =
(
/*******************************************************************************
<DOC> Load an .env file. There is no function for silently loading an .env, so this
is a UI Accessor workaround.
Arguments:
<skin modifier> theSkin: The selected skin modifier (must be in modify panel)
<string> envFile: Filename of the .env file.
Return:
<ok>
*******************************************************************************/
--determine which confirmLoadEnvelopes to use
local confirmFn = case of
(
(removeIncomingPrefix and removeCurrentPrefix):confTT
(removeIncomingPrefix and not removeCurrentPrefix):confTF
(not removeIncomingPrefix and removeCurrentPrefix):confFT
(not removeIncomingPrefix and not removeCurrentPrefix):confFF
)
DialogMonitorOps.Enabled = true --DialogMonitorOps.Enabled = false
DialogMonitorOps.RegisterNotification confirmFn id:#pressSkinOK
skinOps.LoadEnvelope theSkin envFile
DialogMonitorOps.unRegisterNotification id:#pressSkinOK
DialogMonitorOps.Enabled = false
ok
),
That’s the code for anyone who needs it. So you’d put that in a struct, and call:
It seems odd to me that we have to use two workarounds - forcing a redraw AND forcing a silent mode. Has anyone looked in the SDK to see if it’s has a hook to bypass the callback button pressing?
I have worked around this same problem too. Great stuff here - mine was based on that same cgtalk thread IIRC.
Since then I’ve actually changed over to use the skinutils for this stuff as it fits our particular needs a bit better. You can dump/load weights to meshes and save those max scenes out instead of .env files. We’ve got a lod auto-weighter and general save/load envelopes scripts so the guys can failr smoothly store and reuse weighting on similar meshes on similar rigs. It’s pretty nifty even if hacked together.
…then I get a unique copy of the skin modifier with all the bones added, the correct bone affect limit set etc, but none of the actual skinning information is carried over.
I could of course bake, save and load the skin weights, but that seems a little bit convulted - especially since the entire process works manually via the right click copy and paste in the modifier stack pane.
My other solution is to copy the skinweights to an array, then apply that to the copied modifier.
Not sure what is the cause, but it seems DialogMonitorOps doesn’t work anymore.
tested on:
Windows 7 - 3dsmax 2014
Windows 8.1 - 3dsmax 2014 / 3dsmax 2011