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_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER   
    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   
               
    c4d.EventAdd()   
       
    print "*"*10   
               
    # Simulate a later operation, moving subgroup to a new group   
    subgroup2 = CreateUserDataGroup(op,"SUB2",parentgroup)   
    groupdata[c4d.DESC_PARENTGROUP] = subgroup2   
    op.SetUserDataContainer(group,groupdata)   
       
    # 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)   
                        
    c4d.EventAdd()   
  
if __name__=='__main__':   
    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)  
      target.AddUserData(dummybc)  
         
  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:  
          groups.append(UD_ID[1].id)  
            
                      
      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:                 
                 target.AddUserData(grp)                                       
            
          #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"     
                  #etc...           
                 
                  #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))          
  c4d.EventAdd()  
  
if __name__=='__main__':  
  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.

-ScottA