[Closed] Tips of working on a large MaxScript projects
Hi,
I have a few years of experience with Max Script, but so far these have been mainly one file scripts. But as I have started coding some more advanced toolset recently, I need to get this project organized in a neat and functional way. So far I split my project into several .ms files and placed them all in the scripts/startup folder. While it works, I am finding this a clumsy way. There’s one script that calls all the other files in certain order, but since all of the files are in the startup folder, I have a feeling that they get evaluated twice! (though no error appears, which would normally be thrown if the random evaluation order did not follow the expected one (variables not declared etc…)
So here is my question – what are the best practices for having a scripted toolset that have the following features:
- is split into a large number of .ms and .mcr files, possibly in some folder structure. Where would you keep the files?
- The toolset is supposed to create some menus (what’s better – hardcoding menus, reading xml or a folder structure translated into menu system?)
- It will involve some external data files too and some images, icons etc…
- have development copy and the production copy. (the one which will be released for public). How do I switch between them?
- will work with some VCS. Which one is better for a solo max script developer – SVN or git?
- possibly have a simple installer, where user will choose where to save the tool files.
So, basically – if you have any tips on organizing large Max Script projects keeping the above in mind, I would be grateful!
I don’t know that I have any answers to your questions that are “the best” way. The very best advice I have is simply do some good planning at the start. I have a very large MAXScript project that has been being developed for half a decade now. I started it when I still didn’t have any MAXScript experience. After several years I wish I could take a time machine and teach my former self a bunch of tips and tricks that would have saved me headaches in the long run.
Consolidated related functions as methods of generic structs instead of having a ton of global functions is one thing I would recommend.
To alleviate file permission problems (copying MCR to Max’s macroscripts) it’s probably best to just have your installer/updater run your MCR file(s) and let them get put into Userscripts. File permissions is a headache, especially if you start including DLL and EXE files in your project–as you have to worry about Antivirus programs creating false positives and Windows UAC blocking the DLL files if distributed via the internet. As such, make sure to test your installation method on versions of Windows with UAC on to make sure that you find the smoothest way to get the application running.
I personally wouldn’t put all your scripts into the startup folder. If anything, have a single startup script that runs the scripts you need in the order you need. It could even just run your Initialization macroscript, if you make one. The actual scripts could be in a consolidated location (either $scripts or $userscripts or even some other location you know based on INI file in $userscripts that can be set during your installer or edited manually if need be).
For making a development VS release version, you probably have several options. I presume the best scenario depends on how your files are managed. What is generally the difference between your development/release versions? I presume one will include encrypted files? We have tools to encrypt and package release versions that we then test on other installations of Max.
In terms of Git VS SVN. Both are fine. I’ve personally moved to Git, but still use SVN when required.
Menus: I have no advice. I just create the manually in our tools, but we also have no internationalization.
Just some opinions. Certainly not gospel.
Some good tips
http://forums.cgsociety.org/showpost.php?p=8088261&postcount=11
you can read the full post from here:
http://forums.cgsociety.org/showthread.php?f=98&t=1306951&page=1&pp=15
I would suggest you to place needed scripts at autorun in (getdir #userStartupScripts)
If main Script A.ms from autorun uses A_01.ms, …A_0n.ms, then you can call them from Script A
filein A_01.ms, which can be placed in (getdir #userScripts).
This way you will not have all the scripts in startup.
#userIcons – for Icons
Thanks wallworm and try2script – some good info and suggestions there.
I think I’ll limit my startup script to literally just one line, launching the main script sitting outside the startup folder. Then I can just change the path pointing to development or released versions.
Consolidated related functions as methods of generic structs instead of having a ton of global functions is one thing I would recommend.
Yeah, I learnt that the hard way. I have refactored my code many times. When I started these tools – almost three years ago, I didn’t know what the object oriented programming was. While MXS is not an OOP language, we can still use structs as sort of limited classes, combining variables and functions into one global name. Makes the code much cleaner.
What is generally the difference between your development/release versions?
Well, development version contains WIP code, which can be buggy or just not working at all at certain points. Released version will always be stable and represent code frozen at some stage. Then if somebody reports a bug, I can switch to the released version, in case I have pushed my code farther in the meantime, so I can investigate the issue.
Also not all the files may actually need to go to the release version. I found myself writing some auxiliary scripts for debugging or analyzing purposes – they shouldn’t clutter the release pack.
Not sure how I will separate these versions and update them, but probably some script that copies files specified in some xml or something like that.
Still, I have to figure out the folder structure and decide how to split code into smaller files.
Also – is combining MXS and Python generally a good idea? Or would it be better to re-write everything in Python? I’m leaning towards Python recently, since at work I need it for scripting in Maya.
- Categorize your functions and variables in separate structures (separate files) and choose a short namespace for them, for example:
Global variables –> GlbV.MyVariableA
Global functions – > GlbF.FunctionA()
Global Paths –> GlbP.OutputFolder
DotNet functions –> GlbDN.Button()
Python functions –> GlbPy.Button()
…
- Override every function you use a lot, I recreated all primitive objects with my predefined parameters. for example:
fn MyBox Length:1 width:1 height:1 WireColor:(color 174 186 203) Name:(uniqueName "MyBox") Pos:[0,0,0] BoxMode:false BoneMode:true =
(
Bx = box length:Length width:width height:height Pos:Pos Name:Name
Bx.boxmode = boxmode
Bx.WireColor = WireColor
Bx.renderable = false
Bx.boneEnable = BoneMode
Bx
)
This helps you to modify box function in all your scripts every time you want just by modifing this function.
- Separate every tool or rollout in seperate folder and name them with number. for example:
AllScripts/ToolsA/ToolsA001.ms
AllScripts/ToolsA/ToolsA002.ms
AllScripts/ToolsA/ToolsA003.ms
Then use a function to find last file by name and include it in your main script. Note that use include not filein, because filein will execute your script in global scope and this not what you really need.
All that suggestions are really fine but… we are talking about long scripts, with probably hundreds and hundreds of working hours. With lots of small files, how do you protect them to avoid that anyone takes your work home or to another company?
@MZ – thanks for your suggestions! Sounds useful.
However I’m not sure about the last one – the file numbering. Is this some sort of a backup system? Would it not be better to just use some SVN or GIT and have the code stored in a cloud? It’s so easy to delete entire folders… (happened to me)
@aaandres – you can always encrypt your .ms files, though the encryption can be encoded relatively easily, as far as I heard. MXS cannot guarantee full protection.
Another idea is munging your code before release, e.g. replacing files, functions and variables names with some meaningless string sequences, removing all formatting etc… As pretty much no code can be re-used as it is, if somebody has to deal with such a chaos on the screen he would quickly lose motivation to figure out what is what.
The problem for me is not the use of my code by other programmers. We are nice people!
The problem is the use of the script by other companies totally free and profiting your knowledge, time invested, and money.
Subversion and backup is other story, I used this old school way of subversion because is easy to use and I can include them in my file without any coding for SVN. But SVN become very important when you want to work as team on single file.
If you want to sell your scripts, you need to create registration system and this not safe with maxscript. The only way is c++.
I don’t want to sell my script (at least till today). I just want other companies not to use them.
Unprotected scripts can be easily taken outside the company and be used by others.
You can make your own encryption also by using c++. But ask this question from yourself: who want to use my scripts? If someone is able to read your code, it means he or she is able to write it himself. So if you don’t want to make your scripts commercial, don’t waste your time on encryption.
my first advice is – don’t use “include” method. if you use ‘include’ your project is organized wrong.
if you don’t want to have some ‘.ms’ file be executed but want to keep them in an auto-executable directory (like ‘startup’) just change these files extension to ‘.mss’ for example
I have some usage example but let say we want to create namespaces by using “Struct”. This is my example without include:
global MainStruct
(
MainStruct =
(
struct MainStruct
(
SubStruct =
(
struct SubStruct
(
Var = 1
)
)()
)
)()
)
-- Usage:
MainStruct.SubStruct.Var
And If I want to split SubStructs in separate files, I use include to bring them back in the main struct. This is the SubStruct file:
(
SubStruct =
(
struct SubStruct
(
Var = 1
)
)()
)
And main file:
global MainStruct
(
MainStruct =
(
struct MainStruct
(
SubStruct = include @"C:\Test\SubStruct.ms"
)
)()
)
-- Usage:
MainStruct.SubStruct.Var