Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Convenience function for Combobox

    Cinema 4D SDK
    r19 python macos
    2
    5
    1.0k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • S
      saputello2
      last edited by

      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()
      
      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by m_adam

        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.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        1 Reply Last reply Reply Quote 2
        • S
          saputello2
          last edited by

          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

          1 Reply Last reply Reply Quote 0
          • M
            m_adam
            last edited by

            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.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            1 Reply Last reply Reply Quote 2
            • S
              saputello2
              last edited by

              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

              1 Reply Last reply Reply Quote 0
              • First post
                Last post