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