How to Add Enum Values to a Node Attribute?

Dear Community,

This question reached us via mail and since answering it does not require confidential information and since I thought others find this interesting too, I am sharing my answer here. The question was How to add enum values dynamically to a node attribute that uses an Enum GUI?".

Find my answer below.

Cheers,
Ferdinand

Result:
enum_values.gif

Code:

#include "c4d_basematerial.h"
#include "maxon/datadescription_nodes.h"
#include "maxon/graph.h"
#include "maxon/graph_helper.h"
#include "maxon/nodesgraph.h"
#include "maxon/nodesystem.h"

namespace maxon 
{
  /// @brief Demonstrates how to modify the enum values of an attribute.
  /// @details One place where one could do this is inside the #::InstantiateImpl method of a 
  /// #NodeTemplateInterface one is implementing so that every new node is being populated with
  /// enum values of our liking. But as demonstrated by the example below, nothing prevents us
  /// from doing the same thing at runtime on a graph.
  Result<void> AddEnumValues(BaseDocument* doc)
  {
    iferr_scope;
    
    // Get the active material's node graph and start a transaction.
    NodeMaterial* const material = static_cast<NodeMaterial*>(doc->GetActiveMaterial());
    CheckArgument(material);
    nodes::NodesGraphModelRef graph = material->GetGraph(GetActiveNodeSpaceId()) iferr_return;

    GraphTransaction transaction = graph.BeginTransaction() iferr_return;
    {
      // Get the mutable root for #graph. This way is not only shorter than first getting the
      // mutable node system for #graph and then its mutable root, but also the only way that
      // actually works here. We can do this because starting a transaction on a graph model also
      // implies modifying the node system. So, we do not have to call NodeSystem::BeginModification
      // in this case.
      nodes::MutableNode root = nodes::ToMutableNode(graph.GetRoot()) iferr_return;

      // Iterate over all children of the root to get hold of nodes which have our port.
      for (auto node : root.GetChildren())
      {
        // Attempt to get hold of our enum port.
        nodes::MutablePort enumPort = node.GetInputs().FindPort(
          Id("in@eSp1K8T8GNcuPwKSds8Lvs")) iferr_return;

        // We could also add a non-existing port here with MutableNode::AddPort
        if (!enumPort || !enumPort.IsValid())
          continue;

        // NodeTemplateInterface::InstantiateImpl code would start here.

        // Set the data type and label of the port, doing this is obviously optional.
        enumPort.SetType<String>() iferr_return;
        enumPort.SetValue(NODE::BASE::NAME, "My Enumeration"_s) iferr_return;

        // Build the enum data and write it into the port.
        BaseArray<Tuple<Id, Data>> entries;
        for (const Int32 i : {1, 2, 3, 4, 5})
        {
          const String data = FormatString("Item @", i);
          const Id id = Id::Create(label) iferr_return;
          entries.Append(Tuple<Id, Data>(id, data)) iferr_return;
        }

        DataDictionary enumPortData;
        enumPortData.Set(DESCRIPTION::DATA::BASE::ENUM, entries) iferr_return;
        enumPort.SetValue(nodes::PortDescriptionData, std::move(enumPortData)) iferr_return;

        // And set the default value of the port.
        enumPort.SetDefaultValue("Item 1"_s) iferr_return;
      }
    // Commit the transaction and with it the node system modification.
    } transaction.Commit() iferr_return;

    return OK;
  }
}

MAXON SDK Specialist
developers.maxon.net