[Closed] usermacro overwrites
I’ve been cleaning up my script directories in preparation for a new project, and am stuck trying to get everything working again. This is Max 2009. Example:
Last project, I was getting tired of having to explicitly re-evaluate scripts after restarting Max, so decided I’d weed out the old stuff, get everything together and make sure it’s evaluated on startup. Following some (dodgy?) advice on another forum, I threw the keepers into a directory outside Max’s domain, c:_mxs, and put a fileIn into my startup.ms to load everything up. That didn’t work. So I do the fileIn twice in the startup.ms, one after the other, and now it’s fine. Oookay.
Since that doesn’t seem right, I’m not surprised that I have a bigger problem brewing. I made some changes to a macro, saved, evaluated, tested, fine. Restarted Max, found that my changes had not survived the restart. Everything looked fine in the editor. I closed the editor, right clicked on the button to bring the macro back up, and here’s a fresh new usermacro, based on the original in c:_mxs, not including the updates made last session.
I’ve never had this problem before and assume it’s something to do with the new setup. Before, I guess I worked straight off the usermacros generated for the editor. Never really thought about it. The only thing that bothered me was, despite working under the default paths, I had to explicitly re-evaluate things as needed between restarts.
Any idea what’s gone wrong, or what I could do to fix it? Thanks!
–John.
There are big differences between scripts and scripts. It really depends on how you structure them and what is in the file, so it is difficult to generalize, but I will try.
As you might know (because it is documented under "Startup Scripts"), there are several stages of auto-starting scripts when Max boots up, and depending on what portions of Max are ready at any point, one can decide where to put scripts that need to be evaluated. Some runes of thumb:
.MS files that contain global definitions of functions and structs containing functions that do not depend on the UI in any way but provide general functionality to other scripts (let's call them "library scripts"). These are best placed in the \StdPlugs\StdScripts or any subfolder of that path you might wish to create. These are evaluated early on and will be visible to any other scripts that might get loaded later.
.MS files containing scripted plugin definitions, functions and even MacroScript defintions, floaters, rollout and dialog definitions and so on. These can be placed in ANY plugin folder defined in the Plugin.ini or any subfolder thereof. (MAXScript scans folders recursively, so any script deeper in the structure of a plugin.ini path will be loaded). When these are executed, Max has already performed a pass over all plugin paths to load DLLs, but the viewports and the Max scene do not exist yet.
.MCR MacroScripts are loaded from the system's ui\macroscripts (these are the ones shipping with Max) and from ui\usermacros (these can override the shipping ones and provide all Macro definitions that might have been evaluated during any previous sessions). You can copy an .MCR file into ui\usermacros, or let Max manage the content (which is a better idea). Keep in mind that if an .MS file was placed in the plugin folder (see above) and contains anything like 'macroScript someName category:"SomeCategory" ( )' in its body, Max will evaluate it on startup and cause a replacement of the .MCR file in the ui\usermacros folder, so you will end up using what was defined in the .MS file. Thus, modifying an .MCR file found in ui\usermacros while having an .MS file with the actual MacroDefinition loaded earlier in the statup process is a bad idea (see code further below). Also note that only the MacroScript() portion of the script will be copied to the usermacros folder, if the source file contains other code outside of the Macro's body, that won't be copied over.
Finally, you can load a script containing ANY code via fileIn() from the Scripts\Startup\statup.ms (if you want to enforce a certain loading order), or just drop your .MS file into the Scripts\Startup folder to be loaded in an arbitrary order. If the .MS contains any MacroScript defintions, these will create (and potentially override) .MCR files in \usermacros storing these MacroScripts. Thus, once again if you want to change the behavior of a Macro that is being loaded via Startup or a Plugin folder, you should edit the source there, not the .MCR in \usermacros.
There is also the case where you write a MacroScript in a source file that is not included in any startup folder. This is why the \usermacros management was implemented in the first place. Say you wrote an .MS file that contains ONLY a MacroScript definition (or many MacroScripts but nothing else). If that script is loaded or run in a Max session, each Macro will generate a corresponding .MCR using the Name and the Category of the Macro as the file name and will store it in the \usermacros. If you would close and open Max, all your Macros should work correctly (assuming they don't depend on other code that is not inside the Macro's body) without needing to rerun anything.
A mistake people might make would be to define MacroScripts and include code outside of the Macro, then evaluate and hope that both the Macro and the outside code would survive a restart.
For example
(
global someFunction --defines a global variable
fn someFunction txt = format "%
" txt --stores a global function
MacroScript PrintTime category:"Test"
(
someFunction localtime --calls the function
)
)--end script
If you run this and customize a toolbar with the PrintTime button, pressing the button during the current session will print the date and time.
But if you would restart Max, pressing that same button would cause an error (someFunction is undefined) because only the MacroScript() portion was saved in the usermacros to survive a restart, but the someFunction definition above it was not.
-- Error occurred in anonymous codeblock; filename: C:\Program Files\Autodesk\3ds Max 2008\UI\usermacros\Test-PrintTime.mcr; position: 86
-- Frame:
-- someFunction: undefined
>> MAXScript MacroScript Error Exception: -- Type error: Call needs function or class, got: undefined <<
The correct solution is to either:
*Copy the function into the Macro definition (and possibly keep it local to the Macro),
*Define the function in a separate autoload script in \stdplugs\stdscripts or a plugin folder (NOT \scripts\startup because that would get evaluated AFTER the Macros and the Macro would not "see" the definition when evaluating)
*Save the whole script above in any of the startup folders - \stdplugs\stdscripts, plugins folder or \scripts\startup folder to cause both the definition of the function and the definition of the Macro to be reloaded on each Max start.
Without seeing your .MS and .MCR files, I cannot answer why you are having issues with startup, but the usual problems are scope of variables and order of evaluation - if your script is not structured properly and loaded at the right time, some portions of it might not "see" some functions or variables.
I’m a big fan of the fileIn “” technique, where the MacroScript definitions have only a filein to a .ms file that I can then locate in an organized folder structure…
So in “ui\macroscripts” I would have an .mcr file with lines like this. In this Instance the .mcr filename is “\ui\macroscripts\Focus360-Animation.mcr”
macroScript DeleteKeysAtZero category:"Focus360 Animation" toolTip:"Delete Keys At Time Zero" buttonText:"Delete Keys At Time Zero" icon:#("Focus360-Animation",24)
(
filein (( getdir #scripts)+"Focus360_Scripts\\Animation\\Delete_Keys_At_Time_Zero.ms" )
)
By the filepath, you can see I have a sub folder “Animation” in a “Focus360_Scripts” folder in the standard “scripts” folder.
So each sub folder has it’s own .mcr file and it’s own .bmp files for icons.
When I add a script, I add the .ms to the appropriate sub folder, then go to same .mcr and add a filein macroScript definition for the script and an Icon to the .bmp
Q for Bobo:
I’ve seen the same setup with “include” instead of “filein.” I’m under the impression that using include in the .mcr will load all the scripts fully into memory, where filein will only load them at execution… Is that correct?
The Include is a compile-time construct which replaces itself with the actual code in the given file. So when the .MCR gets loaded, the whole content of the included file ends up inside the Macro and will run in the local scope of the Macro. In the case of include, you cannot construct a path from variables the way you did with the fileIn. In short, include() is like a programmatic Ctrl+V (Paste) for code from a file which is executed when the file containing the include statement is evaluated.
The filein() is the MAXScript equivalent to the menu command MAXScript>Run Script or the Ctrl+E in the Editor – it grabs the file and evaluates it. So in your example, the file will only be loaded if the user presses the Macro’s button, but will NOT be loaded when Max starts and evaluates the .MCR file. This also means that if you open the file specified in filein, make some changes, save it back to disk and hit the button of the Macro, the NEW edited code will be reloaded. The code is evaluated in global scope and will not become part of the Macro’s local scope as with include.