[Maya C++] add control curve/spline attribute

Allow user to customize a 2D curve (spline etc.) to control some parameters such as color, grading and more.

In Maya you can add control curves to your custom dependency nodes through the interface MRampAttribute or MCurveAttribute. You might have seen this type of control curve with the soft selection or color grading of skin weights:

Unfortunately, (at least as of now) I have to say not only the documentation is thin regarding this feature but the interface is quite awkward as well as buggy. Rather than deciphering the API and finding workarounds to fix bugs you might loose less time writing your own facilities to evaluate a spline curve and write the corresponding UI element..

MRampAttribute VS MCurveAttribute

While MRampAttribute allows you to define a curve with control points directly on the curve, the MCurveAttribute is controlled by points outside the curve:

The advantage of using control points outside the curve is that it's easier to get smoother function. Both type can be evaluated with:

MCurveAttribute::getValueAtPosition( float position, float& value);
MRampAttribute::getValueAtPosition( float position, float& value);

Where 'position' is a point on the curve ('x' axis) and 'value' the height on the curve ('y' axis). Unfortunately, when I experimented with MCurveAttribute::getValueAtPosition() it was so slow I ended up using MRampAttribute (the later is orders of magnitude faster). My conclusion is that if you need Bezier like control points it's best to write your own Bezier evaluation. Alternatively you can sample values of MCurveAttribute::getValueAtPosition() and do a linear interpolation to evaluate the curve:

/// @param[in] samples : values y of the curve y = f(x) picked at regular intervals 
/// x = 1.0/(nb_samples-1)
/// @param[in] x : position between [0.0f - 1.0f] where we evaluate the samples
/// @return y value as a linear interpolation of the 2 nearest samples to 'x'
float linear_fetch(const std::vector<float>& samples, float x)
{
    int nb_samples = samples.size();
    if(x < 0.f)
        return samples[0];

    x = std::min(1.f, x);
    float u = (nb_samples - 1) * x;
    int i = (int)floor(u);

    assert( i > 0 );
    assert( i < (nb_samples) );

    float v0 = samples[i];
    if(i >= (nb_samples - 1))
        return v0;

    assert( i < (nb_samples - 1) );

    float v1 = samples[i+1];
    float dt = u-i;
    return dt * v1 + (1.f - dt) * v0;
}

/// @param[in] functor: a function with the signature: float f(float x) 
/// typically we use: MCurveAttribute::getValueAtPosition()
std::vector<float> sample_function(int nb_samples, std::function<float (float)> functor)
{
    assert(nb_samples > 0);
    std::vector<float> res(nb_samples);
    for(int i = 0; i < nb_samples; i++)
    {
        float t = (float)i / (float)(nb_samples - 1);
        res[i] = functor(t);
    }
    return res;
}

void Some_node::postConstructor(){
    // Setup the control points of the curve:
    MPlug curvePlug = get_plug( thisMObject(), "aCurve");
    MCurveAttribute curveAttr( curvePlug );
    
    MFloatArray pos;
    pos.append(0.0f); 
    pos.append(0.25f); 
    pos.append(0.5f); 
    pos.append(0.75f);
    pos.append(1.0f);
    
    MFloatArray val;
    val.append(0.2f); 
    val.append(0.9f); 
    val.append(0.2f); 
    val.append(0.2f);
    val.append(0.95f);
    curveAttr.setCurve(val, pos);

    /// Sampling the curve
    auto fun1d = [&curveAttr](float x){
        float y= -1.0f;
        curveAttr.getValueAtPosition(x, y);
        return y;
    };

    int nb_samples = 1000;
    auto samples = sample_function(nb_samples, fun1d );
    /// Check the accuracy (differences) between the original function and the discrete one.
    std::vector<float> values(nb_samples);
    for(float t = 0.f; t < 1.0f; t += 0.01f)
    {
        float y = -1.0f;            
        curveAttr.getValueAtPosition(t, y);
        MGlobal::displayInfo(MString("original: ") + y);
        float y2 = linear_fetch(samples, t);
        MGlobal::displayInfo(MString("linear interpolation: ") + y2);
        MGlobal::displayInfo(MString("diff: ") + std::abs(y-y2));
    }
}

MRampAttribute single instance

If you only need a single ramp attribute at the root of your node this is definitely the most practical and straightforward use case and you can survive without hacks or workarounds. If you need to make arrays or compound a ramp attribute it gets more tricky as we will soon realize further down this post...

So first you can add a ramp or a curve attribute to your custom MPxNode as follows:

class My_node : public MPxNode {
public:
    My_node() : MPxNode() {

    }

    void postConstructor() final;

    virtual ~My_node() { }

    static void* creator();
    /// Init the static attributes of our node
    static MStatus initialize();

    MStatus	compute( const MPlug& plug, MDataBlock& block);

    static MObject _s_ramp;
    static MObject _s_curve;
    static MObject _s_out_attribute;
};

MObject My_node::_s_ramp;
MObject My_node::_s_curve;
MObject My_node::_s_out_attribute;

#define mayaCheck(code) do{ const MStatus _s_(code); \
        if(_s_ != MS::kSuccess){ \
            assert(false);\
        }\
    }while(false)

MStatus My_node::initialize()
{
    MStatus status;
    _s_out_attribute = ...;
    mayaCheck(MPxNode::addAttribute(_s_out_attribute) );

    // Curve
    _s_curve = MCurveAttribute::createCurveAttr("rampAttr", "ra", &status);
    mayaCheck(status);
    mayaCheck(MPxNode::addAttribute(_s_curve) );
    mayaCheck(attributeAffects(_s_curve, _s_out_attribute));

    // Or ramp
    _s_ramp = MRampAttribute::createCurveRamp("curveAttr", "ca", &status);
    mayaCheck(status);
    mayaCheck(MPxNode::addAttribute(_s_ramp) );
    mayaCheck(attributeAffects(_s_ramp, _s_out_attribute));

    return MS::kSuccess;
}

void My_node::postConstructor()
{
    // Attribute should be initialized here in the postConstructor() after
    // the attribute are properply initialized and before they are used.
    // Ramp and curve attributes can't be empty.
    MStatus status;
    MRampAttribute ramp(thisMObject(), _s_ramp, &status);

    // Init ramp default values
    {
        MFloatArray val;
        MFloatArray pos;
        MIntArray interp(5, MRampAttribute::kSpline);
        val.append(0.0f);
        pos.append(0.0f);
        val.append(0.0075f);
        pos.append(0.1351f);
        val.append(0.0553f);
        pos.append(0.3038f);
        val.append(0.1809f);
        pos.append(0.4510f);
        val.append(1.0f);
        pos.append(1.0f);
        mayaCheck( ramp.setRamp(val, pos, interp) );
    }

    MCurveAttribute curveAttr( thisMObject(), _s_curve );
    {
        MFloatArray pos;
        pos.append(0.0f);
        pos.append(0.25f);
        pos.append(0.5f);
        pos.append(1.0f);
        MFloatArray val;
        val.append(0.0f);
        val.append(0.25f);
        val.append(0.50f);
        val.append(1.0f);
        mayaCheck( curveAttr.setCurve(pos, val) );
    }

}

MStatus	My_node::compute( const MPlug& plug, MDataBlock& block)
{
    MStatus status;
    if(plug.attribute() == _s_out_attribute)
    {
        // I'm not 100% sure this is safe since we ignore the
        // data block but it seems to work. 
        // It should be alright especially if there is no input connection because 
        // I don't see how it could interfere with the dependency graph evalution then
        MRampAttribute ramp(thisMObject(), _s_ramp);
        float res;
        float x = 0.5f;
        ramp.getValueAtPosition(x, res, &status);
        mayaCheck(status);

        //curve
        MCurveAttribute curve(thisMObject(), _s_ramp);
        curve.getValueAtPosition(x, res, &status);
        mayaCheck(status);


    }

    block.setClean(plug);
}

You can find a similar sample from autodesk documentation (although it's missing the compute method)

There is actually nothing really special about a ramp or curve attribute because it's not actually a special type such as kFloat3 or kMesh etc. What MCurveAttribute::createCurveAttr(attr_name) or MRampAttribute::createCurveRamp(attr_name) does is simply creating the following compound attribute:

The rest of MRampAttribute etc. is just functions to read and write to those attributes and evaluate the spline curve according to the samples stored in these attributes.

Ramp UI

You can setup the "attribute editor" tab to display a UI element to fiddle with the curve. To this end you need to create a attribute editor template via mel. create a mel file AEMy_nodeTemplate.mel where "My_node" is the name of your custom node:

 // Built in scripts in Maya instal folder:
source "AEaddRampControl.mel";
source "AEaddCurveControl.mel";

global proc AEMy_nodeTemplate( string $nodeName )
{
    editorTemplate -beginScrollLayout;

    editorTemplate -beginLayout "Settings" -collapse 0;
    editorTemplate -label "Envelope" -addControl "envelope";            
    // Call the predefined maya procedure to create a ramp control UI:
    AEaddRampControl( $nodeName + ".rampAttr" );
    AEaddCurveControl( $nodeName + ".curveAttr", "" ); 
    editorTemplate -endLayout;
    editorTemplate -addExtraControls -collapse 1;
    editorTemplate -endScrollLayout;
}

Compound array of MRampAttribute (failed attempts)

This where things get messy :/ The problem seems to be there is no such thing as a special data type representing curves or ramps. Without a proper <code>MFn</code> like interface you can't extract from a MDataBlock a "ramp type" since it does not exists. If you try it anyways using the wrapper MRampAttribute you will get a segfault:

MStatus My_node::initialize()
{
    MStatus status;
    _s_out_attribute = ...;
    mayaCheck(MPxNode::addAttribute(_s_out_attribute) );

    // Or ramp
    _s_ramp = MRampAttribute::createCurveRamp("curveAttr", "ca", &status);
    mayaCheck(status);
    mayaCheck(MPxNode::addAttribute(_s_ramp) );

    MFnNumericAttribute num;
    _s_float = num.create("floatAttr", "fa", MFnNumericData::kFloat, 0.0f, &status);
    mayaCheck(status);
    mayaCheck(MPxNode::addAttribute(_s_float) );

    MFnCompoundAttribute comp;
    _s_compound_array = comp.create("compoundAttr", "coa", &status );
    mayaCheck(status);
    comp.addChild( _s_ramp);
    comp.addChild(_s_float);
    comp.setArray(true);
    mayaCheck(MPxNode::addAttribute(_s_compound_array) );

    mayaCheck(attributeAffects(_s_compound_array, _s_out_attribute));

    return MS::kSuccess;
}

MStatus	My_node::compute( const MPlug& plug, MDataBlock& block)
{
    MStatus status;
    if(plug.attribute() == _s_out_attribute)
    {

        MArrayDataHandle data_array = block.inputArrayValue(_s_compound_array);
        mayaCheck( data_array.jumpToElement(0) );
        MDataHandle data = data_array.inputValue(&status);
        mayaCheck(status);
        MDataHandle ramp_hdl = data.child(_s_ramp);
        
        // Invalid operation as ramp_hdl.type() returns MFnData::kInvalid
        MRampAttribute ramp(thisMObject(), ramp_hdl.data());
        float res;
        float x = 0.5f;
        // Will segfault
        ramp.getValueAtPosition(x, res, &status);
        mayaCheck(status);
    }

    block.setClean(plug);
}

You might be tempted to use Mplug but as clearly stated in the Maya documentation using MPlug to fetch or set attributes within a compute method is not safe. And indeed while attempting to break this rule I faced again a dreadful segfault:

MPlug get_plug(const MObject& node, const MObject& attribute)
{
    MStatus status;
    MFnDependencyNode dg_fn ( node );
    MPlug plug = dg_fn.findPlug ( attribute, true, &status );
    mayaCheck(status);
    return plug;
}

MStatus	My_node::compute( const MPlug& plug, MDataBlock& block)
{
    MStatus status;
    if(plug.attribute() == _s_out_attribute)
    {
        // Not safe
        MPlug settings = get_plug(thisMObject(), _s_compound_array);
        MPlug el = settings.elementByPhysicalIndex(0);
        MPlug child = el.child(_s_curve._ramp);

        MRampAttribute ramp(thisMObject(), child.attribute(), &status);
        mayaCheck(status);               
        float res;
        float x = 0.5f;
        // Likely to segfault:
        ramp.getValueAtPosition(x, res, &status);
        mayaCheck(status);
    }

    block.setClean(plug);
}

MRampAttribute is a mysterious black box / Swiss army knife, but from my experiments this is what I inferred it does. First MRampAttribute::createRamp() will setup the following attributes (as stated in the introduction):

MRampAttribute::setRamp() sets the value of the above attributes, most likely through MPlugs. Finally MRampAttribute::getValueAtPosition() reads out the value of the above attributes (again since nor in the methods or at construction time any MDataBlock is specified I assume it reads values from MPlugs), then evaluate the curve.

Given the interface my conclusion is there is no safe way to use MRampAttribute to read out attributes in a compute() method (unless it's a very simple case like an unconnected ramp attribute at the root of your node like presented earlier).

At this point I'm thinking we can't use MRampAttribute to read/write or setup attributes but maybe we can handle attributes manually and just use MRampAttribute to evaluate the curve?!

But no, to add insult to injury there is no way to use MRampAttribute through the default constructor and we can't decouple the evaluation function MRampAttribute::getValueAtPosition() without linking MRampAttribute to some attributes:

MStatus status;
MRampAttribute ramp; // not giving any attribute here on purpose
MFloatArray val;
MFloatArray pos;
MIntArray interp(5, MRampAttribute::kSpline);
pos.append(0.0f); val.append(1.0f);
pos.append(0.25); val.append(0.100f);
pos.append(0.5f); val.append(0.100f);
pos.append(0.765f); val.append(0.100f);
pos.append(1.0f); val.append(0.0f);
// sadly this will return:
//MS::kFailure "object does not exists"
mayaCheck( ramp.setRamp(val, pos, interp) );

I whish evaluation and storage (node attribute) was not coupled this way :_(

In conclusion my final solution was to have a dummy unconnected attribute at the root of my node dedicated only to the evaluation of the curve and to manually setup and fetch the samples values trough another set of attributes.

Compound array of MRampAttribute (solution)

This is a very hacky solution. First create manually the attributes that store the samples of the curve:

The advantage is that you can make "my_node.attr_name" child of a compound attribute or anything else, you have total freedom. In my case I have "my_node.compound_array[i].ramp_attr_name" so my compound attribute is also an array. Then in the compute() method you will read out your samples as needed through your MDataBlock. Now to evaluate the curve we use MRampAttribute to add another dummy ramp attribute at the root of our node. We will set this dummy attribute values inside our compute() method according to the values of the samples, and voila. Probably unsafe but at least it works and I could not find any better solution aside from re-write the curve evaluation and UI myself (meh).

#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MRampAttribute.h>
#include <maya/MPxNode.h>
#include <maya/MString.h>
#include <maya/MFnEnumAttribute.h>
#include <vector>
#include <cassert>

#define mayaCheck(code) do{ const MStatus _s_(code); \
        if(_s_ != MS::kSuccess){ \
            assert(false);\
        }\
    }while(false)


enum Attr_property_flag {
    eEMPTY                   = 0,
    eREADABLE                = 1 <<  0,
    eWRITABLE                = 1 <<  1,
    eCONNECTABLE             = 1 <<  2,
    eSTORABLE                = 1 <<  3,
    eCACHED                  = 1 <<  4,
    eARRAY                   = 1 <<  5,
    //etc.
};

/// @see set_property
typedef int Attr_property_flags;


void set_property(MFnAttribute& attr, Attr_property_flags prop, bool state)
{
    if(prop == eEMPTY)
        return;

    if( (prop & eREADABLE) != 0 )
        mayaCheck( attr.setReadable( state ) );

    if( (prop & eWRITABLE) != 0 )
        mayaCheck(attr.setWritable( state ) );

    if( (prop & eCONNECTABLE) != 0 )
        mayaCheck(attr.setConnectable( state ) );

    //etc .
}


MObject add_compound_attr(const MString& full_name,
                          const std::vector<MObject>& children,
                          Attr_property_flags enabled_properties = 0,
                          Attr_property_flags disabled_properties = 0)
{
    MStatus status;
    MFnCompoundAttribute cmp_attr;
    MObject attr = cmp_attr.create(full_name, full_name, &status);
    mayaCheck(status);
    for(MObject child : children)
        mayaCheck(cmp_attr.addChild(child));

    set_property(cmp_attr, disabled_properties, false);
    set_property(cmp_attr, enabled_properties, true);
    mayaCheck(MPxNode::addAttribute(attr) );
    return attr;
}


MObject add_numeric_attr(const MString& long_name,
                         MFnNumericData::Type type,
                         Attr_property_flags enabled_properties = 0,
                         Attr_property_flags disabled_properties = 0)
{
    MFnNumericAttribute numeric;
    MStatus status;
    MObject object = numeric.create(long_name, long_name, type, 0, &status);
    mayaCheck( status );
    set_property(numeric, disabled_properties, false);
    set_property(numeric, enabled_properties, true);
    mayaCheck( MPxNode::addAttribute(object) );
    return object;
}


/// @param : the list of fields to add: 
// {{"My enum 1", eEnum1}, {"My enum 1", eEnum2}, {"My enum 3", eEnum3}}
template<class T>
MObject add_enum_attr(const MString& full_name,
                      T init,
                      const std::vector<std::pair<MString, T>>& enum_list = {},
                      Attr_property_flags enabled_properties = 0,
                      Attr_property_flags disabled_properties = 0)
{
    MStatus status;
    MFnEnumAttribute enum_attr;
    MObject attr = enum_attr.create(full_name, full_name, short(init), &status);
    mayaCheck(status);
    for(auto pair : enum_list)
        mayaCheck( enum_attr.addField(pair.first, short(pair.second)) );

    set_property(enum_attr, enabled_properties, true);
    set_property(enum_attr, disabled_properties, false);

    mayaCheck( MPxNode::addAttribute(attr) );
    return attr;
}

struct Attr_ramp {
    MObject _ramp;
    MObject _position;
    MObject _value;
    MObject _interp_type;

    static Attr_ramp add_attributes(const MString& attr_name)
    {
        Attr_ramp ramp_attrs;
        ramp_attrs._position = add_numeric_attr(attr_name+"_Position", MFnNumericData::kFloat);
        ramp_attrs._value = add_numeric_attr(attr_name+"_FloatValue", MFnNumericData::kFloat);

        typedef  MRampAttribute MRa;
        ramp_attrs._interp_type = add_enum_attr(attr_name+"_Interp", MRa::kSpline,
                                                {{"None"  , MRa::kNone},
                                                 {"Linear", MRa::kLinear},
                                                 {"Smooth", MRa::kSmooth},
                                                 {"Spline", MRa::kSpline}}
                                                );

        ramp_attrs._ramp = add_compound_attr( attr_name,
                                              {ramp_attrs._position,
                                               ramp_attrs._value,
                                               ramp_attrs._interp_type},
                                              eARRAY );
        return ramp_attrs;
    }
};

class My_node : public MPxNode {
public:
    My_node() : MPxNode() {

    }

    void postConstructor() final;

    virtual ~My_node() { }

    static void* creator();
    /// Init the static attributes of our node
    static MStatus initialize();

    MStatus	compute( const MPlug& plug, MDataBlock& block);

    static MObject _s_ramp_dummy;
    static Attr_ramp _s_ramp;
    static MObject _s_float;
    static MObject _s_compound_array;
    static MObject _s_out_attribute;
};

MObject My_node::_s_ramp_dummy;
Attr_ramp My_node::_s_ramp;
MObject My_node::_s_float;
MObject My_node::_s_compound_array;
MObject My_node::_s_out_attribute;


MStatus My_node::initialize()
{
    MStatus status;
    _s_out_attribute = /*...*/;
    mayaCheck(MPxNode::addAttribute(_s_out_attribute) );

    //Temporary data holder just to evaluate the curve
    _s_ramp_dummy = MRampAttribute::createCurveRamp("dummyCurve", "ca", &status);
    mayaCheck(status);
    mayaCheck(MPxNode::addAttribute(_s_ramp_dummy) );

    _s_ramp = Attr_ramp::add_attributes("curveAttr");
    mayaCheck(status);

    MFnNumericAttribute num;
    _s_float = num.create("floatAttr", "fa", MFnNumericData::kFloat, 0.0f, &status);
    mayaCheck(status);
    mayaCheck(MPxNode::addAttribute(_s_float) );

    MFnCompoundAttribute comp;
    _s_compound_array = comp.create("compoundAttr", "coa", &status );
    mayaCheck(status);
    comp.addChild( _s_ramp._ramp);
    comp.addChild(_s_float);
    comp.setArray(true);
    mayaCheck(MPxNode::addAttribute(_s_compound_array) );

    mayaCheck(attributeAffects(_s_compound_array, _s_out_attribute));

    return MS::kSuccess;
}

MDataHandle input_value_eval_at(MArrayDataHandle& handle, unsigned physical_idx)
{
    MStatus status;
    mayaCheck(handle.jumpToArrayElement(physical_idx));
    MDataHandle input_at = handle.inputValue(&status);
    mayaCheck(status);
    return input_at;
}

void fetch_MRampAttribute(MRampAttribute& ramp,
                          MArrayDataHandle& array_samples, 
                          const Attr_ramp& attr)
{
    unsigned nb_elements = array_samples.elementCount();
    MFloatArray positions(nb_elements);
    MFloatArray values(nb_elements);
    MIntArray interps(nb_elements);

    for(unsigned i = 0; i < nb_elements; ++i)
    {
        MDataHandle hdl = input_value_eval_at(array_samples, i);

        float x = hdl.child(attr._position).asFloat();
        float y = hdl.child(attr._value).asFloat();
        int t = int(hdl.child(attr._interp_type).asShort());

        positions[i] = x;
        values[i] = y;
        interps[i] = t;
    }

    mayaCheck( ramp.setRamp(values, positions, interps) );

    // Weirdly enough a call to getNumEntries() is necessary to garantee
    // the state of MRampAttribute is up to date and that
    // MRampAttribute::getValueAtPosition() returns valid values.
    // Without this call getValueAtPosition() usually returns garbage
    // and does not reflect the last call of setRamp()
    MStatus status;
    static unsigned nb = ramp.getNumEntries(&status);
    mayaCheck(status);
}

MStatus	My_node::compute( const MPlug& plug, MDataBlock& block)
{
    MStatus status;
    if(plug.attribute() == _s_out_attribute)
    {

        MArrayDataHandle data_array = block.inputArrayValue(_s_compound_array);
        mayaCheck( data_array.jumpToElement(0) );
        MDataHandle data = data_array.inputValue(&status);
        mayaCheck(status);
        MArrayDataHandle array_ramp(data.child(_s_ramp._ramp), &status);
        mayaCheck(status);

        MRampAttribute ramp(thisMObject(), _s_ramp_dummy);
        fetch_MRampAttribute(ramp, array_ramp, _s_ramp);

        mayaCheck(status);
        float res;
        float x = 0.5f;
        ramp.getValueAtPosition(x, res, &status);
        mayaCheck(status);
    }

    block.setClean(plug);
}

But we are not finished yet! The mel procedure AEaddRampControl() etc. only support simple ramp attributes at the root of your node! If you want to support compounded ramp attributes you must re-write the whole mel script AEaddRampControl.mel... The code below will support compounded ramp attributes but not arrays (yet to be done):

// Example: "my_node.compound1.compound2.ramp_attr" will return:
// compound1.compound2.ramp_attr
proc string getAttrName(string $nodeAttr)
{
    string $buffer[];
    tokenize($nodeAttr, ".", $buffer);
    int $nb_items = size($buffer);
    if( $nb_items >= 2 )
    {
        string $rampName = "";
        for($i = 1; $i < $nb_items; $i++){
            string $dot = (($i+1) != $nb_items) ? "." : "";
            $rampName += $buffer[$i] + $dot;
        }
        return $rampName;
    }
    return "";
}

// Untouched Maya procedure:
proc string localizedAttrName( string $name )
{
    if( $name == "Selected Color" ) {
        return (uiRes("m_AEaddRampControl.kSelClr"));
    } else if( $name == "Selected Position" ) {
        return (uiRes("m_AEaddRampControl.kSelPos"));
    } else if( $name == "Selected Value" ) {
        return (uiRes("m_AEaddRampControl.kSelVal"));
    } else if( $name == "Interpolation method" ) {
        return (uiRes("m_AEaddRampControl.kInterpMethod"));
    }
}


global proc AEmakeCompactRamp(
                    string $nodeAttr,
                    int $scaleSlider,
                    int $staticEntries,
                    int $staticPositions,
                    int $adaptiveScaling )
{
    // Fix here: 
    string $attr = getAttrName($nodeAttr);
    //

    string $rampName = $attr + "Ramp";
    string $editButton = $attr + "RampEdit";
    string $scName = $attr +"Scc";
    string $spName = $attr +"Sp";
    string $siName = $attr +"Si";

    string $colEntryName =  ($nodeAttr + "[0]." + $attr + "_ColorR");
    int $isColor = `objExists ($colEntryName)`;
    int $hasInput = `objExists ($nodeAttr +"Input")`;
    int $hasInputScale = `objExists ($nodeAttr +"InputScale")`;
    string $inputName = "InputScale";
    if( !$hasInputScale ) {
        if(`objExists ($nodeAttr +"InputBias")`) {
            $inputName = "InputBias";
            $hasInputScale = true;
        } else if(`objExists ($nodeAttr +"InputMax")`) {
            $inputName = "InputMax";
            $hasInputScale = true;
        }
    }
    setUITemplate -pst attributeEditorTemplate;
    string $rampBlock = `formLayout ($rampName + "Block")`;
    string $attrNiceName = `attributeName -nice $nodeAttr`;
    string $label = `text -label $attrNiceName -align right ($rampName + "Label")`;

    string $rframe = `frameLayout -lv 0 -cll 0 ($rampBlock + "fr")`;
    string $rampForm = `formLayout ($rampName + "Form")`;
    string $spc = `attrFieldSliderGrp
                        -l ""
                        -annotation (localizedAttrName("Selected Position"))
			-h 30 -w 70 -cw4 1 69 1 1 $spName`;

    string $scc;
    if( $isColor ){
        $scc = `attrColorSliderGrp
                                -l ""
                                -annotation (localizedAttrName("Selected Color")) -sb 1
                                -h 26 -w 77 -cw4 2 45 1 26 -adj 3 $scName`;
    } else {
        $scc = `attrFieldSliderGrp
                                -l ""
                                -annotation (localizedAttrName("Selected Value"))
                                -h 30 -w 70 -cw4 1 44 1 26 $scName`;
    }
    $editButton = `button -l ">" -width 23 -c ("editRampAttribute "+$nodeAttr) $editButton`;

    string $widgetName = `gradientControl
                                    -at  $nodeAttr
                                    -snc $staticEntries
                                    -sp  $staticPositions
                                    -ror $adaptiveScaling
                                    -as  $adaptiveScaling
                                    //-w 148 -h 74
                                    -w 135 -h 74
                                    $rampName`;

    string $interp = `attrEnumOptionMenuGrp
                                -l ""
                                -annotation (localizedAttrName("Interpolation method"))
                                -h 30 -w 70 -cw2 1 69  $siName`;
    string $pInput, $pInputScale;
    if( $hasInputScale )
    {
        string $multiplier = (uiRes("m_AEaddRampControl.kMultiplierAnnot"));
        if( $hasInput ){
            $pInputScale = `attrFieldSliderGrp -l ""
                                -ann $multiplier
                                -h 30 -w 58 -cw4 1 50 1 1
                                -at ($nodeAttr + $inputName) ($rampName + $inputName)`;
        } else {
            $pInputScale = `attrFieldSliderGrp
                                    -l (uiRes("m_AEaddRampControl.kInScale"))
                                    -ann $multiplier
                                    -h 30 -w 240 -cw4 70 69 100 1
                                    -at ($nodeAttr + $inputName) ($rampName + $inputName)`;
            }
    }

    if( $hasInput ){
        string $input = (uiRes("m_AEaddRampControl.kInput"));
        string $inputAnnot = (uiRes("m_AEaddRampControl.kInputAnnot"));
        if( $hasInputScale ){
            $pInput = `attrEnumOptionMenuGrp
                                -l $input
                                -ann $inputAnnot
                                -h 30 -w 188 -cw2 78 110
                                -at ($nodeAttr + "Input") ($rampName + "Input")`;
        } else {
            $pInput = `attrEnumOptionMenuGrp
                                    -l $input
                                    -ann $inputAnnot
                                    -h 30 -w 240 -cw2 78 160
                                    -at ($nodeAttr + "Input") ($rampName + "Input")`;
        }
    }

    formLayout -edit
                -attachForm $spc "left" -10
                -attachNone $spc "right"
                -attachForm $spc "top" 2
                -attachNone $spc "bottom"

                -attachForm $scc "left" -10
                -attachNone $scc "right"
                -attachControl $scc "top" -3 $spc
                -attachNone $scc "bottom"

                -attachForm $widgetName "left" 70
                -attachNone $widgetName "right"
                -attachForm $widgetName "top" 3
                -attachNone $widgetName "bottom"

                -attachForm $interp "left" -10
                -attachNone $interp "right"
                -attachControl $interp "top" -3 $scc
                -attachNone $interp "bottom"

                -attachForm $editButton "top" 0
                -attachForm $editButton "bottom" 0
                -attachControl $editButton "left" 5 $widgetName
                -attachNone $editButton "right" 0

                $rampForm;

    if( $hasInput ){
        if($hasInputScale){
            formLayout -edit
                        -attachControl $pInput "top" -3 $interp
                        -attachForm $pInput "left" -10
                        -attachControl $pInputScale "top" -3 $interp
                        -attachControl $pInputScale "left" -10  $pInput
                        $rampForm;
        } else {
            formLayout -edit
                       -attachControl $pInput "top" -3 $interp
                       -attachForm $pInput "left" -10
                       $rampForm;
        }
    } else if( $hasInputScale ){
        formLayout -edit
                   -attachControl $pInputScale "top" -3 $interp
                   -attachForm $pInputScale "left" -10
                   $rampForm;
    }
    setParent ..;
    setParent ..;

    formLayout -edit
               -attachForm $rframe "left" 131
               -attachForm $rframe "top" 0

               -attachControl $label "right" 0 $rframe
               -attachForm $label "top" 30
               $rampBlock;
    setParent ..;

   
    gradientControl -e -scc $scc $widgetName;
    gradientControl -e -spc $spc $widgetName;
    gradientControl -e -sic $interp $widgetName;
    setUITemplate -ppt;
}


global proc AEmakeLargeRamp( string $nodeAttr,
                                    int $bound,
                                    int $indent,
                                    int $staticEntries,
                                    int $staticPositions,
                                    int $adaptiveScaling )
{
    // Fix here:
    string $attr = getAttrName($nodeAttr);
    //

    string $rampName = $attr + "Ramp";
    string $editButton = $attr + "RampEdit";
    string $scName = $attr +"Scc";
    string $spName = $attr +"Sp";
    string $siName = $attr +"Si";

    string $colEntryName =  ($nodeAttr + "[0]." + $attr + "_ColorR");
    int $isColor = `objExists ($colEntryName)`;

    setUITemplate -pst attributeEditorTemplate;
    columnLayout -rowSpacing 2;

    string $rampForm = `formLayout ($rampName + "Form")`;
    string $spc = `attrFieldSliderGrp
                        -label (localizedAttrName("Selected Position"))
                        -cw 1 123 
                        -annotation (localizedAttrName("Selected Position")) $spName`;
    string $scc;
    if( $isColor ){
        $scc= `attrColorSliderGrp
                        -label (localizedAttrName("Selected Color"))
                        -cw 1 123 -cw 2 45 -cw 3 0 
                        -annotation (localizedAttrName("Selected Color")) -sb 1 $scName`;
    } else {
        $scc = `attrFieldSliderGrp
                        -label (localizedAttrName("Selected Value"))
                        -cw 1 123 
                        -annotation (localizedAttrName("Selected Value")) $scName`;
    }

    string $interp = `attrEnumOptionMenuGrp
                            -label (uiRes("m_AEaddRampControl.kInterp"))
                            -cw 1 123 
                            -annotation (localizedAttrName("Interpolation method")) 
                            $siName`;

    string $lmax;
    if ( $adaptiveScaling ) {
        $lmax = `text -label "1.0" ($rampName+"LX")`;
    }
    $editButton = `button -l ">" -width 23 -c ("editRampAttribute "+$nodeAttr) $editButton`;
    string $rframe = `frameLayout -lv 0 -cll 0 ($rampForm + "fr")`;

    string $widgetName = `gradientControl
                                -at $nodeAttr
                                -snc $staticEntries
                                -sp $staticPositions
                                // -w 148 -h 74
                                -w 135 -h 74
                                $rampName`;

    if ( $adaptiveScaling ) {
        gradientControl -e -as $adaptiveScaling -ror $adaptiveScaling -ulc $lmax $widgetName;
    }

    setParent ..;

    formLayout -edit
               -attachForm $spc "left"  0
               -attachNone $spc "right"
               -attachForm $spc "top" 0
               -attachNone $spc "bottom"

               -attachForm $scc "left" 0
               -attachNone $scc "right"
               -attachControl $scc "top" 0 $spc
               -attachNone $scc "bottom"

               -attachForm $interp "left" 0
               -attachNone $interp "right"
               -attachControl $interp "top" 0 $scc
               -attachNone $interp "bottom"

               -attachControl $rframe "left" 2 $interp
               -attachNone $rframe "right"
               -attachForm $rframe "top" 0
               -attachNone $rframe "bottom"

               -attachForm $editButton "top" 0
               -attachForm $editButton "bottom" 0
               -attachControl $editButton "left" 5 $rframe
               -attachNone $editButton "right"
               $rampForm;

    if ( $adaptiveScaling ) {
        formLayout -edit
                   -attachControl $lmax "left" 2 $rframe
                   -attachNone $lmax "right"
                   -attachForm $lmax "top" 0
                   -attachNone $lmax "bottom"
                   $rampForm;
    }
    setParent ..;
    
    if(objExists ($nodeAttr +"Input")){
        string $inLabel;
        string $labelAttr = `attributeName -nice $nodeAttr`;
        string $inputVarAnnot = (uiRes("m_AEaddRampControl.kInputVarAnnot"));

        if( $indent || size( $labelAttr ) < 9 ){
            string $fmt = (uiRes("m_AEaddRampControl.kInputFmt"));
            $inLabel = `format -s $labelAttr $fmt`;
        } else {
            $inLabel = (uiRes("m_AEaddRampControl.kInputShort"));
        }
        if( $indent ){
            attrEnumOptionMenuGrp -l $inLabel
                                  -ann $inputVarAnnot
                                  -cw 1 204
                                  -at ($nodeAttr + "Input") ($rampName + "Input");
        } else {
            attrEnumOptionMenuGrp -l $inLabel
                                  -ann $inputVarAnnot
                                  -cw 1 123
                                  -at ($nodeAttr + "Input") ($rampName + "Input");
        }
    }

    if(  objExists ($nodeAttr +"InputBias") ){
        attrFieldSliderGrp -label (uiRes("m_AEaddRampControl.kInputBias")) -cw4 123 81 130	25
                           -at ($nodeAttr +"InputBias") ($rampName + "InputBias");
    }

    if(  objExists ($nodeAttr +"InputScale") ){
        attrFieldSliderGrp -label (uiRes("m_AEaddRampControl.kInputScale")) -cw4 123 81 130 25
                           -at ($nodeAttr +"InputScale") ($rampName + "InputScale");
    }

    if(  objExists ($nodeAttr +"InputMax") ){
        attrFieldSliderGrp -label (uiRes("m_AEaddRampControl.kInputMax")) -cw4 123 81 130 25
                           -at ($nodeAttr +"InputMax") ($rampName + "InputMax");
    }

    if(  objExists ($nodeAttr +"InputOffset") ){
        attrFieldSliderGrp -label (uiRes("m_AEaddRampControl.kInputOffset")) -cw4 123 81 130 25
                           -at ($nodeAttr +"InputOffset") ($rampName + "InputOffset");
    }

    gradientControl -e -scc $scc $widgetName;
    gradientControl -e -spc $spc $widgetName;
    gradientControl -e -sic $interp $widgetName;
    setUITemplate -ppt;
}

// Untouched Maya procedure:
global proc AEmakeRampControlInteractiveNew_doIt(string $nodeAttr, int $adaptiveScaling)
{
    int $rampDrawMethod = 0;
    if( `optionVar -exists "gradientControlDrawMethod"` ){
        $rampDrawMethod = `optionVar -q "gradientControlDrawMethod"`;
    }

    switch( $rampDrawMethod )
    {
    case 1:
        AEmakeCompactRamp( $nodeAttr, 0, 0, 0, $adaptiveScaling );
    break;
    case 2:
        AEmakeCompactRamp( $nodeAttr,1,0,0,$adaptiveScaling );
    break;
    case 3:
        AEmakeLargeRamp( $nodeAttr, 0, 0, 0, 0, $adaptiveScaling );
    break;
    case 4:
        AEmakeLargeRamp( $nodeAttr,1,0,0,0,$adaptiveScaling );
    break;
    default:
        AEmakeLargeRamp( $nodeAttr,1,1,0,0,$adaptiveScaling );
    break;
    }
}

// Untouched Maya procedure:
global proc AEmakeRampControlInteractiveNew (string $nodeAttr)
{
    AEmakeRampControlInteractiveNew_doIt( $nodeAttr, false );
}

// Untouched Maya procedure:
global proc AEmakeRampControlInteractiveNewAS (string $nodeAttr)
{
    AEmakeRampControlInteractiveNew_doIt( $nodeAttr, true );
}


global proc AEmakeRampControlInteractiveReplace (string $nodeAttr)
{
    // Fix here:
    string $attr = getAttrName($nodeAttr);
    // When creating a widget we use the name of the attribute "xxx.xxx"
    // MEL will replace '.' with '_' automatically so to fetch back the 
    // widget we need to use underscores:
    $attr = `substituteAllString $attr "." "_"`;
    // END FIX ---------

    string $rampName = $attr + "Ramp";
    string $editButton = $attr + "RampEdit";

    gradientControl -edit -at $nodeAttr $rampName;
    if( `button -exists $editButton` ){
        button -edit -c ("editRampAttribute "+$nodeAttr) $editButton;
    }

    if( `objExists ($nodeAttr +"Input")` ){
        attrEnumOptionMenuGrp -edit -at ($nodeAttr + "Input") ($rampName + "Input");
    }
    if( `objExists ($nodeAttr +"InputScale")` ){
        attrFieldSliderGrp -edit -at ($nodeAttr + "InputScale") ($rampName + "InputScale");
    }
    if( `objExists ($nodeAttr +"InputBias")` ){
        attrFieldSliderGrp -edit -at ($nodeAttr + "InputBias") ($rampName + "InputBias");
    }
    if( `objExists ($nodeAttr +"InputMax")` ){
        attrFieldSliderGrp -edit -at ($nodeAttr + "InputMax") ($rampName + "InputMax");
    }
    if( `objExists ($nodeAttr +"InputOffset")` ){
        if (`attrFieldSliderGrp -query -exists ($rampName + "InputOffset")`) {
            attrFieldSliderGrp -edit -at ($nodeAttr + "InputOffset") ($rampName + "InputOffset");
        }
    }
}

global proc AEaddRampControl_doIt( string $rampName, int $adaptiveScaling )
{
    int $rampDrawMethod = 0;
    if( `optionVar -exists "gradientControlDrawMethod"` ) {
        $rampDrawMethod = `optionVar -q "gradientControlDrawMethod"`;
    }
    int $subBlock = (($rampDrawMethod < 1) || ($rampDrawMethod > 2));
    if( $subBlock ){
        string $rampTitle;
        string $nodeAttr[];
    
        tokenize ($rampName,".",$nodeAttr);
        if( size($nodeAttr) >= 2 )
        {
            $rampTitle = `attributeName -nice $rampName`;               
            // Fix here:
            $rampName = getAttrName($rampName);
        } else { 
            $rampTitle = interToUI($rampName);
        }
        editorTemplate -beginLayout $rampTitle -collapse false;
    }

    editorTemplate -callCustom ($adaptiveScaling ? "AEmakeRampControlInteractiveNewAS" : "AEmakeRampControlInteractiveNew")
                   "AEmakeRampControlInteractiveReplace"
                   $rampName;

    if( $subBlock ) {
        editorTemplate -endLayout;
    }
    editorTemplate -suppress ($rampName + "Input");
    editorTemplate -suppress ($rampName + "InputBias");
    editorTemplate -suppress ($rampName + "InputScale");
    editorTemplate -suppress ($rampName + "InputMax");
}

// Untouched procedures
global proc AEaddRampControl( string $rampName )
{
    AEaddRampControl_doIt( $rampName, false );
}

// Untouched procedures
global proc AEaddRampControlAS( string $rampName )
{
    AEaddRampControl_doIt( $rampName, true );
}

No comments

(optional field, I won't disclose or spam but it's necessary to notify you if I respond to your comment)
All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.
Anti-spam question: