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 | |