MRampAttribute

MRampAttribute allows you to create an adjustable curve or color attribute where users can insert and adjust the interpolation of points along the ramp.

To create ramp attributes, we call the convenient classes contained in MRampAttribute:

MStatus RampAttributeDeformer::initialize() {
  // Create the curve ramp attribute
  aCurveRamp = MRampAttribute::createCurveRamp("curveRamp", "cur");
  addAttribute(aCurveRamp);
  attributeAffects(aCurveRamp, outputGeom);

  // Create the color ramp attribute
  aColorRamp = MRampAttribute::createColorRamp("colorRamp", "cor");
  addAttribute(aColorRamp);
  attributeAffects(aColorRamp, outputGeom);
 
  return MS::kSuccess;
}

To access the ramp attribute values inside a node or deformer:

MStatus RampAttributeDeformer::deform(MDataBlock& data, 
                          MItGeometry& itGeo, 
                          const MMatrix &localToWorldMatrix, 
                          unsigned int geomIndex) {
  MStatus status;

  // Get the ramp attributes
  MObject oThis = thisMObject();
  MRampAttribute curveAttribute(oThis, aCurveRamp, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);
  MRampAttribute colorAttribute(oThis, aColorRamp, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);

  float rampPosition = 0.25f, curveRampValue;
  MColor color;

  // Get the corresponding value on the curve ramp attribute
  curveAttribute.getValueAtPosition(rampPosition, curveRampValue, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);

  // Get the corresponding value on the color ramp attribute
  colorAttribute.getColorAtPosition(rampPosition, color, &status);
  CHECK_MSTATUS_AND_RETURN_IT(status);

  // Do your calculation with the values

  return MS::kSuccess;
}

You will also need to make sure the attribute is set correctly in your attribute editor template for the node:

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

        editorTemplate -beginLayout "Ramp Deformer Attributes" -collapse 0;
            AEaddRampControl( $nodeName + ".curveRamp" );
            AEaddRampControl( $nodeName + ".colorRamp" );
        editorTemplate -endLayout;

    editorTemplate -addExtraControls;
    editorTemplate -endScrollLayout;
}

16 thoughts on “MRampAttribute”

kattkieru April 1, 2011 at 7:20 am

Hey, thanks for posting this. I’m doing research on wire deformers and remembered seeing someone else use a curve like this for shaping, which is exactly what I’d like to do. 🙂

NTaylor April 9, 2011 at 11:40 am

Excellent stuff, gave me a nice shortcut around doing too much thinking today. Thanks Chad.

I would add this little snippet of handy info :

When using api ramps with nodes, it’s a good plan to initialize the ramps with single entries to avoid errors when using the Attribute Editor.

Oddly enough this accomplished via the AEtemplate, as in the following code tweak to your AEtemplate ( the extra code runs once & not after a user has added their own ramp entries [seemingly]):

global proc AErampDeformerTemplate( string $nodeName )
{
    editorTemplate -beginScrollLayout;
 
        editorTemplate -beginLayout "Ramp Deformer Attributes" -collapse 0;
            AEaddRampControl( $nodeName + ".curveRamp" );
            AEaddRampControl( $nodeName + ".colorRamp" );
        editorTemplate -endLayout;
 
    editorTemplate -addExtraControls;
    editorTemplate -endScrollLayout;

    // -- With Ramps, we have to add some default values 
    //    to avoid an error when displaying them in the Attribute Editor :

    setAttr ($nodeName + ".curveRamp[0].curveRamp_FloatValue") 1.0;
    setAttr ($nodeName + ".curveRamp[0].curveRamp_Position") 0.0;
    setAttr ($nodeName + ".curveRamp[0].curveRamp_Interp") 1;

    setAttr ($nodeName + ".colorRamp[0].colorRamp_Color") -type double3 1 1 1;
    setAttr ($nodeName + ".colorRamp[0].colorRamp_Position") 0.0;
    setAttr ($nodeName + ".colorRamp[0].colorRamp_Interp") 1;
}

Andrey July 8, 2011 at 9:11 pm

Nice example, thanks!

NTaylor, you should also check if some value already presents in the ramp (smth like `getAttr -silent -size ($nodeName+”.”+”curveRamp”` == 0) or it will override stored value.

NTaylor January 7, 2012 at 4:54 am

Andrey, You’re entirely correct about my suggestion leading to the overriding of values. Bad NTaylor. Bad.

Now that I finally have a chance to test this all out properly, I’ve run into the problem you’ve outlined. My values are indeed being overridden.

I’ve tried tinkering with your code, but I always get a size of 1 when running the code you suggested : My ramps *always* have an entry [0] just after createNode has been executed. This is also regardless of the value stored at index [0], so checking against a presumed default value is tricky [impossible].

Which means that I cant seem to find a MEL/AE solution for safely setting default ramp values :

I can wrap the createNode command within a MEL function that sets those default values, but it’d be nicer to be able to simply default initial ramp values so that they’re ‘correct’ if a user makes the node via createNode as well.

With that in mind I’ve tinkered a fair bit and found an ideal solution (via the API) : set initial ramp values from within the ::postConstructor() and set NO values in the AETemplate at all. Ideal.

===Post Constructor===

void YourNode::postConstructor()
{
MStatus status;

postConstructor_initialise_ramp_curve( YourNode::thisMObject(), aYourRamp, 0, 0.0f, 1.0f, 1 );

}

===postConstructor_initialise_ramp_curve===

MStatus postConstructor_initialise_ramp_curve( MObject& parentNode, MObject& rampObj, int index, float position, float value, int interpolation)
{
MStatus status;

MPlug rampPlug( parentNode, rampObj );

MPlug elementPlug = rampPlug.elementByLogicalIndex( index, &status );

MPlug positionPlug = elementPlug.child(0, &status);
status = positionPlug.setFloat(position);

MPlug valuePlug = elementPlug.child(1);
status = valuePlug.setFloat(value);

MPlug interpPlug = elementPlug.child(2);
interpPlug.setInt(interpolation);

return MS::kSuccess;
}


===AE TEMPLATE===
global proc AErampDeformerTemplate( string $nodeName )
{
editorTemplate -beginScrollLayout;
editorTemplate -beginLayout “Ramp Deformer Attributes” -collapse 0;
AEaddRampControl( $nodeName + “.curveRamp” );
AEaddRampControl( $nodeName + “.colorRamp” );
editorTemplate -endLayout;
editorTemplate -addExtraControls;
editorTemplate -endScrollLayout;
}

____________________________________
(Apologies for the formatting)

The above use of the PostConstructor works great. All Ramps can be built with default values and no errors are reported in maya with the original, simpler, AETemplate.

You can call the postConstructor_initialise_ramp_curve() more than once to set more than 1 default entry/position/value for a ramp

This can be easily extended to handle color ramps as well :

MStatus postConstructor_initialise_color_curve( MObject& parentNode, MObject& rampObj, int index, float position, MColor value, int interpolation )

{
MStatus status;

MPlug rampPlug( parentNode, rampObj );

MPlug elementPlug = rampPlug.elementByLogicalIndex( index, &status );

MPlug positionPlug = elementPlug.child(0, &status);
status = positionPlug.setFloat(position);

MPlug valuePlug = elementPlug.child(1);
status = valuePlug.child(0).setFloat(value.r);
status = valuePlug.child(1).setFloat(value.g);
status = valuePlug.child(2).setFloat(value.b);

MPlug interpPlug = elementPlug.child(2);
interpPlug.setInt(interpolation);

return MS::kSuccess;
}

________________________________________
I’ve left out a tonne of MStatus error checkling/trapping in the above code to reduce it a bit.

Matthew Puchala August 31, 2014 at 11:59 am

Hello, I followed this tutorial and have created a ramp attribute in my node, however it shows up as a list of values and a button to add new items, but not as a graph… How can I have it display as a graph like you have above in the image? Thanks!

Chad September 17, 2014 at 9:14 am

You have to make the attribute editor template.

AlexHrazhdan September 7, 2015 at 7:13 am

Could somebody tell me, why my node doesn’t update itself when I change curve ramp profile?

Chad September 25, 2015 at 9:32 am

@AlexHrazhdan Make sure your attributeAffects are all set up properly and something is pulling on the output of the node.

Laurent October 15, 2015 at 10:23 am

Hi all,
Do you know a way to declare RampAttribute as an internal attribute ?

Alex Hrazhdan October 29, 2015 at 9:46 am

@Chad i think that my attributeAffects setup properly… I’m trying to implement ramp attribute not in the deformer but in a regular MPxNode where I’m grubbing a bunch of transform matrices and put them in to node as input array attribute. Output is same array with influencing to x axis translation from ramp.

#include "arrayOutput.h"

MTypeId ArrayOutput::id(0x00000239);
MObject ArrayOutput::aInArrayMatrix;
MObject ArrayOutput::aOutArrayMatrix;
MObject ArrayOutput::aRamp;

ArrayOutput::ArrayOutput()
{}

ArrayOutput::~ArrayOutput()
{}

void* ArrayOutput::creator()
{
MGlobal::displayInfo("Created!");
return new ArrayOutput();
}
MStatus ArrayOutput::compute(const MPlug& plug, MDataBlock& data)
{
MStatus status;
MGlobal::displayInfo("comput");
MArrayDataHandle hArrayHandle = data.inputValue(aInArrayMatrix);

unsigned int num = hArrayHandle.elementCount();

MObject oThis = thisMObject();
MRampAttribute curveAttribute(oThis, aRamp, &status);

MMatrixArray mArr;
mArr.clear();
MArrayDataHandle hOutputArray = data.outputArrayValue(aOutArrayMatrix);
MArrayDataBuilder myBuilder = hOutputArray.builder();
for (int i = 0; i < num; i++)
{
hArrayHandle.jumpToElement(i);

mArr.append(hArrayHandle.inputValue().asMatrix());

}

float rampStep, rampVal;

for (int j = 0; j < mArr.length(); j++)
{

float pos = j*rampStep;

curveAttribute.getValueAtPosition(pos, rampVal, &status);
mArr[j][3][0] = rampVal;
}

for (unsigned int ii = 0; ii < mArr.length(); ii++)
{
status = hOutputArray.jumpToElement(ii);
MDataHandle myElementHandle = myBuilder.addElement(ii);

myElementHandle.setMMatrix(mArr[ii]);
}

hOutputArray.setAllClean();
hOutputArray.setClean();
data.setClean(plug);

return MS::kSuccess;
}

MStatus ArrayOutput::initialize()
{
MStatus status;

MFnMatrixAttribute mAttr;
MRampAttribute rAttr;
aRamp = rAttr.createCurveRamp("curveRamp", "cur", &status);
addAttribute(aRamp);
attributeAffects(aRamp, aOutArrayMatrix);

aInArrayMatrix = mAttr.create("inM", "inM", MFnMatrixAttribute::kDouble, &status);
mAttr.setKeyable(true);
mAttr.setArray(true);
addAttribute(aInArrayMatrix);

aOutArrayMatrix = mAttr.create("outM", "outM", MFnMatrixAttribute::kDouble, &status);
mAttr.setWritable(false);
mAttr.setStorable(false);
mAttr.setArray(true);
mAttr.setUsesArrayDataBuilder(true);
addAttribute(aOutArrayMatrix);

attributeAffects(aInArrayMatrix, aOutArrayMatrix);

return MS::kSuccess;
}

Alex Hrazhdan October 29, 2015 at 12:48 pm

O, my bad. actualy it has to be float rampStep = (1.0/num), rampVal; But it still doesn’t update…: (

PS/ Thanks for amazing new tutorial on cgcircuit (hope i will be smart anough to understand it…)

Alex Hrazhdan October 30, 2015 at 1:53 am

I have just relocated this line attributeAffects(aRamp, aOutArrayMatrix); after this one attributeAffects(aInArrayMatrix, aOutArrayMatrix); and it fixed the problem… didn’t know that it matters.

Laurent October 30, 2015 at 3:15 am

@alex:
The reason of the disfunction was that you declared your output (aOutArrayMatrix) after the declaration of the dependency of aOutArrayMatrix to aRamp. I think it’s a good practice to delay the attributeAffects(…) declarations at the end of initialize() : first, it makes node internal dependencies very clear, 2d, it avoids those kind of mistakes when you add more attributes to your node.

Any tip for my question about setting RampAttribute as internal ?

If I declare the ramp compound and its childs by myself the way it should be done before MRampAttribute appears, would it work (could I still use the MRampAttribute class functionnalities on that compound that uses same naming scheme) ?

Alex Hrazhdan October 30, 2015 at 5:07 am

@Laurent :
Thanks for advice! )

fruity April 27, 2016 at 3:49 pm

Hey Chad, thank you, one more time, for those explanations =]
After struggling a bit with the AETemplate (meeeeel… ^^), i’m happy to share the solution i use (thanks to NTaylor and Andre =). Not sure it is the best way to do, but at least it works (for me) !

// if catch returns 1 (i.e. if an error occurs), the attribute doesn't exist yet
if( catch(`getAttr -silent ($nodeName + ".colorDisplay[0].colorDisplay_Colors")`) )
    {setAttr ($nodeName + ".colorDisplay[0].colorDisplay_Color") -type double3 0 0 1;}
else
    {float $previousCol0[] = `getAttr -silent ($nodeName + ".colorDisplay[0].colorDisplay_Color")`;}

Of course, this has to come after creating creation of the attribute.

And among all the mel commands to refresh / update the AEditor (didn’t quite understand the diff between each of them..), the 2 that seems to work in most situations for me (maya2015/macOS) are :
source “AEyourPluginTemplate.mel”;
refreshEditorTemplates;

Hope it’ll help =]

denilson January 28, 2017 at 6:29 am

How do I do this using python ?

Thank you

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.