Skip to content

Instantly share code, notes, and snippets.

@theodox
Last active September 23, 2024 22:15
Show Gist options
  • Save theodox/2c712a91155c7e1c4c15 to your computer and use it in GitHub Desktop.
Save theodox/2c712a91155c7e1c4c15 to your computer and use it in GitHub Desktop.
Exposes the MayaPyManager class, which is used to run instances of MayaPy with explict control over paths and environment variables. A Manager can run scripts, modules, or command strings in a separate MayaPy environment; results and errors are captured and returned.
'''
Exposes the MayaPyManager class, which is used to run instances of MayaPy with explict control over paths and environment variables. A Manager can run scripts, modules, or command strings in a separate MayaPy environment; results and errors are captured and returned.
Typical uses might be:
- running unit tests
- running a copy of Maya.standalone as a headless RPC server with StandaloneRPC https://github.com/theodox/standaloneRPC
- spawning multipe copies of maya to batch process files in parallel on a multi-core machine
- do any of the above on multiple maya versions concurrently
Basic usage is simply to create a MayaPyManager and then call run_script, run_module or run_command:
example = MayaPyManager( '/path/to/Maya2014/bin/mayapy.exe', None)
output, errors = example.run_command("print 'hello world'")
print output
> hello world
To control the PYTHONPATH of the created instance, pass the paths you want as a string array to the MayaPyManager. These paths will override the default system paths inside the interpreter.
example = MayaPyManager( '/path/to/Maya2014/bin/mayapy.exe', None, 'path/to/modules', 'another/path/to/modules', 'etc/etc')
You can also control the environment variables in the interpreter by providing a dictionary:
custom_env = os.environ.copy()
custom_env['MAYA_DEBUG'] = 'False'
custom_env['P4_CLIENT'] = 'test_client'
example = MayaPyManager( '/path/to/Maya2014/bin/mayapy.exe', custom_env)
PYTHONHOME and PYTHONPATH will be set automatically, so don't bother changing them yourself.
Copyright (c) 2014 Steve Theodore
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''
import os
import subprocess
class MayaPyManager(object):
'''
For running a maya python interpreter* under controlled conditions:
- Override default paths
- Overrides environment variables
- Disable userSetup.py
* Technically this _should_ work for any Python installation, although non-maya
versions may not be able to find their standard libraries if these are
installed in non-standard locations.
See https://docs.python.org/2/tutorial/interpreter.html for more details on interpeter flags
Here are the supported flags
-B : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x
-d : debug output from parser; also PYTHONDEBUG=x
-E : ignore PYTHON* environment variables (such as PYTHONPATH)
-O : optimize generated bytecode slightly; also PYTHONOPTIMIZE=x
-OO : remove doc-strings in addition to the -O optimizations
-Q arg : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew
-s : don't add user site directory to sys.path; also PYTHONNOUSERSITE
-S : don't imply 'import site' on initialization
-t : issue warnings about inconsistent tab usage (-tt: issue errors)
-v : verbose (trace import statements); also PYTHONVERBOSE=x
can be supplied multiple times to increase verbosity
-W arg : warning control; arg is action:message:category:module:lineno
'''
DEFAULT_FLAGS = []
def __init__(self, interpreter, environ, *paths, **flags):
'''
Create a MayaPyManager for ths supplied interpreter and paths
Arguments:
- intepreter is a disk path to a maya python interpreter
- environ is a dictionary of environment variables.
If no dictionary is provided, intepreter will use os.environ.
- paths is an array of strings. It will completely replace
existing PYTHONPATH variables
Any flag set to True will be used by the intepreter, eg
MayaPyManager('path/to/mayapy.exe', None, 'path', v=True)
will produce a command line like:
path/to/mayapy.exe -v <script.py>
when run.
'''
self.interpreter = interpreter
assert os.path.isfile(interpreter) and os.path.exists(interpreter) , "'%s' is not a valid interpreter path" % interpreter
self.paths = paths
self.flags = flags
self.environ = environ
def run_script(self, pyFile, *args):
'''
Run the supplied script file in the interpreter. Returns a tuple (results, errors) which contain,
respectively, the output and error printouts produced by the script. Note that if errors is not
None, the script did not complete successfully
Arguments will be passed to the script (NOT to the python interpreter). Thus
someMayaPyMgr.run_script('test/script.py', "hello", "world")
will be produce a command line like:
c:/path/to/maya2014/mayapy.exe test/script.py hello world
If the script expects flag -type argument, its up to the user to provide them in the right form:
someMayaPyMgr.run_script('test/script.py', "-g", "greeting")
will be produce a command line like:
c:/path/to/maya2014/mayapy.exe test/script.py -g greeting
'''
rt_env = self._runtime_environment(self.paths)
arg_string = ""
if len(args):
arg_string = " ".join(map(str, *args))
flagstring = self._flag_string()
cmdstring = '''"%s" %s "%s" %s''' % (self.interpreter, flagstring, pyFile, arg_string)
runner = subprocess.Popen(cmdstring, env = rt_env,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
return runner.communicate()
def run_module(self, module):
'''
Run the supplied moudle file in the interpreter ('running' here is 'importing').
Returns a tuple (results, errors) which contain, respectively, the output and
error printouts produced by the module. Note that if errors is not None, the
module did not import correctly
The module must in the PYTHONPATH used by the intepreter.
'''
rt_env = self._runtime_environment(self.paths)
flagstring = self._flag_string()
cmdstring = '''"%s" %s -m %s''' % (self.interpreter, flagstring, module)
runner = subprocess.Popen(cmdstring, env = rt_env,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
return runner.communicate()
def run_command (self, cmd,):
'''
Run the supplied command string in the intepreter.
Returns a tuple (results, errors) which contain, respectively, the output and
error printouts produced by the command string. Note that if errors is not None,
the commands did not execute correctly.
'''
rt_env = self._runtime_environment(self.paths)
flagstring = self._flag_string()
cmdstring = '''"%s" %s -c "%s"''' % (self.interpreter, flagstring, cmd)
runner = subprocess.Popen(cmdstring, env = rt_env,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
return runner.communicate()
def _flag_string(self):
'''
generate correctly formatted flag strings
'''
flagged = lambda x, y : "-" + x
flaggedOpt = lambda x, y: "-" + x + " " + str(y)
default_flags = [flagged(f, v) for f, v in self.flags.items() if v and not f in ('W', 'Q')] or ['']
special_flags = [flaggedOpt(f, v) for f, v in self.flags.items() if v and f in ('W', 'Q')] or ['']
return " ".join(default_flags + special_flags)
def _runtime_environment(self, *new_paths):
'''
Returns a new environment dictionary for this intepreter, with only the supplied paths
(and the required maya paths). Dictionary is independent of machine level settings;
non maya/python related values are preserved.
'''
runtime_env = os.environ.copy()
if self.environ:
runtime_env = self.environ.copy()
new_paths = list(self.paths)
quoted = lambda x : '%s' % os.path.normpath(x)
# set both of these to make sure maya auto-configures
# it's own libs correctly
runtime_env['MAYA_LOCATION'] = os.path.dirname(self.interpreter)
runtime_env['PYTHONHOME'] = os.path.dirname(self.interpreter)
# use PYTHONPATH in preference to PATH
runtime_env['PYTHONPATH'] = ";".join(map(quoted, new_paths))
runtime_env['PATH'] = ''
return runtime_env
@theodox
Copy link
Author

theodox commented May 13, 2014

added quotes to run_command - for some reason this behavior varies for maya 2014 and maya 2011

@theodox
Copy link
Author

theodox commented May 13, 2014

fixed stupid cut-and-paste bug in paths

@trickyslip
Copy link

Take a look at line 114, I think you forgot to add the interpreter variable to the string substitution.

-        assert os.path.isfile(interpreter) and os.path.exists(interpreter) , "'%s' is not a valid interpreter path"
+        assert os.path.isfile(interpreter) and os.path.exists(interpreter) , "'%s' is not a valid interpreter path" % interpreter

@Bastian6574
Copy link

I always getting this error.
File "C:\Python27\lib\subprocess.py", line 672, in init
errread, errwrite)
File "C:\Python27\lib\subprocess.py", line 882, in _execute_child
startupinfo)
TypeError: environment can only contain strings

when trying:
self.le_textOut.setText('...initializing Maya scene, please wait')
custom_env = os.environ.copy()
custom_env['MAYA_LOCATION'.encode("utf-8")] = "C:\Program Files\Autodesk\Maya2015".encode("utf-8")
pmng = MayaPyManager( r'C:/Program Files/Autodesk/Maya2015/bin/mayapy.exe',
custom_env
)
self.le_textOut.append (str(pmng))
pmng.run_module( "maya.standalone" )
self.le_textOut.append ( str(pmng.run_command( 'maya.standalone.initialize(name="python")' ) ))
self.le_textOut.append ( str(pmng.run_command( 'cmds.file(%s, force=True,open=True)'%path ) ))
self.le_textOut.append ( str(pmng.run_command( 'cams=cmds.ls(type="camera")' ) ))

no idea if that's correct, maybe someone can point me out. I tried literally everything to get the subprocess to spawn a mayapy and then to try to load maya standalone in there and do various commands.
basically everything failed.

@theodox
Copy link
Author

theodox commented Apr 5, 2016

It looks like you're trying to use the manager interactively; that's not really what it's for. It's designed to start mayapy with pre-selected set of arguments and environment variables:, To run Maya interactively from outside you'd need to use the commandport or something like standaloneRPC

Unless you're trying to explcitly override MAYA_LOCATION in this standalone you should not need to do anything explicity, since the default behaviour is just to inherit the current environment.,

@theodox
Copy link
Author

theodox commented Apr 5, 2016

Here's the simplest example:

make a python file like this:

import maya.standalone
maya.standalone.initialize()
print "started"
import maya.cmds as cmds
print "LS", cmds.ls()

and save it as test.py.

To call it you'd do

 test_mp = mm.MayaPyManager("C:/program files/Autodesk/maya2016/bin/mayapy.exe", None, '')
results, errors = test_mp.run_script('test.py') 

results should be something like

started
LS [u'time1', u'sequenceManager1', u'hardwareRenderingGlobals', u'renderPartition', u'renderGlobalsList1', u'defaultLightList1', u'defaultShaderList1', u'postProcessList1', u'defaultRenderUtilityList1', u'defaultRenderingList1', u'lightList1', u'defaultTextureList1', u'lambert1', u'particleCloud1', u'initialShadingGroup', u'initialParticleSE', u'initialMaterialInfo', u'shaderGlow1', u'dof1', u'defaultRenderGlobals', u'defaultRenderQuality', u'defaultResolution', u'defaultLightSet', u'defaultObjectSet', u'defaultViewColorManager', u'defaultColorMgtGlobals', u'hardwareRenderGlobals', u'characterPartition', u'defaultHardwareRenderGlobals', u'lightLinker1', u'persp', u'perspShape', u'top', u'topShape', u'front', u'frontShape', u'side', u'sideShape', u'hyperGraphInfo', u'hyperGraphLayout', u'globalCacheControl', u'brush1', u'strokeGlobals', u'ikSystem', u'layerManager', u'defaultLayer', u'renderLayerManager', u'defaultRenderLayer']

Remember, though, that the manager runs a script once -- it's not interactive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment