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

    PYTHON - Userdata CTracks and Basecontainer

    Cinema 4D SDK
    3
    12
    2.2k
    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.
    • Leo_SaramagoL
      Leo_Saramago
      last edited by Leo_Saramago

      Hello, there!

      I'm trying to copy values from one Userdata parameter to another. My python script gets all CTracks available, then seeks matches by using GetUserDataContainer, since not all parameters in Userdata necessarily have keyframes. For example, have a look at this picture:

      BC_UserData.jpg

      Stratocaster_R and Stratocaster_L get a match inside a function that returns a basecontainer:

      for bContainer in uData:
           if bContainer[1][1][:suffixCount_L:] == sourceObj.GetName()[:suffixCount_R:]:
                return bContainer
      

      How would I keyframe that matching basecontainer so I can get a CTrack from it later?

      Thanks in advance,

      Leo

      P.S.: The source and target information comes via dialog box, the script knows it's supposed to look for the _R suffix as source and _L as target beforehand.

      "The idea dictates everything."

      by David Lynch

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

        Hello,

        here are some answers:

        1. You could use a scripting tag instead of a script to avoid having to deal with the CTRacks at the cost of user interaction.
        2. BaseList2D.GetUserDataContainer() returns a list of DescID and BaseContainer tuples. There is nothing to keyframe there, the BaseContainer only holds the interface settings for that description element. The CTrack and current value of that element are attached to the BaseList2D that does host the user data.
        3. You also do not have to keyframe anything, you can just copy the CTrack and retarget it to the new DescID.

        Here is a script which does what you want. I didn't went overboard with your name matching rules, but the rest should be there.

        """ I broke things into two parts:
         1. get_matches() deals with building a data structure of matching DescID
          elements.
         2. add_ctracks() then does the building of CTracks.
        
         You could probably also streamline some stuff here and there, but I tried
         to be verbose so that things are clear. The script also only deals with
         the currently selected object.
        """
        
        import c4d
        
        
        def get_matches():
            """ Returns a list of tuples of the configuration (source_descid, targets),
            where source_descid is a DescID for which there is a CTrack in op, and
            targets is a list of DescIDs that match source_descid in type and name,
            but there is no CTrack for them in op.
            """
        
            res, user_data_container = [], op.GetUserDataContainer()
        
            """ Step through the user data container of op and find elements (sources)
                for which there is a CTrack in op."""
            for source_descid, source_bc in user_data_container:
                if op.FindCTrack(source_descid) is None:
                    continue
                target_descid_list = []
        
                """ Step through the user data container again and find elements
                   (targets) for which there is NO CTrack in op and which match the
                   current source in type and name."""
                for target_descid, target_bc in user_data_container:
                    no_track = op.FindCTrack(target_descid) is None
                    if not no_track:
                        continue
                    match_name = (source_bc[c4d.DESC_NAME][:-2] ==
                                  target_bc[c4d.DESC_NAME][:-2])
                    match_type = type(op[source_descid]) == type(op[target_descid])
                    is_new = sum(target_descid in data for _, data in res) == 0
        
                    if no_track and match_type and match_name and is_new:
                        target_descid_list.append(target_descid)
                    res.append((source_descid, target_descid_list))
            return res
        
        
        def add_ctracks(data):
            """ We copy the CTrack for each source DescID the number of target DescID
             which are attached to that source times back into op and set the CTrack 
             DescID each time to the target DescID. 
            """
            for source_did, target_descid_list in data:
                source_ctrack = op.FindCTrack(source_did)
                for target_did in target_descid_list:
                    new_ctrack = source_ctrack.GetClone()
                    new_ctrack.SetDescriptionID(op, target_did)
                    op.InsertTrackSorted(new_ctrack)
        
        
        def main():
            if op is not None:
                data = get_matches()
                add_ctracks(data)
                op.Message(c4d.MSG_UPDATE)
                c4d.EventAdd()
        
        
        # Execute main()
        if __name__ == '__main__':
            main()
        
        

        MAXON SDK Specialist
        developers.maxon.net

        Leo_SaramagoL 1 Reply Last reply Reply Quote 2
        • Leo_SaramagoL
          Leo_Saramago @ferdinand
          last edited by Leo_Saramago

          Hello, @zipit! Thanks for your input.

          This is a very interesting approach of yours, and I sure do like to learn new stuff. The truth is I didn't phrase my question properly, I'm sorry for the trouble.

          I said "keyframe the basecontainer", that's totally wrong, I know. I should have said something like "create a track based on that basecontainer match". In other words, I need to find the whole path back to the object that holds that specific UserData parameter.

          I don't think GetClone() will do the trick here. The reason is there's more funcionality that comes from the script's dialog box, things like frame range, frame offset, besides prefix/suffix, custom prefix/suffix, and so on... That's why I've been meaning to create tracks on target Userdata that don't have keyframes yet. By the way, this wouldn't leave Userdata that match and already have tracks out of my target list.

          So, I guess, the question remains, is it possible to get a "full name" for Userdata that don't have tracks based on my basecontainer name match? If so, I'd like to create a track for it right away - a simple keyframe with its current value should do. I'm currently looking at DescID here. I can feel the answer is close... all help is appreciated.

          Thanks again,

          Leo

          "The idea dictates everything."

          by David Lynch

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

            Hi,

            @Leo_Saramago said

            I said "keyframe the basecontainer", that's totally wrong, I know. I should have said something like "create a track based on that basecontainer match". In other words, I need to find the whole path back to the object that holds that specific UserData parameter.

            No problem, but your wording does still not really make sense to me. As I already wrote above the BaseContainer which is part of the tuples you get from BaseList2D.GetUserDataContainer() only contain the resource description interface settings you would normally define in a resource file as flags (min and max values, gui types, short and long names etc.).

            @Leo_Saramago said

            That's why I've been meaning to create tracks on target Userdata that don't have keyframes yet. By the way, this wouldn't leave Userdata that match and already have tracks out of my target list.

            Take a look again at the script it does that both.

            @Leo_Saramago said

            So, I guess, the question remains, is it possible to get a "full name" for Userdata that don't have tracks based on my basecontainer name match? If so, I'd like to create a track for it right away - a simple keyframe with its current value should do. I'm currently looking at DescID here. I can feel the answer is close... all help is appreciated.

            Not quite sure what you mean by "full name". User data is just a set of dynamically allocated description elements. Their values are no different from that of any other description element. They are tied to the GeListNode and accessed via __getitem__() with their DescID.

            Description element IDs aka DescID are not tied to a specific object, they are — as their name implies — just identifiers, an address in the namespace of an object. You can apply a DescID taken from object A to the objects B, C, and D and it will happily work as long as these objects happen to also have data stored under that DescID.

            I think you have (at least for me) to either state your problem more clearly or take a look at my script example again, as it shows you everything (I think) you need.

            Cheers
            zipit

            MAXON SDK Specialist
            developers.maxon.net

            Leo_SaramagoL 1 Reply Last reply Reply Quote 1
            • Leo_SaramagoL
              Leo_Saramago @ferdinand
              last edited by Leo_Saramago

              @zipit Hey, again! I've just tested your script and it definitely works, but it leaves duplicate tracks behind. I'm pretty sure there's an easy way out for it.

              I have to spend more time analyzing your code to learn this stuff right.

              I did make another attempt, but it failed tremendously!

              When I say "full name", I mean I want to iterate thru Userdata parameters just as I do when I get a list from srcObjs = objs.GetCTracks() , or in a similar fashion. That's what I've been doing with the info retrieved from the Basecontainer.

              Please, have a look at this rather short code of mine. Once again, thanks a lot!

              import c4d
              from c4d import gui
                      
              def main():
                  objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
                  
                  obj = objs[0] #Grab the Null that has relevant Userdata only
                  
                  UD_ids, UD_names, UD_types, UD_values = [],[],[],[]
                  
                  for descID, bc in obj.GetUserDataContainer():
                      UD_ids.append(descID[1].id) #list of IDs
                      UD_names.append(bc[c4d.DESC_NAME]) #list of names
                      UD_types.append(type((obj[c4d.ID_USERDATA,descID[1].id]))) #list of types
                      UD_values.append(obj[c4d.ID_USERDATA, descID[1].id]) #list of values
                  
                  #just checking if I'm really looking at the right data.
                  print "Userdata Parameters: " + str(UD_names)
                  print "Respective IDs: " + str(UD_ids)
                  print "Respective types: "+ str(UD_types)
                  print "Respective values: " + str(UD_values)
                  print " "
                  
                  srcObjs = obj.GetCTracks() #This is just a test for this specific Null. Only 'Stratocaster_R' is supposed have a CTrack for now.
                  
                  srcObj = srcObjs[0] #this is meant to work just once, for this test.
                  
                  if UD_names[0][:-2] == srcObj.GetName()[:-2]: #Force test to see if name comparisons are really working.
                      print str((srcObj.GetName())+" -> Match Found!\n ")
                  
                  track = obj.FindCTrack(UD_ids[0])
              
                  if track == None:
                      print str("There are no keyframes at the \'"+str(UD_names[0])+"\' Userdata parameter.")
                      print "Creating a track for it now...\n "
                      
                      track = c4d.CTrack(obj, UD_ids[0]) #Create Track using the first Userdata ID.
                      obj.InsertTrackSorted(track)
                      obj.Message(c4d.MSG_UPDATE)
                      c4d.EventAdd()
                      
                      print "Look at the Timeline... the Null has a new track, but it's not where I want it to be. It's not even Userdata!\n "
                      print "I tried GetClone() earlier, and it gave me a duplicate of Stratocaster_R. Just as bad.\n "
                      print "If I were a programmer, and the World depended on my skills, it would have ended long ago.\n "
                      
                      tgtTestObjs = obj.GetCTracks()
                      print tgtTestObjs
                  
                      
              if __name__=='__main__':
                  main()
              

              "The idea dictates everything."

              by David Lynch

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

                Hi,

                I'll just post a commented fraction of your code to make things more clear:

                    for descID, bc in obj.GetUserDataContainer():
                
                        UD_ids.append(descID[1].id) #list of IDs
                        """Why are you trying to split up the ID ;)
                
                        UD_ids.append(descID)
                        """
                        UD_names.append(bc[c4d.DESC_NAME]) #list of names
                        UD_types.append(type((obj[c4d.ID_USERDATA,descID[1].id]))) #list of types
                        """ A bit complicated ;)
                
                        UD_types.append(type(obj[descID]))
                        """
                        UD_values.append(obj[c4d.ID_USERDATA, descID[1].id]) #list of values
                        """ Should be explained by the other examples, but for completeness:
                        
                        UD_values.append(obj[descID]) 
                        """
                ```
                
                Cannot say much about the rest.
                
                Cheers
                zipit

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 0
                • Leo_SaramagoL
                  Leo_Saramago
                  last edited by

                  @zipit That bad? Gee... this one is gonna break my brain! Thanks!!!

                  "The idea dictates everything."

                  by David Lynch

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

                    @Leo_Saramago said in PYTHON - Userdata CTracks and Basecontainer:

                    @zipit That bad? Gee... this one is gonna break my brain! Thanks!!!

                    Nah, DescIDs are not really well explained in the Python SDK (actually they are not explained at all, just documented). So it is understandable to struggle a bit with them. A good reading tip is always the C++ SDK, in this case the DescID Manual which will hopefully make things a bit more understandable. One thing worth mentioning is also – with which you seem to struggle – the way user data DescIDs are build. To access a the x component of a of a non user data description element of the type c4d.Vector you would do this

                    # Build a descid for my_vector as a whole
                    descid_my_vector = c4d.DescID(c4d.DescLevel(c4d.IDC_MY_VECTOR, c4d.DTYPE_VECTOR, 0))
                    # or short hand for that
                    descid_my_vector = c4d.DescID(c4d.IDC_MY_VECTOR)
                    
                    # Build a descid for the x component of my_vector
                    descid_my_vector_x = c4d.DescID(
                        c4d.DescLevel(c4d.IDC_MY_VECTOR, c4d.DTYPE_VECTOR, 0),
                        c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0))
                    # or short hand for that
                    descid_my_vector_x = c4d.DescID(c4d.IDC_MY_VECTOR, c4d.VECTOR_X)
                    

                    For user data the first DescLevel of the DescID will always be c4d.ID_USERDATA which is necessary because the namespace here is not curated by Maxon or or the plugin author so they had to introduce a sub namespace to make the ID unambiguous (there might be a user data element with the ID 1000 but also a static description element with that same first DescLevel) So to just access a user data element with the ID 1 as a whole, you have have to do this:

                    descid_first_userdata = c4d.DescID(c4d.ID_USERDATA, 1)
                    # assuming this element is also a vector, the x component would be then
                    descid_first_userdata_x = c4d.DescID(c4d.ID_USERDATA, 1, c4d.VECTOR_X)
                    

                    Cheers
                    zipit

                    MAXON SDK Specialist
                    developers.maxon.net

                    1 Reply Last reply Reply Quote 0
                    • Leo_SaramagoL
                      Leo_Saramago
                      last edited by

                      @zipit said in PYTHON - Userdata CTracks and Basecontainer:

                      For user data the first DescLevel of the DescID will always be c4d.ID_USERDATA which is necessary because the namespace here is not curated by Maxon or or the plugin author so they had to introduce a sub namespace to make the ID unambiguous (there might be a user data element with the ID 1000 but also a static description element with that same first DescLevel)

                      Those lines should be near the top of any official explanation concerning Userdata in Python. Now that C4D is going Subscription, more and more people will struggle thru this.

                      Your help was invaluable @zipit ! Thanks a lot for your time(and patience). I think I'm finally starting to get the hang of all this. Here's the code that works:

                      def main():
                          objs = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
                          
                          obj = objs[0] #Grab the Null that has relevant Userdata only
                          
                          UD_ids, UD_names, UD_types, UD_values = [],[],[],[]
                          
                          for descID, bc in obj.GetUserDataContainer():
                              #list of IDs
                              UD_ids.append(descID)
                              
                              #list of names
                              UD_names.append(bc[c4d.DESC_NAME]) 
                              
                              #list of types
                              UD_types.append(type(obj[descID]))
                              
                              ##list of values
                              UD_values.append(obj[descID])
                                      
                          print "Userdata Names: " + str(UD_names)
                          print "Respective IDs: " + str(UD_ids)
                          print "Respective types: "+ str(UD_types)
                          print "Respective values: " + str(UD_values)
                          print " "
                          
                          srcObjs = obj.GetCTracks() #This is just a test. Only 'Stratocaster_R' is supposed have a CTrack
                          srcObj = srcObjs[0] #For Testing purposes
                          
                          if UD_names[0][:-2] == srcObj.GetName()[:-2]: #For testing purposes, the actual code will create tracks inside the loop
                              tgt_descID = UD_ids[0]
                          
                          print tgt_descID
                          
                          track = obj.FindCTrack(UD_ids[0])
                          
                          if track == None:
                              print str("There are no keyframes at the \'"+str(UD_names[0])+"\' Userdata parameter.")
                              print ("Creating one now.\n ")
                              track = c4d.CTrack(obj, tgt_descID) #Create Track using descID
                              obj.InsertTrackSorted(track)
                              obj.Message(c4d.MSG_UPDATE)
                              c4d.EventAdd()
                      

                      "The idea dictates everything."

                      by David Lynch

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

                        Hi,

                        I am glad I could help and that it did work out for you. On a side note: Although I did also kind of rant about the documentation here, the quality both of Cinema4D's SDK itself, its documentation and support is pretty good. Especially when compared to what other companies do provide cough, Autodesk, cough.

                        Cheers
                        zipit

                        MAXON SDK Specialist
                        developers.maxon.net

                        1 Reply Last reply Reply Quote 0
                        • Leo_SaramagoL
                          Leo_Saramago
                          last edited by

                          I'm from the Music realm, and I've dealt with all kinds of hardware/software. Cinema 4D gets to be at the very top in every single aspect, support team included.

                          Thanks again, I'll close this topic now.

                          "The idea dictates everything."

                          by David Lynch

                          1 Reply Last reply Reply Quote 1
                          • ManuelM
                            Manuel
                            last edited by

                            hello,
                            thanks @zipit for answering the question 🙂

                            and thanks you both for the kinds words 🙂

                            Cheers,
                            Manuel

                            MAXON SDK Specialist

                            MAXON Registered Developer

                            1 Reply Last reply Reply Quote 0
                            • ferdinandF ferdinand referenced this topic on
                            • First post
                              Last post