Solved Creating and initializing nested ObjectData

This question is for C++ in C4D 2023.

Situation

I'm creating an ObjectData plugin, let's call the class Parent. Parent has various properties which allow the user to define its behavior.

Then there is another ObjectData, let's call that one Child. Child is supposed to create a list of Nulls. The count of Nulls as well as their Shape property must be defined by Parent where one of the exposed properties of Parent has an effect on the count while the Shape is determined by some logic in Parent.

The crucial part is that both the count and Shape are always set together by Parent. This event must be the trigger for Child to purge its current Nulls and rebuild them from the ground up using the desired Shape. It is not acceptable to create the Nulls at one point and simply changing the Shape later on, both steps must always occur at the same time.

What is the best approach for Parent to trigger this logic in Child?

Idea 1: Message MSG_DESCRIPTION_POSTSETPARAMETER

Child could listen to the MSG_DESCRIPTION_POSTSETPARAMETER in the Message function. This however will be triggered for each changed property separately so I can't exactly trigger the logic at the correct time, which would break the requirement. So I don't think this is the way. At least from what I know about this approach.

The code in Parent would look something like this:

Int32 childNullsCount = getChildNullsCount();
Int32 childNullsShape = getChildNullsShape();

BaseObject* child = BaseObject::Alloc(Ochild);
BaseContainer& childData = child ->GetDataInstanceRef();
childData->SetInt32(CHILD_NULLS_COUNT, childNullsCount);
childData->SetInt32(CHILD_NULLS_SHAPE, childNullsShape);

Idea 2: Custom Message

I could define a new message ID and pass the data together.

Code in Parent:

Int32 childNullsCount = getChildNullsCount();
Int32 childNullsShape = getChildNullsShape();

BaseObject* child = BaseObject::Alloc(Ochild);
// MSG_CHILD_CREATENULLS is defined somewhere else.
child->Message(MSG_CHILD_CREATENULLS, &CreateNullsData(childNullsCount, childNullsShape);

Code in Child:

Bool Child::Message(GeListNode* node, Int32 type, void* data)
{
    switch (type)
    {
    case MSG_CHILD_CREATENULLS:
        CreateNullsData* createData = static_cast<CreateNullsData*>(data);

        createNullsWithShape(createData->Count, createData->Shape);
        break;
    }

    return true;
}

Idea 3: Custom function in Child

Child could expose a function like this:

class Child : public ObjectData
{
public:
  /* ... */
  void CreateNulls(Int32 count, Int32 shape);
}

Parent could then do:

Int32 childNullsCount = getChildNullsCount();
Int32 childNullsShape = getChildNullsShape();

BaseObject* child = BaseObject::Alloc(Ochild);
Child* castedChild = /* what code goes here to cast BaseObject* to Child*? */;
castedChild->CreateNulls(childNullsCount, childNullsShape);

Even if this isn't the way I'd still love to know how to cast BaseObject* to Child*.

Disclaimer

My actual plugin handles a more complex scenario which would obscure the question too much due to all the details so I'm trying to keep this topic very abstract.

I'm not sure which design decision would be best - Idea 2 and Idea 3 seem pretty much the same, with the small difference that Idea 2 would make this message usable from other places as well. If you would need the functionality only in Parent, then I suppose Idea 3 would be most "encapsulating".

And for you SDK query about the data cast - this should do the trick. Usually when doing such node data queries, you should always check the type with GetType(), but since you create the object on the line above, the check would be redundant.

BaseObject* child = BaseObject::Alloc(Ochild);
if (child) {
  Child* castedChild = child->GetNodeData<Child>();
  castedChild->CreateNulls(childNullsCount, childNullsShape);
}

Do you need to able to place the "Parent" and "Child" objects anywhere in your scene hierarchy? Otherwise, could one possibility be to make "Child" a generator object, that implements GetVirtualObjects() to generate the desired null objects based on the "parent" object? The "parent" object would need to be placed under the "child" object in the hierarchy, which may or may not be a problem depending on the problem you are trying to solve.

Or do you also need the generated nulls to be editable, i.e., not virtual objects created by a generator?

Best regards
/Filip

Hi @Filip,

Thanks for your response.

The objects must be objects for the user to interact with so the generator approach is not feasible here.

Therefore the final result in the scene must be like the following:

  • Parent
    • Child 1
      • Null 1
      • Null ...
      • Null n
    • Child ...
      • Null 1
      • Null ...
      • Null n
    • Child n
      • Null 1
      • Null ...
      • Null n

I'm very aware of (hopefully) all the design considerations that come with this decision. Since I had to consider all the other plugin requirements I'd like to refrain from elaborating on those if possible. Because if I were to do that I'd need an actual plugin architect to re-consider everything from the ground up with me. :sweat_smile:

Best regards and have a nice Sunday,

CJ

I'm not sure which design decision would be best - Idea 2 and Idea 3 seem pretty much the same, with the small difference that Idea 2 would make this message usable from other places as well. If you would need the functionality only in Parent, then I suppose Idea 3 would be most "encapsulating".

And for you SDK query about the data cast - this should do the trick. Usually when doing such node data queries, you should always check the type with GetType(), but since you create the object on the line above, the check would be redundant.

BaseObject* child = BaseObject::Alloc(Ochild);
if (child) {
  Child* castedChild = child->GetNodeData<Child>();
  castedChild->CreateNulls(childNullsCount, childNullsShape);
}

Hi @CJtheTiger, the most modular way would be the second one with Message, since it does not require you some weird casting and its pretty generic, so if you change your design you can still rely on the same message to do the things. But there is no wrong way, the one that work the best for you is probably the better.

With that's said there is also the MultiMessage method that let you send a message directly to multiple objects according to their hierarchical position via the MULTIMSG_ROUTE input flag.

Finally to retrieve the implementation (the NodeData) of a GeListNode(the instance) you can call instance->GetNodeData<Child>()

Cheers,
Maxime.