Open Search
    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