Get Group description children

On 28/05/2016 at 12:48, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   17 
Platform:   Windows  ;   
Language(s) :     C++  ;

May sound trivial, but how do I get all Description elements that reside within a group?

For example:

        ELEMENT1 {}   
        ELEMENT2 {}   
        ELEMENT3 {}   

I just want to get their names an their data state (e.g. Bool - checked/unchecked)

I just can't wrap my head around that.

edit: Found this thread:;=48478#48478

Seems like that's the solution. Is it okay, to use LoadDescription inside GetDDescription of a node?

On 28/05/2016 at 15:22, xxxxxxxx wrote:

If you implement GetDDescription() you must call LoadDescription() to load static descriptions of the node:

if (!description->LoadDescription(node->GetType()))
		return FALSE;

The Description browsing methods are probably the best way to get at descriptions by group.

On 28/05/2016 at 15:33, xxxxxxxx wrote:

Yes, my descriptions are already loaded.

I should have been a bit more precise, I guess.
Now I just need to get these elements as shown in the first post. Using "LoadDescription()" twice seems to be wrong.
The description already exists, so the only thing needed here is to access these DescLevel elements.

Let me clearify what I want to do: I have a BaseList2D object with a description. A couple of checkboxes insinde a simple group. Now there's a LONG cycle that should yield the currently checked items and display their Description names. (DESC_NAME)

Checking against the group whilst browsing through the description led to weird results. The string entries were visible, but not selectable.

I will post some code if this helps to help me. :)

Here's some code from my actual GetDDescription method:

        // Update available mat slot assignments   
     BaseContainer* slotBc = description->GetParameterI(DescLevel(XNB_PASS_BASE_MATERIAL_SLOT_DIFFUSE), nullptr);   
     if (slotBc)   
          BaseContainer slotMode;   
          void* handle = description->BrowseInit();   
          const BaseContainer* bc = nullptr;   
          DescID id, groupid;   
          while (description->GetNext(handle, &bc;, id, groupid))   
               if (bc)   
                    if (groupid == XNB_PASS_BASE_ENABLE_GROUP)   
                         pass->GetParameter(id, data, DESCFLAGS_GET_0);   
                         if (data.GetBool())   
                              slotMode.SetString(id[0].id, bc->GetString(DESC_NAME));   
          slotBc->SetContainer(DESC_CYCLE, slotMode);   

On 29/05/2016 at 12:15, xxxxxxxx wrote:

This code is a bit old-school (from an R13 project), but it shows 'dynamic' creation of cycle elements in a description LONG CYCLE (drop-down).  Part of your problem might be the use of 'id[0].id' as the index into the drop-down.  Typically I start container indices at 2000.

	BaseContainer*	opBC =		((BaseTag* )node)->GetDataInstance();
	if (!opBC)		return FALSE;
	BaseDocument*	doc =		node->GetDocument();
	if (!doc)		return FALSE;
	// Collect BodyParts into Drop-Down list and add dials for selected BodyPart
	// - Retrieve or Create SubContainer for Links to Figure's Body Parts (iPP Bases)
	subBC =						opBC->GetContainerInstance(ID_BODYPARTS);
	if (subBC == nullptr)
		return FALSE;
	if (!CollectBodyParts(doc, orig, opBC))
		return FALSE;
// IPPFigure.CollectBodyParts
Bool IPPFigure::CollectBodyParts(BaseDocument* doc, BaseObject* root, BaseContainer* tagBC)
	// - Retrieve or Create SubContainer for Links to Figure's Body Parts (iPP Bases)
	subBC =			tagBC->GetContainerInstance(ID_BODYPARTS);
	if (!subBC)
		BaseContainer	mbc(ID_BODYPARTS);
		tagBC->SetContainer(ID_BODYPARTS, mbc);
		subBC =			tagBC->GetContainerInstance(ID_BODYPARTS);
		if (subBC == nullptr)
				return ErrorException::Throw(EE_DIALOG, GeLoadString(IPPERR_MEMORY_TEXT), "IPPFigure.CollectBodyParts.subBC");
	// Collect iPP Bases into SubContainer as BaseLinks
	bpIndex =		FIRST_BPINDEX;
	CollectRecurse(doc, root, root->GetDown());
	tagBC->SetInt32(IPPFIGURE_BPINDEX, bpIndex);
	return TRUE;
// IPPFigure.CollectRecurse - forward recursive, depth-first
void IPPFigure::CollectRecurse(BaseDocument* doc, BaseObject* root, BaseObject* obj)
	BaseContainer*	bc =		nullptr;
	for (; obj; obj = obj->GetNext())
		if (obj->IsInstanceOf(ID_IPPBASE))
			bc = obj->GetDataInstance();
			if (bc && (bc->GetLink(IPP_ROOT, doc) == root))
				// Add a BaseLink to ID_BODYPARTS sub-BaseContainer
				subBC->SetLink(bpIndex,	obj);
		if (obj->GetDown())	CollectRecurse(doc, root, obj->GetDown());

On 29/05/2016 at 22:59, xxxxxxxx wrote:

Hi Robert,

thanks a lot for your example.
It shows me another approach without having to browse the whole description.

Part of your problem might be the use of 'id[0].id' as the index into the drop-down. Typically I start container indices at 2000.

I thought the same but it turned out that manually incrementing the index started at 0 has the same issue.

However, I'll have a deeper look into it tonight.

Thanks for now. ;)

On 30/05/2016 at 02:03, xxxxxxxx wrote:

Just want to add, that' there's quite an elaborate example on dynamic descriptions: objectdata_descriptions.cpp

On 30/05/2016 at 14:53, xxxxxxxx wrote:

Thank you all very much for your input. 
I really wasn't able to figure out what causes the issue.

However, I'm now heading for another approach.

If anybody is willing to solve the riddle or is willing to explain why it doesn't work, feel free to test this code:
(Maybe it has something to do with the Browsing itself, because it works outside the browse loop)

	BaseContainer* slotBc = description->GetParameterI(DescLevel(XNB_PASS_BASE_MATERIAL_SLOT_DIFFUSE, DTYPE_LONG, 0), nullptr); // The target element
	if (slotBc != nullptr)
		BaseContainer* items = slotBc->GetContainerInstance(DESC_CYCLE); // Directly access the already available items (none)
		void* handle = description->BrowseInit(); // Prepare browsing
		const BaseContainer* bc = nullptr; // temp container
		DescID id, groupid; // accessors
		Int32 index = 0; // counter for enumeration
		while (description->GetNext(handle, &bc, id, groupid))
			items->SetString(++index, bc->GetString(DESC_NAME)); // Set index to counter increment, display name to description name
		description->BrowseFree(handle);	// Dealloc


On 01/06/2016 at 11:11, xxxxxxxx wrote:

Just wanted to let you know, that I can reproduce your issue, but haven't found the culprit, yet.

On 01/06/2016 at 14:25, xxxxxxxx wrote:

Hmm, the docs say the caller owns the pointed container, so I guess it must be instanced first? But that would mean the pointed container is only "filled" (though it could also mean otherwise, as I remember I used description->GetDescEntry() before with a nullptr initialisation of the temp basecontainer and it worked fine..hmmm).
The function signature of GetNext() is kinda ugly.

      BaseContainer bc;  
      const BaseContainer* bcptr = &bc;  
      while (desc->GetNext(handle, &bcptr, id, groupid))

Note: I haven't tried this code in practice. And now after writing it kind of doesn't seem right...just grabbing thoughts.

On 01/06/2016 at 15:10, xxxxxxxx wrote:

For Description::GetDescEntry() and GetNext(), the argument is const BaseContainer**.  So, &bcptr; is correct (a pointer to a pointer to a BaseContainer).

On 02/06/2016 at 00:26, xxxxxxxx wrote:

@Samir: Unfortunately the docs are wrong, C4D owns the pointed BaseContainer. I discovered the error yesterday afternoon. Will be corrected in the next release of the SDK Docs. But this is not the reason for the actual issue of this thread. Still looking into it.

On 02/06/2016 at 03:32, xxxxxxxx wrote:

@Kuro: Cheers. What I actually meant was if C4D is actually filling the pointer pointed to with an own instance pointer (allocated internally in the GetNext() function) and the user must free this one or if the pointer comes from the caller (my version). But as the docs are apparently wrong (thanks Andreas for the info), this resolves that it should be nullptr initialised. : )

@Andreas: thx. Hmm, what is the actual issue of the code then? What is not working with the provided example?

On 03/06/2016 at 02:52, xxxxxxxx wrote:

Originally posted by xxxxxxxx

Just wanted to let you know, that I can reproduce your issue, but haven't found the culprit, yet.

Glad to hear. 
Thanks for your efforts. :)

On 03/06/2016 at 10:19, xxxxxxxx wrote:

@mp5gosu: I'm sorry, I wasn't able to find the issue until now. Unfortunately I will be at our developer meeting, so surely won't make any progress during the next week. I'm planning to continue to work on this after my return. By experience I already know, that it will get a bit turbulent, when the team has been off for an entire week, so please ping me again, if I don't return to you within a reasonable amount of time (lets say Wednesday?). Terribly sorry!

On 03/06/2016 at 10:27, xxxxxxxx wrote:

Hey Andreas, no worries! I know you Maxonians are busy but dedicated people. ;)
Sure, I'll note you.

However, thanks again for having a look into this issue. 
It's not crucial for me right now but would make me write way less code. Also for others this would help solving some problems regarding this issue.

Cheers, have a nice weekend and have some fun on the DevMeet!

On 13/06/2016 at 22:50, xxxxxxxx wrote:

Here's your personal reminder. ;)
Did you find some time to sort this out yet?

On 14/06/2016 at 00:38, xxxxxxxx wrote:

No, I haven't yet. But I haven't forgotten either. Nevertheless thanks for the reminder. I'll continue to investigate today.

On 14/06/2016 at 05:33, xxxxxxxx wrote:

TL;DR but here's a class that I built for the Vray project that allows to browse all parameters in the
description and check the parent group. For the way I use it, it's working very good. You may use it
as you like. Usage is pretty straight forward:

vb::desc::StructureHelper structure(description);
for (auto const* info : structure.iter()) {
	if (structure.CheckGroup(info->did, THE_GROUP_ID)) {
		// This parameter is inside the group THE_GROUP_ID
		// ...

In case you don't want to skip the iterator part, nr_iterator is here.

/// Copyright(C) 2015  Niklas Rosenstein
/// Developed for laubLAB KG.
/// Source/Library/lib_desc.h
#pragma once
#include "c4d.h"
#include "nr/iterator.h"
namespace vb {
namespace desc {
	 * Structure that contains the parameter information as it is yielded by
	 * #Description::GetNext().
	struct ParamInfo {
		BaseContainer const* bc = nullptr;
		DescID did;
		DescID gid;
	 * This is a helper class for reading structural information from a node's
	 * #Description. For instance, it allows you to check if a parameter is inside
	 * a specific group.
	class StructureHelper {
		friend class Iterator;
		maxon::HashMap<Int32, ParamInfo> map;
		maxon::BaseArray<Int32> order;  // required for correct order iteration
		StructureHelper() { }
		StructureHelper(Description* desc) { Init(desc); }
		~StructureHelper() { }
		inline Bool Init(Description* desc) {
			if (!desc) return false;
			ParamInfo param;
			void* handle = desc->BrowseInit();
			while (desc->GetNext(handle, &param.bc, param.did, param.gid)) {
				this->map.Put(param.did[0].id, param);
			return true;
		 * Returns the #ParamInfo info structure for a specific description ID
		 * #did or nullptr if there is no parameter with the specified ID.
		// @{
		inline ParamInfo const* Get(Int32 did) const {
			auto* entry = this->map.FindEntry(did);
			if (entry) return &entry->GetValue();
			return nullptr;
		inline ParamInfo const* Get(DescID const& did) const {
			return this->Get(did[0].id);
		// @}
		 * Returns the #ParamInfo for the parent group of the specified parameter.
		inline ParamInfo const* GetParentGroup(ParamInfo const* info) const {
			if (!info || info->did[0].id == info->gid[0].id) return nullptr;
			return this->Get(info->gid);
		 * Checks if the parameter with the description ID #did is anywhere
		 * in the group specified by #gid and returns true in that case, false
		 * otherwise.
		inline Bool CheckGroup(DescID did, DescID const& gid) const {
			while (did != gid) {
				ParamInfo const* group = this->Get(did);
				if (!group) return false;
				did = group->gid;
			return true;
		 * Helper to iterate over the entries in the description. This will
		 * happen in the same order as the iteration over the source #Description.
		class Iterator : public nr::iterator<Iterator> {
			StructureHelper const& helper;
			decltype(StructureHelper::order) ::ConstIterator it, itend;
			Iterator(StructureHelper const& helper) : helper(helper), it(helper.order.begin()), itend(helper.order.end()) { }
			Bool at_end() const { return it == itend; }
			ParamInfo const* operator * () const { return helper.Get(*it); }
			void next() { ++it; }
		inline Iterator iter() const { return Iterator(*this); }
}} // namespace vb::desc

On 14/06/2016 at 09:55, xxxxxxxx wrote:

Thanks Niklas. Storing the data into a struct seems like a reasonable approach. I'll give it a try if I find some time. ;)

On 30/06/2016 at 03:42, xxxxxxxx wrote:

Hey Andreas, any progress here yet? ;)