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:2632
static const Class< typename INTERFACE::Hxx1::ReferenceClass > & GetClass()
Definition: objectbase.h:2714
Definition: apibaseid.h:251
Definition: resultbase.h:766
Definition: string.h:1226
#define MAXON_COMPONENT(KIND,...)
Definition: objectbase.h:2193
#define MAXON_METHOD
Definition: interfacebase.h:942

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:1465

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:178
#define MAXON_COMPONENT_CLASS_REGISTER(C,...)
Definition: objectbase.h:2390
#define MAXON_COMPONENT_OBJECT_REGISTER(C,...)
Definition: objectbase.h:2454

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)
static MAXON_METHOD Result< void > UnregisterDatabase(const Id &databaseId)
Definition: url.h:876
return OK
Definition: apibase.h:2620
#define MAXON_INITIALIZATION(...)
Definition: module.h:750
#define iferr_scope_handler
Definition: resultbase.h:1392
#define iferr_scope
Definition: resultbase.h:1374