Search code examples
c++callbackmayamaya-api

Material change callback (C++)


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!


Solution

  • 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
    );