Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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

    c4d.MatAssignData can't link.

    Cinema 4D SDK
    python windows
    3
    4
    451
    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.
    • DunhouD
      Dunhou
      last edited by

      Hi :

      Question :

      I try to merge all selected materials assignment to a new material assignment . aka merge materials to one new material .

      But it won't assign the new MatAssignData to new material .

      How can I fix this 🤔

      Here is the mini code :

      # @2023.1.0
      from typing import Optional
      import c4d
      
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
      def merge_material() -> None:
          myMat = doc.GetActiveMaterial()
          ObjLink = myMat[c4d.ID_MATERIALASSIGNMENTS]
      
          new_mat = c4d.BaseMaterial(5703)
          doc.InsertMaterial(new_mat)
          new_mat.SetName(myMat.GetName() + " Combine")
          new_mat_link = c4d.MatAssignData()
      
          myCount = ObjLink.GetObjectCount()
          for i in range(myCount) :  
              tag = ObjLink.ObjectFromIndex(doc,i)
              obj = tag.GetObject()
              #print (obj)
              new_mat_link.InsertObject(obj,0)
          
          print(new_mat_link.GetObjectCount())
          
          # this will failed
          # AttributeError: parameter set failed
          new_mat[c4d.ID_MATERIALASSIGNMENTS] = new_mat_link
          
          doc.SetActiveMaterial(new_mat, c4d.SELECTION_NEW)
          
          c4d.EventAdd()
          
      if __name__ == '__main__':
          merge_material()
      

      https://boghma.com
      https://github.com/DunHouGo

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

        Hello @dunhou,

        Thank you for reaching out to us. There are mistakes in your code, but the core issue is caused by the Python API, you can however easily sidestep all the issue.

        What does AttributeError: parameter set failed mean?

        This means that either the Python API does not support writing a parameter or the parameter is read-only/immutable in general. This often does only apply to the parameter object itself, not the data type. So, as a Python analogy, one could easily imagine it not being allowed to overwrite MyType.Data: list[int], i.e., myInstance.Data = [1, 2, 3] being illegal because the setter for MyType.Data does not exist. But while the parameter (in Python the property) is immutable, the data type it is using could be mutable. E.g., myInstance.Data.append(1) is fine because list is a mutable type. The same can be applied to the Cinema 4D Python API. Instead of doing this,

        new_mat_link = c4d.MatAssignData()
        # Mutate new_mat_link ...
        new_mat_link.InsertObject(*data)
        new_mat[c4d.ID_MATERIALASSIGNMENTS] = new_mat_link
        

        you could be doing this:

        new_mat_link = new_mat[c4d.ID_MATERIALASSIGNMENTS]
        # Mutate new_mat_link ...
        new_mat_link.InsertObject(*data)
        

        In the first case we try to mutate new_mat[c4d.ID_MATERIALASSIGNMENTS] by allocating a new MatAssignData, in the second case we don't, i.e., we are sort of doing the same as we did in the example from above, where instead of writing an entirely new list, we modified an existing one.

        What is an object?

        There is also the problem that you tried to insert BaseObject instances into the MatAssignData, probably because the paramater is called pObject. You must insert a tag here. pObject likely stands for pointed object, i.e., similar to op (object pointer). The word object in argument names is meant in our APIs usually as class/type object and not as geometry. Inserting geometry objects cannot work as a BaseObject can have multiple TextureTag instances and the MatAssignData would then not know which one to overwrite/handle.

        Stuff is still now working ...

        When you fix all this, the script is still not working [2]. The reason seems to be that BaseMaterial[c4d.ID_MATERIALASSIGNMENTS] returns a copy of the material assignment data, so the code will run fine, but changes are never reflected in Cinema 4D. There could be a plethora of reasons why it was done like this, ranging from pure oversight to an intentional boundary. This is however all relatively easy fixable by ignoring InsertObject altogether and writing data directly into tags. At least I do not see any substantial difference here. I provided a solution below [1].

        Cheers,
        Ferdinand

        Result:

        • Before: Screenshot 2022-11-14 at 12.49.01.png
        • After: Screenshot 2022-11-14 at 13.08.29.png

        [1]

        """Replaces all materials in a list of materials with a new material in their document, updating
        all TextureTag references to the old material with a reference to the new material.
        """
        
        # @2023.1.0
        import c4d
        import random
        import typing
        
        doc: c4d.documents.BaseDocument  # The active document
        op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected
        
        def ReplaceMaterials(materialList: list[c4d.BaseMaterial]) -> None:
            """Replaces all materials in #materialList in their documents with a new material.
        
            Also creates undo items for each material. Due to how I have fashioned the method, this will
            result in an undo step being added for each material in each document. To change this, you could
            either guarantee #materialList to be always composed out of materials from the same document and
            then simply move the Start/EndUndo() outside of the loop, or you would have to do some 
            preprocessing to group materials by document first and then wrap each group into one undo.
        
            Args:
                materialList (list[c4d.BaseMaterial]): The list of materials to replace, materials can be
                 located in different documents.
        
            Raises:
                RuntimeError: When a material in #materialList is a dangling material.
                MemoryError: On allocation failures.
            """
            # Iterate over all materials in #materialList and get the document of each material.
            for mat in materialList:
                doc: c4d.documents.BaseDocument = mat.GetDocument()
                if not isinstance(doc, c4d.documents.BaseDocument):
                    raise RuntimeError(f"The material {mat} is not attached to a document.")
        
                # Get the material assignment data (the tags referencing a material) and the count of
                # links, step over the material when it has less than one link, i.e., is unused.
                linkList: c4d.MatAssignData = mat[c4d.ID_MATERIALASSIGNMENTS]
                linkCount: int = linkList.GetObjectCount()
                if linkCount < 1:
                    continue
                
                # Allocate a replacement material, give it a meaningful name, a random color, and insert
                # it into the document of #mat, i.e., the document of the material it is meant to replace.
                replacement: c4d.BaseMaterial = c4d.BaseMaterial(c4d.Mmaterial)
                if not isinstance(replacement, c4d.BaseMaterial):
                    raise MemoryError("Could not allocate new material.")
                
                doc.StartUndo()
                replacement.SetName(f"{mat.GetName()} (Combine)")
                replacement[c4d.MATERIAL_COLOR_COLOR] = c4d.Vector(random.uniform(0, 1), 
                                                                   random.uniform(0, 1), 
                                                                   random.uniform(0, 1))
                doc.InsertMaterial(replacement)
                doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, replacement)
                
                # Iterate over all texture tags referencing the old material #mat and replace their link
                # with #replacement, while stepping over dangling material assignment references, i.e.,
                # entries in the MatAssignmentData to TextureTag objects which do not exist anymore.
                for i in range(linkCount):
                    tag: c4d.TextureTag = linkList.ObjectFromIndex(mat.GetDocument(), i) 
                    if not isinstance(tag, c4d.TextureTag):
                        print (f"Warning - Stepping over dangling material assignment data: {mat}({i})")
                        continue
        
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)            
                    tag[c4d.TEXTURETAG_MATERIAL] = replacement
                
                # Close the undo for #mat
                doc.EndUndo()
            
        if __name__ == '__main__':
            # Get all materials in #doc and replace them, due to how we fashioned ReplaceMaterials(), we
            # could insert materials from different documents into #materials. This would of course not
            # persistently change these documents (on disk), one would have to still save their new state
            # to disk.
            materials: list[c4d.BaseMaterial] = doc.GetMaterials()
            # materials += doc1.GetMaterials() + doc2.GetMaterials() + ...
            ReplaceMaterials(materials)
            c4d.EventAdd()
        

        [2]

        # This still does not work due to how the parameter is handled.
        
        # @2023.1.0
        from typing import Optional
        import c4d
        
        doc: c4d.documents.BaseDocument  # The active document
        op: Optional[c4d.BaseObject]  # The active object, None if unselected
        
        def merge_material() -> None:
            myMat = doc.GetActiveMaterial()
            ObjLink = myMat[c4d.ID_MATERIALASSIGNMENTS]
        
            new_mat = c4d.BaseMaterial(5703)
            doc.InsertMaterial(new_mat)
            new_mat.SetName(myMat.GetName() + " Combine")
            new_mat_link = new_mat[c4d.ID_MATERIALASSIGNMENTS]
        
            myCount = ObjLink.GetObjectCount()
            for i in range(myCount) :  
                tag = ObjLink.ObjectFromIndex(doc,i)
                print (new_mat_link.InsertObject(tag, 0))
            
            print(new_mat_link.GetObjectCount())
            doc.SetActiveMaterial(new_mat, c4d.SELECTION_NEW)
            
            c4d.EventAdd()
            
        if __name__ == '__main__':
        

        MAXON SDK Specialist
        developers.maxon.net

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

          @ferdinand Thanks for this detailed explanation.

          About 1st , the AttributeError: parameter set failed . I remembered that I trying to set a cloner Inexclude data or something like that , sometimes it will return None type , but when I drag a "data" to it , it can be received.(seems like in R26 , I have update to 2023.1.0 , and try it just now , it will work again , I can't make sure want is happened earlier )

          new_mat_link = new_mat[c4d.ID_MATERIALASSIGNMENTS]
          

          And the Object , this is a great explanation. I didn't realized that material assignment is depend on actually texture tag 😧

          @ferdinand said in c4d.MatAssignData can't link.:

          There could be a plethora of reasons why it was done like this, ranging from pure oversight to an intentional boundary.

          And the last InsertObject why didn't work explain I can't really understand (It's to technical to me 😢 ) But the fix function is really smart way to bypass the problem .

          Cheers🥂

          https://boghma.com
          https://github.com/DunHouGo

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

            Hello @Dunhou ,

            without further questions or postings, we will consider this topic as solved by Thursday 01/06/2023 and flag it accordingly.

            Thank you for your understanding,
            Maxime.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

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