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

    Insert object in Treeview

    Cinema 4D SDK
    r20 python
    2
    4
    966
    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.
    • P
      pim
      last edited by pim

      The post https://developers.maxon.net/forum/topic/10654/14102_using-customgui-listview gives a great example of using Treeview.
      I tried to add an InsertUnder, but that does not work.
      What am I doing wrong?
      I tested it using R20 and R21.

      # In CreateLayout() I added a simple button
              self.AddButton(1002, c4d.BFH_CENTER, name="Insert Under First")
      
      # In Command() I added:
              if id == 1002:
                  # Insert Under
                  tex = TextureObject("Inserted under first item.")
                  #self._listView.SetDragObject(root, userdata, tex)
      
                  first = self._listView.GetFirst(self._treegui, self._listView)
                  print "First: ", first                # seems ok. It returns the first object T1
      
                  #InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
                  self._listView.InsertObject(self._treegui, self._listView, first, c4d.DRAGTYPE_FILENAME_OTHER, tex, c4d.INSERT_UNDER, True)
                  
                  # Refresh the TreeView
                  self._treegui.Refresh()
      
      # In ListView() I added:
          def InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
              print "Insert Object obj: ", obj                       #seems ok, T1
              print "dragobject: ", dragobject                    #seems ok, Inserted under first item. 
              #self._listView.InsertObject(self._treegui, self._listView, first, c4d.DRAGTYPE_FILENAME_OTHER, tex, c4d.INSERT_UNDER, True)
              return True
      
      1 Reply Last reply Reply Quote 0
      • P
        pim
        last edited by

        Ok, after some more reading I now understand that I should insert the object in the listview myself.

            def InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy):
                self.listOfTexture.append(dragobject)
                return True
        

        03cbb8df-61c9-4f0c-9979-c2b2b0412f0d-image.png
        But now it is inserted at the end (makes sense, because I use an append()).
        My question is how to insert the new object under another one?

        Something like this.
        037ef554-259c-451c-9e5b-6fdaa71e4256-image.png

        I am trying to make treeview of a folder with its subfolders and files.

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

          Hi @pim

          Basically as explained in TreeView made simple – Part 1[URL-REMOVED] you have to override GetDown to make it actually return the first child.
          Then GetNext will be called to retrieve the next children.

          So I would say it's more about how to structure your data that matters.
          But since you seem to use the previous example I extended it to also support children.

          So first we will extend our TextureObject to support children.
          To do so we will add a list that will store all children and also a weakref of our parent. (don't forget to import weakref).
          If you don't know what is a weakref I encourage you to read weakref – Garbage-collectable references to objects.

              def __init__(self, texturePath):
                  self.texturePath = texturePath
                  self.otherData += texturePath
                  self.children = [] # This will store all children of the current TextureObject.
                  self._parent = None # This will store a weakreaf (so don't forget to import weakref) to the parent.
          

          Then we will define some convenient functions in our TextureObject:

              def AddChild(self, obj):
                  obj._parent = weakref.ref(self)
                  self.children.append(obj)
                  
              def GetChildren(self):
                  return self.children
          
              def GetParent(self):
                  if self._parent:
                      return self._parent()
                  
                  return None
          

          Once it's done it's time to adapt our TreeViewFunctions implementation to add support for children.
          So the first thing to override is GetDown() to retrieve the first child of a given TextureObject like so.

              def GetDown(self, root, userdata, obj):
                  """
                  Return a child of a node, since we only want a list, we return None everytime
                  """
                  children = obj.GetChildren()
                  if children:
                      return children[0]
          
                  return None
          

          The treeview will call GetDown to retrieve the first child, then to retrieve the next child it will call GetNext on it.
          So we need to adapt GetNext and GetPref to also support children.
          Typically we will use the same logic, the only difference is is the current obj have a parent, look for self in the parent list instead of the global one stored in our TreeViewFunctions implementation.

              def GetNext(self, root, userdata, obj):
                  """
                  Returns the next Object to display after arg:'obj'
                  """
                  rValue = None
          
                  # If does have a child it means it's a child.
                  objParent = obj.GetParent()
                  listToSearch = objParent.GetChildren() if objParent is not None else self.listOfTexture
          
                  currentObjIndex = listToSearch.index(obj)
                  nextIndex = currentObjIndex + 1
                  if nextIndex < len(listToSearch):
                      rValue = listToSearch[nextIndex]
          
                  return rValue
          
              def GetPred(self, root, userdata, obj):
                  """
                  Returns the previous Object to display before arg:'obj'
                  """
                  rValue = None
          
                  # If does have a child it means it's a child.
                  objParent = obj.GetParent()
                  listToSearch = objParent.GetChildren() if objParent is not None else self.listOfTexture
          
                  currentObjIndex = listToSearch.index(obj)
                  predIndex = currentObjIndex - 1
                  if 0 <= predIndex < len(listToSearch):
                      rValue = listToSearch[predIndex]
          
                  return rValue
          

          We have everything needed now to display everything.

          However, we also need to adapt DeletePressed to support children.
          DeletePressed is a global event without a passed obj.
          Previously we iterated listOfTexture so we need to support children. To do so let's make an iterator that will iterate all nodes and all children.
          And let use it in the DeletePressed function.

          # This is a global function which accept a list and will iterate over each TextureObject
          # of the passed list to retrieve all TextureObject and all their children.
          def TextureObjectIterator(lst):
              
              for parentTex in lst:
                  yield parentTex
                  TextureObjectIterator(parentTex.GetChildren())
          
          
              def DeletePressed(self, root, userdata):
                  "Called when a delete event is received."
          		
          		# Convert the iterator to a list to be able to reverse it
                  for tex in reversed(list(TextureObjectIterator(self.listOfTexture))):
                      if tex.IsSelected:
                          objParent = tex.GetParent()
                          listToRemove = objParent.GetChildren() if objParent is not None else self.listOfTexture
                          listToRemove.remove(tex)
          

          Now everything is done for the TreeViewFunctions implementation.
          So let's add a new button in our GeDialog CreateLayout to add a child to the selected TextureObject.
          And defines its behavior when clicked in the GeDialog Command.

          		# In CreateLayout
                  self.AddButton(1002, c4d.BFH_CENTER, name="Add Child to selected")
          		
          		# In Command
                  if id == 1002:
                      for parentTex in TextureObjectIterator(self._listView.listOfTexture):
                          if not parentTex.IsSelected:
                              continue
          
                          newID = len(parentTex.GetChildren()) + 1 
                          tex = TextureObject("T{0}.{1}".format(str(parentTex), newID))
                          parentTex.AddChild(tex)
                          
                      # Refresh the TreeView
                      self._treegui.Refresh()
          

          Now that we have everything we may also want to support the folding state to show/hide children in the treeview.
          So let's enhance our TextureObject to support folding (very similar to selection state).

          class TextureObject(object):
          
              _open = True
          	
              @property
              def IsOpened(self):
                  return self._open
          
              def Open(self):
                  self._open = True
              
              def Close(self):
                  self._open = False
          

          Then in the TreeViewFunctions implementation we need to override IsOpened and Open.

              def IsOpened(self, root, userdata, obj):
                  """
                  Returns: (bool): Status If it's opened = True (folded) or closed = False.
                  """
                  return obj.IsOpened()
          
              def Open(self, root, userdata, obj, onoff):
                  """
                  Called when the user clicks on a folding state of an object to display/hide its children
                  """
                  if onoff:
                      obj.Open()
          
                  else:
                      obj.Close()
          

          And here you are, find the full code in pastebin, keep in mind it's only one way to store data.
          How you decide to store data is up to you.

          In this case, it would make more sense to have a TextureObject as root, so this way all TextureObject will be handled in the same way and the first level is not stored in a specific one level the only list.
          Maybe it will be for part 3 of TreeView Exploration! 🙂

          Cheers,
          Maxime.


          [URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.

          MAXON SDK Specialist

          Development Blog, MAXON Registered Developer

          1 Reply Last reply Reply Quote 1
          • P
            pim
            last edited by

            Thanks, great explanation!
            One small issue. Delete doesn't work because objParent' is not defined.

            Traceback (most recent call last):
            File "scriptmanager", line 251, in DeletePressed
            NameError: global name 'objParent' is not defined
            

            Here the code that, I think, solves the issue:

                def DeletePressed(self, root, userdata):
                    "Called when a delete event is received."
                    for tex in reversed(list(TextureObjectIterator(self.listOfTexture))):
                        if tex.IsSelected:
                            objParent = tex.GetParent()               # Added
                            listToRemove = objParent.GetChildren() if objParent is not None else self.listOfTexture
                            listToRemove.remove(tex)
            
            1 Reply Last reply Reply Quote 1
            • First post
              Last post