stringFormatNode/stringFormatNode.cpp
 
 
 
stringFormatNode/stringFormatNode.cpp
//-
// ==========================================================================
// Copyright 1995,2006,2008 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.
// ==========================================================================
//+

// Example Plugin: stringFormatNode.cpp
//
// This plug-in is an example of a user-defined dependency graph node.
// It takes several numbers as input, as well as a format string. It
// generates a formatted string as an output.
//
// All occurences of ^[0-9][dft] are replaced with the corresponding
// input parameter using the specified format. 
//
// d means output as an integer.
// f means output as a floating point value.
// t means output as a timecode.
// 
// For example, the string "format ^1f ^2d ^0t end format", using the
// array {2.3, 3.4, 4.5} as an input will generate the result
// "format 3.4 4 00:00:02 end format"
//

#include <math.h>

#include <maya/MIOStream.h>
#include <maya/MPxNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnStringData.h>
#include <maya/MFnPlugin.h>

// The stringFormat class defines the attributes and methods necessary
// for the stringFormatNode plugin
//
class stringFormat : public MPxNode
{
public:
                                                stringFormat();
        virtual                         ~stringFormat();

        virtual MStatus         compute( const MPlug& plug, MDataBlock& data );

        static  void*           creator();
        static  MStatus         initialize();
 
public:
        static  MTypeId         id;                                     // Unique node Id

        static  MObject         attrFormat;                     // Formatting string
        static  MObject         attrValues;                     // Input values (array)
        static  MObject         attrOutput;                     // Result
};

MTypeId     stringFormat::id( 0x81034 );
MObject     stringFormat::attrFormat;        
MObject     stringFormat::attrValues;       
MObject     stringFormat::attrOutput;       

// The creator method creates an instance of the stringFormat class
// and is the first method called by Maya when a stringFormat node
// needs to be created.
//
void* stringFormat::creator()
{
        return new stringFormat();
}

// The initialize routine is called after the node has been created.
// It sets up the input and output attributes and adds them to the
// node. Finally the dependencies are arranged so that when the
// inputs change Maya knowns to call compute to recalculate the output
// value.
//
MStatus stringFormat::initialize()
{
        MFnNumericAttribute numAttr;
        MFnTypedAttribute       typedAttr;
        MFnStringData           stringData;
        MStatus                         stat;
        MStatus                         stat2;

        // Setup the input attributes
        //
        attrFormat = typedAttr.create("format", "f", MFnData::kString, 
                                                                  stringData.create(&stat2), &stat);
        CHECK_MSTATUS( stat2 );
        CHECK_MSTATUS( stat );
        CHECK_MSTATUS( typedAttr.setStorable( true ) );
        CHECK_MSTATUS( typedAttr.setKeyable( true ) );

        attrValues = numAttr.create("values", "v", MFnNumericData::kDouble, 
                                                                0, &stat);
        CHECK_MSTATUS( stat );
        CHECK_MSTATUS( numAttr.setArray( true ) );
        CHECK_MSTATUS( numAttr.setReadable( false ) );
        CHECK_MSTATUS( numAttr.setIndexMatters( true ) );
        CHECK_MSTATUS( numAttr.setStorable( true ) );
        CHECK_MSTATUS( numAttr.setKeyable( true ) );

        attrOutput = typedAttr.create( "output", "o", MFnData::kString,
                                                                   stringData.create(&stat2), &stat);
        CHECK_MSTATUS( stat2 );
        CHECK_MSTATUS( stat );
        CHECK_MSTATUS( typedAttr.setWritable( false ) );
        CHECK_MSTATUS( typedAttr.setStorable( false ) );

        // Add the attributes to the node
        //
        CHECK_MSTATUS( addAttribute( attrFormat ) );
        CHECK_MSTATUS( addAttribute( attrValues ) );
        CHECK_MSTATUS( addAttribute( attrOutput ) );

        // Set the attribute dependencies
        //
        CHECK_MSTATUS( attributeAffects( attrFormat, attrOutput ) );
        CHECK_MSTATUS( attributeAffects( attrValues, attrOutput ) );

        return MS::kSuccess;
} 

// The constructor does nothing
//
stringFormat::stringFormat() {}

// The destructor does nothing
//
stringFormat::~stringFormat() {}

// The compute method is called by Maya when the input values
// change and the output values need to be recomputed.
// The input values are read then using sinf() and cosf()
// the output values are stored on the output plugs.
//


// Look for ^[0-9][a-z] after the given index. Returns the position of
// the characted just after '^'
int findNextMatch(MString & string, int indx, int & param, char & letter)
{
        // Warning this is not I18N compliant
        const char * str = string.asChar();
        while (str[indx]) {
                if ((str[indx] == '^') && 
                        (str[indx+1] >= '0') && (str[indx+1] <= '9') &&
                        (str[indx+2] >= 'a') && (str[indx+1] <= 'z')) {
                        param = str[indx+1] - '0';
                        letter = str[indx+2];
                        return indx+1;
                }
                indx++;
        }

        return -1;
}

MStatus stringFormat::compute (const MPlug& plug, MDataBlock& data)
{
        
        MStatus status;
 
        // Check that the requested recompute is one of the output values
        //
        if (plug == attrOutput) {
                // Read the input values
                //
                MDataHandle inputData = data.inputValue (attrFormat, &status);
                CHECK_MSTATUS( status );
                MString format = inputData.asString();

        // Get input data handle, use outputArrayValue since we do not
        // want to evaluate all inputs, only the ones related to the
        // requested multiIndex. This is for efficiency reasons.
        //
                MArrayDataHandle vals = data.outputArrayValue(attrValues, &status);
                CHECK_MSTATUS( status );

                int indx = 0;
                int param;
                char letter;
                while ((indx = findNextMatch(format, indx, param, letter)) > 0) {
                        double val = 0.;
                        status = vals.jumpToElement(param);
                        if (status == MStatus::kSuccess) {
                                MDataHandle thisVal = vals.inputValue( &status );
                                if (status == MStatus::kSuccess) {
                                        val = thisVal.asDouble();
                                }
                        }
                        MString replace;
                        bool valid = false;
                        switch (letter) {
                                case 'd':                                       // Integer
                                val = floor(val+.5);
                                // No break here

                                case 'f':                                       // Float
                                replace.set(val);
                                valid = true;
                                break;

                                case 't':                                       // Timecode
                                {
                                        const char * sign = "";
                                        if (val<0) {
                                                sign = "-";
                                                val = -val;
                                        }
                                        int valInt = (int)(val+.5);
                                        int sec = valInt / 24;
                                        int frame = valInt - sec * 24;
                                        int min = sec / 60;
                                        sec -= min * 60;
                                        int hour = min / 60;
                                        min -= hour * 60;
                                        char buffer[90];
                                        if (hour>0)
                                                sprintf(buffer, "%s%d:%02d:%02d.%02d", 
                                                                sign, hour, min, sec, frame);
                                        else
                                                sprintf(buffer, "%s%02d:%02d.%02d", 
                                                                sign, min, sec, frame);
                                        replace = buffer;
                                }
                                valid = true;
                                break;
                        }

                        if (valid) {
                                format = format.substring(0, indx-2) + 
                                        replace + format.substring(indx+2, format.length()-1);
                                indx += replace.length() - 3;
                        }
                }

                // Store the result
                //
                MDataHandle output = data.outputValue(attrOutput, &status );
                CHECK_MSTATUS( status );
                output.set( format );

        } else {
                return MS::kUnknownParameter;
        }

        return MS::kSuccess;
}

// The initializePlugin method is called by Maya when the plugin is
// loaded. It registers the node and the UI.
//
MStatus initializePlugin ( MObject obj )
{ 
        MStatus   status;
        MFnPlugin plugin( obj, PLUGIN_COMPANY, "8.0", "Any");

        // Register the node
        CHECK_MSTATUS_AND_RETURN_IT(
                plugin.registerNode( "stringFormat", stringFormat::id,
                                                         stringFormat::creator, stringFormat::initialize));

        // Create the UI for the plugin.
        // Keep on going, do not fail the load because of this.
        CHECK_MSTATUS(plugin.registerUI("stringFormatCreateUI", 
                                                                        "stringFormatDeleteUI", "", ""));

        return status;
}

// The unitializePlugin is called when Maya needs to unload the plugin.
// It does the opposite of initialize.
//
MStatus uninitializePlugin( MObject obj)
{
        MStatus   status;
        MFnPlugin plugin( obj );

        CHECK_MSTATUS_AND_RETURN_IT(plugin.deregisterNode( stringFormat::id ));

        return status;
}