View Full Version : Assign action to keyboard shortcut through script
Pjanssen 01-16-2010, 05:52 PM Is it at all possible to assign an action (macroscript) to a keyboard shortcut through maxscript? And if so, how?
I've searched the docs for this, but couldn't find anything.
|
|
Pjanssen
01-27-2010, 07:22 AM
I haven't found a solution for this yet. Anyone got an idea on how to do this?
*tumbleweed gently rolls past
HornBerger
01-27-2010, 07:56 AM
I do not know how to directly assign a key to a command but you can do this :-
go to customize user interface assign your keys to the commands then click save and save it as a .kbd file then you can use this command
actionMan.loadKeyboardFile "my_kbdfile.kbd"
Pjanssen
01-27-2010, 08:01 AM
Aha! Does that override all keyboard shortcuts though? So that if I wanted to add a shortcut to my script on someone else's configuration, I'd mess it all up?
Pjanssen
01-27-2010, 08:07 AM
Hmm I guess I could maybe get away with this:
* Save kbd file, actionMan.saveKeyboardFile
* Add my own little bit at the end (sounds a bit risky?)
* Load it back up again, actionMan.loadKeyboardFile
Thoughts? :)
HornBerger
01-27-2010, 08:14 AM
may be you can parse both the files and check for conflicts because if you open the kbd as .txt you can find lines like these :- 24=15 66 EPoly_Bevel`Editable Polygon Object (this is my own shortcut for bevel = ctrl alt b and you can see 66 its the ascii for B) but you can create your own kbd then compare it with the users line by line and if the numbers in the beginning match theres a conflict....
Pjanssen
01-27-2010, 08:23 AM
Yeah I was thinking the same thing.
Do you have any idea what the two numbers in the beginning could be? The 24=15 bit. Maybe that's the modifier keys? (ctrl, alt, shift)
Two keys that I have assigned (H and V look like this)
40=3 72 toggleOutliner`Outliner 647394
41=3 86 outlinerHideSelectionToggle`Outliner 647394
So that'd be
?=? KeyCode MacroName MacroID
HornBerger
01-27-2010, 08:29 AM
i did a little experimentation heres the results :
15 = shift + ctrl
19 = ALT
11 = CTRL
27 = ALT + Ctrl
23 = ALT + Shift
3 = single key thers no combo and the next number represents the ascii
7 = shift
HornBerger
01-27-2010, 08:31 AM
i think max is saving keys in category which are being indexed as array numbers for example i have numbers going from index=SpecialKey Ascii so the first number is an index for a category and the next is the key identifier single keys or combo after that its the ascii of the key
Pjanssen
01-27-2010, 08:38 AM
Ok, so the modifier keys are done by bitwise OR, that's nice :)
none = 3 = 00011
shift = 7 = 00111
ctrl = 11 = 01011
alt = 19 = 10011
Thanks for your help!
HornBerger
01-27-2010, 08:46 AM
yep i think now it can be parsed using maxscript ... Cheers :beer:
Pjanssen
01-27-2010, 08:55 AM
Indeed. macro IDs can be found through macros.list(), although that is rather a tedious procedure.
The array index is still a bit confusing to me though....
edit: hang on, macro IDs? It's not the macro ID that comes after the macro name... must be something else?
HornBerger
01-27-2010, 09:01 AM
the index numbers seam to change when the number on the extreme right changes
*edit: i think this number is related to items in the group list box in the customize user interface > keyboard panel because all my pflow hot keys share the same number
Pjanssen
01-27-2010, 09:11 AM
the index numbers seam to change when the number on the extreme right changes
*edit: i think this number is related to items in the group list box in the customize user interface > keyboard panel because all my pflow hot keys share the same number
Looks like you're right again!
What number do your Main UI shortcuts have? Mine all have 647394 or 0 (for lowlevel actions?). I wonder if this is the same for everyone.
A macroscript generates an idea in the order of lastMacro + 1, so these numbers are not global like classId's and differ from system to system. So referencing these indeed requires to parse the macros.
maybe this helps:
ss = stringStream ""
macros.list to:ss
seek ss 0
while (not eof ss) do
(
local l = readLine ss
if matchPattern l pattern:"*Outliner*" do
(
format "id:%, name:%\n" (filterstring l " ")[1] (filterstring l " ")[2]
)
)
-Johan
Pjanssen
01-27-2010, 09:21 AM
Yeah I was aware of the macro IDs not being the same on each system. The number that should be in the keyboard file (luckily?) isn't the macro ID. It would appear to indicate the shortcut group as HornBerger indicated.
Question is though: how to make sure that the correct value is written?
HornBerger
01-27-2010, 09:30 AM
firstly theres 1 disturbing feature of these keyboard shortcuts try this out open the customize ui > keyboard panel and type "6" in the Hotkey text field it shows its not assigned to any key .. but when you change the group panel to pflow its shows its assigned to the particle view toggle also the pflow toggle works only with the overide toggle on :S (i cudnt have figured that 1 had it not been for johan's post in the previous thread) possibly its only searching for the hot keys in 1 particular category i.e the main ui
yea mine too example :- 32=15 73 EPoly_IgnoreBack_Toggle`Editable Polygon Object 647394
i think internally max is either assigning a particular id to the list box items (that would be weird) or these are handles to the main categories like main ui and pflow etc
Ah I see... well, it's like this with some many OS an UI related stuff, just when you think you're there, it's just one little detail missing that max will not reveal. I remember jumping through many hoops trying to build a flexible and automatic menu system for my scripts.
-Johan
HornBerger
01-27-2010, 09:59 AM
alright i find this number 647394 in the modulartoolbarsui file as well,
Item0=2|0|0|31|4|647394|namedSelSets`Edit|0|0|"Edit Named Selection Sets"||0|enss_maintoolbar
and in many more places in the file i think this number represents a handle to a location where you can add your own tool bars ... its being used by every tool bar i have in max (o btw the modulartoolbarui file is located in c:\program files\autodesk\3d max 2010\ui. for me)
Pjanssen
01-27-2010, 10:23 AM
So it looks like it'd be fairly safe to add shortcuts this way:
* Parse the keyboard file, look for the last entry with "handle" 647394, find its index.
* Find any occurrences of mod_key_bits key that you want to store, remove them (?)
* Add a new line:
(last_index + 1)=mod_key_bits key macro_name`macro_category 647394
I'll write some functions to handle this, and see if that works.
HornBerger
01-27-2010, 10:58 AM
yep i tried it out with my macroscripts it works ! :)
Pjanssen
01-27-2010, 11:05 AM
Awesome! I'll post the script once I have something working.
HornBerger
01-27-2010, 02:38 PM
here's my script :
/*
script: assigns the given shortcut to the given macro script
the function takes 3 arguments the hotkey the macro script name and the category of the macro script
on failure the function returns 0 else 1
-- by Charles Bary
*/
(
fn Assign_Hotkey user_hotkey macro_name macro_category =
(
--Load The current keyboard shortcuts into memory
local filename = actionman.getkeyboardfile() as string,
File_handle = openFile filename mode:"a+",
File_line,
Formated_String = #(),
index_value =0,
mod_keys = #(),
ascii = #(),
i = 0
while not eof File_handle do
(
i+=1
File_line = readline File_handle
Formated_String = filterstring File_line "= "
try
(
if formated_string[formated_string.count] == "647394" do
index_value = formated_string[1] as integer + 1
mod_keys[i] = Formated_String[2] as integer
ascii[i] = Formated_String[3] as integer
if ascii[i] < 93 and ascii[i] > 64 do
ascii[i] = ascii[i] + 32
)
catch()
)
--Generate The Output Data for the hotkey from the input
user_hotkey = tolower user_hotkey
local formated_key = filterstring user_hotkey "+",
user_mod_k= 0,
user_ascii = 0,
j = 1
for j = 1 to formated_key.count do
(
case formated_key[j] of
(
"ctrl": user_mod_k = bit.or user_mod_k 11
"alt": user_mod_k = bit.or user_mod_k 19
"shift": user_mod_k = bit.or user_mod_k 7
default: user_ascii = bit.charasint formated_key[j]
)
)
--Check For conflict with existing hotkeys
local flag = 0
for j = 1 to i do
(
if user_mod_k == 0 then
if (ascii[j] == user_ascii) do
flag = 1
else
if ((mod_keys[j] == user_mod_k) And (ascii[j] == user_ascii)) do
flag = 1
)
-- Assign the shortcut to the specified macro
if flag == 0 then
(
format "%=% % %`% 647394\n" index_value user_mod_k (user_ascii-32) macro_name macro_category to:file_handle
--format "%=% % %`% 647394\n" index_value user_mod_k user_ascii macro_name macro_category
actionman.loadkeyboardfile filename
print "Hotkey assigned successfully!"
close File_handle
return 1
)
else
(
close File_handle
print "Hotkey already assigned choose another"
return 0
)
)
-- function call
Assign_hotkey "shift+ctrl+alt+c" "book_rigger" "my_scripts"
)
Enjoy! :)
Pjanssen
01-27-2010, 06:29 PM
Very nice! I'll have a closer look at the script tomorrow, but I'm sure that it'll work fine.
Thanks very much for your help!
here's my script :
Enjoy! :)
And then there was me, arrogantly thinking it couldn't be done, this is really great! Thanks for sharing!
-Johan
HornBerger
01-27-2010, 10:46 PM
sure, no problemo! but the script is far from complete for instance it cannot assign keys other than the combination of alphanumeric characters and the modifier keys also it does not check if the macro supplied is installed or not, may be i will implement these features and post
cheers!:beer:
Pjanssen
01-28-2010, 08:09 AM
I've had a look at the script and started implementing functionality to replace hotkeys from the mainUI (0) and 'user' (647394) group.
Now the interesting thing is, if you just remove a line for the keyboard file, right in the middle of a sequence, max copes with it just fine. In fact, when it saves the file itself, all indices are fixed again.
Having to fix all the indices manually could be quite a pain, so I'm thinking, how much of a sin would it be do just do it the ugly way by cutting out a line and leaving it at that?
( :blush: )
HornBerger
01-28-2010, 08:46 AM
:hmm: when you execute the command
actionman.loadkeyboardfile()
it fixes the indices in the .kbd file so i dont think its necesarry to fix the indices your self even i havent done that in my script ... i dont really like this way of coding as there is no documentation (atleast i couldnt find one) about the kbd file so i cannot tell for sure wether the script will work on all the O.S i mean the number (647394) is still a little mysterious and the entire script is based on the number and thats what is really "ugly" about the script, what if it changes in the next version of max .. so theres lot of uncertainity about the script, but it works fine on my version of max, may be the better way of coding this would be reading the mainui file and retrieving this number from the file and then using it inside the script that way i think 1 can be assured that it will work on all O.S
however i googled this number and found a couple of kbd files people have posted on the web and all of them have this number, heres 1 : http://www.resistdesign.com/media/ryan.kbd
Pjanssen
01-28-2010, 08:51 AM
Yes it all remains a bit tricky. I think that the least we should do when using the script is making a backup of the users kbd file, just in case something goes horribly wrong.
HornBerger
01-28-2010, 02:19 PM
I was playing around with macro recorder the other day and found some thing interesting about the numbers showing up in the listener for instance :
actionMan.executeAction 0 "50002" -- Tools: Select and Rotate
the number 50002 is of interest here it can be found in the keyboard file :-
13=3 69 50002 0
which is the shortcut key for rotate "E"
also this number is found in the MaxStartUI file -the file contains information about the menus and the buttons inside them as well as the action numbers associated with each button in the particular menu which tell Max to execute the specific action when the button is pressed, In this case the action number associated with rotate button in the edit menu is 50002 max processes this number by calling the function responsible for showing the rotaion gizmo - :-
Item10_Action=0|50002
CustomTitle_10="Rotate"
next, when i performed actions like going to the edit>Transform Toolbox here's what the macro recorder shows :
macros.run "PolyTools" "TransformTools"
the correspoing entry in MaxStartUI file reveals this:
Item_13_Mode=2
Item13_Action=647394|TransformTools`PolyTools
this led me to conclude that this number (647394)actually represents an action code which the windows procedure of max processes and decides to call the macros.run command and probably takes as the parameters the values "TransformTools" and "PolyTools "
therfore the line in the kbd file 0=19 69 EPoly_Extrude_Along_Spline`Editable Polygon Object 647394 means pass the macro name "EPoly_Extrude_Along_Spline" in the category "Editable Polygon Object " to the function 647394 which max regards as macros.run function when the keys
19 69 are pressed i.e ALT+E
Pjanssen
01-28-2010, 03:20 PM
Yes that sounds reasonable, and I think that these assumptions are all quite correct. :thumbsup:
By the way, I am still working on my version of the code, but here's a snippet to generate the mod_key + keycode string. It's loosely based on your code, but I think that had a little error in it: when no mod keys are pressed the mod_key result should be 3.
function createHotKeyString user_hotkey =
(
user_hotkey = toupper user_hotkey
local formatted_key = filterstring user_hotkey "+"
mod_key = 3 --no mod key pressed = bit 1, 2 set.
key_code = 0
for key in formatted_key do
(
case key of
(
"SHIFT" : mod_key = bit.or user_mod_k 4;
"CTRL" : mod_key = bit.or user_mod_k 8;
"CONTROL" : mod_key = bit.or user_mod_k 8;
"ALT" : mod_key = bit.or user_mod_k 16;
default :
if (key.count == 1) then
key_code = bit.charasint key;
else
throw ("Unrecognized key in user_hotkey: " + key);
)
)
--Return string
(mod_key as string) + " " + (key_code as string)
)
and here's my take on parsing the kbd file (work in progress)
function assign_hotkey hotkey macro_name macro_category overwrite:false =
(
local hotkey_string = createHotkeyString hotkey;
local mainUIPattern = "*=" + hotkey_string + "*0";
local userPattern = "*=" + hotkey_string + "*647394";
local kbd_filename = actionman.getkeyboardfile();
local kbd_fileStream = openFile kbd_filename mode:"a+";
local userKeysIndex = 0;
while not eof kbd_fileStream do
(
local kbd_line = readline kbd_fileStream;
-- Look for conflicting hotkeys in mainUI and user hotkey groups.
-- If found and overwrite == true, function removes the line
if (matchPattern kbd_line pattern:mainUIPattern OR matchPattern kbd_line pattern:userPattern) then
(
print kbd_line;
if (overwrite == false) then
return false;
else
print "not implemented yet"
--remove line.
)
else if (matchPattern kbd_line pattern:"* 647394") do
(
userKeysIndex += 1;
)
)
format "%=% %`% 647394\n" userKeysIndex hotkey_string macro_name macro_category to:kbd_fileStream;
close kbd_fileStream;
actionman.loadkeyboardfile kbd_filename;
return true;
)
HornBerger
01-28-2010, 04:32 PM
wow ... smart use of the matchpattern function
when no mod keys are pressed the mod_key result should be 3.
thanks for the pointer ! :)
ps: .kbd file is case sensitive it regards the ascii 65 as "a" and 97 as "numpad1"... but i think your code will work fine as you have used the toupper() :thumbsup: btw is this for the outliner?
Pjanssen
01-28-2010, 05:46 PM
ps: .kbd file is case sensitive it regards the ascii 65 as "a" and 97 as "numpad1"...but i think your code will work fine as you have used the toupper() :thumbsup:
Aha I didn't know about the numpad ones :) Any idea what some of those really large numbers (in the 200s) could be?
btw is this for the outliner?
Yeah, it is initially. Quite a lot of effort for assigning what will probably just be a single hotkey, but oh well, everything for usability I guess...
Of course it should be designed so the code can be used on any project though.
HornBerger
01-28-2010, 06:32 PM
Any idea what some of those really large numbers (in the 200s) could be?
:hmm: not sure .. dunno wether its using unicode or ansi maybe the higher range is for keyboards with other layouts .. again not sure ... normally 65 is 'A' and 97 is 'a' this is the first time i have seen the ascii of numpad1 is 97.. max is full of surprises its like cracking Da vinci's code ;)
JHaywood
01-28-2010, 07:07 PM
This is really helpful. Thanks for digging into this, guys.
Pjanssen
01-31-2010, 03:42 PM
Hornberger: thanks once again for your input on this issue. Inspired by this I've now been able to solve another issue I've had for a long time: propagating key events from a .NET control to max.
I'm working on writing a struct to handle both reading and writing the kbd file. Using that, I can now easily run an action assigned to a keyboard shortcut captured in a KeyUp event of a .NET control.
Once I've got the adding/writing code in place, I'll post the code. It is becoming quite a lot though, ~180 lines already...
HornBerger
01-31-2010, 04:12 PM
wow good to know ... looking forward for the code release ... 180 lines!! ... i have done dot net in past for creating simple database apps (infact i still have vb 2008 sitting on my desktop...) but never really integrated it with max.. still learning maxscript [sigh] but soon... actually got busy with pflow scripting .. its more fun than i thought :P ...
cheers! :beer:
ps: btw hope the code worked for the outliner. i think you wanted to get the "h" key to pop the outliner instead of the "select by name" window (right?)
Pjanssen
01-31-2010, 04:16 PM
ps: btw hope the code worked for the outliner. i think you wanted to get the "h" key to pop the outliner instead of the "select by name" window (right?)
Exactly ;) I noticed how a lot of people who aren't too familiar with max don't really know how to assign hotkeys. So doing this automatically (as an option) when installing will take some frustration away.
Cheers :)
Pjanssen
01-31-2010, 06:47 PM
Ok, here goes :) It took a few hours, but I think this is working quite OK.
Using the structs goes as follows:
--create a new KeyboardActionManager instance.
kbd_man = KeyboardActionManager();
--read the keyboard file. default points to the file used by max at the moment.
kbd_man.readactions();
--add an action (macro in this case, which will probably be the most common usage)
--if successful, the result will be true. (if replace is omitted or false, the result will be false if no action was added)
kbd_man.addActionFromKeyString "H" kbd_man.macro_table_id macro_name:"outlinerFreezeSelection" macro_category:"Outliner" replace:true;
--write the actions to the max kbd file.
kbd_man.writeActions();
--have max reload its kbd file to activate the newly added/replaced shortcut.
kbd_man.maxReloadKeyboardFile();
And the manager code:
/**
* KeyboardActionManager by P.J. Janssen, www.threesixty.nl
* Feel free to reuse or modify this code, but please leave the credits in.
*/
/**
* The KeyboardAction represents a single action assigned to a shortcut key combination.
*
* The shortcut is a combination of mod_key_code and key_code. Both of these can be easily obtained from a string or flags
* through the KeyboardActionManager.getModKeyCode and similar functions.
*
* A KeyboardAction should always have a table_id, but depending on the type (Action or Macro), it can have either a persisten_id,
* or a combination of macro_name and macro_category.
*
* The run() function executes the action or macro.
*/
struct KeyboardAction
(
mod_key_code,
key_code,
table_id,
persistent_id,
macro_name,
macro_category,
function isAction =
(
(persistent_id != undefined AND table_id != 647394);
),
function isMacro =
(
(macro_name != undefined AND macro_category != undefined AND table_id == 647394);
),
function run =
(
if (isAction()) then
actionMan.executeAction table_id persistent_id;
else if (isMacro()) then
macros.run macro_category macro_name;
),
function compare a1 a2 =
(
case of
(
(a1.table_id < a2.table_id): -1
(a1.table_id > a2.table_id): 1
default: 0
)
)
)
/**
* The KeyboardActionManager struct handles reading and writing kbd files.
*
* After being instantiated, the readActions functions should be run, to load the users shortcuts.
* The actions property is an array containing all loaded actions.
*
* This struct was written for some specific needs, and not necessarily to provide a complete interface to kbd files.
*/
struct KeyboardActionManager
(
actions,
main_table_id = 0,
macro_table_id = 647394,
/**
* GET (MOD)KEYCODE FUNCTIONS
*/
-- Returns the mod_key_code based on the modifier key flags provided to the function.
function getModKeyCode altPressed:false ctrlPressed:false shiftPressed:false =
(
local mod_key_code = 3;
if (shiftPressed) do mod_key_code = bit.or mod_key_code 4;
if (ctrlPressed) do mod_key_code = bit.or mod_key_code 8;
if (altPressed) do mod_key_code = bit.or mod_key_code 16;
-- Return mod_key_code.
mod_key_code;
),
--Returns the mod_key_code for the supplied string. Format: "ctrl+alt+x"
function getModKeyCodeFromString key_str =
(
key_str = toUpper key_str;
local str_split = filterString key_str "+";
local mod_key_code = 3;
for key in str_split do
(
case key of
(
"SHIFT" : mod_key_code = bit.or mod_key_code 4;
"CTRL" : mod_key_code = bit.or mod_key_code 8;
"CONTROL" : mod_key_code = bit.or mod_key_code 8;
"ALT" : mod_key_code = bit.or mod_key_code 16;
)
)
-- Return mod_key_code.
mod_key_code;
),
--Returns the uppercase key_code of the first occurrence of a single character in a string with the format : "ctrl+alt+x"
function getKeyCodeFromString key_str =
(
key_str = toUpper key_str;
local str_split = filterString key_str "+";
local notfound = true;
local key_code = 0;
for key in str_split while notfound do
(
if (key.count == 1) do
(
key_code = bit.charasint key;
notfound = false;
)
)
--Return the key_code.
key_code
),
/**
* GET / RUN ACTIONS FUNCTIONS
*/
function getActionFromKeyCode mod_key_code key_code table_id1:undefined table_id2:undefined =
(
if (actions == undefined) do
throw "No actions loaded.";
local notfound = true;
local action;
for a in actions while notfound do
(
if (a.key_code == key_code AND a.mod_key_code == mod_key_code) do
(
if (table_id1 == undefined OR a.table_id == table_id1 OR a.table_id == table_id2) do
(
action = a;
notfound = false;
)
)
)
-- Return found action (or undefined if no action was found).
action;
),
function runActionFromKeyCode mod_key_code key_code table_id1:undefined table_id2:undefined =
(
local action = getActionFromKeyCode mod_key_code key_code table_id1:table_id1 table_id2:table_id2;
if (action != undefined) do
action.run();
),
function runActionFromKeyString str =
(
local mod_key_code = getModKeyCodeFromString str;
local key_code = getKeyCodeFromString str;
runActionFromKeyCode mod_key_code key_code table_id1:main_table_id table_id2:macro_table_id;
),
/**
* ADD ACTION TO ACTIONSET
*/
function addAction mod_key_code key_code table_id persistent_id:undefined macro_name:undefined macro_category:undefined replace:false =
(
if (persistent_id == undefined AND macro_name == undefined) do
throw "Either persistent_id or macro_name + macro_category parameter required.";
if ((macro_name != undefined AND macro_category == undefined) OR (macro_name == undefined AND macro_category != undefined)) do
throw "macro_name and macro_category have to be used together.";
if (persistent_id != undefined AND macro_name != undefined AND macro_category != undefined) do
throw "Using both persistent_id and macro_name + macro_category is not allowed.";
if (actions == undefined) do
throw "No actions defined.";
-- Actions that have to be removed are stored in this array to be removed after iteration is completed.
local removeActions = #();
-- Iterate through actions and check for conflicting actions.
for a = 1 to actions.count do
(
local action = actions[a];
if (action.table_id == table_id) do
(
local conflict = false;
-- Check for duplicate key combination.
if (action.mod_key_code == mod_key_code AND action.key_code == key_code) do conflict = true;
-- Check for duplicate persistent_id if necessary.
if (persistent_id != undefined) do
if (action.persistent_id == persistent_id) do
conflict = true;
-- Check for duplicate macro_name and macro_category if necessary.
if (macro_name != undefined AND macro_category != undefined) do
if (action.macro_name == macro_name AND action.macro_category == macro_category) do
conflict = true;
-- Flag action for removal if it is conflicting with new action and replace is true.
-- If there are conflicts and replace is false, return false.
if (conflict AND not replace) then
return false;
else if (conflict AND replace) do
append removeActions a;
)
)
-- Remove conflicting actions.
for a in removeActions do deleteItem actions a;
-- Append new action.
append actions (KeyboardAction mod_key_code:mod_key_code key_code:key_code table_id:table_id persistent_id:persistent_id macro_name:macro_name macro_category:macro_category);
--Adding was successful, return true;
true;
),
function addActionFromKeyString key_str table_id persistent_id:undefined macro_name:undefined macro_category:undefined replace:false =
(
local mod_key_code = getModKeyCodeFromString key_str;
local key_code = getKeyCodeFromString key_str;
addAction mod_key_code key_code table_id persistent_id:persistent_id macro_name:macro_name macro_category:macro_category replace:replace;
),
/**
* WRITE ACTIONS FILE
*/
function writeActions file:(actionMan.getKeyboardFile()) =
(
if (actions == undefined) do
throw "No actions defined.";
qsort actions KeyboardAction.compare;
--Create a backup of the file we're going to write to.
local backup_file = file + ".bak";
if ((getFileSize file) > 0) do
(
if ((getFileSize backup_file) > 0) do
deleteFile backup_file;
if (not (copyFile file backup_file)) do
throw "Unable to make a backup kbd file. This is too tricky to do without man.";
)
local kbd_fileStream = openFile file mode:"w";
if (kbd_fileStream == undefined) do
throw "Unable to write to file." kbdFile;
try (
local i = 0;
local prev_table_id;
for a in actions do
(
if (a.table_id != prev_table_id) do
i = 0;
if (a.isAction()) then
format "%=% % % %\n" i a.mod_key_code a.key_code a.persistent_id a.table_id to:kbd_fileStream;
else if (a.isMacro()) then
format "%=% % %`% %\n" i a.mod_key_code a.key_code a.macro_name a.macro_category a.table_id to:kbd_fileStream;
prev_table_id = a.table_id;
i += 1;
)
)
catch
(
-- Restore backup and throw exception.
close kbd_fileStream;
deleteFile file;
copyFile backup_file file;
--deleteFile backup_file;
throw();
)
close kbd_fileStream;
),
/**
* READ & PARSE ACTIONS FILE
*/
function readActions kbdFile:(actionMan.getKeyboardFile()) =
(
local kbd_fileStream = openFile kbdFile mode:"rS"
if (kbd_fileStream == undefined) do
throw "Keyboard-File could not be opened." kbdFile;
actions = #();
while (not eof kbd_fileStream) do
(
local kbd_line = readLine kbd_fileStream;
local split_line = filterString kbd_line "= ";
if (split_line.count > 4) do
(
local action = KeyboardAction mod_key_code:(split_line[2] as integer) key_code:(split_line[3] as integer) table_id:(split_line[split_line.count] as integer);
if (split_line.count > 5 OR (matchPattern split_line[4] pattern:"*`*")) then
(
--Macro.
local macro = split_line[4];
if (split_line.count > 5) do
(
for i = 5 to (split_line.count - 1) do
macro += " " + split_line[i];
)
local split_macro = filterString macro "`";
action.macro_name = split_macro[1];
action.macro_category = split_macro[2];
)
else
(
--Action.
action.persistent_id = split_line[4];
)
append actions action;
)
)
close kbd_fileStream;
),
function maxReloadKeyboardFile =
(
actionMan.loadKeyboardFile (actionMan.getKeyboardFile());
)
)
Now a little disclaimer :) The code isn't heavily tested at all. Use it at your own risk. When writing the kbd file, a backup will be made and restored when something goes wrong, but this is no guarantee that it will never mess something up!
The entries in the kbd file will very likely be shuffled around a little, since the manager sorts all actions by table_id. This shouldn't be any problem though.
And the runaction part is still a bit rough, especially on the table_id part...
denisT
01-31-2010, 09:33 PM
there are three functions to help you add macro to KBD file.
fn makeMacroKBD name category char shift:off ctrl:off alt:off =
(
local ss = stringstream ""
local kb = 3
if shift do kb = bit.set kb 3 on
if ctrl do kb = bit.set kb 4 on
if alt do kb = bit.set kb 5 on
format "-1=% % %`% 647394" kb (bit.charasint (toUpper char)) name category to:ss
ss as string
)
fn isTakenKBD char shift:off ctrl:off alt:off file: macrosOnly:on =
(
local act
local ch = (bit.charasint (toUpper char))
local kb = 3
if shift do kb = bit.set kb 3 on
if ctrl do kb = bit.set kb 4 on
if alt do kb = bit.set kb 5 on
if file == unsupplied do file = actionMan.getKeyboardFile()
if (ss = openfile file) != undefined do
(
skipToString ss "="
while not eof ss and act == undefined do
(
str = filterstring (readline ss) " "
k = execute str[1]
c = execute str[2]
i = execute str[str.count]
if k == kb and c == ch and (not macrosOnly or i == 647394) then act = on else (skipToString ss "=")
)
close ss
if act == undefined do act = off
)
act
)
fn addMacroKBD name category char shift:off ctrl:off alt:off file: check:on =
(
local act = #failed, taken = off
if file == unsupplied do file = actionMan.getKeyboardFile()
if (new = not doesfileexist file) or not check or (isTakenKBD char shift:shift ctrl:ctrl alt:alt file:file) != true then
(
str = makeMacroKBD name category char shift:shift ctrl:ctrl alt:alt
ss = if new then createfile file else openFile file mode:"a+"
if ss != undefined do
(
format "%\n" str to:ss
flush ss
close ss
act = #added
)
)
else act = #taken
if act == #added do
(
actionMan.loadKeyboardFile file
actionMan.saveKeyboardFile file
)
act
)
enjoy!
PS. use at your own risk.
HornBerger
02-01-2010, 04:02 PM
:applause: wow ! am speechless ;)
Pjanssen
02-02-2010, 07:24 AM
A small note on using the default keyboard file:
It is possible that the file to which actionMan.getKeyboardFile() points, doesn't exist. With a fresh installation of 3dsmax for example.
Although the script catches this when trying to open it (well, still throws an error, but it's not terrible), you should keep it in mind. It might be useful to include a function like this in the struct:
function getDefaultKeyboardFile =
(
local kbd_file = actionMan.getKeyboardFile();
if (getFileSize kbd_file == 0) do
actionMan.saveKeyboardFile kbd_file;
kbd_file;
),
HornBerger
02-02-2010, 04:48 PM
1 more for the book of wisdom :thumbsup: i think this is the after effects of the reinstall you had to do to fix the cui.registerdialogbar"????" problem
Pjanssen
02-02-2010, 04:50 PM
1 more for the book of wisdom :thumbsup: i think this is the after effects of the reinstall you had to do to fix the cui.registerdialogbar"????" problem
Yup! :) Didn't fix that issue though....
JHaywood
02-03-2010, 12:37 AM
Thanks for all the hard work on this. But one other thing that would be really useful would be a function to return the shortcut key for a given macroscript. That way we could add a tooltip or additional text to macro button telling the user what the shortcut is.
I'm going to try and parse the code you guys have already posted and see if I can figure out how to do that.
denisT
02-03-2010, 01:10 AM
Thanks for all the hard work on this. But one other thing that would be really useful would be a function to return the shortcut key for a given macroscript. That way we could add a tooltip or additional text to macro button telling the user what the shortcut is.
I'm going to try and parse the code you guys have already posted and see if I can figure out how to do that.
fn getKeyMacroKBD name category file: =
(
local pattern = "* " + name + "`" + category + " 647394"
local hots = #()
if file == unsupplied do file = actionMan.getKeyboardFile()
if (ss = openfile file) != undefined do
(
while not eof ss do
(
str = readline ss
sss = filterstring str "= "
k = sss[2] as integer
c = sss[3] as integer
i = sss[sss.count] as integer
if matchpattern str pattern:pattern do
(
ch = toUpper (bit.intaschar c)
if bit.get k 5 do ch = "ALT+" + ch
if bit.get k 4 do ch = "CTRL+" + ch
if bit.get k 3 do ch = "SHIFT+" + ch
append hots ch
)
)
close ss
)
hots
)
returns all hot_keys as array of strings.
Enjoy!
CGTalk Moderation
02-03-2010, 01:10 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
vBulletin v3.0.5, Copyright ©2000-2012, Jelsoft Enterprises Ltd.