Add MediaImagePlane Node

This commit is contained in:
2026-03-20 09:30:32 +08:00
parent f513c9010e
commit 2565b8ffd6
16 changed files with 2021 additions and 89 deletions
+190
View File
@@ -0,0 +1,190 @@
//-
// ==========================================================================
// Copyright 2019 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+
// Description:
// Demonstrates how to create your own custom image plane based on
// Maya's internal image plane classes. This allows API users to
// override the default Maya image plane behavior. This class works
// like typical API nodes in that it can have a compute method and
// can contain static attributes added by the API user. This
// example class overrides the default image plane behavior and
// allows users to add transparency to an image plane using the
// transparency attribute on the node. Note, this code also
// illustrates how to use MImage to control the floating point
// depth buffer. When useDepthMap is set to true, depth is added
// is added to the image such that half of the image is at the near
// clip plane and the remaining half is at the far clip plane.
//
// Note, once the image plane node has been created it you must
// attached it to the camera shape that is displaying the node.
// You need to use the imagePlane command to attach the image plane
// you created to the specified camera.
//
// This example works only with renderers that use node evaluation
// as a part of the rendering process, e.g. Maya Software. It does
// not work with 3rd party renderers that rely on a scene translation
// mechanism.
//
// For example,
// string $imageP = `createNode customImagePlane`;
// imagePlane -edit -camera "persp" $imageP
//
#include <maya/MPxImagePlane.h>
#include <maya/MFnPlugin.h>
#include <maya/MImage.h>
#include <maya/MString.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnNumericData.h>
#include <maya/MDataHandle.h>
#include <maya/MPlug.h>
#include <maya/MEventMessage.h>
#include <maya/MGlobal.h>
class customImagePlane : public MPxImagePlane
{
public:
customImagePlane();
virtual ~customImagePlane();
MStatus loadImageMap( const MString &fileName, int frame, MImage &image ) override;
bool getInternalValue( const MPlug&, MDataHandle&) override;
bool setInternalValue( const MPlug&, const MDataHandle&) override;
static void* creator();
static MStatus initialize();
static MTypeId id; // The IFF type id
static MObject aTransparency;
static void timeChangedCallback( void * );
private:
double fTransparency;
MCallbackId fTimeChangedCallbackId;
};
MObject customImagePlane::aTransparency;
customImagePlane::customImagePlane()
: MPxImagePlane()
, fTransparency( 0.0 )
, fTimeChangedCallbackId( 0 )
{
// Uncomment the following line if you need a workaround which updates the
// transparency of the image plane during playback or srubbing with the
// Time Slider.
//
//fTimeChangedCallbackId = MEventMessage::addEventCallback(
// "timeChanged", &timeChangedCallback, this);
}
customImagePlane::~customImagePlane()
{
if (fTimeChangedCallbackId != 0) {
MMessage::removeCallback(fTimeChangedCallbackId);
fTimeChangedCallbackId = 0;
}
}
bool
customImagePlane::getInternalValue( const MPlug &plug, MDataHandle &handle)
{
if ( plug == aTransparency ) {
handle.set( fTransparency );
return true;
}
return MPxImagePlane::getInternalValue( plug, handle );
}
bool
customImagePlane::setInternalValue( const MPlug &plug, const MDataHandle &handle)
{
if ( plug == aTransparency ) {
fTransparency = handle.asDouble();
setImageDirty();
return true;
}
return MPxImagePlane::setInternalValue( plug, handle );
}
MStatus
customImagePlane::loadImageMap( const MString &fileName, int frame, MImage &image )
{
image.readFromFile(fileName);
unsigned int width, height;
image.getSize(width, height);
unsigned int size = width * height;
unsigned char *pixels = image.pixels();
unsigned int i;
for ( i = 0; i < size; i ++, pixels += 4 ) {
pixels[3] = (unsigned char)(pixels[3] * (1.0 - fTransparency));
}
MPlug depthMap( thisMObject(), useDepthMap );
bool value;
depthMap.getValue( value );
if ( value ) {
float *buffer = new float[width*height];
unsigned int c, j;
for ( c = i = 0; i < height; i ++ ) {
for ( j = 0; j < width; j ++, c++ ) {
if ( i > height/2 ) {
buffer[c] = -1.0f;
} else {
buffer[c] = 0.0f;
}
}
}
image.setDepthMap( buffer, width, height );
delete [] buffer;
}
return MStatus::kSuccess;
}
MTypeId customImagePlane::id( 0x1A19 );
void*
customImagePlane::creator()
{
return new customImagePlane;
}
MStatus
customImagePlane::initialize()
{
MFnNumericAttribute nAttr;
aTransparency = nAttr.create( "transparency", "tp",
MFnNumericData::kDouble, 0 );
nAttr.setStorable(true);
nAttr.setInternal(true);
nAttr.setMin(0.0);
nAttr.setMax(1.0);
nAttr.setDefault(0.0);
nAttr.setKeyable(true);
addAttribute( aTransparency );
return MStatus::kSuccess;
}
// These methods load and unload the plugin, registerNode registers the
// new node type with maya
//
MStatus initializePlugin( MObject obj )
{
MFnPlugin plugin( obj, PLUGIN_COMPANY, "7.0", "Any");
MStatus status = plugin.registerNode( "customImagePlane", customImagePlane::id,
customImagePlane::creator,
customImagePlane::initialize,
MPxNode::kImagePlaneNode );
if (!status) {
status.perror("registerNode");
}
return status;
}
MStatus uninitializePlugin( MObject obj )
{
MFnPlugin plugin( obj );
MStatus status = plugin.deregisterNode( customImagePlane::id );
if (!status) {
status.perror("deregisterNode");
}
return status;
}
void customImagePlane::timeChangedCallback(void *clientData)
{
customImagePlane* imagePlane = (customImagePlane *) clientData;
if (imagePlane) {
MString cmd = "getAttr " + imagePlane->name() + ".tp";
MGlobal::executeCommand(cmd);
}
}
+345
View File
@@ -0,0 +1,345 @@
//-
// ==========================================================================
// Copyright 2015 Autodesk, Inc. All rights reserved.
// Use of this software is subject to the terms of the Autodesk license agreement
// provided at the time of installation or download, or which otherwise
// accompanies this software in either electronic or hard copy form.
// ==========================================================================
//+
/*
transformDrawNode uses MPxDrawOverride to draw texts about transformation of
all mesh shapes in the viewport via MPxDrawOverride::addUIDrawables. By setting
isAlwaysDirty to false in MPxDrawOverride constructor, the draw override will
be updated (via prepareForDraw()) only when the node is marked dirty via DG
evaluation or dirty propagation. Additional callbacks are also added to
explicitly mark the node as being dirty (via MRenderer::setGeometryDrawDirty())
for certain circumstances. Note that the draw callback in MPxDrawOverride
constructor is set to NULL in order to achieve better performance.
*/
#include <maya/MCallbackIdArray.h>
#include <maya/MDagMessage.h>
#include <maya/MDrawRegistry.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnEnumAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MFrameContext.h>
#include <maya/MItDag.h>
#include <maya/MPxDrawOverride.h>
#include <maya/MPxLocatorNode.h>
#include <maya/MVectorArray.h>
#include <stdio.h>
using namespace MHWRender;
class transformDrawNode : public MPxLocatorNode
{
public:
enum ETransformType
{
kTranslate,
kRotate,
kScale,
kShear
};
static void* creator() { return new transformDrawNode(); }
static MStatus initialize();
// Registration
static constexpr const char className[] = "transformDrawNode";
static MTypeId id;
static MString drawDbClassification;
static MString drawRegistrantId;
// Attributes
static MObject aTextColor;
static MObject aTransformType;
private:
transformDrawNode();
~transformDrawNode() override;
static void AllDagChangesCallback(
MDagMessage::DagMessage msgType,
MDagPath &child,
MDagPath &parent,
void *clientData);
static void WorldMatrixModifiedCallback(
MObject &transformNode,
MDagMessage::MatrixModifiedFlags &modified,
void *clientData);
void processDagMessage(bool refreshWorldMatrixCbIds);
MCallbackId fAllDagChangesCbId;
MCallbackIdArray fWorldMatrixModifiedCbIds;
};
// Registration
constexpr const char transformDrawNode::className[];
MTypeId transformDrawNode::id(0x80034);
MString transformDrawNode::drawDbClassification("drawdb/geometry/transformDrawNode");
MString transformDrawNode::drawRegistrantId(transformDrawNode::className);
// Attributes
MObject transformDrawNode::aTextColor;
MObject transformDrawNode::aTransformType;
MStatus transformDrawNode::initialize()
{
MFnNumericAttribute nAttr;
aTextColor = nAttr.create("textColor", "tc", MFnNumericData::k3Float);
nAttr.setDefault(1.0f, 1.0f, 1.0f);
nAttr.setUsedAsColor(true);
MPxNode::addAttribute(aTextColor);
MFnEnumAttribute eAttr;
aTransformType = eAttr.create("transformType", "tt", kTranslate);
eAttr.addField("Translate", kTranslate);
eAttr.addField("Rotate", kRotate);
eAttr.addField("Scale", kScale);
eAttr.addField("Shear", kShear);
MPxNode::addAttribute(aTransformType);
return MS::kSuccess;
}
transformDrawNode::transformDrawNode() : MPxLocatorNode()
{
fAllDagChangesCbId = MDagMessage::addAllDagChangesCallback(
AllDagChangesCallback, this);
}
transformDrawNode::~transformDrawNode()
{
if (fWorldMatrixModifiedCbIds.length() > 0)
{
MMessage::removeCallbacks(fWorldMatrixModifiedCbIds);
}
if (fAllDagChangesCbId != 0)
{
MMessage::removeCallback(fAllDagChangesCbId);
}
}
void transformDrawNode::processDagMessage(bool refreshWorldMatrixModifiedCbIds)
{
// Explicitly mark the node as being dirty on certain DAG message callbacks
// so that the draw override can be updated.
MRenderer::setGeometryDrawDirty(thisMObject());
if (refreshWorldMatrixModifiedCbIds)
{
MStatus status;
MItDag dagIt(MItDag::kDepthFirst, MFn::kMesh, &status);
if (status)
{
// Remove existing callbacks
if (fWorldMatrixModifiedCbIds.length() > 0)
{
MMessage::removeCallbacks(fWorldMatrixModifiedCbIds);
fWorldMatrixModifiedCbIds.clear();
}
// Add new callbacks
for ( ;!dagIt.isDone(); dagIt.next() )
{
MDagPath dagPath;
status = dagIt.getPath(dagPath);
if (status)
{
MCallbackId id = MDagMessage::addWorldMatrixModifiedCallback(
dagPath, WorldMatrixModifiedCallback, this);
fWorldMatrixModifiedCbIds.append(id);
}
}
}
}
}
void transformDrawNode::AllDagChangesCallback(
MDagMessage::DagMessage msgType,
MDagPath &child,
MDagPath &parent,
void *clientData)
{
// We need to refresh the world matrix modified callbacks because the DAG
// is just changed.
transformDrawNode *node = static_cast<transformDrawNode*>(clientData);
if (node) node->processDagMessage(true);
}
void transformDrawNode::WorldMatrixModifiedCallback(
MObject &transformNode,
MDagMessage::MatrixModifiedFlags &modified,
void *clientData)
{
// We don't need to refresh the world matrix modified callbacks because the
// DAG is not changed.
transformDrawNode *node = static_cast<transformDrawNode*>(clientData);
if (node) node->processDagMessage(false);
}
class transformDrawData : public MUserData
{
public:
MColor fTextColor{1.0f, 1.0f, 1.0f, 1.0f};
transformDrawNode::ETransformType fTransformType{transformDrawNode::kTranslate};
MVectorArray fPositions;
MVectorArray fVectors;
};
class transformDrawOverride : public MPxDrawOverride
{
public:
static MPxDrawOverride* creator(const MObject& obj)
{
return new transformDrawOverride(obj);
}
DrawAPI supportedDrawAPIs() const override { return kAllDevices; }
bool hasUIDrawables() const override { return true; }
MUserData* prepareForDraw(
const MDagPath& objPath,
const MDagPath& cameraPath,
const MFrameContext& frameContext,
MUserData* oldData) override;
void addUIDrawables(
const MDagPath& objPath,
MUIDrawManager& drawManager,
const MFrameContext& frameContext,
const MUserData* data) override;
private:
// By setting isAlwaysDirty to false in MPxDrawOverride constructor, the
// draw override will be updated (via prepareForDraw()) only when the node
// is marked dirty via DG evaluation or dirty propagation. Additional
// callbacks are also added to explicitly mark the node as being dirty (via
// MRenderer::setGeometryDrawDirty()) for certain circumstances. Note that
// the draw callback in MPxDrawOverride constructor is set to NULL in order
// to achieve better performance.
transformDrawOverride(const MObject& obj) : MPxDrawOverride(obj, NULL, false) {}
~transformDrawOverride() override {}
};
MUserData* transformDrawOverride::prepareForDraw(
const MDagPath& objPath,
const MDagPath& cameraPath,
const MFrameContext& frameContext,
MUserData* oldData)
{
MStatus status;
MObject obj = objPath.node(&status);
if (!status) return NULL;
transformDrawData* tdData = dynamic_cast<transformDrawData*>(oldData);
if (!tdData)
{
tdData = new transformDrawData();
}
// Text color
{
MPlug plug(obj, transformDrawNode::aTextColor);
MObject o = plug.asMObject();
MFnNumericData nData(o);
nData.getData(tdData->fTextColor.r, tdData->fTextColor.g, tdData->fTextColor.b);
}
// Transform type
{
MPlug plug(obj, transformDrawNode::aTransformType);
tdData->fTransformType = (transformDrawNode::ETransformType)plug.asInt();
}
tdData->fPositions.clear();
tdData->fVectors.clear();
MItDag dagIt(MItDag::kDepthFirst, MFn::kMesh, &status);
if (status)
{
for ( ; !dagIt.isDone(); dagIt.next() )
{
MDagPath dagPath;
status = dagIt.getPath(dagPath);
if (!status)
{
status.perror("MItDag::getPath");
continue;
}
MObject transformNode = dagPath.transform(&status);
if (!status)
{
status.perror("MDagPath::transform");
continue;
}
MFnDagNode transform(transformNode, &status);
if (!status)
{
status.perror("MFnDagNode constructor");
continue;
}
MTransformationMatrix matrix(transform.transformationMatrix());
MVector vec = matrix.getTranslation(MSpace::kWorld);
tdData->fPositions.append(vec);
double tmp[3];
MTransformationMatrix::RotationOrder order;
switch (tdData->fTransformType)
{
case transformDrawNode::kRotate:
matrix.getRotation(tmp, order);
vec = MVector(tmp);
break;
case transformDrawNode::kScale:
matrix.getScale(tmp, MSpace::kWorld);
vec = MVector(tmp);
break;
case transformDrawNode::kShear:
matrix.getShear(tmp, MSpace::kWorld);
vec = MVector(tmp);
break;
default:
// Don't reset vec so that translation is drawn by default.
break;
}
tdData->fVectors.append(vec);
}
}
return tdData;
}
void transformDrawOverride::addUIDrawables(
const MDagPath& objPath,
MUIDrawManager& drawManager,
const MFrameContext& frameContext,
const MUserData* data)
{
const transformDrawData* tdData = dynamic_cast<const transformDrawData*>(data);
if (!tdData) return;
drawManager.beginDrawable();
drawManager.setColor(tdData->fTextColor);
for (unsigned int i = 0; i < tdData->fVectors.length(); i++)
{
MPoint pos(tdData->fPositions[i]);
MVector vec(tdData->fVectors[i]);
char tmpStr[128] = {0};
sprintf(tmpStr, "(%.3f, %.3f, %.3f)", vec.x, vec.y, vec.z);
MString text(tmpStr);
drawManager.text(pos, text, MUIDrawManager::kCenter);
}
drawManager.endDrawable();
}
MStatus initializePlugin( MObject obj )
{
MFnPlugin plugin( obj, PLUGIN_COMPANY, "1.0", "Any" );
MStatus status = plugin.registerNode(
transformDrawNode::className,
transformDrawNode::id,
transformDrawNode::creator,
transformDrawNode::initialize,
MPxNode::kLocatorNode,
&transformDrawNode::drawDbClassification);
if (!status)
{
status.perror("registerNode");
return status;
}
status = MDrawRegistry::registerDrawOverrideCreator(
transformDrawNode::drawDbClassification,
transformDrawNode::drawRegistrantId,
transformDrawOverride::creator);
if (!status)
{
status.perror("registerDrawOverrideCreator");
return status;
}
return status;
}
MStatus uninitializePlugin( MObject obj )
{
MFnPlugin plugin( obj );
MStatus status = MDrawRegistry::deregisterDrawOverrideCreator(
transformDrawNode::drawDbClassification,
transformDrawNode::drawRegistrantId);
if (!status)
{
status.perror("deregisterDrawOverrideCreator");
return status;
}
status = plugin.deregisterNode(transformDrawNode::id);
if (!status)
{
status.perror("deregisterNode");
return status;
}
return status;
}