Providing Node Descriptions Programmatically

Table of Contents

Describes how to implement the description of a custom user node programmatically.

Most of the time, Custom user nodes descriptions are defined using the resource editor. But, for more advanced users that want to add lots of nodes, this is time consuming and repetitive. A new interface have been created and solve such an issue. With it, nodes descriptions can be created, defined and stored programmatically. The descriptions will be stored in a DataBase, just like with the resource editor. The interface allow to register this DataBase and manage it as nodes can be spread out on different DataBase. Node description can be defined in several DataDictionary representing several categories, It's not mandatory but highly recommended to create a class to help registering those DataDictionary. Once all the DataDictionary are build, the interface As those descriptions can be very specific and depend on users need, it's impossible to share a common base.

Code

First thing to do is create an implementation of DataDescriptionDefinitionDatabaseImplInterface and override all the MAXON_METHOD the interface declare. And of course, this implementation must be registered with MAXON_COMPONENT_CLASS_REGISTER

Once it's done, registering the database is just a few lines of code.

Id dbId("net.maxonexample.handbook.nodes.proceduraldatabase");
DataDescriptionDefinitionDatabaseImplRef db = ProceduralNodeDataDescriptionDefinitionDatabaseImpl::CreateInit(dbId) iferr_return;
ProceduralNodeDataDescriptionDefinitionDatabaseImpl* impl = ProceduralNodeDataDescriptionDefinitionDatabaseImpl::Get(db);
DataDescriptionDefinitionDatabaseInterface::RegisterDatabase(dbId, db) iferr_return;
ProceduralNodeBuilder b(*impl);
const Class< R > & Get(const Id &cls)
Definition: objectbase.h:2073
#define iferr_return
Definition: resultbase.h:1519

The helper class can look something like the code below.

class ProceduralNodeBuilder
{
public:
explicit ProceduralNodeBuilder(ProceduralNodeDataDescriptionDefinitionDatabaseImpl& db) : _db(db)
{
}
void BeginNode(const Id& node)
{
// Just flush our hashmap of definitions and store the ID of the node we want to create.
_definitions.Flush();
}
Result<DataDescriptionDefinition&> GetDefinition(const Id& category)
{
// Retrieve the DataDescriptionDefinition corresponding to a category
Bool created = false;
DataDescriptionDefinition& def = _definitions.InsertKey(category, created) iferr_return;
if (created)
{
{
DataDictionary info;
info.Set(DESCRIPTION::DATA::INFO::CLASSIFICATION, DESCRIPTION::DATA::INFO::CLASSIFICATION.ENUM_NODE) iferr_return;
def.SetInfo(info) iferr_return;
}
InternedId baseInclude;
baseInclude.Init(NODE::BASE::GetId()) iferr_return;
DataDictionary dd;
dd.Set(DESCRIPTION::DATA::BASE::INCLUDE, baseInclude.Get()) iferr_return;
def.AddEntry(dd) iferr_return;
}
return def;
}
Result<void> SetMenuCategory(const Id& mc)
{
// Retrieve the DataDescriptionDefinition and add a new DartaDictionary for the menu category.
DataDescriptionDefinition& def = GetDefinition(DATADESCRIPTION_CATEGORY_UI) iferr_return;
DataDictionary info;
info.Set(DESCRIPTION::UI::INFO::MENUCATEGORY, Id("net.maxon.nodecategory.math")) iferr_return;
def.SetInfo(info) iferr_return;
return OK;
}
Result<void> SetName(const Id& language, const String& name)
{
// Retrieve the DataDescriptionDefinition and add a new DartaDictionary for the name of the node.
DataDescriptionDefinition& def = GetDefinition(language) iferr_return;
DataDictionary dd;
dd.Set(DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, name) iferr_return;
def.AddEntry(dd) iferr_return;
return OK;
}
Result<void> BeginPort(PORT_DIR dir, const Id& portId)
{
// Flush the port's hashmap and store its InternId.
_portDir = dir;
_portDefinitions.Flush();
return _port.Init(portId);
}
Result<DataDictionary&> GetPortDictionary(const Id& category)
{
// Retrieve the port's Dictionary. It will create it if it doesn't exist.
Bool created = false;
DataDictionary& dict = _portDefinitions.InsertKey(category, created) iferr_return;
if (created)
{
dict.Set(DESCRIPTION::DATA::BASE::CLASSIFICATION, _portDir == PORT_DIR::INPUT ? DESCRIPTION::DATA::BASE::CLASSIFICATION.ENUM_INPUT : DESCRIPTION::DATA::BASE::CLASSIFICATION.ENUM_OUTPUT) iferr_return;
}
return dict;
}
template <typename ATTR> Result<void> Set(const Id& category, const ATTR& attr, const typename ATTR::ValueType& value)
{
// Retrieve the category"s and add an attribute.
DataDictionary& dict = GetPortDictionary(category) iferr_return;
dict.Set(attr, value) iferr_return;
return OK;
}
Result<void> EndPort()
{
// Add the port to DataDescriptionDefinition.
for (const auto& e : _portDefinitions)
{
DataDescriptionDefinition& def = GetDefinition(e.GetKey()) iferr_return;
def.AddEntry(e.GetValue()) iferr_return;
}
_portDefinitions.Flush();
return OK;
}
Result<void> EndNode()
{
// Add the node to the database.
for (const auto& e : _definitions)
{
_db.Add(e.GetKey(), { _node, Id() }, e.GetValue()) iferr_return;
}
_definitions.Flush();
return OK;
}
private:
ProceduralNodeDataDescriptionDefinitionDatabaseImpl& _db;
Id _node;
HashMap<Id, DataDescriptionDefinition> _definitions;
InternedId _port;
PORT_DIR _portDir;
HashMap<Id, DataDictionary> _portDefinitions;
};
#define Set(a0, a1, a2, a3, a4, a5)
Definition: Python-ast.h:586
PyObject * value
Definition: abstract.h:715
const char const char * name
Definition: abstract.h:195
Definition: c4d_string.h:39
PyObject PyObject * dict
Definition: abstract.h:150
COMMAND
Command. (CommandData)
Definition: ge_prepass.h:4
maxon::Bool Bool
Definition: ge_sys_math.h:55
IDENTIFIER
An arbitrary symbol that is neither a string, nor a value. It may contain unicode characters.
Definition: lexer.h:6
Py_ssize_t * e
Definition: longobject.h:89
PORT_DIR
Definition: graph_basics.h:34
const Id DATADESCRIPTION_CATEGORY_UI
Definition: datadescriptiondatabase.h:16
const Id DATADESCRIPTION_CATEGORY_DATA
Definition: datadescriptiondatabase.h:15
struct _node node
INPUT
Definition: parametertype.h:1
PyObject struct PyModuleDef * def
Definition: pycore_pystate.h:130
_Py_clock_info_t * info
Definition: pytime.h:197
#define iferr_scope
Definition: resultbase.h:1384
Definition: node.h:10
#define NAME
Definition: token.h:14

Once this helper class is done, creating a new node is as simple as that

// Begin a new node, this will flush the arrays used in our helper class.
// After that it's just a matter of defining port and attributes just like in the resource editor.
const Id firstNodeId {"net.maxon.node.procedural.firstexample"};
b.BeginNode(firstNodeId);
b.SetName(LANGUAGE_ENGLISH_ID, "Handbook English node name"_s) iferr_return;
b.SetMenuCategory(Id("net.maxon.nodecategory.math")) iferr_return;
b.BeginPort(PORT_DIR::INPUT, Id("testinput")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<AFloat>().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.number")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::NET::MAXON::UI::NUMBER::SLIDER, true) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::ADDMINMAX::LIMITVALUE, true) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::ADDMINMAX::MINVALUE, Data(Float(0.0_f))) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::ADDMINMAX::MAXVALUE, Data(Float(10.0_f))) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::ADDMINMAX::STEPVALUE, Data(Float(0.1_f))) iferr_return;
b.Set(LANGUAGE_ENGLISH_ID, DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, "Localized port name"_s) iferr_return;
b.EndPort() iferr_return;
b.BeginPort(PORT_DIR::INPUT, Id("display.other.ports")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<Bool>().GetId()) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, NODE::ATTRIBUTE::CONSTANTPARAMETER, true) 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, "Show Other Ports"_s) iferr_return;
b.EndPort() iferr_return;
b.BeginPort(PORT_DIR::INPUT, Id("time")) iferr_return;
b.Set(LANGUAGE_ENGLISH_ID, DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, "Time"_s) iferr_return;
b.Set(maxon::DATADESCRIPTION_CATEGORY_DATA, maxon::DESCRIPTION::DATA::BASE::DATATYPE, maxon::GetDataType<maxon::TimeValue>().GetId()) iferr_return;
maxon::TimeValue defaultTime = 0.0_min;
defaultTime.Quantize(30.0);
b.Set(maxon::DATADESCRIPTION_CATEGORY_DATA, maxon::DESCRIPTION::DATA::BASE::UNIT, maxon::DESCRIPTION::DATA::BASE::UNIT.ENUM_TIME) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_UI, DESCRIPTION::UI::BASE::GROUPID, NODE::BASE::GROUP_INPUTS) iferr_return;
b.Set(maxon::DATADESCRIPTION_CATEGORY_UI, maxon::DESCRIPTION::UI::BASE::GUITYPEID, maxon::Id("net.maxon.ui.number.timevalue")) iferr_return;
b.Set(maxon::DATADESCRIPTION_CATEGORY_UI, maxon::DESCRIPTION::UI::BASE::SHOW, maxon::CString("display.other.ports")) iferr_return;
b.EndPort() iferr_return;
b.BeginPort(PORT_DIR::INPUT, Id("otherinput")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<Bool>().GetId()) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, NODE::ATTRIBUTE::CONSTANTPARAMETER, true) 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, "Flag"_s) iferr_return;
b.EndPort() iferr_return;
b.BeginPort(PORT_DIR::OUTPUT, Id("testoutput")) iferr_return;
b.Set(DATADESCRIPTION_CATEGORY_DATA, DESCRIPTION::DATA::BASE::DATATYPE, GetDataType<String>().GetId()) iferr_return;
b.Set(LANGUAGE_ENGLISH_ID, DESCRIPTION::STRING::BASE::TRANSLATEDSTRING, "Output port"_s) iferr_return;
b.EndPort() iferr_return;
// EndNode will add an entry to our database.
b.EndNode() iferr_return;
@ DEFAULTVALUE
Dummy value for the default value GeData constructor.
Definition: c4d_gedata.h:65
Definition: string.h:1490
Definition: apibaseid.h:253
The TimeValue class encapsulates a timer value.
Definition: timevalue.h:33
void Quantize(Float64 frameRate)
quantize the time for a given frame rate, so that its frame value is a multiple of the specified fram...
maxon::Float Float
Definition: ge_sys_math.h:66
OUTPUT
Output flags for OutputWithFlags and DebugOutput.
Definition: debugdiagnostics.h:53
static const Id LANGUAGE_ENGLISH_ID
Definition: stringresource.h:62

Of course the template must be generated and registered

NodeTemplate t = NodesLib::CreateLazyTemplate(firstNodeId,
[firstNodeId]() -> Result<NodeTemplate>
{
return NodesLib::BuildNodeFromDescription(firstNodeId, CoreNodesNodeSystemClass());
}, CoreNodesNodeSystemClass()) iferr_return;
// Register the node.
GenericData res = BuiltinNodes::Register(firstNodeId, t) iferr_return;
g_proceduralNodes.Append(std::move(res)) iferr_return;
Py_UCS4 * res
Definition: unicodeobject.h:1113