Issue moving userdata between parents

  • On 02/02/2017 at 14:26, xxxxxxxx wrote:

    I'm trying to move userdata elements between groups, but it fails if the new parent group was created after the child element (the child element ends up on the top level). I'm guessing this is because elements are read in order and so when the specified parent group hasn't been read in it just punts.

    Is there an elegant way to handle this?

    The script below is a simplified example. When run, the GRP group ends up on the top level, but it should be under PARENT\SUB2 in the User Data tab. You can see the correct result if you Manage User Data and then click OK.

    import c4d   
    from c4d import gui   
    #Welcome to the world of Python   
    def CreateUserDataGroup(obj, name, parentGroup=None, columns=None, shortname=None) :   
        if obj is None: return False   
        if shortname is None: shortname = name   
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_GROUP)   
        bc[c4d.DESC_NAME] = name   
        bc[c4d.DESC_SHORT_NAME] = shortname   
        bc[c4d.DESC_TITLEBAR] = 1   
        if parentGroup is not None:   
            bc[c4d.DESC_PARENTGROUP] = parentGroup   
        if columns is not None:   
            bc[c4d.DESC_LAYOUTGROUP] = True   
            bc[c4d.DESC_COLUMNS] = columns   
        return obj.AddUserData(bc)       
    def CreateUserDataFloat(obj, name, val=0, parentGroup=None, unit=c4d.DESC_UNIT_REAL) :   
        if obj is None: return False   
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_REAL)   
        bc[c4d.DESC_NAME] = name   
        bc[c4d.DESC_SHORT_NAME] = name   
        bc[c4d.DESC_DEFAULT] = val   
        bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_ON   
        bc[c4d.DESC_UNIT] = unit   
        bc[c4d.DESC_MINSLIDER] = -500   
        bc[c4d.DESC_MAXSLIDER] = 500   
        bc[c4d.DESC_STEP] = 1          
        if parentGroup is not None:   
            bc[c4d.DESC_PARENTGROUP] = parentGroup   
        element = obj.AddUserData(bc)   
        obj[element] = val   
        return element   
    def main() :   
        print "*"*10   
        # Create the initial userdata definition   
        parentgroup = CreateUserDataGroup(op,"PARENT")   
        subgroup = CreateUserDataGroup(op,"SUB",parentgroup)   
        group = CreateUserDataGroup(op,"GRP",subgroup)   
        data = CreateUserDataFloat(op,"FLOAT",0,group)   
        # Print the current userdata container   
        ud = op.GetUserDataContainer()   
        for id, bc in ud:   
            print id, bc[c4d.DESC_NAME], bc[c4d.DESC_PARENTGROUP]   
            for i in range(0,id.GetDepth()) :   
                print "-- %s, %s, %s" % (id[i].id, id[i].dtype, id[i].creator)   
            if id == group:   
                groupdata = bc   
        print "*"*10   
        # Simulate a later operation, moving subgroup to a new group   
        subgroup2 = CreateUserDataGroup(op,"SUB2",parentgroup)   
        groupdata[c4d.DESC_PARENTGROUP] = subgroup2   
        # Print the current userdata container   
        ud = op.GetUserDataContainer()   
        for id, bc in ud:   
            print id, bc[c4d.DESC_NAME], bc[c4d.DESC_PARENTGROUP]      
            for i in range(0,id.GetDepth()) :   
                print "-- %s, %s, %s" % (id[i].id, id[i].dtype, id[i].creator)   
    if __name__=='__main__':   

  • On 04/02/2017 at 10:05, xxxxxxxx wrote:

    Hi Rick,
    The new tab group you create does not exist yet when you try to set the FLOAT item's container to it. And AFAIK there is no way to make it update and exist without running two separate scripts.
    If you create the new tab group in one script. Then run another script to move the FLOAT item over to it. It should work.

    I've been doing a lot if UD stuff lately too. And one of the things I've learned is that copying containers this way only seems to work well on non child UD items. And that's about it.
    It falls apart when groups are involved.
    So what I am doing now is to create all of my UD items from scratch. Rather than trying to piggyback their container data. It's just not worth the headaches to try and share the container data when groups are involved.

    So for example. If I am copying a specific UD Group from one object to another.
    I don't try to apply their containers to each other. Instead I scrape their container data then use it when creating the UD items from scratch.

    #Copy a specific UD group and it's children to another object  
    #This script copies all of the Group type UserData items to a list array  
    #Then uses that list data to copy only a specific group and it's children to another object  
    import c4d  
    def main() :  
      source = doc.SearchObject("Cube")  
      if source is None: return  
      target = doc.SearchObject("Sphere")   
      if target is None: return   
      doc.AddUndo(c4d.UNDOTYPE_CHANGE, target)  
      #The specific UD Group to copy to a different object  
      copiedGrp = "Group1"  #<------ Change this as desired  
      #The level ID#s for any Groups found in the master UD container  
      groups = []  
      s_ud = source.GetUserDataContainer()  
      t_ud = target.GetUserDataContainer()   
      deleteFirst = False  
      if len(t_ud) == 0:  
          #Create a dummy UD item so the UD container is not empty and lets us add things to it  
          dummybc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_BOOL)  
      lastItem = len(t_ud)  
      if lastItem == 0:   
          lastItem += 1  
          deleteFirst = True  
      for UD_ID, bc in source.GetUserDataContainer() :  
          #If we've reached a UD Group  
          #Save it's level to the list array  
          if bc.GetLong(c4d.DESC_CUSTOMGUI) == 0:  
          level = UD_ID[1].id          
          #Get the parent level of each UD item  
          descID = bc.GetData(c4d.DESC_PARENTGROUP)  
          pLevel = descID[-1].id   
          for i in xrange(len(groups)) :  
              #Copy the group's data  
              if level == groups[i]:  
                 grp = bc.GetClone(c4d.COPYFLAGS_PRIVATE_CONTAINER_COPY_IDENTICAL)   
                 grpName = grp.GetString(c4d.DESC_NAME)  
                 if grpName == copiedGrp:                 
              #If the UD item is the child of the group              
              if pLevel == groups[i]:                  
                  if grpName == copiedGrp:  
                      value = source[c4d.ID_USERDATA, level]  #Copy it's value  
                      name = bc.GetString(c4d.DESC_NAME)  
                      guiType = bc.GetLong(c4d.DESC_CUSTOMGUI)  
                      unit = bc.GetLong(c4d.DESC_UNIT)               
                      dType = bc.GetId()               
                      newID = len(target.GetUserDataContainer()) + 1 #Create a new level#                     
                      newbc = c4d.GetCustomDatatypeDefault(dType)    #Create a new container  
                      newbc[c4d.DESC_NAME] = name                    #Copy the name value           
                      newbc[c4d.DESC_CUSTOMGUI] = guiType            #Copy the type value  
                      newbc[c4d.DESC_UNIT] = unit                    #Copy the unit value                      
                      newbc[c4d.DESC_PARENTGROUP] = target.GetUserDataContainer()[lastItem][0]  
                      entry = target.AddUserData(newbc)              #Add the copied UD item to the target object  
                      #if entry[1].dtype == 19: print "Real slider"                   
                      #if entry[1].dtype == 15: print "Long Slider"     
                      #if entry[1].dtype == 1000481: print "gradient"     
                      #Copy the value of the source UD item to the target UD item  
                      #Then update the target's master UD container to reflect the changes  
                      target[entry] = value  
                      target.SetUserDataContainer([c4d.ID_USERDATA, newID], newbc)                  
      #Delete the dummy item if it exists  
      if deleteFirst: target.RemoveUserData(1)  
      #print groups  
      c4d.SendCoreMessage(c4d.COREMSG_CINEMA, c4d.BaseContainer(c4d.COREMSG_CINEMA_FORCE_AM_UPDATE))          
    if __name__=='__main__':  

    The support guys might have a better answer for you on Monday.
    But for me personally. Sharing containers the way you're doing it has been nothing but painful for me.


Log in to reply