Here is my latest Example Code, I will check for R2023.1 on Monday.
selecting FFFF and DDDD results in GUI weirdness ... on R20
Update: Also on 2023.1 I get this UI glitch ... selecting KKKKK / LLLLLL (last ones)
Update 2: fiddled a little bit with the IDs (the class has an offset now so the ids are unique) and Layoutchange()
no updated on the glitch when selecting the KKKK + X elements.
import c4d # pyright: ignore[reportMissingImports]
import json
import os
from collections import OrderedDict
DEBUG = True
# Ids used in our Dialog
ID_MAINGROUP = 1000 # ID used for the Group that holds all the other group representing the tab content
ID_QUICKTAB_BAR = 1001 # ID for the quicktab customGui
ID_LOADDEFAULT_MAT_ASIGN = 1003
ID_CREATE_MATERIALS = 1004
BUTTON_PRINT_TEXT = 1005 # ID used for the Print text Button
BUTTON_PRINT_SELECTED = 1006 # ID used for the Print Selected Button
BUTTON_FLUSH_ALL = 1007 # ID used for the Flush All Button
BUTTON_ADD = 1008 # ID used for the Add Button
BUTTON_REMOVE = 1009 # ID used for the Remove Button
# Id used in our SubDialog
ID_QUICKTAB_BASE_GROUP = 5000
# Defines the ID for the string to be displayed
CUSTOM_GROUP_ID_TEXT_BASE = 4000
DEFAULT_FLAGS = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT
DEFAULT_SPACE = (5, 5, 5, 5)
DEFAULT_BORDER_STYLE =c4d.BORDER_NONE
LAYOUT_DEBUG = True
if LAYOUT_DEBUG is True:
DEFAULT_BORDER_STYLE = c4d.BORDER_BLACK
GENERATOR_START_ID = 2000
def plusone_id():
n = GENERATOR_START_ID
while n < GENERATOR_START_ID+999:
yield n
n += 1
some_json = '{"AAAAAAA 85": ["a part_num_1","a part_num_2","a part_num_3"],"BBBBB 40": ["b part_num_4","b part_num_5"],"CCCCCCC": ["c part_num_6","c part_num_7","c part_num_8","c part_num_9"],"DDDDDD": ["d part_num_6","d part_num_7","d part_num_8","d part_num_9"],"FFFFF": ["f Plane"],"GGGGGGG": ["g part_num_6","g part_num_7","g part_num_8","g part_num_9"],"HHHHHHHH": ["h part_num_6","h part_num_7","h part_num_8","h part_num_9"],"IIIIII": ["i part_num_6","i part_num_7","i part_num_8","i part_num_9"],"JJJJJJJJJ": ["j part_num_6","j part_num_7","j part_num_8","j part_num_9"],"KKKKKKKK": ["k part_num_6","k part_num_7","k part_num_8","k part_num_9"],"LLLLLLLLL": ["l part_num_6","l part_num_7","l part_num_8","l part_num_9"],"MMMMMM": ["m part_num_6","m part_num_7","m part_num_8","m part_num_9"]}'
### Json
#####################################################################################
def read_parts_json(json_file_path):
#with open(json_file_path) as f: data = json.load(f)
data = json.loads(some_json)
materials = {}
for material_id, part_numbers in data.items():
materials[material_id] = part_numbers
material_ids = list(data.keys())
return material_ids, materials
class CustomGroup(c4d.gui.SubDialog):
"""A SubDialog to display the passed string, its used as example for the actual content of a Tab"""
def __init__(self, material, parts, offset):
self._material = material
self._parts = '\n'.join(parts)
self.gui_id = CUSTOM_GROUP_ID_TEXT_BASE + offset
def CreateLayout(self):
self.GroupBegin(self.gui_id + 4, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1, rows=2, title=self.gui_id)
self.GroupBorder(DEFAULT_BORDER_STYLE)
self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
self.AddStaticText(self.gui_id + 1 , flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_FIT | c4d.BFV_FIT, name=self._material)
#self.AddStaticText(self.gui_id + 2 , flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_FIT | c4d.BFV_FIT, name=self.gui_id)
self.AddMultiLineEditText(self.gui_id + 3, flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, initw=0, inith=0, style=0)
self.SetString(self.gui_id + 3, value=self._parts)
self.GroupEnd()
#for i, parts in enumerate(self._parts):
# self.AddStaticText(self.gui_id + i, c4d.BFH_SCALEFIT, name=parts)
return True
class MyDialog(c4d.gui.GeDialog):
def __init__(self, items=[]):
# this is from the linkbox example from ferdinand
super(MyDialog, self).__init__()
# will be link box
self._items = []
self._doc = None
self._hasCreateLayout = False
self.Items = items
self._quickTab = None # Stores the quicktab custom GUI
self._tabList = OrderedDict() # Stores the TabName and the SubDialog that represents each tab of the QuickTab
def _DrawQuickTabGroup(self):
""" Creates and draws all the SubDialog for each tab,
take care it does not hide these according to a selection state.
Returns:
True if success otherwise False.
"""
# Checks if the quicktab is defined
if self._quickTab is None:
return False
# Flush the content of the group that holds all ours SubDialogs
self.LayoutFlushGroup(ID_MAINGROUP)
#self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
# Iterates over the number of tab to create and attach the correct SubDialog
for tabId, (tabName, tabGui) in enumerate(self._tabList.items()):
self.AddSubDialog(ID_QUICKTAB_BASE_GROUP + tabId, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0)
self.AttachSubDialog(tabGui, ID_QUICKTAB_BASE_GROUP + tabId)
# Notifies the content of the MainGroup has changed
self.LayoutChanged(ID_MAINGROUP)
return True
def GetActiveTabs(self):
"""Retrieves two list of currently selected tabs from the self._quickTab.
Returns:
list(int), list(name): The first list, contains tabs Id (from self._quickTab the dict) and the second list contains all names of the selected tabs.
"""
# Checks if the quicktab is defined
if self._quickTab is None:
return False, False
returnIds = []
returnNames = []
for tabId, (tabName, tabGui) in enumerate(self._tabList.items()):
if self._quickTab.IsSelected(tabId):
returnIds.append(tabId)
returnNames.append(tabName)
return returnIds, returnNames
def DisplayCorrectGroup(self):
"""Hides all unused groups and display the correct one.
Returns:
True if success otherwise False.
"""
# Retrieves the selected tab
activeIds, activeNames = self.GetActiveTabs()
print("Display Correct Group")
print(activeIds)
# Iterates each CustomGui and defines if they are hidden or not
for tabId in range(len(self._tabList)):
toDisplay = tabId in activeIds
#print("activeIds: ", activeIds, "current: ", ID_QUICKTAB_BASE_GROUP, tabId, " -> toDisplay: ", toDisplay)
self.HideElement(ID_QUICKTAB_BASE_GROUP + tabId, not toDisplay)
# Notifies the content of the MainGroup has changed
self.LayoutChanged(ID_MAINGROUP)
return True
def AppendTab(self, tabName, content, active=True):
"""Appends a tab to the current quicktab with the associated content to be displayed.
Args:
tabName (str): The name the tab should have.
content (c4d.gui.SubDialog): The SubDialog to be drawn/linked when the tab is selected.
active (bool, optional): If True, the inserted tab will be selected. Defaults to True.
Returns:
True if success otherwise False.
"""
# Checks if the quicktab is defined
if self._quickTab is None:
return False
# Adds the tab entry n the quicktab
self._quickTab.AppendString(len(self._tabList), tabName, active)
# Updates our current tabList with tabName and the Subdialog to be linked
self._tabList.update({tabName: content})
""" I uncommented below because it resulted in a lot of gui calls
It seems to work fine just calling self.DisplayCorrectGroup() at the right places
not inside here hence this functionis called for each tab
"""
# Retrieves the current selected tab
#previousActiveId, previousActiveName = self.GetActiveTabs()
# Draws the quicktab SubDialog (in order to have the new one drawn)
#self._DrawQuickTabGroup()
# Defines the just added tab according state
#self._quickTab.Select(len(self._tabList) - 1, active)
# Defines previous active tab
#for tabId in previousActiveId: self._quickTab.Select(tabId, True)
# Display only the selected tab and hides all others
#self.DisplayCorrectGroup()
return True
def FlushAllTabs(self):
"""Removes all tabs and their content from the GUI.
Returns:
True if success otherwise False.
"""
# Checks if the quicktab is defined
if self._quickTab is None:
return False
# Removes all the tabs
self._quickTab.ClearStrings()
# Removes all the customGui
for tabId in range(len(self._tabList)):
self.RemoveElement(ID_QUICKTAB_BASE_GROUP + tabId)
# Reinitializes the stored tablist to an empty dict
self._tabList = OrderedDict()
# Flush the content of the group that holds all ours SubDialogs
self.LayoutFlushGroup(ID_MAINGROUP)
# Notifies the content of the MainGroup has changed
self.LayoutChanged(ID_MAINGROUP)
return True
def RemoveTab(self, tabNameToRemove):
"""Removes a tab by its name
Args:
tabNameToRemove (str): The tab to remove.
Returns:
True if success otherwise False.
"""
# Checks if the quicktab is defined
if self._quickTab is None:
return False
# Copies the tabList
newDict = OrderedDict(self._tabList)
# Checks if the entry exist
if tabNameToRemove not in newDict:
return True
# Removes the entry we want to delete
del newDict[tabNameToRemove]
# Removes all groups
self.FlushAllTabs()
# Re-adds all the one from our copy
for tabName, tabGui in newDict.items():
self.AppendTab(tabName, tabGui)
return True
def CreateLayout(self):
"""This Method is called automatically when Cinema 4D Create the Layout (display) of the Dialog."""
# Creates a QuickTab Custom Gui
bc = c4d.BaseContainer()
bc.SetInt32(c4d.QUICKTAB_BAR, 0) # (0=off, 1=on, 2=non-bold, 3=special separator look)
bc.SetString(c4d.QUICKTAB_BARTITLE, "Title")
bc.SetBool(c4d.QUICKTAB_SPRINGINGFOLDERS, True) # if we can get link fields usefull
bc.SetBool(c4d.QUICKTAB_SHOWSINGLE, False)
bc.SetBool(c4d.QUICKTAB_NOMULTISELECT, False)
bc.SetBool(c4d.QUICKTAB_SEPARATOR, True)
self.GroupBegin(next(plusone_id()), c4d.BFH_SCALEFIT | c4d.BFV_TOP | c4d.BFV_FIT, 0, 0, 'ID_QUICKTAB_BAR', 0)
#self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
self.GroupBorder(DEFAULT_BORDER_STYLE)
self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
self._quickTab = self.AddCustomGui(ID_QUICKTAB_BAR, c4d.CUSTOMGUI_QUICKTAB, 'the tabs', c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, bc)
#self.AddStaticText(id=next(plusone_id()), flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_CENTER | c4d.BFH_SCALE, name="tabs")
#print("layout cusrom gui: ", self._quickTab)
self.GroupEnd()
# Creates a group that will contain all the group representing each tab
self.GroupBegin(ID_MAINGROUP, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=10, rows=10, title='ID_MAINGROUP')
#self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
self.GroupBorder(DEFAULT_BORDER_STYLE)
self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
self.AddStaticText(id=next(plusone_id()), flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_CENTER | c4d.BFH_SCALE, name="main")
self.GroupEnd()
# dummy group to spread vertically
if self.GroupBegin(id=next(plusone_id()), flags=c4d.BFH_FIT | c4d.BFV_FIT): #cols=1, rows=1,
self.GroupBorderSpace(left=0, top=0, right=0, bottom=0)
self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
self.AddStaticText(id=next(plusone_id()), flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_CENTER | c4d.BFH_SCALE, name="")
self.GroupEnd()
# Creates a group with button in order to do some operation with the QuickTab CustomGUI
if self.GroupBegin(next(plusone_id()), c4d.BFH_SCALEFIT | c4d.BFV_BOTTOM, 4, 1, '', 0):
self.AddButton(ID_LOADDEFAULT_MAT_ASIGN, c4d.BFH_SCALEFIT, name="Populate")
self.AddButton(BUTTON_PRINT_TEXT, c4d.BFH_SCALEFIT, name="Print text")
self.AddButton(BUTTON_PRINT_SELECTED, c4d.BFH_SCALEFIT, name="Print Selected")
self.AddButton(BUTTON_FLUSH_ALL, c4d.BFH_SCALEFIT, name="Flush All")
#self.AddButton(BUTTON_ADD, c4d.BFH_SCALEFIT, name="Add")
#self.AddButton(BUTTON_REMOVE, c4d.BFH_SCALEFIT, name="Remove")
self.GroupEnd()
return True
def InitValues(self):
"""This Method is called automatically after the GUI is initialized."""
# Creates the first Tab
#cg1 = CustomGroup(["This is the first Tab", "Just dummy text here"])
#self.AppendTab("First Tab", cg1, True)
# Creates the second Tab
#cg2 = CustomGroup(["This is the second Tab", "Just another dummy text here"])
#self.AppendTab("Second Tab", cg2, False)
#return True
return super(MyDialog, self).InitValues()
def Command(self, id, msg):
"""This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called.
It is also called when a string menu item is selected.
Args:
id: The ID of the gadget that triggered the event.
msg: The original message container
Returns:
False if there was an error, otherwise True.
"""
# If the user interacts with the quicktab, we make sure to display the CustomGUI linked to the active one
if id == ID_QUICKTAB_BAR and self._quickTab:
print("user interacted with tab")
self.DisplayCorrectGroup()
c4d.EventAdd()
return True
# Displays all the Tab name
if id == BUTTON_PRINT_TEXT:
print([key for key in self._tabList])
return True
# Displays the ID and name of the selected tab
if id == BUTTON_PRINT_SELECTED:
print(self.GetActiveTabs())
# Removes all tabs
if id == BUTTON_FLUSH_ALL:
self.FlushAllTabs()
"""
# Adds a new Tab to the quicktab
if id == BUTTON_ADD:
cg3 = CustomGroup(["This is the third Tab"])
self.AppendTab("Third Tab", cg3, True)
# Removes the first tab of the quicktab
if id == BUTTON_REMOVE:
self.RemoveTab("First Tab")
"""
if id == ID_LOADDEFAULT_MAT_ASIGN:
self.Populate()
return True
def Populate(self):
doc = c4d.documents.GetActiveDocument()
directory, _ = os.path.split(__file__)
poart_json_file = os.path.join(directory, "default_asignment.json")
material_ids, materials_dict = read_parts_json(poart_json_file)
self.FlushAllTabs() # clear the GUI
print( str( material_ids))
print("-"*80)
offset=0
for material_id in materials_dict:
part_list = materials_dict[material_id]
#print( str(material_id), str(part_list) )
### Tabs
##############################################
tab_Content = CustomGroup(str(material_id), part_list, offset)
self.AppendTab(str(material_id), tab_Content, True)
offset += 10
print("-"*80)
self._DrawQuickTabGroup()
c4d.EventAdd()
# Main function
def main():
if DEBUG:
c4d.CallCommand(13957) # clear console
# Initializes a QuickTabDialogExample Dialog
diag = MyDialog()
# Opens the Dialog in modal mode
diag.Open(dlgtype=c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=960, defaulth=600)
# Execute main()
if __name__ == '__main__':
main()