Today, I want to talk about the recommended way of handling the deletion of attributes and nodes within Maya through the wise guidance of Dean Edmonds (Autodesk Maya API Architect). We will examine this potential solution for this scenario:
Let’s say you have plug-in that has a connection to a user defined attributes on other objects in a scene. You may write some code that detects that if the node is deleted, then the user defined attributes on the connected objects are deleted.
One way to do this maybe through the passThroughToOne() method and in there you traverse the graph to the connected nodes. Then using the executeCommandOnIdle() method run the MEL command deleteAttr on the unwanted attribute, which supports undo.
Let’s examine some better ways, as well as looking at the downfalls. Let's define some terminology first. When I say "deleteNode command" I mean whatever command caused your node to be deleted. When I say "deleteAttr command" I mean the 'deleteAttr' your node is trying to execute when it is deleted.
Potentially you could use executeCommandOnIdle(), however this would cause a problem. If you executed the deleteAttr command immediately then it should end up being included in the same undo chunk as the deleteNode command.
So the first question is to ask is why are you using executeCommandOnIdle() rather than just executeCommand() and see if there are other ways of addressing those problems.
However if you really do need to do the deleteAttr command using executeCommandOnIdle() then you can use the 'undoInfo -stateWithoutFlush off' command to temporarily disable the undo queue just before doing the deletion, then 'undoInfo -stateWithoutFlush on' immediately after. E.g:
MString cmd = MString( "undoInfo -swf off; deleteAttr ") + attrName + "; undoInfo -swf on;";
The one thing to point out though, is you need to be very careful, that no other scene-modifying commands execute while the undo queue is off.
You will then need a way to restore the deleted attribute when the deleteNode command is undone. There are a couple of ways of doing that. The most flexible is probably to create an MPxCommand which does nothing in its doIt() but restores the attribute in its redoIt(). So when your node is deleted, it would do something like this:
MString cmd = MString("myRestoreAttr ") + attrName;
MGlobal::executeCommand(cmd, false, true /* add it to the undo queue */);
cmd = MString( "undoInfo -swf off; deleteAttr ") + attrName + "; undoInfo -swf on;";
When the node is deleted, 'myRestoreAttr' command will be executed. It won't do anything but it will be added to the same undo chunk as the deleteNode command.
Then, during the next idle cycle, the attribute will be deleted.
When the deleteNode command is undone, it will undo the 'myRestoreAttr' command, which will add the attr back onto the target node.
There are some potential problems with this approach:
1) The timing might not work out. The attribute needs to be restored before the deleteNode command attempts to reconnect your node to it. That will all depend upon how you detect the node deletion. Hopefully one of MNodeMessage::addNodeAboutToDeleteCallback() or addNodePreRemovalCallback() will do the trick.
2) The attribute disconnection done by the deleteNode command may use the attribute's internal pointer for the reconnection, not its name, in which case Maya will probably crash. To get around that, rather than using a 'deleteAttr' command to delete the attribute, use another MPxCommand which uses an MDGModifier to delete it. It would share that MDGModifier with the 'myRestoreAttr' command which could then use it to undo the attribute deletion. That way it's still the same original attribute being restored and Maya's internal pointers to it will still be valid.
3) There's no guarantee that the idle command will have had a chance to execute before the node deletion is undone. That means that 'myRestoreAttr' may be called while the attribute is still there. Note that the MDGModifier solution to problem 2 above will take care of this since undoing an MDGModifier which hasn't been done yet will have no effect.
4) The fact that the idle command may not be called before the node deletion is undone also means that it might execute *after* the node deletion has been undone. So if you are not careful you will end up trying to delete the attribute while your node is still connected to it. How you address that depends upon how your code is set up, but it should be pretty straightforward.