I couldn’t find anything about this event. What is it for?
Run an MCR file but ignore its macroscrip header?
is it somehow related to tri-state of a macro button? I always thought it can only be on/off
isIndeterminate handler is usually used for menu and quad menu items. Because you can see it in UI (see the help).
You can use it in sense of ‘tri-state’ like as an ‘available’ action for not all currently selected (visible, etc.) nodes.
For example some action works for selected geometry class nodes. But current selection also includes helpers…
So you can define ability of this action as ‘indeterminate’ - means it works but not for all.
If there is no geometry nodes in the current list you should set it to ‘disabled’, if all nodes are geometries you should set it to ‘enabled’
Thanks for the explanation, Denis.
There’s not a single word in reference about this particular handler.
Can you please elaborate this?
Does Max treat MS and MCR different, ie, they are not just file extensions?
OK, I understand now and agree the idea I had can’t work.
Just drag-drop macroscripts into max to try them out will need more steps than that. I will need to know what category and what name the macroscript has. Then I need to track it down, before I can run it. Thankfully, I do have a little tool that quickly lists every macroscript there is, sorted by categories. Yet, it will require me to look into the macroscript, to figure out name and category.
So let me redesign my idea then 
I drag drop the macroscript onto a “catcher” (that figures out the path of the file and then fileIn it for me) or I browse for the macroscript and press run, or equivalent method.
Then we’d need the maxscript to figure out the name and category that was just created.
Is that possible?
If so, then the next step could be simply presenting [macro name] [category] [run-button]
In case there are more macroscripts in the same file, they just get listed underneath each other
[macro name1] [category] [run-button]
[macro name2] [category] [run-button]
[macro name3] [category] [run-button]
What do you say?
With something like this you can all the registered MacroScripts. The last entry should the latest MacroScript you registered.
If you run it via MaxScript, then the MacroScript ID is returned and you could use it for tracking it.
(
stream = "" as stringstream
macros.list to:stream
seek stream 0
macroscrips = #()
while not eof stream do append macroscrips (readline stream)
lastentry = filterstring macroscrips[macroscrips.count] "\""
lastentry = for j in lastentry where j != " " collect trimleft (trimright j)
format "MacroScript:\n"
format "\t ID: %\n" lastentry[1]
format "\t Name: %\n" lastentry[2]
format "\t Category: %\n" lastentry[3]
format "\tInt. Category: %\n" lastentry[4]
format "\t FileName: %\n" lastentry[5]
format "\t Icon File: %\n" lastentry[6]
format "\t Icon Index: %\n" lastentry[7]
)
MCR file expects only macros in it. MS file doesn’t have this expectation. But both of them will be just executed by the system
I tried this out just now.
When I put an MCR under $userMacros, they get loaded upon max start, or when I load them with macros.load().
Putting an MS file either under $userScripts or $userMacros don’t get loaded until I drag-drop them into max or load them somehow.
I was hoping that comments and stuff outside the macroscript would be left intact when I work with MS, but the effect was just the same.
So I dont really get the benefit. What am I doing wrong or what am I misunderstanding?
Can confirm that it didn’t work properly because of a space in my category name.
What if the MCR-file has multiple Macroscripts like some developers do? Can I catch that?
Or maybe if we had a “catcher UI”, ie a window that says “drop your script here”, we could keep track of everything that was dropped here?
Every MacroScript, regardless if they are in the same or different files, will have a new entry, so if you run a file with 3 MacroScripts in it you’ll have to delete the latest 3 entries not just the last one.
I don’t know if there is a callback to catch when a new MacroScript is registered. A quick workaround could be to monitor the Users MacroScript folder and record how many additional files are in there since you started the Max session.
thank you, works great now 
About monitoring, I propose an easier way. What your script does is giving us the last ID.
- We keep track of that ID
- We drop the files onto max (I think we dont even need a “drop your files here”-GUI anymore)
- We check what the last ID is now.
- We list all the created Macroscripts, the category and a run-button
[ Macroscript name ] [Category name ] [ Run ]
I guess this would only work if we had the tool interface open, so that we can start tracking the IDs
Yes, much better.
BTW, I just noticed that the naming convention of the registered MacroScripts is not always “correct” according to the documentation (Category + “-” + MacroScript name).
For example, if the Category Name starts with “–”, the registered Category is correct in the Max UI, but the registered filename will start with “__”.
I don’t know what other characters could be replaced.
.
Perhaps you could record the last ID with a script in the startup folder and query it anytime you need?
Apparently, any none alphanumeric or space characters in the Category name are converted to underscore character for the filename.
There is a SDK method in the MacroDir class to validate this name MakeCategoryValid().
If you happen to be on Max 2014+ you could use something like this to make a valid filename for the registered user MacroScript:
(
categoryName = "-- My Category"
gi = (dotnetclass "Autodesk.Max.GlobalInterface").instance
gi.MacroScriptDir.MakeCategoryValid categoryName
)
I am afraid I’ll need your help here. I am just the designer 
Please see the attached file. I placed a list-by-age-option and three buttons that I need help with.
MacroscriptLister.mcr (6.1 KB)
-
List macroscripts by age. I did rethink this. No need to memorize the last ID before adding new macroscripts. We could simply list a given amount in backwards order.
Once you press the checkbox, the upper dropdownlist gets greyed out and made non-clickable. The lower will now list the last created scripts. The spinner becomes activated and lets you define the amount of last created scripts to show. You can put the default to a number of your liking. -
Unload Macroscripts: @Serejah had a solution here but I couldn’t rewrite the code into a button.
-
Rename MCR. If you don’t agree with the rename-rollout I made, don’t hesitate to make it better.
-
Before physically deleting a file, we need a confirmation popup like “Do you really want to delete the file?”
Thank you in advance!!! 
I took a look at your script but I think I didn’t completely understand the design of it. Is not that much the code, but the implementation.
So in an attempt to help with your request, I decided to take a completely different path and so I created a little utility based the things I can remember from the whole Thread.
I fully understand if this is not at all what you need, but I think there is a lot of code in the tool to help you implement your own solution.
The only thing I didn’t implement is the “Renaming” feature.
Renaming: I don’t understand what the purpose of it is, as renaming the filename of a registered MacroScript has no effect anywhere.
Hope you find it useful.
EDIT 1
Fixed a few minor things
Added default cell style
Added load individual MacroScript
EDIT 2
Some code reorganization
Some visual improvements
EDIT 3
Fixed bug in Delete MacroScript
EDIT 4
Added limit of shown MacroScript
/**********************************************************************************************
DESCRIPTION: Small Utility for Loading/Unloading/Deleting Users MacroScripts
AUTHOR: Jorge Rodríguez - www.polytools3d.com
DATE: 03.04.2019
NOTES:
For 3ds Max 2014+
Not fully implemented. It might containg bugs. Not optimized.
USE AT YOUR OWN RISK!
CGSociety Thread:
https://forums.cgsociety.org/t/run-an-mcr-file-but-ignore-its-macroscrip-header/1810600
/**********************************************************************************************/
(
try destroydialog ::RO_USER_MACROS_MANAGER catch()
rollout RO_USER_MACROS_MANAGER "User MacroScripts Manager" width:438 height:484
(
dotNetControl dnc_grid "DataGridView" pos:[ 8, 8] width:420 height:384
spinner sp_rowsCount "Limit:" pos:[ 8,405] fieldwidth:48 range:[1,1,1] type:#integer
button bt_load "Load" pos:[114,400] width:104 height:24
button bt_run "Run" pos:[220,400] width:104 height:24
button bt_edit "Edit" pos:[326,400] width:104 height:24
button bt_unload "Unload" pos:[ 8,440] width:104 height:32
button bt_rename "Rename" pos:[114,440] width:104 height:32
button bt_delete "Delete" pos:[220,440] width:104 height:32
button bt_reload "Reload All" pos:[326,440] width:104 height:32
local macroEntryClass
local userMacrosPath = getDir #userMacros
local macroEntries = #()
local gi = (dotnetclass "Autodesk.Max.GlobalInterface").instance
local actionTable = gi.coreinterface.ActionManager.GetTable 82
local headerCellStyle, disabledCellStyle, enabledCellStyle
local rowsCountLimit = 20
struct macroEntryClass
(
id = 0,
name = "",
category = "",
internalCategory = "",
fileName = "",
toolTip = "",
buttonText = "",
buttonIconFile = "",
buttonIconIndex = 0,
userFilename = "",
loaded = true
)
fn GetMacroScriptsInfo =
(
macroScripts = getfiles (userMacrosPath + "\\*.mcr")
macroEntries = for j in macroScripts collect
(
entry = gi.MacroScriptDir.LoadMacroScript j
macroEntryClass id:entry.Id \
name:entry.Name \
category:entry.Category \
internalCategory:entry.InternalCategory \
fileName:entry.FileName \
toolTip:entry.ToolTip \
buttonText:entry.ButtonText \
buttonIconFile:entry.ButtonIconFile \
buttonIconIndex:entry.ButtonIconIndex \
userFilename:j
)
fn sortByID e1 e2 order:1 =
(
if e1.id < e2.id then -order
else if e1.id > e2.id then order
else 0
)
qsort macroEntries sortByID order:1
)
fn DisableRow row:0 =
(
for j = 0 to 2 do dnc_grid.Rows.Item[row].Cells.Item[j].Style = disabledCellStyle
)
fn EnableRow row:0 =
(
for j = 0 to 2 do dnc_grid.Rows.Item[row].Cells.Item[j].Style = enabledCellStyle
)
fn UpdateGridContent =
(
if macroEntries.count > 0 do
(
dnc_grid.RowCount = rowsCountLimit
total = macroEntries.count
for j = 1 to rowsCountLimit do
(
rowIdx = j-1
entryIdx = total-rowsCountLimit+j
dnc_grid.Rows.Item[rowIdx].Selected = false
dnc_grid.Rows.Item[rowIdx].Cells.Item[0].Value = macroEntries[entryIdx].id
dnc_grid.Rows.Item[rowIdx].Cells.Item[1].Value = macroEntries[entryIdx].category
dnc_grid.Rows.Item[rowIdx].Cells.Item[2].Value = macroEntries[entryIdx].name
if macroEntries[entryIdx].loaded then EnableRow row:rowIdx else DisableRow row:rowIdx
)
)
)
fn SetupGrid =
(
(dotnetclass "system.gc").collect()
gc light:true
dnc_grid.EnableHeadersVisualStyles = false
dnc_grid.ColumnHeadersDefaultCellStyle = headerCellStyle
dnc_grid.RowsDefaultCellStyle = enabledCellStyle
dnc_grid.ColumnHeadersBorderStyle = dnc_grid.ColumnHeadersBorderStyle.Single
dnc_grid.SelectionMode = dnc_grid.SelectionMode.FullRowSelect
dnc_grid.ColumnHeadersHeightSizeMode = dnc_grid.ColumnHeadersHeightSizeMode.DisableResizing
dnc_grid.AllowUserToResizeColumns = false
dnc_grid.AllowUserToResizeRows = false
dnc_grid.AllowUserToAddRows = false
dnc_grid.ReadOnly = true
dnc_grid.ColumnHeadersHeight = 30
dnc_grid.Rows.Clear()
dnc_grid.ColumnCount = 3
dnc_grid.RowHeadersVisible = false
dnc_grid.TabStop = false
titles = #( #(80, "Macro ID"), #(160, "Macro Category"), #(160, "Macro Name"))
for j = 1 to titles.count do
(
dnc_grid.Columns.Item[j-1].Width = titles[j][1]
dnc_grid.Columns.Item[j-1].Name = titles[j][2]
)
dnc_grid.Columns.Item[2].AutoSizeMode = dnc_grid.Columns.Item[2].AutoSizeMode.Fill
)
fn DefineCellStyles =
(
headerCellStyle = dotnetobject "System.Windows.Forms.DataGridViewCellStyle"
headerCellStyle.BackColor = (dotnetclass "System.Drawing.Color").FromArgb 48 48 48
headerCellStyle.ForeColor = (dotnetclass "System.Drawing.Color").FromArgb 220 220 220
enabledCellStyle = dotnetobject "System.Windows.Forms.DataGridViewCellStyle"
enabledCellStyle.BackColor = (dotnetclass "System.Drawing.Color").FromArgb 250 250 250
enabledCellStyle.ForeColor = (dotnetclass "System.Drawing.Color").FromArgb 32 32 32
enabledCellStyle.SelectionBackColor = (dotnetclass "System.Drawing.Color").FromArgb 50 150 250
enabledCellStyle.SelectionForeColor = (dotnetclass "System.Drawing.Color").FromArgb 250 250 250
disabledCellStyle = dotnetobject "System.Windows.Forms.DataGridViewCellStyle"
disabledCellStyle.BackColor = (dotnetclass "System.Drawing.Color").FromArgb 200 200 200
disabledCellStyle.ForeColor = (dotnetclass "System.Drawing.Color").FromArgb 150 150 150
disabledCellStyle.SelectionBackColor = (dotnetclass "System.Drawing.Color").FromArgb 150 0 0
disabledCellStyle.SelectionForeColor = (dotnetclass "System.Drawing.Color").FromArgb 200 150 150
)
fn ReloadAllMacroScripts =
(
setwaitcursor()
GetMacroScriptsInfo()
UpdateGridContent()
rowsCountLimit = amin rowsCountLimit macroEntries.count
sp_rowsCount.range = [1,macroEntries.count,rowsCountLimit]
setarrowcursor()
)
fn GetSelectedMacrosIDs =
(
selRows = for j = 1 to dnc_grid.SelectedRows.Count collect dnc_grid.SelectedRows.Item[j-1].Index
result = for j in selRows collect #(dnc_grid.Rows.Item[j].Cells.Item[0].Value, j)
return result
)
fn LoadMacro id: =
(
if id != unsupplied do
(
if (gi.MacroScriptDir.ValidID id) then
(
for k in macroEntries where k.id == id do
(
if k.loaded == false then
(
gi.MacroScriptDir.LoadMacroScript k.userFilename
k.loaded = true
return true
)else messagebox ("MacroScript " + (id as string) + " is already Loaded")
)
)else format "Can't Load MacroScript | Invalid ID:%\n" id
)
return false
)
fn UnloadMacro id: =
(
/* @Serejah
https://forums.cgsociety.org/t/unload-macroscript-from-memory/2050211/3
action_table = gi.coreinterface.ActionManager.GetTable 82 -- Macro Scripts
your_action = action_table.GetAction 12345 -- your macro index
action_table.DeleteOperation your_action
*/
if id != unsupplied do
(
if (gi.MacroScriptDir.ValidID id) then
(
for k in macroEntries where k.id == id do
(
if k.loaded == false then
(
messagebox ("MacroScript " + (id as string) + " is already Unloaded")
)else(
actionTable.DeleteOperation (actionTable.GetAction id)
k.loaded = false
return true
)
)
)else format "Can't Unload MacroScript | Invalid ID:%\n" id
)
return false
)
fn DeleteMacroScriptFile id: =
(
if id != unsupplied do
(
if (gi.MacroScriptDir.ValidID id) then
(
for k = macroEntries.count to 1 by -1 where macroEntries[k].id == id do
(
if macroEntries[k].loaded == true do UnloadMacro id:id
if macroEntries[k].userFilename != "" then
(
deletefile macroEntries[k].userFilename
deleteitem macroEntries k
)else format "Could not locate file for MacroScript:%\n" id
)
)else format "Can't Delete MacroScript File | Invalid ID:%\n" id
)
return false
)
fn LoadSelectedMacroScripts =
(
ids = GetSelectedMacrosIDs()
if ids.count == 0 do return messagebox "Please select a Macro"
answer = querybox "You are attempting to LOAD one or more MacroScripts.\n\nDo you want to continue?"
if answer do for j in ids do if (LoadMacro id:j[1]) do EnableRow row:j[2]
)
fn RunSelectedMacroScripts =
(
ids = GetSelectedMacrosIDs()
if ids.count == 0 do return messagebox "Please select a Macro"
if ids.count > 1 then
(
answer = querybox "You are attempting to RUN more than 1 MacroScript.\n\nDo you want to continue?"
if answer do for j in ids do macros.run j[1]
)else macros.run ids[1][1]
)
fn EditSelectedMacroScripts =
(
ids = GetSelectedMacrosIDs()
if ids.count == 0 do return messagebox "Please select a Macro"
if ids.count > 1 then
(
answer = querybox "You are attempting to EDIT more than 1 MacroScript.\n\nDo you want to continue?"
if answer do for j in ids do macros.edit j[1]
)else macros.edit ids[1][1]
)
fn UnloadSelectedMacroScripts =
(
ids = GetSelectedMacrosIDs()
if ids.count == 0 do return messagebox "Please select a Macro"
answer = querybox "You are attempting to UNLOAD one or more MacroScripts.\n\nDo you want to continue?"
if answer do for j in ids do if (UnloadMacro id:j[1]) do DisableRow row:j[2]
)
fn RenameSelectedMacroScript =
(
messagebox "Not implemented."
)
fn DeleteSelectedMacroScripts =
(
ids = GetSelectedMacrosIDs()
if ids.count == 0 do return messagebox "Please select a Macro"
msg = "You are attempting to DELETE one or more MacroScript files.\n"
msg += "This procedure will also UNLOAD the MacroScripts from 3ds Max\n\n"
msg += "Do you want to continue?"
answer = querybox msg
if answer do
(
for j in ids do DeleteMacroScriptFile id:j[1]
UpdateGridContent()
)
)
on RO_USER_MACROS_MANAGER open do
(
DefineCellStyles()
SetupGrid()
ReloadAllMacroScripts()
)
on sp_rowsCount changed arg do
(
rowsCountLimit = arg
UpdateGridContent()
)
on bt_load pressed do LoadSelectedMacroScripts()
on bt_run pressed do RunSelectedMacroScripts()
on bt_edit pressed do EditSelectedMacroScripts()
on bt_unload pressed do UnloadSelectedMacroScripts()
on bt_rename pressed do RenameSelectedMacroScript()
on bt_delete pressed do DeleteSelectedMacroScripts()
on bt_reload pressed do ReloadAllMacroScripts()
)
createdialog RO_USER_MACROS_MANAGER style:#(#style_toolwindow, #style_sysmenu)
)
@PolyTools3D Nice sample.
But may i ask how to list all of the macros by category?
just like what output from “macros.list()”
or Build-in Customize User Interface > Toolbars > Categroy > All Commands.
just like what they were talking about
I don’t know about the thread you mention as I was focused on this thread. But if you want to sort by Category just click on the header. You can sort by ID, Category and Name.
