I'm currently working on a custom viewport in Maya 2018 and 2019. At the moment, I'm implementing my material parser to parse Maya material to my own framework's materials.
However, I'm quite struggling with creating callbacks. I already managed to get the initial callback to work, which can be seen in the code snippet below. This is just a callback to load meshes and materials when they get added to the scene.
MCallbackId material_added_id = MDGMessage::addNodeAddedCallback(
MaterialAddedCallback,
"mesh",
m_material_parser.get(),
&status
);
In this callback function, I get all shaders that are connected to this mesh and try to attach a callback to any changes that are made to the shader object (see code snippet below).
void wmr::MaterialParser::Parse(const MFnMesh& mesh)
{
// ...
MObjectArray shaders; // References to the shaders used on the meshes
MIntArray material_indices; // Indices to the materials in the object array
// Get all attached shaders for this instance
mesh.getConnectedShaders(instance_index, shaders, material_indices);
switch (shaders.length())
{
// No shaders applied to this mesh instance
case 0:
{
}
break;
// All faces use the same material
case 1:
{
MPlug surface_shader = GetSurfaceShader(shaders[0]);
// Invalid surface shader
if (!surface_shader.has_value())
return;
// Find all plugs that are connected to this shader
MPlugArray connected_plugs;
surface_shader.value().connectedTo(connected_plugs, true, false);
// Could not find a valid connected plug
if (connected_plugs.length() != 1)
return;
auto shader_type = GetShaderType(connected_plugs[0].node());
// Shader type not supported by this plug-in
if (shader_type == detail::SurfaceShaderType::UNSUPPORTED)
return;
// Found a Lambert shader
if (shader_type == detail::SurfaceShaderType::LAMBERT)
{
MGlobal::displayInfo("Found a Lambert shader!");
MPlug color_plug = GetPlugByName(connected_plugs[0].node(), "color");
// Retrieve the texture associated with this plug
auto texture_path = GetPlugTexture(color_plug);
// Print the texture location
MGlobal::displayInfo(texture_path.asChar());
// Add callback that filters on material changes
MStatus status;
MCallbackId attributeId = MNodeMessage::addAttributeChangedCallback(
shaders[0],
MaterialCallback,
this,
&status
);
CallbackManager::GetInstance().RegisterCallback(attributeId);
}
}
break;
// Two or more materials are used
default:
{
// ...
break;
}
}
}
void MaterialCallback(MNodeMessage::AttributeMessage msg, MPlug &plug, MPlug &otherPlug, void *clientData)
{
MGlobal::displayInfo("Hey! Im a material callback!");
}
Notice the switch-case. Especially case 1:
, when there is one shader found. "Found Lambert"
is printed in the output when a lambert shader was found. After that, I attach a callback addAttributeChangedCallback
to the shader object. However, this is never triggered.
The output is shown below (after changing the transparency and color value sometimes). As you can see, "Hey! Im a material callback!"
is not printed anywhere, but it should be called when I change an attribute from the material.
// Found a Lambert shader!
//
select -r pCylinder1 ;
setAttr "lambert1.transparency" -type double3 0.0779221 0.0779221 0.0779221 ;
setAttr "lambert1.transparency" -type double3 0.194805 0.194805 0.194805 ;
setAttr "lambert1.color" -type double3 0.0779221 0.0779221 0.0779221 ;
So, I'm not sure what I'm doing wrong here, as there aren't many (or any) samples about this issue as far as I can find.
Some help would be very much appreciated. Thanks in advance!
I've solved my issue myself. For the sake of frustration that the answer has not given by the questioner, I'll tell what the issue is :)
So, there were two issues with the implementation. First of all, I was binding the callback to the incorrect object. I was binding the callback to shader[0]
. This connected shader is a shader group with a surface shader, displacement shader etc. The attributes of this shader[0]
do not change when I change a property of the Lambert shader. So instead, I have to bind the callback to the connected_plug[0]
(this is a plug that is connected to shader[0]
). The connected_plug[0]
is the surface shader node from the shader group that has been received eralier.
Secondly, I was using the incorrect callback function. addAttributeChangedCallback
turned out to be the wrong factory function. Instead, I had to use addNodeDirtyCallback
because the object that I bind the callback to is a node/plug.
MObjectArray shaders;
mesh.getConnectedShaders(instance_index, shaders, material_indices);
// ...
MPlugArray connected_plugs;
surface_shader.value().connectedTo(connected_plugs, true, false);
// ...
MObject connected_plug = connected_plugs[0].node();
// ...
// Add callback that filters on material changes
MStatus status;
MCallbackId attributeId = MNodeMessage::addNodeDirtyCallback(
connected_plug,
DirtyNodeCallback,
this,
&status
);