Before I get into the nitty gritty of dealing with output plug arrays,
there's a small bug in your code. The compute() method should only
return kUnknownParameter. So rather than this:

       def compute( self, plug, dataBlock ):
               if ( plug == pyNode.output ):
                       [...]
                       return om.kUnknownParameter

it should do this:

       def compute( self, plug, dataBlock ):
               if ( plug == pyNode.output ):
                       [...]
               else::
                       return om.kUnknownParameter

Okay, on to the juicy stuff...

On Thu, Feb 5, 2009 at 15:36, ryant <[email protected]> wrote:
>
> its sweet how
> when you add to the input array it automatically adds to the output
> array.

It doesn't and that's why there's another bug in your code.

The individual elements of a plug array are created on the fly, as
they are needed. You would have to set a value, request a value or
make a connection to a plug array element before Maya will create it.

What will happen in your code is the following:

1) Immediately after 'pNode' is created, 'inputs' and 'output' will
both have zero elements.

2) After the three 'connectAttr' commands 'inputs' will have plugs at
elements 0, 1 and 2 but 'output' will still have no elements.

3) With the first 'getAttr' command, Maya will create element 0 of
'output' and then call your node's compute() to calculate its value.

4) compute() will get the number of elements from 'inputs', which is 3
and try to iterate through the loop that many times.

5) On the first iteration of the loop the array data handles for
'inputs' and 'output' will both jump to element 0, which will work
because they both have an element 0.

6) On the second iteration of the loop the array data handles will
jump to element 1. That will work for 'inputs's handle but 'output's
handle will generate an exception because 'output' does not have an
element 1. The exception will halt the processing of the compute() and
cause the 'getAttr' to fail.


In general, there are three ways of handling the computation of output
plug arrays:

1) Arrange it so that the output elements are created before compute()
needs them. For example, you might have a deformer node which gets
inserted into existing geometry connections in such a way that
whenever one of the input elements is connected to the source node the
corresponding output element is connected to the destination node.

Another example would be to rearrange the order of operations in your
test code. If you had performed the operations like this:

    cmds.connectAttr( (sphere[0] + '.translateX'), (pNode +
'.inputs[0]'), f=True )
    cmds.getAttr( (pNode + '.output[0]') )

    cmds.connectAttr( (sphere[0] + '.translateY'), (pNode +
'.inputs[1]'), f=True )
    cmds.getAttr( (pNode + '.output[1]') )

    cmds.connectAttr( (sphere[0] + '.translateZ'), (pNode +
'.inputs[2]'), f=True )
    cmds.getAttr( (pNode + '.output[2]') )

then it would have worked because you'd never end up in a situation
where compute() is being called with 'inputs' having a different
number of elements than 'output'.

The downside of this approach is that it is inflexible and tends to
make your code rather fragile. It can also lead to doing computations
before they are really needed, simply to ensure that inputs and
outputs are properly balanced. For example, in the code above you'd
end up computing 'output[0]' three times and 'output[1]' twice even
though you only need each of them once.


2) Only have compute() process one element at a time. If Maya asks
compute() to calculate the value of 'output[2]' then you can be
assured that that element of the plug array already exists. Rather
than run a loop to calculate all of the output array values, just
calculate the one asked for and you won't run into any problems.

In most cases this is the simplest, safest and most efficient way of
handling output plug arrays.

Modifying your original compute() to work with just one element at a
time, it would look like this:

        def compute(self, plug, dataBlock):
                if (plug == pyNode.output) and plug.isElement():
                        # Get the input handle
                        dataHandle = dataBlock.inputArrayValue(pyNode.inputs)

                        # Get output handle
                        outputHandle = dataBlock.outputArrayValue(pyNode.output)

                        # Get the element index
                        index = plug.logicalIndex()

                        # Position the arrays at the correct element.
                        dataHandle.jumpToElement(index)
                        outputHandle.jumpToElement(index)

                        # Copy the input element value to the output element.
                        
outputHandle.outputValue().setFloat(dataHandle.inputValue().asFloat())
                else:
                        return om.kUnknownParameter


3) Sometimes there is a lot of setup required to calculate a single
element of the output plug array and that setup is common across all
elements. In such a case it may be more efficient to do that setup
once and then calculate all of the output plug array elements at once.
To do that you need some way of creating output elements that don't
yet exist from within your compute(). You do that by using an
MArrayDataBuilder.

MArrayDataHandle.builder() can be used to retrieve the plug array's
data builder. Then, as you loop through the elements of the input
array you try to jump the output's MArrayDataHandle to the next index.
If the jump fails that tells you that there is no element at that
index, so you use MArrayDataBuilder.addElement() to add one. Once you
are done the loop you use MArrayDataHandle.set() to put the modified
array builder back onto the plug array.

Modifying your original compute() to use an MArrayDataBuilder, it
would look like this:

        def compute(self, plug, dataBlock):
                if (plug == pyNode.output) and plug.isElement():
                        # Get the input handle
                        dataHandle = dataBlock.inputArrayValue(pyNode.inputs)
                        numElements = dataHandle.elementCount()

                        # Get output handle and its array data builder
                        outputHandle = dataBlock.outputArrayValue(pyNode.output)
                        outputBuilder = outputHandle.builder()

                        # Do some really expensive setup code.

                        # Iterate through input array and set the output array
                        for i in range(numElements):
                                dataHandle.jumpToElement(i)
                                handle = dataHandle.inputValue()
                                result = handle.asFloat()

                                try:
                                        outputHandle.jumpToElement(i)
                                        outdatahandle = 
outputHandle.outputValue()
                                except:
                                        outdatahandle = 
outputBuilder.addElement(i)

                                outdatahandle.setFloat(result)

                        outputHandle.set(builder)
                else:
                        return om.kUnknownParameter

You would also need to modify your initialize() function so that it
marked the 'output' attribute as using an array data builder:

    nAttr.setUsesArrayDataBuilder(True)

-- 
-deane

--~--~---------~--~----~------------~-------~--~----~
Yours,
Maya-Python Club Team.
-~----------~----~----~----~------~----~------~--~---

Reply via email to