Get Group description children



  • 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. :)

    edit:
    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));   
                        }   
                   }   
              }   
              description->BrowseFree(handle);   
              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.

    GetDDescription()
    {
    	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);
    				++bpIndex;
    			}
    		}
    		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
    	}
      
    	flags |= DESCFLAGS_DESC_LOADED;
      
    
    

    Cheers!



  • 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 {
    	private:
    		friend class Iterator;
    		maxon::HashMap<Int32, ParamInfo> map;
    		maxon::BaseArray<Int32> order;  // required for correct order iteration
    	public:
    		StructureHelper() { }
    		StructureHelper(Description* desc) { Init(desc); }
    		~StructureHelper() { }
      
    		inline Bool Init(Description* desc) {
    			this->map.Flush();
    			this->order.Flush();
    			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);
    				this->order.Append(param.did[0].id);
    			}
    			desc->BrowseFree(handle);
    			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> {
    		private:
    			StructureHelper const& helper;
    			decltype(StructureHelper::order) ::ConstIterator it, itend;
    		public:
    			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? ;)



  • On 18/08/2016 at 09:07, xxxxxxxx wrote:

    Hi,

    not sure, if you are still waiting for me, here... I'm terribly sorry, but you gave me quite a hard nut and I was stupidly searching at the wrong end. And in the end it was Sebastian, who solved this.

    Cinema 4D calls GetDDescription() quite a bunch of times. Amongst others when building the GUI and during SetParameter(). When building the GUI your code works and so you see the correct entries in the cycle box. But when calling it during SetParameter(), GetDDescription() is called in "single description mode". When in this mode, calling GetParameterI() sets the description into a special mode, with the effect that any following browse will return only one parameter. With your code this has the unfortunate effect, that you won't fill the items container. So SetParameter() thinks, there are no options and the parameter won't be set.

    For you this means, you can either split your loop and first browse/collect the cycle box items before calling GetParameterI().
    Or you can use a second temporary description for your browse loop.

    AutoAlloc<Description> tempDescription;
    tempDescription->LoadDescription(node->GetType());
    

    The reasons for this behavior seem to be some heavy optimization, but we need to check this with development. Then we will see, how we can document it properly.

    Again, terribly sorry, this took so long.



  • On 18/08/2016 at 09:19, xxxxxxxx wrote:

    Wow, huuuge thanks to you Andreas!
    Also Sebastian, please buy him a beer. ;)

    Indeed I was eagerly waiting for a response to this. And your answer is very enlightening. 
    With this information, I'm able to circumvent this behaviour in future projects and also reduce my current code a lot!

    Cheers!

    :hugging:


Log in to reply