Add MediaImagePlane Node
This commit is contained in:
parent
f513c9010e
commit
2565b8ffd6
|
|
@ -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/
|
||||
6
AGENT.md
6
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 測試
|
||||
|
||||
|
||||
|
|
|
|||
79
README.md
79
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" <nodeName>;
|
||||
```
|
||||
|
||||
### 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\<username>\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 |
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,106 @@
|
|||
// AEMediaImagePlaneTemplate.mel
|
||||
// Attribute Editor Template for MediaImagePlane
|
||||
// This file customizes the appearance of MediaImagePlane node attributes in Maya's Attribute Editor
|
||||
|
||||
global proc AEMediaImagePlaneTemplate(string $nodeName)
|
||||
{
|
||||
editorTemplate -beginScrollLayout;
|
||||
|
||||
// Video File Section
|
||||
editorTemplate -beginLayout "Video File" -collapse 0;
|
||||
// Use custom control for video file with browse button
|
||||
editorTemplate -callCustom "AEMediaImagePlaneTemplateVideoFileCreate" "AEMediaImagePlaneTemplateVideoFileUpdate" "videoFile";
|
||||
editorTemplate -endLayout;
|
||||
|
||||
// Playback Section
|
||||
editorTemplate -beginLayout "Playback" -collapse 0;
|
||||
editorTemplate -addControl "playbackRate";
|
||||
editorTemplate -addControl "useMayaFrameRate";
|
||||
editorTemplate -addControl "loop";
|
||||
editorTemplate -endLayout;
|
||||
|
||||
// Post Effects Section
|
||||
editorTemplate -beginLayout "Post Effects" -collapse 0;
|
||||
editorTemplate -addControl "postEffectCrop";
|
||||
editorTemplate -addControl "postEffectResize";
|
||||
editorTemplate -addControl "postEffectFlip";
|
||||
editorTemplate -endLayout;
|
||||
|
||||
// Cache Section
|
||||
editorTemplate -beginLayout "Cache" -collapse 0;
|
||||
editorTemplate -addControl "cacheSize";
|
||||
editorTemplate -addControl "clearCache";
|
||||
editorTemplate -addControl "outCacheHitRatio";
|
||||
editorTemplate -endLayout;
|
||||
|
||||
// Output Section
|
||||
editorTemplate -beginLayout "Output" -collapse 0;
|
||||
editorTemplate -addControl "outFrameWidth";
|
||||
editorTemplate -addControl "outFrameHeight";
|
||||
editorTemplate -addControl "outFrameCount";
|
||||
editorTemplate -endLayout;
|
||||
|
||||
// Add AE call to the base class
|
||||
editorTemplate -addExtraControls;
|
||||
|
||||
editorTemplate -endScrollLayout;
|
||||
}
|
||||
|
||||
// Custom control creation for video file with browse button
|
||||
global proc AEMediaImagePlaneTemplateVideoFileCreate(string $nodeName)
|
||||
{
|
||||
setUITemplate -pushTemplate attributeEditorTemplate;
|
||||
|
||||
// Create label and textField in a row
|
||||
rowLayout -numberOfColumns 3
|
||||
-columnWidth3 120 320 80
|
||||
-adjustableColumn 2
|
||||
-columnAlign3 "right" "center" "center"
|
||||
-rowAttach 1 "left" 0;
|
||||
|
||||
text -label "videoFile";
|
||||
|
||||
textField -tx "" -width 320 "AEMediaImagePlaneTemplateVideoFileTextField";
|
||||
|
||||
button -label "Browse..." -width 80
|
||||
-command "AEMediaImagePlaneTemplateBrowseButton()"
|
||||
"AEMediaImagePlaneTemplateBrowseButton";
|
||||
|
||||
setUITemplate -popTemplate;
|
||||
}
|
||||
|
||||
// Update callback for video file control
|
||||
global proc AEMediaImagePlaneTemplateVideoFileUpdate(string $nodeName)
|
||||
{
|
||||
string $value = `getAttr ($nodeName + ".videoFile")`;
|
||||
textField -edit -tx $value "AEMediaImagePlaneTemplateVideoFileTextField";
|
||||
}
|
||||
|
||||
// Browse button command
|
||||
global proc AEMediaImagePlaneTemplateBrowseButton()
|
||||
{
|
||||
// Get the current text field value
|
||||
string $currentFile = `textField -q -tx "AEMediaImagePlaneTemplateVideoFileTextField"`;
|
||||
|
||||
// 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 (*.*)";
|
||||
|
||||
// Open file dialog
|
||||
string $result[] = `fileDialog2
|
||||
-fileFilter $filters
|
||||
-dialogStyle 2
|
||||
-caption "Select Video File"
|
||||
-startingDirectory ($currentFile != "" ? `dirname $currentFile` : "")`;
|
||||
|
||||
// If user selected a file, update the text field
|
||||
if (size($result) > 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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 <maya/MImage.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// 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<MediaPlane::FFmpegVideoDecoder>();
|
||||
m_frameCache = std::make_unique<MediaPlane::FrameCache>(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<int>(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<std::mutex> 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<std::mutex> 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<int64_t>(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<unsigned int>(frameData.width);
|
||||
unsigned int height = static_cast<unsigned int>(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<unsigned char> 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<MediaImagePlane*>(clientData);
|
||||
if (node) {
|
||||
node->setImageDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Plugin initialization and uninitialization are handled in Plugin.cpp
|
||||
// This file only contains the node implementation
|
||||
|
|
@ -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 <maya/MPxImagePlane.h>
|
||||
#include <maya/MObject.h>
|
||||
#include <maya/MString.h>
|
||||
#include <maya/MTypeId.h>
|
||||
#include <maya/MPlug.h>
|
||||
#include <maya/MDataBlock.h>
|
||||
#include <maya/MDataHandle.h>
|
||||
#include <maya/MImage.h>
|
||||
#include <maya/MFnTypedAttribute.h>
|
||||
#include <maya/MFnNumericAttribute.h>
|
||||
#include <maya/MGlobal.h>
|
||||
#include <maya/MTime.h>
|
||||
#include <maya/MAnimControl.h>
|
||||
#include <maya/MEventMessage.h>
|
||||
#include <maya/MMessage.h>
|
||||
|
||||
// Forward declare MFnPlugin
|
||||
class MFnPlugin;
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
// 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" <nodeName>
|
||||
*/
|
||||
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<MediaPlane::FFmpegVideoDecoder> m_decoder;
|
||||
|
||||
// Frame cache
|
||||
std::unique_ptr<MediaPlane::FrameCache> 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
|
||||
|
|
@ -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 <maya/MFnDependencyNode.h>
|
||||
#include <maya/MFnDagNode.h>
|
||||
#include <maya/MPlug.h>
|
||||
#include <maya/MStatus.h>
|
||||
|
||||
// ============================================================================
|
||||
// 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<MediaPlaneDrawData*>(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<const MediaPlaneDrawData*>(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();
|
||||
}
|
||||
|
|
@ -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 <maya/MObject.h>
|
||||
#include <maya/MTypeId.h>
|
||||
#include <maya/MString.h>
|
||||
#include <maya/MUserData.h>
|
||||
#include <maya/MDagPath.h>
|
||||
#include <maya/MPoint.h>
|
||||
#include <maya/MBoundingBox.h>
|
||||
#include <maya/MMatrix.h>
|
||||
|
||||
// Maya Hardware Rendering
|
||||
#include <maya/MPxDrawOverride.h>
|
||||
#include <maya/MFrameContext.h>
|
||||
#include <maya/MUIDrawManager.h>
|
||||
|
||||
// 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
|
||||
|
|
@ -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<MediaPlane::FFmpegVideoDecoder>();
|
||||
m_frameCache = std::make_unique<MediaPlane::FrameCache>(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<std::mutex> 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<std::mutex> 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)
|
||||
{
|
||||
|
|
@ -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 <maya/MObject.h>
|
||||
|
|
@ -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
|
||||
|
|
@ -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 <maya/MFnPlugin.h>
|
||||
#include <maya/MGlobal.h>
|
||||
#include <maya/MDrawRegistry.h>
|
||||
|
||||
// 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 <nodeName>");
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue