Let's say you have added a custom schema type to USD, and that maps to some custom plug-in or tool you have within Maya. In those cases, it is highly likely you'd want to trigger some sort of translation step to create your custom node set up.
Ordinarily this wouldn't be a terrifying ordeal, however once you factor in variant switching in a scene, things can become a little bit more involved.
To try to explain how this all works, let's start of with an extremely silly plug-in example that will create a custom translator plugin to represent a polygon cube in Maya.
PolyCubeNodeTranslator.h*
#pragma once
#include "AL/usdmaya/fileio/translators/TranslatorBase.h"
#include "AL/usdmaya/fileio/translators/TranslatorContext.h"
class PolyCubeNodeTranslator
{
public:
MStatus import(const UsdPrim& prim, MObject& parent) override;
MStatus
update(
const UsdPrim& prim)
override;
MStatus
tearDown(
const SdfPath& primPath)
override;
private:
MObject m_width;
MObject m_height;
MObject m_depth;
MObject m_subdivisionsWidth;
MObject m_subdivisionsHeight;
MObject m_subdivisionsDepth;
MObject m_outputMesh;
MObject m_inputMesh;
};
As an absolute minimum, you'll need to implement the update and tearDown methods. The following is an explanation of what those methods do, and how to implement them correctly.
General Setup
PolyCubeNodeTranslator.cpp*
#include "PolyCubeNodeTranslator.h"
#include "AL/usd/schemas/PolyCube.h"
initialize
The initialize method is a one time initialisation step for your translator plug-in. Now we all want to ensure our plug-ins operate as quickly as possible right? So the initialize step is really to help improve the performance when accessing data via MPlugs.
MStatus PolyCubeNodeTranslator::initialize()
{
MNodeClass polyCube("polyCube");
m_width = polyCube.attribute("width");
m_height = polyCube.attribute("height");
m_depth = polyCube.attribute("depth");
m_subdivisionsWidth = polyCube.attribute("subdivisionsWidth");
m_subdivisionsHeight = polyCube.attribute("subdivisionsHeight");
m_subdivisionsDepth = polyCube.attribute("subdivisionsDepth");
m_outputMesh = polyCube.attribute("output");
MNodeClass mesh("mesh");
m_inputMesh = mesh.attribute("input");
return MS::kSuccess;
}
needsTransformParent
One function you may want to overload is the needsTransformParent() function.
bool PolyCubeNodeTranslator::needsTransformParent() const
{
return true;
}
If your node is a DAG node, it will need to have a transform created for it, so return true. If however you node is a simple DG node (e.g. surface shader, texture etc), then you should return false from this method.
import
The Import method should only really be used to create the Maya nodes that will represent your custom prim. Now there is a small caveat to this. If the contents of your prim does not have any relationships to other prims in the stage, then you may as well do all of the setup you need within Import.
This example will create a simple polyCubeCreator node, a mesh, and connect them together. To do this will not require information from any other prim (for example, if there was another prim that contained a surface material, or a mesh deformation, then there would be a second step involved here to make those relationships in the Maya DG).
MStatus PolyCubeNodeTranslator::import(const UsdPrim& prim, MObject& parent)
{
MFnDependencyNode fnDep;
MFnDagNode fnDag;
MObject oPolyCube = fnDep.createNode();
MObject oMesh = fnDag.createNode("mesh", parent);
context()->insertItem(prim.GetPath(), oPolyCube);
context()->insertItem(prim.GetPath(), oMesh);
float width = 1.0f;
float height = 1.0f;
float depth = 1.0f;
int32_t subdivisionsWidth = 1;
int32_t subdivisionsHeight = 1;
int32_t subdivisionsDepth = 1;
AL_usd_PolyCube schema(prim);
schema.GetWidthAttr().Get(&width);
schema.GetHeightAttr().Get(&height);
schema.GetDepthAttr().Get(&depth);
schema.GetSubdivisionsWidthAttr().Get(&subdivisionsWidth);
schema.GetSubdivisionsHeightAttr().Get(&subdivisionsHeight);
schema.GetSubdivisionsDepthAttr().Get(&subdivisionsDepth);
MPlug(oPolyCube, m_width).setValue(width);
MPlug(oPolyCube, m_height).setValue(height);
MPlug(oPolyCube, m_depth).setValue(depth);
MPlug(oPolyCube, m_subdivisionsWidth).setValue(subdivisionsWidth);
MPlug(oPolyCube, m_subdivisionsHeight).setValue(subdivisionsHeight);
MPlug(oPolyCube, m_subdivisionsDepth).setValue(subdivisionsDepth);
return MS::kSuccess;
}
Post Import
Having generated all of the nodes you need to, you might end up needing to hook those nodes to other prims. This is admittedly a bit of a bad example (because in this case the node connections could have all been made within import itself).
However, in cases where the scene involves relationships between prims (e.g. one prim is a material, the other is the shape), it won't be possible to make those connections within import (because the other Maya node may not have been created yet). In those cases, you will need to make use of the postImport method to perform the connection of the maya nodes to other prims.
MStatus PolyCubeNodeTranslator::postImport(const UsdPrim& inputPrim, MObject& parent)
{
MObjectHandle handleToMesh;
if(!context()->getMObject(inputPrim, handleToMesh, MFn::kMesh))
{
MGlobal::displayError("unable to locate mesh");
return MS::kFailure;
}
MObjectHandle handleToPolyCube;
if(!context()->getMObject(inputPrim, handleToPolyCube, MFn::kPolyCube))
{
MGlobal::displayError("unable to locate polycube");
return MS::kFailure;
}
MDGModifier mod;
mod.connect(MPlug(handleToPolyCube.object(), m_outputMesh), MPlug(handleToMesh.object(), m_inputMesh));
mod.doIt();
return MS::kSuccess;
}
Variant Switching
If you've only supported the methods previously discussed, then your custom prim type should now be imported when you load a usd scene with the proxy shape.
If however you want to be able to respond to variant switches, and swap in or out nodes as a result, there is a little bit more work to do.
When a variant is switched, the proxy shape intercepts an event generated by USD that indicates that a variant is about to switch on a specific prim. At this point, the AL maya plugin will traverse the hierarchy under the prim on which the variant switched, and call a preTearDown() method. This method can be used to copy any values from your maya nodes into a layer within the usd stage.
MStatus PolyCubeNodeTranslator::preTearDown(UsdPrim& prim)
{
MObjectHandle handleToPolyCube;
if(!context()->getMObject(prim, handleToPolyCube, MFn::kPolyCube))
{
MGlobal::displayError("unable to locate polycube");
return MS::kFailure;
}
MObject oPolyCube = handleToPolyCube.object();
float width = 1.0f;
float height = 1.0f;
float depth = 1.0f;
int32_t subdivisionsWidth = 1;
int32_t subdivisionsHeight = 1;
int32_t subdivisionsDepth = 1;
MPlug(oPolyCube, m_width).getValue(width);
MPlug(oPolyCube, m_height).getValue(height);
MPlug(oPolyCube, m_depth).getValue(depth);
MPlug(oPolyCube, m_subdivisionsWidth).getValue(subdivisionsWidth);
MPlug(oPolyCube, m_subdivisionsHeight).getValue(subdivisionsHeight);
MPlug(oPolyCube, m_subdivisionsDepth).getValue(subdivisionsDepth);
AL_usd_PolyCube schema(prim);
schema.GetWidthAttr().Set(width);
schema.GetHeightAttr().Set(height);
schema.GetDepthAttr().Set(depth);
schema.GetSubdivisionsWidthAttr().Set(subdivisionsWidth);
schema.GetSubdivisionsHeightAttr().Set(subdivisionsHeight);
schema.GetSubdivisionsDepthAttr().Set(subdivisionsDepth);
return MS::kSuccess;
}
After the variant switch has occurred, the AL USD plugin will do a quick sanity check comparing the prims that were there previously, and the ones that are there now.
For each prim, if a corresponding prim still exists after the variant switch, AND the prim type is the same, then it call an update() method on your translator. Adding this method is optional, however it can improve the speed of a variant switch, so it is recommended!
If you wish to provide an update method to your translator, you will first need to opt in to this mechanism. By returning true from supportsUpdate (by default it returns false), you will now be able to provide a slightly quicker way for handling prims that do not change as a result of the switch. If however you return false here, your node will always be destroyed (via tear down), before being re-imported.
bool PolyCubeNodeTranslator::supportsUpdate() const
{
return true;
}
Once you have notified AL usd maya that your translator can update, simply provide your update function (which should simply copy the values from the prim and onto the maya nodes you previously created)
MStatus PolyCubeNodeTranslator::update(const UsdPrim& prim)
{
MObjectHandle handleToPolyCube;
if(!context()->getMObject(prim, handleToPolyCube, MFn::kPolyCube))
{
MGlobal::displayError("unable to locate polycube");
return MS::kFailure;
}
MObject oPolyCube = handleToPolyCube.object();
float width = 1.0f;
float height = 1.0f;
float depth = 1.0f;
int32_t subdivisionsWidth = 1;
int32_t subdivisionsHeight = 1;
int32_t subdivisionsDepth = 1;
AL_usd_PolyCube schema(prim);
schema.GetWidthAttr().Get(&width);
schema.GetHeightAttr().Get(&height);
schema.GetDepthAttr().Get(&depth);
schema.GetSubdivisionsWidthAttr().Get(&subdivisionsWidth);
schema.GetSubdivisionsHeightAttr().Get(&subdivisionsHeight);
schema.GetSubdivisionsDepthAttr().Get(&subdivisionsDepth);
MPlug(oPolyCube, m_width).setValue(width);
MPlug(oPolyCube, m_height).setValue(height);
MPlug(oPolyCube, m_depth).setValue(depth);
MPlug(oPolyCube, m_subdivisionsWidth).setValue(subdivisionsWidth);
MPlug(oPolyCube, m_subdivisionsHeight).setValue(subdivisionsHeight);
MPlug(oPolyCube, m_subdivisionsDepth).setValue(subdivisionsDepth);
return MS::kSuccess;
}
Now the eagle eyed reader may notice that the above function looks very similar to the import() function we initially wrote. To save yourself from a boiler plate code explosion, one option would be to simply call update from import:
MStatus PolyCubeNodeTranslator::import(const UsdPrim& prim, MObject& parent)
{
MFnDependencyNode fnDep;
MFnDagNode fnDag;
MObject oPolyCube = fnDep.createNode();
MObject oMesh = fnDag.createNode("mesh", parent);
context()->insertItem(prim.GetPath(), oPolyCube);
context()->insertItem(prim.GetPath(), oMesh);
return update(prim);
}
Now, if the variant switch results in the prim type changing, or the prim being removed, then a final method will be called, which is tearDown. The simplest implementation of this method is the following:
MStatus PolyCubeNodeTranslator::tearDown(const SdfPath& primPath)
{
context()->removeItems(primPath);
return MStatus::kSuccess;
}
In most cases that is probably enough. In some cases however, there may be times when you need to ensure the nodes are deleted in a specific order, or you have some other book keeping exercise to perform. Feel free to do so here!
It should be noted that whilst preTearDown and update are optional, tearDown is NOT. You must implement this method in order to support variant switching!