Creating a core node: 1

Maxon introduced the new node system in R21 and it has become very powerful over subsequent releases. No matter how many nodes are supplied, however, there is likely to be a demand for more, so this is a new variety of plugin for Cinema. These four pages are intended to show how to write a very simple core node. It won't do anything fancy...but it will show how this type of simple, low-level node can be written fairly easily, once you know how the system works.

I am also writing this for myself so that I can remember all the steps required when I come to write more nodes! So let's go about writing our first node. I'll add the source code at the end of this tutorial so you can build a working version if you're following this but something went wrong for you along the way.

Building a new core node for Maxon’s new node system isn’t difficult but there is limited documentation on how to do it, plus it requires the use of the resource editor, which is not very well documented yet. The purpose of these pages is to show how a new node can be built with all the necessary steps explained.

This will be the simplest kind of node, a core node. The function will be to take a velocity vector and split it into the direction and speed components. A velocity is a combination of speed and direction but often you need to split it into these two components; this node is intended do that. In fact this could equally well be done by using two existing nodes, the Normalize node (for direction) and Vector Length node (for speed) but this new node will do both at the same time.

1. Project definition

The first thing is to create the project definition file. This is like any other such file except that a core node requires the corenodes framework. This is the file I used:

// Supported platforms
Platform=Win64;OSX

// Type of project - can be [Lib;DLL;App]
Type=DLL

// API dependencies
APIS=cinema.framework;\
image.framework;\
misc.framework;\
core.framework;\
crypt.framework;\
math.framework;\
command.framework;\
corenodes.framework;

// C4D component
C4D=true

//stylecheck.level=3
//stylecheck.level=3 // must be set after c4d=true
stylecheck=false
stylecheck.enum-registration=false
stylecheck.enum-class=false

// Custom ID
ModuleId=uk.co.microbion.splitvelocity

It’s up to you what you do about the style check; I always turn it off but that’s personal preference.

Note that the file must include ‘corenodes.framework’ and must have a unique custom ID. Maxon has some guidelines about choosing IDs and in this case I’ve chosen to use the reverse URL of my website as the base.

2. Initial code

In a conventional plugin, we would probably design a basic description interface first, then add some code to load the description so that we get something in the Cinema user interface (UI) to work with. Then we can add the actual implementation code.

Nodes are a bit different. We create the node’s interface with the Cinema 4D resource editor, but we can’t do that until a resource database which holds the description is registered with Cinema. So we need to compile a basic node without an interface, but which does register a database, then create the node description in that database using the resource editor. Then you can add the code to use that resource and actually do something useful in the node. In this context a ‘database’ is made up of one or more .json files and can contain one or more descriptions.

Note: the following code is essentially lifted from the SDK documentation with the necessary modifications to build this node.

First we will need a couple of global variables:

static maxon::BaseArray<maxon::GenericData> g_coreNodeDescriptions;
static maxon::Id g_corenodesDatabaseId = maxon::Id("uk.co.microbion.splitveldb");

The first is a BaseArray which holds the core node descriptions. The second is an ID value which is the ID of the resource database (not the description) the resource editor will create. In this node, the ID is uk.co.microbion.splitveldb. This ID is used to create the filename for the database files written out by the resource editor. In this case, when the files are created, they will be named uk.co.microbion.splitveldb.db.json and uk.co.microbion.splitveldb.db.en_US.json.

Then we need a class to hold the node. You can call it whatever you like; in this example it is SplitVel. Within that class we need another class for the actual implementation. Maxon seem to use the name Impl for that class but it can be anything. It must derive from the SDK class BasicMicroNode. It’s within this class that we’ll actually do something with the port values, but at the moment we don’t have any ports to work with, so we can omit that for the time being.

Our main class SplitVel will need an Init() function though. This is called when an instance of the node is created in the node editor. In this case it simply adds the node to the MicroNode node group. The final class looks like this:

class SplitVel
{
public:
  class Impl : public BasicMicroNode // empty at present, we'll add code later
  {
  public:
  };

  static maxon::Result<void> Init(const MicroNodeGroupRef& group)
  {
    return group.AddChild<Impl>();
  }
};

Now, we don’t have a description for the node yet, we have to create that in the resource editor. But the editor has to know the name of the database file to create and it has to know where to create them and where to load the database from. This is done in a separate function (not part of the SplitVel class). This is the function, which is lifted straight from the SDK docs:

static maxon::Result<void> HandleInitializeModule()
{
  iferr_scope_handler
  {
    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
  const maxon::Url coreNodesResourceUrl = pluginDir.Append("res"_s).Append("nodes"_s) iferr_return;

  // Load core node descriptions (they register automatically).
  maxon::DataDescriptionDefinitionDatabaseInterface::RegisterDatabaseWithUrl(g_corenodesDatabaseId, coreNodesResourceUrl) iferr_return;

  return maxon::OK;
}

All this does is get the location of the node’s resource database, which will be in a subfolder of the plugin’s folder called res/nodes, and then registers it in the core node registry. We already know the name of the actual file (or rather, the root of its name) because the global variable, g_corenodesDatabaseId was initialized to uk.co.microbion.splitveldb. Note that right now, the database files do not exist because we haven’t used the resource editor to create them yet. However, we must make sure that the folder structure exists because if it doesn’t the resource editor won’t be able to save the database files. At present we need to create that folder structure manually, so in this case it will look like this:

plugins/SplitVelocity/res/nodes

Where the ‘plugins’ folder is the folder which Cinema will load plugins from.

Finally, we need to free up the description resource when Cinema exits with another function:

static void HandleFreeModule()
{
  iferr_scope_handler
  {
    err.CritStop();
    return;
  };

  g_coreNodeDescriptions.Reset();
}

These functions are called automatically by using this macro:

MAXON_INITIALIZATION(HandleInitializeModule, HandleFreeModule);

At this point the node can be compiled and is ready to have a description added. That is covered in the next page.

Page last updated July 10th 2023