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

    Changing material projection in takes using Python

    Cinema 4D SDK
    2
    12
    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
      last edited by ferdinand

      Hi @Futurium,

      thank you for reaching out to us. It is rather hard to untangle what is going wrong there for you, since you only show a snippet and do not tell us in which environment it does run. I assume this is a script manager script? The way you remove the old tag and then add a new take seems a bit fishy to me, you might have to update the scene graph there, but I would have to try that myself.

      However, it seems also to be a bit overkill for what you are trying to do. Why not change just the projection of the existing tag? Below you will find an example for creating a take for a projection change and the scene file I did ran it on. @m_adam also wrote a bunch of nice take system examples, where I did ninja most of my code from 😉

      Cheers,
      Ferdinand

      example file: pc13077_scene.c4d

      """Will create a take entry for a projection change to 
      TEXTURETAG_PROJECTION_UVW on the selected objects texture tag.
      """
      import c4d
      
      def main():
          # Checks if there is an active object.
          if op is None:
              raise ValueError("op is none, please select one object.")
      
          # Gets the TakeData from the active document (holds all information about 
          # Takes)
          takeData = doc.GetTakeData()
          if takeData is None:
              raise RuntimeError("Failed to retrieve the take data.")
      
          # Gets the active Take and check it's not the main one
          take = takeData.GetCurrentTake()
          if take.IsMain():
              raise RuntimeError("The selected take is already the main one.")
      
          # Gets the material tag of the cube.
          tag = op.GetTag(c4d.Ttexture)
          if tag is None:
              raise RuntimeError("Blah, no texture tag on selected object.")
          
          # The single level DescId for the projection parameter.
          descId = c4d.DescID(
              c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0))
          newValue = c4d.TEXTURETAG_PROJECTION_UVW
          
          # Add an override if this parameter is not already overridden, otherwise 
          # returns the already existing override.
          overrideNode = take.FindOrAddOverrideParam(
              takeData, tag, descId, newValue)
          if overrideNode is None:
              raise RuntimeError("Failed to find the override node.")
      
          # Updates the scene with the new Take
          overrideNode.UpdateSceneNode(takeData, descId)
      
          # Pushes an update event to Cinema 4D
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      

      MAXON SDK Specialist
      developers.maxon.net

      1 Reply Last reply Reply Quote 1
      • F
        Futurium
        last edited by ferdinand

        @zipit said in Changing material projection in takes using Python:

        TEXTURETAG_PROJECTION_UVW on the selected objects texture tag.
        """
        import c4d
        
        def main():
            # Checks if there is an active object.
            if op is None:
                raise ValueError("op is none, please select one object.")
        
            # Gets the TakeData from the active document (holds all information about 
            # Takes)
            takeData = doc.GetTakeData()
            if takeData is None:
                raise RuntimeError("Failed to retrieve the take data.")
        
            # Gets the active Take and check it's not the main one
            take = takeData.GetCurrentTake()
            if take.IsMain():
                raise RuntimeError("The selected take is already the main one.")
        
            # Gets the material tag of the cube.
            tag = op.GetTag(c4d.Ttexture)
            if tag is None:
                raise RuntimeError("Blah, no texture tag on selected object.")
            
            # The single level DescId for the projection parameter.
            descId = c4d.DescID(
                c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0))
            newValue = c4d.TEXTURETAG_PROJECTION_UVW
            
            # Add an override if this parameter is not already overridden, otherwise 
            # returns the already existing override.
            overrideNode = take.FindOrAddOverrideParam(
                takeData, tag, descId, newValue)
            if overrideNode is None:
                raise RuntimeError("Failed to find the override node.")
        
            # Updates the scene with the new Take
            overrideNode.UpdateSceneNode(takeData, descId)
        
            # Pushes an update event to Cinema 4D
            c4d.EventAdd()
        
        
        if __name__ == '__main__':
            main()```
        

        Thank you @zipit
        I've tried as suggested but still doesn't seems to be working the way I wanted.
        I modified my code and included your suggestions, as well as my test scene, so you can see what's my issue. I bet it's something simple ;-).

        import c4d
        import os
        from c4d import documents
        
        listOfParentTakes = []
        listOfChildTakes = []
        objects = ["Plane","Plane.1","Plane.2"]
        
        
        def GetListOfParentTakes():
            takeData = doc.GetTakeData()
            if takeData is None:
                return
        
            mainTake = takeData.GetMainTake()
            take = mainTake.GetDown()
        
            while take is not None:
                listOfParentTakes.append((take))
                take = take.GetNext()
        
        def GetListOfChildrenTakes():
            for parent in listOfParentTakes:
                take = parent.GetDown()
        
                while take is not None:
                    listOfChildTakes.append(take)
                    take = take.GetNext()
        
        # Main function
        
        def main():
        
            c4d.CallCommand(13957)  # Clear Console
            doc = documents.GetActiveDocument()
            takeData = doc.GetTakeData()
            GetListOfParentTakes()
            GetListOfChildrenTakes()
            if takeData is not None:
                for el in listOfChildTakes:
                    print ("Current take name : "+ el.GetName())
                    takeData.SetCurrentTake(el)
        
                    for currentObject in objects:
                        obj = doc.SearchObject(currentObject)
                        if obj is not None:
                            # Gets the material tag of the cube.
                            tag = obj.GetTag(c4d.Ttexture)
                            if tag is None:
                                raise RuntimeError("Blah, no texture tag on selected object.")
                                # The single level DescId for the projection parameter.
                            descId = c4d.DescID(c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0))
                            newValue = c4d.TEXTURETAG_PROJECTION_UVW
        
                            # Add an override if this parameter is not already overridden, otherwise
                            # returns the already existing override.
                            overrideNode = el.FindOrAddOverrideParam(
                                takeData, tag, descId, newValue)
                            if overrideNode is None:
                                raise RuntimeError("Failed to find the override node.")
        
        
        # Execute main()
        if __name__=='__main__':
            main()
        

        TakeProjectionTest.c4d

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

          Hi @Futurium,

          first of all your code is missing the instructions to actually update the take graph (see lines 62 and 63 in the attached example), and secondly you should try to include error messages/exceptions in the future, so that we know that we are talking about the same thing. When I run your code with a default startup Cinema 4D configuration, it will raise Failed to find the override node. defined in your code, meaning el.FindOrAddOverrideParam on line 57 failed. This is caused by Cinema not being in Lock Overrides mode by default, enabling it will let your code complete on my machine.

          1.png

          If you meant something else with "doesn't seems to be working the way I wanted", I would have to ask you to clarify what you would consider not to be working.

          Cheers,
          Ferdinand

          import c4d
          import os
          from c4d import documents
          
          listOfParentTakes = []
          listOfChildTakes = []
          objects = ["Plane","Plane.1","Plane.2"]
          
          
          def GetListOfParentTakes():
              takeData = doc.GetTakeData()
              if takeData is None:
                  return
          
              mainTake = takeData.GetMainTake()
              take = mainTake.GetDown()
          
              while take is not None:
                  listOfParentTakes.append((take))
                  take = take.GetNext()
          
          def GetListOfChildrenTakes():
              for parent in listOfParentTakes:
                  take = parent.GetDown()
          
                  while take is not None:
                      listOfChildTakes.append(take)
                      take = take.GetNext()
          
          # Main function
          
          def main():
              c4d.CallCommand(13957)  # Clear Console
              doc = documents.GetActiveDocument()
              takeData = doc.GetTakeData()
              GetListOfParentTakes()
              GetListOfChildrenTakes()
              print (listOfChildTakes)
              if takeData is not None:
                  for el in listOfChildTakes:
                      print ("Current take name : "+ el.GetName())
                      takeData.SetCurrentTake(el)
          
                      for currentObject in objects:
                          obj = doc.SearchObject(currentObject)
                          if obj is not None:
                              # Gets the material tag of the cube.
                              tag = obj.GetTag(c4d.Ttexture)
                              if tag is None:
                                  raise RuntimeError("Blah, no texture tag on selected object.")
                                  # The single level DescId for the projection parameter.
                              descId = c4d.DescID(c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0))
                              newValue = c4d.TEXTURETAG_PROJECTION_UVW
          
                              # Add an override if this parameter is not already overridden, otherwise
                              # returns the already existing override.
                              overrideNode = el.FindOrAddOverrideParam(
                                  takeData, tag, descId, newValue)
                              if overrideNode is None:
                                  raise RuntimeError("Failed to find the override node.")
                              # Lines your code was missing
                              overrideNode.UpdateSceneNode(takeData, descId)
              c4d.EventAdd()
          
          # Execute main()
          if __name__=='__main__':
              main()
          

          MAXON SDK Specialist
          developers.maxon.net

          F 1 Reply Last reply Reply Quote 1
          • F
            Futurium @ferdinand
            last edited by Futurium

            Hi @zipit,
            I'm sorry if I wasn't clear what I was trying to achieve. I used your updated script and recorded a short gif, which shows, the projection materials I'm trying to change in takes stays the same after running the script. I'm expecting the projection to change from current Spherical to new UVW Mapping. What am I missing?

            My test

            Best regards,
            Tomasz

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

              Hi @Futurium,

              we have seen your issue and I will report back when we have an answer, which might take a while, because I had to reach out to the animation team.

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • F
                Futurium
                last edited by

                Hi @zipit Not sure what do you mean. The problem is not related to animation.
                I recorded my screen when tried your solution, and shared with you the results of the script, which were not what I was expecting - the projection stays "Spherical" after running the script in the Script Manager.

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

                  Hi @Futurium,

                  your problem is related to the Take System which is being handled by Maxon's animation development team. I do understand your problem, and you can reproduce it in Cinema 4D itself (without Python). I just reached out to the animation team so ask them if what you are trying to do is simply not intended to be done or a bug.

                  I will give you a heads up here when I did hear back from the animation team.

                  Cheers,
                  Ferdinand

                  MAXON SDK Specialist
                  developers.maxon.net

                  1 Reply Last reply Reply Quote 0
                  • F
                    Futurium
                    last edited by

                    Hi @zipit
                    Thank you for your clarification.
                    Is there any other way of doing that? Currently, I'm trying to modify projection for existing takes, but if there is a way to do it from scratch when the take is created - that could work for now.
                    Basically what I'm trying to do is to create multiple different takes per room, and for every take - apply the same material to different floor meshes (so if you're in bedroom1 and you change carpet - the carpet material would change on all other defined the rooms)
                    I don' mind to send you my current code + files we use to generate the takes, but that's not something I would be able to share on the public forum.
                    Best regards,
                    Tomasz

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

                      Hi @Futurium,

                      The problem here for us at the SDK-Team is that we are not so sure if what you are trying to do is encountering a bug or simply not intended to be done. I spoke a bit with one of the devs and they will have a look at that odd behavior (which can be reproduced in Cinema without any code), but it will probably take us some time, because we are currently quite busy.

                      The "problem" with your approach is that you are trying to pile a parameter override on top of tag which is crated by an override group. This is not how it is usually done in Cinema's Take system, you normally just edit that "virtual tag". If you try to do manually what your script does, Cinema will simply delete these overrides once you change the current take (which is also what happens in your script, the last take will contain the UVW-override, but it will not be respected by Cinema).

                      I personally would say this level of complexity - to create an attribute override for such "virtual tags" - was never intended in Cinema's take system, but I might be wrong. You can of course still do it in Python like you would normally do it Cinema, i.e., simply edit the override group tag. I have attached a script example based on your last script which does that. It will change the projection of your material tags to UVW, but won't do it with the same implied complexity as your example does it (by creating a specific attribute override for it).

                      Happy holidays,
                      Ferdinand

                      """Example for "working around" the limitations of take system override groups
                      as discussed in:
                      
                          https://developers.maxon.net/forum/topic/13077
                      """
                      
                      import c4d
                      
                      def yieldSecondLevelTakes():
                          """Yields all second level take nodes in the scenes take graph.
                      
                          Your functions, just slightly refactored. Kept this way for clarity, but
                          could be done in just one function instead of your two function design.
                          """
                          def yieldTopLevelTakeNodes():
                              """Yields the top level take nodes of a document.
                              """
                              takeData = doc.GetTakeData()
                              if takeData is None:
                                  return
                      
                              mainTake = takeData.GetMainTake()
                              take = mainTake.GetDown()
                      
                              while take is not None:
                                  yield take
                                  take = take.GetNext()
                      
                          for take in yieldTopLevelTakeNodes():
                              chldTake = take.GetDown()
                              while chldTake is not None:
                                  yield chldTake
                                  chldTake = chldTake.GetNext()
                      
                      def main():
                          """Entry point.
                          """
                          # Clear Console
                          c4d.CallCommand(13957) 
                          # Not necessary, doc is predefined in a script.
                          # doc = documents.GetActiveDocument()
                      
                          # Terminate branches early so that you have less indented code (and it
                          # technically also runs a bit faster).
                          takeData = doc.GetTakeData()
                          if takeData is None:
                              raise RuntimeError("No take data in the document.")
                      
                          # Loop over all second level take nodes like you want to do.
                          for take in yieldSecondLevelTakes():
                              print ("Current take name : " + take.GetName())
                      
                              # Not needed
                              # takeData.SetCurrentTake(take)
                      
                              # This is not necessary in your scene, since all your objects
                              # have the same override group tag, i.e. changing a parameter
                              # on one tag will change the respective parameters on all other
                              # 'instances' of the tag.
                      
                              # for currentObject in objects:
                              #    obj = doc.SearchObject(currentObject)
                              # ...
                      
                              # This should be technically moved outside of the loop for
                              # performance reasons, kept here for parity reasons with your script.
                              node = doc.SearchObject("Plane")
                              if node is None:
                                  raise RuntimeError("Could not find targeted object node.")
                      
                              # The first override group, there could be more of course in a more
                              # complex scene.
                              overrideGroup = take.GetFirstOverrideGroup()
                              if overrideGroup is None:
                                  continue
                      
                              # Get the material tag associated with this override group.
                              tag = overrideGroup.GetTag(c4d.Ttexture)
                              if tag is None:
                                  continue
                      
                              # The "workaround" here is to simply write to the tag. This of
                              # course will not give you the same complexity as one could
                              # imagine with takes, but since the tag is a BaseOverride tag,
                              # i.e. a tag that itself is bound to  a Take, your data will
                              # still be conditional in that sense.
                              tag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW
                      
                          # Notify Cinema 4D for updates.
                          c4d.EventAdd()
                      
                      # Execute main()
                      if __name__=='__main__':
                          main()
                      

                      MAXON SDK Specialist
                      developers.maxon.net

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

                        Hi,

                        without further feedback, we will consider this thread as solved by Wednesday and flag it accordingly.

                        Cheers,
                        Ferdinand

                        MAXON SDK Specialist
                        developers.maxon.net

                        1 Reply Last reply Reply Quote 0
                        • F
                          Futurium
                          last edited by

                          Thank you @zipit
                          I adopted your solution into our code and it works exactly as expected.
                          Thank you for your help.
                          Best regards,
                          Tomasz

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