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

    Cant select newly created Instance

    Cinema 4D SDK
    python
    3
    7
    1.1k
    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.
    • esanE
      esan
      last edited by

      Have a function that creates a new instance of selected object (or, child of selected for now) - that works. Have functions that creates Cycle UserData on selected object, then populates with its children - that works. Next step I thought would be a breeze is creating the User Data "on" the newly created instance. Im pretty new to python so im sure theres a blatantly obvious way to do It I keep dancing around.

      In my head, I think I want to pass the result of my createInstance function into my AddData function, but any means I try to do so gives me errors or no result. This version of my script is just from an arbitrary point so may be some other weirdness you could point out, just piecemealing at the moment.

      C4D R20

      import c4d
      from c4d import gui
      
      
      
      def createInstance(): # create Instance of Child of Selected Object
        if op is None:
            return
        obj = c4d.GeListNode.GetDown(op)
        inst = c4d.InstanceObject()
        inst.SetReferenceObject(obj)
      
        return obj
      
        c4d.EventAdd()
      
      
      def AddLongDataType(obj): # create User Data Container named Picker
        if obj is None: return
      
        bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG)
        bc[c4d.DESC_NAME] = "Picker"
      
        obj.AddUserData(bc)
      
        c4d.EventAdd()
      
      def setQuickTab(obj, data): # change User Date container to type Cycle, populate with Children Names
      
        descid = data[0][0]
        bc = data[0][1]
      
        # Get the number of child objects
        parent = doc.GetActiveObject()
        count = len(parent.GetChildren())
      
        H = c4d.GeListNode.GetChildren(parent)
      
        # Build new cycle options container dynamically
        cycle = c4d.BaseContainer()
      
        for i in range (0,count):
            child = H[i]
            n = c4d.BaseList2D.GetName(H[i])
            cycle.SetData(i, n)
      
      
        bc[c4d.DESC_CYCLE] = cycle
      
        # Set modified description data container
        obj.SetUserDataContainer(descid, bc)
      
        # Notify Cinema 4D for changes
        c4d.EventAdd()
      
      def main():
        
        AddLongDataType(obj)
        data = obj.GetUserDataContainer()
        setQuickTab(obj, data)
      
        doc.StartUndo()
        doc.InsertObject(inst)
        doc.AddUndo(c4d.UNDO_NEW, inst)
        doc.EndUndo()
      
      if __name__=='__main__':
        main()
      
      1 Reply Last reply Reply Quote 0
      • ?
        A Former User
        last edited by A Former User

        Hello @esan !
        I'm not entirely sure what the purpose of your script is, so I made changes to what you provided. You're probably already aware, but this script will only work with children of your selected object, not grandchildren.

        children.pnggrandchildren.png

        The biggest changes were:

        • createInstance: passing the selected object and returning the Instance created using BaseObject & the Instance Object type
        • Your loop for populating the Integer Cycle was reduced by several lines of code by using Python's enumerate function. It gives you an index and instance variable so you don't have to do it all manually. Here's an article about it.
        • To allow for a one-step Undo, you need to add Undos along the way in your functions. Here's more info about the types of Undos in the C4D SDK documentation.
        • This doesn't impact your script too much, but for future scripts it's better to use your main function to get references to your document and active object(s) and then pass them to the functions you write. That will allow you to call those GetActiveDocument and GetActiveObject methods less frequently and reuse your code in the case you want to pass other objects to the functions (e.g. the 'parent' variable in your setQuickTab function).
        import c4d
        from c4d import gui
        
        def createInstance(obj):
            inst = c4d.BaseObject(c4d.Oinstance) # created Instance with Base Object
            inst[c4d.INSTANCEOBJECT_LINK] = obj # set Instance's reference link
            # set the name using the object passed to the function
            inst[c4d.ID_BASELIST_NAME] = "%s Instance"%obj[c4d.ID_BASELIST_NAME]
            return inst # return the Instance instance
        
        def AddLongDataType(obj): # create User Data Container named Picker
            if obj is None: return
        
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG)
            bc[c4d.DESC_NAME] = "Picker"
        
            doc.AddUndo(c4d.UNDO_CHANGE, obj)
            obj.AddUserData(bc)
        
            c4d.EventAdd()
        
        def setQuickTab(obj, data): # change User Date container to type Cycle, populate with Children Names
            descid = data[0][0]
            bc = data[0][1]
        
            children = obj.GetChildren()
        
            # Build new cycle options container dynamically
            cycle = c4d.BaseContainer()
        
            for i,child in enumerate(children):
              cycle.SetData(i, child.GetName())
        
            bc[c4d.DESC_CYCLE] = cycle
        
            doc.AddUndo(c4d.UNDO_CHANGE, obj)
            # Set modified description data container
            obj.SetUserDataContainer(descid, bc)
        
        def main(doc):
            obj = doc.GetActiveObject()
            if (obj == None):
                gui.MessageDialog("Please select an object.")
                return
        
            doc.StartUndo()
            AddLongDataType(obj)
            data = obj.GetUserDataContainer()
            setQuickTab(obj, data)
            inst = createInstance(obj)
            doc.InsertObject(inst)
            doc.AddUndo(c4d.UNDO_NEW, inst)
            doc.EndUndo()
            c4d.EventAdd()
        
        if __name__=='__main__':
            main(doc)
        
        esanE 2 Replies Last reply Reply Quote 1
        • M
          m_adam
          last edited by

          Hi @esan beside what @blastframe already say, I would like to add that while the next code is working

          c4d.GeListNode.GetDown(op)
          

          This is not how it's designed to work and how oop works in general.
          the usual way will be

          op.GetDown()
          

          Since it's a method internally python will assign the first parameter to self (Or the Object we are asking the child, so in your case, self is op.) so in your case this is similar but it may not be always the case and this could cause several issues, so please try to write proper OOP code.

          For more information see Difference between Method and Function in Python

          Another way to your initial problem is to return both the Children Object and the created instance object with

          import c4d
          
          def createInstance(): # create Instance of Child of Selected Object
            if op is None:
                return
            obj = c4d.GeListNode.GetDown(op)
            inst = c4d.InstanceObject()
            inst.SetReferenceObject(obj)
          
            return obj, inst
          

          Then when you call createInstance you can simply do the next things

          child, instance = createInstance()
          

          Hope it helps,
          Cheers,
          Maxime.

          MAXON SDK Specialist

          Development Blog, MAXON Registered Developer

          esanE 1 Reply Last reply Reply Quote 0
          • esanE
            esan @m_adam
            last edited by

            @m_adam @blastframe

            Man your replies are a treasure trove of valuable info for me lol. I’ve found it tough finding focused info on c4d/python methods and the documentation lacks some context that makes it tough for beginners to implement, let alone “best practices”.

            Ya I had a version where I was handling all the “get active” stuff in “main” but tied myself in knots in other ways so reverted back to a safe place haha.

            Yes children are what I want, the purpose of the script is as follows:

            With my job I have to re-create a lot of Ui and their components in C4D. I built an xPresso rig onto an Instance object that dynamically sets reference object based on defined hierarchy [so I want all of the children in the root]. Then manually create the user data to reflect that hierarchy, setting the reference object. That’s all well and good, from that point I can dup my “icon” instance (that’s referencing all the objects in my “icon” root) and just pick which icon it is from the user data list. Makes it easy/fast and clean creating large scenes of Ui.

            But who wants to manually populate, and have to manually keep up with the user data fields to make sure they reflect children in your desired root. Didn’t see a way to automate that in xPresso so figured would be the perfect python exercise for me.

            Select root, run script. Script creates your instance and integer cycle user data populated with the child hierarchy...and also creates the xPresso with node network, but I suspect I may find keeping all that functionality (selecting ref object for given instance) in python will be easier than figured out how to create the xPresso node network in python.

            Again, thanks for the help!

            1 Reply Last reply Reply Quote 0
            • esanE
              esan @A Former User
              last edited by

              @blastframe Just tried to run the code and it seems the original issue is still present. User Data is being added to the Selected Root instead of the newly created instance. Also its now instancing the root, instead of first child. That part I can fix, and hopefully have enough here to figure out the rest to get my original intent!

              1 Reply Last reply Reply Quote 0
              • esanE
                esan
                last edited by

                This post is deleted!
                1 Reply Last reply Reply Quote 0
                • esanE
                  esan @A Former User
                  last edited by

                  @blastframe Ok was able to retrofit your version to get what I wanted, a bit of guesswork in here, but got the result im after lol

                  import c4d
                  from c4d import gui
                  
                  def createInstance():
                      obj = op.GetDown()
                      inst = c4d.BaseObject(c4d.Oinstance) # created Instance with Base Object
                      inst[c4d.INSTANCEOBJECT_LINK] = obj # set Instance's reference link
                      # set the name using the object passed to the function
                      inst[c4d.ID_BASELIST_NAME] = "%s Instance"%obj[c4d.ID_BASELIST_NAME]
                      return inst # return the Instance instance
                  
                  def AddLongDataType(obj): # create User Data Container named Picker
                      if obj is None: return
                  
                      bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG)
                      bc[c4d.DESC_NAME] = "Picker"
                  
                      doc.AddUndo(c4d.UNDO_CHANGE, obj)
                      obj.AddUserData(bc)
                  
                      c4d.EventAdd()
                  
                  def setQuickTab(obj, inst, data): # change User Date container to type Cycle, populate with Children Names
                      descid = data[0][0]
                      bc = data[0][1]
                  
                      children = obj.GetChildren()
                  
                      # Build new cycle options container dynamically
                      cycle = c4d.BaseContainer()
                  
                      for i,child in enumerate(children):
                        cycle.SetData(i, child.GetName())
                  
                      bc[c4d.DESC_CYCLE] = cycle
                  
                      doc.AddUndo(c4d.UNDO_CHANGE, obj)
                      # Set modified description data container
                      inst.SetUserDataContainer(descid, bc)
                  
                  def main(doc):
                      obj = doc.GetActiveObject()
                      if (obj == None):
                          gui.MessageDialog("Please select the Root of the Assets you wish to Instance.")
                          return
                  
                      doc.StartUndo()
                      inst = createInstance()
                      doc.InsertObject(inst)
                      AddLongDataType(inst)
                      data = inst.GetUserDataContainer()
                      setQuickTab(obj, inst, data)
                      
                      doc.AddUndo(c4d.UNDO_NEW, inst)
                      doc.EndUndo()
                      c4d.EventAdd()
                  
                  if __name__=='__main__':
                      main(doc)
                  
                  1 Reply Last reply Reply Quote 1
                  • First post
                    Last post