SOLVED Adding Node Descriptions Pragmatically

Hello Everyone,

I am trying to add some custom nodes following https://developers.maxon.net/docs/Cinema4DCPPSDK/html/page_handbook_node_implementation_programmatically.html in maxon SDK but I am having some build issues with this part on Add() function, which gives me build error.

Result<void> EndNode()
  {
    // Add the node to the database.
    iferr_scope;
    for (const auto& e : _definitions)
    {
      _db.Add(e.GetKey(), { _node, Id() }, e.GetValue()) iferr_return;
    }
    _definitions.Flush();
    return OK;
  }

What type of object is ProceduralNodeDataDescriptionDefinitionDatabaseImpl from the example of the doc as I implemented it of type DataDescriptionDefinitionDatabaseImplInterface but it does not have the Add() function that I need.

Am I missing something from the documentation or is there any other way to add the nodes in the database?

Thank you,

Hi,

As you understood, you need to implement a class that inherit from DataDescriptionDefinitionDatabaseImplInterface. You can extend your class adding your own methods. Do not use the MAXON_METHOD to define that function.

Here's an example of one implementation of that class. I will update the documentation. Of course you need to change the ID when you register the component.

class ProceduralNodeDataDescriptionDefinitionDatabaseImpl : public Component<ProceduralNodeDataDescriptionDefinitionDatabaseImpl, DataDescriptionDefinitionDatabaseImplInterface>
{
	MAXON_COMPONENT();

public:
	MAXON_METHOD Bool Contains(const Id& category, const IdAndVersion& dataType, const LanguageRef& language) const
	{
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		return _definitions.Contains(ToTuple(cat, dataType));
	}

	MAXON_METHOD Result<DataDescriptionDefinition> LoadDescription(LOADDESCRIPTIONMODE mode, const Id& category, const LanguageRef& language, const IdAndVersion& dataType, Id* foundInDatabase) const
	{
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		const DataDescriptionDefinition* def = _definitions.FindValue(ToTuple(cat, dataType));
		if (!def)
			return IllegalArgumentError(MAXON_SOURCE_LOCATION);
		if (foundInDatabase)
			*foundInDatabase = _id;
		return *def;
	}

	MAXON_METHOD Result<BaseArray<IdAndVersion>> GetRegisteredDescriptions(const Id& category, const LanguageRef& language) const
	{
		iferr_scope;
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		BaseArray<IdAndVersion> array;
		for (const Tuple<Id, IdAndVersion>& key : _definitions.GetKeys())
		{
			if (key.first == cat)
			{
				array.Append(key.second) iferr_return;
			}
		}
		return array;
	}

	MAXON_METHOD Result<BaseArray<LanguageRef>> GetRegisteredLanguages() const
	{
		iferr_scope;
		BaseArray<LanguageRef> array;
		array.CopyFrom(_languages) iferr_return;
		return array;
	}

	MAXON_METHOD Result<Tuple<Bool, DataDescriptionDefinition*>> StoreDescription(const Id& databaseId, const Id& category, const LanguageRef& language, const IdAndVersion& dataType, const DataDescriptionDefinition& description, const DataDescriptionDefinition& overwrittenDescription, Bool usePendingSystem)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<Bool> DeleteDescription(const Id& category, const LanguageRef& language, const IdAndVersion& dataType)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<void> WriteDataBases() const
	{
		return OK;
	}

	MAXON_METHOD Result<Int> RenameId(const Id& oldId, const Id& newId)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<Int> RenameAttribute(const InternedId& oldId, const InternedId& newId)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<Bool> CheckUniqueAttributeName(const InternedId& searchId) const
	{
		return true;
	}

	MAXON_METHOD Result<DataDescription> UpdateDescriptions(const Id& category, const IdAndVersion& dataTypeId, const LanguageRef& language) const
	{
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		const DataDescriptionDefinition* def = _definitions.FindValue(ToTuple(cat, dataTypeId));
		if (!def)
			return {};
		DataDescriptionDefinition d = *def;
		return DataDescriptionDefinitionDatabaseInterface::UpdateDescription(_id, category, language, dataTypeId, d, false);
	}

	MAXON_METHOD Bool IsWritableDatabase(const Id& languageId) const
	{
		return false;
	}

	MAXON_METHOD Int GetChangeCount() const
	{
		return 0;
	}

	MAXON_METHOD Result<void> Write() const
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	ResultOk<void> Init(const Id& id)
	{
		_id = id;
		return OK;
	}

	Result<void> Add(const Id& category, const IdAndVersion& asset, const DataDescriptionDefinition& def)
	{
		iferr_scope;
		if (category != DATADESCRIPTION_CATEGORY_DATA && category != DATADESCRIPTION_CATEGORY_UI)
		{
			LanguageRef lang = Resource::FindLanguage(LANGUAGE_ENGLISH_ID);
			if (lang)
			{
				_languages.Insert(lang) iferr_return;
			}
		}
		_definitions.Insert(ToTuple(category, asset), def) iferr_return;
		return OK;
	}

private:
	Id _id;
	HashMap<Tuple<Id, IdAndVersion>, DataDescriptionDefinition> _definitions;
	HashSet<LanguageRef> _languages;
};

MAXON_COMPONENT_CLASS_REGISTER(ProceduralNodeDataDescriptionDefinitionDatabaseImpl, "net.maxon.test.ddb");

Cheers,
Manuel

Hi,

As you understood, you need to implement a class that inherit from DataDescriptionDefinitionDatabaseImplInterface. You can extend your class adding your own methods. Do not use the MAXON_METHOD to define that function.

Here's an example of one implementation of that class. I will update the documentation. Of course you need to change the ID when you register the component.

class ProceduralNodeDataDescriptionDefinitionDatabaseImpl : public Component<ProceduralNodeDataDescriptionDefinitionDatabaseImpl, DataDescriptionDefinitionDatabaseImplInterface>
{
	MAXON_COMPONENT();

public:
	MAXON_METHOD Bool Contains(const Id& category, const IdAndVersion& dataType, const LanguageRef& language) const
	{
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		return _definitions.Contains(ToTuple(cat, dataType));
	}

	MAXON_METHOD Result<DataDescriptionDefinition> LoadDescription(LOADDESCRIPTIONMODE mode, const Id& category, const LanguageRef& language, const IdAndVersion& dataType, Id* foundInDatabase) const
	{
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		const DataDescriptionDefinition* def = _definitions.FindValue(ToTuple(cat, dataType));
		if (!def)
			return IllegalArgumentError(MAXON_SOURCE_LOCATION);
		if (foundInDatabase)
			*foundInDatabase = _id;
		return *def;
	}

	MAXON_METHOD Result<BaseArray<IdAndVersion>> GetRegisteredDescriptions(const Id& category, const LanguageRef& language) const
	{
		iferr_scope;
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		BaseArray<IdAndVersion> array;
		for (const Tuple<Id, IdAndVersion>& key : _definitions.GetKeys())
		{
			if (key.first == cat)
			{
				array.Append(key.second) iferr_return;
			}
		}
		return array;
	}

	MAXON_METHOD Result<BaseArray<LanguageRef>> GetRegisteredLanguages() const
	{
		iferr_scope;
		BaseArray<LanguageRef> array;
		array.CopyFrom(_languages) iferr_return;
		return array;
	}

	MAXON_METHOD Result<Tuple<Bool, DataDescriptionDefinition*>> StoreDescription(const Id& databaseId, const Id& category, const LanguageRef& language, const IdAndVersion& dataType, const DataDescriptionDefinition& description, const DataDescriptionDefinition& overwrittenDescription, Bool usePendingSystem)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<Bool> DeleteDescription(const Id& category, const LanguageRef& language, const IdAndVersion& dataType)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<void> WriteDataBases() const
	{
		return OK;
	}

	MAXON_METHOD Result<Int> RenameId(const Id& oldId, const Id& newId)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<Int> RenameAttribute(const InternedId& oldId, const InternedId& newId)
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	MAXON_METHOD Result<Bool> CheckUniqueAttributeName(const InternedId& searchId) const
	{
		return true;
	}

	MAXON_METHOD Result<DataDescription> UpdateDescriptions(const Id& category, const IdAndVersion& dataTypeId, const LanguageRef& language) const
	{
		Id cat = (category == DATADESCRIPTION_CATEGORY_STRING) ? language.GetIdentifier() : category;
		const DataDescriptionDefinition* def = _definitions.FindValue(ToTuple(cat, dataTypeId));
		if (!def)
			return {};
		DataDescriptionDefinition d = *def;
		return DataDescriptionDefinitionDatabaseInterface::UpdateDescription(_id, category, language, dataTypeId, d, false);
	}

	MAXON_METHOD Bool IsWritableDatabase(const Id& languageId) const
	{
		return false;
	}

	MAXON_METHOD Int GetChangeCount() const
	{
		return 0;
	}

	MAXON_METHOD Result<void> Write() const
	{
		return UnsupportedOperationError(MAXON_SOURCE_LOCATION);
	}

	ResultOk<void> Init(const Id& id)
	{
		_id = id;
		return OK;
	}

	Result<void> Add(const Id& category, const IdAndVersion& asset, const DataDescriptionDefinition& def)
	{
		iferr_scope;
		if (category != DATADESCRIPTION_CATEGORY_DATA && category != DATADESCRIPTION_CATEGORY_UI)
		{
			LanguageRef lang = Resource::FindLanguage(LANGUAGE_ENGLISH_ID);
			if (lang)
			{
				_languages.Insert(lang) iferr_return;
			}
		}
		_definitions.Insert(ToTuple(category, asset), def) iferr_return;
		return OK;
	}

private:
	Id _id;
	HashMap<Tuple<Id, IdAndVersion>, DataDescriptionDefinition> _definitions;
	HashSet<LanguageRef> _languages;
};

MAXON_COMPONENT_CLASS_REGISTER(ProceduralNodeDataDescriptionDefinitionDatabaseImpl, "net.maxon.test.ddb");

Cheers,
Manuel

I see. I thought it was an already defined function.
Thanks for the explanation.

Hello @m_magalhaes
I am replying to this thread as my following question is related to this.

After adding some of the nodes and some test parameters I am not getting the desired result or at least the one I was expecting.

Capture.PNG

As you can see from the image, for the same node I am getting different inputs on the node graph and attributes panel. The last material node added has 4 parameters, and thus all other previously added nodes get 4 "Input" named inputs, ignoring theirs.

In the resource editor, it shows the proper parameters added but on the node itself it is different. I am mostly using the code from the documentation but that does not seem to work properly or most probably I am doing something wrong.

ea7c5b5f-37f9-4761-9501-c016e1f3f7d3-image.png

Cheers.

hi,

I forked this thread. Even if it is related to the same project, the other thread was about adding node description with code, while this one is creating user node with the resource editor.
I have to say that the resource editor is "tricky" to learn, and the result will, sometime or at least at the beginning, not be the one you expect. But once you get used to it, it is ok.

I see in the screenshot of the resource editor that you created 3 entries and one output. Is that the correct screenshot or did you change something? This should not create 4 inputs. What do you want to create, 4 inputs or 3?

The resulting node depends also on what you selected as datatype for the port and what UI you have defined. You could define the datatype as float but the UI as Bool. That would result to have a checkbox instead of a fload field. Same for the input that have no field in the attribut manager, it could be the resut of selecting the wrong "gui type ID"

Without the database that could be hard to say.

Or maybe i did not understood what you are trying to achieve in your test.

Something that can also help sometimes, is to delete the "dc" folder that you have inside your preference folder. This is the cache of nodes. Only delete this directory during development only and if you think there is an update issue.

Cheers,
Manuel

Hello,

The node is the one created with code, not through the resource editor. I have managed to create the nodes fine using the resource editor but it is time-consuming and each time there is an update on the shader (we use osl shaders), we have to update the UI and it's parameters in the resource editor to match our shaders which is not what we want.

The node has only three inputs and one output added (all inputs are boolean), but when I create the node it also gets those "additional input named" inputs that I have not added. When I check on the resource editor for that node it shows the right inputs as in the image (only three booleans I have added). Both images are of the same node that is created through code.

Thank you.

One more thing to add, I have added a couple of nodes pragmatically each with a different number of input parameters. The last added node has 4 input parameters and that's the only node that shows correctly when created.
Other nodes seem to get those 4 inputs from the last node although not with the proper name (all are named "input" as seen from the image).
This "weirdness" is not reflected in the resource editor of that node though. It's like the node registered is global or static but I have not used something like that.

hi,

sorry i misunderstood.
i did tried it and there is an error on the example provided with our handbook. I was using the databaseID to register the node which is wrong. Even if for one node, it works.

If you used the same code to create several nodes, that could be the issue.

To create a node you must use a unique ID. Note that when adding the lambda in the CreateLazyTemplate function, the ID of the node is captured as a copy, not as reference. The reference will not be alive when this lambda is executed.

iferr_scope;
Id nodeId {"net.maxon.node.procedural.nodewithtwobool"};

b.BeginNode(nodeId);
b.SetMenuCategory(Id("net.maxon.nodecategory.math")) iferr_return;
b.SetName(LANGUAGE_ENGLISH_ID, "Handbook 2 English node name"_s) iferr_return;

b.BeginPort(PORT_DIR::INPUT, Id("testinput")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<Bool>().GetId()) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GROUPID, NODE::BASE::GROUP_INPUTS) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GUITYPEID, Id("net.maxon.ui.bool")) iferr_return;
b.Set(LANGUAGE_ENGLISH_ID, DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, "My first checkbox"_s) iferr_return;
b.EndPort() iferr_return;

b.BeginPort(PORT_DIR::INPUT, Id("testinput2")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<Bool>().GetId()) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GROUPID, NODE::BASE::GROUP_INPUTS) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GUITYPEID, Id("net.maxon.ui.bool")) iferr_return;
b.Set(LANGUAGE_ENGLISH_ID, DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, "My second checkbox"_s) iferr_return;
b.EndPort() iferr_return;

b.BeginPort(PORT_DIR::OUTPUT, Id("output")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<Bool>().GetId()) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GROUPID, NODE::BASE::GROUP_OUTPUTS) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GUITYPEID, Id("net.maxon.ui.bool")) iferr_return;
b.Set(LANGUAGE_ENGLISH_ID, DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, "Result"_s) iferr_return;
b.EndPort() iferr_return;

b.EndNode() iferr_return;

NodeTemplate t = NodesLib::CreateLazyTemplate(nodeId,
	[nodeId]() -> Result<NodeTemplate>
	{
		return NodesLib::BuildNodeFromDescription(nodeId, CoreNodesNodeSystemClass());
	}, CoreNodesNodeSystemClass()) iferr_return;

// Register the node.
GenericData res = BuiltinNodes::Register(nodeId, t) iferr_return;
g_proceduralNodes.Append(std::move(res)) iferr_return;


Of course i will fix the issue on the documentation

Cheers,
Manuel

Thank you. I was getting a unique ID indeed but had declared it as a global (I don't know how I missed it after checking couple of times) and using it like that in the lambda function was causing the issue.

All good now, thank you very much.

Cheers,
Ogers