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" }
}
},
}
);
}