Open Search
    Implementing Custom Node Spaces

    Table of Contents

    Describes how to create a custom node space by implementing an interface and creating the required description with the Resource Editor. A node space defines what node types will be compatible with a node system.

    Resource Editor

    A custom node space requires description resources which must be loaded on the startup of Cinema 4D. In order to be able to create the required description files, Cinema 4D has to be launched with the following parameters:

    • g_applicationRepositoryWritable=true
    • g_developerNodeEditorFunctions=true
    • g_descriptionEditorDeveloperMode=true

    For successfully running a Cinema 4D instance to modify such a resource, one has to compile a work-in-progress state of a plugin which already does contain the database registration steps as described in Registering Node Databases. Then a Cinema 4D instance should be run with the required startup parameters and work-in-progress plugin registered. In such an instance the Resource Editor can be used to define and modify the resources related to a plugin. In order to create a custom node space with the Resource Editor, one has to take the following steps:

    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.
    Add a new data type by invoking "Data Type/Add Data Type" in the menu of the Resource Editor.
    In order to be distinguishable for Cinema 4D, all data types have to be identified with a unique identifier. "net.maxonexample.handbook.nodespace.node_renderer" will be the identifier used in this example.
    Once a data type has been defined, attributes can be defined for it by running the menu command "Attribute/Add Attribute/New".
    In a newly created attribute under the drop-down element "Command" the value "include" has to be selected. Where under the heading "Data", the value "Identifier" should then be set as "net.maxon.nodespace.base". This will include all the attributes that are defined in the data type net.maxon.nodespace.base; a predefined data type that is being shipped with Cinema 4D.
    Select "Data" and "String" as the attributes to be included for the newly defined data type.
    In the drop-down menu, "net.maxonexample.handbook.nodespace.node_renderer" has to be selected again in order to refresh the dialog and finalize including all attributes. The display name of a node space or its translations can be defined with the field "String" under the element-identifier "net.maxon.object.base.name".
    A database can be saved from the Resource Editor menu with "Database/Save All", writing the database that has been defined into JSON format.
    The node space can now be selected in the drop-down menu on the top left of the interface of Cinema 4D.

    Code

    Now that a description for the node space has been defined, a NodeSystemClassInterface and NodeSpaceInterface can be implemented for it. Both need an unique identifier.

    A NodeSystemClassInterface should be implemented and registered first:

    class NodeRendererNodeSystemClassImpl : public maxon::Component<NodeRendererNodeSystemClassImpl, maxon::nodes::NodeSystemClassInterface>
    {
    MAXON_COMPONENT(FINAL, maxon::nodes::BaseCoreNodesNodeSystemClass().GetClass());
    public:
    MAXON_METHOD maxon::Result<maxon::Bool> SupportsImpl(const maxon::nodes::NodeTemplate& templ) const
    {
    const maxon::Id& id = templ.GetId();
    const maxon::String idString = id.ToString();
    if (idString.StartsWith("net.maxon"_s))
    return true;
    return super.SupportsImpl(templ);
    }
    MAXON_METHOD maxon::Result<maxon::Id> SubstituteVariant(const maxon::nodes::NodeTemplate& templ, const maxon::Block<const maxon::Id>& variant, const maxon::Block<const maxon::Id>& options) const
    {
    return { };
    }
    };
    Definition: block.h:423
    Definition: objectbase.h:2651
    static const Class< typename INTERFACE::Hxx1::ReferenceClass > & GetClass()
    Definition: objectbase.h:2733
    Definition: apibaseid.h:253
    Definition: resultbase.h:766
    Definition: string.h:1235
    #define MAXON_COMPONENT(KIND,...)
    Definition: objectbase.h:2212
    #define MAXON_METHOD
    Definition: interfacebase.h:1001

    Followed by a node space implementation based on NodeSpaceInterface; which also has to be registered:

    class NodeRendererNodeSpaceImpl : public maxon::Component<NodeRendererNodeSpaceImpl, maxon::nodes::NodeSpaceInterface>
    {
    MAXON_COMPONENT(NORMAL, maxon::nodes::NodeSpaceBaseClass);
    public:
    MAXON_METHOD maxon::Result<void> Init(maxon::DataDictionary spaceData);
    private:
    maxon::nodes::NodeSystemClass _class;
    };
    @ NORMAL
    Samples the surface as the user moves over it the SculptObject and returns a new hit point and normal...

    Now the node space has to be registered and unregistered. To do that, one can extend the previously created LoadResources() and FreeResources() functions. Make sure to register the NodeSpace after having registered the NodeSpace definition database. The node space identifier is defined in the description resource that has been defined earlier.

    // 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;
    static MAXON_METHOD Result< DataDictionary > LoadDescription(const Id &spaceDescriptionId)
    #define iferr_return
    Definition: resultbase.h:1519

    The global node space resource should be overwritten with a new maxon::GenericData instance when a node space is freed in the context of the macro MAXON_INITIALIZATION.

    g_exampleNodeSpace = maxon::GenericData();
    Definition: genericdata.h:20

    Final Code

    If all the steps have been followed, the code should look like this. The .h file

    #ifndef _NODESPACE_H__
    #define _NODESPACE_H__
    // This is the ID of a particular render
    static maxon::Int32 g_nodeRendererId = 1052839;
    // Implementation of NodeSystemClassInterface
    static maxon::Id g_nodeRendererNodeSystemClass = maxon::Id("net.maxonexample.handbook.class.noderenderernodesystemclass");
    class NodeRendererNodeSystemClassImpl : public maxon::Component<NodeRendererNodeSystemClassImpl, maxon::nodes::NodeSystemClassInterface>
    {
    MAXON_COMPONENT(FINAL, maxon::nodes::BaseCoreNodesNodeSystemClass().GetClass());
    public:
    MAXON_METHOD maxon::Result<maxon::Bool> SupportsImpl(const maxon::nodes::NodeTemplate& templ) const
    {
    const maxon::Id& id = templ.GetId();
    const maxon::String idString = id.ToString();
    if (idString.StartsWith("net.maxon"_s))
    return true;
    return super.SupportsImpl(templ);
    }
    MAXON_METHOD maxon::Result<maxon::Id> SubstituteVariant(const maxon::nodes::NodeTemplate& templ, const maxon::Block<const maxon::Id>& variant, const maxon::Block<const maxon::Id>& options) const
    {
    return { };
    }
    };
    MAXON_COMPONENT_CLASS_REGISTER(NodeRendererNodeSystemClassImpl, g_nodeRendererNodeSystemClass);
    static maxon::Id g_nodeRendererNodespaceID = maxon::Id("net.maxonexample.handbook.class.noderenderernodespace");
    class NodeRendererNodeSpaceImpl : public maxon::Component<NodeRendererNodeSpaceImpl, maxon::nodes::NodeSpaceInterface>
    {
    MAXON_COMPONENT(NORMAL, maxon::nodes::NodeSpaceBaseClass);
    public:
    MAXON_METHOD maxon::Result<void> Init(maxon::DataDictionary spaceData);
    private:
    maxon::nodes::NodeSystemClass _class;
    };
    MAXON_COMPONENT_OBJECT_REGISTER(NodeRendererNodeSpaceImpl, g_nodeRendererNodespaceID);
    #endif
    int32_t Int32
    32 bit signed integer datatype.
    Definition: apibase.h:176
    #define MAXON_COMPONENT_CLASS_REGISTER(C,...)
    Definition: objectbase.h:2409
    #define MAXON_COMPONENT_OBJECT_REGISTER(C,...)
    Definition: objectbase.h:2473

    The .cpp file

    #include "maxon/module.h"
    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();
    }
    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: url.h:952
    return OK
    Definition: apibase.h:2690
    #define MAXON_INITIALIZATION(...)
    Definition: module.h:795
    #define iferr_scope_handler
    Definition: resultbase.h:1402
    #define iferr_scope
    Definition: resultbase.h:1384