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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Copy LayerShader to a new Material

    Cinema 4D SDK
    python
    2
    11
    1.8k
    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.
    • indexofrefractionI
      indexofrefraction
      last edited by indexofrefraction

      Hi,

      I'm trying to copy a LayerShader to a new Material and am confused on how to do it.
      To catch all possible cases this must be a recursive processing (LayerShaders inside LayerShaders).

      Would I just set the slot and use InsertShader with the already set up main LayerShader,
      then iterate through all LayerShaderLayers and InsertShader these as well?
      Or would I need to first clone all shaders with shader.GetClone() ?
      If yes, would it be sufficient to clone the main LayerShader ?
      And how about the special LayerShaderLayers like HSL Adjustment etc.

      Finally the inserted shaders need to be nested I guess? So if I first do material.InsertShader(xlayer),
      is material.InsertShader(subshader, xlayer) the same as xlayer.InsertShader(subshader) ?

      best, index

      ferdinandF 1 Reply Last reply Reply Quote 0
      • 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