Delete when disconnected

Delete when disconnected

negow
Advocate Advocate
989 Views
3 Replies
Message 1 of 4

Delete when disconnected

negow
Advocate
Advocate

Hi all,

 

When implementing an MPxNode, there's an option to setExistWithoutInConnections(false); such that the node is deleted automatically whenever a connection to it is broken. That's great!

 

Is there a similar option for creating nodes in Maya, either via the API or cmds?

 

Psuedo-code:

from maya import cmds
parent = cmds.createNode("transform")
cube, _ = cmds.polyCube()
cmds.connectAttr(parent + ".translateX", cube + ".translateX")

# Delete `cube` when `parent` is deleted
cmds.setExistWithoutInConnections(cube, False)

 

The specific problem I'm facing is when I build a network of nodes, one of which is a user-interactable node, like a transform with visual handle. The user could then delete this transform node, but alas! It leaves behind the additional DG nodes created to keep it company, like `multiplyDivide` and `composeMatrix` nodes. I'd like these associated to this transform, such that they are removed as soon as the transform no longer plugs into it.

 

Worst case, I'll have to install event handlers onto my node to monitor disconnects, but would rather not given that I'd like this behavior to persist on scene save and can already see the hoops I'd need to jump through to make that work.

990 Views
3 Replies
Replies (3)
Message 2 of 4

negow
Advocate
Advocate

A suitable workaround for anyone stumbling upon this issue in the future is to containerise DG nodes, and parent them underneath the transform to be deleted.

 

As a container (i.e. cmds.container(type="dagContainer")), the DG nodes would be deleted alongside the parent transform as part of the usual hierarchy cleanup. The container can be cmds.setAttr(container + ".hiddenInOutliner", True) as well, making things appear as though they are still DG nodes and not polluting the outliner.

Message 3 of 4

negow
Advocate
Advocate

I'm back at this and setExistWithoutInConnections no longer cuts it.

 

Now, my node is a MPxNode (rather than MPxSurfaceNode) much like the `polyCube` generator for `mesh` nodes. Unlike `polyCube` that gets all of its connections broken whenever the mesh is deleted, my node has a number of connections that remain behind, such as `time1`. And because `time1` is never deleted, the connection is never severed and thus the node never deleted.

 

I could setup a callback to monitor whenever some specific connection is broken, but I'm unable to have this participate in undo/redo. 😞 Whenever the `mesh` is deleted and `polyCube` is deleted alongside it, it's happening within the same MDGModifier context, and is thus recreated and reconnected during undo. But I can't figure out how to "fake" that, or otherwise get the same effect. There's no callback providing the modifier for when connections break, like there is for when nodes are deleted.

 

So, how can I avoid leaving DG nodes behind when their associated DagNode is deleted?

0 Likes
Message 4 of 4

negow
Advocate
Advocate

I eventually found something that works.

 

1. On connection to an attribute on my node, e.g. `.inputAttr`

2. Register a callback on the connecting node, e.g. `someTransform.message`

3. When the connecting node is deleted, append my node to its MDGModifier

4. When disconnected, unregister this callback

 

Since the connect/disconnect of any attribute is a closed loop, there is no (?) way this callback could trigger unless both were sure to exist, and no way it could remain behind when one of them is deleted (?). My only hesitation is whether there are mechanisms outside of this connect/disconnect loop that can inflence this relationship and either trigger the callback when it isn't supposed to, or not trigger it when it should.

 

Generally, it also feels "dirty" to append callbacks to node outside of my own DG nodes. I would have preferred keeping it internal, and not using callbacks at all. Any thoughts on this?

 

Here's the relevant code.

 

static void onInputNodeDeleted(MObject& /*node*/, MDGModifier& modifier, void* clientData) {
    MyNode* node = static_cast<MyNode*>(clientData);
    modifier.deleteNode(node->thisMObject());
}

MStatus MyNode::connectionMade(const MPlug& plug, const MPlug& otherPlug, bool /*asSrc*/) {
    MStatus status;

    if (plug == MarkerNode::aInputAttr) {
        _inputNodeMonitorCallback = MNodeMessage::addNodeAboutToDeleteCallback(
            otherNode, onSourceNodeDeleted, this, &status);
        assert(status == MS::kSuccess);
    }

    return MS::kUnknownParameter;
}


MStatus MyNode::connectionBroken(const MPlug& plug, const MPlug& /*otherPlug*/, bool /*asSrc*/) {
    MStatus status;

    if (plug == MarkerNode::aInputAttr) {
        status = MMessage::removeCallback(_inputNodeMonitorCallback);
        
        if (status != MS::kSuccess) {
            // Theoretically not possible to fail, but would ok OK
        }
    }

    return MS::kUnknownParameter;
}

 

0 Likes