0

I'm creating a script in Maya with python that is designed to navigate local directory folders.

The way I've been asked to make it navigate is with a collection of lists each representing a different directory level.

enter image description here

now on selecting the 1st list [Type] it propagates the 2nd list correctly

giving me this feedback Item clicked on list 0 Received column: 0, Current column index: 0 Correct column Not last column Selected directory: D:\WORKSPACE\new_folderStructure\3D\ASSETS\PROPS Is directory

but when i click on an item on the 2nd list it doesnt seem to respond at all. Item clicked on list 1 Received column: 0, Current column index: 1

I'm very confused.

  • I have tried stripping it back to a more basic script that just adds numbers into each list when you click on an item in the list, this worked just fine.
  • Re-introducing the dir navigation this errored again.
  • I tried changing os.path.join to os.path.normpath()
  • I tried introducing checks to tell me the dir selected and again it just tells me nothing on clicking the 2nd list.
  • I tried changing the item Clicked Function, and separating it into new functions based on which list is interacted with.
  • Even asked chat gtp for help and it just lost its mind.
import os
import maya.cmds as cmds
from PySide2 import QtWidgets, QtGui

class AssetBrowser(QtWidgets.QWidget):
    def __init__(self, asset_dir, parent=None):
        super(AssetBrowser, self).__init__(parent)
        self.asset_dir = asset_dir
        self.levels = 6  # Number of levels to display
        self.list_titles = ["Type", "Category", "Asset", "LOD", "User", "Scene"]  # List titles
        self.file_lists = []  # List to store file list widgets for each level
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Asset Browser')
        layout = QtWidgets.QVBoxLayout(self)

        # Adding the image
        image_label = QtWidgets.QLabel()
        pixmap = QtGui.QPixmap(r"D:\WORKSPACE\safeBank\images\safeBank_logoTitle_v001.png")
        image_label.setPixmap(pixmap)
        layout.addWidget(image_label)

        # Add frame for project settings
        project_frame = QtWidgets.QFrame()
        project_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        project_layout = QtWidgets.QHBoxLayout(project_frame)
        layout.addWidget(project_frame)

        # Add button to set project directory
        self.set_project_button = QtWidgets.QPushButton("Set Project")
        self.set_project_button.clicked.connect(self.setProjectDirectory)
        project_layout.addWidget(self.set_project_button)

        # Add line edit for displaying selected folder path
        self.folder_path_lineedit = QtWidgets.QLineEdit()
        self.folder_path_lineedit.setReadOnly(True)
        project_layout.addWidget(self.folder_path_lineedit)

        # Create a layout for the lists
        lists_layout = QtWidgets.QHBoxLayout()
        layout.addLayout(lists_layout)

        # Create frames for each list
        for i in range(self.levels):
            frame = QtWidgets.QFrame()
            frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
            frame_layout = QtWidgets.QVBoxLayout(frame)
            lists_layout.addWidget(frame)

            # Add title label
            title_label = QtWidgets.QLabel(self.list_titles[i])
            frame_layout.addWidget(title_label)

            # Create the file list widget
            file_list_widget = QtWidgets.QTreeWidget()
            file_list_widget.setHeaderHidden(True)
            frame_layout.addWidget(file_list_widget)

            # Connect itemClicked signal to unique itemClicked method for each list
            file_list_widget.itemClicked.connect(lambda item, col, index=i: self.itemClicked(item, col, index))

            self.file_lists.append(file_list_widget)

        # Add feedback field
        feedback_field = QtWidgets.QLabel()
        layout.addWidget(feedback_field)
        self.feedback_field = feedback_field

        # Set initial project directory
        self.setProjectDirectory(self.asset_dir)

        self.populateFileLists(self.asset_dir)

    def populateFileLists(self, directory):
        for level, file_list_widget in enumerate(self.file_lists):
            file_list_widget.clear()
            if os.path.exists(directory) and os.listdir(directory):  # Check if directory exists and is not empty
                self.populateDirectory(directory, file_list_widget)
                directory = os.path.join(directory, os.listdir(directory)[0])  # Go down a level
            else:
                break  # Stop populating lists if directory is empty or doesn't exist

    def populateDirectory(self, directory, file_list_widget):
        for item_name in sorted(os.listdir(directory)):
            item_path = os.path.join(directory, item_name)
            item = QtWidgets.QTreeWidgetItem(file_list_widget)
            item.setText(0, item_name)
            if os.path.isdir(item_path):
                item.setIcon(0, QtGui.QIcon.fromTheme("folder"))

    def itemClicked(self, item, column, index):
        print(f"Item clicked on list {index}")
        print(f"Received column: {column}, Current column index: {index}")

        if column == index:  # Only proceed if the click is in the correct column
            selected_directory = self.asset_dir

            # Build the path based on the selected folders in the previous columns
            for level in range(index + 1):
                selected_directory = os.path.join(selected_directory, self.file_lists[level].currentItem().text(0))
                print(f"Selected directory: {selected_directory}")

            if os.path.isdir(selected_directory):
                # Update the path for the next column
                self.populateFileList(selected_directory, self.file_lists[index + 1])  # Populate next column
                self.feedback_field.setText(selected_directory)  # Display selected directory in feedback field
            else:
                print(f"Selected directory is not valid: {selected_directory}")

    def populateFileList(self, directory, file_list_widget):
        file_list_widget.clear()
        self.populateDirectory(directory, file_list_widget)

    def setProjectDirectory(self, directory=None):
        if directory is None:
            directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Project Directory")
        if directory:
            self.asset_dir = directory
            self.folder_path_lineedit.setText(directory)
            cmds.workspace(directory, openWorkspace=True)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
    asset_dir = r"D:\WORKSPACE\new_folderStructure\GF\3D\ASSETS"
    asset_browser = AssetBrowser(asset_dir)
    asset_browser.show()
    sys.exit(app.exec_())
3
  • Using a QTreeWidget for this seems quite pointless. Do you know about QColumnView? In any case, please provide a proper minimal reproducible example and ensure that you're properly formatting code by checking the post preview before submitting. Commented Feb 13, 2024 at 19:38
  • @musicamante Thank you so much for your help and suggestions, this is very much my first time posting and I greatly appreciate your time and guidance both towards a solution and proper etiquette! Im going to look into QColumnView today and see if this will solve the problem too! thank you for the kind suggestion! any others are more than welcome, im very new and very stuck! Commented Feb 14, 2024 at 1:07
  • @musicamante I tried the QColumnView and although I much prefer it and think its far more efficient my users have specifically requested that it function as a collection of horizontal lists that each display different levels of sub directories. I personally don't like this or agree this is more efficient but they are being stubborn. Commented Feb 14, 2024 at 2:09

1 Answer 1

0

The column argument in itemClicked() is wrong, most importantly because it refers to the column of the clicked QTreeWidget, which, similarly to QTableView, can show multiple columns.
You are only showing one column, so that value will always be 0, which is the reason for which if column == index: only works in the first QTreeWidget: the column is always 0, and the first QTreeWidget has index 0.

Simply removing that comparison will solve the problem:

    def itemClicked(self, item, column, index):
        selected_directory = self.asset_dir

        for level in range(index + 1):

        ... etc.

Note that using QTreeWidget for this purpose is completely pointless, because you are never using the primary feature of QTreeView, which is to show hierarchical structures.

Since you are also never using more than one column, a much more accurate choice is to use QListWidget.

class AssetBrowser(QWidget):
    ...
    def initUI(self):
        ...
        for i in range(self.levels):
            ...
            file_list_widget = QListWidget()
            frame_layout.addWidget(file_list_widget)

            file_list_widget.itemClicked.connect(
                lambda item, index=i: self.itemClicked(item, index))

            self.file_lists.append(file_list_widget)

    ...

    def populateDirectory(self, directory, file_list_widget):
        for item_name in sorted(os.listdir(directory)):
            item_path = os.path.join(directory, item_name)
            item = QListWidgetItem(item_name, file_list_widget)
            if os.path.isdir(item_path):
                item.setIcon(QIcon.fromTheme("folder"))

    def itemClicked(self, item, index):
        selected_directory = self.asset_dir

        for level in range(index + 1):
            selected_directory = os.path.join(selected_directory, 
                self.file_lists[level].currentItem().text())

        if os.path.isdir(selected_directory):
            self.populateFileList(selected_directory, 
                self.file_lists[index + 1])
            self.feedback_field.setText(selected_directory)

        # you should always clear the remaining lists
        for list_view in self.file_lists[index + 2:]:
            list_view.clear()

There is another important issue. By default, populateFileLists automatically "fills" the remaining views if the given path goes beyond the "root" path; this is wrong from the UX perspective, because it doesn't show the user the currently selected directory within its list, and you're also arbitrarily sorting the contents alphabetically, which is not consistent with os.listdir(directory)[0] and with the conventional order used in file browsers.

A more appropriate implementation should be the following:

    def populateFileLists(self, directory):
        prev_file_list = None
        for level, file_list_widget in enumerate(self.file_lists):
            file_list_widget.clear()
            if os.path.isdir(directory):
                self.populateDirectory(directory, file_list_widget)
                if prev_file_list:
                    match = prev_file_list.findItems(
                        os.path.basename(directory), Qt.MatchExactly)
                    if match:
                        prev_file_list.setCurrentItem(match[0])
                prev_file_list = file_list_widget
                for entry in sorted(os.listdir(directory)):
                    entry = os.path.join(directory, entry)
                    if os.path.isdir(entry):
                        directory = entry
                        break
                else:
                    break
            else:
                break

Finally, when accessing the file system, using low level classes such as QTreeWidget or QListWidget makes things a bit cumbersome and prone to errors/bugs.

QFileSystemModel provides a more appropriate interface through the file system, and it should be considered instead.
Implementing this requires some further efforts, but also ensures a more accurate file system and object logic management. I strongly suggest to follow that path, then, and if you find issues while trying to achieve it, post another question.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.