Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active February 10, 2025 13:56
Show Gist options
  • Save mottosso/e02a2d58482eae3e36be9d9cbd3040d1 to your computer and use it in GitHub Desktop.
Save mottosso/e02a2d58482eae3e36be9d9cbd3040d1 to your computer and use it in GitHub Desktop.
Dirty Node

dirtyNode

The node has 3 attributes.

  • shapeType
  • outputMesh
  • state

The affect each other, like so.

shapeType -> outputMesh -> state

Reading from state should compute outputMesh which depend on shapeType. But state is not dirtied when shapeType changes. Why?


Usage

import os
import sys
from maya import cmds

fname = "c:/maya_devkit/plug-ins/simpleSimulationNode/simpleSimulationNode.mll"  # or .so on Linux
cmds.loadPlugin(fname)

node = cmds.createNode("dirtyNode")

# Suceeds
assert cmds.getAttr(node + ".state") == 1

cmds.setAttr(node + ".shapeType", 2)

# Fails
assert cmds.getAttr(node + ".state") == 2


# I can force `outputMesh` to be dirty, and cause `simulation` to be dirty
cmds.setAttr(node + ".outputMesh", 0)  # Should not be writable

# Succeeds, but should be 2
assert cmds.getAttr(node + ".state") == 0


# Succeeds
cmds.setAttr(node + ".outputMesh", 2)  # Should not be writable
assert cmds.getAttr(node + ".state") == 2

Build

Your simplest option is to replace any devkit example with this source and build from there.

  1. Copy/paste the contents of dirtynode.cpp into simpleSimulationNode.cpp
  2. Launch a Visual Studio 2019+ environment
  3. Call the below.
cd c:\maya_devkit\plug-ins\simpleSimulationNode
$env:DEVKIT_LOCATION="c:\maya_devkit"
cmake . -G Ninja
cmake --build .
# [2/2] Linking CXX shared library simpleSimulationNode.mll
#include <maya/MPxNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MString.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MGlobal.h>
#include <maya/MDataHandle.h>
#include <maya/MFnPlugin.h>
class dirtyNode final : public MPxNode {
public:
MStatus compute(const MPlug& plug, MDataBlock& data) override;
static void* creator() { return new dirtyNode(); }
static MStatus initialize();
public:
// Attributes
static MObject aShapeType;
static MObject aOutputMesh;
static MObject aState;
public:
static MTypeId id;
static MString nodeName;
};
MTypeId dirtyNode::id = { 0x00081165 };
MString dirtyNode::nodeName = { "dirtyNode" };
MObject dirtyNode::aShapeType;
MObject dirtyNode::aOutputMesh;
MObject dirtyNode::aState;
MStatus dirtyNode::initialize() {
MFnNumericAttribute nAttr;
MStatus status;
aShapeType = nAttr.create("shapeType", "shty", MFnNumericData::kInt);
nAttr.setDefault(1);
nAttr.setKeyable(false);
nAttr.setWritable(true); // Mark as user-provided (not computed)
nAttr.setReadable(true);
nAttr.setStorable(true);
nAttr.setChannelBox(true);
aOutputMesh = nAttr.create("outputMesh", "oume", MFnNumericData::kInt);
nAttr.setKeyable(false);
nAttr.setChannelBox(true);
nAttr.setWritable(false); // Mark as computed (non-writable)
nAttr.setStorable(false); // Don't store this computed value
nAttr.setReadable(true);
aState = nAttr.create("state", "stat", MFnNumericData::kInt);
nAttr.setWritable(false);
nAttr.setStorable(false);
nAttr.setReadable(true);
status = addAttribute(aShapeType);
status = addAttribute(aOutputMesh);
status = addAttribute(aState);
status = attributeAffects(aShapeType, aOutputMesh);
status = attributeAffects(aOutputMesh, aState);
return MS::kSuccess;
}
MStatus dirtyNode::compute(const MPlug& plug, MDataBlock& data) {
MStatus status;
if (plug == aOutputMesh) {
MDataHandle shapeTypeHandle = data.inputValue(aShapeType, &status);
CHECK_MSTATUS_AND_RETURN_IT(status);
int shapeType = shapeTypeHandle.asInt();
// Compute outputMesh based on shapeType
MDataHandle outputMeshHandle = data.outputValue(aOutputMesh, &status);
CHECK_MSTATUS_AND_RETURN_IT(status);
outputMeshHandle.setInt(shapeType); // Or more complex logic
outputMeshHandle.setClean();
MGlobal::displayInfo("Computed aOutputMesh");
return MS::kSuccess;
}
else if (plug == aState) {
// Get outputMesh value
MDataHandle outputMeshHandle = data.inputValue(aOutputMesh, &status);
CHECK_MSTATUS_AND_RETURN_IT(status);
int outputMesh = outputMeshHandle.asInt();
// Compute currentStatus based on outputMesh and initialStatus
MDataHandle stateHandle = data.outputValue(aState, &status);
CHECK_MSTATUS_AND_RETURN_IT(status);
stateHandle.setInt(outputMesh);
stateHandle.setClean();
MGlobal::displayInfo("Computed aState");
return MS::kSuccess;
}
// Let Maya handle it
return MS::kUnknownParameter;
}
MStatus initializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "1.0", "Any");
status = plugin.registerNode(
dirtyNode::nodeName,
dirtyNode::id,
dirtyNode::creator,
dirtyNode::initialize
);
if (!status) {
status.perror("registerNode");
return status;
}
return status;
}
MStatus uninitializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj);
status = plugin.deregisterNode(dirtyNode::id);
if (!status) {
status.perror("deregisterNode");
return status;
}
return status;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment