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. -~----------~----~----~----~------~----~------~--~---
