IN_EXCLUDE list with virtual objects with parameters

Voronoi Sources Tab

Hello guys!
In my plugin i want to create functionality like on the Sources tab of Voronoy Fracture object. There is a InExclude list and a button below. With a button user can add to the list a "virtual" objects that no present on Object Manager. Below the button, after selecting an item in the list, appears some UI elements to edit "virtual" object data.
So how it can be done? May be there is some code examples.
Thank you!

Checkout my python tutorials, plugins, scripts, xpresso presets and more

Hello @mikeudin,

Thank you for reaching out to us. I will split my answer into three parts, the List UI, the Node Storage, and the Node UI, to give it more structure. There are also no Python (or C++) examples for what you want to be done. In C++ due to the very/too specific nature of the example, in Python also due to API limitations.

TLDR: What you want to do is possible with NBIT_OHIDE and NodeData::GetDDescription in Python, but depending on the nature of your nodes, it can be quite labor intensive to implement.

List UI

The question here is how to display lists or trees of nodes inside another node, i.e., a description. There are multiple solutions with advantages and disadvantages.

  • [C++/Python] InExcludeData: You already mentioned it, and it is a good choice, but the disadvantages are no hierarchical information, only one image/icon as additional data per node, and quite manual workflows when it comes to adding and deleting nodes. The node hosting the parameter must create new nodes and also make sure that they are deleted when they have been removed from the InExcludeData.
  • [C++] customgui_itemtree: This is a semi-public type used by multiple of the character animation tools, and used to display trees or lists of things. The data type is ItemTreeData. Here you have more control over the UI with its flags and can also display trees.
  • [C++] iCustomGui: The most versatile variant is to create your own custom GUI, building for example on top of c4d_listview.h. The only drawback is here that it is time consuming to implement a custom GUI.

Node Storage

The question here is how to store nodes in a scene, there are again two approaches whose viability heavily depends on the type of node one does implement.

  • [C++/Python] NBIT_OHIDE. With GeListNode.ChangeNBit and NBIT_OHIDE you can hide objects, tags, and materials from the object and material manager. They will still be visible in the viewport (you can change that too with NBIT_EHIDE) but the user will otherwise not be aware of them. The disadvantage is data garbage here. What happens to these hidden nodes when the node managing them is being deleted? In cases such as a BaseObject instance managing hidden BaseTag instances on itself, this is no problem and an internally frequently used technique because the tags will always be deleted and moved with the tag. A BaseObject which holds hidden nodes as its direct children is likely also fine, but a BaseObject which has hidden materials or inserts hidden objects at the scene root is prone to inflating scenes with garbage data on deletion, being copied, etc.
  • [C++] Branching: The natural answer to this problem is branching, we recently talked about it here. The link shows the example case of 'Motion System' tags which holds a custom branch holding the motion data as objects. Accessing and populating custom branches is possible in Python, but implementing them is not, due to NodeData::GetBranchInfo not being exposed/overwritable in Python. Find a brief example for C++ at the end of this posting. We will add NodeData::GetBranchInfo in an upcoming release for Python.

Node UI

Here it is about displaying a node inside the UI of another node. The natural GUI for that is DescriptionCustomGui but it cannot be used directly inside a description. There are again multiple options here.

  • [C++/Python] NodeData::GetDDescription: Here you basically have a group inside your description, which upon the right (when the description single ID is equal to the group ID) GetDDescription call is being flushed and then manually repopulated with gadgets for the currently selected item. We have some examples for this, but when your to be displayed node has more than two or three parameters, I would really not recommend doing this, as it will be quite a bit of work.
  • [C++/Python] BaseLink: You can use a BaseLink parameter with the LINKBOX_NODE_MODE flag to hide the link field of the link, giving you a result close to the native DescriptionCustomGui. You would have still to use NodeData::GetDDescription to make sure the parameter is/keeps unfolded (should be the field DESC_GUIOPEN).
  • [C++] iCustomGui: The most versatile variant is again to create your own custom GUI, for example targeting the BaseLink data type. Here you can then use DescriptionCustomGui.


The custom branching example. I lifted the code from the answer to a question which reached us via mail, I just obfuscated the user code a bit and added stuff specific for this case here:

/// @short Inspects the passed in tag for having an ID_STUFF_OBJECT branch.
/// @details This is an example for accessing custom branching data.
maxon::Result<void> InspectStuffTag(BaseTag* const tag)
        ApplicationOutput("@: @", MAXON_FUNCTIONNAME, err);
        return err;

    // When this is not a StuffTag, i.e., the only thing which can hold ID_STUFF_OBJECT branches.
    if (MAXON_UNLIKELY(!tag || tag->GetType() != ID_STUFF_TAG))
        return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "Passed tag is not a stuff tag."_s);

    GeListHead* stuffBranchHead (nullptr); // Will hold the ID_STUFF_OBJECT branch head.
    BaseObject* firstStuffObject (nullptr); // Will hold the first StuffObject in that branch.

    // Get the branches for the tag. A maximum of 24 branches is very generous and will never be
    // exceeded by native Cinema 4D nodes (there could always be a rogue 3rd-party plugin which is 
    // creating hundreds of branches on its node type, but that is very unlikely).
    BranchInfo branches[24];
    Int32 count = tag->GetBranchInfo(branches, 24, GETBRANCHINFO::GELISTNODES);

    // Iterate over the branches to find our "Stuff Objects" branch.
    for (Int32 i = 0; i < count; i++)
        GeListHead* const head = branches[i].head;
        GeListNode* const firstChild = head->GetFirst();

            "Branch @ | name: @, id: @, head: @{X}", 
            i, branches[i].name, branches[i].id, (void*)branches[i].head);

        // Continue searching when the branch is not ID_STUFF_OBJECT.
        if (branches[i].id != ID_STUFF_OBJECT)

        // Will always be there when the branch does exist.
        stuffBranchHead = head;
        // Can be the null pointer as branches are commonly empty.
        firstStuffObject = static_cast<BaseObject*>(firstChild);


    // Will always trigger when we did not implement StuffTag::GetBranchInfo().
    if (!stuffBranchHead)
        return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not find stuff branch for tag."_s);
        // Print out the stuff tag, its stuff object branch head, and the first object in it.
        ApplicationOutput("Stuff Tag: @", tag);
        ApplicationOutput("Stuff Branch Head: @", stuffBranchHead);
        ApplicationOutput("Stuff Branch First Object: @", firstStuffObject); 

    return maxon::OK;

// -------------------------------------------------------------------------------------------------

class StuffTag : public TagData
    INSTANCEOF(StuffTag , TagData);

    virtual ~StuffTag();
    virtual Bool Init(GeListNode* node);
    virtual Int32 GetBranchInfo(GeListNode* node, BranchInfo* info, Int32 max, GETBRANCHINFO flags);

    bool AddObject(BaseObject* obj);

    static NodeData* Alloc() { return NewObjClear(StuffTag); }

    AutoAlloc<GeListHead> _head; // The list head for our custom object branch.

/// @short Called by Cinema 4D to traverse the branches of a node.
/// @details Used in this case to expose the GeListHead _head as an ID_STUFF_OBJECT branch of the
/// node.
Int32 StuffTag::GetBranchInfo(GeListNode* node, BranchInfo* info, Int32 max, GETBRANCHINFO flags)
    // Get out when the tag has not established yet the field with the GeListHead holding the 
    // objects or when this GetBranchInfo() was called for less than one element (does not make too
    // much sense in this case, but could if you would want to have more than one custom branch).
    if (!_head || max < 1)
        return 0;

    info[0].head = _head;                  // The list head for the new branch, holding its content, 
                                           // in this case objects.
    info[0].id = ID_STUFF_OBJECT;          // The ID of the branch. It is usually the ID of the base
                                           // class of the things contained in the branch, e.g., 
                                           // Obase. Here we customized it and used instead the custom
                                           // ID_STUFF_OBJECT ID. Which will cause the branch to be
                                           // be invisible for someone searching for Obase branches.
    info[0].name = "stuff"_s;              // A label for humans for the branch.
    info[0].flags = BRANCHINFOFLAGS::NONE; // The flags for this branch, see docs for details.

    return 1;

/// @brief Adds an object to the custom stuff object branch of the tag.
/// @details All objects are handled as a flat list in this simple scenario.
bool StuffTag::AddObject(BaseObject* obj)
    if(!obj | !_head)
        return false;

    return true;

MAXON SDK Specialist

Thank you for response @ferdinand! Will try to make it with hidden tags 🤔

Checkout my python tutorials, plugins, scripts, xpresso presets and more