Open Search
    Implementing Custom User Nodes

    Table of Contents

    Custom user nodes are containers of data created with the resource editor in Cinema 4D and provide a functionality similar to resource descriptions in the Classic API. A custom user node does not provide any logic, but is only a container for data. The logic for a node is being provided by a core node which can be connected to a custom user node. But that is not mandatory since a custom user node can also exist on its own. Such a node will then act like a dummy node, representing a set of data. For example, a texture node will hold some information - the texture path, the first frame, the last frame, and the result - but will just output that data without processing it.

    Code

    The following code is needed register the Description Processors that will be able to create and register the templates or our core nodes:

    static maxon::nodes::NodeSystemClass g_nodeSystemClass;
    // This description processor has to be used for all nodes of the example namespace unless they register themselves at the BuiltinNodes registry (such as DynamicNode).
    MAXON_DECLARATION_REGISTER(maxon::DescriptionProcessors, "net.maxonexample.handbook.nodespace.noderendererprocessor")
    {
    return maxon::nodes::NodesLib::CreateNodeDescriptionProcessor([] (const maxon::Id& descriptionId, const maxon::DataDescription& dataDescription) -> maxon::Result<maxon::nodes::NodeTemplate>
    {
    {
    return maxon::nodes::NodesLib::BuildNodeFromDescription(descriptionId, g_nodeSystemClass);
    }, g_nodeSystemClass);
    });
    }
    Definition: apibaseid.h:253
    Definition: resultbase.h:766
    static MAXON_METHOD Result< NodeTemplate > CreateLazyTemplate(const Id &nodeId, Delegate< Result< NodeTemplate >()> &&creator, Delegate< Result< Bool >(const NodeSystemClass &cls)> &&support={})
    static MAXON_METHOD Result< DescriptionProcessor > CreateNodeDescriptionProcessor(Delegate< Result< NodeTemplate >(const Id &descriptionId, const DataDescription &dataDescription)> &&delegate)
    static MAXON_METHOD Result< NodeSystemBasedNodeTemplate > BuildNodeFromDescription(const Id &id, const NodeSystemClass &nodeClass, Bool addDependencies=true, const Delegate< Result< void >(const MutableRoot &root, const TemplateArguments &args)> &finalizer={})
    #define MAXON_DECLARATION_REGISTER(...)
    Definition: module.h:933

    Resource Editor

    The string identifiers defined in the resource editor can be exported to symbol definitions in the form of header files.

    This has the advantage that the identifiers themselves can change over the development of a plugin, while their references in code, the symbols, do not have to. There are two options for generating such header files.

    • Writing header files: When running Cinema 4D with the startup argument g_autoExportHeaderPaths="{name_of_the_file}.h", modifications to a node database with the Resource Editor can be reflected in the header file defined in the startup argument. This write operation still has to be manually invoked and will write the identifiers of the database that are not being ignored into a header file.
    • Comparison tools: When running Cinema 4D with the startup argument g_externalCompareTool="{absolute_path_to}\{application}", modifications to a node database with the Resource Editor can be reflected with a comparison tool. Instead of writing the non-ignored identifiers to a specified file, a temporary file will be created that will the be opened alongside the original file in the comparison tool.

    To use this feature, compile the plugin, start Cinema 4D and open the Resource Editor.

    Switch the Resource Editor mode to "Developer Mode" in the menu.
    In "Database" select the custom database net.maxonexample.handbook.nodes.registereddatabase. The selected database should be the one registered for the plugin, as described in Registering Node Databases.net.maxonexample.handbook.nodes.registereddatabase
    Add a new data type by invoking "Data Type/Add Data Type" in the menu of the Resource Editor.
    Set the data type identifier to "net.maxonexample.handbook.noderenderer.usernode"
    Once a data type has been defined, attributes can be defined for it by running the menu command "Attribute/Add Attribute/New".
    In the new attribute, select "Command/Include" and set the field "Identifier" as "net.maxon.node.base". Check "Data", "UI" and "String". Select "net.maxonexample.handbook.noderenderer.usernode" again, in order to update the Resource Editor. This will include different attributes:
    • "net.maxon.object.base.name" defines the name of the node.
    • "net.maxon.object.base.tags" defines the tags which describe the node in the Asset Browser.
    • "net.maxon.object.base.annotations" defines the annotation of the node in the Asset Browser.
    To export the header for this node, the attribute we just included has to be ignored. We have to do this because the identifier, "net.maxon.object.base", is one provided by Cinema 4D and therefore already has a symbol; which can be found as maxon::NODE::BASE in nodes_corenodes_base.h. An attribute can be ignored by checking the field "Ignore on Header Export" in the "Development" tab of the attribute.
    Now the settings for exporting identifiers have to be defined. Clear the selection of the tree view by clicking into an empty area of the gadget and then in the view on the right:
    • Select "node" in "Classification",
    • set "Description Processor" to "net.maxonexample.handbook.nodespace.noderendererprocessor",
    • set "Menu Category" to "Uncategorized",
    • and select the header file to which the identifiers should be exported to with "Include file".
    Now a new attribute can be defined which will be exported:
    • Set "Command" to "attribute",
    • set "Identifier" to "net.maxonexample.handbook.node.usernode.out",
    • select "Yes", when an "Invalid Identifier" warning message box opens,
    • set "Datatype" to maxon::ColorA,
    • set "Classification" to "output",
    • set "Default Value" to some color,
    • set "String" to "Result".
    Once a file has been generated, the following information has to be added:
    • A header guard,
    • some includes, at least "maxon/fid.h", so that Maxon macros and data types can be used,
    • at the end of the file, but before the header guard #endif, add an include-statement to include the files generated by the source processor for the node-project.
      #include "<name_of_the_file>1.hxx"
      #include "<name_of_the_file>2.hxx"


    Final Code

    If all steps have been followed, the code should look like the following:

    #include "maxon/module.h"
    #include "maxon/nodeslib.h"
    static maxon::nodes::NodeSystemClass g_nodeSystemClass;
    // This description processor has to be used for all nodes of the example namespace unless they register themselves at the BuiltinNodes registry (such as DynamicNode).
    MAXON_DECLARATION_REGISTER(maxon::DescriptionProcessors, "net.maxonexample.handbook.nodespace.noderendererprocessor")
    {
    return maxon::nodes::NodesLib::CreateNodeDescriptionProcessor([] (const maxon::Id& descriptionId, const maxon::DataDescription& dataDescription) -> maxon::Result<maxon::nodes::NodeTemplate>
    {
    {
    return maxon::nodes::NodesLib::BuildNodeFromDescription(descriptionId, g_nodeSystemClass);
    }, g_nodeSystemClass);
    });
    }
    static maxon::Result<void> ConfigurePreviewImageRequest(maxon::DataDictionaryObjectRef request)
    {
    request.Set(maxon::nodes::PREVIEWIMAGEREQUEST::PROVIDER, PreviewImageProviderExampleHandbook::GetClass()) iferr_return;
    return maxon::OK;
    }
    MAXON_METHOD maxon::Result<void> NodeRendererNodeSpaceImpl::Init(maxon::DataDictionary spaceData)
    {
    _class = NodeRendererNodeSystemClassImpl::GetClass().Create() iferr_return;
    spaceData.Set(maxon::nodes::NODESPACE::NODESYSTEMCLASS, _class) iferr_return;
    super.Init(spaceData) iferr_return;
    return maxon::OK;
    }
    static maxon::Id g_nodeRendererDatabaseID = maxon::Id("net.maxonexample.handbook.nodes.registereddatabase");
    static maxon::GenericData g_exampleNodeSpace;
    static maxon::Result<void> LoadResources()
    {
    {
    err.CritStop();
    return err;
    };
    // Get plugin location
    const maxon::Url& binaryUrl = maxon::g_maxon.GetUrl();
    // Get plugin folder
    maxon::Url pluginDir = binaryUrl.GetDirectory();
    // Get resource folder (this folder must exist)
    const maxon::Url resourceUrl = pluginDir.Append("res"_s).Append("nodes"_s) iferr_return;
    // Register database
    // Register the node space
    // Use the same ID as in the resource files
    const maxon::Id spaceDescriptionId = maxon::Id("net.maxonexample.handbook.nodespace.node_renderer");
    // Load the description
    maxon::DataDictionary nodeRendererData = maxon::nodes::NodeSpaceHelpersInterface::LoadDescription(spaceDescriptionId) iferr_return;
    maxon::nodes::NodeSpaceRef nodeRendererSpace = NodeRendererNodeSpaceImpl::CreateInit(nodeRendererData) iferr_return;
    // Register the node space implementation
    g_exampleNodeSpace = maxon::nodes::MaterialNodeSpaces::Register(NodeRendererNodeSpaceImpl::GetDescriptor().GetId(), nodeRendererSpace) iferr_return;
    return maxon::OK;
    }
    static void FreeResources()
    {
    {
    err.CritStop();
    return;
    };
    // Unregister a database
    g_exampleNodeSpace = maxon::GenericData();
    g_nodeSystemClass = nullptr;
    }
    MAXON_INITIALIZATION(LoadResources, FreeResources);
    static MAXON_METHOD Result< void > RegisterDatabaseWithUrl(const Id &databaseId, const Url &url, const CString &version=CString::NullValue())
    static MAXON_METHOD Result< void > UnregisterDatabase(const Id &databaseId)
    Definition: genericdata.h:20
    Definition: url.h:952
    static MAXON_METHOD Result< DataDictionary > LoadDescription(const Id &spaceDescriptionId)
    return OK
    Definition: apibase.h:2690
    #define MAXON_INITIALIZATION(...)
    Definition: module.h:795
    #define MAXON_METHOD
    Definition: interfacebase.h:1001
    #define iferr_scope_handler
    Definition: resultbase.h:1402
    #define iferr_scope
    Definition: resultbase.h:1384
    #define iferr_return
    Definition: resultbase.h:1519