Overview
Maya environment setup for you and your team. Deliver your tools and integrate scripts easily. Automate the setup in a team setting and add callbacks to manage scene preferences and source control.
What is the Maya environment?
Maya has specific environment variables that defines where it looks for scripts and resources.
Add your own paths to these variables so that we can store our scripts and resources wherever we want.
Maya environment scripts
The most common way to modify the Maya environment is through three script files in the preference directory, namely Maya.env, userSetup.mel and userSetup.py. Maya evaluates these scripts, in the order mentioned, during boot and thus presents an opportunity to inject paths for our tool environment.
“What if I want to organize my add ons so I can easily turn things on and off? ”
“As a tech artist, I don’t want to deal with pre existing configuration information when I modify the end user’s Maya environment.”
Luckily, there is an alternative to the three files mentioned above, namely Maya modules.
Maya modules
A Maya module is a .mod file that we create to modify the Maya environment. First, look in Maya’s install directory/modules .
– Windows example path: “C:\Program Files\Autodesk\Maya2018\modules”.
Fbx, Substance, Xgen and Houdini Engine all use this method to bootstrap their environment in Maya.
We will create our own .mod file in a modules directory in the Maya preferences directory.
The sub folder names “icons”, “shelves” and “scripts” are automatically added to the Maya environment during startup.

Maya Module Site
Manually implementing a module
Put the directory in the root, as opposed to into a Maya version folder, which causes all modules to be loaded for all versions of Maya.
If you need specific version control, you can also create a modules directory inside of any given Maya version preference folder.

Maya Preferences
Next, create a .mod file inside the modules folder you just created (I name mine LCG.mod). Put the content below in the .mod file, and change the paths how you see fit.
1 2 3 4 5 |
+ LCG 1.0 C:/Code/LCG/maya PYTHONPATH += C:/Code/LCG/python PYTHONPATH += C:/Code/LCG/python/lcg/lib MAYA_PLUG_IN_PATH += C:/Code/LCG/python/lcg/maya/plugin MAYA_SHELF_PATH += C:/Code/LCG/maya/shelves |
The first line tells Maya that we want to add (+) this module. We give it a name and a version as well as the path to our “Maya site”. Maya adds the “icons” and “scripts” folders automatically during boot. To get shelves to auto load in Maya, we add the path to the MAYA_SHELF_PATH environment variable.
Verifying the Maya environment
Start up Maya, copy and paste the code below into the script editor and run it to verify that the paths have been added to your environment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import pymel.util print '-------------Start Maya Script Paths----------------------' for path in pymel.util.getEnv("MAYA_SCRIPT_PATH").split(';'): print 'Script path - %s' % path print '-------------End Maya Script Paths----------------------\n' print '-------------Start Maya Icon Paths----------------------' for path in pymel.util.getEnv("XBMLANGPATH").split(';'): print 'Icon path - %s' % path print '-------------End Maya Icon Paths----------------------\n' print '-------------Start Maya Shelf Paths----------------------' for path in pymel.util.getEnv("MAYA_SHELF_PATH").split(';'): print 'Shelf path - %s' % path print '-------------End Maya Shelf Paths----------------------\n' print '-------------Start Maya Python Paths----------------------' for path in pymel.util.getEnv("PYTHONPATH").split(';'): print 'Python path - %s' % path print '-------------End Maya Python Paths----------------------\n' print '-------------Start Maya Plug in Paths----------------------' for path in pymel.util.getEnv("MAYA_PLUG_IN_PATH").split(';'): print 'Plugin path - %s' % path print '-------------End Maya Plug in Paths----------------------\n' import sys print '-------------Start Sys Paths----------------------' for path in sys.path: print 'Sys path - %s' % path print '-------------End Sys Paths----------------------' |
Thanks to Maya modules, we append to the Maya environment without dealing with Maya.env, userSetup.mel or userSetup.py. Feel free to create as many modules as you want. One per project/team/game/freelance job – the sky is the limit.
“I want to automate the creation of this setup for my team(s)? ”
“I want the Maya site and framework to be able to be stored in any random location for each member of the team.
Set this up is via a Maya Python plug in. It is flexible, easy to maintain and simple to implement.
Automating the module install
A Maya Python plug in is, is a .py file that we load from the Maya plug in manager. Look below how easy it is to implement.
Copy and paste the code into a .py file and load it from the Maya plug in manager, you will get the print statement when you check the load checkbox in the Maya plug in manager.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
import maya.api.OpenMaya as OpenMaya import traceback def maya_useNewAPI(): """ The presence of this function tells Maya that the plugin produces, and expects to be passed, objects created using the Maya Python API 2.0. """ pass class LCGInitialize(OpenMaya.MPxCommand): kPluginCmdName = "lcgInitialize" @staticmethod def cmdCreator(): return LCGInitialize() def __init__(self): super(LCGInitialize, self).__init__() def doIt(self, argList): raise Exception('Plugin not supposed to be invoked - only loaded or unloaded.') def initializePlugin(obj): """ :param obj: OpenMaya.MObject :return: None """ plugin = OpenMaya.MFnPlugin(obj, 'LCG United', '1.0', 'Any') try: plugin.registerCommand(LCGInitialize.kPluginCmdName, LCGInitialize.cmdCreator) load() except Exception, e: raise RuntimeError, 'Failed to register command: %s\nDetails:\n%s' % (e, traceback.format_exc()) def uninitializePlugin(obj): """ :param obj: OpenMaya.MObject :return: None """ plugin = OpenMaya.MFnPlugin(obj) try: teardown() plugin.deregisterCommand(LCGInitialize.kPluginCmdName) except Exception, e: raise RuntimeError, 'Failed to unregister command: %s\nDetails:\n%s' % (e, traceback.format_exc()) def load(): """ On initialization """ print 'Executing load function' def teardown(): """ On uninitialization """ print 'Executing teardown function' |
Introduce the modification to the Maya environment in the load function and do whatever house cleaning necessary when the plug in is unloaded in the teardown function.
I detect where the plug in loads from and set up the Maya environment from there…
Let’s dive into the load function.
The load function
The plug in will auto-load, so the following gets added or created every time Maya is started.
Below is the basic structure of our framework. If you geek out on how to structure your framework and dynamically resolve certain Python modules within your own framework, check this post out.

Let’s step through the load function. Read the comments for each section to follow along what is happening. I marked the variables that we are defining in code below on the overview image of the framework structure above.
Tip
Since some of the code lines below are fairly long, use the “Open code in new window” button on the far right to maximize while still being able to see the framework structure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
import maya.api.OpenMaya as OpenMaya import traceback import os import sys import inspect import pymel.core.language import pymel.util pythonRoot = None saveSceneJobId = -1 openSceneJobId = -1 newSceneJobId = -1 def load(): """ On initialization, this function gets called to: - Add plug in paths to the Maya environment - Add Python root path to sys.path - Create a mod file in user local that adds plug in path to this plug in to retain auto load - Create Maya before scene save callback - Create Maya after open scene callback - Create Maya new scene callback - Override incrementalSaveScene (due to AD bug) """ # Declare globals so that we can modify the variables global pythonRoot global saveSceneJobId global openSceneJobId global newSceneJobId # This is where the plug in is currently being loaded from plugPath = inspect.getfile(inspect.currentframe()) plugInRootFolderPath = os.path.abspath(os.path.join(plugPath, os.pardir)).replace('\\', '/') # Our framework's internal folder structure dictates that the python root folder path # is 4 levels up from .../lcg/maya/plugin/LCG.py which is the "plugPath" variable pythonRoot = '/'.join(plugPath.split('/')[0:-4]) # Check if the modules directory exists in the user preference directory (if it doesn't, create it) mayaModuleDirectoryPath = '%s/modules' % pymel.util.getEnv('MAYA_APP_DIR') if not os.path.exists(mayaModuleDirectoryPath): os.makedirs(mayaModuleDirectoryPath) # Define the module file path mayaModuleFile = '%s/LCG.mod' % mayaModuleDirectoryPath # Given where the plug in is loaded from this the root dir of our framework (LCG) toolRoot = '/'.join(plugPath.split('/')[0:-5]) # Write our module file with open(mayaModuleFile, 'w') as moduleFile: # String for adding the Maya site output = '+ LCG 1.0 %s/maya' % toolRoot # Add string for Python paths output += '\r\nPYTHONPATH += %s' % pythonRoot output += '\r\nPYTHONPATH += %s/lcg/lib' % pythonRoot # Dynamically add all subdirectories containing py files of the root maya plugin Python folder for directory, subDirectories, filenames in os.walk(plugInRootFolderPath): if len([filename for filename in filenames if not '__init__' in filename and filename.lower().endswith('.py')]) == 0: continue plugInFolderPath = directory.replace('\\', '/') output += '\r\nMAYA_PLUG_IN_PATH += %s' % plugInFolderPath # First time that the module is written, the path will not yet be initialized - do it here if not plugInFolderPath in pymel.util.getEnv("MAYA_PLUG_IN_PATH"): pymel.util.putEnv("MAYA_PLUG_IN_PATH", [pymel.util.getEnv("MAYA_PLUG_IN_PATH"), plugInFolderPath]) # Any shelf file in this directory will be automatically loaded on Maya startup output += '\r\nMAYA_SHELF_PATH += %s/maya/shelves' % toolRoot moduleFile.write(output) # The very first time the plug in is loaded Python path will not be initialized - do it here if not pythonRoot in sys.path: sys.path.append(pythonRoot) sys.path.append('%s/lcg/lib' % pythonRoot) # Now our framework is available to Maya. # Import modules to set up pre-save callback and scene configuration import lcg.maya.preSaveCallback import lcg.maya.sceneConfig import lcg.maya.about # Add pre save scene callback saveSceneJobId = OpenMaya.MSceneMessage.addCheckCallback(OpenMaya.MSceneMessage.kBeforeSaveCheck, lcg.maya.preSaveCallback.run, None) # Add after open scene callback (for scene configuration) openSceneJobId = OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kAfterOpen, lcg.maya.sceneConfig.run, None) # Add after new scene callback (for scene configuration) newSceneJobId = OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kAfterNew, lcg.maya.sceneConfig.run, None) # For incremental save we have to overload the global proc incrementalSaveScene because # Autodesk/Maya removes the file from file system when saving an iteration and then puts it back # We need to trigger our pre-save callback before that happens. The Overload script sits in the module folder's scripts directory (LCG/maya/scripts) # I got this behavior logged as a bug in the middle of 2017 try: # First make sure script path is added (first time plug in is loaded it will not be) scriptRootPath = '%s/maya/scripts' % toolRoot if not scriptRootPath in pymel.util.getEnv("MAYA_SCRIPT_PATH"): pymel.util.putEnv("MAYA_SCRIPT_PATH", [pymel.util.getEnv("MAYA_SCRIPT_PATH"), scriptRootPath]) pymel.core.language.Mel.source('Overload_%s' % lcg.maya.about.version, language='mel') pymel.core.language.Mel.source('Overload_%s' % lcg.maya.about.version, language='mel') except: pass |
Below is low level content that you put in lcg.maya.preSaveCallback, lcg.maya.sceneConfig, and lcg.maya.about Specifically, what you put inside of the preSave and sceneConfig functions will depend on each project.
Pre-save, I have generally only implemented Perforce checkout and add Maya file to Perforce if it is not already checked in.
sceneConfig will run any time a file is opened and new file created, so here you can implement things like setting units and preferences that should be global to the entire team…
1 2 3 4 5 |
import pymel.core.system def run(): sceneName = pymel.core.system.sceneName() print('Run any code you want to execute before maya saves the scene here (maybe checking out the Maya file from source control if it is read only)') |
1 2 3 4 |
import pymel.core.system def run(): print('Configuring Maya scene - %s' % pymel.core.system.sceneName()) |
1 2 3 4 |
import pymel.core.general # Creating this to have a hook for when AD messes up to properly update this function in future releases version = str(pymel.core.general.about(version=True)) |
The teardown function
Compared to the load function, the teardown function is pretty simple.
We get the following things removed in our teardown:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import maya.api.OpenMaya as OpenMaya import sys import pymel.core.language def teardown(): """On uninitialization, this function gets called to: - Remove Python root path from sys.path - Remove Maya before scene callback - Remove Maya after open scene callback - Remove Maya new scene callback - Re-source the original incrementalSaveScene """ # Remove our Python root from sys.path for sysPath in sys.path: if sysPath == pythonRoot: sys.path.remove(sysPath) # Remove Maya before scene callback OpenMaya.MSceneMessage.removeCallback(saveSceneJobId) # Remove Maya after open scene callback OpenMaya.MSceneMessage.removeCallback(openSceneJobId) # Remove Maya new scene callback OpenMaya.MSceneMessage.removeCallback(newSceneJobId) # Re-source the original incrementalSaveScene pymel.core.language.Mel.source('incrementalSaveScene', language='mel') |
Conclusion
I hope this article will be useful. If it is, please drop me a comment below. Also feel free to share your own experience and how you go about dealing with the Maya environment setup.
Cheers,