Navigation

    • Register
    • Login
    • Search
    1. Home
    2. mogh
    3. Posts
    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups

    Posts made by mogh

    RE: Quicktab SDK Example

    Ok Maxime, thanks for the heads up,

    I might just display all all the time ....
    or build a Classic Tab version ... (is there an example ?)

    Barebone classic Tab:

    import c4d
    
    DEBUG = True
    
    DEFAULT_FLAGS = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT
    DEFAULT_SPACE = (5, 5, 5, 5)
    DEFAULT_BORDER_STYLE = c4d.BORDER_NONE
    
    DEFAULT_TEXT_FLAGS = c4d.BFV_CENTER | c4d.BFV_FIT | c4d.BFH_CENTER | c4d.BFH_FIT
    
    class MyDialog(c4d.gui.GeDialog):
    
        def CreateLayout(self):
            
            if self.GroupBegin(id=950,  flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT):
                self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
            
                if self.TabGroupBegin(id=1000, flags=c4d.BFH_LEFT | c4d.BFH_SCALEFIT | c4d.BFV_CENTER | c4d.BFV_SCALEFIT, tabtype=c4d.TAB_TABS):
                    self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
        
                    if self.GroupBegin(id=1001, cols=1, rows=1, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, title="Tab 1"):
                        self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                        self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
                        self.AddStaticText(id=2001, flags=DEFAULT_TEXT_FLAGS, name="some content 1")
                        self.GroupEnd()
                    
                    if self.GroupBegin(id=1002, cols=1, rows=1, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, title="Tab 2"):
                        self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                        self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
                        self.AddStaticText(id=2002, flags=DEFAULT_TEXT_FLAGS, name="some content 2")
                        self.GroupEnd()        
                    
                    if self.GroupBegin(id=1003, cols=1, rows=1, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, title="Tab 3 long name for test"):
                        self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
                        self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
                        self.AddStaticText(id=2003, flags=DEFAULT_TEXT_FLAGS, name="some content 3")
                        self.GroupEnd()                    
    
                    self.GroupEnd()    # tab group
    
                self.GroupEnd() # main window
    
            return True
    
    
    def main():
        
        if DEBUG:
            c4d.CallCommand(13957)  # clear console
        
        diag = MyDialog()
    
        diag.Open(dlgtype=c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=960, defaulth=600)
    
    
    if __name__ == '__main__':
        main()
    

    kind regards ...
    mogh

    posted in Cinema 4D SDK •
    RE: Quicktab SDK Example

    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.

    2023-03-27-Window_000269.png

    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()
    
    posted in Cinema 4D SDK •
    RE: Quicktab SDK Example

    Hi Maxime,

    GUI disintegration mostly happening when selecting first and last Tab ...

    tabs_gui_dissintegration_2.png

    tabs_gui_dissintegration.png

    I am troubleshooting this thing for 5 hours now and the outcome seems to stay the same, I slim-lined the code to draw less gui refreshs, printed the ids / tab names/ and content in several places, but the code and underlying list do not indicate CCCC on ID 10 - So it seems only the TABs are wrong not the data structure ...

    # json list
    ['KKKKKKKK', 'BBBBB 40', 'LLLLLLLLL', 'HHHHHHHH', 'GGGGGGG', 'IIIIII', 'JJJJJJJJJ', 'CCCCCCC', 'AAAAAAA 85', 'FFFFF', 'DDDDDD']
    --------------------------------------------------------------------------------
    #AppendTab
    _tabList:
    {'KKKKKKKK': <__main__.CustomGroup object at 0x000001FBD74848B8>, 'BBBBB 40': <__main__.CustomGroup object at 0x000001FBD7484948>, 'LLLLLLLLL': <__main__.CustomGroup object at 0x000001FBD7484A68>, 'HHHHHHHH': <__main__.CustomGroup object at 0x000001FBD74849D8>, 'GGGGGGG': <__main__.CustomGroup object at 0x000001FBD7484990>, 'IIIIII': <__main__.CustomGroup object at 0x000001FBD7484A20>, 'JJJJJJJJJ': <__main__.CustomGroup object at 0x000001FBD7C23C60>, 'CCCCCCC': <__main__.CustomGroup object at 0x000001FBDF91FCA8>, 'AAAAAAA 85': <__main__.CustomGroup object at 0x000001FBD7C23CF0>, 'FFFFF': <__main__.CustomGroup object at 0x000001FBDF91FC60>, 'DDDDDD': <__main__.CustomGroup object at 0x000001FBD7C23D80>}
    --------------------------------------------------------------------------------
    #_DrawQuickTabGroup
    (0, 'KKKKKKKK', ' layout inserted subdialog id-> ', 4000)
    (1, 'BBBBB 40', ' layout inserted subdialog id-> ', 4001)
    (2, 'LLLLLLLLL', ' layout inserted subdialog id-> ', 4002)
    (3, 'HHHHHHHH', ' layout inserted subdialog id-> ', 4003)
    (4, 'GGGGGGG', ' layout inserted subdialog id-> ', 4004)
    (5, 'IIIIII', ' layout inserted subdialog id-> ', 4005)
    (6, 'JJJJJJJJJ', ' layout inserted subdialog id-> ', 4006)
    (7, 'CCCCCCC', ' layout inserted subdialog id-> ', 4007)
    (8, 'AAAAAAA 85', ' layout inserted subdialog id-> ', 4008)
    (9, 'FFFFF', ' layout inserted subdialog id-> ', 4009)
    (10, 'DDDDDD', ' layout inserted subdialog id-> ', 4010)
    
    # GetActiveTabs for loop
    (0, 'KKKKKKKK')
    (1, 'BBBBB 40')
    (2, 'LLLLLLLLL')
    (3, 'HHHHHHHH')
    (4, 'GGGGGGG')
    (5, 'IIIIII')
    (6, 'JJJJJJJJJ')
    (7, 'CCCCCCC')
    (8, 'AAAAAAA 85')
    (9, 'FFFFF')
    (10, 'DDDDDD')
    
    ('GetActiveTabs id: ', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    ('GetActiveTabs names: ', ['KKKKKKKK', 'BBBBB 40', 'LLLLLLLLL', 'HHHHHHHH', 'GGGGGGG', 'IIIIII', 'JJJJJJJJJ', 'CCCCCCC', 'AAAAAAA 85', 'FFFFF', 'DDDDDD'])
    

    a few moments ago I tested briefly OrderedDict() and tuple with no change in outcome ...

    EDIT: ok so i missed the flushing, changing every {} to self._tabList = OrderedDict() seems top work - The GUI Problems (screenshot) remain ...

    I had the idea to link to # instead of names but that is not what the SDK example does it takes the names ?

    I am at 2023.1 at work and in my free time on R20.059
    I checked 2023.1 before I pester you here because I know you will limit support otherwise 🤷

    thank you for your time.
    mogh

    posted in Cinema 4D SDK •
    RE: Export fbx of only selected objects

    You probably still using the PuginID (chatGPT gibberisch) look at the above mentioned savedoc() and compare it to yours .... I testet it just now and it works for me ...

    posted in Cinema 4D SDK •
    RE: Export fbx of only selected objects

    Even though my answer might not be as perfect as the C4D teams I 'll jump in here.

    You are passing 5 arguments (noob speek 5 variables in the brakets ) to a function that only takes 4 -> SaveDocument ... as you can see here it needs only 4 the fbx settings are stored they re not passed as I understand.

    Noob Gotcha: You can combine flags as a single argument with the pipe | symbol

    # this is one argument
    flags=c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST | c4d.SAVEDOCUMENTFLAGS_EXPORTDIALOG
    

    as a beginner i find it helpful to write the function with its parameters

    # i did not test this ... 
    c4d.documents.SaveDocument(doc=doc, name=fbx_path, saveflags=c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, 
                               format=c4d.FORMAT_FBX_EXPORT)
    

    ChatGPT gives better results if you prompt it to give you a single python function. With C4D. specific stuff it often cobbles 10 year old code with new sdk stuff.... resulting in a weird code

    hope this helps ----
    cheers mogh

    posted in Cinema 4D SDK •
    RE: Quicktab SDK Example

    I am sorry, I do not want to come across as lacy but it is more or less the sdk example thats why I asked so sparse.

    I numerated the IDS from top to bottom that solved the FFFFF unchecking but there is still GUI disintegration and the switch between CCCC and DDDD

    The occasional supper and so is the prework for the link boxes I want to integrate (copied from ferdinands recent GUI example here in the forum)

    kind regards mogh

    """
    Copyright: MAXON Computer GmbH
    Author: Maxime Adam
    
    Description:
        - Creates a Modal Dialog displaying a different SubDialog according to the selected entry of the QuickTab.
        - Demonstrates how to add, flushes, remove tab interactively.
    
    Class/method highlighted:
        - c4d.gui.QuickTabCustomGui
        - QuickTabCustomGui.ClearStrings()
        - QuickTabCustomGui.AppendString()
        - c4d.gui.GeDialog
        - GeDialog.CreateLayout()
        - GeDialog.InitValues()
        - GeDialog.Command()
        - GeDialog.HideElement()
        - GeDialog.RemoveElement()
        - c4d.gui.SubDialog
    
    """
    import c4d # pyright: ignore[reportMissingImports]
    import json
    import os
    
    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_QUICKTAB_BASE_GROUP = 1002  # Base ID for each SubDialog
    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
    CUSTOM_GROUP_ID_TEXT_BASE = 4000  # Defines the ID for the string to be displayed
    
    LAYOUT_DEBUG = True
    DEFAULT_BORDER_STYLE =c4d.BORDER_NONE
    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
    
    somne_json = '{"AAAAAAA 85": ["a part_number_1","a part_number_2","a part_number_3"],"BBBBB 40": ["b part_number_4","b part_number_5"],"CCCCCCC": ["c part_number_6","c part_number_7","c part_number_8","c part_number_9"],"DDDDDD": ["d part_number_6","d part_number_7","d part_number_8","d part_number_9"],"FFFFF": ["f Plane"],"GGGGGGG": ["g part_number_6","g part_number_7","g part_number_8","g part_number_9"],"HHHHHHHH": ["h part_number_6","h part_number_7","h part_number_8","h part_number_9"],"IIIIII": ["i part_number_6","i part_number_7","i part_number_8","i part_number_9"],"JJJJJJJJJ": ["j part_number_6","j part_number_7","j part_number_8","j part_number_9"],"KKKKKKKK": ["k part_number_6","k part_number_7","k part_number_8","k part_number_9"],"LLLLLLLLL": ["l part_number_6","l part_number_7","l part_number_8","l part_number_9"]}'
    
    ### Json
    #####################################################################################
    
    def read_parts_json(json_file_path):
        #with open(json_file_path) as f: data = json.load(f)
        
        data = json.loads(somne_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):
    
        DEFAULT_FLAGS = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT
        DEFAULT_SPACE = (5, 5, 5, 5)
        
        """A SubDialog to display the passed string, its used as example for the actual content of a Tab"""
        def __init__(self, material, parts):
    
            self._material = material
            self._parts = '\n'.join(parts)
    
        def CreateLayout(self):
            
            #self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
            self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + 1 , flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_FIT | c4d.BFV_FIT, name=self._material)
            self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + 2 , flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_FIT | c4d.BFV_FIT, name=CUSTOM_GROUP_ID_TEXT_BASE)
            self.AddMultiLineEditText(CUSTOM_GROUP_ID_TEXT_BASE + 3, flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, initw=0, inith=0, style=0)
            self.SetString(CUSTOM_GROUP_ID_TEXT_BASE + 3, value=self._parts)
    
            #for i, parts in enumerate(self._parts):
            #    self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + i, c4d.BFH_SCALEFIT, name=parts)
            
            return True
    
    
    class MyDialog(c4d.gui.GeDialog):
    
        DEFAULT_FLAGS = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT
        DEFAULT_SPACE = (5, 5, 5, 5)
    
        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 = {}  # 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()
    
            # Iterates each CustomGui and defines if they are hidden or not
            for tabId in range(len(self._tabList)):
                toDisplay = tabId in activeIds
                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, False)
    
            # Updates our current tabList with tabName and the Subdialog to be linked
            self._tabList.update({tabName: content})
    
            # 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 = {}
    
            # 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 = dict(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.SetBool(c4d.QUICKTAB_BAR, False)
            #bc.SetBool(c4d.QUICKTAB_SPRINGINGFOLDERS, True) # if we can get link fields usefull
            bc.SetBool(c4d.QUICKTAB_SHOWSINGLE, True)
            bc.SetBool(c4d.QUICKTAB_NOMULTISELECT, False)
    
            self.GroupBegin(next(plusone_id()), c4d.BFH_SCALEFIT | c4d.BFV_TOP | c4d.BFV_FIT, 0, 0, '', 0)
            self._quickTab = self.AddCustomGui(ID_QUICKTAB_BAR, c4d.CUSTOMGUI_QUICKTAB, '', c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, bc)
            self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
            self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
            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='')
            self.GroupBorderNoTitle(borderstyle=DEFAULT_BORDER_STYLE)
            self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
            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, 6, 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:
                self.DisplayCorrectGroup()
                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)
    
            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)
                self.AppendTab(str(material_id), tab_Content, False)
            
            print("-"*80)
            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=400, defaulth=400)
    
    # Execute main()
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK •
    Quicktab SDK Example

    Dear Developpers,

    I am trying to implement a Quicktab Gui into my Plugin. While i get some User interface that is somehow usable, I see some content getting mixed up in the gui after multi selecting some tabs.

    It might be a simple thing as an ID not matching, but I am a little bit lost hence its basically just the SDK example i use.

    quicktabgui.png

    as you can see FFFF is deselected but shown
    DDDD and CCCC are switched up ....
    and no its not just a row shuffle .. deselecting DDD hides CCC
    deselecting more result in more gui disintegration ...

    Can you confirm that applying more content results in some weird stuff? Or what is your hunch .,... where to look ?

    Thank you for your time.
    cheers mogh

    only little was added .... rest is basically the SDK example ....

    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):
            self._material = material
            self._parts = '\n'.join(parts)
    
        def CreateLayout(self):
            
            
            self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + 1 , flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_FIT | c4d.BFV_FIT, name=self._material)
            self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + 2 , flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_FIT | c4d.BFV_FIT, name=CUSTOM_GROUP_ID_TEXT_BASE)
            self.AddMultiLineEditText(CUSTOM_GROUP_ID_TEXT_BASE + 3, flags=c4d.BFH_LEFT | c4d.BFV_TOP | c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, initw=0, inith=0, style=c4d.DR_MULTILINE_WORDWRAP)
            self.SetString(CUSTOM_GROUP_ID_TEXT_BASE + 3, value=self._parts)
    
            #for i, parts in enumerate(self._parts):
            #    self.AddStaticText(CUSTOM_GROUP_ID_TEXT_BASE + i, c4d.BFH_SCALEFIT, name=parts)
            
            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)
    
            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)
                self.AppendTab(str(material_id), tab_Content, False)
    posted in Cinema 4D SDK •
    RE: How can I change the order of Reflection layers

    Oh my, this is horrible 😉

    ...
    cheers mogh

    posted in Cinema 4D SDK •
    RE: Color accuracy 32 bit - how to archive - things to look out for ...

    Thanks, @ferdinand

    Yes seems intended but could be troublesome if you use mograph / redshift object color to drive something ?

    C4D Version : 2023.1.2

    A solution to guide the user could be to disable the object color when set to "Render Instance" , but from a UX standpoint I am not a fan of taking away control from the user.

    thanks for taking it to CS
    kind regards mogh

    posted in General Talk •
    RE: Color accuracy 32 bit - how to archive - things to look out for ...

    Hi @ferdinand ,

    I might not come up with a striped down code quickly, I can tell you that i render scenes without any materials just display color ... everything set to linear 32 bit as far as I could cobble together myself ... nothing special because I code R20 and 2023.

    I suspected something like:
    Render Instance is accessed in memory (and the shifted because of internals) and then rendered wrong with the color of the original polygon.
    "Classic" Instance - Polygon is copied into place and inherits the correct color from the Classic Instance

    Before fixing my code with changing all render instances to classic and back - I also could not reproduce the issue with generic cubes ....

    from memory the color of one of the checked instances shifted from (0.016, 0.56, 0.4) to (0.016, 0.56, 0.8) pixel readout was 0.7999999999999999999324324 ... see picture

    Edit:

    here is a Picture with "Classic" Instance and Render instance Display Color renderings ...

    instance_color.png

    kind regards

    posted in General Talk •
    RE: Color accuracy 32 bit - how to archive - things to look out for ...

    Hi again.

    As mentioned in my other Post it seems Render Instances Produce "shifted" colors when measured as pixel value in Code as also in the Picture viewer.

    Is this a limitation of render instances ?

    When I switch the render Instances to "Classic" Instances the produce the correct color in the rendering and in pixel readout from code ...

    kind regards
    mogh

    posted in General Talk •
    RE: SDK typo/discrepancy ? bitmap.Init() regarding bitdepth

    Thank you Ferdinand for your time,
    and also providing a high / low level solution.

    As far as I can tell it works all as intended - my code now results in clean colors "measured". (in lowlvl R20 and highlvl 2023)

    I found a small discrepancy with render instances but that's another topic.
    Cheers,
    mogh

    posted in Cinema 4D SDK •
    RE: Can we add a folding option for the code block when searching

    Yes a folding button would be cool,
    ... or another Code block option for the future where the responsible poster can dump lots of code and it will not show in searches ...

    anyway I feel your pain even if its sometime good to see if its relevant to your search ...

    posted in General Talk •
    RE: SDK typo/discrepancy ? bitmap.Init() regarding bitdepth

    Hi Ferdinand,

    thank you for your thoroughness,
    I dabbled a little bit with the suggestions you gave. But i have to admit I am a little bit out of my comfort zone with all that bytes 😉 anyway here is a more corect but still strange output giving code snippet ...

    green (0.0, 0.9, 0.1) pixel -> red readout => (R:0.00273437425494, G:0.0, B:0.0)
    This completely wrong readout might just be me unfamiliar with bits and bytes ...

    Also c4d.COLORMODE_RGBf return 36 which is kinda strange, hence ist stated as 32bit 3 channels. But perhaps this is just an internal ID ...

    FYI: At the moment I do not have access to GetPixelDirect Thats why I use struct.unpack()

    Cheers,
    mogh

    import c4d
    import struct
    import sys
    from c4d import bitmaps
    
    PYTHONVERSION = float(str(sys.version_info[0]) + "." + str(sys.version_info[1]))
    
    c4d.CallCommand(13957) # clear console
    
    # Get the active document
    doc = c4d.documents.GetActiveDocument()
    
    bd_now = doc.GetRenderBaseDraw()
    bd_now.GetEditorCamera()
    
    # Get the active render data
    rd = doc.GetActiveRenderData().GetData()
    
    both = 32
    width = both
    height = both
    rd[c4d.RDATA_XRES] = both
    rd[c4d.RDATA_YRES] = both
    
    bmp = c4d.bitmaps.BaseBitmap()
    bmp.Init(width, height, 96)
    result = c4d.documents.RenderDocument(doc, rd, bmp, c4d.RENDERFLAGS_EXTERNAL)
    
    if result==c4d.RENDERRESULT_OK:
        bitmaps.ShowBitmap(bmp)
    
    # Set up the parameters for GetPixelCnt()
    cnt = 1 # = how many pixels ?
    inc = bmp.GetBt() // 8 # 96/8 = 12  = how many bytes ?
    dstmode = c4d.COLORMODE_RGBf
    flags = c4d.PIXELCNT_0
    float_mem = 2 # double the buffer ???
    
    if PYTHONVERSION < 3.5:
        membuffer = c4d.storage.ByteSeq(None, cnt * inc * float_mem)
    else:
        membuffer = bytearray(cnt * inc * float_mem)
    
    print(bmp.GetBt())
    print(inc)
    print(dstmode)
    
    #https://plugincafe.maxon.net/topic/11154/setpixel-for-32-bit-float-images/2
    # Loop through all the pixels in the bitmap and read their values
    for y in range(height):
        for x in range(width):
            # Read the pixel value
            bmp.GetPixelCnt(x, y, cnt, membuffer, inc, dstmode, flags)
    
            # Convert the byte values to 32-bit floating point values
            #r, = struct.unpack('f', membuffer[0:4])
            #g, = struct.unpack('f', membuffer[4:8])
            #b, = struct.unpack('f', membuffer[8:12])
            #a, = struct.unpack('f', membuffer[12:16])
            r, = struct.unpack('d', membuffer[0:8])  # f float single, d float double
            g, = struct.unpack('d', membuffer[8:16])
            b, = struct.unpack('d', membuffer[16:24])
            
            # Print the pixel value if not black
            if r or g or b != 0:
                print("Pixel at ({}, {}): (R:{}, G:{}, B:{})".format(x, y, r, g, b))
    
    posted in Cinema 4D SDK •
    RE: SDK typo/discrepancy ? bitmap.Init() regarding bitdepth

    @mogh said in SDK typo/discrepancy ? bitmap.Init() regarding bitdepth:

    Which led me to the asumption its per channel .... but its not!

    It was late - no excuse ... I meant "which led me to the asumption its combined"
    I am sorry.

    @ferdinand said in SDK typo/discrepancy ? bitmap.Init() regarding bitdepth:

    Would that not be a bit fringe? Or do you simply want us to state that the value is meant as the sum of all channels?

    I want the min max number to be statet what BaseBitmap.Init() acecpts / or returns sane values ... ?!?
    I interpreted the SDk that 32 is the max ...

    The code you posted here still contains semantic errors; I am not sure if this was intended. bmp.Init(width, height, 96) is incorrect code. The maximum bit depth a bitmap can have is 32, as cited by yourself above.

    Now you confuse me - only 96 gives me correct values when I am reading the pixel values in 32 bit.

    bmp.Init(width, height, 32) #converted gives c4d.Vector(1.0, 1.0, 1.0)
    bmp.Init(width, height, 96) #converted gives c4d.Vector(0.022, 0.012, 0.0) for example
    

    So I am clearly running in circles hence the sdk states something different than example code on github and here in the forum.

    Please forgive me, I might just run down the wrong path again ... I am trying to read Pixel colors from a c4d.documents.RenderDocument() passing an 32 bit image.

    kind regards
    mogh

    posted in Cinema 4D SDK •
    Color accuracy 32 bit - how to archive - things to look out for ...

    Dear Developers,

    I am currently trying to compare color Vector values of rendered pixel against set color in the editor.

    While this works fine for 8Bit RGB I am having trouble to get it to work in 32 Bit eg. 0.001 steps of color.

    What are the gotchas I have to doublecheck ?

    • My render settings are linear 32 bit
    • My Project is set to Basic, Linear
    • My Render storage image is set to 32 (96) Bit

    My returned Values in Python are for a test all matching but not recognized for comparison.

    if color in colorlist: # -> returns no matches
    

    Color generated = Vector(0, 0, 0.001)
    Color object applied and readout = Vector(0, 0, 0.001)
    Color rendered pixel = Vector(0, 0, 0.001)

    only the colopicker in C4D Pictureviewer gives me Vector(0, 0, 0.013) which should not be a problem hence the retrieved rendered pixel value is Vector(0, 0, 0.001)

    Any tips on color ?

    And if any on comparing c4d.Vectors in python hence the seem not to match.

    kind regards mogh

    ignore the output on line 3 in the console its just a reminder for me from different part of the code
    32bitcolor.png

    posted in General Talk •
    SDK typo/discrepancy ? bitmap.Init() regarding bitdepth

    I was struggling because of this ...

    In the Documentation: BaseBitmap.Init it states:

    depth (int) –The requested bit depth (24 default).The possible values are {1,4,8,16,24,32}.

    Which led me to the asumption its per channel .... but its not!
    I was creating a code reading a 32 bit per channel image per pixel from the renderer.

    Hence the BaseBitmap.GetPixelCnt needs the corect inc (int) – The byte increment per pixel in the buffer. i was calculating on the wrong source bitrate ...

    My code works now after I set bmp.Init(width, height, 96) instead of the before mentioned 32!

    This might be a small thing and people more fluent in bits and bytes will notice ... but i was keeping myself strict to the sdk till I found old code from niklas calculating the inc from an loaded 32 bit image (which divided the image bit by 3 and checked against 32 -> which led me to 96)

    anyway feel free corect me, but for me this seems like a typo in the sdk ...

    kind regards mogh

    here is some sample code for folks pasing by:

    import c4d
    import struct
    from c4d import bitmaps
    
    c4d.CallCommand(13957)
    
    # Get the active document
    doc = c4d.documents.GetActiveDocument()
    
    bd_now = doc.GetRenderBaseDraw()
    bd_now.GetEditorCamera()
    
    # Get the active render data
    rd = doc.GetActiveRenderData().GetData()
    
    # Set the coordinate of the pixel you want to read
    both = 32
    
    width = both
    height = both
    
    rd[c4d.RDATA_XRES] = both
    rd[c4d.RDATA_YRES] = both
    
    bmp = c4d.bitmaps.BaseBitmap()
    bmp.Init(width, height, 96)
    result = c4d.documents.RenderDocument(doc, rd, bmp, c4d.RENDERFLAGS_EXTERNAL)
    
    if result==c4d.RENDERRESULT_OK:
        bitmaps.ShowBitmap(bmp)
    
    
    print(bmp.GetBt())
    
    # Set up the parameters for GetPixelCnt()
    cnt = 4
    inc = bmp.GetBt() // 8
    
    # membuffer = c4d.storage.ByteSeq(None, cnt * inc) # python 2.7
    membuffer = bytearray(cnt * inc)
    dstmode = c4d.COLORMODE_RGBf
    print(dstmode)
    flags = c4d.PIXELCNT_0
    
    """
    # Read the pixel value
    pixel_value = bmp.GetPixelCnt(width-1, height-1, cnt, membuffer, inc, dstmode, flags)
    
    # Convert the byte values to 32-bit floating point values
    r, = struct.unpack('f', membuffer[0:4])
    g, = struct.unpack('f', membuffer[4:8])
    b, = struct.unpack('f', membuffer[8:12])
    a, = struct.unpack('f', membuffer[12:16])
    
    # Print the pixel value
    #print((r, g, b, a))
    """
    
    # https://plugincafe.maxon.net/topic/11154/setpixel-for-32-bit-float-images/2
    # Loop through all the pixels in the bitmap and read their values
    for y in range(height):
        for x in range(width):
            # Read the pixel value
            bmp.GetPixelCnt(x, y, cnt, membuffer, inc, dstmode, flags)
    
            # Convert the byte values to 32-bit floating point values
            r, = struct.unpack('f', membuffer[0:4])
            g, = struct.unpack('f', membuffer[4:8])
            b, = struct.unpack('f', membuffer[8:12])
            a, = struct.unpack('f', membuffer[12:16])
    
            # Print the pixel value
            if r or g or b or a != 0:
                print("Pixel at ({}, {}): (R:{}, G:{}, B:{}, A:{})".format(x, y, r, g, b, a))
    
    posted in Cinema 4D SDK •
    RE: How to get the bounding box for the whole scene

    Follow up question is it possible to exclude the world origin from the list of points?
    If you place the objects outside the "Center" the world origin is included as a "point" hence the bounding box extends to the world Origin.

    I am not sure where in the codec c4d.Vector(0,0,0) is set

    thank you for your time.

    posted in Cinema 4D SDK •
    Catch - 'c4d.BaseDraw' is not alive

    Dear Developers;

    Whats the best practice to handle these conditions where the c4d.BaseDraw is not alive anymore or the plugin probably has old data ?!?

    My hunch is I have to reinitialize on restore / reload but I am clueless how to tackle this.

    My environment:

    User closes all documents (new unsaved document is displayed)
    or the user creates new document - then clicks on plugin.

    Code snipet:

        def __init__(self):
    
            self.doc = GetActiveDocument()
            
            try:
                self.rval_bd = self.doc.GetActiveBaseDraw()
            except ReferenceError as e:
                print("Error: ", e)
        """
        more code
        """
    
        def Restore(self, pluginid, sec_ref) :  
            
            # Set the pointers to the specific sub dialog here 
            # rather than in the CommandData section of this plugin  
            if sec_ref['subid'] == 1: 
                return self.sub_dialog1.Restore(pluginid, sec_ref)
            else: 
                return super(MainDialog, self).Restore(pluginid, sec_ref)
    

    Where the error occurs:

    self.AddCheckbox(ID_SAVEFRAME, flags=c4d.BFH_LEFT, initw=270, inith=0, name="Show Save Frame")  
    self.SetBool(ID_SAVEFRAME, self.rval_bd[c4d.BASEDRAW_DATA_SHOWSAFEFRAME])
    

    ReferenceError: the object 'c4d.BaseDraw' is not alive

    The Try test above does nothing in this regard.
    The only thing that works is reloading the python plugins ...

    There is more code than this but this might be relevant ?

    class MenuCommand(c4d.plugins.CommandData):
        dialog = None
    
        def Execute(self, doc):
            if self.dialog is None:
                self.dialog = MainDialog()
            return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=100, defaultw=300)
    
        def RestoreLayout(self, sec_ref):
            if self.dialog is None:
                self.dialog = MainDialog()
            return self.dialog.Restore(PLUGIN_ID, sec_ref)
    

    Kind regards
    mogh

    posted in Cinema 4D SDK •
    RE: Dialog Menu

    EDIT:
    Sorry this was not what I thought I have a dual Dialog example somewhere but not at hand ...

    *This is an Subdialog Example from the SDK.

    Github Subdialog

    hope that is in line what you are searching.*
    Cheers mogh

    posted in Cinema 4D SDK •