Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active August 29, 2020 11:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mottosso/f2d520af4dea5bc7b07576e02d544c65 to your computer and use it in GitHub Desktop.
Save mottosso/f2d520af4dea5bc7b07576e02d544c65 to your computer and use it in GitHub Desktop.
MyData - How to use MPxData with Maya

MyData - How to use MPxData with Maya

          Sender                   Receiver
     _______________           _______________ 
    |               |         |               |
    |    outputData o-------->o inputData     |
    |               |         |               |
--->o value         |         |         value o--->
    |               |         |               |
    |_______________|         |_______________|

Here's a working example of how to pass a custom C++ instance as attributes between nodes in Maya, using MPxData.

import os
import sys
import cmdx
from maya import cmds

cmds.loadPlugin("MyData")

sender = cmdx.createNode("mySender")
receiver = cmdx.createNode("myReceiver")

sender["outputData"] >> receiver["inputData"]
sender["value"] = 5
assert receiver["value"] == 5, "The plug-in didn't work!"

Tested with Maya 2020, but is expected to run from 2011+.

References


Build

git clone https://gist.github.com/f2d520af4dea5bc7b07576e02d544c65.git MyData
cd MyData
mkdir build
cd build
export DEVKIT_LOCATION=/path/to/devkit
cmake .. -GNinja
cmake --build .

Motivation

When would you want to use MPxData? Why not stick with the built-in types for integers, floats, matrices.. you name it?

You tell me! Sometimes, there's data between two nodes you'd like to share that can't (or shouldn't) be serialised to plain data. Like pointers or heavy data structures not suited or relevant for plugs. For example, if you're writing a physics solver, you'll have plenty of state relevant to a simulation and not-so-much to Maya. Not until the simulation is complete and can be translated into matrices.

Why did you make this example?

Because at the time of this writing the Maya documentation has two examples of how to use MPxData, one is 6,000+ lines and the other is a command rather than a node. I spent a long time decrypting the apiMesh example which features not only use of MPxData but tons and tons of other unrealted features and boiled it down into this one example, for both my future self and for you.

cmake_minimum_required(VERSION 2.8)
# include the project setting file
include($ENV{DEVKIT_LOCATION}/cmake/pluginEntry.cmake)
# specify project name
set(PROJECT_NAME MyData)
set(SOURCE_FILES
Sender.cpp
Receiver.cpp
main.cpp
)
set(LIBRARIES
OpenMaya
OpenMayaFX
Foundation
)
# Build plugin
build_plugin()
Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "Sender.h"
#include "Receiver.h"
#include <maya/MFnPlugin.h>
/**
* @brief Pull `value` from `inputData`
*
* Sender Receiver
* _______________ _______________
* | | | |
* | outputData o-->o inputData |
* | | | |
* --->o value | | value o--->
* | | | |
* |_______________| |_______________|
*
*/
MStatus initializePlugin (MObject obj) {
MStatus status;
MFnPlugin plugin(obj, "MyData", "2020.2", "Any");
status = plugin.registerData(MyData::typeName,
MyData::id,
&MyData::creator,
MPxData::kData);
if (!status) {
status.perror("registerData");
return status;
}
status = plugin.registerNode(Sender::typeName,
Sender::id,
Sender::creator,
Sender::initialize);
if (!status) {
status.perror("registerNode");
return status;
}
status = plugin.registerNode(Receiver::typeName,
Receiver::id,
Receiver::creator,
Receiver::initialize);
if (!status) {
status.perror("registerNode");
return status;
}
return status;
}
MStatus uninitializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj);
status = plugin.deregisterData(MyData::id);
if (!status) {
status.perror("deregisterData");
return status;
}
status = plugin.deregisterNode(Sender::id);
if (!status) {
status.perror("deregisterNode");
return status;
}
status = plugin.deregisterNode(Receiver::id);
if (!status) {
status.perror("deregisterNode");
return status;
}
return status;
}
#include "Receiver.h"
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnPluginData.h>
#include "stdio.h" // cerr
const MTypeId Receiver::id(0x85004);
const MString Receiver::typeName("myReceiver");
MObject Receiver::value;
MObject Receiver::inputData;
inline void SOFTCHECK(MStatus status, MString msg) {
if (!status) { cerr << "ERROR: " << msg << "\n"; }
}
#define HARDCHECK(STAT, MSG) \
if (MS::kSuccess != STAT) { \
cerr << "ERROR: " << MSG << endl; \
return MS::kFailure; \
}
MStatus Receiver::initialize() {
MStatus status;
MFnTypedAttribute typFn;
MFnNumericAttribute numFn;
value = numFn.create("value", "v", MFnNumericData::kInt, 0, &status);
numFn.setStorable(true);
numFn.setReadable(true);
numFn.setWritable(true);
SOFTCHECK(status, "failed to create value");
inputData = typFn.create("inputData", "ind", MyData::id, MObject::kNullObj, &status);
typFn.setStorable(true);
typFn.setReadable(true);
typFn.setWritable(true);
SOFTCHECK(status, "failed to create inputData");
addAttribute(value);
addAttribute(inputData);
attributeAffects(inputData, value);
return MStatus::kSuccess;
}
Receiver::Receiver() {}
MStatus Receiver::computeValue(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kSuccess };
// With MPxData, we read from our datablock like any normal attribute
MDataHandle inputHandle = datablock.inputValue(inputData, &status);
HARDCHECK(status, "I wasn't able to get a inputHandle to inputData!");
// But here we explicitly cast it from a void* to the type we've
// registered it as. There's room for error here if you should try
// and cast it to a different type or if the value is `nullptr`
MyData* newData = static_cast<MyData*>(inputHandle.asPluginData());
HARDCHECK(status, "I got a inputHandle, but newData was null!");
// From here, we can access the data instance like any
// normal C++ class instance
MDataHandle outputHandle = datablock.outputValue(value);
outputHandle.set(newData->getValue());
datablock.setClean(plug);
return status;
}
MStatus Receiver::compute(const MPlug &plug, MDataBlock &datablock) {
MStatus status { MS::kSuccess };
if (plug == value) {
return computeValue(plug, datablock);
}
else if (plug == inputData) {
datablock.setClean(plug);
}
else {
status = MS::kUnknownParameter;
}
return status;
}
#ifndef RECEIVER_H
#define RECEIVER_H
#include "Sender.h"
#include <maya/MPxNode.h>
#include <maya/MPxData.h>
#include <maya/MTypeId.h>
#include <maya/MString.h>
class Receiver : public MPxNode {
public:
Receiver();
MStatus compute(const MPlug &plug, MDataBlock &dataBlock) override;
MStatus computeValue(const MPlug &plug, MDataBlock &dataBlock);
static void* creator() { return new Receiver; }
static MStatus initialize();
static const MTypeId id;
static const MString typeName;
// Attributes
static MObject inputData;
static MObject value;
};
#endif
#include "Sender.h"
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnPluginData.h>
#include "stdio.h" // cerr
const MTypeId Sender::id(0x85005);
const MString Sender::typeName("mySender");
const MTypeId MyData::id(0x80096);
const MString MyData::typeName("myData");
MObject Sender::value;
MObject Sender::outputData;
inline void SOFTCHECK(MStatus status, MString msg) {
if (!status) { cerr << "ERROR: " << msg << "\n"; }
}
#define HARDCHECK(STAT, MSG) \
if (MS::kSuccess != STAT) { \
cerr << "ERROR: " << MSG << endl; \
return MS::kFailure; \
}
MStatus Sender::initialize() {
MStatus status;
MFnTypedAttribute typFn;
MFnNumericAttribute numFn;
value = numFn.create("value", "v", MFnNumericData::kInt, 0, &status);
numFn.setStorable(true);
numFn.setReadable(true);
numFn.setWritable(true);
SOFTCHECK(status, "Failed to create value");
outputData = typFn.create(
"outputData", "oud",
// Custom MPxData types are created as a regular Typed attribute,
// where the "type" is the data custom TypeId
MyData::id,
// When you read from this later, it'll be received as
// an MObject, initialised to this empty type.
MObject::kNullObj,
&status
);
typFn.setStorable(false);
typFn.setReadable(true);
typFn.setWritable(false);
SOFTCHECK(status, "Failed to create outputData");
addAttribute(value);
addAttribute(outputData);
attributeAffects(value, outputData);
return MStatus::kSuccess;
}
Sender::Sender() {}
MStatus Sender::computeOutputData(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kSuccess };
// Here we'll read from the `value` plug of this node, translate
// it into a MyData type and pass it on to whomever is connected.
MDataHandle inputHandle = datablock.inputValue(value, &status);
HARDCHECK(status, "I wasn't able to get a inputHandle to inputData!");
// The thing that separates MPxData from e.g. MFnNumericAttribute
// is that we can't just create the type ourselves. Instead, we
// ask Maya to instantiate our type - which calls on `MyData::copy()`
//
// This way, you won't have to manage the dangling pointer you would
// otherwise get if you were to try and pass it yourself. Maya will
// manage this memory and clean it up alongside the plug.
MFnPluginData fnDataCreator;
MTypeId tmpid(MyData::id);
fnDataCreator.create(tmpid, &status);
HARDCHECK(status, "Creating MyData");
// The thing that got me the first time was how we must create
// a new copy of our data each time. We can't fetch whatever data
// was present in the output and simply update it. If your data
// is heavy - like millions of vertices - that would mean changes
// to one of those verts would incur the cost of copying all of them.
// So, the lesson is, keep your MPxData small and store references
// to large data stored internally.
MyData* newData = (MyData*)fnDataCreator.data(&status);
HARDCHECK(status, "Getting proxy MyData object");
// Now we've got the actual instance to our MyData instance,
// we can use it like any other C++ class instance.
newData->setValue(inputHandle.asInt());
// Finally, we write to the output handle like we would
// any normal plug. Except this time, the value is our
// custom data.
MDataHandle outputHandle = datablock.outputValue(outputData);
outputHandle.set(newData);
datablock.setClean(plug);
return status;
}
MStatus Sender::compute(const MPlug &plug, MDataBlock &datablock) {
MStatus status { MS::kSuccess };
if (plug == value) {
datablock.setClean(plug);
}
else if (plug == outputData) {
return computeOutputData(plug, datablock);
}
else {
status = MS::kUnknownParameter;
}
return status;
}
#ifndef SENDER_H
#define SENDER_H
#include <maya/MPxNode.h>
#include <maya/MPxData.h>
#include <maya/MTypeId.h>
#include <maya/MString.h>
class MyData : public MPxData {
public:
// Our data
int getValue() const { return _value; }
void setValue(int value) { _value = value; }
int _value { 0 };
public:
// Maya boiler-plate
MyData() {}
~MyData() override {}
static const MTypeId id;
static const MString typeName;
MString name() const override { return MyData::typeName; }
MTypeId typeId() const override { return MyData::id; }
void copy(const MPxData& other) override {
if (other.typeId() == MyData::id) {
const MyData* otherData = static_cast<const MyData*>(&other);
this->setValue(otherData->getValue());
}
}
static void* creator() { return new MyData; }
};
class Sender : public MPxNode {
public:
Sender();
MStatus compute(const MPlug &plug, MDataBlock &dataBlock) override;
MStatus computeOutputData(const MPlug &plug, MDataBlock &dataBlock);
static void* creator() { return new Sender; }
static MStatus initialize();
static const MTypeId id;
static const MString typeName;
// Attributes
static MObject value;
static MObject outputData;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment