BaseContainer and UserData
-
Hello,
What is the proper way to initialize user data in a BaseContainer?
bc->SetData( paramId, GeData( MY_CUSTOMDATATYPE, DEFAULTVALUE ) );
...or...
auto userDataContainer = bc->GetContainerInstance( ID_USERDATA ); userDataContainer->SetData( paramId, GeData( MY_CUSTOMDATATYPE, DEFAULTVALUE ) );
I always used the former, but noticed a BaseContainder inside
NodeData::Read()
formatted like the latter. -
Hi @rsodre,
thank you for reaching out to us. We are a bit confused by your question, because your two code snippets either do different things or it is at least is not obvious what you are doing in the first example. So your question of which one is the better solution, cannot be answered.
In the first example of yours, you write to the ID
paramId
in aBaseContainer
; from the context it seems reasonable to assume that this container is meant to be either a copy or an instance of a node's data container. But since you write a non-container value, your custom data type, the IDparamId
cannot be the ID700
, i.e.ID_USERDATA
, the id where the user data container is stored in a node's data container. In your second example you do retrieve that user data container from the node's data container first and then do the write operation of your custom data into the user data container.So you are doing two different things here, in the first case you write to some id in the data container and in the second case you write to some id in the user data container (which is attached to the data container). Your first example should not be parsed by Cinema 4D as user data and subsequently not show up in the user data UI. The only place where
paramId
could show up is in the UI of the node, if said ID part of the description of that node.I hope this makes sense,
Ferdinand -
Hi @zipit
Yes, both do different things, but because I want to understand which one is correct, if any, when initializing the object inside
NodeData::Init()
.My custom user data does not have UI and is not available to be added manually on the object's UserData menu, so it didn't pass in my mind to look there. I declare them inside
ID_OBJECTPROPERTIES
of the res file.I rarely access data directly from the node's data container, other than in
NodeData::Init()
. I usually useSetParameter()
andGetParameter()
, with the proper DescId levels. From your answer I understand that I will be retrieving the second snippet's data later withGetParameter()
, correct?If I do not initialize at all, peeking at the data container after I use
SetParameter()
, user data are inside a 700 container. -
Hi @rsodre,
I am even more confused now by your answer So let me explain what I was talking about. I will refer to your first example as
f1
and to your second example asf2
. I will assume thatparamId
equals1
and for simplicity I will also assume aDTYPE_LONG
(i.e. an integer value) instead of your custom data type. Let's assume that we write the value42
.After you have run
f1
, the data container of the node will look like this:// f1 wrote simply to the index `1` of the container, here the value `42`. // This is dangerous, because Cinema stores internal data in the data container range 0 to 1000. // You should never write to this range. I assumed value `1` for `paramID`, because user data IDs // usually start in this very low range. [1]: 42 ... // The user data container, it will be always in data container. [700, i.e. ID_USERDATA]: BaseContainer // In this case we assume the user data container to be empty ...
And after you have run
f2
, the data container of the node will look like this:... // The user data container, it will be always in data container. [700, i.e. ID_USERDATA]: BaseContainer // The value written by `f2` [1]: 42 ...
Both values can be retrieved with
GetParamater
which is mostly a convenience wrapper around the data container of a node. The differences forGet/SetParamater
are:- They enforce the data type of the container at the given id, i.e. you cannot simply overwrite an existing value type with another value type.
- They allow for the access of node attributes which are not being stored in its data container, e.g. its name.
- They allow to reach "deeper" into a data container which can contain composed value types (e.g. a
BaseContainer
or aVector
) . This is done viaDescID
andDescLevel
.
To retrieve the value written by
f1
, we simply need aDescID
with oneDescLevel
, becauseparamId
was not a container type value.const DescLevel dlvlParamId (paramId, DTYPE_LONG, 0); const DescID did = DescID(dlvlParamId);
For
f2
theDescID
would have to have two levels. This would be an example for reaching "deeper", here retrieving aDTYPE_LONG
in aDTYPE_SUBCONTAINER
. IfdlvlParamId
would be ofDTYPE_VECTOR
, we could also reach one more level further down, e.g., we could retrieve the first component of a vector inside a subcontainer.const DescLevel dlvlUserData(ID_USERDATA, DTYPE_SUBCONTAINER, 0); const DescLevel dlvlParamId(paramId, DTYPE_LONG, 0); const DescID did = DescID(dlvlUserData, dlvlParamId);
So, I still don't quite get what your goals are, but yes, initializing an attribute in a data container will make it accessible as a parameter. But you do not clarify how the (dynamic) description of the data container hosting node is constituted, so there might be no GUI for your data. The "proper" way to create a new user data entry is described in the DynamicDescription Manual. Which then later can be written to in the way described here.
I hope this helps and happy holidays,
Ferdinand -
Thanks, @zipit that's very clarifying.