Tuesday, 12 September 2017

Working with multiple Bullet solvers in a scene

When creating a Bullet body in a Maya scene in which there are multiple Bullet solvers, Maya simply picks the first solver it encounters and assigns that to the new body. This is almost always not the one you actually need. Unfortunately there are no tools to assign a different solver to a body.
In order to get around this issue, I modified the Bullet scripts that come with Maya. It's a quick hack, but it allows me to get on with things.
The part of code responsible for picking the solver is found in C:\Program Files\Autodesk\Maya2016\Python\Lib\site-packages\maya\app\mayabullet\BulletUtils.py in a function named getSolver. As you will see if you read it, it simply picks the first solver found in the scene. I have added the following bit of code:
try: if maya.cmds.optionVar( exists='preferredBulletSolver' ) :
preferedSolverName = maya.cmds.optionVar( q='preferredBulletSolver' )
preferedSolvers = maya.cmds.ls(preferedSolverName, type='bulletSolverShape',long=True)
if len(preferedSolvers)>0:
solvers = preferedSolvers
except:
pass
 This gets inserted from the third line of code in the function, right after the line that reads solvers = maya.cmds.ls(type='bulletSolverShape',long=True). What it does is it checks for an optionVar named preferredBulletSolver. An optionVar is Maya's way of storing user settings which are persistent and accessible to scripts. We can store the name of a solver in it, and when we come to create a new rigid body, the script will pick up on it and use that solver. Here is a piece of code that creates or updates the optionVar:
import maya.cmds as mc
selectedSolvers = mc.ls(sl=True, typ='bulletSolverShape')
if len(selectedSolvers) == 0 :
selectedSolvers = mc.listRelatives(mc.ls(sl=True), typ='bulletSolverShape')
if selectedSolvers is None or len(selectedSolvers) == 0 :
mc.warning( "Select a Bullet solver." )
else :
mc.optionVar( stringValue=('preferredBulletSolver', selectedSolvers[0]) )
mc.warning( "New Bullet rigid bodies will use the solver: " + selectedSolvers[0] )
I keep it as a shelf button. It is used by selecting the desired solver, then clicking the shelf button. From then on, newly created Bullet bodies will use the solver I selected, rather then some random one.

Thursday, 3 April 2014

Vector Matrix multiplication in Maya

Multiply PyMel vectors by matrices will not give you the result you might expect. Try a simple operation like this:
import pymel.core as pm
pm.polySphere() # Make a sphere.
pm.move((0,1,0)) # Move 1 unit up.
wm = pm.SCENE.pSphere1.getMatrix(worldSpace=True) # Get the local->world space matrix.
v = pm.dt.Vector((0,0,0)) # Create a zero vector.
print v*wm # Multiply the zero vector by the matrix and print the resulting vector.
At this point you'd expect to get (0, 1, 0) - the result of multiplying a (0,0,0) vector by a matrix that translates points by 1 in the Y axis. Instead you'll get (0,0,0). The reason is PyMel's vectors are only 3 components long, and the matrix is 4x4. Mathematically that's a no-no, but PyMel is happy to do it, but what you're getting is only the rotation and scale contained in the matrix affecting the vector. These are contained in the 3x3 part of the matrix while translation lives in the fourth row.
To get the translation you need to add the w component. However trying to initialize a PyMel Vector object with 4 components is not possible. Instead you need to use the VectorN class. If we do that in the code above, it will work as expected:
v = pm.dt.VectorN((0,0,0,1)) # Create a zero vector.
Alternatively you can use the Point class, which describes a 4-component vector:
v = pm.dt.Point((0,0,0))

Thursday, 5 December 2013

MQuaternion inversion functions

The wording in the documentation for Maya's quaternion inversion functions can lead you to use them erroneously. MQuaternion::invertIt() (and probably inverse() as well) will make the quaternion represent the inverse rotation and not do an inversion of the quaternion's components. To the latter you will need someQuaternion = -someQuaternion. This will correctly invert the XYZ and W components of the quaternion, and not just the XYZ. Because of double cover, inverted quaternions represent an equivalent orientation in 3D space. You might want to invert all the components when interpolating between two orientations.

Friday, 17 August 2012

Performance comparison: MEL vs PyMel vs OpenMaya vs maya.cmds vs C++

Over the years Maya has grown to have quite the myriad of programming APIs. MEL and the C++ API have been joined by Python maya.cmds, the OpenMaya Python API wrapper and PyMel. Now I read Maya 2013 is getting Python API 2.0. I've read a lot of discussion on what's quickest but most of them are based on generic assumptions (simpler is quicker so PyMel must be slowest) and don't quote any specific timings. So I've done some myself, while profiling tools and trying to get the most performance out of them. (All timings are in seconds)
Example: having selected 10000 edges, convert the selection to faces.
PyMel:1.7
maya.cmds:7.5
MEL:7.4
OpenMaya:0.2

OpenMaya is quickest by far, but PyMel is still 4.5x faster then MEL or maya.cmds. What will slow it down however, is if we add a small change - flattening the selection list (i.e.: "ls -sl -fl"):
Timings with selection list flattening:
PyMel:7.9
maya.cmds:8.0
MEL:7.7
OpenMaya:0.2 (there is no distinction in the API, same code)

Here it's roughly the same speed as MEL and maya.cmds. What seems to slow it down is creating instances of PyMel classes as we will see in the next example.
Example: custom edge ring select:
PyMel:2.4
maya.cmds:1.2
OpenMaya:0.03

PyMel stumbles here, maya.cmds is quicker but OpenMaya turns out nearly 40x faster still. However it's interesting to note that a lot of the PyMel code actually runs 2 or 3 times faster then the maya.cmds code. What slows it down is one part of it, which creates PyMel objects. To be clear, by creating PyMel objects, I mean something like: edge = pymel.core.general.MeshEdge( someMesh, someEdgeIndex ).

The overall conclusion I can draw is that PyMel is quick. A lot of it builds on top of OpenMaya, which is the quickest of all the scripting APIs. Maya.cmds is really just a thin wrapper on top of MEL, and there is no significant performance difference between the two. As long as you can avoid creating a lot of PyMel objects in your code, you should find it outperforming both maya.cmds and MEL.

We know OpenMaya is very quick, but lets compare it to it's non interpreted, compiled flavour: the C++ API. I have a simple plugin deformer implemented in both C++ and Python. It basically takes a point on a curve, calculates a normal to the curve at that point and then bends geometry along that curve. The timings are:

Python
Finding the point along the curve:0.027
Calculating a normal:0.15
Deforming passed points:0.060

C++
Finding the point along the curve:0.017
Calculating a normal:0.0023
Deforming passed points:0.000062

"Finding the point along the curve" is a single function call and most of the work gets done by Maya. There is only a small difference between Python and C++. "Deforming passed points" however, is a bunch of adding and multiplying. No API function gets called. Here C++ outperforms Python by nearly a thousand-fold. The more I work on this plugin the more dramatic the overall difference becomes. For deformers at least, C++ is massively quicker then Python.

These tests are only very limited, I admit, but the medal standings are: gold for C++, silver for OpenMaya, and bronze for PyMel. MEL and maya.cmds are tied for a close fourth.

Wednesday, 2 May 2012

Maya normals locking and what it does to tangent space

Apparently locking normals in Maya is no simple matter. There are several problems with using MEL, PyMel or methods accessed through the UI:
  • Locking normals might modify them. If you locked a normal before, locking it again might set it to something you didn't want.
  • Edge smoothing is re-calculated. Any vertex that has at least one hard edge, will make all the edges that are connected to it, hard. So for example you have a vertex with two soft edges and two hard edges connected to it. Lock that vertex's normal and suddenly all the edges are now hard.
Edge softness is still important even when you lock normals. With locked normals, changing edge softness will make no apparent difference in the viewport. However Maya will still look at edge smoothing to determine if vertices should share tangent and bi-normal vectors. This has  impact on normal maps and also on tri-stripping on graphics cards.
The only reliable way I found of locking normals is:

  • Store edge softness in an array
  • Save all the normals into an array
  • Set all the normals to what they are using this array
  • Lock all the normals per vertex, per poly
  • Restore edge softness

Wednesday, 11 January 2012

Nested function variable scope in Javascript

The way scope works for nested functions in Javascript can trip you up:
#target photoshop
function test()
{
this.a10 = function()
{
a = 10;
this.a20();
return a;
}
this.a20 = function()
{
a = 20;
}
}
t = new test();
alert( t.a10() ); // Returns 20!
And now with the added "var" keyword:
#target photoshop
function test()
{
this.a10 = function()
{
var a = 10;
this.a20();
return a;
}
this.a20 = function()
{
var a = 20;
}
}
t = new test();
alert( t.a10() ); // Returns 10

Wednesday, 14 December 2011

Locking vertex normals using Maya API

OpenMaya.MFnMesh.lockFaceVertexNormals is the function to use but it's got not very clear from the documentation what you need to pass to it. It needs an array of faces and of vertices formed like this:
faces = ( 0, 0, 0, 0, 1, 1, 1, 1 )
verts = ( 1, 2, 3, 4, 1, 2, 5, 6 )
In this example the face with index 0 will have vertices 1, 2, 3 and 4 locked. Face 1 will have vertices 1, 2, 5, 6 locked. This code will lock all vertex face normals:

verts = OpenMaya.MIntArray()
faces = OpenMaya.MIntArray()
vertsForFace = OpenMaya.MIntArray()
for faceID in range( meshFn.numPolygons() ) :
meshFn.getPolygonVertices( faceID, vertsForFace )
for i in range( vertsForFace.length() ) :
faces.append( faceID )
verts.append( vertsForFace[i] )
meshFn.lockFaceVertexNormals( faces, verts )

Wednesday, 2 November 2011

Context sensitive hotkeys

Want the same hotkey to do different things dependent on which Maya window you're in?

string $panelWithFocus = `getPanel -withFocus`;
if( $panelWithFocus == "polyTexturePlacementPanel1" ){
//we're in the UV Editor!
}
else{
//We're somewhere else
}
Great for having different marking menus under one hotkey.

UV editor marking menus

How do you get a custom Maya marking menu to pop up in the UV editor instead of the viewport? Go to the hotkey editor and change the marking menu script to say:
-parent polyTexturePlacementPanel1Window
instead of:
-parent viewPanes
This should work with any Maya UI element.

Saturday, 29 October 2011

Maya Tool Settings disappeared

I fire up Maya 2012 this morning and the Tool Settings widget is gone. It seemed to have shrunk to nothing whilst docked in the default left side of the window. Fixed it like this:
import maya.cmds as mc
mc.dockControl( "dockControl5", e=True, area="right" )
This moves it to the right side of the window like in older versions of Maya, from there it can be moved around as normal.

Tuesday, 18 October 2011

Handling HasNoEffect state on a plugin node.

If you're writing a Maya plugin node, you might want to handle the case where the user sets the node state to HasNoEffect. The state is an input attribute that is implicitly defined for any node. You can query it in a similar way to any other input attribute:
stateHnd = dataBlock.inputValue( self.state ) #MDataHandle
if stateHnd.asInt() == 1 :
inMeshHnd = dataBlock.inputValue( self.inMesh )
outMeshHnd = dataBlock.outputValue( self.outMesh )
inMeshObj = inMeshHnd.asMesh() # MObject
outMeshHnd.setMObject( inMeshObj )
dataBlock.setClean( plug )
return OpenMaya.MStatus.kSuccess

Thursday, 13 October 2011

Wing IDE up and running!

Got Wing IDE set up and debugging Maya. This page was very useful for that: Mel Wiki. However I had to change something to get it to send hi-lighted text from the editor to Maya. The line:
af, socktype, proto, canonname, sa = res[0]
became:
af, socktype, proto, canonname, sa = res[1]

Wednesday, 28 September 2011

Wednesday, 7 September 2011

ddx and ddy HLSL Intrinsic Function

The ddx and ddy functions return the partial derivatives of a given value with respect to screen coordinates.
In other words: the slope of the function described by the value. Or in other words again: the rate at which a given value changes across the screen.
For example ddx( position ) will tell you how much the surface slopes either left or right. So normalize( cross( ddx(position), ddy(position) ) ) returns a normal to the surface.

Friday, 2 September 2011

Maya itemFilter nodes

Maya deletes itemFilter nodes when a user creates a new scene or opens another one. This is unfortunate when the itemFilter is filtering something in a custom Outliner you've made. Unfortunately, specifying the Outliner as a parent is not supported. Setting the category of the itemFilter to "builtIn" however, makes them stick around, even after the current scene is closed.