Custom C++ plug-ins

If a rig includes custom Maya plug-ins, those must be converted in Rumba plug-ins and declared to MTORBA in order to be exported like any other Maya nodes.

The project located in rumba/mtorba/sdk/custom_plugins_example describes how to write such a plug-in and how to register it to MTORBA.

The sample is divided in three sub projects:

  • maya_plugin: the Maya custom plug-in source code

  • rumba_plugin: the Rumba custom plug-in source code

  • mtorba_exporter: the exporter to register the custom plug-ins in MTORBA

Build

This process builds the Maya plug-in in the custom_plugin/plug-ins folder and register it using a module file installed in your user Maya folder.

Windows

mkdir custom_plugin
cd custom_plugin
cmake path_to_custom_plugins_example -G "Visual Studio 16 2019" -DMAYA_LOCATION="C:/Program Files/Autodesk/Maya2020" -Drumba_DIR=rumba_path/sdk/

Export

To export a rig including custom nodes, you have to set the MTORBA_USER_EXPORTERS environment variable with path to the folder containing the custom exporters:

MTORBA_USER_EXPORTERS=rumba/mtorba/sdk/custom_plugins_example/mtorba_exporter/ mayapy mtorba/scripts/export.py -s -w /d/rig/tests/on/test_meCustomDeformer.ma

Run Rumba

To use a rig including custom nodes, you have to set the RUMBA_USER_PLUGINS environment variable with path to the folder containing the custom Rumba plug-ins:

RUMBA_USER_PLUGINS=custom_rumba_plugins/ rumba

Other resources

Here are the port of multiple Maya plug-ins:

MTORBA exporter source code

from mtorba.exporter import Input
from mtorba.exporters.geometryFilter import geometryFilterExporter

# Declare a custom Maya deformer
class meCustomDeformerExporter(geometryFilterExporter):
    def __init__(self):
        geometryFilterExporter.__init__(self, "meCustomDeformer", [
            # The deformer attributes, name in Maya, name in Rumba, type (Float, Int, String, V2f, V3f, V4f, M44f, Points, Spline, NurbsSurface, Mesh, Array)
            Input("envelope", "envelope", "Float"),
            Input("scale", "scale", "Float"),
            # paintList is an array of compounds, declare the compound elements to export:
            Input(
                "paintList", # Name of the attribute in Maya
                "paintList", # Name of the attribute in Rumba
                "Array",     # Attribute type in Rumba (compounds and array are Array in Rumba)
                # List of the compound element to export.
                children=[
                    "paint", # index 0 in the compound Array in Rumba
                ])
        ],
        weights_attribute_name="weights_name", # Attribute name with the weight channel name
        geom_index=True # Need an attribute with the deformed geometry index
                                        )

exporter = meCustomDeformerExporter()

MTORBA plug-in source code

/*

             *
            ***
           *****
   *********************       Mercenaries Engineering SARL
     *****************              Copyright (C) 2021
       *************
         *********        http://www.mercenaries-engineering.com
        ***********
       ****     ****
      **           **

*/

/* The Rumba version of the meCustomDeformer plug-in.
*
* Note there is one instance of the deformer node in Rumba for each deformed geometry. In Maya, the deformer is instanced once for every geometries.
*/

#include <Maquina/Maquina.h>

using namespace maquina;
using namespace Imath;

enum
{
  // Warning, must be in the same order than the dependencies declared in the register_node call.
  Dep_envelope=0,
  Dep_geom_index,
  Dep_inputGeometry,
  Dep_weights_name,
  Dep_scale,
  Dep_paintList
};

// The outputGeometry evaluation function. Call once for every deformed geometries.
static Value eval_outputGeometry(EvalContext& ctx)
{
  // ** Warning : The evaluation functions can be called concurrently in different threads.

  // * Warning : the input values must not be modified. Keep them const.
  const float envelope = ctx.as_float(Dep_envelope);
  const int geom_index = ctx.as_int(Dep_geom_index);
  const Points inputGeometry(ctx.value(Dep_inputGeometry)); // The input geometry
  const std::string& weights_name = ctx.as_string(Dep_weights_name);
  const float scale = ctx.as_float(Dep_scale);
  const Array paintList(ctx.value(Dep_paintList));

  // Makes sure geom_index is correct
  if(geom_index >= int(paintList.size()))
    return inputGeometry;

  // Maya array attributes are materrialized with maquina::Array objects.
  // Get the compound plug from the array using the geometry index.
  const Array paint_compound(paintList[geom_index]);
  if(paint_compound.size() == 0)
    return inputGeometry;

  // Maya compounds are also materalized with maquina::Array objects.
  // Finally get its first element. The index in the array must be the same than the compound declaration done in the MTORBA exporter .py file.
  const Value paint_value = paint_compound[0];

  // Here, our paint attribute is a float array, which is also materialized with a maquina::Array object in Rumba.
  const Array paint(paint_value);
  const uint32_t paint_size = uint32_t(paint.size());

  // Now duplicates the geometry. All internal buffers are shared for the moment.
  Points outputGeometry = inputGeometry.duplicate();
  BufferV3f normals_buffer;
  inputGeometry.compute_vertex_normals(normals_buffer);  // Get the vertex normals

  // Get a pointer on the normals
  const gsl::span<const V3f> normals = normals_buffer.read();

  // Get a pointer on the source points
  const gsl::span<const V3f> points_in = inputGeometry.read_points().read();

  // Maya geometry points might be stored in local space. In case you need the local-to-world matrix, here it is:
  const M44d& world_matrix = inputGeometry.read_attribute("world_matrix", Shape::Topology::constant).as_M44d();

  // Get a pointer on the destination points, now points are specialized for outputGeometry. The other buffers are shared with inputGeometry.
  const gsl::span<V3f> points_out = outputGeometry.write_points().write();

  // A helper class to iterate on weighted/selected vertices
  const WeightedComponent component(inputGeometry, weights_name.c_str(), envelope, Shape::Topology::vertex);
  const uint32_t count = component.size();
  parallel_for(0, count, 100, [&](uint32_t first, uint32_t last)
  {
    for(uint32_t i = first; i < last; ++i)
    {
      WeightedComponent::Iterator ite = component.begin()+i;

      // The vertex index
      const uint32_t index = ite.index();
      if(index >= paint_size)
        continue;

      // The final default Maya weights (including the envelope, the vertex components and the deformer weights)
      const float weight = ite.weight();

      // The additionnal painted user vertex attribute
      const float custom_weight = paint[index].as_float();

      points_out[index] = points_in[index] + normals[index] * weight * scale * custom_weight;
    }
  });

  return outputGeometry;
}

// Register the node class
void register_meCustomDeformer( Registry &r )
{
  r.register_node
  (
    // The node class name
    "meCustomDeformer",
    // The parent node class name
    "Node",
    {
      // * The MFnDeformer inputs
      { "envelope", 1.f },  // The deformer envelope
      { "geom_index", 0 },  // Index of the deformed geometry
      { "inputGeometry", Points::default_value }, // The input geometry
      { "weights_name", "" }, // The name of the weight attribute

      // * Our additionnal attributes
      { "scale", 1.f },     // A simple float attribute
      { "paintList", Array::default_value },  // A per geometry, per vertex, float attribute

      // An output float
      { "outputGeometry", Points::default_value, 0, "",
        // The output evaluation function
        eval_outputGeometry,
        {
          // ** The output plug dependencies
          { "envelope" },
          { "geom_index" },
          { "inputGeometry" },
          { "weights_name" },
          { "scale" },
          { "paintList" }
        }
      },
    }
  );
}