SOLVED How to select directories in a CUSTOMGUI_FILENAME?

Thanks @ferdinand,
Your post helped me a lot.
Any idea how I could set a DIRECTORY selector instead of a FILE selector ?

What is the corresponding pluginId to relace c4d.CUSTOMGUI_FILENAME ?

I know how to popup a directory selector with this command

c4d.storage.LoadDialog(c4d.FILESELECTTYPE_ANYTHING, "Select Directory", c4d.FILESELECT_DIRECTORY)

But how could I integrate this into a layout ?

Thanks 😉

[edit @ferdinand]: Forked from https://plugincafe.maxon.net/topic/13872/

Hello @plc,

Thank you for reaching out to us. I have forked your question from the original topic, as while it was also a related question, it was effectively a new question. The reason why we are doing this, is so that forum remains searchable.

The "trick" to let the user only select directories in the CUSTOMGUI_FILENAME, is to set its flag
FILENAME_DIRECTORY. Custom GUIs usually have a wide range of flags/options which one can use. These flags are documented partially in the Python documentation and fully in the C++ documentation. In this case you will not find the CUSTOMGUI_FILENAME flags in the Python documentation because the matching GUI type FilenameCustomGui has not been exposed in Python. You will only find the flags for wrapped custom GUIs in the Python docs. But this often does not prevent you from using them, as these GUI controllers often must not be accessed for making use of the GUIs.

In these cases you then must look up the custom GUI in the C++ docs, to see their flags, e.g., here for CUSTOMGUI_FILENAME. I provided an example below.

Cheers,
Ferdinand

The code:

"""Simple example for a FILENAME_DIRECTORY GUI in which only directories can be selected.

Can be run as a script manager script and will display all files located within a given path.

The "trick" to let the user only select directories in the CUSTOMGUI_FILENAME, is to set its flag
FILENAME_DIRECTORY. Custom GUIs usually have a wide range of flags/options which one can use. These
flags are documented partially in the Python and fully in the C++ documentation. In this case you
will not find the CUSTOMGUI_FILENAME in the Python documentation because the matching GUI type
FilenameCustomGui has not been exposed in Python. You will only find the flags for wrapped custom 
GUIs in the Python docs [1]. But this often does not prevent you from using them, as these GUI
controllers often must not be accessed for making use of the GUIs.

In these cases you then must look up the custom GUI in the C++ docs, to see their flags, e.g., here
[2] for CUSTOMGUI_FILENAME.

References:
    [1] https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.gui/BaseCustomGui/index.html
    [2] https://developers.maxon.net/docs/Cinema4DCPPSDK/html/group___f_i_l_e_n_a_m_e___c_u_s_t_o_m_g_u_i_s_e_t_t_i_n_g_s.html

"""

import c4d
import os
import typing

class MyDialog(c4d.gui.GeDialog):
    """Defines a dialog.
    """
    ID_FILENAME = 1000
    ID_MULTILINE = 1001
    ID_BUTTON = 1002

    ID_GROUP = 10000

    def CreateLayout(self) -> bool:
        """Called by Cinema 4D to populate the dialog with gadgets.
        """
        # Set the title of the dialog.
        self.SetTitle("My Dialog")

        # Open a container to put other elements into.
        self.GroupBegin(id=MyDialog.ID_GROUP, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1)
        self.GroupSpace(spacex=5, spacey=5)
        
        # Add a CUSTOMGUI_FILENAME with the FILENAME_DIRECTORY flag set, i.e., it will let the user
        # select directories instead of files. The flags of a custom GUI must be written into a
        # BaseContainer as shown below. Most of the time the flag values are booleans (on/off), but
        # in some cases they can also be other data types as integers or strings. 

        # The flags container.
        flags: c4d.BaseContainer = c4d.BaseContainer()
        flags[c4d.FILENAME_DIRECTORY] = True
        # Add a CUSTOMGUI_FILENAME with our flags settings.
        self.AddCustomGui(id=MyDialog.ID_FILENAME, 
                          pluginid=c4d.CUSTOMGUI_FILENAME, 
                          name="Path", 
                          flags=c4d.BFH_SCALEFIT, 
                          minw=0, minh=0, 
                          customdata=flags)

        # Add text box spanning multiple lines (that is set to read only mode), it will display the
        # output of GetFilesBelowDirectory() when the "Run" button is being pressed.
        self.AddMultiLineEditText(id=MyDialog.ID_MULTILINE, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 
                                  inith=50, style=c4d.DR_MULTILINE_READONLY)
        # Add a button.
        self.AddButton(id=MyDialog.ID_BUTTON, flags=c4d.BFH_SCALEFIT, name="Run")

        # Close the lyaout group.
        self.GroupEnd()
        
        return super().CreateLayout()

    def InitValues(self) -> bool:
        """Called by Cinema 4D to initialize a layout.
        """
        self.SetFilename(MyDialog.ID_FILENAME, "")
        self.SetString(MyDialog.ID_MULTILINE, "")
        return super().InitValues()


    def Command(self, mid: int, msg: c4d.BaseContainer) -> bool:
        """Called by Cinema 4D when the user interacts with one of the gadgets in the dialog.
        """
        # The "Run" button has been pressed.
        if mid == MyDialog.ID_BUTTON:
            # Put the files contained within the path stored under #ID_FILENAME in the the output
            # field #ID_MULTILINE or display an error message when there is no valid path.
            result = MyDialog.GetFilesBelowDirectory(self.GetFilename(MyDialog.ID_FILENAME))
            if result:
                self.SetString(MyDialog.ID_MULTILINE, result)
            else:
                c4d.gui.MessageDialog("Please select a directory first.")
            
        return super().Command(mid, msg)

    @staticmethod
    def GetFilesBelowDirectory(path: str) -> typing.Optional[str]:
        """Returns a string which contains all files which are below the given #path.

        This function is not part of the #GeDialog interface and just a cheap example how one could
        utilize a FILENAME custom in directory mode.
        """
        # Validations that #path is an existing directory path string.
        if not isinstance(path, str) or path == "":
            return None
        if not os.path.exists(path) or not os.path.isdir(path):
            return None

        # The string into which all file paths are being put.
        result: str = ""
        # Iterate over all files which are in #path or one of its ancestor paths and put them into
        # #result.
        for root, _, files in os.walk(path):
            for f in files:
                result += os.path.join(root, f) + "\n"

        return result

def main():
    """Opens the dialog.
    """
    # Instantiate the dialog.
    dialog = MyDialog()
    # Open the dialog in modal mode, i.e., it will be the only thing the user can focus on.
    dialog.Open(dlgtype=c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=400, xpos=-2, ypos=-2)

if __name__=='__main__':
    main()

Hello @plc,

Thank you for reaching out to us. I have forked your question from the original topic, as while it was also a related question, it was effectively a new question. The reason why we are doing this, is so that forum remains searchable.

The "trick" to let the user only select directories in the CUSTOMGUI_FILENAME, is to set its flag
FILENAME_DIRECTORY. Custom GUIs usually have a wide range of flags/options which one can use. These flags are documented partially in the Python documentation and fully in the C++ documentation. In this case you will not find the CUSTOMGUI_FILENAME flags in the Python documentation because the matching GUI type FilenameCustomGui has not been exposed in Python. You will only find the flags for wrapped custom GUIs in the Python docs. But this often does not prevent you from using them, as these GUI controllers often must not be accessed for making use of the GUIs.

In these cases you then must look up the custom GUI in the C++ docs, to see their flags, e.g., here for CUSTOMGUI_FILENAME. I provided an example below.

Cheers,
Ferdinand

The code:

"""Simple example for a FILENAME_DIRECTORY GUI in which only directories can be selected.

Can be run as a script manager script and will display all files located within a given path.

The "trick" to let the user only select directories in the CUSTOMGUI_FILENAME, is to set its flag
FILENAME_DIRECTORY. Custom GUIs usually have a wide range of flags/options which one can use. These
flags are documented partially in the Python and fully in the C++ documentation. In this case you
will not find the CUSTOMGUI_FILENAME in the Python documentation because the matching GUI type
FilenameCustomGui has not been exposed in Python. You will only find the flags for wrapped custom 
GUIs in the Python docs [1]. But this often does not prevent you from using them, as these GUI
controllers often must not be accessed for making use of the GUIs.

In these cases you then must look up the custom GUI in the C++ docs, to see their flags, e.g., here
[2] for CUSTOMGUI_FILENAME.

References:
    [1] https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.gui/BaseCustomGui/index.html
    [2] https://developers.maxon.net/docs/Cinema4DCPPSDK/html/group___f_i_l_e_n_a_m_e___c_u_s_t_o_m_g_u_i_s_e_t_t_i_n_g_s.html

"""

import c4d
import os
import typing

class MyDialog(c4d.gui.GeDialog):
    """Defines a dialog.
    """
    ID_FILENAME = 1000
    ID_MULTILINE = 1001
    ID_BUTTON = 1002

    ID_GROUP = 10000

    def CreateLayout(self) -> bool:
        """Called by Cinema 4D to populate the dialog with gadgets.
        """
        # Set the title of the dialog.
        self.SetTitle("My Dialog")

        # Open a container to put other elements into.
        self.GroupBegin(id=MyDialog.ID_GROUP, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1)
        self.GroupSpace(spacex=5, spacey=5)
        
        # Add a CUSTOMGUI_FILENAME with the FILENAME_DIRECTORY flag set, i.e., it will let the user
        # select directories instead of files. The flags of a custom GUI must be written into a
        # BaseContainer as shown below. Most of the time the flag values are booleans (on/off), but
        # in some cases they can also be other data types as integers or strings. 

        # The flags container.
        flags: c4d.BaseContainer = c4d.BaseContainer()
        flags[c4d.FILENAME_DIRECTORY] = True
        # Add a CUSTOMGUI_FILENAME with our flags settings.
        self.AddCustomGui(id=MyDialog.ID_FILENAME, 
                          pluginid=c4d.CUSTOMGUI_FILENAME, 
                          name="Path", 
                          flags=c4d.BFH_SCALEFIT, 
                          minw=0, minh=0, 
                          customdata=flags)

        # Add text box spanning multiple lines (that is set to read only mode), it will display the
        # output of GetFilesBelowDirectory() when the "Run" button is being pressed.
        self.AddMultiLineEditText(id=MyDialog.ID_MULTILINE, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 
                                  inith=50, style=c4d.DR_MULTILINE_READONLY)
        # Add a button.
        self.AddButton(id=MyDialog.ID_BUTTON, flags=c4d.BFH_SCALEFIT, name="Run")

        # Close the lyaout group.
        self.GroupEnd()
        
        return super().CreateLayout()

    def InitValues(self) -> bool:
        """Called by Cinema 4D to initialize a layout.
        """
        self.SetFilename(MyDialog.ID_FILENAME, "")
        self.SetString(MyDialog.ID_MULTILINE, "")
        return super().InitValues()


    def Command(self, mid: int, msg: c4d.BaseContainer) -> bool:
        """Called by Cinema 4D when the user interacts with one of the gadgets in the dialog.
        """
        # The "Run" button has been pressed.
        if mid == MyDialog.ID_BUTTON:
            # Put the files contained within the path stored under #ID_FILENAME in the the output
            # field #ID_MULTILINE or display an error message when there is no valid path.
            result = MyDialog.GetFilesBelowDirectory(self.GetFilename(MyDialog.ID_FILENAME))
            if result:
                self.SetString(MyDialog.ID_MULTILINE, result)
            else:
                c4d.gui.MessageDialog("Please select a directory first.")
            
        return super().Command(mid, msg)

    @staticmethod
    def GetFilesBelowDirectory(path: str) -> typing.Optional[str]:
        """Returns a string which contains all files which are below the given #path.

        This function is not part of the #GeDialog interface and just a cheap example how one could
        utilize a FILENAME custom in directory mode.
        """
        # Validations that #path is an existing directory path string.
        if not isinstance(path, str) or path == "":
            return None
        if not os.path.exists(path) or not os.path.isdir(path):
            return None

        # The string into which all file paths are being put.
        result: str = ""
        # Iterate over all files which are in #path or one of its ancestor paths and put them into
        # #result.
        for root, _, files in os.walk(path):
            for f in files:
                result += os.path.join(root, f) + "\n"

        return result

def main():
    """Opens the dialog.
    """
    # Instantiate the dialog.
    dialog = MyDialog()
    # Open the dialog in modal mode, i.e., it will be the only thing the user can focus on.
    dialog.Open(dlgtype=c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=400, xpos=-2, ypos=-2)

if __name__=='__main__':
    main()

Hi @ferdinand ,
Thanks a lot, that's exactly what I was looking for !

flags: c4d.BaseContainer = c4d.BaseContainer()
        flags[c4d.FILENAME_DIRECTORY] = True
        # Add a CUSTOMGUI_FILENAME with our flags settings.
        self.AddCustomGui(id=MyDialog.ID_FILENAME, 
                          pluginid=c4d.CUSTOMGUI_FILENAME, 
                          name="Path", 
                          flags=c4d.BFH_SCALEFIT, 
                          minw=0, minh=0, 
                          customdata=flags)