Working with Arrays C++

Working with Arrays C++

Anonymous
Not applicable
2,324 Views
6 Replies
Message 1 of 7

Working with Arrays C++

Anonymous
Not applicable

Greetings. I am a beginner and only recently got into programming. I've made a couple nodes already for maya that work very well. I would like to expand my knowledge but there is very little solid information on these subjects.

To learn more about the API and general programming, I'm trying to reconstruct some form of plusMinusAverage node.
Making a simple node is easy, but I would like to have an undefined number of inputs (arrays) to be calculated, though I am unsure how to grab the data from the inputs and compute all of them.

float input1 = data.inputValue(aInput1, &status).asFloat();

float input2= data.inputValue(aInput2, &status).asFloat();

etc...

 

Now I'd like to accomplish that but for an array of inputs.

Any help, quick guidance to solid, precise documentation explaining in detail, example script or any good tip would be very appreciated.

 

Cheers

Accepted solutions (1)
2,325 Views
6 Replies
Replies (6)
Message 2 of 7

jmreinhart
Advisor
Advisor

I don't know of a single resource that covers everything but if you look into the MArrayDataHandle, and MFnNumericAttribute.isArray().

 

I wrote a node recently that uses an array of inputs, you can see the code here.

 

www.jonah-reinhart.com/single-post/2020/05/10/mathArray-Node

0 Likes
Message 3 of 7

kramer-bastian
Explorer
Explorer
Accepted solution

Hi there,

 

working with array attributes starts at the "initialize" function, where you can define array or array data attributes in more then one way. Which way to choose depends on how you would like to use the attributes later in maya.

 

Based on your description, I would suggest to use an array attribute and not an array data attribute.

The difference is: An array attribute has for each element of the array its own plug (like "node.attr[0]" in Maya). An array data attribute has one plug containing the whole array as one data object.

 

To declare a attribute as an array you have to call some extra functions while creating the attribute with MFn*Attribute.

 

For example:

MStatus TestNode::initialize() {
    MStatus status;
    
    MFnNumericAttribute nAttr;
    
    arrayAttribute = nAttr.create("foo", "foo", MFnNumericData::kFloat, 0, &status);
    nAttr.setArray(true);
    nAttr.setUsesArrayDataBuilder(true);
    status = addAttribute(arrayAttribute);

    // more initialization code
}

 

The functions "setArray" and the more optional "setUsesArrayDataBuilder" define the attribute as an array attribute.

 

For getting the data of the array attribute inside the compute function you have to use the MArrayDataHandle class.

This class offers functions to work on array attribute data.

 

For example:

MStatus TestNode::compute(const MPlug &plug, MDataBlock &data) {
    MStatus status;
    
    MArrayDataHandle arrayHandle = data.inputArrayValue(arrayAttribute, &status);
    
    MFloatArray values(arrayHandle.elementCount(), 0);
    for (unsigned int i = 0; i < values.length(); i++) {
        arrayHandle.jumpToArrayElement(i);
        MDataHandle elementHandle = arrayHandle.inputValue(&status);
        values[i] = elementHandle.asFloat();
    }

    // more computation
}

This example shows a straight forward approach for getting array values from data. Please notice, that this approach ignores array elements "logical index". For more information about the difference between "logical" and "physical" index in array attributes refer to the documentation of the MArrayDataHandle class.

 

For writing to array attributes the "setUsesArrayDataBuilder" function mentioned earlier gets relevant.

 

For example:

MStatus TestNode::compute(const MPlug &plug, MDataBlock &data) {
    // some computation

    MArrayDataHandle outputArrayHandle = data.outputArrayValue(outputArrayAttribute, &status);
    MArrayDataBuilder outputArrayBuilder = outputArrayHandle.builder();
    for (unsigned int i = 0; i < values.length(); i++) {
        MDataHandle outputElementHandle = outputArrayBuilder.addLast(&status);
        outputElementHandle.setFloat(values[i]);
    }
    outputArrayHandle.set(outputArrayBuilder);
    outputArrayHandle.setAllClean();

    // more computation
}

 This example shows how to write to an array attribute using the MArrayDataBuilder class. To enable the usage of this class the "setUsesArrayDataBuilder" function must get called with "true" in the initialize function. Also important is to call the "setAllClean" function at the end of writing to the attribute to mark all array plugs as no more dirty.

 

I hope this helps.

Message 4 of 7

Anonymous
Not applicable

Yes Kramer, this is what I had been looking for. I had looked around the API examples, dug around and either skipped ahead the answer to my question or never fell on it. Now I have an answer to that one. 

 

Now since this is related, if I wanted to average all these results, would I have to add a line like this? - 

 

MStatus TestNode::compute(const MPlug &plug, MDataBlock &data) {
    // some computation
    // case average

    MArrayDataHandle outputArrayHandle = data.outputArrayValue(outputArrayAttribute, &status);
    MArrayDataBuilder outputArrayBuilder = outputArrayHandle.builder();
    for (unsigned int i = 0; i < values.length(); i++) {
        MDataHandle outputElementHandle = outputArrayBuilder.addLast(&status);
        outputElementHandle.setFloat(values[i]);
    }
    "" outputElementHandle.setFloat(values[i] / [i]) or
    outputElementHandle.setFloat(values[i] / values.length()) ""

    outputArrayHandle.set(outputArrayBuilder);
    outputArrayHandle.setAllClean();

 

Cheers for the answer!

0 Likes
Message 5 of 7

kramer-bastian
Explorer
Explorer

Hi,

I'm glad I could help.

 

Regarding to your question:

 

If you just want to evaluate the average, you don't need to have an array attribute as output.

The average is just a single value, which you can write into a simple non array attribute.

 

First I would suggest to separate handling attributes and evaluating values. It is easier to debug your code if you do just one task per line.

 

Evaluating the average is easy by accumulating all input array values and divide the sum by the array's length.

 

Here a short example:

MStatus TestNode::compute(const MPlug &plug, MDataBlock &data) {
    MStatus status;
    
    // READ
    // read input values from the array attribute like suggested in the example before.
    
    // EVALUATE
    // accumulate all input values
    float average = 0;
    for (unsigned int i = 0; i < values.length(); i++)
        average += values[i];
    // divide sum by number of values, but first check if you do not divide by zero!
    if (values.length() > 0)
        average /= values.length();
    
    // WRITE
    // write to single float output attribute
    MDataHandle outputHandle = data.outputValue(outputAttribute, &status);
  
  outputHandle.setFloat(average);
    outputHandle.setClean();
    
    data.setClean(plug);
    return MStatus::kSuccess;
}

 

I hope that helps.

 

Message 6 of 7

791475642
Explorer
Explorer

Excuse me, how do I judge that a single value of the output array has changed

 

for example:

Mstatus compute(const MPlug &plug,MDataBlock &data)

{

if(plug == output)//output is Array

{

//What I get here is the whole of the output array, not the change of the single value of the array

}

}

 

0 Likes
Message 7 of 7

791475642
Explorer
Explorer

Excuse me, how do I judge that a single value of the output array has changed

 

for example:

Mstatus compute(const MPlug &plug,MDataBlock &data)

{

if(plug == output)//output is Array

{

//What I get here is the whole of the output array, not the change of the single value of the array

}

}

0 Likes