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

    Cinema crashes renaming items in a TreeView

    Bugs
    r20 s26 python windows macos
    2
    11
    2.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.
    • H
      HerrMay
      last edited by ferdinand

      Hi guys,

      I'm currently testing around with the Treeview GUI. For that I'm using the example made by Niklas Rosenstein. But instead of building a Treeview for objects I changed the code to build a Treeview for materials and their shaders.

      TL;DR
      The Treeview is working so far as expected. The problem I encounter is when I try to rename any item in the Treeview. Cinema reliably crashes when I do so.

      I don't know if this is a bug or - what's more likely - something is done wrong with my implementation of the Treeview and its methods.

      I also tried to build the Treeview by wrapping every material with its shaders in a custom class. While that works in regards to the renaming procedure (Cinema then does not crash), the Treeview does not refresh correctly. That is, deleting any material or shader in the document does not get reflected in the Treeview. That is why I went back to the example below.

      Here's the code I'm using. I hope someone can enlighten me on this one.

      Cheers,
      Sebastian

      import c4d
      import weakref
      
      
      class Hierarchy(c4d.gui.TreeViewFunctions):
      
          def __init__(self, dlg):
              # Avoid a cyclic reference.
              self._dlg = weakref.ref(dlg)
      
      
          def GetFirst(self, root, userdata):
              """
              Return the first element in the hierarchy. This can be any Python
              object. With Cinema 4D nodes, it is easy to implement, but it could
              as well be a Python integer which you can use as an index in a list.
      
              Returns:
              (any or None): The first element in the hierarchy or None.
              """
      
              doc = c4d.documents.GetActiveDocument()
              return doc.GetFirstMaterial()
      
      
          def GetDown(self, root, userdata, node):
              """
              Returns:
              (any or None): The child element going from *node* or None.
              """
      
              if isinstance(node, c4d.Material):
                  return node.GetFirstShader()
              
              return node.GetDown()
      
      
          def GetNext(self, root, userdata, node):
              """
              Returns:
              (any or None): The next element going from *node* or None.
              """
      
              return node.GetNext()
      
      
          def GetPred(self, root, userdata, node):
              """
              Returns:
              (any or None): The previous element going from *node* or None.
              """
      
              return node.GetPred()
      
      
          def GetName(self, root, userdata, node):
              """
              Returns:
              (str): The name to display for *node*.
              """
      
              return node.GetName()
      
      
          def Open(self, root, userdata, node, opened):
              """
              Called when the user opens or closes the children of *obj* in
              the Tree View.
              """
              
              doc = c4d.documents.GetActiveDocument()
              doc.StartUndo()
              doc.AddUndo(c4d.UNDOTYPE_CHANGE_SELECTION, node)
      
              if opened:
                  node.SetBit(c4d.BIT_OFOLD)
              else:
                  node.DelBit(c4d.BIT_OFOLD)
      
              doc.EndUndo()
              c4d.EventAdd()
      
      
          def IsOpened(self, root, userdata, node):
              """
              Returns:
              (bool): True if *obj* is opened (expaneded), False if it is
                  closed (collapsed).
              """
      
              return node.GetBit(c4d.BIT_OFOLD)
      
      
          def DeletePressed(self, root, userdata):
              """
              Called when the user right-click Deletes or presses the Delete key.
              Should delete all selected elements.
              """
      
              def delete_recursive(node):
                  if node is None: 
                      return
                      
                  if node.GetBit(c4d.BIT_ACTIVE) == True:
                      doc.AddUndo(c4d.UNDOTYPE_DELETE, node)
                      node.Remove()
                      return
      
                  for child in node.GetChildren():
                      delete_recursive(child)
      
              doc = c4d.documents.GetActiveDocument()
              doc.StartUndo()
      
              for mat in doc.GetMaterials():
                  delete_recursive(mat)
      
              doc.EndUndo()
              c4d.EventAdd()
      
      
          def Deselect(self, doc):
      
              for mat in iter_baselist(doc.GetFirstMaterial()):
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, mat)
                  mat.DelBit(c4d.BIT_ACTIVE)
                  for shader in iter_children(mat.GetFirstShader()):
                      doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, shader)
                      shader.DelBit(c4d.BIT_ACTIVE)
      
      
          def Select(self, root, userdata, node, mode):
              """
              Called when the user selects an element.
              """
      
              doc = c4d.documents.GetActiveDocument()
              doc.StartUndo()
              doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, node)
      
              if mode == c4d.SELECTION_NEW:
                  self.Deselect(doc)
                  node.SetBit(c4d.BIT_ACTIVE)
      
              elif mode == c4d.SELECTION_ADD:
                  node.SetBit(c4d.BIT_ACTIVE)
      
              elif mode == c4d.SELECTION_SUB:
                  node.DelBit(c4d.BIT_ACTIVE)
      
              doc.EndUndo()
              c4d.EventAdd()
      
      
          def IsSelected(self, root, userdata, node):
              """
              Returns:
              (bool): True if *obj* is selected, False if not.
              """
      
              return node.GetBit(c4d.BIT_ACTIVE)
      
      
          def IsResizeColAllowed(self, root, userdata, lColID):
              if lColID > 2:
                  return True
              return False
      
      
          def IsTristate(self, root, userdata):
              return False
      
      
          def GetDragType(self, root, userdata, node):
              """
              Returns:
              (int): The drag datatype.
              """
      
              return c4d.NOTOK
      
      
          # def DragStart(self, root, userdata, node):
          #     """
          #     Returns:
          #     (int): Bitmask specifying options for the drag, whether it is
          #         allowed, etc.
          #     """
      
          #     return c4d.TREEVIEW_DRAGSTART_ALLOW | c4d.TREEVIEW_DRAGSTART_SELECT
      
      
          def GetId(self, root, userdata, node):
              """
              Return a unique ID for the element in the TreeView.
              """
      
              return node.GetUniqueID()
      
      
          def DoubleClick(self, root, userdata, node, col, mouseinfo):
              """
              Called when the user double-clicks on an entry in the TreeView.
      
              Returns:
              (bool): True if the double-click was handled, False if the
                  default action should kick in. The default action will invoke
                  the rename procedure for the object, causing `SetName()` to be
                  called.
              """
      
              if col == ID_TREEVIEW:
                  return False
              else:
                  return True
      
      
          # Performing a rename on any item in the Treeview is crashing Cinema reliably. 
          def SetName(self, root, userdata, node, name):
              """
              Called when the user renames the element. `DoubleClick()` must return
              False for this to work.
              """
      
              doc = c4d.documents.GetActiveDocument()
              doc.StartUndo()
              doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, node)
              node.SetName(name)
              doc.EndUndo()
              c4d.EventAdd()
      
      
          def DrawCell(self, root, userdata, node, col, drawinfo, bgColor):
              """
              Called for a cell with layout type `c4d.LV_USER` or `c4d.LV_USERTREE`
              to draw the contents of a cell.
              """
      
              if col == ID_ICON:
                  icon = node.GetIcon()
                  drawinfo["frame"].DrawBitmap(
                      icon["bmp"], drawinfo["xpos"], drawinfo["ypos"],
                      16, 16, icon["x"], icon["y"], icon["w"], icon["h"], c4d.BMP_ALLOWALPHA)
      
              if col == ID_TREEVIEW_TYPE:
                  name = node.GetTypeName()
                  geUserArea = drawinfo["frame"]
                  w = geUserArea.DrawGetTextWidth(name)
                  h = geUserArea.DrawGetFontHeight()
                  xpos = drawinfo["xpos"]
                  ypos = drawinfo["ypos"] + drawinfo["height"]
                  drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
      
              if col == ID_TREEVIEW_CHANNEL:
                  if isinstance(node, c4d.Material):
                      return
      
                  if node == self.GetShader(c4d.MATERIAL_COLOR_SHADER):
                      name = "Color"
      
                  elif node == self.GetShader(c4d.MATERIAL_LUMINANCE_SHADER):
                      name = "Luminance"
      
                  elif node == self.GetShader(c4d.MATERIAL_DIFFUSION_SHADER):
                      name = "Diffusion"
                  
                  else:
                      name = ""
      
                  if name != "":
                      geUserArea = drawinfo["frame"]
                      w = geUserArea.DrawGetTextWidth(name)
                      h = geUserArea.DrawGetFontHeight()
                      xpos = drawinfo["xpos"]
                      ypos = drawinfo["ypos"] + drawinfo["height"]
                      drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
      
      
          def SetCheck(self, root, userdata, node, column, checked, msg):
              """
              Called when the user clicks on a checkbox for an object in a
              `c4d.LV_CHECKBOX` column.
              """
      
              if checked:
                  node.SetBit(BIT_CHECKED)
              else:
                  node.DelBit(BIT_CHECKED)
      
              def checked_collect(op):
                  if op is None: 
                      return
                  elif isinstance(op, c4d.documents.BaseDocument):
                      for obj in op.GetObjects():
                          for x in checked_collect(obj):
                              yield x
                  else:
                      if op.GetBit(BIT_CHECKED):
                          yield op
                      for child in op.GetChildren():
                          for x in checked_collect(child):
                              yield x
      
              status = ', '.join(obj.GetName() for obj in checked_collect(node.GetDocument()))
              self._dlg().SetString(1001, "Checked: " + status)
              self._dlg()._treegui.Refresh()
      
      
          def IsChecked(self, root, userdata, node, column):
              """
              Returns:
              (int): Status of the checkbox in the specified *column* for *obj*.
              """
      
              val = node.GetBit(BIT_CHECKED)
      
              if val == True:
                  return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
              else:
                  return c4d.LV_CHECKBOX_ENABLED
      
      
          def HeaderClick(self, root, userdata, column, channel, is_double_click, mouseX, mouseY, ua):
              """
              Called when the TreeView header was clicked.
      
              Returns:
              (bool): True if the event was handled, False if not.
              """
      
              c4d.gui.MessageDialog("You clicked on the '%i' header." % (column))
              return True
      
      
          def AcceptDragObject(self, root, userdata, node, dragtype, dragobject):
              """
              Called when a drag & drop operation hovers over the TreeView to check
              if the drag can be accepted.
      
              Returns:
              (int, bool)
              """
      
              # if dragtype != c4d.DRAGTYPE_ATOMARRAY:
              #     return 0
      
              # return c4d.INSERT_BEFORE | c4d.INSERT_AFTER | c4d.INSERT_UNDER, True
      
              return 0
      
      
          def GenerateDragArray(self, root, userdata, node):
              """
              Return:
              (list of c4d.BaseList2D): Generate a list of objects that can be
                  dragged from the TreeView for the `c4d.DRAGTYPE_ATOMARRAY` type.
              """
      
              if node.GetBit(c4d.BIT_ACTIVE):
                  return [node, ]
      
      
          def InsertObject(self, root, userdata, node, dragtype, dragobject, insertmode, bCopy):
              """
              Called when a drag is dropped on the TreeView.
              """
      
              if dragtype != c4d.DRAGTYPE_ATOMARRAY:
                  return # Shouldnt happen, we catched that in AcceptDragObject
      
              for op in dragobject:
                  op.Remove()
      
                  if insertmode == c4d.INSERT_BEFORE:
                      op.InsertBefore(node)
                  elif insertmode == c4d.INSERT_AFTER:
                      op.InsertAfter(node)
                  elif insertmode == c4d.INSERT_UNDER:
                      op.InsertUnder(node)
              return
      
      
          def GetColumnWidth(self, root, userdata, node, col, area):
              """Measures the width of cells.
      
              Although this function is called #GetColumnWidth and has a #col, it is
              not only executed by column but by cell. So, when there is a column
              with items requiring the width 5, 10, and 15, then there is no need
              for evaluating all items. Each item can return its ideal width and
              Cinema 4D will then pick the largest value.
      
              Args:
                  root (any): The root node of the tree view.
                  userdata (any): The user data of the tree view.
                  obj (any): The item for the current cell.
                  col (int): The index of the column #obj is contained in.
                  area (GeUserArea): An already initialized GeUserArea to measure
                   the width of strings.
              
              Returns:
                  TYPE: Description
              """
              # The default width of a column is 80 units.
              width = 80
              # Replace the width with the text width. area is a prepopulated
              # user area which has already setup all the font stuff, we can
              # measure right away.
      
              if col == ID_ICON:
                  return 5
      
              if col == ID_TREEVIEW:
                  return area.DrawGetTextWidth(node.GetName()) + 5
                  
              if col in (ID_TREEVIEW_TYPE, ID_TREEVIEW_CHANNEL):
                  return area.DrawGetTextWidth(node.GetTypeName()) + 5
                  
              return width
      
      
          def IsMoveColAllowed(self, root, userdata, lColID):
              # The user is allowed to move all columns.
              # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
              return False
      
      
          def GetColors(self, root, userdata, node, pNormal, pSelected):
              """
              Retrieve colors for the TreeView elements.
      
              Returns:
              (int or c4d.Vector, int or c4d.Vector): The colors for the normal
                  and selected state of the element.
              """
      
              usecolor = node[c4d.ID_BASEOBJECT_USECOLOR]
      
              if usecolor == c4d.ID_BASEOBJECT_USECOLOR_ALWAYS:
                  pNormal = node[c4d.ID_BASEOBJECT_COLOR]
      
              return pNormal, pSelected
      
      
          # Context menu entry for "Hello World!"
          ID_HELLOWORLD = 355435345
      
      
          def CreateContextMenu(self, root, userdata, node, lColumn, bc):
              """
              User clicked with the right mouse button on an entry so
              we can here enhance the menu
              """
      
              bc.SetString(self.ID_HELLOWORLD, "Set Original Name")
      
      
          def ContextMenuCall(self, root, userdata, node, lColumn, lCommand):
              """
              The user executes an entry of the context menu.
      
              Returns:
              (bool): True if the event was handled, False if not.
              """
      
              if lCommand == self.ID_HELLOWORLD:
                  doc = c4d.documents.GetActiveDocument()
                  doc.StartUndo()
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, node)
                  node.SetName(node.GetTypeName())
                  doc.EndUndo()
                  c4d.EventAdd()
                  return True
      
              return False
      
      
          def SelectionChanged(self, root, userdata):
              """
              Called when the selected elements in the TreeView changed.
              """
      
              print("The selection changed")
      
      
          def Scrolled(self, root, userdata, h, v, x, y):
              """
              Called when the TreeView is scrolled.
              """
      
              self._dlg().SetString(1002, ("H: %i V: %i X: %i Y: %i" % (h, v, x, y)))
      
      
      class TestDialog(c4d.gui.GeDialog):
      
          _treegui = None
      
          def CreateLayout(self):
              # Create the TreeView GUI.
              customgui = c4d.BaseContainer()
              customgui.SetBool(c4d.TREEVIEW_BORDER, True)
              customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True)
              customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False)
              customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True)
              customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True)
              customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True)
              customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True)
              
              self._treegui = self.AddCustomGui(
              1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,
              300, 300, customgui)
      
              if not self._treegui:
                  print("[ERROR]: Could not create TreeView")
                  return False
      
              self.AddMultiLineEditText(1001, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_BOTTOM, 0, 40)
              self.AddEditText(1002, c4d.BFH_SCALEFIT, 0, 25)
              return True
      
      
          def CoreMessage(self, id, msg):
              if id == c4d.EVMSG_CHANGE:
                  # Update the treeview on each change in the document.
                  self._treegui.Refresh()
              return True
      
      
          def InitValues(self):
              # Initialize the column layout for the TreeView.
              layout = c4d.BaseContainer()
              layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
              layout.SetLong(ID_ICON, c4d.LV_USER)
              layout.SetLong(ID_TREEVIEW, c4d.LV_TREE)
              layout.SetLong(ID_TREEVIEW_TYPE, c4d.LV_USER)
              layout.SetLong(ID_TREEVIEW_CHANNEL, c4d.LV_USER)
              self._treegui.SetLayout(5, layout)
      
              # Set the header titles.
              self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
              self._treegui.SetHeaderText(ID_ICON, "Icon")
              self._treegui.SetHeaderText(ID_TREEVIEW, "Name")
              self._treegui.SetHeaderText(ID_TREEVIEW_TYPE, "Type")
              self._treegui.SetHeaderText(ID_TREEVIEW_CHANNEL, "Channel")
              self._treegui.Refresh()
      
              # Don't need to store the hierarchy, due to SetRoot()
      
              root = doc = c4d.documents.GetActiveDocument()
              data_model = Hierarchy(self)
              self._treegui.SetRoot(root, data_model, None)
      
              self.SetString(1002, "Scroll values")
      
              return True
      
      
      class MenuCommand(c4d.plugins.CommandData):
      
          dialog = None
      
          def Execute(self, doc):
              """
              Just create the dialog when the user clicked on the entry
              in the plugins menu to open it
              """
      
              if self.dialog is None:
                  self.dialog = TestDialog()
      
              return self.dialog.Open(
                  c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
      
          def RestoreLayout(self, sec_ref):
              """
              Same for this method. Just allocate it when the dialog is needed.
              """
      
              if self.dialog is None:
                  self.dialog = TestDialog()
      
              return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
      
      
      def main():
      
          # Using a global variable here for testing only.
          global dialog
          dialog = TestDialog()
          dialog.Open(c4d.DLG_TYPE_ASYNC, defaulth=600, defaultw=600)
      
      
      if __name__ == "__main__":
        main()
      
      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @HerrMay
        last edited by ferdinand

        Hello @herrmay,

        Thank you for reaching out to us. I unfortunately cannot run your code, as it seems to be missing quite a lot of integer ID symbols.

        fcb2a8c5-0bca-4ee7-9b28-5de6163ba462-image.png

        25b310f0-d247-45d8-8692-182ab4cf6894-image.png

        Could you please provide a version that is executable?

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        H 1 Reply Last reply Reply Quote 0
        • H
          HerrMay @ferdinand
          last edited by

          Hello @ferdinand,

          must've cropped something there. Of course, here you go.

          # Example of using the Cinema 4D Tree View GUI in Python.
          
          import c4d
          import os
          import weakref
          
          # Be sure to use a unique ID obtained from [URL-REMOVED]
          PLUGIN_ID = 9912399
          PLUGIN_NAME = "Python TreeView Example"
          PLUGIN_HELP = "Show the current scene hierarchy in a tree."
          
          # Bit for the Checkbox in the GUI. Kind of a bad practice to use this
          # but reduces complexity for this example.
          BIT_CHECKED = 256
          # TreeView Column IDs.
          ID_CHECKBOX = 1
          ID_ICON = 2
          ID_TREEVIEW = 3
          ID_TREEVIEW_TYPE = 4
          ID_TREEVIEW_CHANNEL = 5
          
          def TraverseShaders(node):
              """Yields all shaders that are hierarchical or shader branch descendants of #node.
          
              Semi-iterative traversal is okay in this case, at least I did not bother with implementing it fully
              iteratively here.
          
              In case it is unclear, you can throw any kind of node at this, BaseObject, BaseMaterial,
              BaseShader, etc., to discover shaders which a descendants of them.
              """
              if node is None:
                  return
          
              while node:
                  if isinstance(node, c4d.BaseShader):
                      yield node
          
                  # The shader branch traversal.
                  for descendant in TraverseShaders(node.GetFirstShader()):
                      yield descendant
          
                  # The hierarchical traversal.
                  for descendant in TraverseShaders(node.GetDown()):
                      yield descendant
          
                  node = node.GetNext()
          
          def get_next(node):
          
              if isinstance(node, c4d.Material) and node.GetFirstShader():
                  return node.GetFirstShader()
          
              if node.GetDown():
                  return node.GetDown()
          
              while not node.GetNext() and node.GetUp():
                  node = node.GetUp()
          
              return node.GetNext()
          
          
          def iter_children(node):
              """Yields all descendants of ``node`` in a truly iterative fashion.
          
              The passed node itself is yielded as the first node and the node graph is
              being traversed in depth first fashion.
          
              This will not fail even on the most complex scenes due to truly
              hierarchical iteration. The lookup table to do this, is here solved with
              a dictionary which yields favorable look-up times in especially larger
              scenes but results in a more convoluted code. The look-up could
              also be solved with a list and then searching in the form ``if node in
              lookupTable`` in it, resulting in cleaner code but worse runtime metrics
              due to the difference in lookup times between list and dict collections.
              """
              if not node:
                  return
          
              # The lookup dictionary and a terminal node which is required due to the
              # fact that this is truly iterative, and we otherwise would leak into the
              # ancestors and siblings of the input node. The terminal node could be
              # set to a different node, for example ``node.GetUp()`` to also include
              # siblings of the passed in node.
              visisted = {}
              terminator = node
          
              while node:
                  # C4DAtom is not natively hashable, i.e., cannot be stored as a key
                  # in a dict, so we have to hash them by their unique id.
                  node_uuid = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
                  if not node_uuid:
                      raise RuntimeError("Could not retrieve UUID for {}.".format(node))
          
                  # Yield the node when it has not been encountered before.
                  if not visisted.get(bytes(node_uuid)):
                      yield node
                      visisted[bytes(node_uuid)] = True
          
                  # Attempt to get the first child of the node and hash it.
                  child = node.GetDown()
          
                  if child:
                      child_uuid = child.FindUniqueID(c4d.MAXON_CREATOR_ID)
                      if not child_uuid:
                          raise RuntimeError("Could not retrieve UUID for {}.".format(child))
          
                  # Walk the graph in a depth first fashion.
                  if child and not visisted.get(bytes(child_uuid)):
                      node = child
          
                  elif node == terminator:
                      break
          
                  elif node.GetNext():
                      node = node.GetNext()
          
                  else:
                      node = node.GetUp()
          
          
          def iter_shader(shader):
          
              for shd in iter_children(shader):
                  yield shd
          
          
          def iter_baselist(node):
          
              while node:
                  yield node
                  node = node.GetNext()
          
          
          class Hierarchy(c4d.gui.TreeViewFunctions):
          
              def __init__(self, dlg):
                  # Avoid a cyclic reference.
                  self._dlg = weakref.ref(dlg)
          
          
              def GetShader(self, shader):
                  doc = c4d.documents.GetActiveDocument()
                  mat = doc.GetFirstMaterial()
                  return next(iter_shader(mat[shader]), None)
          
          
              def GetFirst(self, root, userdata):
                  """
                  Return the first element in the hierarchy. This can be any Python
                  object. With Cinema 4D nodes, it is easy to implement, but it could
                  as well be a Python integer which you can use as an index in a list.
          
                  Returns:
                  (any or None): The first element in the hierarchy or None.
                  """
          
                  doc = c4d.documents.GetActiveDocument()
                  return doc.GetFirstMaterial()
          
          
              def GetDown(self, root, userdata, node):
                  """
                  Returns:
                  (any or None): The child element going from *node* or None.
                  """
          
                  if isinstance(node, c4d.Material):
                      return node.GetFirstShader()
          
                  return node.GetDown()
          
          
              def GetNext(self, root, userdata, node):
                  """
                  Returns:
                  (any or None): The next element going from *node* or None.
                  """
          
                  return node.GetNext()
          
          
              def GetPred(self, root, userdata, node):
                  """
                  Returns:
                  (any or None): The previous element going from *node* or None.
                  """
          
                  return node.GetPred()
          
          
              def GetName(self, root, userdata, node):
                  """
                  Returns:
                  (str): The name to display for *node*.
                  """
          
                  return node.GetName()
          
          
              def Open(self, root, userdata, node, opened):
                  """
                  Called when the user opens or closes the children of *obj* in
                  the Tree View.
                  """
          
                  doc = c4d.documents.GetActiveDocument()
                  doc.StartUndo()
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SELECTION, node)
          
                  if opened:
                      node.SetBit(c4d.BIT_OFOLD)
                  else:
                      node.DelBit(c4d.BIT_OFOLD)
          
                  doc.EndUndo()
                  c4d.EventAdd()
          
          
              def IsOpened(self, root, userdata, node):
                  """
                  Returns:
                  (bool): True if *obj* is opened (expaneded), False if it is
                      closed (collapsed).
                  """
          
                  return node.GetBit(c4d.BIT_OFOLD)
          
          
              def DeletePressed(self, root, userdata):
                  """
                  Called when the user right-click Deletes or presses the Delete key.
                  Should delete all selected elements.
                  """
          
                  def delete_recursive(node):
                      if node is None:
                          return
          
                      if node.GetBit(c4d.BIT_ACTIVE) == True:
                          doc.AddUndo(c4d.UNDOTYPE_DELETE, node)
                          node.Remove()
                          return
          
                      for child in node.GetChildren():
                          delete_recursive(child)
          
                  doc = c4d.documents.GetActiveDocument()
                  doc.StartUndo()
          
                  for mat in doc.GetMaterials():
                      delete_recursive(mat)
          
                  doc.EndUndo()
                  c4d.EventAdd()
          
          
              def Deselect(self, doc):
          
                  for mat in iter_baselist(doc.GetFirstMaterial()):
                      doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, mat)
                      mat.DelBit(c4d.BIT_ACTIVE)
                      for shader in iter_children(mat.GetFirstShader()):
                          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, shader)
                          shader.DelBit(c4d.BIT_ACTIVE)
          
          
              def Select(self, root, userdata, node, mode):
                  """
                  Called when the user selects an element.
                  """
          
                  doc = c4d.documents.GetActiveDocument()
                  doc.StartUndo()
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, node)
          
                  if mode == c4d.SELECTION_NEW:
                      self.Deselect(doc)
                      node.SetBit(c4d.BIT_ACTIVE)
          
                  elif mode == c4d.SELECTION_ADD:
                      node.SetBit(c4d.BIT_ACTIVE)
          
                  elif mode == c4d.SELECTION_SUB:
                      node.DelBit(c4d.BIT_ACTIVE)
          
                  doc.EndUndo()
                  c4d.EventAdd()
          
          
              def IsSelected(self, root, userdata, node):
                  """
                  Returns:
                  (bool): True if *obj* is selected, False if not.
                  """
          
                  return node.GetBit(c4d.BIT_ACTIVE)
          
          
              def DoubleClick(self, root, userdata, node, col, mouseinfo):
                  """
                  Called when the user double-clicks on an entry in the TreeView.
          
                  Returns:
                  (bool): True if the double-click was handled, False if the
                      default action should kick in. The default action will invoke
                      the rename procedure for the object, causing `SetName()` to be
                      called.
                  """
          
                  if col == ID_TREEVIEW:
                      return False
                  else:
                      return True
          
          
              def IsResizeColAllowed(self, root, userdata, lColID):
                  if lColID > 2:
                      return True
                  return False
          
          
              def IsTristate(self, root, userdata):
                  return False
          
          
              def GetDragType(self, root, userdata, node):
                  """
                  Returns:
                  (int): The drag datatype.
                  """
          
                  return c4d.NOTOK
          
          
              # def DragStart(self, root, userdata, node):
              #     """
              #     Returns:
              #     (int): Bitmask specifying options for the drag, whether it is
              #         allowed, etc.
              #     """
          
              #     return c4d.TREEVIEW_DRAGSTART_ALLOW | c4d.TREEVIEW_DRAGSTART_SELECT
          
          
              def GetId(self, root, userdata, node):
                  """
                  Return a unique ID for the element in the TreeView.
                  """
          
                  return node.GetUniqueID()
          
          
              def SetName(self, root, userdata, node, name):
                  """
                  Called when the user renames the element. `DoubleClick()` must return
                  False for this to work.
                  """
          
                  doc = c4d.documents.GetActiveDocument()
                  doc.StartUndo()
                  doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, node)
                  node.SetName(name)
                  doc.EndUndo()
                  c4d.EventAdd()
          
          
              def DrawCell(self, root, userdata, node, col, drawinfo, bgColor):
                  """
                  Called for a cell with layout type `c4d.LV_USER` or `c4d.LV_USERTREE`
                  to draw the contents of a cell.
                  """
          
                  if col == ID_ICON:
                      icon = node.GetIcon()
                      drawinfo["frame"].DrawBitmap(
                          icon["bmp"], drawinfo["xpos"], drawinfo["ypos"],
                          16, 16, icon["x"], icon["y"], icon["w"], icon["h"], c4d.BMP_ALLOWALPHA)
          
                  if col == ID_TREEVIEW_TYPE:
                      name = node.GetTypeName()
                      geUserArea = drawinfo["frame"]
                      w = geUserArea.DrawGetTextWidth(name)
                      h = geUserArea.DrawGetFontHeight()
                      xpos = drawinfo["xpos"]
                      ypos = drawinfo["ypos"] + drawinfo["height"]
                      drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
          
                  if col == ID_TREEVIEW_CHANNEL:
                      if isinstance(node, c4d.Material):
                          return
          
                      if node == self.GetShader(c4d.MATERIAL_COLOR_SHADER):
                          name = "Color"
          
                      elif node == self.GetShader(c4d.MATERIAL_LUMINANCE_SHADER):
                          name = "Luminance"
          
                      elif node == self.GetShader(c4d.MATERIAL_DIFFUSION_SHADER):
                          name = "Diffusion"
          
                      else:
                          name = ""
          
                      if name != "":
                          geUserArea = drawinfo["frame"]
                          w = geUserArea.DrawGetTextWidth(name)
                          h = geUserArea.DrawGetFontHeight()
                          xpos = drawinfo["xpos"]
                          ypos = drawinfo["ypos"] + drawinfo["height"]
                          drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
          
          
              def SetCheck(self, root, userdata, node, column, checked, msg):
                  """
                  Called when the user clicks on a checkbox for an object in a
                  `c4d.LV_CHECKBOX` column.
                  """
          
                  if checked:
                      node.SetBit(BIT_CHECKED)
                  else:
                      node.DelBit(BIT_CHECKED)
          
                  def checked_collect(op):
                      if op is None:
                          return
                      elif isinstance(op, c4d.documents.BaseDocument):
                          for obj in op.GetObjects():
                              for x in checked_collect(obj):
                                  yield x
                      else:
                          if op.GetBit(BIT_CHECKED):
                              yield op
                          for child in op.GetChildren():
                              for x in checked_collect(child):
                                  yield x
          
                  status = ', '.join(obj.GetName() for obj in checked_collect(node.GetDocument()))
                  self._dlg().SetString(1001, "Checked: " + status)
                  self._dlg()._treegui.Refresh()
          
          
              def IsChecked(self, root, userdata, node, column):
                  """
                  Returns:
                  (int): Status of the checkbox in the specified *column* for *obj*.
                  """
          
                  val = node.GetBit(BIT_CHECKED)
          
                  if val == True:
                      return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
                  else:
                      return c4d.LV_CHECKBOX_ENABLED
          
          
              def HeaderClick(self, root, userdata, column, channel, is_double_click, mouseX, mouseY, ua):
                  """
                  Called when the TreeView header was clicked.
          
                  Returns:
                  (bool): True if the event was handled, False if not.
                  """
          
                  c4d.gui.MessageDialog("You clicked on the '%i' header." % (column))
                  return True
          
          
              def AcceptDragObject(self, root, userdata, node, dragtype, dragobject):
                  """
                  Called when a drag & drop operation hovers over the TreeView to check
                  if the drag can be accepted.
          
                  Returns:
                  (int, bool)
                  """
          
                  # if dragtype != c4d.DRAGTYPE_ATOMARRAY:
                  #     return 0
          
                  # return c4d.INSERT_BEFORE | c4d.INSERT_AFTER | c4d.INSERT_UNDER, True
          
                  return 0
          
          
              def GenerateDragArray(self, root, userdata, node):
                  """
                  Return:
                  (list of c4d.BaseList2D): Generate a list of objects that can be
                      dragged from the TreeView for the `c4d.DRAGTYPE_ATOMARRAY` type.
                  """
          
                  if node.GetBit(c4d.BIT_ACTIVE):
                      return [node, ]
          
          
              def InsertObject(self, root, userdata, node, dragtype, dragobject, insertmode, bCopy):
                  """
                  Called when a drag is dropped on the TreeView.
                  """
          
                  if dragtype != c4d.DRAGTYPE_ATOMARRAY:
                      return # Shouldnt happen, we catched that in AcceptDragObject
          
                  for op in dragobject:
                      op.Remove()
          
                      if insertmode == c4d.INSERT_BEFORE:
                          op.InsertBefore(node)
                      elif insertmode == c4d.INSERT_AFTER:
                          op.InsertAfter(node)
                      elif insertmode == c4d.INSERT_UNDER:
                          op.InsertUnder(node)
                  return
          
          
              def GetColumnWidth(self, root, userdata, node, col, area):
                  """Measures the width of cells.
          
                  Although this function is called #GetColumnWidth and has a #col, it is
                  not only executed by column but by cell. So, when there is a column
                  with items requiring the width 5, 10, and 15, then there is no need
                  for evaluating all items. Each item can return its ideal width and
                  Cinema 4D will then pick the largest value.
          
                  Args:
                      root (any): The root node of the tree view.
                      userdata (any): The user data of the tree view.
                      obj (any): The item for the current cell.
                      col (int): The index of the column #obj is contained in.
                      area (GeUserArea): An already initialized GeUserArea to measure
                       the width of strings.
          
                  Returns:
                      TYPE: Description
                  """
                  # The default width of a column is 80 units.
                  width = 80
                  # Replace the width with the text width. area is a prepopulated
                  # user area which has already setup all the font stuff, we can
                  # measure right away.
          
                  if col == ID_ICON:
                      return 5
          
                  if col == ID_TREEVIEW:
                      return area.DrawGetTextWidth(node.GetName()) + 5
          
                  if col in (ID_TREEVIEW_TYPE, ID_TREEVIEW_CHANNEL):
                      return area.DrawGetTextWidth(node.GetTypeName()) + 5
          
                  return width
          
          
              def IsMoveColAllowed(self, root, userdata, lColID):
                  # The user is allowed to move all columns.
                  # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
                  return False
          
          
              def GetColors(self, root, userdata, node, pNormal, pSelected):
                  """
                  Retrieve colors for the TreeView elements.
          
                  Returns:
                  (int or c4d.Vector, int or c4d.Vector): The colors for the normal
                      and selected state of the element.
                  """
          
                  usecolor = node[c4d.ID_BASEOBJECT_USECOLOR]
          
                  if usecolor == c4d.ID_BASEOBJECT_USECOLOR_ALWAYS:
                      pNormal = node[c4d.ID_BASEOBJECT_COLOR]
          
                  return pNormal, pSelected
          
          
              # Context menu entry for "Hello World!"
              ID_HELLOWORLD = 355435345
          
          
              def CreateContextMenu(self, root, userdata, node, lColumn, bc):
                  """
                  User clicked with the right mouse button on an entry so
                  we can here enhance the menu
                  """
          
                  bc.SetString(self.ID_HELLOWORLD, "Set Original Name")
          
          
              def ContextMenuCall(self, root, userdata, node, lColumn, lCommand):
                  """
                  The user executes an entry of the context menu.
          
                  Returns:
                  (bool): True if the event was handled, False if not.
                  """
          
                  if lCommand == self.ID_HELLOWORLD:
                      doc = c4d.documents.GetActiveDocument()
                      doc.StartUndo()
                      doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, node)
                      node.SetName(node.GetTypeName())
                      doc.EndUndo()
                      c4d.EventAdd()
                      return True
          
                  return False
          
          
              def SelectionChanged(self, root, userdata):
                  """
                  Called when the selected elements in the TreeView changed.
                  """
          
                  print("The selection changed")
          
          
              def Scrolled(self, root, userdata, h, v, x, y):
                  """
                  Called when the TreeView is scrolled.
                  """
          
                  self._dlg().SetString(1002, ("H: %i V: %i X: %i Y: %i" % (h, v, x, y)))
          
          
          class TestDialog(c4d.gui.GeDialog):
          
              _treegui = None
          
              def CreateLayout(self):
                  # Create the TreeView GUI.
                  customgui = c4d.BaseContainer()
                  customgui.SetBool(c4d.TREEVIEW_BORDER, True)
                  customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True)
                  customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False)
                  customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True)
                  customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True)
                  customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True)
                  customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True)
          
                  self._treegui = self.AddCustomGui(
                  1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,
                  300, 300, customgui)
          
                  if not self._treegui:
                      print("[ERROR]: Could not create TreeView")
                      return False
          
                  self.AddMultiLineEditText(1001, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_BOTTOM, 0, 40)
                  self.AddEditText(1002, c4d.BFH_SCALEFIT, 0, 25)
                  return True
          
          
              def CoreMessage(self, id, msg):
                  if id == c4d.EVMSG_CHANGE:
                      # Update the treeview on each change in the document.
                      self._treegui.Refresh()
                  return True
          
          
              def InitValues(self):
                  # Initialize the column layout for the TreeView.
                  layout = c4d.BaseContainer()
                  layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
                  layout.SetLong(ID_ICON, c4d.LV_USER)
                  layout.SetLong(ID_TREEVIEW, c4d.LV_TREE)
                  layout.SetLong(ID_TREEVIEW_TYPE, c4d.LV_USER)
                  layout.SetLong(ID_TREEVIEW_CHANNEL, c4d.LV_USER)
                  self._treegui.SetLayout(5, layout)
          
                  # Set the header titles.
                  self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
                  self._treegui.SetHeaderText(ID_ICON, "Icon")
                  self._treegui.SetHeaderText(ID_TREEVIEW, "Name")
                  self._treegui.SetHeaderText(ID_TREEVIEW_TYPE, "Type")
                  self._treegui.SetHeaderText(ID_TREEVIEW_CHANNEL, "Channel")
                  self._treegui.Refresh()
          
                  # Don't need to store the hierarchy, due to SetRoot()
          
                  root = doc = c4d.documents.GetActiveDocument()
                  data_model = Hierarchy(self)
                  self._treegui.SetRoot(root, data_model, None)
          
                  self.SetString(1002, "Scroll values")
          
                  return True
          
          
          class MenuCommand(c4d.plugins.CommandData):
          
              dialog = None
          
              def Execute(self, doc):
                  """
                  Just create the dialog when the user clicked on the entry
                  in the plugins menu to open it
                  """
          
                  if self.dialog is None:
                      self.dialog = TestDialog()
          
                  return self.dialog.Open(
                      c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
          
              def RestoreLayout(self, sec_ref):
                  """
                  Same for this method. Just allocate it when the dialog is needed.
                  """
          
                  if self.dialog is None:
                      self.dialog = TestDialog()
          
                  return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
          
          
          def main():
          
              # c4d.plugins.RegisterCommandPlugin(
              #     PLUGIN_ID, PLUGIN_NAME, 0, None, PLUGIN_HELP, MenuCommand())
          
              global dialog
              dialog = TestDialog()
              dialog.Open(c4d.DLG_TYPE_ASYNC, defaulth=600, defaultw=600)
          
          
          if __name__ == "__main__":
            main()
          
          

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

          1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand
            last edited by

            Thanks, will have look - hopefully before the weekend starts.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            ferdinandF 1 Reply Last reply Reply Quote 0
            • ferdinandF
              ferdinand @ferdinand
              last edited by ferdinand

              Hey,

              So, I had a look, and it is not crashing for me. I created a material with a Fusion shader in it where a shader was attached to each of the three Fusion shader slots. Then I opened your dialog and played a bit around with it, with no luck, nothing was/is crashing for me.

              1. Is Cinema 4D crashing or freezing for you? You claim the former, but the latter seems more likely.
              2. If it is indeed crashing, can you provide your crash report? In Cinema 4D hit CTRL/CMD + E, click on the Preferences folder button at the bottom, open the selected folder, and navigate to _bugreports. There you find the call stack txt file for the last crash and the crash zip. See end of posting for an example.
              3. Can you provide the scene file with which it is crashing?

              Given that you only added my shader traversal method, I would say it is the culprit. Given that there is a nasty while loop in it, I would say we/you are missing an exit condition which then leads to a freeze. But I cannot either come up with a shader setup that triggers the problem, nor can I see analytically where this code will run forever in circles.

              Cheers,
              Ferdinand

              edit: Eh, I overread the deleting material part first, but even when I delete the a/the currently displayed material which is shown in the dialog, nothing is crashing for me.

              I tried S26.107 given the version range you put as tags on this topic and 2023.1.3. Which exact version are you using, and can you help me a bit with reproduction steps?

              15559077-da8b-4eeb-ab27-c8531cc820b9-image.png

              Here is how you can find the last crash report:
              crash_report.gif

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • H
                HerrMay
                last edited by

                Hi @ferdinand,

                unfortunately it is crashing not freezing. I set up a scene file with one material and a fusion shader which has three shaders in it and put it in the color texture slot.

                I played around in the Treeview Dialog and tried to rename a shader. While that worked, renaming the material did not and resulted in the crash for me.

                Please find attached the scene file and the bugreports.

                I hope this will bring light into it.

                Cheers and have a nice weekend. 🙂
                Sebastian

                Crashing_file.zip

                1 Reply Last reply Reply Quote 0
                • ferdinandF
                  ferdinand
                  last edited by ferdinand

                  I just tried the renaming thing. This did not crash either for me. Could you please provide a scene file and the exact version you are using? Thank you for the bug report!

                  Cheers,
                  Ferdinand

                  edit: oh, okay the scene is in the zip, thanks a lot 😉
                  edit 2: Ah, okay, I get it. It is crashing when you rename the material/shader from your dialog. I was just renaming the material in the Material Manager. Now it is crashing for me too, yay 🙂

                  MAXON SDK Specialist
                  developers.maxon.net

                  ferdinandF 1 Reply Last reply Reply Quote 0
                  • ferdinandF
                    ferdinand @ferdinand
                    last edited by ferdinand

                    Hello @HerrMay,

                    So, I had a look again. It crashes as soon as your return False in TreeViewFunctions.DoubleClick, indicating that you want to carry out the default action, renaming the node. As you can see in your crash report, it crashes then inside one of the core Cinema 4D C++ modules, c4dplugin. I am not yet sure if there is some sort of callback going wrong or if there is just flat-out a bug in the TreeViewFunctions::DoubleClick + node renaming default code of ours. I will have a look on Monday and then file a bug report or come back here when there is some sort of callback thing going wrong with your treeview interface.

                    In the meantime, you can replace your TreeViewFunctions.DoubleClick code with this. Not pretty, but it will get the job done. You could ofc do this in a fancier fashion yourself, imitating the node renaming behavior more closely.

                        def DoubleClick(self, root, userdata, node, col, mouseinfo):
                            """
                            Called when the user double-clicks on an entry in the TreeView.
                    
                            Returns:
                            (bool): True if the double-click was handled, False if the
                                default action should kick in. The default action will invoke
                                the rename procedure for the object, causing `SetName()` to be
                                called.
                            """
                            if not isinstance(node, c4d.BaseList2D):
                                return True
                    
                            node.SetName(c4d.gui.RenameDialog(node.GetName()))
                            return True
                    

                    Here is the result:
                    rename.gif

                    Cheers,
                    Ferdinand

                    Full code:

                    shader_view.py

                    MAXON SDK Specialist
                    developers.maxon.net

                    ferdinandF 1 Reply Last reply Reply Quote 0
                    • ferdinandF
                      ferdinand @ferdinand
                      last edited by

                      Hello @HerrMay,

                      So, today I found out that this is an extension of an already known bug. There were attempts made to fix this before, but that was apparently harder than it looks like. I gave the issue a little push and added this new context. We will fix this in a future version of Cinema 4D, but for now you will have to apply the workaround shown above.

                      Cheers,
                      Ferdinand

                      MAXON SDK Specialist
                      developers.maxon.net

                      H 1 Reply Last reply Reply Quote 0
                      • H
                        HerrMay @ferdinand
                        last edited by

                        Hello @ferdinand,

                        ah okay I see. I was afraid it could be a bug since now it means that it'll take some time before it gets fixed. Too bad but not a deal breaker since you provided a viable solution for the time being. Thanks for looking into it and your workaround I will stick to for now.

                        Maybe a little bit off topic but since we're already talking Treeviews. There seems to be a bug too when it comes to multi-selecting objects in the Treeview. At least when using c4ds native BaseList2D objects. This has already been discussed here: TreeView Shift Click & Keyboard Arrows Input

                        Maybe you want to add this to the list of fixes as well.

                        Cheers,
                        Sebastian

                        ferdinandF 1 Reply Last reply Reply Quote 0
                        • ferdinandF
                          ferdinand @HerrMay
                          last edited by

                          Hello @HerrMay,

                          Thank you for your reply, and please excuse that I have overlooked it.

                          Maybe a little bit off topic but since we're already talking Treeviews. There seems to be a bug too when it comes to multi-selecting objects in the Treeview. At least when using c4ds native BaseList2D objects.

                          Without wanting to be rude, that statement is too vague to make a bug report out of it. The thread is quite old and therefore not the most reliable source of information, geared towards C++, and from what I see, not even conclusive in the assessment if there is a bug or not. I see there a user claiming that there is a bug, and another user, probably a Maxon employee, being skeptical about it.

                          We are happy to file bugs, but we need a reproducible case. And just as a heads up, us filing a bug does not necessarily mean that we will fix soon or at all. I understand that this can be disheartening, but we must prioritize where our bug fixing efforts are most needed, which can lead to minor bugs being pushed for a long time.

                          I have closed this thread due to its age, please feel free to open a new thread when you want to discuss that other bug. The thread is still be tracked due to its to_fix tag.

                          Cheers,
                          Ferdinand

                          MAXON SDK Specialist
                          developers.maxon.net

                          1 Reply Last reply Reply Quote 0
                          • maxonM maxon moved this topic from Cinema 4D SDK on
                          • First post
                            Last post