Notifications
Clear all

[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?

1 Reply
(@polytools3d)
Joined: 10 months ago

Posts: 0

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.

  1. We keep track of that ID
  2. We drop the files onto max (I think we dont even need a “drop your files here”-GUI anymore)
  3. We check what the last ID is now.
  4. 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

3 Replies
(@polytools3d)
Joined: 10 months ago

Posts: 0

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?

(@polytools3d)
Joined: 10 months ago

Posts: 0

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
)
(@haider_of_sweden)
Joined: 10 months ago

Posts: 0

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)

  1. 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.

  2. Unload Macroscripts: @Serejah had a solution here but I couldn’t rewrite the code into a button.

  3. Rename MCR. If you don’t agree with the rename-rollout I made, don’t hesitate to make it better.

  4. 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
)

Page 3 / 3