Skip to content

Instantly share code, notes, and snippets.

@mottosso
Last active February 18, 2021 13:41
Show Gist options
  • Save mottosso/1534ec5230c093ff76c41875d3853de3 to your computer and use it in GitHub Desktop.
Save mottosso/1534ec5230c093ff76c41875d3853de3 to your computer and use it in GitHub Desktop.
Editable Initial State - Attempt 9

⬅️ Back

Can we pull pCube1.matrix in response to pulling pCube1.translateX? As it turns out, you can!

Test Status
Manipulator ✔️
Channel Box ✔️
Keyframe ✔️
Auto-keyframe ✔️
Hierarchy
Parented to each other

Manipulator Test

The translate manipulator both edits and evaluates the translate values of a node. Unlike editing values via the channel box, which does not trigger an evaluation. The manipulator has final say, despite our plug-in outputting different values than what the manipulator does.

Channel Box Text

Values in the channel box are reflected in the initial state.

channelboxtest

Keyframe Test

What about when we aren't using a manipulator, when the initial state comes from a pairBlend? Seems like it, yes!

Auto-keyframe Test

With auto-keyframe enabled, Maya would (1) write to translateX from our plug-in, (2) overwrite those values with whatever the manipulator outputs and (3) pull on translateX in order to store those values as keyframes. As it pulls on translateX, it could overwrite the values overwritten by the manipulator, resulting in stale values. In this case, it works as expected.

Hierarchy Test

Since we are editing and querying the local matrix, we cannot account for hierarchy..


Build & Run

From Powershell.

cd c:\
git clone https://gist.github.com/1534ec5230c093ff76c41875d3853de3.git initialStateNode9
mkdir initialStateNode9/build
cd initialStateNode9/build
$env:DEVKIT_LOCATION="c:\path\to\your\devkit"
cmake .. -G Ninja
ninja

Then from the Script Editor.

import os
from maya import cmds

fname = r"c:\initialState9\build\initialStateNode.mll"
cmds.loadPlugin(fname)

time = "time1"
cube, _ = cmds.polyCube()
cmds.move(-3, 15, -7)
cmds.scale(4, 4, 4)
node = cmds.createNode("initialStateNode")
cmds.setAttr(node + ".startTime", cmds.getAttr(time + ".outTime"))
cmds.setAttr(node + ".gravity", 0, -9.81, 0)
cmds.connectAttr(time + ".outTime", node + ".currentTime")
cmds.connectAttr(cube + ".matrix", node + ".inMatrix")
cmds.connectAttr(node + ".outputTranslateX", cube + ".translateX")
cmds.connectAttr(node + ".outputTranslateY", cube + ".translateY")
cmds.connectAttr(node + ".outputTranslateZ", cube + ".translateZ")


#cmds.file(new=True, force=True)
#cmds.unloadPlugin(os.path.basename(fname))
cmake_minimum_required(VERSION 2.8)
include($ENV{DEVKIT_LOCATION}/cmake/pluginEntry.cmake)
set(PROJECT_NAME initialStateNode)
set(SOURCE_FILES
initialStateNode.cpp
)
set(LIBRARIES
OpenMaya
OpenMayaRender
OpenMayaUI
Foundation
)
build_plugin()
#include <string.h>
#include <maya/MPxNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MString.h>
#include <maya/MVector.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MMatrix.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MStreamUtils.h>
#include <maya/MNodeMessage.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnMatrixData.h>
#include <maya/MCallbackIdArray.h>
#define Print MStreamUtils::stdErrorStream
class initialStateNode : public MPxNode {
public:
initialStateNode();
~initialStateNode() override;
MStatus compute(const MPlug& plug, MDataBlock& data) override;
MStatus computeStartState(const MPlug& plug, MDataBlock& data);
MStatus computeFirstState(const MPlug& plug, MDataBlock& data);
MStatus computeNextState(const MPlug& plug, MDataBlock& data);
MStatus writeOutputTranslate(MDataBlock& datablock);
MStatus stepSimulation(MDataBlock& datablock);
void setMatrix(MMatrix mat) { _restMatrix = mat; }
static void* creator() { return new initialStateNode(); }
static MStatus initialize();
void postConstructor();
bool isPassiveOutput(const MPlug& plug) const override;
public:
static MTypeId id;
// Attributes
static MObject aStartTime;
static MObject aCurrentTime;
static MObject aGravity;
static MObject aGravityX;
static MObject aGravityY;
static MObject aGravityZ;
static MObject aInMatrix;
static MObject aOutputTranslate;
static MObject aOutputTranslateX;
static MObject aOutputTranslateY;
static MObject aOutputTranslateZ;
private:
bool _isStartTime { false };
bool _isFirstTime { false };
MTime _lastTime { };
MTime _currentTime { };
MString _name { };
MMatrix _restMatrix {};
MMatrix _outputMatrix {};
MVector _velocity { 0, 0, 0 };
MCallbackIdArray _callbackIds;
};
MTypeId initialStateNode::id(0x80012);
MObject initialStateNode::aInMatrix;
MObject initialStateNode::aOutputTranslate;
MObject initialStateNode::aOutputTranslateX;
MObject initialStateNode::aOutputTranslateY;
MObject initialStateNode::aOutputTranslateZ;
MObject initialStateNode::aGravity;
MObject initialStateNode::aGravityX;
MObject initialStateNode::aGravityY;
MObject initialStateNode::aGravityZ;
MObject initialStateNode::aStartTime;
MObject initialStateNode::aCurrentTime;
initialStateNode::initialStateNode() {}
initialStateNode::~initialStateNode() {}
bool initialStateNode::isPassiveOutput(const MPlug& plug) const {
if (plug == aOutputTranslate ||
plug == aOutputTranslateX ||
plug == aOutputTranslateY ||
plug == aOutputTranslateZ) {
return true;
}
return false;
}
MStatus initialStateNode::initialize() {
MStatus status;
MFnNumericAttribute numFn;
MFnMatrixAttribute matFn;
MFnUnitAttribute uniFn;
aStartTime = uniFn.create("startTime", "stti", MFnUnitAttribute::kTime, 0.0, &status);
uniFn.setKeyable(true);
aCurrentTime = uniFn.create("currentTime", "cuti", MFnUnitAttribute::kTime, 0.0, &status);
uniFn.setKeyable(true);
aInMatrix = matFn.create("inMatrix", "inma");
aOutputTranslateX = uniFn.create("outputTranslateX", "outx", MFnUnitAttribute::kDistance, 0.0, &status);
aOutputTranslateY = uniFn.create("outputTranslateY", "outy", MFnUnitAttribute::kDistance, 0.0, &status);
aOutputTranslateZ = uniFn.create("outputTranslateZ", "outz", MFnUnitAttribute::kDistance, 0.0, &status);
aOutputTranslate = numFn.create("outputTranslate", "outr", aOutputTranslateX, aOutputTranslateY, aOutputTranslateZ);
uniFn.setWritable(false);
uniFn.setStorable(false);
aGravityX = uniFn.create("gravityX", "grvx", MFnUnitAttribute::kDistance, 0.0, &status);
aGravityY = uniFn.create("gravityY", "grvy", MFnUnitAttribute::kDistance, -981.0, &status);
aGravityZ = uniFn.create("gravityZ", "grvz", MFnUnitAttribute::kDistance, 0.0, &status);
aGravity = numFn.create("gravity", "grav", aGravityX, aGravityY, aGravityZ);
numFn.setKeyable(true);
addAttribute(aStartTime);
addAttribute(aCurrentTime);
addAttribute(aInMatrix);
addAttribute(aOutputTranslate);
addAttribute(aGravity);
attributeAffects(aGravity, aOutputTranslate);
attributeAffects(aCurrentTime, aOutputTranslate);
return MS::kSuccess;
}
/**
* Because we don't pull on the input matrix at the start frame
* we aren't able to store the original values from the input matrix.
* To cope with that, we read those whenever anything connects to it
*
*/
void onAttributeChanged(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug& otherPlug, void* clientData) {
if (msg & MNodeMessage::kConnectionMade) {
if (plug == initialStateNode::aInMatrix) {
auto* node = static_cast<initialStateNode*>(clientData);
MObject obj = otherPlug.asMObject();
node->setMatrix(MFnMatrixData(obj).matrix());
}
}
}
void initialStateNode::postConstructor() {
MStatus status { MS::kSuccess };
// Bypass warning; below callback takes a reference,
// even though it doesn't care for actually keeping
// it around. Bad design.
MObject tempobj { this->thisMObject() };
_callbackIds.append(
MNodeMessage::addAttributeChangedCallback(
tempobj, onAttributeChanged, this, &status
)
);
}
/**
* Restore rest matrix
*
*/
MStatus initialStateNode::computeStartState(const MPlug& plug, MDataBlock& datablock) {
MFnDependencyNode fn { thisMObject() };
_name = fn.name();
Print() << _name << ".computeStartState()\n";
MStatus status { MS::kSuccess };
_outputMatrix = _restMatrix;
_isFirstTime = true;
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Following the start time
*
*/
MStatus initialStateNode::computeFirstState(const MPlug& plug, MDataBlock& datablock) {
Print() << " " << _name << ".computeFirstState()\n";
MStatus status { MS::kUnknownParameter };
MMatrix matrix = datablock.inputValue(aInMatrix).asMatrix();
_restMatrix = matrix;
_outputMatrix = _restMatrix;
_velocity = { 0, 0, 0 };
_isFirstTime = false;
stepSimulation(datablock);
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Following the first frame
*
*/
MStatus initialStateNode::computeNextState(const MPlug& plug, MDataBlock& datablock) {
Print() << " " << _name << ".computeCurrentState()\n";
MStatus status { MS::kSuccess };
stepSimulation(datablock);
writeOutputTranslate(datablock);
datablock.setClean(plug);
return status;
}
/**
* Apply gravity and update position
*
*/
MStatus initialStateNode::stepSimulation(MDataBlock& datablock) {
MStatus status { MS::kSuccess };
MTransformationMatrix tm { _outputMatrix };
double deltaTime = (_currentTime - _lastTime).as(MTime::kSeconds);
if (deltaTime > 0.0) {
MVector gravity = datablock.inputValue(aGravity, &status).asDouble3();
_velocity += gravity * deltaTime;
tm.addTranslation(_velocity, MSpace::kTransform);
}
_outputMatrix = tm.asMatrix();
return status;
}
MStatus initialStateNode::writeOutputTranslate(MDataBlock& datablock) {
MStatus status { MS::kSuccess };
// Find local matrix
MTransformationMatrix tm { _outputMatrix };
MVector translate = tm.translation(MSpace::kTransform);
MDataHandle tx = datablock.outputValue(aOutputTranslateX),
ty = datablock.outputValue(aOutputTranslateY),
tz = datablock.outputValue(aOutputTranslateZ);
tx.set(translate.x);
ty.set(translate.y);
tz.set(translate.z);
return status;
}
MStatus initialStateNode::compute(const MPlug& plug, MDataBlock& datablock) {
MStatus status { MS::kUnknownParameter };
const MTime currentTime = datablock.inputValue(aCurrentTime).asTime();
const MTime startTime = datablock.inputValue(aStartTime).asTime();
_currentTime = currentTime;
_isStartTime = currentTime <= startTime;
if (plug == aOutputTranslate ||
plug == aOutputTranslateX ||
plug == aOutputTranslateY ||
plug == aOutputTranslateZ) {
if (_isStartTime) status = computeStartState(plug, datablock);
else if (_isFirstTime) status = computeFirstState(plug, datablock);
else status = computeNextState(plug, datablock);
}
_lastTime = currentTime;
return status;
}
MStatus initializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerNode("initialStateNode",
initialStateNode::id,
initialStateNode::creator,
initialStateNode::initialize);
if (!status) {
status.perror("registerNode");
return status;
}
Print() << "==============\n";
Print() << "Initialising\n";
return status;
}
MStatus uninitializePlugin(MObject obj) {
MStatus status;
MFnPlugin plugin(obj);
status = plugin.deregisterNode(initialStateNode::id);
if (!status) {
status.perror("deregisterNode");
return status;
}
Print() << "Deinitialising.\n";
Print() << "==============\n";
return status;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment