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.
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.
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_())
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.