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.