Dynamic Attr doesn't update in realtime

812 views
Skip to first unread message

Rhett...@gmail.com

unread,
Aug 25, 2008, 6:36:10 PM8/25/08
to python_inside_maya
I've created a node and am running into a problem. I made a simple
node that has one Input attribute, one predefined Output attribute,
and one dynamic Output attribute. The input attribute affects the two
Output attributes in the same way.

I connect "time1" to the input then I connect each output attribute to
a separate sphere.translateY.

When I scrub through the timeControl bar, the predefined attribute
updates properly while the dynamic output updates only when I let go
from scrubbing or stop playing.

Another interesting thing is that if I connect any kind of predefined
attribute elsewhere on the sphere driven by the dynamic attribute...
it updates properly.

here's a version of the code if someone would help. I've tried
several things but just don't know where to go with it. This is the
last problem I'm facing before I can finish the current node project
I'm working on.



class staticDynamicTest(momx.MPxNode):
aInput = mom.MObject()
aStaticOutput = mom.MObject()
aCreateDynamic = mom.MObject()
MyArray = [0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
dynamicArray = []

def __init__(self):
momx.MPxNode.__init__(self)

def compute(self, plug, data):
if plug == staticDynamicTest.aStaticOutput:
mom.MPlug(self.thisMObject(),
staticDynamicTest.aStaticOutput).setFloat(staticDy
namicTest.MyArray[int(data.inputValue(staticDynamicTest.aInput).asFl
oat())])
mom.MPlug(self.thisMObject(),
staticDynamicTest.dynamicArray[0]).setFloat(staticDynamicTest.MyArray[int(data.inputValue(staticDynamicTest.aInput).asFl
oat())])
elif plug == staticDynamicTest.aInput:
staticDynamicTest.dynamicArray.append(mom.MObject( ))
nNAttr = mom.MFnNumericAttribute()

staticDynamicTest.dynamicArray[len(staticDynamicTest.dynamicArray)-1]
= nNAttr.create("dynAttr"+str(len(staticDynamicTest.dynamicArray)),
"DA"+str(len(staticDynamicTest.dynamicArray)),
mom.MFnNumericData.kFloat, 0.0)
mom.MFnDependencyNode(self.thisMObject()).addAttri
bute(staticDynamicTest.dynamicArray[len(staticDynamicTest.dynamicArray)-1],
mom.MFnDependencyNode.kLocalDynamicAttr)
data.setClean(plug)

def nodeCreator():
return momx.asMPxPtr(staticDynamicTest())

def nodeInitializer():
nNAttr = mom.MFnNumericAttribute()

staticDynamicTest.aInput = nNAttr.create("Input", "In",
mom.MFnNumericData.kFloat, 0.0)
staticDynamicTest.aStaticOutput = nNAttr.create("StaticOutput",
"SOut", mom.MFnNumericData.kFloat, 0.0)
staticDynamicTest.aCreateDynamic = nNAttr.create("CreateDynamic",
"CD", mom.MFnNumericData.kFloat, 0.0)

staticDynamicTest.addAttribute(staticDynamicTest.a StaticOutput)
staticDynamicTest.addAttribute(staticDynamicTest.a Input)
staticDynamicTest.addAttribute(staticDynamicTest.a CreateDynamic)

staticDynamicTest.attributeAffects(staticDynamicTe
st.aCreateDynamic, staticDynamicTest.aInput)
staticDynamicTest.attributeAffects(staticDynamicTe st.aInput,
staticDynamicTest.aStaticOutput)

Dean Edmonds

unread,
Aug 26, 2008, 9:44:22 AM8/26/08
to python_in...@googlegroups.com
On Mon, Aug 25, 2008 at 3:36 PM, Rhett...@gmail.com
<Rhett...@gmail.com> wrote:
>
> I've created a node and am running into a problem. I made a simple
> node that has one Input attribute, one predefined Output attribute,
> and one dynamic Output attribute. The input attribute affects the two
> Output attributes in the same way.

Your attributeAffects() calls show aCreateDynamic affecting aInput and
aInput affecting aStaticOutput. That's a transitive relationship (A
affects B affects C) however Maya only follows the first step in the
chain. So if a new value appears on aCreateDynamic then aInput will be
marked dirty, but that will not ripple on to dirty aStaticOutput.

If you want aStaticOutput to be dirtied when aCreateDynamic changes
then you'll have to add an explicit attributeAffects() call for that.
Read on, though, because I don't think you should be using the
aCreateDynamic attribute at all.

> I connect "time1" to the input then I connect each output attribute to
> a separate sphere.translateY.
>
> When I scrub through the timeControl bar, the predefined attribute
> updates properly while the dynamic output updates only when I let go
> from scrubbing or stop playing.

You haven't told Maya that the input attribute affects the dynamic output.

For static attributes you do that using attributeAffects() in your
initialize() method, but that won't work for dynamic attrs because
they don't exist at the time when your initialize() method is
executed. So for dynamic attrs your node has to provide a
setDependentsDirty() method which adds the dynamic attr to its array
argument whenever it is passed the input attr.

> here's a version of the code if someone would help. I've tried
> several things but just don't know where to go with it. This is the
> last problem I'm facing before I can finish the current node project
> I'm working on.

Your code got pretty mashed up. If you can't find a way around that
then you might consider in future putting the code into an attachment.

> class staticDynamicTest(momx.MPxNode):
> aInput = mom.MObject()
> aStaticOutput = mom.MObject()
> aCreateDynamic = mom.MObject()
> MyArray = [0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
> dynamicArray = []
>
> def __init__(self):
> momx.MPxNode.__init__(self)
>
> def compute(self, plug, data):
> if plug == staticDynamicTest.aStaticOutput:
> mom.MPlug(self.thisMObject(),
> staticDynamicTest.aStaticOutput).setFloat(staticDy
> namicTest.MyArray[int(data.inputValue(staticDynamicTest.aInput).asFl
> oat())])

This is the node's compute() method so you shouldn't be using an MPlug
to set the output value. Instead you need to get a handle to the
output attribute and use that to set its value. Otherwise Maya can get
pretty confused.
E.g:

data.outputValue(staticDynamicTest.aStaticOutput).setFloat(...);

> elif plug == staticDynamicTest.aInput:
> staticDynamicTest.dynamicArray.append(mom.MObject( ))
> nNAttr = mom.MFnNumericAttribute()
> staticDynamicTest.dynamicArray[len(staticDynamicTest.dynamicArray)-1]
> = nNAttr.create("dynAttr"+str(len(staticDynamicTest.dynamicArray)),
> "DA"+str(len(staticDynamicTest.dynamicArray)),
> mom.MFnNumericData.kFloat, 0.0)
> mom.MFnDependencyNode(self.thisMObject()).addAttri
> bute(staticDynamicTest.dynamicArray[len(staticDynamicTest.dynamicArray)-1],
> mom.MFnDependencyNode.kLocalDynamicAttr)

Adding attributes within the compute() is not a good idea. It might
work but there's no guarantee that it will continue to do so in the
future. The compute() method is intended to calculate outputs based on
inputs and Maya is written to optimized that workflow. So if you try
doing anything other than that you're sailing into uncertain waters.

It looks like what you are trying to do is have the user set a value
on the 'createDynamic' attribute and have that trigger the creation of
a new dynamic attribute. There are a bunch of ways i which that won't
work the way you want it to in certain circumstances. But really
shouldn't be doing it that way at all.

Instead, add a separate method to your node to do the dynamic
attribute creation. If the only code which ever triggers attribute
creation lies within your plugin then you can simply have it call the
new method rather that setting the 'createDynamic' attribute. If you
also want code outside your plugin (e.g. MEL scripts or other Python
scripts) to trigger dynamic attribute creation, then add a custom
command to your plugin which calls the new method and have the other
scripts use that command.

--
-deane

Rhett...@gmail.com

unread,
Aug 27, 2008, 9:22:31 AM8/27/08
to python_inside_maya
thank you for the response.

I've tried to add the attribute to the dirtyArray via the
setDependentsDirty... but I could not get it to work, I believe it
kept crashing Maya.

I usually use a handle to work with data, but I was trying to use
whatever method I would have to use to get the dynamic attribute to
work.

I have read recently that you shouldn't add attributes via the
compute(), I suppose that should probably be done in an AETemplate
right? Though I understand why you wouldn't do it in the case where
it would be used as part of a calculation, but why is it bad if it's
independent of the calculation? Is it because the user could make it
part of the calculation or is it because Maya API is scary and no one
knows what will happen for sure.

Well so based on the information you have given me, I've decided to
create the attributes from MEL calls in an AETemplate. I'm working
with a set of data to put into the attributes and decided to put the
data into animation curves and drive the attributes instead of using
an array... now my question is... I've connected the animation curve
to the node, and I've connected attributes from other nodes to my
node... well this doesn't update in playback, it updates when I stop
playback. Or it doesn't update scrubbing, it updates when I stop
scrubbing.

Any workaround?

Dean Edmonds

unread,
Sep 5, 2008, 1:09:56 PM9/5/08
to python_in...@googlegroups.com
On Wed, Aug 27, 2008 at 6:22 AM, Rhett...@gmail.com
<Rhett...@gmail.com> wrote:
>
> I've tried to add the attribute to the dirtyArray via the
> setDependentsDirty... but I could not get it to work, I believe it
> kept crashing Maya.

This is what worked for me:

def setDependentsDirty(self, dirty, dependents):
if dirty == normal.aInValue:
thisNode = self.thisMObject()
nodeFn = om.MFnDependencyNode(thisNode)
try:
plug = nodeFn.findPlug("deane")
dependents.append(plug)
except:
pass
return ompx.MPxNode.setDependentsDirty(self, dirty, dependents)

In my case "deane" was the name of the dynamic attribute and
'normal.aInValue' is an MObject containing the static attribute it
depends on. If you have an MObject containing the dynamic attribute
then you can use that in the findPlug() call, which will be a bit more
efficient than using the attribute's name.

> I have read recently that you shouldn't add attributes via the
> compute(), I suppose that should probably be done in an AETemplate
> right?

It depends. Presumably the attribute is being added in response to
some action on the part of the user. If it's in response to the user
selecting a menu item, then you would add the attribute in the menu
item's code, and so on.

If you want to add the attribute whenever the user changes a specific
"control attribute" on your node then you could do it in the
AETemplate, but then it wouldn't work if the user made the change
using a 'setAttr' command. A better approach would be to make that
control attribute internal and then create the dynamic attribute in
your node's 'setInternalValueInContext' method.

> Though I understand why you wouldn't do it in the case where
> it would be used as part of a calculation, but why is it bad if it's
> independent of the calculation?

There are a number of reasons:

1) The datablock will have already been built before your comput()e
was called. Any attrs you add during the compute() won't be part of
that datablock. That means that if you want to access the new attr
within the compute() you will have to use an MPlug, which may force a
secondary evaluation. If that occurs in a part of the DG which is
already being evaluated it could lead to a situation where an output
attribute stops updating in response to the dirtying of its inputs.

2) Adding the attribute may cause callbacks to fire, again leading to
undesirable secondary evaluation of the graph.

3) The compute() method is generally the most frequentally called
method of your node. It should be as fast as possible, without any
extraneous computation. Even the extra 'if' statement to check to see
if the control attribute has changed can add up if there are a lot of
instances of your node and they are heavily used.

4) To keep DG evaluation as fast as possible, there is some internal
checking which Maya does not do during your compute(). By performing
unexpected operations within the compute(), you are opening yourself
up to tripping over that streamlining.

5) Perhaps most importantly, it just doesn't make sense to do it in
the compute(). The compute() only fires when a dirty output needs to
be recalculated. You want to create the attribute in response to the
user changing the control field, which does not involve the
recalculation of a dirty output. So you end up having to kludge up
extra attributes and dependencies just to get it to work. It's cleaner
to trigger directly off of the action which requires the new dynamic
attribute -- in this case a change in the value of the control
attribute.

> Well so based on the information you have given me, I've decided to
> create the attributes from MEL calls in an AETemplate.

If you're adding a 'Create Attr' button to your AETemplate, then it
makes sense to do the creation there. But if you want the dynamic attr
to be created whenever, say, the user changes a specific string
attribute in your node, then as noted above I would recommend making
that string attribute internal and creating the dynamic attribute in
your node's setInternalValueInContext() method.

> I'm working
> with a set of data to put into the attributes and decided to put the
> data into animation curves and drive the attributes instead of using
> an array... now my question is... I've connected the animation curve
> to the node, and I've connected attributes from other nodes to my
> node... well this doesn't update in playback, it updates when I stop
> playback. Or it doesn't update scrubbing, it updates when I stop
> scrubbing.

Your node's compute() is only called when a dirty output is requested.
If playback doesn't need one of your node's outputs then it won't
update during playback. Probably the only reason that it's updating at
all is because you have the Attribute Editor open and it wants to
refresh its display of the output attribute after playback has
finished.

--
-deane

Reply all
Reply to author
Forward
0 new messages