Convenience function for Combobox
-
Hello,
Thanks for looking into this! I'm attempting to create a script that adds User Data. Below is a snippet from the larger script, which uses convenience functions for Real, Integer, and Vector user data.
I'm interested to see if there's a convenience function for the combobox user data (CUSTOMGUI_CYCLE), since the script will add multiple comboboxes to the user data container. I've searched high and low for this without any luck -- admittedly, I'm new to python and not exactly sure how "high" and "low" I'm actually searching.
The solution would clean up the code, as well as give me some more insight into using python in C4D. Thanks much for your time. Any help is greatly appreciated!
Thanks,
Eric
import c4d from c4d import gui #Welcome to the world of Python def CreateUserDataGroup(obj, name, parentGroup=None, columns=None, shortname=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_GROUP) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_TITLEBAR] = 1 bc[c4d.DESC_GUIOPEN] = 1 if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if columns is not None: #DESC_COLUMNS VALUE IS WRONG IN 15.057 - SHOULD BE 22 bc[22] = columns return obj.AddUserData(bc) def CreateUserDataFloat(obj, name, val=0, parentGroup=None, unit=c4d.DESC_UNIT_REAL, sliderMin = 0, sliderMax = 0, step = 0): if obj is None: return False bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_REAL) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = name bc[c4d.DESC_DEFAULT] = val bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_ON bc[c4d.DESC_UNIT] = unit bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER bc[c4d.DESC_MINSLIDER] = sliderMin bc[c4d.DESC_MAXSLIDER] = sliderMax bc[c4d.DESC_MAX] = sliderMax bc[c4d.DESC_MIN] = sliderMin bc[c4d.DESC_STEP] = step if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = val return element def main(): for op in doc.GetActiveObjects(0): if (len(op.GetUserDataContainer()) == 0): #creates user data layerGroup = CreateUserDataGroup(op,"Shader Controls",c4d.DescID(0)) #SHADER subgroup subGroup2 = CreateUserDataGroup(op,"Shader",layerGroup,1) #NOISE combobox noiseType = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG) noiseType[c4d.DESC_PARENTGROUP] = subGroup2 noiseType[c4d.DESC_NAME] = "Noise Type" noiseType[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_CYCLE names = c4d.BaseContainer() names.SetString(0,"Turbulence") names.SetString(1,"Fractal") names.SetString(2,"Cell") noiseType.SetContainer(c4d.DESC_CYCLE, names) entries = op.AddUserData(noiseType) op[entries] = 0 c4d.SendCoreMessage(c4d.COREMSG_CINEMA, c4d.BaseContainer(c4d.COREMSG_CINEMA_FORCE_AM_UPDATE)) noiseScale = CreateUserDataFloat(op,"Noise Scale",.5,subGroup2, c4d.DESC_UNIT_FLOAT, 0, 1, .001) c4d.EventAdd() if __name__=='__main__': main()
-
Hi @saputello2, I'm not sure in which way you would expect this "convenience" to work. You can for sure build a convenience function to creates your combobox as you did for the CreateUserDataFloat. But their is nothing like that already made in Cinema 4D.
With that's said, if you want to build a combobox with all kinds of noise you can use c4d.utils.noise.C4DNoise.CreateMenuContainer.# NOISE combobox noiseType = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG) noiseType[c4d.DESC_PARENTGROUP] = subGroup2 noiseType[c4d.DESC_NAME] = "Noise Type" noiseType[c4d.DESC_CYCLE] = c4d.utils.noise.C4DNoise.CreateMenuContainer() entries = op.AddUserData(noiseType) op[entries] = c4d.NOISE_LUKA
Finally, please read Q&A New Functionality in order to set up correctly your post (I've done it for you this time, don't worry since it's your first topic).
Cheers,
Maxime. -
Thanks much for the reply, @m_adam. I'm creating a script for a Redshift material using Redshift's native noises, but that's a fantastic tip on
c4d.utils.noise.C4DNoise.CreateMenuContainer
. And apologies for missing the post rules -- I'll be more mindful in the future.Really just looking for a way to clean up the code (and greater understand how the function works).
This is what I came up with:
def CreateUserDataCycle(obj, name, val, entry1, entry2, entry3, entry4, entry5, entry6, parentGroup=None): if obj is None: return False bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = name bc[c4d.DESC_DEFAULT] = 1 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_CYCLE names = c4d.BaseContainer() names.SetString(0,entry1) names.SetString(1,entry2) names.SetString(2,entry3) names.SetString(3,entry4) names.SetString(4,entry5) names.SetString(5,entry6) bc.SetContainer(c4d.DESC_CYCLE, names) if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = val return element
and...
noiseType = CreateUserDataCycle(op,"Noise Type",2,"Type01", "Type02", "Type03", "Type04", "", "",subGroup2) textureType = CreateUserDataCycle(op, "Texture", 1, "Concrete", "Scratches", "Grunge", "", "", "", subGroup2)
So far it works, though I'm still working on how to manage the number of "entries" so there aren't blank strings:
""
.Thanks again, your advice pushed me in the right direction.
Eric
-
Hi @saputello2,
The pythonic way would be to use *args and **kwargs, for more information, please read *args and **kwargs in python explained.
This will give us something like.def CreateUserDataCycle(obj, name, val, *argv, **kargs): if obj is None: return False bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = name bc[c4d.DESC_DEFAULT] = 1 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_CYCLE names = c4d.BaseContainer() # Iterates and enumerate the list of addition no named arguments for i, arg in enumerate(*argv): names.SetString(i, arg) bc.SetContainer(c4d.DESC_CYCLE, names) # Check if a named argument "parentGroup" as been filled parentGroup = kargs.get("parentGroup", None) if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = val return element
The only drawback of this is that the if you want to set parentGroup, you have to explicitly do it in the call of the method like so
CreateUserDataCycle(obj, name, val,"String1", "String2", "String3", parentGroup=GroupId)
The other way could be to simply use a list to store all the data, this is probably the way a C++ developer would have choosen.
def CreateUserDataCycle(obj, name, val, entryList, parentGroup=None): if obj is None: return False bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = name bc[c4d.DESC_DEFAULT] = 1 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_CYCLE names = c4d.BaseContainer() for i, entry in enumerate(entryList): names.SetString(i, entry) bc.SetContainer(c4d.DESC_CYCLE, names) if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = val return element
And the method will be called this way [] bracket operator create a list, so in this case, we create a list of string.
CreateUserDataCycle(obj, name, val, ["String1", "String2", "String3"])
If you have any question, please let me know.
Cheers,
Maxime. -
Thank you again, @m_adam! I ended up using the C++ method, and it worked like a charm.
Also, I appreciate the link to the *args and **kwargs at pythontips.com. It was very helpful to understand those concepts.
I do have (many) more questions, but I'll start new posts since they're not related to this topic.
My Best,
Eric