[Closed] Run an MCR file but ignore its macroscrip header?
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?
I had the feeling something was wrong as well as me lazy.
I think it’s fixed now.
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.
Recently i am trying to write a highly customized tool shelf, as i mentioned before ->here
One of the features is, user can add a macroscript to the shelf panel or hotbox as a customize style button.
Thanks to you guys discussion, I find the way.
I did not copy what @PolyTools3D has implemented. No needs in my case.
here is my version
/************************************************************************************************
-- MacroScripts_Lister.ms
-- Author: Zhimou Yuan (FusioN) / fusionyuan@gmail.com) / QQ: 18048972
-- Description:
-- listing all of the MacroScripts for user to choose which one add to the tool shelf
-- Last Updated: 2019-03-14
************************************************************************************************/
(
try DestroyDialog ::YZM_MACROS_LISTER catch()
rollout rltMain "MacroScripts Lister - YZM" width:300 height:520
(
label cat_lab "Category:" pos:[10, 10] width:60
dropdownlist cat_dd "" pos:[70, 10] width:220
dotNetControl dnc_lv "ListView" pos:[10, 40] width:280 height:385
button btn_edit "Edit" pos:[10, 435] width:138 height:32
button btn_run "Run" pos:[152, 435] width:138 height:32
button btn_add "" pos:[10, 472] width:280 height:32 visible:false
local macroList, catList
-- https://forums.cgsociety.org/t/macroscripts-category-names-lists/1406006/2
fn getMacros =
(
local ss = StringStream ""
macros.list to:ss
seek ss 0
local macroList = #()
while not eof ss do
(
id = (readDelimitedString ss " ") as integer
info = filterstring (readLine ss) "\""
-- #(ID, name, category, internal_category, macro_source_filename)
append macroList #(id, info[1], info[3], info[5], info[7])
)
macroList
)
fn getCategorys macroList allCmds:false =
(
local catList = #()
for m in macroList do appendIfUnique catList m[3]
sort catList
if allCmds do insertItem "All Commands" catList 1
catList
)
fn getMacrosByCategory macroList cat =
(
if cat != "All Commands" then
for m in macroList where m[3] == cat collect m
else
macroList
)
fn feedDropDownItems dd list =
(
dd.items = list
dd.selection = 1
dd.height = 393
)
fn setupList lv =
(
lv.backcolor = lv.backcolor.gray
lv.forecolor = lv.forecolor.ghostwhite
lv.view = lv.view.details
lv.FullRowSelect = true
lv.GridLines = false
lv.MultiSelect = false
-- lv.checkboxes = true
lv.fullrowselect = true
-- lv.headerstyle = lv.headerstyle.none
lv.hideselection = false
lv.columns.add "Macro ID" 60
lv.columns.add "Macro Category" 0
lv.columns.add "Macro Name" (lv.width-81)
)
fn feedList lv macroList cat =
(
lv.items.Clear()
local mlist = getMacrosByCategory macroList cat
local mNames = sort ( for mn in mlist collect mn[2] )
-- sort by item's macro name, a foolish way
local mlist_sortByName = #()
for mN in mNames do (
for m in mlist where m[2] == mN do append mlist_sortByName m
)
for m in mlist_sortByName do ListViewOps.AddLvItem lv pTextItems:#( (m[1] as string), m[3], m[2] )
)
fn getSelectedId =
(
local id
if dnc_lv.SelectedItems.count != 0 then
id = dnc_lv.SelectedItems.item[0].text as integer
else
id = 0
id
)
fn editSelectedMacro id =
(
if id !=0 then
try macros.edit id catch()
else
messageBox "whoops!"
)
fn runSelectedMacro id =
(
if id !=0 then
try macros.run id catch()
else
messageBox "whoops!"
)
on rltMain open do
(
macroList = getMacros()
local catList = getCategorys macroList allCmds:true
setupList dnc_lv
feedDropDownItems cat_dd catList
-- feedList dnc_lv macroList "All Commands"
cat_dd.selection = findItem cat_dd.items "Modifiers"
feedList dnc_lv macroList "Modifiers"
)
on cat_dd selected arg do
(
feedList dnc_lv macroList cat_dd.selected
)
-- find the first item which macro name start from the key
-- If the target is not displayed, the build-in seems that can't jump there?
-- the build-in seen like
-- on dnc_lv keyUp s e do
-- (
-- local key = e.KeyCode.ToString()
-- )
on dnc_lv MouseUp s e do
(
local theItem = dnc_lv.GetItemAt e.x e.y
local id = theItem.SubItems.item[0].text
local mName = theItem.SubItems.item[2].text
btn_edit.text = "Edit: " + id
btn_run.text = "Run: " + id
btn_add.visible = true
btn_add.text = "Add [ " + mName + " ] To Shelf"
)
on btn_edit pressed do editSelectedMacro (getSelectedId())
on btn_run pressed do runSelectedMacro (getSelectedId())
)
global YZM_MACROS_LISTER = rltMain
CreateDialog YZM_MACROS_LISTER
)