Cant select newly created Instance



  • 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()
    


  • 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)
    


  • 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.



  • @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!



  • @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!



  • This post is deleted!


  • @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)
    

Log in to reply