|
Autodesk
Brain storming changes autodesk could make to imporove pymel integration
What Autodesk Can Do To Improve maya.cmdsPymel patches many bugs and design problems in maya.cmds, but there are major limitations to maya.cmds which pymel cannot circumvent. These issues must be addressed by Autodesk. Once resolved, the door will be open for a host of new features and speed improvements, not just for pymel but for any 3rd party app. Exceptions when Calling Mel from PythonThe ProblemIf maya.mel.eval fails to execute its string as mel code, it does not return any information on how or where the mel script failed. The result is that when a mel script called from a python script fails, there is no traceback information, and it is very difficult to trouble-shoot. The python scripter must setup the same scenario in mel in order to produce a useful traceback to help determine why and where the error is occuring. The DetailsIf a mel script fails when executed in the mel environment the user gets a meaningful error message, telling in what script and on what line the error occurred. For example: global proc example(){
string $f[]= `ls -type foo`;
};
example();
// Error: line 3: Unknown object type: foo // But if we now execute this same code via python, the exception message tells us only that the mel script failed and nothing else. >>> import maya.mel as mm >>> mm.eval( "string $res[] = `ls -type foo`;") // Error: line 1: Unknown object type: foo // # Error: Error occurred during execution of MEL script # Traceback (most recent call last): # File "<maya console>", line 1, in <module> # RuntimeError: Error occurred during execution of MEL script # The mel error that appears first ( the one that starts with // ) is only available from an interactive session within the script editor. It does not appear when executing maya.mel.eval from within an imported python module. To further illustrate the problem, imagine I call a mel script that is thousands of lines long. >>> import maya.mel as mm >>> try: >>> mm.eval( "myHugeMelCommand()") >>> except RuntimeError, msg: >>> print "The mel script failed and all I got was this message: %s" % msg The mel script failed and all I got was this message: Error occurred during execution of MEL script as a TD responsible for troubleshooting this, I have no clues as to what caused myHugeMelCommand to fail. I now have to attempt to recreate the exact scenario that caused the failure, but this time purely IN MEL, just so i can get a line number and useful error message. The SolutionThe solution is fairly straightforward. Fix maya.mel.eval to return an exception with the mel error message. Here is how it would work in practice: >>> import maya.mel as mm >>> mm.eval( "string $res[] = `ls -type foo`;") # Error: Error occurred during execution of MEL script # Traceback (most recent call last): # File "<maya console>", line 1, in <module> # RuntimeError: line 3: Unknown object type: foo # Alternately, a MelError class could be defined, for an even more explicit error: >>> import maya.mel as mm >>> mm.eval( "string $res[] = `ls -type foo`;") # Error: Error occurred during execution of MEL script # Traceback (most recent call last): # File "<maya console>", line 1, in <module> # MelError: line 3: Unknown object type: foo # Stringifying ArgumentsThe ProblemCertain commands, when passed instances of classes which are not derived from str or unicode, but which implement str and unicode methods, raise a TypeError. Similarly, iterable classes are rejected if they are not lists or tuples. The DetailsSome commands fail with user classes, and some succeed. Here is a test that shows the move command works, but the rename command fails. import maya.cmds as cmds class MyClass(object): def __init__( self, object ): self.object = str(object) def __str__(self): return self.object def __unicode__(self): return self.object sphereString, hist = cmds.sphere() sphereObj = MyClass( sphereString ) print try: cmds.rename( sphereObj, "foo" ) except Exception, msg: print "rename with custom class failed: '%s'" % msg else: print "rename with custom class succeeded" try: cmds.rename( sphereString, "foo" ) except Exception, msg: print "rename with string failed: '%s'" % msg else: print "rename with string class succeeded" The Results: rename with custom class failed: 'Object nurbsSphere4 is invalid' rename with string class succeeded The SolutionAutodesk must check that they are dealing with string arguments in a consistent manner across all commands. For strings, their code should not be doing any of the following: isinstance(arg, str) isinstance(arg, unicode) isinstance(arg, basestring) but should instead do: hasattr( arg, '__str__') or simply: try: arg = str(arg) except: ... The problem also exists for iterable arguments, and the solution here is similar: hasattr( arg, '__iter__') or try: arg = list(arg) except: ... Passing an MObject Instead of a StringThe ProblemFunctions in maya.cmds automatically reject passed arguments of type MObject. Allowing these arguments to be passed in place of a string could increase performance on large loops by an order of magnitude -- providing API level speed within maya.cmds. It would also facilitate a new object-oriented paradigm in maya.cmds. The Detailsmaya.cmds is created by autodesk's wrapper around the c++ code responsible for the mel commands. as a result of this heritage, maya.cmds is not capable of receiving object types which cannot nicely resolve to one of mel's data types: string, int, float, etc. When a command is passed a string argument and needs it converted to an MObject, it must use MSelectionList. The MSelectionList can be populated with a list of strings or MObjects, or it can use currently selected objects. It is notworthy that the MSyntax object already has some notion of allowing or disallowing MString vs. MObject From the docs: MSyntax:: setObjectType( MObjectFormat objectFormat, ...) "Specifies the representation of the objects passed to the command as either MObjects or MStrings. Also specifies the minimum number of objects required by the command." the first argument for this method is an MObjectFormat enum: MObjectFormat
on the back-end, its most likely that the mel commands must convert string =>> MObject via an MSelectionList. MSelectionList can generate its list from selected objects or from those passed in to it. The 'add' method is overloaded to support inputs of multiple types: strings, MObjects, MDagPaths, and MPlugs. since python supports lists of mixed types, it would not matter if the input list was a mixture of these accepted types ( e.g. 'mySphere', MObject() ). the end result would always be the same: cycle through the selection list as maya objects. Speed TestBelow is a demonstration of the order of magnitude speed improvements that could be achieved by opening up this features. The idea behind the test: The move command can be called with and without a passed object. If the object argument is omitted then the currently selected object is used. In API terms, what this means is that when a string naming an object to be moved is passed to the command, it must first convert this string into an MObject using MSelectionList. Omitting the argument avoids the string-to-MObject lookup, and the MSelectionList retrieves the MObject directly. This test demonstrates that the move command is dramatically faster when using a selection than when passing a node to operate on. The cause is the string-to-MObject lookup. If autodesk allows MObjects to be passed directly, we remove this bottleneck. import maya.cmds as cmds
cmds.file( new=1, force=True)
cmds.sphere()
cmds.group()
cmds.group()
cmds.group()
cmds.group()
cmds.group()
cmds.group()
cmds.group()
cmds.group()
cmds.group()
print
startTimer = cmds.timerX()
for t in xrange(2000):
cmds.duplicate(rr=True)
print 'dupe spheres 2000 times:'.ljust(40), cmds.timerX(startTime = startTimer)
print "-"*60
print "By Selection"
startTimer = cmds.timerX()
cmds.select("nurbsSphere*")
time1 = cmds.timerX(startTime = startTimer)
print 'select Spheres'.ljust(40), time1
startTimer = cmds.timerX()
cmds.move(10,10,10)
time2 = cmds.timerX(startTime = startTimer)
print 'move the selected objects'.ljust(40), time2
print 'total'.ljust(40), time1 + time2
print "-"*60
print "By Passed Argument"
startTimer = cmds.timerX()
obList = cmds.ls(sl=True)
time3 = cmds.timerX(startTime = startTimer)
print 'create list of selected objects'.ljust(40), time3
startTimer = cmds.timerX()
cmds.move(0,0,0, obList)
time4 = cmds.timerX(startTime = startTimer)
print 'move a list of objects'.ljust(40), time4
print 'total'.ljust(40), time3 + time4
print "-"*60
print 'By Selection is %.02fx faster' % ( (time3+time4)/(time1+time2) )
The Results: dupe spheres 2000 times: 12.46 ------------------------------------------------------------ By Selection select Spheres 0.0199999999968 move the selected objects 0.23 total 0.249999999996 ------------------------------------------------------------ By Passed Argument create list of selected objects 0.119999999999 move a list of objects 23.18 total 23.3 ------------------------------------------------------------ By Selection is 93.204x faster The SolutionAutodesk must change its wrapper to allow MObjects to be passed in place of strings. In addition to accepting MObjects, the python commands could look for a special method __mobject__ where any user class could place the MObject instance. for example: def handleStringArg( arg ):
if isinstance(arg,MObject):
doItMObject( arg )
elif hasattr( arg,'__mobject__' ):
if not isinstance(arg.__mobject__,MObject):
raise TypeError, "special method __mobject__ must contain an MObject instance"
doItMObject( arg.__mobject__ )
elif hasattr( arg,'__str__' ) or hasattr( arg,'__unicode__' ):
doItString( arg )
else:
raise TypeError, "A string or MObject is required"Command Syntax Introspection and SpecificityThe ProblemInformation on the syntax of functions within maya.cmds is not accessible from within python. As a result pymel is unable to generate documentation strings and node classes for custom commands and nodes. Pymel's object-oriented design rests on pairing a maya node with a mel command, and then wrapping that command as a class. Unfortunately, the MSyntax class which defines a command's syntax is very loose, so most of the flag handling logic -- such as setting defaults and pairing primary flags with secondary flags -- is coded by hand by the programmer of the command. The result is that this additional level of syntax specificity is unstandardized and inaccessible. The DetailsCurrently, pymel relies heavily on parsing maya's html documentation to find out information about commands and their flags, but obviously this is not possible for custom commands and nodes. In order to provide the same pymel features to custom commands on plugin load, we need to know certain information. Required Command Information (currently parsed from docs, or maintained manually)
Optional Command Information
The MSyntax class has no concept of secondary flags, per flag modes, flag defaults, or command-node pairs, so even if the C++ to Python command generator could add documentation to the doc string of each command, it could not provide any more information than is retrieved from the mel help command, which is insufficient. The SolutionProvide a higher level MSyntax and MArgParser pair with support for per-flag mode info (queryable, editable), modifier flags, and command-node pairs. class MSuperSyntax( OpenMaya.MSyntax ): def makeFlagQueryable(self, flag): ... def makeFlagEditable(self, flag): ... def addSecondaryFlag(self, primaryFlag, shortname, longname, arg1, arg2,... ): ... def setFlagDefault(self, val): ... def setNodeCounterpart(self, id): ... Once these classes are provided, there are two ways to make the information available:
In either case, adding greater specificity to the MSyntax of a command has the added bonus of protecting the programmer from the often tedious task of coding exceptions for all the flag scenarios that are not supported. For instance, with the current MSyntax one can designate that a command as a whole is queryable, but not which specific flags -- certain flags might only be editable. Rejecting these exceptions is up to the programmer to code manually and the user to find out through trial and error (since the help command in python and mel is of no help in this regard). Similarly, the onus is on the programmer to sort out primary and secondary flag compatibility. A higher level MSyntax and MArgParser pair with greater syntax control would provide a standardized interface for programmers to specify compatible flag combinations while simulataneously providing greater syntax information to the user of the command through more detailed help. |