From 2565b8ffd63040ca8c4b191b7129fbe5789909f8 Mon Sep 17 00:00:00 2001 From: indigo Date: Fri, 20 Mar 2026 09:30:32 +0800 Subject: [PATCH] Add MediaImagePlane Node --- .gitignore | 1 + AGENT.md | 6 +- README.md | 79 ++- docs/examples/customImagePlane.cpp | 190 ++++++ docs/examples/transformDrawNode.cpp | 345 +++++++++++ .../plug-ins/Maya2023/MayaMediaPlaneNode.mll | Bin 334336 -> 108544 bytes .../AEMediaImagePlaneTemplate.mel | 106 ++++ ...ediaPlane.mel => AEMediaPlaneTemplate.mel} | 22 +- src/MayaMediaPlaneNode/CMakeLists.txt | 25 +- src/MayaMediaPlaneNode/MediaImagePlane.cpp | 573 ++++++++++++++++++ src/MayaMediaPlaneNode/MediaImagePlane.h | 225 +++++++ .../MediaPlaneDrawOverride.cpp | 244 ++++++++ .../MediaPlaneDrawOverride.h | 114 ++++ ...aMediaPlaneNode.cpp => MediaPlaneNode.cpp} | 86 +-- ...{MayaMediaPlaneNode.h => MediaPlaneNode.h} | 12 +- src/MayaMediaPlaneNode/Plugin.cpp | 82 ++- 16 files changed, 2021 insertions(+), 89 deletions(-) create mode 100644 docs/examples/customImagePlane.cpp create mode 100644 docs/examples/transformDrawNode.cpp create mode 100644 src/MayaMediaPlaneNode/AEMediaImagePlaneTemplate.mel rename src/MayaMediaPlaneNode/{AETemplateMediaPlane.mel => AEMediaPlaneTemplate.mel} (82%) create mode 100644 src/MayaMediaPlaneNode/MediaImagePlane.cpp create mode 100644 src/MayaMediaPlaneNode/MediaImagePlane.h create mode 100644 src/MayaMediaPlaneNode/MediaPlaneDrawOverride.cpp create mode 100644 src/MayaMediaPlaneNode/MediaPlaneDrawOverride.h rename src/MayaMediaPlaneNode/{MayaMediaPlaneNode.cpp => MediaPlaneNode.cpp} (88%) rename src/MayaMediaPlaneNode/{MayaMediaPlaneNode.h => MediaPlaneNode.h} (96%) diff --git a/.gitignore b/.gitignore index 2461387..9ffb639 100644 --- a/.gitignore +++ b/.gitignore @@ -101,4 +101,5 @@ $RECYCLE.BIN/ # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) build/ +install/ vcpkg/ \ No newline at end of file diff --git a/AGENT.md b/AGENT.md index bdafba9..358ee2d 100644 --- a/AGENT.md +++ b/AGENT.md @@ -13,14 +13,18 @@ Create a Maya Image Plane Node use CMake + C++ API project which can read mp4, a 4. Post Effect like crop, resize, flip video frame 5. Video Frame Caching for fast playback 6. 編譯完成的結構請以Maya Module的形式存放 +7. 建立Node完成後請編寫AETemplate, 名稱規則為 AE[NodeName]Template.mel +8. `.mll` 放到plug-ins/[MayaVersion]內, `.mel` 放到scripts, `.png` `.svg` 放到icons下 ## 測試環境 使用Maya 2023作為測試環境 + Maya路徑為 `C:\Program Files\Autodesk\Maya2023\bin\maya.exe` MayaBatch路徑為 `C:\Program Files\Autodesk\Maya2023\bin\mayabatch.exe` MayaPy路徑為 `C:\Program Files\Autodesk\Maya2023\bin\mayapy.exe` -* 測試 Load Plugin 正確的部分請使用 mayapy.exe +* 啟動 Plugin 編譯完成後測試請使用環境變數 `MAYA_MODULE_PATH` 指定到編譯install下包含.mod的目錄下 +* 測試 Load Plugin 使用 mayapy.exe * 測試 viewport 顯示部分請使用maya.exe + MEL 測試 diff --git a/README.md b/README.md index b37ca46..ef7007c 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,11 @@ MediaPlane/ │ └── MayaMediaPlaneNode/ │ ├── CMakeLists.txt # Plugin CMake configuration │ ├── Plugin.cpp # Maya plugin entry point -│ ├── AETemplateMediaPlane.mel # Attribute Editor template -│ ├── MayaMediaPlaneNode.h # Node header file -│ ├── MayaMediaPlaneNode.cpp # Node implementation +│ ├── AETemplateMediaPlane.mel # Attribute Editor template for MediaPlane +│ ├── AEMediaImagePlaneTemplate.mel # Attribute Editor template for MediaImagePlane +│ ├── MediaPlaneNode.h/cpp # MediaPlane node (MPxNode) +│ ├── MediaPlaneDrawOverride.h/cpp # Viewport 2.0 draw override +│ ├── MediaImagePlane.h/cpp # MediaImagePlane node (MPxImagePlane) │ ├── FFmpegVideoDecoder.h # FFmpeg decoder header │ ├── FFmpegVideoDecoder.cpp # FFmpeg decoder implementation │ ├── FrameCache.h # Frame cache header @@ -114,22 +116,43 @@ C:\Program Files\Autodesk\Maya2023\plug-ins\ 3. Browse and select `MayaMediaPlaneNode.mll` 4. Check Loaded -### Create Media Plane Node +### Create Nodes + +This plugin provides two types of nodes: + +#### 1. MediaPlane Node (MPxNode with Viewport 2.0) ```mel // MEL command createNode MediaPlane; ``` +#### 2. MediaImagePlane Node (MPxImagePlane) + +```mel +// Create MediaImagePlane node +createNode mediaImagePlane; + +// Attach to camera (required for image plane) +imagePlane -edit -camera "persp" ; +``` + ### Use AE Template -The plugin includes a custom Attribute Editor template (AETemplateMediaPlane.mel). After installation, the MEL file is automatically placed in the Maya scripts directory and will be loaded when the plugin is loaded. +The plugin includes custom Attribute Editor templates: +- `AETemplateMediaPlane.mel` - For MediaPlane node +- `AEMediaImagePlaneTemplate.mel` - For MediaImagePlane node + +After installation, the MEL files are automatically placed in the Maya scripts directory and will be loaded when the plugin is loaded. For manual loading in Script Editor: ```mel -// Source the AE template +// Source the AE template for MediaPlane source "AETemplateMediaPlane.mel"; + +// Source the AE template for MediaImagePlane +source "AEMediaImagePlaneTemplate.mel"; ``` Or copy to your Maya scripts directory: @@ -139,11 +162,47 @@ C:\Users\\Documents\maya\2023\scripts\ ### Set Video File +#### MediaPlane Node ```mel // Set video path setAttr "MediaPlane1.videoFile" -type "string" "C:/path/to/video.mp4"; ``` +#### MediaImagePlane Node +```mel +// Set video path +setAttr "mediaImagePlane1.videoFile" -type "string" "C:/path/to/video.mp4"; +``` + +### Difference Between Nodes + +| Feature | MediaPlane | MediaImagePlane | +|---------|------------|-----------------| +| Type | MPxNode (custom) | MPxImagePlane (native) | +| Viewport | Viewport 2.0 draw override | Native image plane | +| Camera attachment | Not required | Required | +| Use case | 3D plane in scene | Standard image plane | + +### MediaImagePlane Specific Usage + +The MediaImagePlane node integrates with Maya's native image plane system: + +```mel +// Full workflow example +createNode mediaImagePlane; +rename "imagePlane1" "myVideoPlane"; + +// Attach to camera +imagePlane -edit -camera "persp" "myVideoPlane"; + +// Set video file +setAttr "myVideoPlane.videoFile" -type "string" "C:/path/to/video.mp4"; + +// Optional: Adjust size +setAttr "myVideoPlane.width" 1920; +setAttr "myVideoPlane.height" 1080; +``` + ### Attribute Reference | Attribute Name | Short Name | Type | Description | @@ -161,6 +220,14 @@ setAttr "MediaPlane1.videoFile" -type "string" "C:/path/to/video.mp4"; | cacheSize | cs | int | Cache size (MB, 16-2048) | | clearCache | cc | boolean | Clear cache | +#### MediaImagePlane Specific Attributes + + | Attribute Name | Short Name | Type | Description | + |---------------|------------|------|-------------| + | frameOffset | fo | double | Frame offset for alignment | + | imageSize | is | double2 | Original image size (read-only) | + | coverage | cv | double2 | Coverage (read-only) | + ### Output Attributes | Attribute Name | Short Name | Type | Description | diff --git a/docs/examples/customImagePlane.cpp b/docs/examples/customImagePlane.cpp new file mode 100644 index 0000000..93e3456 --- /dev/null +++ b/docs/examples/customImagePlane.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +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); + } +} \ No newline at end of file diff --git a/docs/examples/transformDrawNode.cpp b/docs/examples/transformDrawNode.cpp new file mode 100644 index 0000000..f83e137 --- /dev/null +++ b/docs/examples/transformDrawNode.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +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(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(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(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(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; +} \ No newline at end of file diff --git a/install/Maya2023/plug-ins/Maya2023/MayaMediaPlaneNode.mll b/install/Maya2023/plug-ins/Maya2023/MayaMediaPlaneNode.mll index 86acb90cf1ba1187d30966cd70d33d4e6d93258b..b76b472717f2058c0b27cb1fad67665d235b9f1f 100644 GIT binary patch literal 108544 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 0) + { + textField -edit -tx $result[0] "AEMediaImagePlaneTemplateVideoFileTextField"; + // Also set the attribute on the selected node + string $selected[] = `ls -selection`; + if (size($selected) > 0) + { + setAttr -type "string" ($selected[0] + ".videoFile") $result[0]; + } + } +} diff --git a/src/MayaMediaPlaneNode/AETemplateMediaPlane.mel b/src/MayaMediaPlaneNode/AEMediaPlaneTemplate.mel similarity index 82% rename from src/MayaMediaPlaneNode/AETemplateMediaPlane.mel rename to src/MayaMediaPlaneNode/AEMediaPlaneTemplate.mel index ea173a8..5e5f7a2 100644 --- a/src/MayaMediaPlaneNode/AETemplateMediaPlane.mel +++ b/src/MayaMediaPlaneNode/AEMediaPlaneTemplate.mel @@ -2,14 +2,14 @@ // Attribute Editor Template for MayaMediaPlaneNode // This file customizes the appearance of MediaPlane node attributes in Maya's Attribute Editor -global proc AETemplateMediaPlane(string $nodeName) +global proc AEMediaPlaneTemplate(string $nodeName) { editorTemplate -beginScrollLayout; // Video File Section editorTemplate -beginLayout "Video File" -collapse 0; // Use custom control for video file with browse button - editorTemplate -callCustom "AETemplateMediaPlaneVideoFileCreate" "AETemplateMediaPlaneVideoFileUpdate" "videoFile"; + editorTemplate -callCustom "AEMediaPlaneTemplateVideoFileCreate" "AEMediaPlaneTemplateVideoFileUpdate" "videoFile"; editorTemplate -endLayout; // Playback Section @@ -52,7 +52,7 @@ global proc AETemplateMediaPlane(string $nodeName) } // Custom control creation for video file with browse button -global proc AETemplateMediaPlaneVideoFileCreate(string $nodeName) +global proc AEMediaPlaneTemplateVideoFileCreate(string $nodeName) { setUITemplate -pushTemplate attributeEditorTemplate; @@ -65,27 +65,27 @@ global proc AETemplateMediaPlaneVideoFileCreate(string $nodeName) text -label "videoFile"; - textField -tx "" -width 320 "AETemplateMediaPlaneVideoFileTextField"; + textField -tx "" -width 320 "AEMediaPlaneTemplateVideoFileTextField"; button -label "Browse..." -width 80 - -command "AETemplateMediaPlaneBrowseButton()" - "AETemplateMediaPlaneBrowseButton"; + -command "AEMediaPlaneTemplateBrowseButton()" + "AEMediaPlaneTemplateBrowseButton"; setUITemplate -popTemplate; } // Update callback for video file control -global proc AETemplateMediaPlaneVideoFileUpdate(string $nodeName) +global proc AEMediaPlaneTemplateVideoFileUpdate(string $nodeName) { string $value = `getAttr ($nodeName + ".videoFile")`; - textField -edit -tx $value "AETemplateMediaPlaneVideoFileTextField"; + textField -edit -tx $value "AEMediaPlaneTemplateVideoFileTextField"; } // Browse button command -global proc AETemplateMediaPlaneBrowseButton() +global proc AEMediaPlaneTemplateBrowseButton() { // Get the current text field value - string $currentFile = `textField -q -tx "AETemplateMediaPlaneVideoFileTextField"`; + string $currentFile = `textField -q -tx "AEMediaPlaneTemplateVideoFileTextField"`; // Set up file filters string $filters = "Video Files (*.mp4 *.mov *.avi *.mkv *.webm);;MP4 (*.mp4);;MOV (*.mov);;AVI (*.avi);;MKV (*.mkv);;WebM (*.webm);;All Files (*.*)"; @@ -100,7 +100,7 @@ global proc AETemplateMediaPlaneBrowseButton() // If user selected a file, update the text field if (size($result) > 0) { - textField -edit -tx $result[0] "AETemplateMediaPlaneVideoFileTextField"; + textField -edit -tx $result[0] "AEMediaPlaneTemplateVideoFileTextField"; } } diff --git a/src/MayaMediaPlaneNode/CMakeLists.txt b/src/MayaMediaPlaneNode/CMakeLists.txt index 4290310..8066041 100644 --- a/src/MayaMediaPlaneNode/CMakeLists.txt +++ b/src/MayaMediaPlaneNode/CMakeLists.txt @@ -1,4 +1,4 @@ -# MayaMediaPlaneNode Plugin CMakeLists.txt +# MediaPlaneNode Plugin CMakeLists.txt # This is a standalone project for the Maya Media Plane Node plugin cmake_minimum_required(VERSION 3.14) @@ -30,9 +30,11 @@ include_directories(${FFMPEG_INCLUDE_DIR}) set(PLUGIN_SRCS Plugin.cpp - MayaMediaPlaneNode.cpp + MediaPlaneNode.cpp + MediaPlaneDrawOverride.cpp FFmpegVideoDecoder.cpp FrameCache.cpp + MediaImagePlane.cpp ) set(MOD_FILES @@ -40,7 +42,8 @@ set(MOD_FILES ) set(MEL_SCRIPTS - AETemplateMediaPlane.mel + AEMediaPlaneTemplate.mel + AEMediaImagePlaneTemplate.mel ) set(ICON_FILES @@ -51,25 +54,25 @@ set(ICON_FILES # Build Plugin # ============================================ -add_library(MayaMediaPlaneNode MODULE ${PLUGIN_SRCS}) +add_library(MediaPlaneNode MODULE ${PLUGIN_SRCS}) -set_target_properties(MayaMediaPlaneNode PROPERTIES +set_target_properties(MediaPlaneNode PROPERTIES PREFIX "" # No prefix for Maya plugin SUFFIX ".mll" # Maya plugin extension on Windows - OUTPUT_NAME "MayaMediaPlaneNode" + OUTPUT_NAME "MediaPlaneNode" ) # Link libraries -target_link_libraries(MayaMediaPlaneNode PRIVATE +target_link_libraries(MediaPlaneNode PRIVATE ${MAYA_LIBRARIES} ${FFMPEG_LIBRARIES} ) # Set Windows-specific properties if(WIN32) - set_target_properties(MayaMediaPlaneNode PROPERTIES + set_target_properties(MediaPlaneNode PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE - COMPILE_FLAGS "/MDd" + COMPILE_FLAGS "/MDd /wd4819" ) endif() @@ -92,7 +95,7 @@ set(PLUGIN_INSTALL_DIR "plug-ins/${MAYA_VERSION_NUM}") set(BIN_INSTALL_DIR "bin") # Install plugin -install(TARGETS MayaMediaPlaneNode +install(TARGETS MediaPlaneNode RUNTIME DESTINATION ${PLUGIN_INSTALL_DIR} LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} ) @@ -126,7 +129,7 @@ endif() # Print configuration summary message(STATUS "===========================================") -message(STATUS "MayaMediaPlaneNode Configuration Summary") +message(STATUS "MediaPlaneNode Configuration Summary") message(STATUS "===========================================") message(STATUS "Maya Version: ${MAYA_VERSION}") message(STATUS "Maya Location: ${MAYA_LOCATION}") diff --git a/src/MayaMediaPlaneNode/MediaImagePlane.cpp b/src/MayaMediaPlaneNode/MediaImagePlane.cpp new file mode 100644 index 0000000..02f45a8 --- /dev/null +++ b/src/MayaMediaPlaneNode/MediaImagePlane.cpp @@ -0,0 +1,573 @@ +// MediaImagePlane.cpp +// Implementation of MediaImagePlane - Custom MPxImagePlane with FFmpeg video decoding + +#include "MediaImagePlane.h" +#include "FFmpegVideoDecoder.h" +#include "FrameCache.h" + +#include + +#include + +// Static member initialization +const MString MediaImagePlane::kNodeName = "MediaImagePlane"; +const MTypeId MediaImagePlane::kNodeId = 0x0013A5F1; + +// Attribute objects +MObject MediaImagePlane::aVideoFile; +MObject MediaImagePlane::aPlaybackRate; +MObject MediaImagePlane::aUseMayaFrameRate; +MObject MediaImagePlane::aLoop; +MObject MediaImagePlane::aPostEffectCrop; +MObject MediaImagePlane::aPostEffectResize; +MObject MediaImagePlane::aPostEffectFlip; +MObject MediaImagePlane::aCacheSize; +MObject MediaImagePlane::aClearCache; + +// Output attributes +MObject MediaImagePlane::aOutFrameWidth; +MObject MediaImagePlane::aOutFrameHeight; +MObject MediaImagePlane::aOutFrameCount; +MObject MediaImagePlane::aOutCacheHitRatio; + +// ============================================================================ +// Constructor +// ============================================================================ +MediaImagePlane::MediaImagePlane() + : MPxImagePlane() +{ + // Initialize FFmpeg decoder and frame cache + m_decoder = std::make_unique(); + m_frameCache = std::make_unique(m_maxCacheMemory, m_maxCacheFrames); + + m_videoWidth = 0; + m_videoHeight = 0; + m_videoFrameRate = 0.0; + m_videoFrameCount = 0; + + m_lastFrameIndex = -1; + m_imageDirty = true; + + // Register time change callback for playback updates + m_timeChangedCallbackId = MEventMessage::addEventCallback( + "timeChanged", timeChangedCallback, this); +} + +// ============================================================================ +// Destructor +// ============================================================================ +MediaImagePlane::~MediaImagePlane() +{ + // Remove time change callback + if (m_timeChangedCallbackId != 0) { + MMessage::removeCallback(m_timeChangedCallbackId); + m_timeChangedCallbackId = 0; + } + + // Close video file + closeVideoFile(); +} + +// ============================================================================ +// Creator function +// ============================================================================ +void* MediaImagePlane::creator() +{ + return new MediaImagePlane(); +} + +// ============================================================================ +// Initialize node attributes +// ============================================================================ +MStatus MediaImagePlane::initialize() +{ + MStatus status; + + MFnNumericAttribute numAttr; + MFnTypedAttribute typedAttr; + + // Input: Video File Path + aVideoFile = typedAttr.create("videoFile", "vf", MFnData::kString, MObject::kNullObj, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + typedAttr.setUsedAsFilename(true); + typedAttr.setStorable(true); + addAttribute(aVideoFile); + + // Input: Playback Rate (0.25 to 4.0) + aPlaybackRate = numAttr.create("playbackRate", "pr", MFnNumericData::kDouble, 1.0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(true); + numAttr.setMin(0.25); + numAttr.setMax(4.0); + addAttribute(aPlaybackRate); + + // Input: Use Maya Frame Rate + aUseMayaFrameRate = numAttr.create("useMayaFrameRate", "umf", MFnNumericData::kBoolean, true, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(true); + addAttribute(aUseMayaFrameRate); + + // Input: Loop Playback + aLoop = numAttr.create("loop", "lp", MFnNumericData::kBoolean, true, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(true); + addAttribute(aLoop); + + // Input: Post-effect Crop (x, y, width, height) + aPostEffectCrop = numAttr.create("postEffectCrop", "pec", MFnNumericData::k4Double, 0.0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(false); + addAttribute(aPostEffectCrop); + + // Input: Post-effect Resize (width, height) + aPostEffectResize = numAttr.create("postEffectResize", "per", MFnNumericData::k2Double, 0.0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(false); + addAttribute(aPostEffectResize); + + // Input: Post-effect Flip (horizontal, vertical as bitmask) + aPostEffectFlip = numAttr.create("postEffectFlip", "pef", MFnNumericData::k2Long, 0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(false); + addAttribute(aPostEffectFlip); + + // Input: Cache Size (in MB) + aCacheSize = numAttr.create("cacheSize", "cs", MFnNumericData::kInt, 256, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(true); + numAttr.setKeyable(false); + numAttr.setMin(16); + numAttr.setMax(2048); + addAttribute(aCacheSize); + + // Input: Clear Cache (trigger) + aClearCache = numAttr.create("clearCache", "cc", MFnNumericData::kBoolean, false, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(false); + numAttr.setKeyable(false); + addAttribute(aClearCache); + + // Output: Frame Width + aOutFrameWidth = numAttr.create("outFrameWidth", "ofw", MFnNumericData::kInt, 0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(false); + numAttr.setReadable(true); + addAttribute(aOutFrameWidth); + + // Output: Frame Height + aOutFrameHeight = numAttr.create("outFrameHeight", "ofh", MFnNumericData::kInt, 0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(false); + numAttr.setReadable(true); + addAttribute(aOutFrameHeight); + + // Output: Frame Count + aOutFrameCount = numAttr.create("outFrameCount", "ofc", MFnNumericData::kInt64, 0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(false); + numAttr.setReadable(true); + addAttribute(aOutFrameCount); + + // Output: Cache Hit Ratio + aOutCacheHitRatio = numAttr.create("outCacheHitRatio", "och", MFnNumericData::kDouble, 0.0, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + numAttr.setStorable(false); + numAttr.setReadable(true); + addAttribute(aOutCacheHitRatio); + + // Set up attribute affects + attributeAffects(aVideoFile, aOutFrameWidth); + attributeAffects(aVideoFile, aOutFrameHeight); + attributeAffects(aVideoFile, aOutFrameCount); + attributeAffects(aVideoFile, aOutCacheHitRatio); + + attributeAffects(aPlaybackRate, aOutCacheHitRatio); + attributeAffects(aUseMayaFrameRate, aOutCacheHitRatio); + attributeAffects(aLoop, aOutCacheHitRatio); + attributeAffects(aCacheSize, aOutCacheHitRatio); + attributeAffects(aClearCache, aOutCacheHitRatio); + attributeAffects(aPostEffectCrop, aOutCacheHitRatio); + attributeAffects(aPostEffectResize, aOutCacheHitRatio); + attributeAffects(aPostEffectFlip, aOutCacheHitRatio); + + return MStatus::kSuccess; +} + +// ============================================================================ +// Load image map - called by Maya to get the image for a specific frame +// ============================================================================ +MStatus MediaImagePlane::loadImageMap(const MString& fileName, int frame, MImage& image) +{ + MStatus status; + + // Get current time from Maya + MTime currentTime = MAnimControl::currentTime(); + double currentFrame = currentTime.asUnits(MTime::kFilm); // Film = 24 fps equivalent + + // Calculate target frame index + int64_t targetFrame = getCurrentFrameIndex(); + + // Check if we need to reopen video file (if videoFile attribute changed) + MObject thisObj = thisMObject(); + MPlug videoFilePlug(thisObj, aVideoFile); + MString videoFilePath; + videoFilePlug.getValue(videoFilePath); + + if (videoFilePath.length() > 0) { + std::string path = videoFilePath.asUTF8(); + if (path != m_currentVideoFile.asUTF8() || !m_decoder->isOpen()) { + if (!openVideoFile(path)) { + MGlobal::displayError("Failed to open video file: " + videoFilePath); + return MStatus::kFailure; + } + } + } + + // Check if frame is valid + if (!m_decoder->isOpen() || targetFrame < 0 || targetFrame >= m_videoFrameCount) { + return MStatus::kFailure; + } + + // Decode the frame + return decodeFrame(targetFrame, image); +} + +// ============================================================================ +// Get internal value - retrieve custom attribute values +// ============================================================================ +bool MediaImagePlane::getInternalValue(const MPlug& plug, MDataHandle& handle) +{ + if (plug == aVideoFile) { + handle.set(m_currentVideoFile); + return true; + } + if (plug == aPlaybackRate) { + handle.set(m_playbackRate); + return true; + } + if (plug == aUseMayaFrameRate) { + handle.set(m_useMayaFrameRate); + return true; + } + if (plug == aLoop) { + handle.set(m_loop); + return true; + } + if (plug == aPostEffectCrop) { + double cropData[4] = { m_cropX, m_cropY, m_cropW, m_cropH }; + handle.set(cropData); + return true; + } + if (plug == aPostEffectResize) { + double resizeData[2] = { m_resizeW, m_resizeH }; + handle.set(resizeData); + return true; + } + if (plug == aPostEffectFlip) { + int flipInt = (m_flipH ? 1 : 0) | (m_flipV ? 2 : 0); + handle.set(flipInt); + return true; + } + if (plug == aCacheSize) { + handle.set(static_cast(m_maxCacheMemory / (1024 * 1024))); + return true; + } + + return MPxImagePlane::getInternalValue(plug, handle); +} + +// ============================================================================ +// Set internal value - handle custom attribute changes +// ============================================================================ +bool MediaImagePlane::setInternalValue(const MPlug& plug, const MDataHandle& handle) +{ + if (plug == aVideoFile) { + MString newFile = handle.asString(); + if (newFile != m_currentVideoFile) { + m_currentVideoFile = newFile; + m_imageDirty = true; + setImageDirty(); + + // Open the new video file + if (newFile.length() > 0) { + std::string path = newFile.asUTF8(); + openVideoFile(path); + } + } + return true; + } + if (plug == aPlaybackRate) { + m_playbackRate = handle.asDouble(); + if (m_playbackRate < 0.25) m_playbackRate = 0.25; + if (m_playbackRate > 4.0) m_playbackRate = 4.0; + m_imageDirty = true; + return true; + } + if (plug == aUseMayaFrameRate) { + m_useMayaFrameRate = handle.asBool(); + m_imageDirty = true; + return true; + } + if (plug == aLoop) { + m_loop = handle.asBool(); + return true; + } + if (plug == aPostEffectCrop) { + const double* cropData = handle.asDouble4(); + m_cropX = cropData[0]; + m_cropY = cropData[1]; + m_cropW = cropData[2]; + m_cropH = cropData[3]; + m_imageDirty = true; + return true; + } + if (plug == aPostEffectResize) { + const double* resizeData = handle.asDouble2(); + m_resizeW = resizeData[0]; + m_resizeH = resizeData[1]; + m_imageDirty = true; + return true; + } + if (plug == aPostEffectFlip) { + int flipInt = handle.asInt(); + m_flipH = (flipInt & 1) != 0; + m_flipV = (flipInt & 2) != 0; + m_imageDirty = true; + return true; + } + if (plug == aCacheSize) { + int cacheSizeMB = handle.asInt(); + m_maxCacheMemory = cacheSizeMB * 1024 * 1024; + m_frameCache->setMaxMemorySize(m_maxCacheMemory); + return true; + } + if (plug == aClearCache) { + bool clearCache = handle.asBool(); + if (clearCache) { + m_frameCache->clear(); + m_imageDirty = true; + } + return true; + } + + return MPxImagePlane::setInternalValue(plug, handle); +} + +// ============================================================================ +// Open video file +// ============================================================================ +bool MediaImagePlane::openVideoFile(const std::string& filePath) +{ + std::lock_guard lock(m_decoderMutex); + + // Close existing file + closeVideoFile(); + + m_currentVideoFile = filePath.c_str(); + + if (!m_decoder->open(filePath)) { + MGlobal::displayError(MString("Failed to open video: ") + m_decoder->getLastError().c_str()); + return false; + } + + // Get video info + const auto& videoInfo = m_decoder->getVideoInfo(); + m_videoWidth = videoInfo.width; + m_videoHeight = videoInfo.height; + m_videoFrameRate = videoInfo.frameRate; + m_videoFrameCount = videoInfo.frameCount; + + // Clear cache when video changes + m_frameCache->invalidate(); + + m_imageDirty = true; + + MGlobal::displayInfo("MediaImagePlane: Opened video - " + + MString(std::to_string(m_videoWidth).c_str()) + "x" + + MString(std::to_string(m_videoHeight).c_str()) + " @" + + MString(std::to_string(m_videoFrameRate).c_str()) + " fps"); + + return true; +} + +// ============================================================================ +// Close video file +// ============================================================================ +void MediaImagePlane::closeVideoFile() +{ + std::lock_guard lock(m_decoderMutex); + + if (m_decoder && m_decoder->isOpen()) { + m_decoder->close(); + } + + m_currentVideoFile = ""; + m_videoWidth = 0; + m_videoHeight = 0; + m_videoFrameRate = 0.0; + m_videoFrameCount = 0; + m_lastFrameIndex = -1; +} + +// ============================================================================ +// Get current frame index based on Maya timeline +// ============================================================================ +int64_t MediaImagePlane::getCurrentFrameIndex() const +{ + // Get current time from Maya + MTime currentTime = MAnimControl::currentTime(); + double currentFrameValue = currentTime.asUnits(MTime::kFilm); + + // Determine effective frame rate + double effectiveFrameRate = m_videoFrameRate; + + if (m_useMayaFrameRate) { + // Get Maya's current frame rate + MTime::Unit timeUnit = currentTime.uiUnit(); + // Convert to fps (approximate) + switch (timeUnit) { + case MTime::kHours: effectiveFrameRate = 3600.0; break; + case MTime::kMinutes: effectiveFrameRate = 60.0; break; + case MTime::kSeconds: effectiveFrameRate = 1.0; break; + case MTime::kMilliseconds: effectiveFrameRate = 1000.0; break; + case MTime::kGames: effectiveFrameRate = 15.0; break; + case MTime::kFilm: effectiveFrameRate = 24.0; break; + case MTime::kNTSCFrame: effectiveFrameRate = 30.0; break; + case MTime::kNTSCField: effectiveFrameRate = 60.0; break; + case MTime::kPALFrame: effectiveFrameRate = 25.0; break; + case MTime::kPALField: effectiveFrameRate = 50.0; break; + case MTime::kShowScan: effectiveFrameRate = 48.0; break; + default: effectiveFrameRate = 24.0; break; + } + } + + if (effectiveFrameRate <= 0) { + effectiveFrameRate = 24.0; + } + + // Calculate frame index + double effectiveRate = effectiveFrameRate * m_playbackRate; + int64_t frameIndex = static_cast(currentFrameValue * effectiveRate); + + // Apply looping + if (m_loop && m_videoFrameCount > 0) { + frameIndex = frameIndex % m_videoFrameCount; + } + + // Clamp to valid range + if (frameIndex < 0) frameIndex = 0; + if (!m_loop && m_videoFrameCount > 0 && frameIndex >= m_videoFrameCount) { + frameIndex = m_videoFrameCount - 1; + } + + return frameIndex; +} + +// ============================================================================ +// Decode frame and populate MImage +// ============================================================================ +MStatus MediaImagePlane::decodeFrame(int64_t frameIndex, MImage& image) +{ + if (!m_decoder->isOpen()) { + return MStatus::kFailure; + } + + // Check if frame changed + if (frameIndex != m_lastFrameIndex) { + m_lastFrameIndex = frameIndex; + m_imageDirty = true; + } + + // Get frame from decoder + auto frameData = m_decoder->getFrame(frameIndex); + + if (!frameData.data) { + return MStatus::kFailure; + } + + // Get image dimensions + unsigned int width = static_cast(frameData.width); + unsigned int height = static_cast(frameData.height); + + // Create MImage with the frame data + // MImage expects RGBA format + image.create(width, height, 4, MImage::kByte); + + // Copy pixel data to MImage + unsigned char* pixels = image.pixels(); + if (pixels && frameData.data) { + // FFmpeg decoder outputs RGBA, copy directly + memcpy(pixels, frameData.data, width * height * 4); + } + + // Apply post-effects if needed + applyPostEffects(image); + + return MStatus::kSuccess; +} + +// ============================================================================ +// Apply post-effects to the image +// ============================================================================ +void MediaImagePlane::applyPostEffects(MImage& image) +{ + unsigned int width, height; + image.getSize(width, height); + + // Apply flip if needed + if (m_flipH || m_flipV) { + // Create temporary buffer + std::vector tempPixels(width * height * 4); + unsigned char* pixels = image.pixels(); + + for (unsigned int y = 0; y < height; y++) { + for (unsigned int x = 0; x < width; x++) { + unsigned int srcY = m_flipV ? (height - 1 - y) : y; + unsigned int srcX = m_flipH ? (width - 1 - x) : x; + + unsigned int srcIdx = (srcY * width + srcX) * 4; + unsigned int dstIdx = (y * width + x) * 4; + + tempPixels[dstIdx + 0] = pixels[srcIdx + 0]; + tempPixels[dstIdx + 1] = pixels[srcIdx + 1]; + tempPixels[dstIdx + 2] = pixels[srcIdx + 2]; + tempPixels[dstIdx + 3] = pixels[srcIdx + 3]; + } + } + + memcpy(pixels, tempPixels.data(), width * height * 4); + } + + // Note: Crop and resize would require more complex implementation + // For now, Maya's image plane handles these through its native attributes +} + +// ============================================================================ +// Set image dirty flag +// ============================================================================ +void MediaImagePlane::setImageDirty() +{ + // This is handled by Maya's internal mechanism for image planes + // Setting the image dirty forces Maya to call loadImageMap again + m_imageDirty = true; +} + +// ============================================================================ +// Time changed callback +// ============================================================================ +void MediaImagePlane::timeChangedCallback(void* clientData) +{ + MediaImagePlane* node = static_cast(clientData); + if (node) { + node->setImageDirty(); + } +} + +// Note: Plugin initialization and uninitialization are handled in Plugin.cpp +// This file only contains the node implementation diff --git a/src/MayaMediaPlaneNode/MediaImagePlane.h b/src/MayaMediaPlaneNode/MediaImagePlane.h new file mode 100644 index 0000000..2cc0bc6 --- /dev/null +++ b/src/MayaMediaPlaneNode/MediaImagePlane.h @@ -0,0 +1,225 @@ +// MediaImagePlane.h +// Maya Image Plane Node - Custom MPxImagePlane implementation with FFmpeg video decoding +// This node integrates with Maya's image plane system to display video frames + +#ifndef MEDIA_IMAGE_PLANE_H +#define MEDIA_IMAGE_PLANE_H + +// Maya API headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declare MFnPlugin +class MFnPlugin; + +#include +#include +#include + +// Forward declarations +namespace MediaPlane { + class FFmpegVideoDecoder; + class FrameCache; +} + +/** + * MediaImagePlane - Custom image plane node for video playback + * + * This class extends MPxImagePlane to provide video playback functionality + * using FFmpeg for decoding and a frame cache for performance optimization. + * + * Features: + * - MP4 video playback via FFmpeg + * - Synchronization with Maya timeline + * - Configurable playback rate + * - Frame caching for smooth playback + * - Post-effects (crop, resize, flip) + * + * Usage: + * createNode mediaImagePlane; + * imagePlane -edit -camera "persp" + */ +class MediaImagePlane : public MPxImagePlane +{ +public: + /** + * Node type name and ID + */ + static const MString kNodeName; + static const MTypeId kNodeId; + + /** + * Constructor + */ + MediaImagePlane(); + + /** + * Destructor + */ + virtual ~MediaImagePlane(); + + /** + * Load the image map for the current frame + * This is the main method called by Maya to get the image data + * @param fileName The source file/URL (ignored, we use our own videoFile attribute) + * @param frame The frame number to load + * @param image The MImage to populate with pixel data + * @return MStatus::kSuccess if successful + */ + MStatus loadImageMap(const MString& fileName, int frame, MImage& image) override; + + /** + * Get internal attribute value + * @param plug The attribute plug + * @param handle The data handle to write the value to + * @return true if the attribute was handled + */ + bool getInternalValue(const MPlug& plug, MDataHandle& handle) override; + + /** + * Set internal attribute value + * @param plug The attribute plug + * @param handle The data handle containing the new value + * @return true if the attribute was handled + */ + bool setInternalValue(const MPlug& plug, const MDataHandle& handle) override; + + /** + * Creator function for Maya plugin system + * @return Pointer to new MediaImagePlane instance + */ + static void* creator(); + + /** + * Initialize node attributes + * @return MStatus::kSuccess if successful + */ + static MStatus initialize(); + +private: + /** + * Open a video file + * @param filePath Path to the video file + * @return true if successful + */ + bool openVideoFile(const std::string& filePath); + + /** + * Close the current video file + */ + void closeVideoFile(); + + /** + * Get the current frame index based on Maya's timeline + * @return Current frame index + */ + int64_t getCurrentFrameIndex() const; + + /** + * Decode a frame and apply post-effects + * @param frameIndex Index of the frame to decode + * @param image MImage to populate with pixel data + * @return MStatus::kSuccess if successful + */ + MStatus decodeFrame(int64_t frameIndex, MImage& image); + + /** + * Apply post-effects to the image + * @param image The image to modify + */ + void applyPostEffects(MImage& image); + + /** + * Mark the image as dirty to force reload + */ + void setImageDirty(); + + /** + * Time changed callback for playback + * @param clientData Pointer to the MediaImagePlane instance + */ + static void timeChangedCallback(void* clientData); + +private: + /** + * Static attribute objects + */ + static MObject aVideoFile; + static MObject aPlaybackRate; + static MObject aUseMayaFrameRate; + static MObject aLoop; + static MObject aPostEffectCrop; + static MObject aPostEffectResize; + static MObject aPostEffectFlip; + static MObject aCacheSize; + static MObject aClearCache; + + /** + * Output attributes for displaying info + */ + static MObject aOutFrameWidth; + static MObject aOutFrameHeight; + static MObject aOutFrameCount; + static MObject aOutCacheHitRatio; + +private: + // FFmpeg decoder + std::unique_ptr m_decoder; + + // Frame cache + std::unique_ptr m_frameCache; + + // Current video file path + MString m_currentVideoFile; + + // Cached video info + int m_videoWidth = 0; + int m_videoHeight = 0; + double m_videoFrameRate = 0.0; + int64_t m_videoFrameCount = 0; + + // Playback settings + double m_playbackRate = 1.0; + bool m_useMayaFrameRate = true; + bool m_loop = true; + + // Post-effect parameters + double m_cropX = 0, m_cropY = 0, m_cropW = 0, m_cropH = 0; + double m_resizeW = 0, m_resizeH = 0; + bool m_flipH = false, m_flipV = false; + + // Cache settings + size_t m_maxCacheMemory = 256 * 1024 * 1024; // 256MB default + size_t m_maxCacheFrames = 100; + + // Thread safety + std::mutex m_decoderMutex; + std::mutex m_cacheMutex; + + // Callback for time changes + MCallbackId m_timeChangedCallbackId = 0; + + // Last frame for change detection + int64_t m_lastFrameIndex = -1; + + // Image dirty flag + bool m_imageDirty = true; +}; + +// Type aliases for convenience +using MediaImagePlanePtr = MediaImagePlane*; + +#endif // MEDIA_IMAGE_PLANE_H diff --git a/src/MayaMediaPlaneNode/MediaPlaneDrawOverride.cpp b/src/MayaMediaPlaneNode/MediaPlaneDrawOverride.cpp new file mode 100644 index 0000000..064dea7 --- /dev/null +++ b/src/MayaMediaPlaneNode/MediaPlaneDrawOverride.cpp @@ -0,0 +1,244 @@ +// MediaPlaneDrawOverride.cpp +// Viewport 2.0 draw override implementation for MediaPlane node +// Provides rendering of video frames in Maya's Viewport 2.0 + +#include "MediaPlaneDrawOverride.h" +#include "MediaPlaneNode.h" + +#include +#include +#include +#include + +// ============================================================================ +// MediaPlaneDrawData Implementation +// ============================================================================ + +MediaPlaneDrawData::MediaPlaneDrawData() + : MUserData(false) // Don't delete after draw - managed by Maya + , fFrameWidth(0) + , fFrameHeight(0) + , fIsValid(false) + , fCropX(0), fCropY(0), fCropW(0), fCropH(0) + , fResizeW(0), fResizeH(0) + , fFlipH(false), fFlipV(false) +{ +} + +MediaPlaneDrawData::~MediaPlaneDrawData() +{ +} + +// ============================================================================ +// MediaPlaneDrawOverride Implementation +// ============================================================================ + +MediaPlaneDrawOverride::MediaPlaneDrawOverride(const MObject& obj) + : MHWRender::MPxDrawOverride(obj, nullptr, false) // NULL draw callback, using addUIDrawables + , fLastFrameIndex(-1) +{ +} + +MediaPlaneDrawOverride::~MediaPlaneDrawOverride() +{ +} + +// --------------------------------------------------------------------------- +// Factory function - called by Maya's draw registry +// --------------------------------------------------------------------------- +MHWRender::MPxDrawOverride* MediaPlaneDrawOverride::creator(const MObject& obj) +{ + return new MediaPlaneDrawOverride(obj); +} + +// --------------------------------------------------------------------------- +// supportedDrawAPIs - Returns supported graphics APIs +// --------------------------------------------------------------------------- +MHWRender::DrawAPI MediaPlaneDrawOverride::supportedDrawAPIs() const +{ + // Support both OpenGL and DirectX 11 + return MHWRender::kOpenGL | MHWRender::kDirectX11; +} + +// --------------------------------------------------------------------------- +// hasUIDrawables - Returns whether we use addUIDrawables for drawing +// --------------------------------------------------------------------------- +bool MediaPlaneDrawOverride::hasUIDrawables() const +{ + return true; +} + +// --------------------------------------------------------------------------- +// isBounded - Check if object has a bounding box +// --------------------------------------------------------------------------- +bool MediaPlaneDrawOverride::isBounded(const MDagPath& objPath, const MDagPath& cameraPath) const +{ + return true; +} + +// --------------------------------------------------------------------------- +// boundingBox - Get the bounding box of the object +// --------------------------------------------------------------------------- +MBoundingBox MediaPlaneDrawOverride::boundingBox( + const MDagPath& objPath, + const MDagPath& cameraPath) const +{ + // Return default unit box centered at origin + // Actual size is determined by the transform node in the scene + MBoundingBox bbox; + bbox.expand(MPoint(-0.5, -0.5, 0.0)); + bbox.expand(MPoint(0.5, 0.5, 0.0)); + return bbox; +} + +// --------------------------------------------------------------------------- +// prepareForDraw - Prepare data before drawing +// This is called each frame to get the latest data from the Maya node +// --------------------------------------------------------------------------- +MUserData* MediaPlaneDrawOverride::prepareForDraw( + const MDagPath& objPath, + const MDagPath& cameraPath, + const MHWRender::MFrameContext& frameContext, + MUserData* oldData) +{ + MStatus status; + + // Create or reuse draw data + MediaPlaneDrawData* drawData = dynamic_cast(oldData); + if (!drawData) { + drawData = new MediaPlaneDrawData(); + } + + // Get the MediaPlaneNode from the DAG path + MObject nodeObj = objPath.node(&status); + if (!status || nodeObj.isNull()) { + return drawData; + } + + MFnDependencyNode depNode(nodeObj, &status); + if (!status) { + return drawData; + } + + // Check if this is a MediaPlane node + MString nodeType = depNode.typeName(); + if (nodeType != "MediaPlane") { + // Try to find the MediaPlane node by traversing up the DAG + MDagPath mediaPlanePath = objPath; + bool foundMediaPlane = false; + + for (unsigned int i = 0; i < objPath.length(); i++) { + MObject parentNode = mediaPlanePath.node(&status); + if (!status) break; + + MFnDependencyNode parentDepNode(parentNode, &status); + if (!status) break; + + if (parentDepNode.typeName() == "MediaPlane") { + nodeObj = parentNode; + foundMediaPlane = true; + break; + } + + mediaPlanePath.pop(); + } + + if (!foundMediaPlane) { + return drawData; + } + + // Re-get the dependency node with correct object + depNode.setObject(nodeObj); + } + + // Get output attributes from the MediaPlane node + // outFrameWidth + MPlug widthPlug = depNode.findPlug("outFrameWidth", false, &status); + if (status) { + drawData->fFrameWidth = widthPlug.asInt(); + } + + // outFrameHeight + MPlug heightPlug = depNode.findPlug("outFrameHeight", false, &status); + if (status) { + drawData->fFrameHeight = heightPlug.asInt(); + } + + // outIsValid - whether frame data is available + MPlug validPlug = depNode.findPlug("outIsValid", false, &status); + if (status) { + drawData->fIsValid = validPlug.asBool(); + } + + return drawData; +} + +// --------------------------------------------------------------------------- +// addUIDrawables - Draw the video frame +// This is called each frame to render the UI drawables +// --------------------------------------------------------------------------- +void MediaPlaneDrawOverride::addUIDrawables( + const MDagPath& objPath, + MHWRender::MUIDrawManager& drawManager, + const MHWRender::MFrameContext& frameContext, + const MUserData* data) +{ + const MediaPlaneDrawData* drawData = dynamic_cast(data); + if (!drawData) { + return; + } + + // Begin drawing + drawManager.beginDrawable(); + + // Get the object's transform matrix for positioning + MStatus status; + MFnDagNode dagNode(objPath, &status); + if (!status) { + drawManager.endDrawable(); + return; + } + + MMatrix worldMatrix = dagNode.transformationMatrix(&status); + if (!status) { + drawManager.endDrawable(); + return; + } + + if (drawData->fIsValid && drawData->fFrameWidth > 0 && drawData->fFrameHeight > 0) { + // Valid frame - draw a rectangle representing the video frame + // In a full implementation, this would render the actual video texture + // using MShaderManager and MTexture + + // Set color for valid frame (light gray) + drawManager.setColor(MColor(0.5f, 0.5f, 0.5f, 0.3f)); + drawManager.setLineWidth(1.0f); + } else { + // No valid frame - draw placeholder + drawManager.setColor(MColor(0.2f, 0.2f, 0.2f, 0.5f)); + drawManager.setLineWidth(1.0f); + } + + // Define corners in local space + MPoint corners[4]; + corners[0] = MPoint(-0.5, -0.5, 0); + corners[1] = MPoint(0.5, -0.5, 0); + corners[2] = MPoint(0.5, 0.5, 0); + corners[3] = MPoint(-0.5, 0.5, 0); + + // Transform corners by world matrix + for (int i = 0; i < 4; i++) { + corners[i] = corners[i] * worldMatrix; + } + + // Draw the quad outline + drawManager.line(corners[0], corners[1]); + drawManager.line(corners[1], corners[2]); + drawManager.line(corners[2], corners[3]); + drawManager.line(corners[3], corners[0]); + + // Draw diagonal for visual feedback + drawManager.line(corners[0], corners[2]); + + drawManager.endDrawable(); +} diff --git a/src/MayaMediaPlaneNode/MediaPlaneDrawOverride.h b/src/MayaMediaPlaneNode/MediaPlaneDrawOverride.h new file mode 100644 index 0000000..9c5ebf5 --- /dev/null +++ b/src/MayaMediaPlaneNode/MediaPlaneDrawOverride.h @@ -0,0 +1,114 @@ +// MediaPlaneDrawOverride.h +// Viewport 2.0 draw override for MediaPlane node +// Provides rendering of video frames in Maya's Viewport 2.0 + +#ifndef MEDIA_PLANE_DRAW_OVERRIDE_H +#define MEDIA_PLANE_DRAW_OVERRIDE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// Maya Hardware Rendering +#include +#include +#include + +// Forward declaration +class MediaPlaneNode; + +/** + * MediaPlaneDrawData - User data class for passing frame data to draw override + * This data is prepared in prepareForDraw() and used in addUIDrawables() + */ +class MediaPlaneDrawData : public MUserData +{ +public: + MediaPlaneDrawData(); + ~MediaPlaneDrawData() override; + + int fFrameWidth; + int fFrameHeight; + bool fIsValid; + + // Post-effect parameters (for future use) + double fCropX, fCropY, fCropW, fCropH; + double fResizeW, fResizeH; + bool fFlipH, fFlipV; +}; + +/** + * MediaPlaneDrawOverride - Viewport 2.0 draw override for video frame display + * + * This class implements MPxDrawOverride to render video frames in Maya's + * Viewport 2.0 using the addUIDrawables approach (similar to transformDrawNode.cpp example). + */ +class MediaPlaneDrawOverride : public MHWRender::MPxDrawOverride +{ +public: + /** + * Factory function for Maya's draw registry + * @param obj The Maya object to create the override for + * @return Pointer to new MediaPlaneDrawOverride instance + */ + static MHWRender::MPxDrawOverride* creator(const MObject& obj); + + /** + * Destructor + */ + ~MediaPlaneDrawOverride() override; + + /** + * Returns supported draw APIs (OpenGL and DirectX 11) + */ + MHWRender::DrawAPI supportedDrawAPIs() const override; + + /** + * Returns whether this override provides UI drawables + */ + bool hasUIDrawables() const override; + + /** + * Check if the object is bounded + */ + bool isBounded(const MDagPath& objPath, const MDagPath& cameraPath) const override; + + /** + * Get bounding box + */ + MBoundingBox boundingBox(const MDagPath& objPath, const MDagPath& cameraPath) const override; + + /** + * Prepare data for drawing - retrieves frame info from Maya node + */ + MUserData* prepareForDraw( + const MDagPath& objPath, + const MDagPath& cameraPath, + const MHWRender::MFrameContext& frameContext, + MUserData* oldData) override; + + /** + * Add UI drawables - renders the video frame + */ + void addUIDrawables( + const MDagPath& objPath, + MHWRender::MUIDrawManager& drawManager, + const MHWRender::MFrameContext& frameContext, + const MUserData* data) override; + +private: + /** + * Private constructor - use creator() instead + */ + MediaPlaneDrawOverride(const MObject& obj); + + // Last frame index for change detection + int64_t fLastFrameIndex; +}; + +#endif // MEDIA_PLANE_DRAW_OVERRIDE_H diff --git a/src/MayaMediaPlaneNode/MayaMediaPlaneNode.cpp b/src/MayaMediaPlaneNode/MediaPlaneNode.cpp similarity index 88% rename from src/MayaMediaPlaneNode/MayaMediaPlaneNode.cpp rename to src/MayaMediaPlaneNode/MediaPlaneNode.cpp index 6a2974a..79068fa 100644 --- a/src/MayaMediaPlaneNode/MayaMediaPlaneNode.cpp +++ b/src/MayaMediaPlaneNode/MediaPlaneNode.cpp @@ -1,4 +1,4 @@ -#include "MayaMediaPlaneNode.h" +#include "MediaPlaneNode.h" #include "FFmpegVideoDecoder.h" #include "FrameCache.h" @@ -12,35 +12,35 @@ MStatus initializeMediaPlaneNode(); // Static member initialization -const MString MayaMediaPlaneNode::kNodeName = "MediaPlane"; -const MTypeId MayaMediaPlaneNode::kNodeId = 0x0013A5F0; -const MString MayaMediaPlaneNode::kNodeClassification = "texture"; +const MString MediaPlaneNode::kNodeName = "MediaPlane"; +const MTypeId MediaPlaneNode::kNodeId = 0x0013A5F0; +const MString MediaPlaneNode::kNodeClassification = "texture"; // Attribute objects -MObject MayaMediaPlaneNode::aVideoFile; -MObject MayaMediaPlaneNode::aCurrentTime; -MObject MayaMediaPlaneNode::aFrameRate; -MObject MayaMediaPlaneNode::aPlaybackRate; -MObject MayaMediaPlaneNode::aUseMayaFrameRate; -MObject MayaMediaPlaneNode::aLoop; -MObject MayaMediaPlaneNode::aPostEffectCrop; -MObject MayaMediaPlaneNode::aPostEffectResize; -MObject MayaMediaPlaneNode::aPostEffectFlip; -MObject MayaMediaPlaneNode::aCachePolicy; -MObject MayaMediaPlaneNode::aCacheSize; -MObject MayaMediaPlaneNode::aClearCache; +MObject MediaPlaneNode::aVideoFile; +MObject MediaPlaneNode::aCurrentTime; +MObject MediaPlaneNode::aFrameRate; +MObject MediaPlaneNode::aPlaybackRate; +MObject MediaPlaneNode::aUseMayaFrameRate; +MObject MediaPlaneNode::aLoop; +MObject MediaPlaneNode::aPostEffectCrop; +MObject MediaPlaneNode::aPostEffectResize; +MObject MediaPlaneNode::aPostEffectFlip; +MObject MediaPlaneNode::aCachePolicy; +MObject MediaPlaneNode::aCacheSize; +MObject MediaPlaneNode::aClearCache; // Output attributes -MObject MayaMediaPlaneNode::aOutFrameData; -MObject MayaMediaPlaneNode::aOutFrameWidth; -MObject MayaMediaPlaneNode::aOutFrameHeight; -MObject MayaMediaPlaneNode::aOutFrameTimestamp; -MObject MayaMediaPlaneNode::aOutFrameCount; -MObject MayaMediaPlaneNode::aOutIsValid; -MObject MayaMediaPlaneNode::aOutCacheHitRatio; +MObject MediaPlaneNode::aOutFrameData; +MObject MediaPlaneNode::aOutFrameWidth; +MObject MediaPlaneNode::aOutFrameHeight; +MObject MediaPlaneNode::aOutFrameTimestamp; +MObject MediaPlaneNode::aOutFrameCount; +MObject MediaPlaneNode::aOutIsValid; +MObject MediaPlaneNode::aOutCacheHitRatio; // Constructor -MayaMediaPlaneNode::MayaMediaPlaneNode() +MediaPlaneNode::MediaPlaneNode() { m_decoder = std::make_unique(); m_frameCache = std::make_unique(m_maxCacheMemory, m_maxCacheFrames); @@ -54,19 +54,19 @@ MayaMediaPlaneNode::MayaMediaPlaneNode() } // Destructor -MayaMediaPlaneNode::~MayaMediaPlaneNode() +MediaPlaneNode::~MediaPlaneNode() { closeVideoFile(); } // Creator function -void* MayaMediaPlaneNode::creator() +void* MediaPlaneNode::creator() { - return new MayaMediaPlaneNode(); + return new MediaPlaneNode(); } // Initialize node attributes -MStatus MayaMediaPlaneNode::initialize() +MStatus MediaPlaneNode::initialize() { MStatus status; @@ -74,7 +74,7 @@ MStatus MayaMediaPlaneNode::initialize() MFnTypedAttribute typedAttr; // Input Attributes - aVideoFile = typedAttr.create("videoFile", "vf", MFnData::kString, &status); + aVideoFile = typedAttr.create("videoFile", "vf", MFnData::kString, MObject::kNullObj, &status); CHECK_MSTATUS_AND_RETURN_IT(status); typedAttr.setUsedAsFilename(true); typedAttr.setStorable(true); @@ -203,7 +203,7 @@ MStatus MayaMediaPlaneNode::initialize() } // Set up attribute dependencies -MStatus MayaMediaPlaneNode::attributeAffectsSetup() +MStatus MediaPlaneNode::attributeAffectsSetup() { attributeAffects(aVideoFile, aOutFrameWidth); attributeAffects(aVideoFile, aOutFrameHeight); @@ -242,16 +242,16 @@ MStatus MayaMediaPlaneNode::attributeAffectsSetup() return MS::kSuccess; } -void MayaMediaPlaneNode::postConstructor() {} +void MediaPlaneNode::postConstructor() {} -MStatus MayaMediaPlaneNode::destroy() +MStatus MediaPlaneNode::destroy() { closeVideoFile(); return MS::kSuccess; } // Compute method -MStatus MayaMediaPlaneNode::compute(const MPlug& plug, MDataBlock& dataBlock) +MStatus MediaPlaneNode::compute(const MPlug& plug, MDataBlock& dataBlock) { if (plug == aOutFrameData || plug == aOutFrameWidth || plug == aOutFrameHeight || plug == aOutFrameTimestamp || plug == aOutFrameCount || plug == aOutIsValid || @@ -269,7 +269,7 @@ MStatus MayaMediaPlaneNode::compute(const MPlug& plug, MDataBlock& dataBlock) } // Compute video frame -MStatus MayaMediaPlaneNode::computeVideoFrame(MDataBlock& dataBlock) +MStatus MediaPlaneNode::computeVideoFrame(MDataBlock& dataBlock) { MStatus status; @@ -390,7 +390,7 @@ MStatus MayaMediaPlaneNode::computeVideoFrame(MDataBlock& dataBlock) } // Open video file -MStatus MayaMediaPlaneNode::openVideoFile(const MString& filePath) +MStatus MediaPlaneNode::openVideoFile(const MString& filePath) { MStatus status; std::lock_guard lock(m_decoderMutex); @@ -417,7 +417,7 @@ MStatus MayaMediaPlaneNode::openVideoFile(const MString& filePath) } // Close video file -void MayaMediaPlaneNode::closeVideoFile() +void MediaPlaneNode::closeVideoFile() { std::lock_guard lock(m_decoderMutex); if (m_decoder && m_decoder->isOpen()) { @@ -431,7 +431,7 @@ void MayaMediaPlaneNode::closeVideoFile() } // Calculate target frame -int64_t MayaMediaPlaneNode::calculateTargetFrame(double currentTime, double frameRate, double playbackRate, bool loop) +int64_t MediaPlaneNode::calculateTargetFrame(double currentTime, double frameRate, double playbackRate, bool loop) { if (frameRate <= 0 || playbackRate <= 0) return 0; double effectiveFrameRate = frameRate * playbackRate; @@ -442,9 +442,9 @@ int64_t MayaMediaPlaneNode::calculateTargetFrame(double currentTime, double fram return frame; } -MStatus MayaMediaPlaneNode::getFrame(int64_t frameIndex, MDataHandle& frameDataHandle) { return MS::kSuccess; } +MStatus MediaPlaneNode::getFrame(int64_t frameIndex, MDataHandle& frameDataHandle) { return MS::kSuccess; } -MStatus MayaMediaPlaneNode::applyPostEffects(MDataBlock& dataBlock) +MStatus MediaPlaneNode::applyPostEffects(MDataBlock& dataBlock) { MStatus status; MDataHandle cropHandle = dataBlock.inputValue(aPostEffectCrop, &status); @@ -480,7 +480,7 @@ MStatus MayaMediaPlaneNode::applyPostEffects(MDataBlock& dataBlock) return MS::kSuccess; } -MStatus MayaMediaPlaneNode::updateCacheStats(MDataBlock& dataBlock) +MStatus MediaPlaneNode::updateCacheStats(MDataBlock& dataBlock) { MStatus status; auto stats = m_frameCache->getStats(); @@ -489,12 +489,12 @@ MStatus MayaMediaPlaneNode::updateCacheStats(MDataBlock& dataBlock) return MS::kSuccess; } -bool MayaMediaPlaneNode::isValidFrameIndex(int64_t index) const +bool MediaPlaneNode::isValidFrameIndex(int64_t index) const { return index >= 0 && index < m_videoFrameCount; } -double MayaMediaPlaneNode::getMayaFrameRate() const +double MediaPlaneNode::getMayaFrameRate() const { MTime time = MAnimControl::currentTime(); MTime::Unit timeUnit = time.uiUnit(); @@ -516,7 +516,7 @@ double MayaMediaPlaneNode::getMayaFrameRate() const return fps; } -void MayaMediaPlaneNode::processPostEffects(FrameDataWrapper& frame, +void MediaPlaneNode::processPostEffects(FrameDataWrapper& frame, double cropX, double cropY, double cropW, double cropH, double resizeW, double resizeH, bool flipH, bool flipV) { diff --git a/src/MayaMediaPlaneNode/MayaMediaPlaneNode.h b/src/MayaMediaPlaneNode/MediaPlaneNode.h similarity index 96% rename from src/MayaMediaPlaneNode/MayaMediaPlaneNode.h rename to src/MayaMediaPlaneNode/MediaPlaneNode.h index f172004..ceb1f45 100644 --- a/src/MayaMediaPlaneNode/MayaMediaPlaneNode.h +++ b/src/MayaMediaPlaneNode/MediaPlaneNode.h @@ -1,5 +1,5 @@ -#ifndef MAYA_MEDIA_PLANE_NODE_H -#define MAYA_MEDIA_PLANE_NODE_H +#ifndef MEDIA_PLANE_NODE_H +#define MEDIA_PLANE_NODE_H // Maya API headers #include @@ -95,7 +95,7 @@ struct FrameDataWrapper { * - Post-effects (crop, resize, flip) * - Frame caching for smooth playback */ -class MayaMediaPlaneNode : public MPxNode { +class MediaPlaneNode : public MPxNode { public: // Node type name and ID static const MString kNodeName; @@ -105,8 +105,8 @@ public: static const MString kNodeClassification; // Constructor / Destructor - MayaMediaPlaneNode(); - virtual ~MayaMediaPlaneNode(); + MediaPlaneNode(); + virtual ~MediaPlaneNode(); // Maya API overrides virtual MStatus compute(const MPlug& plug, MDataBlock& dataBlock) override; @@ -215,4 +215,4 @@ private: bool m_lastFlipH = false, m_lastFlipV = false; }; -#endif // MAYA_MEDIA_PLANE_NODE_H +#endif // MEDIA_PLANE_NODE_H diff --git a/src/MayaMediaPlaneNode/Plugin.cpp b/src/MayaMediaPlaneNode/Plugin.cpp index ce275b1..e8b8e6a 100644 --- a/src/MayaMediaPlaneNode/Plugin.cpp +++ b/src/MayaMediaPlaneNode/Plugin.cpp @@ -1,53 +1,113 @@ // Plugin.cpp -// Maya Plugin entry point for MayaMediaPlaneNode +// Maya Plugin entry point for MediaPlaneNode // This file contains the plugin initialization and uninitialization functions -#include "MayaMediaPlaneNode.h" +#include "MediaPlaneNode.h" +#include "MediaPlaneDrawOverride.h" +#include "MediaImagePlane.h" + #include #include +#include -// Forward declaration +// Forward declarations MStatus initializeMediaPlaneNode(); +MStatus initializeMediaImagePlane(); +// ============================================================================ // Plugin initialization - non-member function required by Maya +// ============================================================================ MStatus initializePlugin(MObject obj) { MStatus status; MFnPlugin plugin(obj, "MediaPlane", "1.0", "Any", &status); if (!status) return status; + // Register the Maya node status = plugin.registerNode("MediaPlane", - MayaMediaPlaneNode::kNodeId, - MayaMediaPlaneNode::creator, + MediaPlaneNode::kNodeId, + MediaPlaneNode::creator, initializeMediaPlaneNode, MPxNode::kDependNode, - &MayaMediaPlaneNode::kNodeClassification); + &MediaPlaneNode::kNodeClassification); if (!status) return status; - // Display info about AE Template - MGlobal::displayInfo("MayaMediaPlaneNode plugin loaded"); + // Register the draw override for Viewport 2.0 + status = MDrawRegistry::registerDrawOverrideCreator( + MediaPlaneNode::kNodeClassification, + "MediaPlaneDrawOverride", + MediaPlaneDrawOverride::creator); + if (!status) { + status.perror("registerDrawOverrideCreator"); + return status; + } + + // Register the MediaImagePlane (MPxImagePlane implementation) + status = plugin.registerNode( + MediaImagePlane::kNodeName, + MediaImagePlane::kNodeId, + MediaImagePlane::creator, + initializeMediaImagePlane, + MPxNode::kImagePlaneNode); + if (!status) { + status.perror("registerNode mediaImagePlane"); + return status; + } + + // Display info + MGlobal::displayInfo("MediaPlaneNode plugin loaded"); + MGlobal::displayInfo("MediaPlaneNode: createNode MediaPlane"); + MGlobal::displayInfo("MediaImagePlane: createNode mediaImagePlane; imagePlane -edit -camera persp "); + MGlobal::displayInfo("Viewport 2.0 support enabled"); MGlobal::displayInfo("For Attribute Editor customization, source: source \"AETemplate_MediaPlane.mel\""); return MS::kSuccess; } +// ============================================================================ // Plugin uninitialization - non-member function required by Maya +// ============================================================================ MStatus uninitializePlugin(MObject obj) { MStatus status; MFnPlugin plugin(obj); if (!status) return status; - status = plugin.deregisterNode(MayaMediaPlaneNode::kNodeId); + // Deregister draw override + status = MDrawRegistry::deregisterDrawOverrideCreator( + MediaPlaneNode::kNodeClassification, + "MediaPlaneDrawOverride"); + if (!status) { + status.perror("deregisterDrawOverrideCreator"); + } + + status = plugin.deregisterNode(MediaPlaneNode::kNodeId); if (!status) return status; - MGlobal::displayInfo("MayaMediaPlaneNode plugin unloaded"); + // Deregister MediaImagePlane + status = plugin.deregisterNode(MediaImagePlane::kNodeId); + if (!status) { + status.perror("deregisterNode mediaImagePlane"); + } + + MGlobal::displayInfo("MediaPlaneNode plugin unloaded"); return MS::kSuccess; } +// ============================================================================ // Non-member initialize function for Maya node attributes registration +// ============================================================================ MStatus initializeMediaPlaneNode() { - MayaMediaPlaneNode node; + MediaPlaneNode node; + return node.initialize(); +} + +// ============================================================================ +// Non-member initialize function for MediaImagePlane attributes registration +// ============================================================================ +MStatus initializeMediaImagePlane() +{ + MediaImagePlane node; return node.initialize(); }