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