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

    Finding duplicate materials - Octane Render

    Cinema 4D SDK
    python r19
    7
    17
    2.7k
    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.
    • A
      AndreAnjos
      last edited by AndreAnjos

      Hi all,

      I'm currently trying to find duplicate materials by comparing the values of its BaseContainer parameters.
      Unfortunately, I can see that exact same objects have different memory addresses and it will always fail.

      <c4d.BaseShader object called 'Absorption Medium/Absorption Medium' with ID 1029510 at **0x00000223328084F0**>
      <c4d.BaseShader object called 'Absorption Medium/Absorption Medium' with ID 1029510 at **0x00000223328085F0**>

      Have the following code:

      import c4d
      
      
      def get_data(mat):
          """
          Get the parameters from the materials container and return a list.
          """
      
          data = mat.GetDataInstance()
          mat_params = []
      
          for i in range(len(data) - 1):
              index = data.GetIndexId(i)
              try:
                  mat_params.append(data[index])
              except AttributeError:
                  continue
      
          return mat_params
      
      
      def compare_data(lst_a, lst_b):
          """
          Compare the data between lists a and b. If data is the same, than material is a duplicate.
          """
      
          zipped = zip(lst_a, lst_b)
      
          for params in zipped:
              if params[0] != params[1]:
                  return False
      
          return True
      
      def main():
      
          mat_lst = doc.GetMaterials()
          duplicate_materials = []
      
          for a, mat_a in enumerate(mat_lst):
              data_a = get_data(mat_a)
      
              for b, mat_b in enumerate(mat_lst):
                  if a != b:
      
                      data_b = get_data(mat_b)
                      data_difference = compare_data(data_a, data_b)
      
                      if data_difference:
                          duplicate_materials.append(mat_b)
      
      
          assert not duplicate_materials, "You have {0} materials duplicated! " \
                                          "See list below:\n\n{1}".format(len(duplicate_materials),
                                                                          ", ".join(mat.GetName() for mat in duplicate_materials))
                                                                          
      if __name__ == '__main__':
      
          main() 
      

      Wonder if you have any advice on how to deal with this issue or perhaps a different approach to duplicate objects.

      Thank you in advance! 😃

      Andre

      1 Reply Last reply Reply Quote 0
      • P
        PluginStudent
        last edited by

        Not sure if it helps in your case, but BaseMaterial has a Compare() function.

        1 Reply Last reply Reply Quote 1
        • A
          AndreAnjos
          last edited by

          Hi @PluginStudent ,

          Thank you very much for your help! 🙂

          I should have mentioned that it needs to work for any render engine. Currently we use Octane and I think the Compare() function may just be related to Cinema native materials (I may be wrong!), as from my previous tests it did not work.

          So I'm trying to go with data comparising, just in case.

          Let me know if you find another solution please!

          Thank you again! 😃

          1 Reply Last reply Reply Quote 0
          • CairynC
            Cairyn
            last edited by

            If you have two materials, and both use a certain shader, then these shader are (as you have noticed) still different objects at different memory addresses. Since C4D classes are mutable (other than, say, the Python integer class) Python cannot reuse the object. After all, the user may at any time change a parameter in the shader used by material 1, but not the equivalent shader used by material 2, resulting in 2 different shader instances.

            With all hierarchical structure based on dynamic objects, you need to perform the comparison recursively (deeply), or you will run into the issue that data identity is not necessarily object identity. If you're lucky, your classes contain a deep comparison function already; otherwise, you need to go into the objects-to-compare and continue on that level, all the way down.

            A 1 Reply Last reply Reply Quote 1
            • A
              AndreAnjos @Cairyn
              last edited by

              Hi @Cairyn,

              Thank you very much for your thorough explanation! 😃
              That makes a lot a sense to me and seems to be the way to proceed with this.
              I will give it a go and see where it leads me.

              Have a good one and thanks again!

              1 Reply Last reply Reply Quote 0
              • A
                AndreAnjos
                last edited by AndreAnjos

                Hi all,

                So I after a bit of tinkering and based on what @Cairyn explained, this is the outcome.
                Please do let me know if I can improve on something.

                My only doubt is regarding the PyCObject types and what to do with it. In the code below I'm just ignoring it as I can't do anything with it anyway in a Python data context (I think!) and it doesn't seem to create any unusal cases when comparing the materials values.

                import c4d
                
                
                def get_data(data, lst):
                    """
                    Get the parameters from the materials container and return a list.
                    """
                
                    for i in range(len(data) - 1):
                        index = data.GetIndexId(i)
                        try:
                            if isinstance(data[index], (c4d.BaseShader, c4d.BaseTime)):
                                sub_data = data[index].GetDataInstance()
                                get_data(sub_data, lst)
                            else:
                                if type(data[index]).__name__ == 'PyCObject':
                                    continue
                                lst.append(data[index])
                        except AttributeError:
                            continue
                
                    return lst
                
                
                def compare_data(lst_a, lst_b):
                    """
                    Compare the data between lists a and b. If data is the same, than material is a duplicate.
                    """
                
                    zipped = zip(lst_a, lst_b)
                
                    for params in zipped:
                        if params[0] != params[1]:
                            return False
                
                    return True
                
                def main():
                
                    mat_lst = doc.GetMaterials()
                    duplicate_materials = []
                
                    for a, mat_a in enumerate(mat_lst):
                        data_a = get_data(mat_a, [])
                
                        for b, mat_b in enumerate(mat_lst):
                            if a != b:
                
                                data_b = get_data(mat_b, [])
                                data_difference = compare_data(data_a, data_b)
                
                                if data_difference:
                                    duplicate_materials.append(mat_b)
                
                
                    assert not duplicate_materials, "You have {0} materials duplicated! " \
                                                    "See list below:\n\n{1}".format(len(duplicate_materials),
                                                                                    ", ".join(mat.GetName() for mat in duplicate_materials))
                                                                                    
                if __name__ == '__main__':
                
                    main() 
                

                Hope this helps anyone else!

                Cheers! 😃

                1 Reply Last reply Reply Quote 0
                • r_giganteR
                  r_gigante
                  last edited by

                  Thanks @AndreAnjos for coming up with the final code.

                  Two notes:

                  • first reading your initial question: was hard to understand what you meant with regard to "duplicated" materials and how you were defining the equality among twos. Standing on the solution you presented, basically two materials are identical only and only if the values of all their parameters are equal
                  • second the get_data function expects two arguments, whilst, in your code, only one is given to both calls in the main.

                  Finally, as pointed out by @PluginStudent, BaseMaterial::Compare() actually does exactly what you're trying to achieve, and any material implementing MaterialData can be checked via this method belonging to the BaseMaterial class to which they inherit from (independently from the rendering engine they belong and assuming they following up with the best practice of storing data in BaseContainers)

                  The Compare actually does:

                  • compares BaseChannels
                  • compares BaseContainers
                  • compares Ckeys found in CCurves belonging to CTrack.

                  Only when these three checks give all positive results, the two materials are considered identical

                  Best, R

                  1 Reply Last reply Reply Quote 0
                  • A
                    AndreAnjos
                    last edited by

                    Hi @r_gigante,

                    Thank you very much for pointing stuff out! 😃

                    first reading your initial question: was hard to understand what you meant with regard to "duplicated" materials and how you were defining the equality among twos. Standing on the solution you presented, basically two materials are identical only and only if the values of all their parameters are equal

                    Apologies for this! Should have been more specific with my description.

                    second the get_data function expects two arguments, whilst, in your code, only one is given to both calls in the main.

                    This has now been updated! Thanks for pointing it out.

                    Finally, as pointed out by @PluginStudent, BaseMaterial::Compare() actually does exactly what you're trying to achieve, and any material implementing MaterialData can be checked via this method belonging to the BaseMaterial class to which they inherit from (independently from the rendering engine they belong and assuming they following up with the best practice of storing data in BaseContainers)
                    The Compare actually does:

                    • compares BaseChannels
                    • compares BaseContainers
                    • compares Ckeys found in CCurves belonging to CTrack.

                    Only when these three checks give all positive results, the two materials are considered identical

                    Great! Thank you for your explanation! Did not know if that would be the case since my tests were not coming with the results. So will give it another go!

                    Thanks again! 🙂

                    1 Reply Last reply Reply Quote 0
                    • A
                      AndreAnjos
                      last edited by AndreAnjos

                      Hi @r_gigante

                      I must be really stupid here, but can't have the result you are saying with this function.
                      A simple test of having 2 supposedly duplicate materials and still returns False.

                      import c4d
                      
                      def main():
                      
                          mat_lst = doc.GetMaterials()
                          result = mat_lst[0].Compare(mat_lst[1])
                          print(result)
                      
                      if __name__ == '__main__':
                          main()
                      

                      I must be missing something very obvious and I can't figure it out. 😆

                      Thanks for your help!

                      1 Reply Last reply Reply Quote 0
                      • A
                        AndreAnjos
                        last edited by AndreAnjos

                        Also as an update!
                        I've been talking with a couple of artists here and after a few simple tests using the Delete Duplicate Materials in Cinema R19 and R21, does not work as well.

                        Is this a common issue?

                        Thanks!

                        1 Reply Last reply Reply Quote 0
                        • r_giganteR
                          r_gigante
                          last edited by

                          Hi @AndreAnjos, looking at the code it seems I've spotted a bug but it's still under investigation. I'll let you know if that's the case and report back.

                          Cheers, R

                          A 1 Reply Last reply Reply Quote 0
                          • A
                            AndreAnjos @r_gigante
                            last edited by AndreAnjos

                            Hi @r_gigante,

                            Any luck with this? It's no rush at all for us, just curious if you have an update 😃

                            Cheers!

                            Andre

                            1 Reply Last reply Reply Quote 0
                            • r_giganteR
                              r_gigante
                              last edited by

                              Hi @AndreAnjos, no updates so far.

                              Cheers, R.

                              1 Reply Last reply Reply Quote 0
                              • A
                                AndreAnjos
                                last edited by

                                Hi @r_gigante,

                                Thanks for letting me know!

                                Andre

                                1 Reply Last reply Reply Quote 0
                                • T
                                  Thodos
                                  last edited by

                                  Sooooo, I guess this bug was not adressed still?
                                  Compare returns False even if I compare the same material or copied one.

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

                                    Hi @Thodos in which version are you? And would it be possible for you to share a scene?

                                    Thanks in advance,
                                    Cheers,
                                    Maxime.

                                    MAXON SDK Specialist

                                    Development Blog, MAXON Registered Developer

                                    1 Reply Last reply Reply Quote 0
                                    • John_DoJ
                                      John_Do
                                      last edited by John_Do

                                      Hi, sorry for bringing back up this topic but the Compare() method still gives weird results in 2024.5.1.

                                      Cinema_4D_D5fWj9O4RQ.gif

                                      • Comparing a material duplicated with Ctrl-Drag returns sometimes True, sometimes False
                                      • Comparing a material against a copy-pasted version (in the same scene) always returns False
                                      • Comparing two identical materials both copy-pasted together in one-go in another scene always returns False, even if True was returned in the original scene.

                                      Please note that I'm using Corona Materials here but results are the same with the Standard Material.

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