Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active November 7, 2024 02:18
Show Gist options
  • Save mottosso/939ecaa992a3c98df5988484bbfb5e02 to your computer and use it in GitHub Desktop.
Save mottosso/939ecaa992a3c98df5988484bbfb5e02 to your computer and use it in GitHub Desktop.
Callback on removal, even on undo?

Is there a callback for when a node is removed from the scene? Especially when removed by undoing its creation?

Callback Description
MNodeMessage::addNodePreRemovalCallback Called on delete, and on redoing delete, but not from undoing its creation
MNodeMessage::addNodeAboutToDeleteCallback Also called on delete, but not from undoing its creation
MNodeMessage::addNodeDestroyedCallback Called after ~destroyMePlease
MDGMessage::addNodeAddedCallback Called after creation, even during undo

Example

from maya import cmds
from maya.api import OpenMaya as om

cmds.loadPlugin("destroyMePlease")

mod = om.MDagModifier()
parent = mod.createNode("transform")
rigid = mod.createNode("destroyMePlease", parent=parent)
mod.doIt()

mod.undoIt()
# ~destructor not called

cmds.file(new=True, force=True)
# still not called

cmds.unloadPlugin("destroyMePlease")
# RuntimeError: Plug-in, "destroyMePlease", cannot be unloaded because it is still in use # 

del mod
# ~destructor called

cmds.unloadPlugin("destroyMePlease")
# Success!

Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/939ecaa992a3c98df5988484bbfb5e02.git destroyMePlease
mkdir destroyMePlease/build
cd destroyMePlease/build
$env:DEVKIT_LOCATION="c:\path\to\your\devkit"
cmake .. -G Ninja
ninja
#-
# ==========================================================================
# Copyright (c) 2018 Autodesk, Inc.
# All rights reserved.
#
# These coded instructions, statements, and computer programs contain
# unpublished proprietary information written by Autodesk, Inc., and are
# protected by Federal copyright law. They may not be disclosed to third
# parties or copied or duplicated in any form, in whole or in part, without
# the prior written consent of Autodesk, Inc.
# ==========================================================================
#+
cmake_minimum_required(VERSION 2.8)
# include the project setting file
include($ENV{DEVKIT_LOCATION}/cmake/pluginEntry.cmake)
# specify project name
set(PROJECT_NAME destroyMePlease)
# set SOURCE_FILES
set(SOURCE_FILES
destroyMePlease.cpp
)
# set linking libraries
set(LIBRARIES
OpenMaya
OpenMayaUI
Foundation
)
# Build plugin
build_plugin()
/*
>>> from maya.api import OpenMaya as om
>>> mod = om.MDagModifier()
>>> parent = mod.createNode("transform")
>>> rigid = mod.createNode("destroyMePlease", parent=parent)
>>> mod.doIt()
>>> mod.undoIt()
# ~destructor not called
>>>
>>> del mod
# still not called
>>>
>>> cmds.flushUndo()
# still not called
>>>
>>> cmds.file(new=True, force=True)
# still not called
>>>
>>> cmds.unloadPlugin("destroyMePlease")
# RuntimeError: Plug-in, "destroyMePlease", cannot be unloaded because it is still in use #
*/
#include <string.h>
#include <assert.h>
#include <maya/MPxLocatorNode.h>
#include <maya/MFnPlugin.h>
#include <maya/MBoundingBox.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MVector.h>
#include <maya/MMatrix.h>
#include <maya/MDataBlock.h>
#include <maya/MStreamUtils.h>
#include <maya/MNodeMessage.h>
#include <maya/MDGModifier.h>
#include <maya/MCallbackIdArray.h>
#define Print MStreamUtils::stdErrorStream
class destroyMePlease : public MPxLocatorNode {
public:
destroyMePlease();
~destroyMePlease() override;
MStatus compute(const MPlug& plug, MDataBlock& data) override;
void postConstructor() override;
static void* creator() { return new destroyMePlease(); }
static MStatus initialize();
static void onPreRemoval(MObject&, void*);
static void onAboutToDelete(MObject&, MDGModifier&, void*);
static void onDestroyed(void*);
public:
static MTypeId id;
static MString drawDbClassification;
MCallbackIdArray callbackIds;
};
MTypeId destroyMePlease::id(0x80012);
MString destroyMePlease::drawDbClassification("drawdb/geometry/destroyMePlease");
destroyMePlease::destroyMePlease() {
// Called during MDagModifier::createNode..
// ..before MDagModifier::doIt()
Print() << "Destroy me please..\n";
}
destroyMePlease::~destroyMePlease() {
// Called after MDagModifier goes out of Python scope
Print() << "I'm being destroyed, yaaaay!\n";
}
MStatus destroyMePlease::initialize() {
return MS::kSuccess;
}
/* Called on delete, and on redoing delete, but not from undoing its creation */
void destroyMePlease::onPreRemoval(MObject&, void*) {
Print() << "addNodePreRemovalCallback()\n";
}
/* Also called on delete, but not from undoing its creation */
void destroyMePlease::onAboutToDelete(MObject&, MDGModifier&, void*) {
Print() << "addNodeAboutToDeleteCallback()\n";
}
/* Called after ~destroyMePlease */
void destroyMePlease::onDestroyed(void*) {
Print() << "addNodeDestroyedCallback()\n";
}
void destroyMePlease::postConstructor() {
MStatus status { MS::kFailure };
MObject tempobj { this->thisMObject() };
callbackIds.append(MNodeMessage::addNodePreRemovalCallback(tempobj, onPreRemoval, nullptr, &status));
assert(status == MS::kSuccess);
callbackIds.append(MNodeMessage::addNodeAboutToDeleteCallback(tempobj, onAboutToDelete, nullptr, &status));
assert(status == MS::kSuccess);
callbackIds.append(MNodeMessage::addNodeDestroyedCallback(tempobj, onDestroyed, nullptr, &status));
assert(status == MS::kSuccess);
}
MStatus destroyMePlease::compute(const MPlug& plug, MDataBlock& datablock) {
return MS::kUnknownParameter;
}
MStatus initializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
plugin.registerNode("destroyMePlease",
destroyMePlease::id,
destroyMePlease::creator,
destroyMePlease::initialize,
MPxNode::kLocatorNode,
&destroyMePlease::drawDbClassification);
Print() << "==============\n";
Print() << "Initialising\n";
return status;
}
MStatus uninitializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj);
plugin.deregisterNode(destroyMePlease::id);
Print() << "Deinitialising.\n";
Print() << "==============\n";
return status;
}
@perrauo-adsk
Copy link

Hi! I know it has been a while. But for this situation, you can use the DGMessage::addNodeRemovedCallback. This one is triggered on "undos". Please note that at that stage the node's path won't be valid but the rest is ok.

Sorry for the delay

@mottosso
Copy link
Author

Thanks, I can't remember where I posted this question; was it on the Autodesk Maya forums?

Since then, I've found a callback specifically for undo and redo that has been working as expected in Maya 2018-2023.

static void onUndoRedo(void*) {}

MEventMessage::addEventCallback("Undo", onUndoRedo, nullptr, &status);
MEventMessage::addEventCallback("Redo", onUndoRedo, nullptr, &status);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment