Solved 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 a BaseContainer; 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 ID paramId cannot be the ID 700, 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

MAXON SDK Specialist
developers.maxon.net

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 use SetParameter() and GetParameter(), with the proper DescId levels. From your answer I understand that I will be retrieving the second snippet's data later with GetParameter(), 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 as f2. I will assume that paramId equals 1 and for simplicity I will also assume a DTYPE_LONG (i.e. an integer value) instead of your custom data type. Let's assume that we write the value 42.

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 for Get/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 a Vector) . This is done via DescID and DescLevel.

To retrieve the value written by f1, we simply need a DescID with one DescLevel, because paramId was not a container type value.

const DescLevel dlvlParamId (paramId, DTYPE_LONG, 0);
const DescID did = DescID(dlvlParamId);

For f2 the DescID would have to have two levels. This would be an example for reaching "deeper", here retrieving a DTYPE_LONG in a DTYPE_SUBCONTAINER. If dlvlParamId would be of DTYPE_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

MAXON SDK Specialist
developers.maxon.net

Thanks, @zipit that's very clarifying.