NodeData – what is it good for?

If you’re new to programming for CINEMA 4D, you might have wondered what’s the deal with NodeData and its derivatives (e.g. ObjectData, TagData, ShaderData, et cetera).
When you are writing a plugin object, which is a BaseObject, why do you have to derive your plugin class from ObjectData instead of BaseObject? What is the connection between NodeData and the GeListNode derivatives (BaseObject, BaseTag, BaseShader, et cetera)?
In this article, you’re going to find out.

Introduction

In this article, we will exemplarily talk about BaseObject, but it’s the same for all other GeListNode derivatives.

Let’s start with a simple analogy:
If the BaseObject is the body, the ObjectData is the brain.

The connection

If you have a plugin that is derived from NodeData (or ObjectData, TagData, et cetera), an instance of your NodeData will exist for each of its GeListNode representations in in the document.

Whenever the user adds another one of your plugin objects to his scene, CINEMA 4D will allocate a new BaseObject and put it in the document, and it will also allocate a new instance of the ObjectData and connect the BaseObject to it. While is BaseObject is visible to the user through its icon representation in the Object Manager, the ObjectData remains invisible. The BaseObject is only there to give the user an interface to work with, and to hold data in its BaseContainer. That also explains why data in the BaseContainer is copied automatically, when the BaseObject is cloned, but data stored in member variables of the ObjectData has to be explicitly copied in the overridden CopyTo() function.

class MyPluginObjectData : public ObjectData
{
public:
  virtual Bool Init(GeListNode *node);
  virtual Bool Message(GeListNode *node, LONG type, void *t_data);

  static NodeData *Alloc(void)		{ return gNew MyPluginObjectData; }
};

Bool RegisterMyPluginObject(void)
{
  return RegisterObjectPlugin(ID_MYOBJECT, "My Plugin Object", 0, MyPluginObjectData::Alloc(), "Omyobject", AutoBitmap("Omyobject.tif"), 0);
}

Routing of function calls

Whenever the BaseObject in your scene is asked to do something, it passes the call on to its corresponding ObjectData. If the call requires the ObjectData to know which object in the scene is meant exactly, the pointer to the object is passed to the function, too. That’s why many of the NodeData member functions have a *node or *op parameter.

  • Call op->IsInstanceOf() to find out about the type of an object, it will internally call op->GetNodeData()->IsInstanceOf().
  • If the Attribute Manager needs to determine which attributes of an object should be greyed out, it will internally call op->GetNodeData()->GetDEnabling(), and pass op as the node parameter to the function.
  • Send a message to an object by calling op->Message(), it will internally call op->GetNodeData()->Message(), and again pass op as the node parameter.

You get the idea.

Undo

The Undo system in CINEMA 4D also works with the BaseObject instead of the ObjectData: If the user changes a parameter in an object, the current BaseObject with the old settings is cloned, and the clone is put on the Undo stack. The BaseObject in the document gets the new settings. However, the BaseObject on the Undo stack and the BaseObject in the document both are connected to the same ObjectData.

More information

Avatar

Frank Willeke

worked with computers since more than 20 years | got hooked on computer graphics back on the AMIGA | started programming at the age of 13 | relesed some successful plugins for cinema 4d | started working for maxon computer gmbh in 2009 | now contributing to cinema 4d as a senior developer making electronic music since 1993 | playing the electric guitar since 1995 age of 14 | first live gigs in 1997 | playing the bass guitar 2005 | playing keyboards and synths since 2012 experimenting with photography since 2003