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

    Copy LayerShader to a new Material

    Cinema 4D SDK
    python
    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.
    • ferdinandF
      ferdinand @indexofrefraction
      last edited by ferdinand

      Hello @indexofrefraction,

      Thank you for reaching out to us. Copying a shader is pretty straight forward in the classic API of Cinema 4D, as shaders carry all their dependencies as children (see BaseList2D.GetFirstShader() for details on the data model of materials and shaders). As indicated by yourself, you can do this with c4d.C4DAtom.GetClone(). Find an example at the end of my posting. The 'recursive' quality you are looking for is already guaranteed by the hierarchical data model of shaders.

      is material.InsertShader(subshader, xlayer) the same as xlayer.InsertShader(subshader) ?

      No, the former defines an insertion point, after the shader xlayer, the latter does not and defaults to inserting the new shader at the top of the shader c4d.GeListHead of material. In practice, this should however make no difference, as shaders should not be identified by their index/position.

      Cheers,
      Ferdinand

      The result:
      matcopy.gif

      The code:

      """Demonstrates how to copy a shader from one material to another.
      
      Run this example as a Script Manger script with a material selected.
      """
      
      from typing import Optional
      import c4d
      
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
      def main() -> None:
          """
          """
          # Get the active material and the shader assigned to its color channel.
          selectedMaterial = doc.GetActiveMaterial()
          shader = selectedMaterial[c4d.MATERIAL_COLOR_SHADER]
          if not isinstance(shader, c4d.BaseShader):
              raise RuntimeError(f"The material {material} has no shader assigned to its color channel.")
      
          print (f"{shader =}")
      
          # Copy that shader, including its children. It is important to include the children, as shaders 
          # will carry their dependences, other shaders their output relies on, as children.
          copiedShader = shader.GetClone(c4d.COPYFLAGS_0)
      
          # Create a new material and assign the copy there to the color channel. It is important to
          # insert the shader into the material for this to work.
          newMaterial = c4d.BaseList2D(c4d.Mmaterial)
          newMaterial.SetName(f"{selectedMaterial.GetName()} (Copy)")
          newMaterial.InsertShader(copiedShader)
          newMaterial[c4d.MATERIAL_COLOR_SHADER] = copiedShader
      
          # Insert the material into the active document and make it the active material.
          doc.InsertMaterial(newMaterial)
          doc.SetActiveMaterial(newMaterial)
      
          # Inform Cinema 4D about the changes the we made.
          c4d.EventAdd()
      
      if __name__ == '__main__':
          main()
      

      MAXON SDK Specialist
      developers.maxon.net

      1 Reply Last reply Reply Quote 0
      • indexofrefractionI
        indexofrefraction
        last edited by indexofrefraction

        hi ferdinand,
        thanks for all of this ! but I have some follow up questions... 🙂

        a)
        I guess the shaders inserted with InsertShader should be in the correct hierarchy ?
        material.InsertShader(subshader, xlayer) --> inserts subshader after or as a child of xlayer? (should it be a child?)
        xlayer.InsertShader(subshader) --> did you notice the xlayer here? material is not even used.
        (i thought the latter might be used to insert subshader as child of xlayer)
        In general: Can this hierarchy generated outside of the material and inserted as a whole at the end, or does this not work?

        b)
        if you do this from your example :
        newMaterial.InsertShader(copiedShader)
        newMaterial[c4d.MATERIAL_COLOR_SHADER] = copiedShader

        and copiedShader is a LayerShader with other shaders inside, maybe even other LayerShaders having more shaders
        wouldnt these subshaders not needed to get inserted as well ?
        or is this hierarchy already present with the given copiedShader ?

        in this example of creating a LayerShader the subshaders are inserted separately as well :
        https://developers.maxon.net/forum/topic/13469/python-layershader-addlayer-defining-shader

        in my tests i did it already without inserting the sub shaders but I end up with rogue shaders (and crashes)

        best, index

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

          Hi @indexofrefraction,

          material.InsertShader(subshader, xlayer) --> inserts subshader after or as a child of xlayer? (should it be a child?)
          xlayer.InsertShader(subshader) --> did you notice the xlayer here? material is not even used.

          No, I did not notice that. I assume xlayer is meant to be layer shader in this example. As I hinted at in my previous posting, shaders are organized hierarchically below BaseList2D instances. These BaseList2D can be anything, objects, tags, other shaders, and of course also materials. When you have a shader which has dependencies, it will carry these dependencies as children (of its shaders GeListHead which can be accessed with the mentioned BaseList2D methods).

          So, for example, a MoGraph shader effector that has a gradient shader in its shader slot, will look like this (slightly idealized, since the shaders are attached to a GeListHead in the branches of the node):

          BaseList2D 'Shader Effector'
          + - BaseList2D 'Gradient Shader'
          

          For materials it is the same, when you have a material which has a gradient shader in the color channel, and a noise shader in the bump channel, it will look like this (again, idealized):

          BaseList2D 'My Material'
          + - BaseList2D 'Gradient Shader'
          + - BaseList2D 'Noise Shader'
          

          So, every BaseList2D carries its direct shader dependencies. This also means in consequence that shaders which rely on shaders will carry them in such fashion. If the color channel of the material would contain a layer shader which contains a gradient shader, the node tree would look like this (again, idealized):

          BaseList2D 'My Material'
          + - BaseList2D 'Layer Shader'
              + - BaseList2D 'Gradient Shader'
          + - BaseList2D 'Noise Shader'
          

          So, your expression xlayer.InsertShader(subshader) is likely meant to depict someone inserting a shader dependency under a layer shader. You can also check yourself which shaders are attached to a node with these lines of code:

          node = myHostingNode.GetFirstShader()
              while node:
                  print (node)
                  node = node.GetNext()
          

          wouldnt these subshaders not needed to get inserted as well ?
          or is this hierarchy already present with the given copiedShader ?

          No, they would not, because of the data structures lined out above. My screen grab also showed such (double) nested layer shader example, and all shaders were copied correctly.

          in my tests i did it already without inserting the sub shaders but ended up with rogue shaders (and crashes)

          You either must have copied the shader without children (its own shader dependencies) or something else went wrong there. I just tried again my code from above with such nested layer shader example, and even after deleting the source material, assigning the copied material to some geometry, and rendering the scene, nothing is crashing.

          If you are still running into crashes, I would recommend providing executable code, so that we can try to reproduce your problem.

          Cheers,
          Ferdinand

          MAXON SDK Specialist
          developers.maxon.net

          indexofrefractionI 1 Reply Last reply Reply Quote 0
          • indexofrefractionI
            indexofrefraction
            last edited by indexofrefraction

            concerning the post above ...
            the following change inserts the gradient as child of the LayerShader
            now printNodeTree gives the same output as for a manually set up LayerShader

            mat.InsertShader(xlayer)
            mat[c4d.MATERIAL_COLOR_SHADER] = xlayer
            ...
            gradient.InsertUnder(xlayer)
            

            ... it also seems this has solved my rogue shader / crash problem.
            does InsertShader do something different / more than the normal GeListNode Insert methods ?

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

              Hello @indexofrefraction,

              it is a bit tricky to answer your questions here. First of all, please follow our forum rules regarding creating new topics for new questions. The title of the posting was "Copy LayerShader to a new Material". Copying is different from creating such shader setup. One of your follow up questions was therefore out of scope for this topic. I have forked that question into How to Create and Populate a Layer Shader?.

              Secondly, the topic you were referring to, with the code from @m_adam, contained incorrect code. See How to Create and Populate a Layer Shader? for more information on the subject, but in short, that code parented the shader contained in a layer shader to the wrong node.

              does InsertShader do something different / more than the normal GeListNode Insert methods ?

              Yes, it does. These are different branches. You seem to be unaware of the concept of branches. Shaders must be placed in the "Shaders" branch of a node, not in the normal hierarchical relations of the node (the layer shader is there an annoying exception, see the linked thread above).

              Below you find a little script which lines out roughly the concept of branches, or in other words: How scene graphs are organized in the classic API of Cinema 4D.

              Cheers,
              Ferdinand

              """Briefly explains parts of the scene graph model of the Cinema 4D classic API.
              
              Run this script with in the Script Manger, to have a more meaningful output, the scene should 
              contain at least one object, with at least one tag, and at least one material with at least one
              shader.
              """
              
              from typing import Optional
              import c4d
              
              doc: c4d.documents.BaseDocument  # The active document
              op: Optional[c4d.BaseObject]  # The active object, None if unselected
              
              TAB: str = "  " # A two spaces tab
              
              def PrintRelatedNodes(node: c4d.GeListNode, indent: int=0):
                  """Yields all descendants in all node relations of #node.
              
                  Even in the classic API, scene elements are organized in a graph that contains more relations
                  than just hierarchical relations, just as in any other 3D DCC app. There multiple ways one
                  could look at this, e.g., as a poly-hierarchical taxonomy/tree, or as as an ontology/graph, but
                  the reason is simple, when we have for example the object #parent, with the child object #child,
                  and the tag #tag attached to #parent, we must be able to distinguish between the relation 
                  #is_child_of and #is_tag_of.
              
                  E.g., we cannot express these relations as a hierarchy:
              
                      parent
                         + - tag
                         + - child
              
                  But instead must express it as a graph with dedicated relation types:
              
                            parent
                            /    \
                    #is_child   #is_tag
                          /        \
                     child          tag
              
                  Cinema 4D takes in the classic API more the view of a tree with a twist, which is realized with
                  the type GeListHead. Nodes can have "direct" hierarchical relations, and indirect ones over
                  branches. The example looks like this in the classic API of Cinema 4D:
              
                      parent -------tags_branch-------> GeListHead Branch Tags
                          + - child                        + - tag
              
                  The relations of a node are tied together with GeListNode.GetBranchInfo() which returns the 
                  branches of a node, i.e., all relations that are not direct hierarchial ones, where each
                  branch is represented by a GeListHead which then can contain hierarchial relations of arbitrary
                  complexity and also branches for each node in that hierarchy.
              
                  Almost everything is tied together in this manner in a classic API scene, documents, objects, 
                  tags, tracks, curves (but not keys), materials, shaders, and other things that a user is usually
                  not aware of. This realizes in the classic API what is commonly referred to as a scene graph, a 
                  traversable graph that contains all elements in a scene.
              
                  Note:
                      This function was written for educational purposes, it could be optimized and also does not
                      depict a classic API scene graph in its entirety, as it ignores the concept of caches.
                  """
                  def RepresentNode(node: c4d.GeListNode) -> None:
                      """Represents a node over its string representation and branches.
                      """
                      # Print the node at the current indentation.
                      print(f"{TAB * indent}{node}")
              
                      # This node has no branches.
                      if node.GetBranchInfo() == None:
                          return
              
                      # Iterate over the branches of the node.
                      for item in node.GetBranchInfo():
                          # The GeListHead of this branch, it will contain all nodes as direct children which
                          # are in a direct relation of type #name with #node.
                          branchHead = item["head"]
                          # The name of the branch, i.e., the type od relation, as for example "objects", "tags",
                          # or "shaders".
                          name = item["name"]
                          # The type ID of the branch, for a tracks branch this will for example be c4d.CTrack. It
                          # describes the id of the base type of the nodes to find below the list head.
                          tid = item["id"]
              
                          # This branch is empty, i.e., the branch has been created, but no relations have been
                          # yet established.
                          if branchHead.GetDown() == None:
                              continue
                          
                          # Print the name of the branch and unpack 
                          print (f"{TAB * (indent + 1)}Branching into '{name}:'")
                          PrintRelatedNodes(branchHead, indent + 2)
              
                  if not isinstance(node, c4d.GeListNode):
                      return None
              
                  if indent == 0:
                      print (f"{'-' * 100}\n")
              
                  # Walk all direct hierarchial relations of #node.
                  while node:
                      RepresentNode(node)
                      if node.GetDown():
                          print (f"{TAB * (indent + 1)}Direct Children:'")
                          PrintRelatedNodes(node.GetDown(), indent + 2)
              
                      node = node.GetNext()
              
                  if indent == 0:
                      print (f"{'-' * 100}\n")
                  
              
              def main() -> None:
                  """
                  """
                  # We can use #PrintRelatedNodes to walk the relations of many node types ...
              
                  # A document ...
                  PrintRelatedNodes(doc)
              
                  # An object ...
                  obj = doc.GetFirstObject()
                  PrintRelatedNodes(obj)
              
                  # A tag ...
                  if obj:
                      tag = obj.GetFirstTag()
                      PrintRelatedNodes(tag)
              
                  # A material ...
                  material = doc.GetFirstMaterial()
                  PrintRelatedNodes(material)
              
                  # A shader ...
                  if material:
                      shader = material[c4d.MATERIAL_COLOR_SHADER]
                      PrintRelatedNodes(shader)
              
                  # and everything else that is a GeListNode (and therefore can have children and branches), as
                  # for example tracks, curves, layers, etc.
                  
              
              if __name__ == '__main__':
                  main()
              

              And here you can find an example output for this scene:
              Screenshot 2022-06-03 at 17.12.46.png

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • indexofrefractionI
                indexofrefraction
                last edited by indexofrefraction

                @ferdinand
                Thanks a lot for taking the time!
                The copy LayerShader problem solved plus good infos on how things work under the hood 🙂

                1 Reply Last reply Reply Quote 0
                • indexofrefractionI
                  indexofrefraction
                  last edited by indexofrefraction

                  I have a follow up question.... and post it here because of the sample code above...
                  if that is not good, could you move it to a new thread?

                  Isn't there a code example somewhere how to properly walk the shader tree?
                  i struggle with extending a "simple" walker function like the one below

                  also if there are different node types wouldnt that need/allow an argument on which ones to follow ?
                  e.g. would it be possible to walk from doc.GetFirstObject() and just get the tags?
                  and finally is the walker below at least ok for objects?
                  (its not my code, just copied from some old library)

                  def walker(op):
                    if not op: return None
                    if op.GetDown():
                      return op.GetDown()
                    while op.GetUp() and not op.GetNext():
                      op = op.GetUp()
                    return op.GetNext()
                  
                  1 Reply Last reply Reply Quote 0
                  • indexofrefractionI
                    indexofrefraction
                    last edited by indexofrefraction

                    trying myself...
                    a walker as above is probably not possible
                    but this might be something handy for others :

                    def nodesToList(op, li=None):
                      ''' returns a list of a complete shader tree '''
                      if not li: li = []
                      if isinstance(op, c4d.GeListNode):
                        while op:
                          if not isinstance(op, c4d.GeListHead):
                            li.append(op)
                          if op.GetBranchInfo():
                            for branch in op.GetBranchInfo():
                              li = nodesToList(branch["head"].GetDown(), type, li)
                          li = nodesToList(op.GetDown(), type, li)
                          op = op.GetNext()
                      return li
                    
                    # example
                    for op in nodesToList(Mat):
                          print(op)
                    
                    ferdinandF 1 Reply Last reply Reply Quote 0
                    • ferdinandF
                      ferdinand @indexofrefraction
                      last edited by

                      Hey @indexofrefraction,

                      yeah this looks good, but it also depends a bit on what you want to do. I would recommend two things:

                      1. Only branch into shaders (I think the branch-head is labeled 'shader') as there are a lot of branches and branching into everything will increase your runtime significantly.
                      2. Consider using a generator/yield. Imagine you have a node graph which contains including its direct hierarchy and branches 10,000 nodes. But the node you are looking for, is actually already the second node in your list. And now you have run your nodesToList very often for some reason. Your variant would have to traverse all 10,000 nodes every time, a generator will only traverse what is required, only 2 nodes in our example.

                      For shader graphs both points are not so important, as there are usually less than 1000 nodes inside a material and there the impact is quite low. But if you want to traverse a whole document, it will make a difference.

                      Cheers,
                      Ferdinand

                      PS: I now just realized that this thread I was pointing too from the other thread was actually also by you. Mea culpa ^^

                      MAXON SDK Specialist
                      developers.maxon.net

                      1 Reply Last reply Reply Quote 0
                      • indexofrefractionI
                        indexofrefraction
                        last edited by

                        tx ferdinand,
                        and yes intertwined threads .-)

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post