Thanks Deane that is a huge help! I would have got into all kinds of
trouble doing it the way I was.

On Feb 5, 11:59 pm, Dean Edmonds <[email protected]> wrote:
> 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