I am designing an Asset browser for navigating a pre-defined folder structure and with the help of @musicamante its now looking alot better!
its meant to function in a similar way to OS X's finder in Column view mode:
I'm just having a little bit of trouble with how the lists display's. At launch the lists seem to want to show the root dir in the lists that should be blank.
Does anyone have an idea what I need to do have these lists display blank? Or generally get them to function more like OS X's finder in Column view mode? At the moment I can only get it to remain expanded as far as 6 but I have no idea what to do if their directory structure goes more than 6 folders deep... Help?
import os
from PySide2 import QtWidgets, QtGui, QtCore
import maya.cmds as cmds
import maya.mel as mel # Import the Maya MEL module
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_views = [] # List to store file views for each level
self.thumbnail_label = None
self.file_info_text = None
self.selected_file_path = None # Variable to store the selected file path
self.initUI()
def initUI(self):
self.setWindowTitle('Asset Browser')
layout = QtWidgets.QVBoxLayout(self)
# Adding the image
image_label = QtWidgets.QLabel()
pixmap = QtGui.QPixmap(r"N:\Asset_Browser\tools\safeBank\icons\safeBank_logoTitle_v001.png")
image_label.setPixmap(pixmap)
layout.addWidget(image_label)
# Add a frame for the project setting and folder path
project_frame = QtWidgets.QFrame()
project_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
project_frame_layout = QtWidgets.QHBoxLayout(project_frame)
layout.addWidget(project_frame)
# Add the set project button
set_project_button = QtWidgets.QPushButton("Set Project")
set_project_button.clicked.connect(self.setProject)
project_frame_layout.addWidget(set_project_button)
# Add the folder path line edit
self.folder_path_lineedit = QtWidgets.QLineEdit()
project_frame_layout.addWidget(self.folder_path_lineedit)
# Add the refresh button
refresh_icon = QtGui.QPixmap(r"N:\Asset_Browser\tools\safeBank\icons\refresh.png")
refresh_label = QtWidgets.QLabel()
refresh_label.setPixmap(refresh_icon)
refresh_label.mousePressEvent = self.refreshDirectories
project_frame_layout.addWidget(refresh_label)
# Create a layout for the views, thumbnail, and file info
views_thumbnail_layout = QtWidgets.QHBoxLayout()
layout.addLayout(views_thumbnail_layout)
# Create views for each level
for i in range(self.levels):
view = QtWidgets.QListView()
model = QtWidgets.QFileSystemModel()
model.setRootPath(QtCore.QDir.rootPath())
view.setModel(model)
view.setIconSize(QtCore.QSize(20, 20)) # Set icon size
model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.Files | QtCore.QDir.NoDotAndDotDot)
model.setNameFilters(["*.mb", "*.ma", "*.fbx"])
model.setNameFilterDisables(False)
view.clicked.connect(lambda index, i=i: self.viewClicked(index, i))
views_thumbnail_layout.addWidget(view)
self.file_views.append(view)
# Populate only the first view upon launch
self.populateDirectory(self.asset_dir, self.file_views[0])
# Clear the root index for all other views to keep them blank
for view in self.file_views[1:]:
view.setRootIndex(QtCore.QModelIndex())
# Connect the directory change for the first view to update subsequent views
self.file_views[0].doubleClicked.connect(lambda index: self.populateNextView(index))
# Add a frame for the thumbnail and file info
info_frame = QtWidgets.QFrame()
info_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
info_frame_layout = QtWidgets.QVBoxLayout(info_frame)
views_thumbnail_layout.addWidget(info_frame)
# Add thumbnail label
self.thumbnail_label = QtWidgets.QLabel()
self.thumbnail_label.setFixedSize(200, 200)
info_frame_layout.addWidget(self.thumbnail_label)
# Add file info text field
self.file_info_text = QtWidgets.QTextEdit()
info_frame_layout.addWidget(self.file_info_text)
# Add feedback field
self.feedback_field = QtWidgets.QLabel()
layout.addWidget(self.feedback_field)
# Add a frame for search and notes (occupying half the window width each)
search_notes_frame = QtWidgets.QFrame()
search_notes_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
search_notes_layout = QtWidgets.QHBoxLayout(search_notes_frame)
layout.addWidget(search_notes_frame)
# Add a frame for search (occupying half the window width)
search_frame = QtWidgets.QFrame()
search_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
search_frame_layout = QtWidgets.QVBoxLayout(search_frame)
search_notes_layout.addWidget(search_frame)
# Add the search bar to the search frame
search_bar_layout = QtWidgets.QHBoxLayout()
search_frame_layout.addLayout(search_bar_layout)
self.search_bar = QtWidgets.QLineEdit()
search_bar_layout.addWidget(self.search_bar)
# Add the search button to the search frame
search_button = QtWidgets.QPushButton("Search")
search_button.clicked.connect(self.searchFiles)
search_bar_layout.addWidget(search_button)
# Connect returnPressed signal of the search bar to searchFiles method
self.search_bar.returnPressed.connect(self.searchFiles)
# Add the search results list to the search frame
self.results_list = QtWidgets.QListWidget()
search_frame_layout.addWidget(self.results_list)
# Add a frame for notes (occupying half the window width)
notes_frame = QtWidgets.QFrame()
notes_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
notes_layout = QtWidgets.QVBoxLayout(notes_frame)
search_notes_layout.addWidget(notes_frame)
# Add notes box to the notes frame
notes_label = QtWidgets.QLabel("Notes:")
notes_layout.addWidget(notes_label)
self.notes_box = QtWidgets.QTextEdit()
notes_layout.addWidget(self.notes_box)
def populateDirectory(self, directory, view):
view.model().setRootPath(directory)
view.setRootIndex(view.model().index(directory))
# Find and select the current directory in the previous file list
if len(self.file_views) > 1:
prev_view = self.file_views[self.file_views.index(view) - 1]
match_indexes = prev_view.model().match(
prev_view.model().index(prev_view.model().rootPath()),
QtCore.Qt.DisplayRole,
os.path.basename(directory),
1,
QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive
)
if match_indexes:
prev_view.selectionModel().select(
match_indexes[0], QtCore.QItemSelectionModel.ClearAndSelect)
# Clear the root index for all other views to keep them blank
for subsequent_view in self.file_views[self.file_views.index(view) + 1:]:
subsequent_view.setRootIndex(QtCore.QModelIndex())
def viewClicked(self, index, view_index):
selected_file_path = self.file_views[view_index].model().filePath(index)
self.folder_path_lineedit.setText(selected_file_path) # Update folder path line edit
if os.path.isdir(selected_file_path):
self.populateDirectory(selected_file_path, self.file_views[view_index + 1])
else:
self.selected_file_path = selected_file_path
self.displayNotes()
self.displayThumbnail(selected_file_path)
def displayNotes(self):
# Check if a file is selected
if self.selected_file_path is not None:
# Construct the note file path by replacing the file extension with '.txt'
note_file_path = os.path.splitext(self.selected_file_path)[0] + '.txt'
# Check if the note file exists
if os.path.isfile(note_file_path):
# Read and display the contents of the note file
with open(note_file_path, 'r') as f:
notes_content = f.read()
self.notes_box.setText(notes_content)
return
# If no note file found or no file is selected, clear the notes box
self.notes_box.clear()
def displayThumbnail(self, file_path):
# Construct the thumbnail file path
thumbnail_path = os.path.splitext(file_path)[0] + '.png'
# Debug print for the thumbnail path
print(f"Thumbnail Path: {thumbnail_path}")
# Check if thumbnail exists
if os.path.exists(thumbnail_path):
try:
pixmap = QtGui.QPixmap(thumbnail_path)
self.thumbnail_label.setPixmap(pixmap.scaled(200, 200, QtCore.Qt.KeepAspectRatio))
return
except Exception as e:
print(f"Error loading thumbnail: {e}")
else:
print("Thumbnail not found.")
def refreshDirectories(self, event):
for view in self.file_views:
view.model().refresh()
def setProject(self):
selected_directory = self.folder_path_lineedit.text()
mel.eval('setProject "{}";'.format(selected_directory))
def searchFiles(self):
search_text = self.search_bar.text()
selected_directory = self.folder_path_lineedit.text()
results = []
# Search only within the last selected folder
for root, dirs, files in os.walk(selected_directory):
for file in files:
if search_text in file and file.lower().endswith(('.mb', '.ma', '.fbx')):
results.append(os.path.join(root, file))
self.results_list.clear()
self.results_list.addItems(results)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
asset_dir = r"N:\Asset_Browser\work\Asset"
asset_browser = AssetBrowser(asset_dir)
asset_browser.show()
sys.exit(app.exec_())
nothing seems to keep them blank until needed.
Any help is greatly appreciated!
In Qt item views, an invalid QModelIndex represents the root of the model. For a QFileSystemModel, that means the file system root (/
on *nix, "My Computer" on Windows).
There is no immediate way to show an empty list with such a model (maybe there is on Windows using proper QDir.Filters
, but I'm not sure), but there are possible alternatives:
rowCount()
in order to always return 0 whenever required;In order to achieve the second solution, you obviously need to keep a reference to the model: just create a property when the view is created, and then it's just a matter of resetting the model whenever necessary.
self.dummy = QStringListModel()
...
for i in range(self.levels):
view = QtWidgets.QListView()
view.fsModel = model = QtWidgets.QFileSystemModel()
model.setRootPath(QtCore.QDir.rootPath())
...
def populateDirectory(self, directory, view):
view.setModel(view.fsModel)
...
for subsequent_view in self.file_views[self.file_views.index(view) + 1:]:
subsequent_view.setModel(self.dummy)
Alternatively, you can subclass the model and provide a way to "invalidate" it:
class MyFileSystemModel(QFileSystemModel):
_valid = True
def invalidate(self):
self.modelAboutToBeReset.emit()
self._valid = False
self.modelReset.emit()
def rowCount(self, parent=QModelIndex()):
if self._valid:
return super().rowCount(parent)
return 0
def setRootPath(self, path):
self._valid = True
return super().setRootPath(path)
class AssetBrowser(QtWidgets.QWidget):
...
def populateDirectory(self, directory, view):
view.model().setRootPath(directory)
...
for subsequent_view in self.file_views[self.file_views.index(view) + 1:]:
subsequent_view.invalidate()