How to retrieve the text content (options/items) of a User Data Cycle Property?
-
Hello everyone,
I'm working with Python in Cinema 4D and I've added a custom user data attribute to an object. This attribute is a Cycle (or enum) property, similar to Maya's enum, which generates a dropdown menu.
My attribute has the following options:
0;X
1;Y
2;Z
3;-X
...
I've written a script to switch this attribute's value programmatically. My current approach involves getting the selected object and accessing its user data. I can successfully change the value, but I'm relying on hardcoded IDs (e.g., op[descId] = 0 for 'X', op[descId] = 1 for 'Y', etc.).
Here's a snippet of my current code:import c4d ATTR_NAME = 'Axis' userData = op.GetUserDataContainer() for descId, container in userData: if container.GetInt32(c4d.DESC_CUSTOMGUI) != c4d.CUSTOMGUI_CYCLE: continue if container[c4d.DESC_NAME] != ATTR_NAME: continue enumString = container.GetString(c4d.CUSTOMGUI_CYCLE) print(enumString) # is None # clicked X op[descId] = 0 c4d.EventAdd()
My goal is to be able to switch the attribute's value by its name (e.g., 'X', '-Z') instead of its integer ID. To do this, I need to retrieve the full list of options (the 0;X\n1;Y... string) from the Cycle property itself, so I can map the option names to their corresponding integer IDs.
I tried using container.GetString(c4d.DESC_CYCLE), but it returned an empty string, which is unexpected for a properly set up Cycle user data.
Could anyone guide me on the correct way to extract this textual content (the 0;X\n1;Y... string) from a User Data Cycle property in Python?
Any help would be greatly appreciated!
-
Hey @Oliver,
Thank you for reaching out to us. You can technically achieve that by accessing the description, but that is not really how parameter access is intended in Cinema 4D. We do not use strings but symbols to access things. And when you want an X, Y, Z symbol, you can just define it.
In plugins this is usually done in either a description, dialog, or global resource. When you just have some user data and some local script, you can just do it in that file.
Cheers,
FerdinandResult
File: param_access.c4dCode
import c4d import typing doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. def main() -> None: """Called by Cinema 4D when the script is being executed. """ if not op: c4d.gui.MessageDialog("No object selected.") return # Cinema 4D operates with something called "symbols", i.e., identifiers defined in code, to # address parameters in the Cinema API. In the Maxon API we also use string identifiers, but # that is another subject. # So, to access the position of an object, we must do this: print("Position: ", op[903]) # Because the integer value 903 is where this data is being stored in the object. But that is # not very readable, so we can use the symbol `c4d.ID_BASEOBJECT_POSITION: print("Position: ", op[c4d.ID_BASEOBJECT_POSITION]) # This applies to many many things in Cinema 4D, that they are identified by integer values, # which then usually are expressed as symbols in code. # User data, the case you are talking about, by definition cannot have symbols, because the user # just defined that parameter. E.g., we access (ID_USERDATA, 1) and not (ID_USERDATA, SOME_SYMBOL). try: value: typing.Any = op[c4d.ID_USERDATA, 1] # Accessing user data with ID 1. except: c4d.gui.MessageDialog("User data with ID 1 does not exist.") return # When we now assume that #value is of type `int` ... value: int # ... then there exist of course no builtin symbols for it, but we can make some up. ID_X_AXIS: int = 0 ID_Y_AXIS: int = 1 ID_Z_AXIS: int = 2 if value == ID_X_AXIS: print("User data is set to X axis.") op[c4d.ID_USERDATA, 1] = ID_Y_AXIS # Set user data to Y axis. # Lastly, for some things exists symbols you can simply reuse. You could for example hijack # the Mograph control for this as defined in [1], so that you would write: # # [1] https://developers.maxon.net/docs/py/2025_2_0/cinema_resource/object/mggridarray.html if op[c4d.ID_USERDATA, 1] == c4d.MG_GRID_UVAXIS_XP: print("User data is set to +X axis.") # Which should be complemented by going into for example the Mograph Cloner, navigating to the # W(UV)-Orientation parameter in the Transform tab, right click it, and then select # "User Data Interface > Copy User Data Interface". To then go into our object, and then select # from the Attribute Manager "User Data > Paste User Data Interface ...". You can not just copy # singular parameters like this but whole interfaces. Each user data parameter is then still # editable in the User Data Manager, so you can change the names and such. # When you are hell-bent on doing it, you can also evaluate label-id relations at runtime, but # that is not how this is done in Cinema 4D. description: c4d.Description = op.GetDescription(c4d.DESCFLAGS_DESC_0) data: c4d.BaseContainer = description.GetParameter( c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1))) print (f"{data[c4d.DESC_NAME] = } with the cycle values:") for value, label in data[c4d.DESC_CYCLE]: print(f"{label = }, {value = }") if __name__ == '__main__': main()
-
@ferdinand
Thank you for the detailed explanation and the solution! This perfectly solved my problem, and I really appreciate you clarifying how to access the DESC_CYCLE values!!