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