Accessing Userdata

On 12/06/2013 at 12:15, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   R14 Stud 
Platform:      Mac OSX  ; 
Language(s) :     C++  ;

I'm trying to access userdata of existing objects. My objects have string, real and bool type data.

I have learned thus far that I need to fetch the dynamic description for the object. Using this I can iterate through the userdata items using BrowseInit, BrowseGetNext and BrowseClose methods within the dynamic description.

Iterating through the items I get a pointer to the basecontainer for each. Using that basecontainer I can retrieve the userdata name using GetString().

What I'm completely missing is how to get at the actual userdata values themselves; ie the string, real and bool.

Here's the code thus far...

DynamicDescription *dynDesc = obj->GetDynamicDescription();
        if (dynDesc == NULL)
        DescID id;
        const BaseContainer *pbc;
        void *browseHandle = dd->BrowseInit();
        while (dynDesc->BrowseGetNext(browseHandle, &id, &pbc))
    // need to access the userdata values here!

The code iterates through the list of userdata items quite happily. I just don't understand how the values are accessed through the basecontainer (pbc in my code).

Any help much appreciated especially in terms of the C++ SDK.

On 12/06/2013 at 15:58, xxxxxxxx wrote:

I haven't really looked into "userdata" before, but here is some code that may help you figure out what's inside any container (particularly if you don't already know the IDs)...

static String VecToString(const Vector& vec)  
  return String(RealToString(vec.x)+" "+RealToString(vec.y)+" "+RealToString(vec.z));  
static void DumpMatrix(Matrix xform)  
  GePrint("v1  = " + VecToString(xform.v1));  
  GePrint("v2  = " + VecToString(xform.v2));  
  GePrint("v3  = " + VecToString(xform.v3));  
  GePrint("off = " + VecToString(;  
void container_dump(BaseContainer *pBc, LONG tab=0)  
  if(!pBc)    return;  
  LONG id, j, i=0;  
  String tabStr;  
  for(j=0; j<tab; j++)  
      tabStr += "____";  
  while (TRUE)  
      id = pBc->GetIndexId(i++);  
      if (id==NOTOK) break;  
      BaseContainer *pSubc = NULL;  
      GeData data = pBc->GetData(id);  
          case DA_NIL:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_NIL");                                                    break;  
          case DA_VOID:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_VOID");                                                    break;  
          case DA_LONG:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_LONG ("+LongToString(data.GetLong())+")");                break;  
          case DA_REAL:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_REAL ("+RealToString(data.GetReal())+")");                break;  
          case DA_TIME:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_TIME ("+RealToString(data.GetTime().GetNumerator())+" / "+RealToString(data.GetTime().GetDenominator())+")");    break;  
          case DA_VECTOR:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_VECTOR ("+VecToString(data.GetVector())+")");            break;  
          case DA_MATRIX:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_MATRIX...");    DumpMatrix(data.GetMatrix());            break;  
          case DA_LLONG:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_LLONG ("+LLongToString(data.GetLLong())+")");            break;  
          case DA_BYTEARRAY:        GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_BYTEARRAY");                                            break;  
          case DA_STRING:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_STRING (\""+data.GetString()+"\")");                    break;  
          case DA_FILENAME:        GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_FILENAME (\""+data.GetFilename().GetString()+"\")");    break;  
          case DA_CONTAINER:        GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_CONTAINER");    pSubc = data.GetContainer();    container_dump(pSubc, tab+1);    break;  
          case DA_ALIASLINK:        GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_ALIASLINK");                                            break;  
          case DA_MARKER:            GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_MARKER");                                                break;  
          case DA_MISSINGPLUG:    GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_MISSINGPLUG");                                            break;  
          case DA_CUSTOMDATATYPE:    GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - DA_CUSTOMDATATYPE");                                        break;  
          default:                GePrint(tabStr+LongToString(i)+"| id: "+LongToString(id)+" - unknown");                                                    break;  

On 12/06/2013 at 17:13, xxxxxxxx wrote:

To change the UD values. You would need use the GetParameter() & SetParameter() functions.

A short example:

//This is how to get a specific UserData entry by it's ID#  
//And if the type is correct it changes the value  
  BaseObject *obj = doc->GetActiveObject();   
  DynamicDescription *dd = obj->GetDynamicDescription();     
  //Get the UD entry with ID# 2  
  LONG gizmo = 2;  
  DescID udEntry(DescLevel(ID_USERDATA, DTYPE_SUBCONTAINER, 0), DescLevel(gizmo));  
  GeData d;  
  obj->GetParameter(udEntry, d, DESCFLAGS_GET_0);  
  if(d.GetType() != DTYPE_REAL)  
      GePrint("Wrong Data type");  
      obj->SetParameter(DescID(udEntry), GeData(.5), DESCFLAGS_SET_0);  //Set the value to 50%  


On 13/06/2013 at 00:26, xxxxxxxx wrote:

Thanks for the information. Very helpful and indeed allows me to get and potentially set user data values.

The only thing which I don't quite understand based upon your example Scott is how to get the name of a UD entry. I can iterate through the list of names using the browse code as I listed in my original question. I don't yet understand how to translate a name into an id (aka gizmo in your example) or vice verse how to translate an id into it's UD name.

Any pointers...?

PS Whilst I'm regularly underwhelmed by the coherency of information in the C4D documentation, I am conversely equally appreciative of the shared knowledge herein. Thanks to all.

On 13/06/2013 at 09:20, xxxxxxxx wrote:

Sorry that confused you.
I didn't have time to edit your code. So I just posted a piece of code I already had.
The important thing is that you need to use Get & SetParameters() to change their values.

Try this one.
It's basically your code with a few changes:

    BaseObject *obj = doc->GetActiveObject();  
  if(!obj) return FALSE;  
  DynamicDescription *dynDesc = obj->GetDynamicDescription();   //The master UD container  
  if(!dynDesc) return FALSE;  
  DescID dscID = NULL;  
  const BaseContainer *bc;  
  void *browseHandle = dynDesc->BrowseInit();  
  while (dynDesc->BrowseGetNext(browseHandle, &dscID, &bc))  
      String name = bc->GetString(DESC_NAME);              //Look for this specific UD entry name  
      //If we find the name.   
      //Then we get the info for that specific UD entry. And store it in a GeData variable  
      if (name == "MY_UD")  
          GeData d;   
          obj->GetParameter(dscID, d, DESCFLAGS_GET_0);   //Get the info of this specific UD item and store the values in "d"  
          //Now lets make sure the data type of this UD entry is correct before we attempt to change it(for safety reasons)  
          if(d.GetType() == DA_REAL)  
              obj->SetParameter(DescID(dscID), GeData(0.5), DESCFLAGS_SET_0); //Change it's value to 50% using the GeData() part of this function  


On 13/06/2013 at 10:50, xxxxxxxx wrote:

Thanks Scott. Works (of course!)

Now I have to spend a few minutes understanding how the id returned by the browse next call is the equivalent of ...

    DescID udEntry(DescLevel(ID_USERDATA, DTYPE_SUBCONTAINER, 0), DescLevel(gizmo));

Yes I understand that both are DescId's. I also understand that this is what the browse next call is giving me back. What I have to think about is the construction of a DescID using the code fragment above. I guess I just haven't found the documentation to understand the DescLevel(...) bits.

For the moment I can use and work with the code as you've given. Thanks very much for that and for your time. For my own sake, I just want to understand the udEntry construction so I can see how else to use it.



On 13/06/2013 at 11:47, xxxxxxxx wrote:

I'll try to explain.

Each UD item has an ID# When you create one. C4D automatically creates a new ID#for it.
You can target a specific UD entry by using this ID#. Rather than using the browse method.
"gizmo" stands for the ID# of the UD entry.
I just used a separate variable that I used instead of hard coding the number 2 into the DescID code.

Suppose you wanted to target the first UD item on the object or tag?
You can write this: DescID udEntry(DescLevel(ID_USERDATA, DTYPE_SUBCONTAINER, 0), DescLevel(1));

Suppose you wanted to target the second UD item on the object or tag?
You can write this: DescID udEntry(DescLevel(ID_USERDATA, DTYPE_SUBCONTAINER, 0), DescLevel(2));



On 13/06/2013 at 23:14, xxxxxxxx wrote:

Thanks Scott.

Slowly the fog clears...

It's taking time to get to grips with the internals of c4d. I only starting using it a few months ago. Thank G__ I don't have to learn C at the same time although I've also been enjoying (if that's the word) some of the curious aspects of Xcode C; for example the necessity sometimes to enclose switch cases in braces to avoid subsequent cases being considered errors!