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

    How to Add Child Shaders to a Fusion Shader?

    Cinema 4D SDK
    python r20
    2
    6
    1.5k
    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 noticed some odd behavior regarding shader creation with python scripts.

      The situation is as follows. I use the Treeview example made by Niklas Rosenstein to display a material/shader tree. And I have a simple test script for creating a material and inserting some shaders into it.

      When I create the material manually, the Treeview is showing the correct hierarchy of the material with all its shaders and possible sub-shaders. But when I create it via script the Treeview is showing the fusion shader without its sub-shaders.

      It seems almost as cinema does'nt get informed correctly about the new material and its shaders that is. Am I missing something here?

      Cheers,
      Sebastian

      edit ( @ferdinand ) : I change the title to better reflect the subjects in the thread.

      TL;DR
      The script to create the aforementioned material:

      import c4d
      
      def main():
      
          doc = c4d.documents.GetActiveDocument()
          doc.StartUndo()
      
          ao = c4d.BaseShader(c4d.Xambientocclusion)
          color = c4d.BaseShader(c4d.Xcolor)
          fresnel = c4d.BaseShader(c4d.Xfresnel)
      
          fusion = c4d.BaseShader(c4d.Xfusion)
          fusion[c4d.SLA_FUSION_USE_MASK] = True
          fusion[c4d.SLA_FUSION_BLEND_CHANNEL] = ao
          fusion[c4d.SLA_FUSION_MASK_CHANNEL] = color
          fusion[c4d.SLA_FUSION_BASE_CHANNEL] = fresnel
          fusion.InsertShader(ao)
          fusion.InsertShader(color)
          fusion.InsertShader(fresnel)
      
          mat = c4d.BaseMaterial(c4d.Mmaterial)
          mat[c4d.MATERIAL_COLOR_SHADER] = fusion
          mat.InsertShader(fusion)
          mat.Message(c4d.MSG_UPDATE)
          mat.Update(preview=True, rttm=True)
      
          doc.InsertMaterial(mat)
          doc.AddUndo(c4d.UNDOTYPE_NEW, mat)
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      The Treeview example code:

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

      Re: Shader created by script does not get recognized by Treeview

      Forgot to provide a screenshot that shows the difference. So here it is. It is taken in R26 but it makes no difference to R20.

      Screenshot 2023-02-02 083513.png


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

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

        Hello @herrmay,

        thank you for reaching out to us. You said,

        When I create the material manually, [...]. But when I create it via script, [...]

        implying that both operations are equal, but they are not. You insert the shader dependencies of your fusion shader into its shader branch, but that is by convention incorrect. Most shaders carry their dependencies as direct children. So, this

        fusion.InsertShader(ao)
        fusion.InsertShader(color)
        fusion.InsertShader(fresnel)
        

        should be replaced with this:

        ao.InsertUnderLast(fusion)
        color.InsertUnderLast(fusion)
        fresnel.InsertUnderLast(fusion)
        

        which will then result in this when comparing your old with this new output
        8430988e-a4a9-41ee-9159-a77d4b8c3dae-image.png

        For 2023.0 I just added the Access and Structure section to the C++ BaseShader manual which discusses the topic in more detail.

        The underyling cause for the treeview not finding these shaders in in the malformed fusion shader, is due to your iter_children method. It traverses only hierachical descendants but not branches. Since shaders are not guranteed all to behave like our native layer or fusion shader, it would be better to fix your shader traversal. Here is how it could look like:

        import c4d
        
        doc: c4d.documents.BaseDocument  # The active document
        
        def TraverseShaders(node: c4d.BaseList2D) -> c4d.BaseShader:
            """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 main() -> None:
            """
            """
            material: c4d.BaseMaterial = doc.GetActiveMaterial()
            print (f"{material = }\n")
            for shader in TraverseShaders(material):
                print (shader)
        
        if __name__ == '__main__':
            main()
        

        For the malformed fusion shader it would spit out this here:

        7e5438b0-47ee-47af-8fbe-c0f9b946db62-image.png

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

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

          Hi @ferdinand,

          well that makes sense. Of course - stupid me! 🤦 🤣 Thanks for seeing things I obviously don't.

          I have a follow up question which is kind of related but more towards the Treeview not the material/shader question. Should I rather open a new topic for that question?

          Thank you @ferdinand. You really are golden to this forum and community. ✊ 👌

          Cheers,
          Sebastian

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

            Hey @herrmay,

            I am glad that this solved your problem. For a question about the tree view GUI, I would ask you to open a new topic, as this one is mostly about shader traversal and the makeup of the fusion shader.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

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

              Hi @ferdinand,

              alright, will do. 🙂
              Thanks again for the immediate help.

              Cheers
              Sebastian

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

                Hello @ferdinand,

                when trying out your function to traverse shaders I noticed that for two materials in the scene it yields for the first material all the shaders of itself but also all the shaders of the second material. For second material it works like expected and yields only the shaders of itself.

                After fiddling around a bit with some other code of you from this post I think I ended up with a function that is yielding all the shaders from a material iteratively.

                Find the code below. Maybe this will help others. 🙂

                Cheers,
                Sebastian

                def iter_shaders(node):
                    """Credit belongs to Ferdinand from the Plugincafe. I added only the part with the material and First Shader checking.
                
                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:
                
                        if isinstance(node, c4d.Material) and not node.GetFirstShader():
                           break
                        
                        if isinstance(node, c4d.Material) and node.GetFirstShader():
                            node = node.GetFirstShader()
                        
                        # 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()
                
                1 Reply Last reply Reply Quote 1
                • H HerrMay referenced this topic on
                • First post
                  Last post