Working with UserData Python
-
Really weird results.
Okay so I have these functions am using to create this user data's .
def CreateUserDataGroup(obj, name, parentGroup=None, columns=None, shortname=None, defaultopen=None, scale=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_DEFAULT] = 0 bc[c4d.DESC_TITLEBAR] = 1 bc[c4d.DESC_GROUPSCALEV] = 0 if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if columns is not None: #DESC_COLUMNS VALUE IS WRONG IN 15.057 - SHOULD BE 22 bc[22] = columns if defaultopen != None: bc[c4d.DESC_DEFAULT] = 1 if scale != None: bc[c4d.DESC_GROUPSCALEV] = 1 return obj.AddUserData(bc) def CreateUserDataLink(obj, name, link, parentGroup=None, shortname=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_BASELISTLINK) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname if link is None: pass else: bc[c4d.DESC_DEFAULT] = link bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_OFF bc[c4d.DESC_SHADERLINKFLAG] = True if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = link return element def CreateUserDataInteger(obj, name, parentGroup=None, shortname=None, text=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_ANIMATE] = 0 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_CYCLEBUTTON if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if text is not None: #Create a list of names and put them into a container names = c4d.BaseContainer() # for x in text.split(","): count = 0 for x in text: names.SetString(int(count), str(x)) count += 1 bc.SetContainer(c4d.DESC_CYCLE, names) bc[c4d.DESC_DEFAULT] = 0 element = obj.AddUserData(bc) return element def CreateUserDataString(obj, name, parentGroup=None, shortname=None, text=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_STRING) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_ANIMATE] = 0 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_STRINGMULTI if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if text is not None: bc[c4d.DESC_DEFAULT] = text element = obj.AddUserData(bc) return element
but when I do Groups work fine so do links But all the others are glitched out.
Until I manually go into the user data and fix make a random edit then the integer works But as you can see in the gif Multiline string does not get fixed as shown in the image below
I still plan on adding "Scale V" to the string Userdata so help on that would be helpful
Below is the Simplified version of my code if you just want to copy and paste as see it for yourself
import c4d from c4d import gui from random import randint # Welcome to the world of Python def randomColor(): r = randint(0,255) / 256.0 g = randint(0,255) / 256.0 b = randint(0,255) / 256.0 color = c4d.Vector(r,g,b) return color def CreateUserDataGroup(obj, name, parentGroup=None, columns=None, shortname=None, defaultopen=None, scale=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_DEFAULT] = 0 bc[c4d.DESC_TITLEBAR] = 1 bc[c4d.DESC_GROUPSCALEV] = 0 if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if columns is not None: #DESC_COLUMNS VALUE IS WRONG IN 15.057 - SHOULD BE 22 bc[22] = columns if defaultopen != None: bc[c4d.DESC_DEFAULT] = 1 if scale != None: bc[c4d.DESC_GROUPSCALEV] = 1 return obj.AddUserData(bc) def CreateUserDataLink(obj, name, link, parentGroup=None, shortname=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_BASELISTLINK) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname if link is None: pass else: bc[c4d.DESC_DEFAULT] = link bc[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_OFF bc[c4d.DESC_SHADERLINKFLAG] = True if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup element = obj.AddUserData(bc) obj[element] = link return element def CreateUserDataInteger(obj, name, parentGroup=None, shortname=None, text=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_ANIMATE] = 0 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_CYCLEBUTTON if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if text is not None: #Create a list of names and put them into a container names = c4d.BaseContainer() # for x in text.split(","): count = 0 for x in text: names.SetString(int(count), str(x)) count += 1 bc.SetContainer(c4d.DESC_CYCLE, names) bc[c4d.DESC_DEFAULT] = 0 element = obj.AddUserData(bc) return element def CreateUserDataString(obj, name, parentGroup=None, shortname=None, text=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_STRING) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_ANIMATE] = 0 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_STRINGMULTI if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if text is not None: bc[c4d.DESC_DEFAULT] = text element = obj.AddUserData(bc) return element def CreateUserDataPopUP(obj, name, parentGroup=None, shortname=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_POPUP) def SmartCheckSelectedObjs(): objs = doc.GetActiveObjects(1) if objs is None: return None elif len(objs) == 2: print ("In here 222") fixedobjs = [] FoundAParent = False count = 0 for x in objs: GetParent = x if x.GetUp() != None: while GetParent != None: count = count + 1 if count == 100: print ("Count break: " + count) print ("Inside whileLOop") GetParent = GetParent.GetUp() print (GetParent) # count = count + 1 if GetParent == None: print ("Yesttt ") else: GetParentPrev = GetParent # print ("This should neva happen") FoundAParent =True if FoundAParent == True: fixedobjs.append(GetParentPrev) else: fixedobjs.append(x) else: fixedobjs.append(x) for elem in fixedobjs: if fixedobjs.count(elem) > 1: print ("Duplicate found") return None return fixedobjs elif len(objs) <= 2 or len(objs) > 15: return None else: print ("Fuck it all broken") def main(): CheckRun = doc.SearchObject("EaZyRetarget") if CheckRun == None: CreateER=c4d.BaseObject(c4d.Onull) CreateER[c4d.ID_BASELIST_NAME]="EaZyRetarget" CreateER()[c4d.ID_BASELIST_ICON_FILE] = "300000157" CreateER[c4d.ID_BASELIST_ICON_COLORIZE_MODE] = 1 CreateER()[c4d.ID_BASELIST_ICON_COLOR] = randomColor() doc.InsertObject(CreateER) layerGroup = CreateUserDataGroup(CreateER,"EaZyRetargetMain",c4d.DescID(0), "EaZyRetarget", defaultopen=True, scale=True) FsubGroup = CreateUserDataGroup(CreateER,"From",layerGroup,2,defaultopen=True) ListOfSupportedRigTypes = [ " EaZyRig_Game\n" "1; EaZYRig_Standard\n" "2; EaZYRig_Full\n" "-1; " "3; AccuRig\n" "4; Mixamo\n" "5; Rococo\n" "6; Cascadeur\n" "7; UE5\n" "8; Custom\n" ] fromCycleButton = CreateUserDataInteger(CreateER,"From",FsubGroup, None, ListOfSupportedRigTypes) try: print ("IN try") checkobjs = SmartCheckSelectedObjs()[0] print (checkobjs) print ("No try error") except: print ("Yeppp totally") checkobjs = SmartCheckSelectedObjs() # checkobjs = None fromCycleLink = CreateUserDataLink(CreateER,"From", checkobjs,FsubGroup, None) TsubGroup1 = CreateUserDataGroup(CreateER,"To",layerGroup,2, defaultopen=True) ToCyclebutton = CreateUserDataInteger(CreateER,"To",TsubGroup1, None, ListOfSupportedRigTypes) try: checkobjs = SmartCheckSelectedObjs()[1] print (checkobjs) except: checkobjs = SmartCheckSelectedObjs() # checkobjs = None ToCycleLink = CreateUserDataLink(CreateER,"To", checkobjs,TsubGroup1, None) QUickGuidesubGroup = CreateUserDataGroup(CreateER,"Quick Guide for Custom",layerGroup,2,defaultopen=False,scale=True) msg = "WIP Disclamer:\n This could go Bad really Really Fast\n\n \ You want to make sure the Same Number of\n \ lines are avail on both include & exclude.\n\n\ If there is nothing to exclude just make that line\n\ a None in the exclude userdata" QuickGuideVar = CreateUserDataString(CreateER,"Quick Guide for Custom",QUickGuidesubGroup,msg) CustomsubGroup1 = CreateUserDataGroup(CreateER,"Custom Rigs",layerGroup,2, defaultopen=False,scale=True) # bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_GROUP) # Create default container # bc[c4d.DESC_NAME] = "EaZyRetargetMain" # bc[c4d.DESC_SHORT_NAME] = "EaZyRetarget" # bc[c4d.DESC_VERSION] = 3 # bc[c4d.DESC_ANIMATE] = 0 # bc[c4d.DESC_COLUMNS] = 1 # bc[c4d.DESC_TITLEBAR] = True # bc[c4d.DESC_DEFAULT] = 1 # # pass empty DescID # bc[c4d.DESC_PARENTGROUP] = c4d.DescID() # element = CreateER.AddUserData(bc) # Add userdata container # bc1 = c4d.GetCustomDataTypeDefault(c4d.DTYPE_STRING) # Create default container # bc1[c4d.DESC_NAME] = "Name" # Rename the entry # bc1[c4d.DESC_ANIMATE] = 0 # bc1[c4d.DESC_PARENTGROUP] = element message = "I have created a simple 'EaZyRetarget' object.\n\n\ All you need to do is drag the Parent of your Rig 'DOes not matter if it is a \ Null or your hipBone or the Rootbone' Inside the 'From' and 'To' link userdata\n\n\ and Rerun this Script We will work the work for you " gui.MessageDialog(message) else: print("AAAAA") # for descId, bc in CheckRun.GetUserDataContainer(): # print ("*"*5,descId,"*"*5) # for key, val in bc: # for txt, i in c4d.__dict__.iteritems(): # if key == i and txt[:5]=="DESC_": # print (txt,"=",val) # break # else: # print (key,"=",val) # Execute main() if __name__=='__main__': main() c4d.EventAdd()
-
Hello @eazy5d,
Thank you for reaching out to us.
Your Problem
I am not quite sure what you consider going wrong in your example since you clearly state what is not working for you. You are talking about a "multiline string [...] not get[ing] fixed", I assume you are talking here about the indentations in the multi-line string you are using for your "help section". This is caused by how you define the string in Python. Defining a string with the concatenation character "" to split the string definition over multiple lines will include all characters of the next line, e.g., this
msg_a: str = "Bob is your uncle, \ and Mary is your aunt." msg_b: str = "Bob is your uncle, \ and Mary is your aunt." print(f"{msg_a = }") print(f"{msg_b = }")
will output this:
msg_a = 'Bob is your uncle, and Mary is your aunt.' msg_b = 'Bob is your uncle, and Mary is your aunt.'
You are unintentionally including white space characters in this manner in your
msg
variable definition which then will be reflected in the GUI. To fix this, you must either remove all white space characters as shown formsg_b
in my example or use other forms of defining strings.# Python multi line strings suffer from the same problem but have implicit line breaks. msg: str = """Bob is your uncle, and Mary your aunt.""" print(msg) # Join a string using the implicit concatenation syntax. msg: str = ("Bob is your uncle,\n" "and Mary your aunt.") print(msg) # Join a string using the explicit concatenation syntax. msg: str = ("Bob is your uncle,\n" + "and Mary your aunt.") print(msg)
These three will all print the same as:
Bob is your uncle, and Mary your aunt. Bob is your uncle, and Mary your aunt. Bob is your uncle, and Mary your aunt. [Finished in 65ms]
Other Problems
You also seem to generate a malformed user data container, because this is how your setup looks for me (notice the misplaced labels).
I have not debugged your code for you here, please provide an isolated example when you require further help. We cannot debug your code for you. In case you are interested, I have also shown here how you can build a Python scripting object user data GUI from inside the object. Which will mean that you can just create the object, load the code/preset and are ready to go, opposed to the intermediate step of the script manager script.
Cheers,
Ferdinand -
@ferdinand I tried editing my post before, as I was able to get most of it working.
What I consider wrong is not the Indentations in the multi-line string, What I mean Is that I am trying to make this Whole Multiline String Gui
Using this code below Which I think is okay#it's Isolated now
LIne 13-28 and line 51 are all you really need to look atimport c4d from c4d import gui from random import randint # Welcome to the world of Python def randomColor(): r = randint(0,255) / 256.0 g = randint(0,255) / 256.0 b = randint(0,255) / 256.0 color = c4d.Vector(r,g,b) return color def CreateUserDataString(obj, name, parentGroup=None, shortname=None, text=None): if obj is None: return False if shortname is None: shortname = name bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_STRING) bc[c4d.DESC_NAME] = name bc[c4d.DESC_SHORT_NAME] = shortname bc[c4d.DESC_ANIMATE] = 0 bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_STRINGMULTI if parentGroup is not None: bc[c4d.DESC_PARENTGROUP] = parentGroup if text is not None: bc[c4d.DESC_DEFAULT] = text element = obj.AddUserData(bc) return element # Main function def main(): CheckRun = doc.SearchObject("EaZyRetarget") if CheckRun == None: CreateER=c4d.BaseObject(c4d.Onull) CreateER[c4d.ID_BASELIST_NAME]="EaZyRetarget" CreateER()[c4d.ID_BASELIST_ICON_FILE] = "300000157" CreateER[c4d.ID_BASELIST_ICON_COLORIZE_MODE] = 1 CreateER()[c4d.ID_BASELIST_ICON_COLOR] = randomColor() doc.InsertObject(CreateER) msg = "WIP Disclamer:\n This could go Bad really Really Fast\n\n \ You want to make sure the Same Number of\n \ lines are avail on both include & exclude.\n\n\ If there is nothing to exclude just make that line\n\ a None in the exclude user data" QuickGuideVar = CreateUserDataString(CreateER,"Quick Guide for Custom",None,msg) message = "I have created a simple 'EaZyRetarget' object.\n\n\ All you need to do is drag the Parent of your Rig 'DOes not matter if it is a \ Null or your hipBone or the Rootbone' Inside the 'From' and 'To' link userdata\n\n\ and Rerun this Script We will work the work for you " gui.MessageDialog(message) else: print ("Don't Know don't care") if __name__=='__main__': main() c4d.EventAdd()
Here is what I get outputted the whole thing is not useable
An isolated code, Expected Behaviour, the Behaviour am getting.
-
Hello @eazy5d,
Thank you for your update code. You still did not provide a clear description of what you wanted to happen, at least for me the statement "what I mean Is that I am trying to make this whole multiline string GUI" does not really clarify what was going wrong for you.
I now have debugged your code for you, which is something we usually do not do. Please note that this is an exception. There were three differences in the screenshots provided by you.
- The in-code created element has an exceptionally long label, the manually created one a short label.
- The in-code created element has the empty string as its value, the manually created one has a longer string as its value.
- The in-code created element uses the standard Multi-Line String GUI, the manually created one the Python Multi-Line String GUI.
This was caused by three issues:
- You were calling your own function
CreateUserDataString
incorrectly, passing the (default) value of the element as its label. - You misunderstood the purpose of
DESC_DEFAULT
; it will not set the value of the element, it will only define what value the element takes when 'Reset to Default' is being invoked on it. - You did not set the
DR_MULTILINE_PYTHON
flag to turn on the Python GUI.
Find below an annotated and stripped version of your script.
Cheers,
FerdinandThe result of the script:
The code:"""Contains your stripped script with annotations. """ import c4d doc: c4d.documents.BaseDocument def CreateUserDataString(obj: c4d.BaseList2D, name: str, parentGroup: c4d.DescID = None, shortname: str = None, text: str = None, isPython: bool = False) -> c4d.DescID: """Creates a multi-line edit user data element on #obj and returns its DescID. """ if obj is None: return False bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_STRING) bc[c4d.DESC_NAME] = name # Using Python's short-hand fall-through syntax: # Write #shortname to DESC_SHORT_NAME when shortname != None, else write #name. bc[c4d.DESC_SHORT_NAME] = shortname or name bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_STRINGMULTI bc[c4d.DESC_PARENTGROUP] = parentGroup # Setting the default value in the parameter description will not set the value of the # parameter. bc[c4d.DESC_DEFAULT] = text or "" # Set if the MLE should use the Python GUI or not, i.e., a mono space layout with Python # syntax highlighting. bc[c4d.DR_MULTILINE_PYTHON] = isPython # Since #AddUserData cannot fail, it will always return a DescID, we can return the return value # of the method directly. return obj.AddUserData(bc) def main(): """ """ # I have cleaned up a bit to make things more readable by removing irrelevant stuff. null: c4d.BaseObject = c4d.BaseObject(c4d.Onull) # The label and the value for the multi-line edit. I have named the value #text because # that is how you named it in your function #CreateUserDataString. label: str = "Quick Guide for Custom" text: str = "Bob is your uncle!\n" * 5 # You are here unintentionally violating your own function signature. Your call sets # shortname=text and lets the default value kick in for #text. Your function expects five # arguments in total, you are only passing four. You are also misunderstanding the purpose # of #DESC_DEFAULT. Since you never make use of the returned DescID, the value for this MLE # is not being set. CreateUserDataString(null, label, None, text) # You probably meant to do this call instead, here #text will be set as the default value. # I am also passing in the sixth argument I have added, to make the MLE a Python MLE as in # your screenshots. descId: c4d.DescID = CreateUserDataString(null, label, None, None, text, isPython=True) # Write a new value to the parameter. null[descId] = text doc.InsertObject(null) c4d.EventAdd() if __name__ == '__main__': main()
-
Humm thank you for the exception I just noticed I stopped my explanation at "GUI" Which totally was not my intention but also must have been frustrating for you. Again Thank you Really good support
-
Hey @eazy5d,
no worries. We understand that asking clear technical questions can be a non-trivial task, as oneself is already "in the picture" and it is then easy to overlook essential information. To effectively answer questions, the SDK Group must know this essential information, which is why we are explicitly asking for it in most threads. This might seem a bit robotic or pedantic from time to time, but is done in the best interest of users, as experience shows that guessing that information often leads to miscommunication.
Cheers,
Ferdinand -
Thank you once again I marked as solved