Maya scripts folders

Hello all,

Ok so I’ve recently started coding python for maya and I have run into a few questions about where I should be putting my scripts and how I should be importing them.

Maya has a couple of paths that you can put script in:
\Documents\maya\2018\scripts
\Documents\maya\2018\prefs\scripts

Just to name a few.

For some reason when I import the scripts in the maya Script editor it will only work if I put them into the \Documents\maya\2018\prefs\scripts and nowhere else.

import PlayblastMainCam
reload (PlayblastMainCam)

I was just wondering if this is normal, can I change this? Am I doing something wrong possibly? Just a few beginner questions I guess, less technical and more organizational, but it’s been bothering me for a while now.

Anyway, I thank you for any and all help possible.

In the script editor, if you do the following:

import sys
for path in sys.path:
    print(path)

That will show you all the current folder locations you should be able to import a python module from.

If you want to add your own path, its easy enough to do

sys.path.append('drive:/path/to/my/scripts/folder')

In which case you should now be able to import files from that location.
There are also ways to ensure paths are available at startup using either the Maya.env or a Maya module. Though for just starting out I wouldn’t worry to much about those. That’s more of a “I want to share this with others” style problem.

2 Likes

Thank you for the reply, this works perfectly.

The reason I’m wondering about this is because I would like to try and create a git repo with all my scripts that I can share with my friend so he can test out my scripts on his projects.
For some reason when I’m working on scripts in the repo they didn’t seem to be reloading properly and I thought it might have been an issue with the script path.
But this managed to fix the initial issue I was having with one of my scripts, so thank you!

Glad that helped.

For problems related to reload I’d probably need to see the actual code to be able to offer much advice there. reload is a very handy tool for rapidly testing changes, but it isn’t the most reliable, and sometimes you just have to reload Maya.

1 Like

Ooh, could it have just been an issue with the reload and not my paths? Because I’ve been trying to get a module with a folder hierarchy working by doing

import folder.otherfoler.script
reload (folder.otherfoler.script)

When I was trying this and made changes to my script it didn’t seem to change, but when I reopened maya everything seemed fine. So it could just have been a quirk with reload
Hmm, thank you for the info, this helps a lot too.

That kind of thing should work for most normal cases, though it could depend on if there is any code in folder/__init__.py or folder/otherfolder/__init__.py that folder/otherfolder/script.py depends on.

Or if script.py is importing code from other modules, those modules will not have reloaded, so if you’re making changes across multiple files and expecting the one reload call to fix them all up it won’t.

1 Like

Here I come with yet another terrible, horrible, messing-with-the-pipes-of-python solution. You can royally break things if you mess with this too much (as in, be very careful not to remove built-in stuff).

That said, we’ve been using this in production for about a decade (I just checked the commit log). This way our artists don’t have to close maya for 99% of our updates (there’s still some that require a close/reopen). We also emit a custom Qt signal to close any UI’s that will be reloaded. Otherwise you get some very bad behavior

There’s a dictionary sys.modules that holds all the modules that have been imported, keyed by their name. This is where import looks first so it doesn’t have to import the same module multiple times. So, if you remove something from this dictionary, it’s like it was never imported in the first place.

Then, if you loop through sys.modules, and check sys.modules[moduleName].__path__ to see if it lives in some folder you specify, and remove those keys from the dictionary. This will force the next import to re load everything that was in those folders.

import os, sys

def clearPathSymbols(paths, keepers=None):
	"""
	Removes path symbols from the environment.

	This means I can unload my tools from the current process and re-import them
	rather than dealing with the always finicky reload()

	I use directory paths rather than module names because it gives me more control
	over what is unloaded

	Make sure to close any UI's you're clearing before using this function

	Parameters
	----------
	paths : list
		List of directory paths that will have their modules removed
	keepers : list, optional
		List of module names that will not be removed
	"""
	keepers = keepers or []
	paths = [os.path.normcase(os.path.normpath(p)) for p in paths]

	for key, value in sys.modules.items():
		protected = False

		# Used by multiprocessing library, don't remove this.
		if key == '__parents_main__':
			protected = True

		# Protect submodules of protected packages
		if key in keepers:
			protected = True

		ckey = key
		while not protected and '.' in ckey:
			ckey = ckey.rsplit('.', 1)[0]
			if ckey in keepers:
				protected = True

		if protected:
			continue

		try:
			packPath = value.__file__
		except AttributeError:
			continue

		packPath = os.path.normcase(os.path.normpath(packPath))

		isEnvPackage = any(packPath.startswith(p) for p in paths)
		if isEnvPackage:
			sys.modules.pop(key)

With that defined, you could do something like this

clearPathSymbols([r'drive:\path\to\myModule', r'drive:\path\to\some\other\submodule'])
import myModule
1 Like

Word to the wise, reload won’t exist in Python 3. You’ll have to use

 from importlib import reload

The tricky thing about all reload-based worfklows is that it’s very hard to reload a nested set of modules in the correct order — if A imports a name out of B and, say, makes a local instance of a class using that name, reloading B won’t change the existing live instance contained in A.

Reload works great if you are sure your ‘workspace’ is really just the one module. But it’s often dicey if you are trying to iterate on something like a piece of code that is used by a menu item or GUI.

2 Likes

Thank you all for the advice and pointers. I’m going to have to take some time to look into this as it’s a bit over my head at the moment. I’m not super advanced when it comes to pipeline deving, but I look forward to hopefully understanding this at some point.

So thank you!

I have a small python script that I sometimes place in a folder that I want in my path. And when I import that script it automatically adds the folder that the script is in to the path. The source looks like this:

import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

All imported modules have the modulename.__file__ that will tell you where the module is located. Maybe that can be of use to you.

1 Like

I have not read all the comments but I would suggest that you use environment variables. I would also suggest that you build a python package out of your python files.

The benefit of this approach is that you can dynamically change the version of your python package when you launch maya from a terminal.

# tcsh
setenv PYTHONPATH /path/to/library

# bash
PYTHONPATH=/path/to/library

# cmd
set PYTHONPATH=C:/path/to/library

1 Like

I’m not exactly sure what the file is, I know that I have some init files, but I’m not exactly sure how you can import a file that will append the path before the path it’s in is appended.

I have no idea how to open Maya via the terminal haha, are there any good tutorials for how to go about setting something like this up?

Python has a special variable __file__ that is simply the path to the current file. This is available from inside any module. This isn’t something that you’d normally use from outside the module or file (except when you’re searching for a file when debugging).

However, inside the file os.path.dirname(os.path.abspath(__file__)) gets the folder that contains the current file. So that whole line (with the sys.path.append) adds the folder containing the current file to the python path.

This is good for a kickoff/startup script. Say I’ve got a folder that isn’t on the python path called myModuleFolder, and in that folder, I’ve got some files like __init__.py, kickoff.py, and myModule.py. And kickoff.py has that whole sys.path.append thing in it.
You could run python path/to/myModuleFolder/kickoff.py from the command line. That would add the current folder to the python path. Then the __init__.py file’s existence tells python that the folder (in this example, myModuleFolder) is an importable module.

So then, back inside kickoff.py, you can import myModuleFolder (after the sys.path.append of course) and do whatever you need with it.

I think your confusion here is that you don’t actually import kickoff anywhere. That’s a file that you just happen to know the path to, and it makes sure that the rest of your module that’s not on the python path gets loaded in properly.
However, if you’re inside Maya, this won’t work. If you were building a shelf button, you’d have to hard code the path to the folder to add.

There are better ways to make a module run-able from the command line.
Specifically you can define a __main__.py file in the module and then run that from the command line with python -m <module_name>.

Generally speaking you want to do all of your work to get paths setup first thing, and then never really worry about / manipulate them again. (not always possible, but this is the goal)

There are a few ways to go about this, for python there are the sitecustomize.py and usercustomize.py files, these get run during startup and can be used to customize sys.path or ensure that some modules are imported by default.

In addition this this, there is the PYTHONPATH environment variable as was mentioned previously.

For Maya, you also get the Maya.env file, this lets you add specific environment variables that only exist when running Maya. In addition to this you get both userSetup.mel and userSetup.py files. These work similarly to the customize files in standard python in that they are run during startup and are useful for setting up the paths and defaults.
Be aware that Maya will only run the FIRST userSetup.mel file it finds, but will run EVERY userSetup.py file that it finds.
A third option for Maya is using their Module system, module definitions are similar to the Maya.env file in that you can pre-define some environment variables, but it also lets you describe some default paths for script files, plugins, icons, etc… In addition module files can be versioned, or flagged for specific operating systems.

Personally, once your up at the point where you need to be distributing and coordinating tools, my recommendation would be to just work with some module files, and userSetup.py. It gives you the most flexibility / control, while still allowing for users to add things to Maya.env or add their own scripts to the default locations.

If all of this sounds overwhelming, that’s because it can be at first. But once you get your feet under you, and break a few things a lot of this will be a lot less terrifying.