Many of you probably knows about the sineNode available in the Maya devkit written in C++ and/or Python. This example is very useful if you interested running the sin() function. But there is several limitations which make this example a bit dry.
First, it is just the sin() function and with this approach it means you would need to create as many 'math' node you want mathematical functions.
Second, it has one input and one output parameter only, so it limits you in a way that you need to combine many nodes to do more complicated expression such as sum(), degree to radian, etc... before going to sin(). And may be few more after...
with a big DG graph for a fairly simple operation.
What are the solutions?
One obvious is to use the Maya Expression system. Maya's evaluation of Expressions depends on the contents of the expression – sometimes you have to force the evaluation - whereas, for a node, the Maya DG will always do it for you when needed. Debugging a Maya Expression is not easy either (no tools are available), and requires a knowledge of MEL. And it is pretty slow :( so I would not recommend using it everywhere.
Fortunately, because they are written in C++, they are fast - Unfortunately, it means it is a library of Nodes, which needs maintenance (be recompiled for every release/platform), and they are not extensible.
Still a very good solution, but there is another one which makes a single node the ultimate choice, with a small performance hit. This is using Python and the fact Python can evaluation expression on the fly vs C++ which can't.
In this example, we will use the Python fundamentals of the best of any interpreted languages. To start we will create 3 input float attributes and 1 output float attribute. But the number of inputs and outputs could be bigger, that won't cause any problem for Python and our node. In a future version, I'll make the number of input attributes dynamic. So anyone could decide he needs 5 vs 3, and do the same for outputs. Input attributes are of type 'float', but in theory it could potentially be strings too since the Python interpreter would cope with that easily. That is another advantage of using Python vs C++ that we may take a look in another post. But let's keep it simple for now and use floats.
Here is my node attribute definition:
# Input exprSt =OpenMaya.MFnStringData () exprStCreator =exprSt.create ("a") tAttr =OpenMaya.MFnTypedAttribute () asdkMathNode.expression =tAttr.create ("expression", "expr", OpenMaya.MFnStringData.kString, exprStCreator) tAttr.setStorable (1) tAttr.setKeyable (False) nAttr =OpenMaya.MFnNumericAttribute () asdkMathNode.aIn =nAttr.create ("aIn", "a", OpenMaya.MFnNumericData.kFloat, 0.0) nAttr.setStorable (1) asdkMathNode.bIn =nAttr.create ("bIn", "b", OpenMaya.MFnNumericData.kFloat, 0.0) nAttr.setStorable (1) asdkMathNode.cIn =nAttr.create ("cIn", "c", OpenMaya.MFnNumericData.kFloat, 0.0) nAttr.setStorable (1) # Output nAttr =OpenMaya.MFnNumericAttribute () asdkMathNode.result =nAttr.create ("result", "res", OpenMaya.MFnNumericData.kFloat, 0.0) nAttr.setStorable (1) nAttr.setWritable (1) # Setup node attributes asdkMathNode.addAttribute (asdkMathNode.expression) asdkMathNode.addAttribute (asdkMathNode.aIn) asdkMathNode.addAttribute (asdkMathNode.bIn) asdkMathNode.addAttribute (asdkMathNode.cIn) asdkMathNode.addAttribute (asdkMathNode.result) asdkMathNode.attributeAffects (asdkMathNode.expression, asdkMathNode.result) asdkMathNode.attributeAffects (asdkMathNode.aIn, asdkMathNode.result) asdkMathNode.attributeAffects (asdkMathNode.bIn, asdkMathNode.result) asdkMathNode.attributeAffects (asdkMathNode.cIn, asdkMathNode.result)
In this implementation, in addition to the float attributes, we created a string attribute to handle the mathematical expression. But we will come back on this one. What is important here is to note that each numerical attributes has a default value - otherwise Python won't be happy with us later.
Ok nothing really new yet, but let's now talk about the 2 Python principles we are going to use. Symbols defined in the global state are always available for use in a Python expression, and Python eval() function evaluate a formatted Python expression string as if it was code written by the developer. The expression argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the globals and locals dictionaries as global and local namespace. That simply means that the string can contain any Python valid code to be executed on the fly.
In that case, as long out a, b , c input attributes are copied into some a, b, c Python symbols in the local or global dictionaries, eval() will just use them and parse/execute the code contained into the string. Here we go! not only can we use a mathematical expression, but we can use plain Python code and custom Python functions.
Our compute() custom node method becomes:
def compute (self, plug, dataBlock): if ( plug == asdkMathNode.result ): exprHandle =dataBlock.inputValue (asdkMathNode.expression) exprStData =OpenMaya.MFnStringData (exprHandle.data ()) exprSt =exprStData.string () # This is where our Maya Node' Attributes become Python Symbols aHandle =dataBlock.inputValue (asdkMathNode.aIn) a =aHandle.asFloat () bHandle =dataBlock.inputValue (asdkMathNode.bIn) b =bHandle.asFloat () cHandle =dataBlock.inputValue (asdkMathNode.cIn) c =cHandle.asFloat () # This where the magic plays # We also handle the fact that the Python Expression may not be valid try: result =eval (exprSt) except: sys.stderr.write ("Expression %s failed" % exprSt) raise outputHandle =dataBlock.outputValue (asdkMathNode.result) outputHandle.setFloat (result) dataBlock.setClean (plug) return return OpenMaya.kUnknownParameter
As long as exprSt contains valid Python code and the expression returns a number - our node will be fully functional. Python' mathematical functions being written in C/C++, the performance hit is very minimal.