SOLVED Changing description of material nodes dynamically

Hi,

I have two questions regarding the descriptions of nodes.

My first question is, if it is possible and how to change some description property of a node port, based on the value of another constant port. As an example, in a NodeData::GetDDescription(..) one can easily read a parameter and change the description of another parameter (e.g. change the name of one parameter based on a checkbox).
Is something similar possible to do in material nodes (or nodes in general) and how?

My second question is related to the first one in regards of dynamic changes of nodes. I tried using a custom GUI implementation based on the example.nodes plugin in the SDK. In particular I used the implementation in example.nodes/source/gui/customgui_string.cpp as a reference for using a custom GUI. I found that the getDataCallback parameter delegate can be used to actually get the GraphNode for a node in UiConversionCustomGuiString::CreateC4DDescription(..). But my question is if there is a way to trigger this function to be called again upon changes on the node, to allow dynamic updates on the node based on port value in the node?

Thanks in advance 🙂

Hi and sorry for the late reply,

you can react to a change in a node parameter registering a changed message with RegisterValueChangedMessage
You need to define the Node ID and the port. If you change some parameter this will call an update of the UI and CreateC4DDescription will be called.

From there you can achieve what you want. I did not try but as you said, in the customgui_string you can defined parameter for the custom GUI.

Let me know if it is not enough.

#include "maxon/datadescriptiondefinitiondatabase.h"
#include "maxon/datadescription_ui.h"
#include "maxon/datadescription_nodes.h"
#include "maxon/graph.h"
#include "customnode-customnodespace_descriptions.h"

static maxon::Result<void> ColorChanged(const maxon::DataDictionary& userData, maxon::DataDictionary& multiSelectionStorage)
{
	iferr_scope;

	maxon::GraphModelRef graph = userData.Get(maxon::ARGUMENTS::NODECORE::GRAPHMODEL) iferr_return;
	// Node that owns the port (NODECORE::PORTPATH).
	maxon::IoNodePath nodePath = userData.Get(maxon::ARGUMENTS::NODECORE::NODEPATH) iferr_return;
	// Internal port on which the callback was registered.
	maxon::NodePath innerPortPath = userData.Get(maxon::ARGUMENTS::NODECORE::INNERPORTPATH) iferr_return;

	// Should always just be valid if the callback is fired.
	CheckAssert(graph);
	CheckAssert(nodePath.first.IsPopulated());
	CheckAssert(innerPortPath.IsPopulated());




	maxon::GraphNode currentNode = graph.GetNode(nodePath.first);
	if (!currentNode.IsValid())
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	const maxon::GraphNode colorA = graph.GetNode(innerPortPath);
	if (!colorA.IsValid())
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);
	const maxon::GraphNode hybridPort = currentNode.GetInputs().FindChild(maxonexample::NODE::USERNODE::HYBRID) iferr_return;
	if (!colorA.IsValid())
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	const maxon::ColorA colorAValue = colorA.GetDefaultValue(maxon::ColorA(0)) iferr_return;
	if (colorAValue.r > 0.5)
	{
		// Make the graph modifiable.
		maxon::GraphTransaction trans = graph.BeginTransaction() iferr_return;

		hybridPort.SetValue(maxon::NODE::BASE::NAME, "Code Red"_s) iferr_return;

		// Apply the change to the graph.
		return trans.Commit();
	}
	return maxon::OK;
}


MAXON_INITIALIZATION(
	[]() -> maxon::Result<void>
	{
		iferr_scope;
		maxon::DataDescriptionDefinitionDatabaseInterface::RegisterValueChangedMessage(
			maxonexample::NODE::USERNODE::GetId(), maxonexample::NODE::USERNODE::COLORA,
			maxon::DescriptionMessageFunction(maxon::DESCRIPTION::UI::BASE::COMMANDCONTEXT.ENUM_NIMBUSCORE, nullptr, nullptr, ColorChanged)) iferr_return;
		return maxon::OK;
	}, nullptr);

Cheers,
Manuel

Hi and sorry for the late reply,

you can react to a change in a node parameter registering a changed message with RegisterValueChangedMessage
You need to define the Node ID and the port. If you change some parameter this will call an update of the UI and CreateC4DDescription will be called.

From there you can achieve what you want. I did not try but as you said, in the customgui_string you can defined parameter for the custom GUI.

Let me know if it is not enough.

#include "maxon/datadescriptiondefinitiondatabase.h"
#include "maxon/datadescription_ui.h"
#include "maxon/datadescription_nodes.h"
#include "maxon/graph.h"
#include "customnode-customnodespace_descriptions.h"

static maxon::Result<void> ColorChanged(const maxon::DataDictionary& userData, maxon::DataDictionary& multiSelectionStorage)
{
	iferr_scope;

	maxon::GraphModelRef graph = userData.Get(maxon::ARGUMENTS::NODECORE::GRAPHMODEL) iferr_return;
	// Node that owns the port (NODECORE::PORTPATH).
	maxon::IoNodePath nodePath = userData.Get(maxon::ARGUMENTS::NODECORE::NODEPATH) iferr_return;
	// Internal port on which the callback was registered.
	maxon::NodePath innerPortPath = userData.Get(maxon::ARGUMENTS::NODECORE::INNERPORTPATH) iferr_return;

	// Should always just be valid if the callback is fired.
	CheckAssert(graph);
	CheckAssert(nodePath.first.IsPopulated());
	CheckAssert(innerPortPath.IsPopulated());




	maxon::GraphNode currentNode = graph.GetNode(nodePath.first);
	if (!currentNode.IsValid())
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	const maxon::GraphNode colorA = graph.GetNode(innerPortPath);
	if (!colorA.IsValid())
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);
	const maxon::GraphNode hybridPort = currentNode.GetInputs().FindChild(maxonexample::NODE::USERNODE::HYBRID) iferr_return;
	if (!colorA.IsValid())
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	const maxon::ColorA colorAValue = colorA.GetDefaultValue(maxon::ColorA(0)) iferr_return;
	if (colorAValue.r > 0.5)
	{
		// Make the graph modifiable.
		maxon::GraphTransaction trans = graph.BeginTransaction() iferr_return;

		hybridPort.SetValue(maxon::NODE::BASE::NAME, "Code Red"_s) iferr_return;

		// Apply the change to the graph.
		return trans.Commit();
	}
	return maxon::OK;
}


MAXON_INITIALIZATION(
	[]() -> maxon::Result<void>
	{
		iferr_scope;
		maxon::DataDescriptionDefinitionDatabaseInterface::RegisterValueChangedMessage(
			maxonexample::NODE::USERNODE::GetId(), maxonexample::NODE::USERNODE::COLORA,
			maxon::DescriptionMessageFunction(maxon::DESCRIPTION::UI::BASE::COMMANDCONTEXT.ENUM_NIMBUSCORE, nullptr, nullptr, ColorChanged)) iferr_return;
		return maxon::OK;
	}, nullptr);

Cheers,
Manuel

Hi @m_magalhaes, this is exactly what I was looking for. It works like a charm for my first usecase.

But for the second part I encountered just a slight inconvenience. In my second usecase I actually don't want to change parameter name, I just want CreateC4DDescription(..) to be called again. From what I tested, if the name is set with the same value, the description creation is not triggered. I also tried changing another (seemingly irrelevant) attribute of the node, but that did not trigger a description change either (in particular I tried changing maxon::NODE::BASE::COMMENT).
Finally, I tried adding a new hidden port, that is not actually used, just to change its name when the description change function should be called - and this seems to work as expected. I was just wondering if there is a better solution that may achieve the same result without a dummy port?

Cheers,
Deyan

I forgot an important point; the message will only be called at edit time (if the user changes the value). If the field is animated, the message will not get triggered.

About just triggering the function i need to check if we can do something with timestamp.

Another small question that came to mind - this registration function RegisterValueChangedMessage(..) seems to have been added in R26 SDK - is there an alternative for earlier SDK versions?

@deyan said in Changing description of material nodes dynamically:

is there an alternative for earlier SDK versions?

In our example, you have the files nodessystem_observer.h/cpp this shows how to create a ticket system. This system is based on observable that you can use, those observables are accessible in the GraphModelInterface or the NodeSystemManagerInterface.

  • ObservableTransactionStarted
  • ObservableTransactionCommitted
  • ObservableTransactionRolledBack

You have another example in our manual

The problem with observable is that you need to be sure that your function is registered or unregistered the right way/time. You need a proper system to handle those "tickets".
For now, you only have this "basic system".

Cheers,
Manuel