Skip to content

Instantly share code, notes, and snippets.

@mottosso
Created August 29, 2020 13:00
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/335cda3667791e16cd3ba77b170fd8c8 to your computer and use it in GitHub Desktop.
Save mottosso/335cda3667791e16cd3ba77b170fd8c8 to your computer and use it in GitHub Desktop.

MPxData and Arrays

A combination of 1 and 2 whereby there is an array plug with custom MPxData, feeding into a target MPxData plug.

Build instructions are identical to (1).

cmake_minimum_required(VERSION 2.8)
# include the project setting file
include($ENV{DEVKIT_LOCATION}/cmake/pluginEntry.cmake)
# specify project name
set(PROJECT_NAME ArrayData)
set(SOURCE_FILES
Sender.cpp
Receiver.cpp
main.cpp
)
set(LIBRARIES
OpenMaya
OpenMayaFX
Foundation
)
# Build plugin
build_plugin()
#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/MArrayDataBuilder.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);
typFn.setArray(true);
typFn.setUsesArrayDataBuilder(true);
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 };
MDataHandle inputHandle = datablock.inputValue(value, &status);
HARDCHECK(status, "I wasn't able to get a inputHandle to inputData!");
int index = plug.logicalIndex(&status);
HARDCHECK(status, "Could not get logical index");
MArrayDataHandle arrayhandle = datablock.outputArrayValue(outputData, &status);
HARDCHECK(status, "computeValues : outputArrayValue");
MArrayDataBuilder builder = arrayhandle.builder(&status);
HARDCHECK(status, "Couldn't get a builder");
MDataHandle datahandle = builder.addElement(index, &status);
HARDCHECK(status, "Couldn't addElement");
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() * index);
// Finally, we write to the output handle like we would
// any normal plug. Except this time, the value is our
// custom data.
datahandle.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