Creating and initializing nested ObjectData
-
This question is for C++ in C4D 2023.
Situation
I'm creating an
ObjectData
plugin, let's call the classParent
.Parent
has various properties which allow the user to define its behavior.Then there is another
ObjectData
, let's call that oneChild
.Child
is supposed to create a list ofNulls
. The count ofNulls
as well as theirShape
property must be defined byParent
where one of the exposed properties ofParent
has an effect on the count while theShape
is determined by some logic inParent
.The crucial part is that both the count and
Shape
are always set together byParent
. This event must be the trigger forChild
to purge its currentNulls
and rebuild them from the ground up using the desiredShape
. It is not acceptable to create theNulls
at one point and simply changing theShape
later on, both steps must always occur at the same time.What is the best approach for
Parent
to trigger this logic inChild
?Idea 1:
Message MSG_DESCRIPTION_POSTSETPARAMETER
Child
could listen to theMSG_DESCRIPTION_POSTSETPARAMETER
in theMessage
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*
toChild*
.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.
-
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
- Child 1
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.
Best regards and have a nice Sunday,
CJ
- Parent
-
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.