SOLVED XPresso: Modify UserData

I have a UserData "MyData" of type Button Cycle and i would like to modify its contents, in python node

Where is that Cycle Button?

Is it on some scene object, on some tag, or even on the Python node itself?

Also, it is typically not the job of the Python node to edit scene elements.

Hi,

modifying a dynamic description from a (threaded) Python context is IMHO is totally fine. Although admittedly in the context XPresso it will probably yield some weird results, if not implemented very carefully, due to the constant execution of a XPresso graph. To modify the dynamic description of an object (its user data) you will need the methods BaseList2D.GetUserDataContainer and .GetUserDataContainer. The cycle container in a description is c4d.DESC_CYCLE. Something like this:

import c4d

def modify_cycle(container):
    """The specific cycle modification logic. Will add an element in 
     this case.
    
    Args:
        container (c4d.BaseContainer): The c4d.DESC_CYCLE container of an
         description element.
    
    Raises:
        TypeError: Description

    Returns:
        c4d.BaseContainer: The modifed cycle container.
    
    """
    if not isinstance(container, c4d.BaseContainer):
        raise TypeError(container)

    # The next id, could also be done more efficiently with
    # ``BaseContainer.GetIndexId`` and the length of the container. We
    # are just adding an ID one after the last ID.
    new_id = [n for n, _ in container][-1] + 1
    container[new_id] = "New Item " + str(new_id)
    return container


def modify_user data(node, identifier=None):
    """General logic to modify some user data. In this case only modifies 
     cycle elements.

    Args:
        node (c4d.BaseList2D): The node with the user data.
        identifier (int, str or None, optional): Either the name or index of 
         the element to be modified. When None, all cycle elements will be 
         modified. Defaults to None.
    
    Raises:
        TypeError: Description
        ValueError: Description
    """
    # Sort out some invalid inputs and get the user data container.
    if not isinstance(node, c4d.BaseList2D):
        msg = "Expected 'node' to be of type BaseList2D: Received: {type}."
        raise TypeError(msg.format(type=type(node)))
    data = node.GetUserDataContainer()
    if not data:
        msg = "The user data container of {node} is empty."
        raise ValueError(msg.format(node=node))
    # Go over all user data.
    index = 1
    for descid, item in data:
        # Step over user data where the cycle field is empty.
        if item[c4d.DESC_CYCLE] is None:
            continue
        # Slightly dicey way to fire our cylce modification function when
        # an element matches our identifier condition.
        name = item[c4d.DESC_NAME]
        if identifier in [name, index, None]:
             item[c4d.DESC_CYCLE] = modify_cycle(item[c4d.DESC_CYCLE])
        # Write the modified data back. One confusing thing about
        # Set/GetUserDataContainer is that SetUser... sets a single element,
        # while GetUser... returns the containers for all elements.
        node.SetUserDataContainer(descid, item)
        index += 1

def main():
    """Entry point.
    """
    modify_user data(op)
    c4d.EventAdd()

if __name__=='__main__':
    main()

Cheers,
zipit

@zipit Thanks. I test and keep you informed 😉

@zipit
In this test, the modification works well, but the cycle button is not updated.
I added c4d.EventAdd(), but in this case it runs in a loop 🙂
Test2.c4d

Hi,

you never write your data back. Also this is exactly the scenario @PluginStudent and I did warn you about. Your file will continuously execute that Python Xpresso node, which is probably not what you want. You will need some execution condition.

import c4d
from c4d import gui

def main():
    print "Modify ButtonCycle"
    description = Objet.GetUserDataContainer()

    for descid, element in description:
        if descid.GetDepth() < 2:
            continue
        if descid[1].id == 2:
            bc = element[c4d.DESC_CYCLE]
            if not isinstance(bc, c4d.BaseContainer):
                return
            bc.FlushAll()
            bc[0] = "Modifier"
            element[c4d.DESC_CYCLE] = bc
            Objet.SetUserDataContainer(descid, element)
            # You probably want the element to be selected.
            Objet[descid] = 0

Cheers,
zipit

@zipit work fine 👍 thanks a lot