Friday, December 7, 2012

Alternative to maya.standalone

Recently I had a challenge to write a tool that would export scene information of a maya file. I had the tool working for a while, however it needed to have maya GUI open so as to be able to load the files that it needed to export. The problem I faced this time around was the need for the exporter to work in the background as it needed to open a different file to the one currently being used. From the get-go I wanted to avoid using mayabatch as I didn't want to go the MEL path. After some research, the alternative that I found was maya.standalone module. All seemed fine and dandy, but after some time testing this module, I found that there was a major drawback to maya's standalone module. The very problem is that as soon as you try to load any file that utilizes Mental Ray, or just loads the Mayatomr.mll plugin, the remote maya crashes. There are alternatives to bypass that, but it basically means making sure that the file you are loading in no way requests for that plugin. If you are using ".ma" files, one way is to make sure that there is no requirement request for the Mayatomr.mll. Just search the file in a text editor (of course only works if you are using a maya ASCII '.ma' format), and delete the line if it is there. However, keep in mind that if you do have something in the scene that requires that plugin, you will get errors, and many things might become unstable and rather unpredictable. In my case, I could not bypass the MR issue. We are using MentalRay, and there is no way for me to go through nearly a 100 assets and adjust every one of them. Being desperate I started googling everywhere for a possible alternative. I did notice that some people mentioned the use of pymel for their batching, but unfortunately noone ever clarified what they meant, and how exactly they utilized pymel. I used python's subprocess module to call for a python script to be run by mayapy. Here is what the call looks like:
import subprocess
import dataExporter #this is the script I want to run remotely
import sys
import os.path

paths = sys.path
rootPath = None
for path in paths:
    if os.path.exists(path + "\\Maya.exe"):
        rootPath = path + "\\mayapy.exe"
        break

scriptPath = dataExporter.__file__
myVar = "Hello World"
command = '{0} {1} {2}'.format(rootPath, scriptPath, myVar)
mayaVar = subprocess.Popen(command, stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
subprocess.Popen.wait(mayaVar)
a, b = mayaVar.communicate()
if mayaVar.returncode:
    print b
else:
    print a

First of all, I need to give credit to Henry Foster for a wonderful post that allowed me to clarify the use of subprocess and how to get the data from it. Here is his original post.
Now I'll take a bit to clarify what I am doing in the code. The first part:
paths = sys.path
rootPath = None
for path in paths:
    if os.path.exists(path + "\\Maya.exe"):
        rootPath = path + "\\mayapy.exe"
        break
This piece of code simply figures out where your location for mayapy.exe is. Pretty straight forward. Now for some fun stuff:
scriptPath = dataExporter.__file__
myVar = "Hello World"
command = '{0} {1} "{2}"'.format(rootPath, scriptPath, myVar)
mayaVar = subprocess.Popen(command, stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
subprocess.Popen.wait(mayaVar)
output, errors = mayaVar.communicate()
if not mayaVar.returncode:
    print output
else:
    print errors
Here I am looking for the full path to the script I am going to run in the subprocess, and then initialize the subprocess itself. The "mayaVar" variable holds for us the output that that happens inside the subprocess. As you can see from the code, I am passing to the subprocess first the location of mayapy.exe, then the location of the script I want mayapy to run, and then I pass it the single variable that the script is going to accept. I also open up the PIPE from the subprocess to send and receive values between our mayapy. Then comes a rather important part of the script where I request the subprocess to wait for itself to finish the job. Now that part isnt' always necessary. However, my tool needed for the exported data to be available to continue, so I couldn't allow maya to just go on. If the wait method is not envoked, your subprocess will run just fine, but the output values from the mayaVar will not be available right away. In some cases, i.e. batch exporting, you probably don't even want to wait as you might be doing some other logic after the batch has been sent off. Keep in mind that invoking wait() will cause maya to stall and wait for the output from your subprocess and hence will not allow any user interaction during that time. Once my data is exported I check whether the process was a success at all, and if any errors were thrown. That is done via subprocess' "returncode": 1 - True, some errors have occured, 0 - False, nothing went wrong, all is good (I found this quite unintuitive at start, but got used to it). Now once your process is done, you can assign the subprocess outputs to some variables. And finally here is the actual variation of the 'dataExporter' file that can give you some sort of output:
import sys
import pymel.core as pm


def replyToMessage(message):
    reply = ""
    if message == "Hello World":
        reply = "Hello to you too!"
    elif message == "Goodbye":
        reply = "Leaving already? Ah well, take care!"
    else:
        reply = "I am sorry, not sure what you just said..."
    sys.stdout.write(reply)
    return reply


if __name__ == "__main__":
    replyToMessage(sys.argv[1])
By just importing the pymel.core you initialize a specialized maya.standalone. The 'sys.stdout.write' will write you the output that you will get back. Keep in mind, all of the feedback you get will be in string format. I do not think there is any way to pass more complex data between this script being run and the current maya session you are running as a user without any socket connection. Anyway, hope someone finds this helpful at some point! Till next time, DK

3 comments:

  1. Thank you for this post!
    This one line fixed the main bug in my own batch manager and change everything in my work:
    import pymel.core as pm
    I don't realy understand, why it works, but thank you once more!

    ReplyDelete
    Replies
    1. Hey, you are very welcome. Yea, the pymel import seems to do some of it's own little bit of magic that fixes all this 'standalone' stuff. Very glad you found this useful!

      Delete
  2. Thanks for the post.
    I tried this but had no luck yet. Running your example code worked out but when it come to loading a file the maya standalone instance crashes:

    pymel.internal.factories : INFO : MFnDagNode.model is deprecated
    Warning: file: C:/PROGRA~1/Autodesk/Maya2014/scripts/startup/initialStartup.mel line 192: Y-axis is already the Up-axis
    pymel.internal.startup : ERROR : could not perform Maya initialization sequence: failed on namedCommandSetup.mel: Error occurred during execution of MEL script

    How are you doing this?
    Thanks, Andi

    ReplyDelete