Created
May 2, 2025 17:08
-
-
Save Pakmanv/1fef0bcd051d7a6ecd697686c3065a52 to your computer and use it in GitHub Desktop.
Asset File Manager VV 1.9.7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| import os | |
| import glob | |
| import json | |
| import fnmatch | |
| import re | |
| import subprocess | |
| from PySide2 import QtWidgets, QtCore, QtGui | |
| import maya.cmds as cmds | |
| import maya.mel as mel | |
| import time | |
| import sys | |
| # Script version | |
| SCRIPT_VERSION = "1.9.7" # Updated to 1.9.7 to reflect "always on top" enforcement | |
| class AssetFileManagerVV(QtWidgets.QDialog): | |
| def clean_file_path(self, file_path): | |
| """Remove {N} Suffix from file path.""" | |
| return re.sub(r'\{[0-9]+\}', '', file_path) | |
| def cache_assets(self): | |
| """Cache .ma, .mb, and .fbx files in the project directory.""" | |
| ma_files = glob.glob(os.path.join(self.project_dir, "**", "*.ma"), recursive=True) | |
| mb_files = glob.glob(os.path.join(self.project_dir, "**", "*.mb"), recursive=True) | |
| fbx_files = glob.glob(os.path.join(self.project_dir, "**", "*.fbx"), recursive=True) | |
| return ma_files + mb_files + fbx_files | |
| def cache_folder_assets(self, folder_path): | |
| """Cache .ma, .mb, and .fbx files in the specified folder.""" | |
| ma_files = glob.glob(os.path.join(folder_path, "*.ma")) | |
| mb_files = glob.glob(os.path.join(folder_path, "*.mb")) | |
| fbx_files = glob.glob(os.path.join(folder_path, "*.fbx")) | |
| return ma_files + mb_files + fbx_files | |
| def __init__(self, project_dir): | |
| super(AssetFileManagerVV, self).__init__() | |
| # NEW: Set window to always stay on top of all windows | |
| self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) | |
| self.project_dir = project_dir | |
| try: | |
| self.full_asset_cache = self.cache_assets() | |
| self.asset_cache = self.full_asset_cache[:] | |
| except Exception: | |
| self.full_asset_cache = [] | |
| self.asset_cache = [] | |
| self.current_folder = None | |
| self.import_items = {} # Now stores {"item": QListWidgetItem, "color": str} | |
| self.bookmarks = self.load_bookmarks() | |
| self.show_full_path = False | |
| self.import_sort_mode = "asset" | |
| self.group_colors = [ | |
| "#FF9999", # Pastel red | |
| "#CCFFCC", # Pastel green | |
| "#ADD8E6", # Pastel blue | |
| "#D9B3FF", # Pastel purple | |
| "#FFFF99", # Pastel yellow | |
| "#FFB3E6", # Pastel pink | |
| ] | |
| self.import_list_height = 200 | |
| self.browser_list_height = 240 | |
| try: | |
| self.project_history = self.load_project_history() | |
| except Exception: | |
| self.project_history = [] | |
| self.clear_after_import = False | |
| self.disable_delete_prompt = False | |
| self.incremental_save = False | |
| try: | |
| self.load_window_state() | |
| except Exception: | |
| pass | |
| try: | |
| self.setup_ui() | |
| except Exception: | |
| raise | |
| self.initialize_import_list() | |
| def create_colored_icon(self, color): | |
| """Create a QIcon with a solid colored square.""" | |
| pixmap = QtGui.QPixmap(16, 16) | |
| pixmap.fill(QtGui.QColor(color)) | |
| return QtGui.QIcon(pixmap) | |
| def validate_file(self, file_path): | |
| """Validate file before importing.""" | |
| try: | |
| if not os.path.exists(file_path): | |
| return False, "File does not exist" | |
| if not os.access(file_path, os.R_OK): | |
| return False, "No read permission" | |
| file_size = os.path.getsize(file_path) | |
| if file_size == 0: | |
| return False, "File is empty" | |
| if file_path.endswith(".ma"): | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| first_line = f.readline().strip() | |
| if not first_line.startswith("//Maya ASCII"): | |
| return False, "Invalid Maya ASCII file format" | |
| return True, "" | |
| except (IOError, UnicodeDecodeError) as e: | |
| return False, f"Error reading file: {str(e)}" | |
| def setup_ui(self): | |
| self.setWindowTitle(f"Asset File Manager VV {SCRIPT_VERSION}") | |
| self.resize(self.window_width, self.window_height) | |
| layout = QtWidgets.QVBoxLayout() | |
| # Menu Bar | |
| menu_bar = QtWidgets.QMenuBar(self) | |
| menu_bar.setContextMenuPolicy(QtCore.Qt.NoContextMenu) | |
| tools_menu = menu_bar.addMenu("Tools") | |
| tools_menu.setContextMenuPolicy(QtCore.Qt.NoContextMenu) | |
| refresh_ui_action = QtWidgets.QAction("Refresh UI", self) | |
| refresh_ui_action.triggered.connect(self.refresh_ui) | |
| import_action = QtWidgets.QAction("Import Options", self) | |
| import_action.triggered.connect(self.open_import_options) | |
| save_state_action = QtWidgets.QAction("Save Window State", self) | |
| save_state_action.triggered.connect(self.save_window_state) | |
| tools_menu.addAction(refresh_ui_action) | |
| tools_menu.addAction(import_action) | |
| tools_menu.addAction(save_state_action) | |
| layout.addWidget(menu_bar) | |
| # Project Directory | |
| dir_layout = QtWidgets.QHBoxLayout() | |
| dir_layout.addWidget(QtWidgets.QLabel("Project Directory:")) | |
| self.dir_field = QtWidgets.QLineEdit() | |
| self.dir_field.setReadOnly(True) | |
| self.dir_field.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.dir_field.customContextMenuRequested.connect(self.show_dir_field_context_menu) | |
| self.update_dir_field() | |
| dir_layout.addWidget(self.dir_field) | |
| self.dir_btn = QtWidgets.QPushButton("Change") | |
| self.dir_btn.clicked.connect(self.change_project_dir) | |
| dir_layout.addWidget(self.dir_btn) | |
| layout.addLayout(dir_layout) | |
| # Session Path Field | |
| session_path_layout = QtWidgets.QHBoxLayout() | |
| session_path_layout.addWidget(QtWidgets.QLabel("Session Path:")) | |
| self.session_path_field = QtWidgets.QLineEdit() | |
| self.session_path_field.setReadOnly(True) | |
| self.session_path_field.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.session_path_field.customContextMenuRequested.connect(self.show_session_path_context_menu) | |
| self.session_path_field.mouseDoubleClickEvent = self.on_session_path_double_click | |
| self.session_path_field.setStyleSheet("color: #888888;") # Changed to brighter grey | |
| self.update_session_path_field() | |
| session_path_layout.addWidget(self.session_path_field) | |
| layout.addLayout(session_path_layout) | |
| # Line Spacer Between Session Path and Current File | |
| line = QtWidgets.QFrame() | |
| line.setFrameShape(QtWidgets.QFrame.HLine) | |
| line.setFrameShadow(QtWidgets.QFrame.Sunken) | |
| layout.addWidget(line) | |
| # Asset Browser Header | |
| browser_header_layout = QtWidgets.QHBoxLayout() | |
| browser_header_layout.addWidget(QtWidgets.QLabel("Asset Browser:")) | |
| self.assets_found_label = QtWidgets.QLabel("Assets Found: 0") | |
| self.assets_found_label.setStyleSheet("font-size: 10px; color: gray;") | |
| browser_header_layout.addStretch() | |
| browser_header_layout.addWidget(self.assets_found_label) | |
| layout.addLayout(browser_header_layout) | |
| # Search Bar | |
| self.search_bar = QtWidgets.QLineEdit() | |
| self.search_bar.setPlaceholderText("e.g., aaron v0408, tv, *sigma*") | |
| self.search_bar.textChanged.connect(self.update_browser) | |
| layout.addWidget(self.search_bar) | |
| # Current File Field (Below Search Bar) | |
| current_file_layout = QtWidgets.QHBoxLayout() | |
| current_file_layout.addWidget(QtWidgets.QLabel("Current File:")) | |
| self.current_file_field = QtWidgets.QLineEdit() | |
| self.current_file_field.setReadOnly(True) | |
| self.current_file_field.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.current_file_field.customContextMenuRequested.connect(self.show_current_file_context_menu) | |
| self.current_file_field.mouseDoubleClickEvent = self.on_current_file_double_click | |
| self.update_current_file_field() | |
| current_file_layout.addWidget(self.current_file_field) | |
| layout.addLayout(current_file_layout) | |
| # Show Full Path Checkbox (Above Browser List) | |
| path_layout = QtWidgets.QHBoxLayout() | |
| self.path_checkbox = QtWidgets.QCheckBox("Show Full Path") | |
| self.path_checkbox.setChecked(self.show_full_path) | |
| self.path_checkbox.stateChanged.connect(self.toggle_path_display) | |
| path_layout.addWidget(self.path_checkbox) | |
| # Directory Indicator Label | |
| self.directory_indicator = QtWidgets.QLabel("You are inside a directory, right click to go back") | |
| self.directory_indicator.setStyleSheet("color: yellow; font-size: 10px;") | |
| path_layout.addWidget(self.directory_indicator) | |
| path_layout.addStretch() | |
| layout.addLayout(path_layout) | |
| # Asset Browser List | |
| self.browser_list = QtWidgets.QListWidget() | |
| self.browser_list.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) | |
| self.browser_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.browser_list.customContextMenuRequested.connect(self.show_browser_context_menu) | |
| self.browser_list.doubleClicked.connect(self.on_browser_double_click) | |
| self.browser_list.itemSelectionChanged.connect(self.update_current_file_field_on_selection) | |
| self.browser_list.setFixedHeight(self.browser_list_height) | |
| self.browser_list.setFont(QtGui.QFont()) # Default font | |
| self.browser_list.setStyleSheet("QListWidget::item:selected { background-color: palette(highlight); }") | |
| layout.addWidget(self.browser_list) | |
| # Double-Click Note | |
| browser_note = QtWidgets.QLabel("Note: Double-click a file to list assets in its directory.") | |
| browser_note.setStyleSheet("font-size: 10px; font-style: italic; color: gray;") | |
| layout.addWidget(browser_note) | |
| # Browser Actions | |
| btn_layout = QtWidgets.QHBoxLayout() | |
| self.new_scene_btn = QtWidgets.QPushButton("New Scene") | |
| self.new_scene_btn.setStyleSheet("QPushButton { background-color: #ADD8E6; color: black; }") | |
| self.new_scene_btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | |
| self.new_scene_btn.clicked.connect(self.new_scene) | |
| btn_layout.addWidget(self.new_scene_btn) | |
| self.open_scene_btn = QtWidgets.QPushButton("Open Scene (Right Click for More Options)") | |
| self.open_scene_btn.setStyleSheet("QPushButton { background-color: #D9B3FF; color: black; }") | |
| self.open_scene_btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | |
| self.open_scene_btn.clicked.connect(self.open_scene) | |
| self.open_scene_btn.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.open_scene_btn.customContextMenuRequested.connect(self.show_open_scene_context_menu) | |
| btn_layout.addWidget(self.open_scene_btn) | |
| self.save_scene_btn = QtWidgets.QPushButton("Save (Right Click for More Options)") | |
| self.save_scene_btn.setStyleSheet("QPushButton { background-color: #CCFFCC; color: black; }") | |
| self.save_scene_btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | |
| self.save_scene_btn.clicked.connect(self.save_scene) | |
| self.save_scene_btn.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.save_scene_btn.customContextMenuRequested.connect(self.show_save_context_menu) | |
| btn_layout.addWidget(self.save_scene_btn) | |
| self.add_to_import_btn = QtWidgets.QPushButton("Add Assignment") | |
| self.add_to_import_btn.setStyleSheet("QPushButton { background-color: #FFCC99; color: black; }") | |
| self.add_to_import_btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | |
| self.add_to_import_btn.clicked.connect(self.add_to_import_list) | |
| btn_layout.addWidget(self.add_to_import_btn) | |
| layout.addLayout(btn_layout) | |
| # Assignments Header | |
| import_header_layout = QtWidgets.QHBoxLayout() | |
| import_header_layout.addWidget(QtWidgets.QLabel("Assignment(s):")) | |
| import_header_layout.addStretch() | |
| sort_label = QtWidgets.QLabel("Sort by:") | |
| self.import_sort_dropdown = QtWidgets.QComboBox() | |
| self.import_sort_dropdown.addItems(["Alphabetical A-Z", "Alphabetical Z-A", "Version", "Asset"]) | |
| self.import_sort_dropdown.setCurrentText(self.import_sort_dropdown_text) | |
| self.import_sort_dropdown.currentTextChanged.connect(self.on_import_sort_changed) | |
| self.import_sort_dropdown.setMinimumWidth(150) | |
| import_header_layout.addWidget(sort_label) | |
| import_header_layout.addWidget(self.import_sort_dropdown) | |
| self.bookmark_dropdown = QtWidgets.QComboBox() | |
| self.bookmark_dropdown.addItem("Bookmarks") | |
| self.update_bookmark_dropdown() | |
| self.bookmark_dropdown.setMinimumWidth(300) | |
| self.bookmark_dropdown.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.bookmark_dropdown.currentIndexChanged.connect(self.on_bookmark_selected) | |
| self.bookmark_dropdown.customContextMenuRequested.connect(self.show_bookmark_context_menu) | |
| import_header_layout.addWidget(self.bookmark_dropdown) | |
| self.assets_registered_label = QtWidgets.QLabel("Asset(s) Registered: 0") | |
| self.assets_registered_label.setStyleSheet("font-size: 10px; color: gray;") | |
| import_header_layout.addWidget(self.assets_registered_label) | |
| layout.addLayout(import_header_layout) | |
| # Assignments List | |
| self.import_list = QtWidgets.QListWidget() | |
| self.import_list.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) | |
| self.import_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.import_list.customContextMenuRequested.connect(self.show_import_list_context_menu) | |
| self.import_list.doubleClicked.connect(self.on_import_list_double_click) | |
| self.import_list.setFixedHeight(self.import_list_height) | |
| self.import_list.setFont(QtGui.QFont()) # Default font | |
| self.import_list.setStyleSheet("QListWidget::item:selected { background-color: palette(highlight); }") | |
| layout.addWidget(self.import_list) | |
| # Import Settings Note | |
| import_note = QtWidgets.QLabel("Note: Namespace and resolve settings must be configured in Maya’s global import settings.") | |
| import_note.setStyleSheet("font-size: 10px; font-style: italic; color: gray;") | |
| layout.addWidget(import_note) | |
| # Clear Import List Checkbox | |
| clear_layout = QtWidgets.QHBoxLayout() | |
| self.clear_after_import_checkbox = QtWidgets.QCheckBox("Clear Item(s) After Import") | |
| self.clear_after_import_checkbox.setChecked(self.clear_after_import) | |
| self.clear_after_import_checkbox.stateChanged.connect(self.update_clear_after_import) | |
| clear_layout.addWidget(self.clear_after_import_checkbox) | |
| clear_layout.addStretch() | |
| layout.addLayout(clear_layout) | |
| # Import Actions | |
| import_action_layout = QtWidgets.QHBoxLayout() | |
| self.import_btn = QtWidgets.QPushButton("Import Selections (Right click to import all)") | |
| self.import_btn.setStyleSheet("QPushButton { background-color: #FFCC99; color: black; }") | |
| self.import_btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | |
| self.import_btn.clicked.connect(self.import_selections) | |
| self.import_btn.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.import_btn.customContextMenuRequested.connect(self.show_import_context_menu) | |
| import_action_layout.addWidget(self.import_btn) | |
| self.remove_from_import_btn = QtWidgets.QPushButton("Remove from Assignment List") | |
| self.remove_from_import_btn.setStyleSheet("QPushButton { background-color: #FF9999; color: black; }") | |
| self.remove_from_import_btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) | |
| self.remove_from_import_btn.clicked.connect(self.remove_from_import_list) | |
| self.remove_from_import_btn.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.remove_from_import_btn.customContextMenuRequested.connect(self.show_remove_from_import_context_menu) | |
| import_action_layout.addWidget(self.remove_from_import_btn) | |
| layout.addLayout(import_action_layout) | |
| # Status Bar | |
| self.status_bar = QtWidgets.QLabel("Ready") | |
| layout.addWidget(self.status_bar) | |
| # Disclaimer | |
| disclaimer = QtWidgets.QLabel("Tool made by Vypac Voeur and should not be distributed.") | |
| disclaimer.setStyleSheet("font-size: 10px; color: gray;") | |
| layout.addWidget(disclaimer) | |
| self.setLayout(layout) | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| def update_directory_indicator(self): | |
| """Show or hide directory indicator based on current_folder.""" | |
| self.directory_indicator.setVisible(bool(self.current_folder)) | |
| def update_clear_after_import(self): | |
| """Update clear_after_import state and save it.""" | |
| self.clear_after_import = self.clear_after_import_checkbox.isChecked() | |
| self.save_window_state() | |
| def refresh_ui(self): | |
| """Refresh all UI lists to reflect current state.""" | |
| self.update_import_list_display() | |
| self.update_browser() | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.status_bar.setText("UI refreshed.") | |
| def open_import_options(self): | |
| """Open Maya's Import Options dialog.""" | |
| try: | |
| mel.eval('ImportOptions; fileOptions2 "Import" "projectViewer Import" false; fo_changeRadioCollection ("Import");') | |
| self.status_bar.setText("Opened Import Options dialog") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error opening Import Options: {str(e)}") | |
| def cleanup_popup_menus(self): | |
| """Clean up temporary popup menus.""" | |
| try: | |
| mel.eval('MarkingMenuPopDown; if (`popupMenu -exists tempMM`) { deleteUI tempMM; } if (`popupMenu -exists tempMM2`) { deleteUI tempMM2; };') | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error cleaning up popup menus: {str(e)}") | |
| def show_open_scene_context_menu(self, pos): | |
| selected = self.browser_list.selectedItems() | |
| menu = QtWidgets.QMenu(self) | |
| open_current_action = menu.addAction("Open in Current Session") | |
| open_new_action = menu.addAction("Open in New Session") | |
| open_current_action.setEnabled(len(selected) == 1) | |
| open_new_action.setEnabled(len(selected) == 1) | |
| action = menu.exec_(self.open_scene_btn.mapToGlobal(pos)) | |
| if action == open_current_action and len(selected) == 1: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| self.open_scene(file_path) | |
| elif action == open_new_action and len(selected) == 1: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| self.open_scene_new_session(file_path) | |
| self.cleanup_popup_menus() | |
| def show_import_context_menu(self, pos): | |
| self.import_list.clearSelection() | |
| self.import_btn.setFocus() | |
| menu = QtWidgets.QMenu(self) | |
| import_all_action = menu.addAction("Import All") | |
| import_all_action.setIcon(self.create_colored_icon("#FFCC99")) | |
| action = menu.exec_(self.import_btn.mapToGlobal(pos)) | |
| if action == import_all_action: | |
| self.import_all() | |
| self.cleanup_popup_menus() | |
| def show_remove_from_import_context_menu(self, pos): | |
| menu = QtWidgets.QMenu(self) | |
| clear_all_action = menu.addAction("Clear All Assignments") | |
| action = menu.exec_(self.remove_from_import_btn.mapToGlobal(pos)) | |
| if action == clear_all_action: | |
| self.clear_all_import_list() | |
| self.cleanup_popup_menus() | |
| def show_save_context_menu(self, pos): | |
| menu = QtWidgets.QMenu(self) | |
| save_as_action = menu.addAction("Save As") | |
| version_up_action = menu.addAction("Version Up") | |
| incremental_action = QtWidgets.QWidgetAction(self) | |
| incremental_checkbox = QtWidgets.QCheckBox("Incremental Save") | |
| incremental_checkbox.setChecked(self.incremental_save) | |
| incremental_checkbox.stateChanged.connect(self.update_incremental_save) | |
| incremental_action.setDefaultWidget(incremental_checkbox) | |
| menu.addAction(incremental_action) | |
| action = menu.exec_(self.save_scene_btn.mapToGlobal(pos)) | |
| if action == save_as_action: | |
| self.save_scene_as() | |
| elif action == version_up_action: | |
| self.version_up_scene() | |
| self.cleanup_popup_menus() | |
| def update_incremental_save(self, state): | |
| """Update incremental_save state.""" | |
| self.incremental_save = bool(state) | |
| self.save_window_state() | |
| def show_current_file_context_menu(self, pos): | |
| """Show context menu for current file field with navigation option.""" | |
| menu = QtWidgets.QMenu(self) | |
| open_location_action = menu.addAction("Open Directory Location") | |
| navigate_browser_action = menu.addAction("Navigate Asset Browser to Folder") | |
| current_file = self.current_file_field.text() | |
| is_valid_path = bool(current_file and current_file != "Untitled") | |
| open_location_action.setEnabled(is_valid_path) | |
| navigate_browser_action.setEnabled(is_valid_path) | |
| action = menu.exec_(self.current_file_field.mapToGlobal(pos)) | |
| if action == open_location_action and is_valid_path: | |
| self.open_current_file_location() | |
| elif action == navigate_browser_action and is_valid_path: | |
| self.navigate_browser_to_current_file() | |
| self.cleanup_popup_menus() | |
| def show_session_path_context_menu(self, pos): | |
| """Show context menu for session path field with navigation options.""" | |
| menu = QtWidgets.QMenu(self) | |
| open_location_action = menu.addAction("Open File Directory") | |
| navigate_browser_action = menu.addAction("Navigate Asset Browser to Folder") | |
| session_path = self.session_path_field.text() | |
| # Validate path: not "Untitled" and exists (file or directory) | |
| full_path = os.path.join(self.project_dir, session_path) if self.project_dir and not os.path.isabs(session_path) else session_path | |
| is_valid_path = bool(session_path and session_path != "Untitled" and os.path.exists(full_path)) | |
| open_location_action.setEnabled(is_valid_path) | |
| navigate_browser_action.setEnabled(is_valid_path) | |
| action = menu.exec_(self.session_path_field.mapToGlobal(pos)) | |
| if action == open_location_action and is_valid_path: | |
| self.open_session_path_location() | |
| elif action == navigate_browser_action and is_valid_path: | |
| self.navigate_browser_to_session_path() | |
| self.cleanup_popup_menus() | |
| def show_import_list_context_menu(self, pos): | |
| item_at_pos = self.import_list.itemAt(pos) | |
| if item_at_pos and item_at_pos not in self.import_list.selectedItems(): | |
| self.import_list.clearSelection() | |
| item_at_pos.setSelected(True) | |
| selected_items = self.import_list.selectedItems() | |
| menu = QtWidgets.QMenu(self) | |
| remove_action = menu.addAction("Remove from Assignment List") | |
| rename_action = menu.addAction("Rename File") | |
| open_current_action = menu.addAction("Open in Current Session") | |
| open_new_action = menu.addAction("Open in New Session") | |
| open_location_action = menu.addAction("Open Source Location") | |
| menu.addSeparator() | |
| save_json_action = menu.addAction("Save Assignments to JSON") | |
| load_json_action = menu.addAction("Load Assignments from JSON") | |
| menu.addSeparator() | |
| color_menu = menu.addMenu("Set Text Color") | |
| red_action = color_menu.addAction("Red") | |
| yellow_action = color_menu.addAction("Yellow") | |
| green_action = color_menu.addAction("Green") | |
| white_action = color_menu.addAction("White") | |
| menu.addSeparator() | |
| taller_action = menu.addAction("Make Taller") | |
| shorter_action = menu.addAction("Make Shorter") | |
| taller_action.setEnabled(self.import_list_height < 400) | |
| shorter_action.setEnabled(self.import_list_height > 100) | |
| action = menu.exec_(self.import_list.mapToGlobal(pos)) | |
| if action == remove_action: | |
| self.remove_from_import_list() | |
| elif action == rename_action and len(selected_items) == 1: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == selected_items[0]), None) | |
| if file_path: | |
| self.rename_file(file_path, from_import_list=True) | |
| elif action == open_current_action and len(selected_items) == 1: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == selected_items[0]), None) | |
| if file_path: | |
| self.open_scene(file_path) | |
| elif action == open_new_action and len(selected_items) == 1: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == selected_items[0]), None) | |
| if file_path: | |
| self.open_scene_new_session(file_path) | |
| elif action == open_location_action and len(selected_items) == 1: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == selected_items[0]), None) | |
| if file_path: | |
| self.open_source_location(file_path) | |
| elif action == save_json_action: | |
| self.save_assignments_to_json() | |
| elif action == load_json_action: | |
| self.load_assignments_from_json() | |
| elif action in (red_action, yellow_action, green_action, white_action): | |
| color = {"Red": "red", "Yellow": "yellow", "Green": "green", "White": "white"}[action.text()] | |
| self.set_import_item_color(selected_items, color) | |
| elif action == taller_action: | |
| self.import_list_height = min(self.import_list_height + 50, 400) | |
| self.import_list.setFixedHeight(self.import_list_height) | |
| self.import_list.updateGeometry() | |
| self.status_bar.setText(f"Assignments list height set to {self.import_list_height}px") | |
| elif action == shorter_action: | |
| self.import_list_height = max(self.import_list_height - 50, 100) | |
| self.import_list.setFixedHeight(self.import_list_height) | |
| self.import_list.updateGeometry() | |
| self.status_bar.setText(f"Assignments list height set to {self.import_list_height}px") | |
| self.cleanup_popup_menus() | |
| def show_dir_field_context_menu(self, pos): | |
| menu = QtWidgets.QMenu(self) | |
| if not self.project_history: | |
| menu.addAction("No previous directories").setEnabled(False) | |
| else: | |
| for entry in sorted(self.project_history, key=lambda x: x["timestamp"], reverse=True): | |
| path = entry["path"] | |
| action = menu.addAction(path) | |
| action.setData(path) | |
| action = menu.exec_(self.dir_field.mapToGlobal(pos)) | |
| if action and action.data(): | |
| self.change_project_dir(action.data()) | |
| self.cleanup_popup_menus() | |
| def show_browser_context_menu(self, pos): | |
| item_at_pos = self.browser_list.itemAt(pos) | |
| if item_at_pos and item_at_pos not in self.browser_list.selectedItems(): | |
| self.browser_list.clearSelection() | |
| item_at_pos.setSelected(True) | |
| selected_items = self.browser_list.selectedItems() | |
| menu = QtWidgets.QMenu(self) | |
| go_back_action = menu.addAction("Go Back") | |
| rename_action = menu.addAction("Rename File") | |
| info_action = menu.addAction("Show Asset Info") | |
| open_source_action = menu.addAction("Open Source") | |
| open_location_action = menu.addAction("Open Source Location") | |
| delete_action = menu.addAction("Delete File") | |
| disable_prompt_action = QtWidgets.QWidgetAction(self) | |
| disable_prompt_checkbox = QtWidgets.QCheckBox("Disable Delete Prompt") | |
| disable_prompt_checkbox.setChecked(not self.disable_delete_prompt) | |
| disable_prompt_checkbox.stateChanged.connect(self.update_delete_prompt_state) | |
| disable_prompt_action.setDefaultWidget(disable_prompt_checkbox) | |
| menu.addAction(disable_prompt_action) | |
| menu.addSeparator() | |
| refresh_action = menu.addAction("Refresh List") | |
| menu.addSeparator() | |
| taller_action = menu.addAction("Make Taller") | |
| shorter_action = menu.addAction("Make Shorter") | |
| is_single_file = len(selected_items) == 1 | |
| if is_single_file: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected_items[0].text() or asset == selected[0].text()), None) | |
| is_single_file = file_path and os.path.isfile(self.clean_file_path(file_path)) | |
| rename_action.setEnabled(is_single_file) | |
| delete_action.setEnabled(len(selected_items) > 0) | |
| info_action.setEnabled(len(selected_items) > 0) | |
| open_source_action.setEnabled(len(selected_items) == 1) | |
| open_location_action.setEnabled(len(selected_items) == 1) | |
| refresh_action.setEnabled(True) | |
| go_back_action.setEnabled(self.current_folder is not None) | |
| taller_action.setEnabled(self.browser_list_height < 400) | |
| shorter_action.setEnabled(self.browser_list_height > 100) | |
| action = menu.exec_(self.browser_list.mapToGlobal(pos)) | |
| if action == go_back_action and self.current_folder is not None: | |
| self.current_folder = None | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText("Returned to full asset list.") | |
| elif action == rename_action and is_single_file: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected_items[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| self.rename_file(file_path) | |
| elif action == delete_action and selected_items: | |
| self.delete_selected_files(selected_items) | |
| elif action == info_action: | |
| info_text = "" | |
| for item in selected_items: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == item.text() or asset == item.text()), None) | |
| if file_path: | |
| try: | |
| clean_file_path = self.clean_file_path(file_path) | |
| ctime = time.ctime(os.path.getctime(clean_file_path)) | |
| owner = str(os.stat(clean_file_path).st_uid) | |
| info_text += f"Asset: {os.path.basename(clean_file_path)}\nCreated: {ctime}\nOwner UID: {owner}\n\n" | |
| except OSError as e: | |
| info_text += f"Asset: {os.path.basename(clean_file_path)}\nError retrieving info: {str(e)}\n\n" | |
| if info_text: | |
| QtWidgets.QMessageBox.information(self, "Asset Info", info_text.strip()) | |
| elif action == open_source_action and len(selected_items) == 1: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected_items[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| self.open_scene(file_path) | |
| elif action == open_location_action and len(selected_items) == 1: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected_items[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| self.open_source_location(file_path) | |
| elif action == refresh_action: | |
| self.full_asset_cache = self.cache_assets() | |
| if self.current_folder: | |
| self.asset_cache = self.cache_folder_assets(self.current_folder) | |
| else: | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.status_bar.setText("Asset browser list refreshed.") | |
| elif action == taller_action: | |
| self.browser_list_height = min(self.browser_list_height + 50, 400) | |
| self.browser_list.setFixedHeight(self.browser_list_height) | |
| self.browser_list.updateGeometry() | |
| self.status_bar.setText(f"Asset Browser list height set to {self.browser_list_height}px") | |
| elif action == shorter_action: | |
| self.browser_list_height = max(self.browser_list_height - 50, 100) | |
| self.browser_list.setFixedHeight(self.browser_list_height) | |
| self.browser_list.updateGeometry() | |
| self.status_bar.setText(f"Asset Browser list height set to {self.browser_list_height}px") | |
| self.cleanup_popup_menus() | |
| def show_bookmark_context_menu(self, pos): | |
| menu = QtWidgets.QMenu(self) | |
| add_bookmark_action = menu.addAction("Add Bookmark") | |
| delete_bookmark_action = menu.addAction("Delete Current Bookmark") | |
| current_index = self.bookmark_dropdown.currentIndex() | |
| delete_bookmark_action.setEnabled(current_index > 0) | |
| action = menu.exec_(self.bookmark_dropdown.mapToGlobal(pos)) | |
| if action == add_bookmark_action: | |
| self.add_bookmark() | |
| elif action == delete_bookmark_action and current_index > 0: | |
| self.delete_bookmark(current_index) | |
| self.cleanup_popup_menus() | |
| def add_bookmark(self): | |
| """Prompt for bookmark name and add current path to bookmarks.""" | |
| if not self.current_folder: | |
| QtWidgets.QMessageBox.warning( | |
| self, | |
| "Invalid Path", | |
| "Please choose a valid path to bookmark.", | |
| QtWidgets.QMessageBox.Ok | |
| ) | |
| self.status_bar.setText("Bookmark cancelled: No valid path.") | |
| return | |
| name, ok = QtWidgets.QInputDialog.getText( | |
| self, | |
| "Add Bookmark", | |
| "Enter name for bookmark:", | |
| QtWidgets.QLineEdit.Normal, | |
| os.path.basename(self.current_folder) | |
| ) | |
| if ok and name: | |
| self.bookmarks.append({"name": name, "path": self.current_folder}) | |
| self.save_bookmarks() | |
| self.update_bookmark_dropdown() | |
| for i in range(self.bookmark_dropdown.count()): | |
| if self.bookmark_dropdown.itemText(i) == name: | |
| self.bookmark_dropdown.setCurrentIndex(i) | |
| break | |
| self.status_bar.setText(f"Bookmarked {name}: {self.current_folder}") | |
| def delete_bookmark(self, index): | |
| """Delete the bookmark at the given dropdown index.""" | |
| if 0 < index <= len(self.bookmarks): | |
| bookmark = self.bookmarks.pop(index - 1) | |
| self.save_bookmarks() | |
| self.update_bookmark_dropdown() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText(f"Deleted bookmark: {bookmark['name']}") | |
| def update_bookmark_dropdown(self): | |
| """Update the bookmark dropdown with current bookmarks.""" | |
| current_text = self.bookmark_dropdown.currentText() | |
| self.bookmark_dropdown.clear() | |
| self.bookmark_dropdown.addItem("Bookmarks") | |
| for bookmark in self.bookmarks: | |
| self.bookmark_dropdown.addItem(bookmark["name"], userData=bookmark["path"]) | |
| for i in range(self.bookmark_dropdown.count()): | |
| if self.bookmark_dropdown.itemText(i) == current_text: | |
| self.bookmark_dropdown.setCurrentIndex(i) | |
| break | |
| def on_bookmark_selected(self, index): | |
| """Navigate to the bookmarked path when selected.""" | |
| if index > 0: | |
| path = self.bookmark_dropdown.itemData(index) | |
| if os.path.exists(path): | |
| self.current_folder = path | |
| self.asset_cache = self.cache_folder_assets(path) | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Navigated to bookmark: {self.bookmark_dropdown.currentText()}") | |
| else: | |
| self.status_bar.setText(f"Bookmark path not found: {path}") | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| else: | |
| self.current_folder = None | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText("Returned to full asset list.") | |
| def load_bookmarks(self): | |
| """Load bookmarks from JSON file.""" | |
| bookmark_file = os.path.join(os.path.expanduser("~"), "Documents", "maya_json", "bookmarks.json") | |
| if os.path.exists(bookmark_file): | |
| try: | |
| with open(bookmark_file, 'r') as f: | |
| bookmarks = json.load(f) | |
| return [b for b in bookmarks if isinstance(b, dict) and "name" in b and "path" in b] | |
| except (json.JSONDecodeError, IOError): | |
| return [] | |
| return [] | |
| def save_bookmarks(self): | |
| """Save bookmarks to JSON file.""" | |
| bookmark_dir = os.path.join(os.path.expanduser("~"), "Documents", "maya_json") | |
| bookmark_file = os.path.join(bookmark_dir, "bookmarks.json") | |
| os.makedirs(bookmark_dir, exist_ok=True) | |
| try: | |
| with open(bookmark_file, 'w') as f: | |
| json.dump(self.bookmarks, f, indent=4) | |
| except IOError as e: | |
| self.status_bar.setText(f"Error saving bookmarks: {str(e)}") | |
| def save_assignments_to_json(self): | |
| """Save the assignments list to a JSON file using a file dialog.""" | |
| default_dir = os.path.join(os.path.expanduser("~"), "Documents", "maya_json") | |
| os.makedirs(default_dir, exist_ok=True) | |
| file_path, _ = QtWidgets.QFileDialog.getSaveFileName( | |
| self, | |
| "Save Assignments", | |
| os.path.join(default_dir, "assignments.json"), | |
| "JSON Files (*.json)" | |
| ) | |
| if not file_path: | |
| self.status_bar.setText("Save assignments cancelled.") | |
| return | |
| if not file_path.endswith(".json"): | |
| file_path += ".json" | |
| assignments = [ | |
| {"path": path, "color": info.get("color", "white")} | |
| for path, info in self.import_items.items() | |
| ] | |
| try: | |
| with open(file_path, 'w') as f: | |
| json.dump(assignments, f, indent=4) | |
| self.status_bar.setText(f"Saved assignments to {file_path}") | |
| except IOError as e: | |
| self.status_bar.setText(f"Error saving assignments: {str(e)}") | |
| def load_assignments_from_json(self): | |
| """Load assignments from a JSON file using a file dialog.""" | |
| default_dir = os.path.join(os.path.expanduser("~"), "Documents", "maya_json") | |
| file_path, _ = QtWidgets.QFileDialog.getOpenFileName( | |
| self, | |
| "Load Assignments", | |
| default_dir, | |
| "JSON Files (*.json)" | |
| ) | |
| if not file_path: | |
| self.status_bar.setText("Load assignments cancelled.") | |
| return | |
| if not os.path.exists(file_path): | |
| self.status_bar.setText(f"No assignments file found at {file_path}") | |
| return | |
| try: | |
| with open(file_path, 'r') as f: | |
| assignments = json.load(f) | |
| added_count = 0 | |
| for assignment in assignments: | |
| path = assignment.get("path") | |
| color = assignment.get("color", "white") | |
| if path and os.path.exists(path) and path not in self.import_items: | |
| display_text = path if self.show_full_path else os.path.basename(path) | |
| list_item = QtWidgets.QListWidgetItem(display_text) | |
| list_item.setForeground(QtGui.QColor(color)) | |
| self.import_list.addItem(list_item) | |
| self.import_items[path] = {"item": list_item, "color": color} | |
| added_count += 1 | |
| self.update_import_list_display() | |
| self.status_bar.setText(f"Loaded {added_count} assignments from {file_path}") | |
| except (json.JSONDecodeError, IOError) as e: | |
| self.status_bar.setText(f"Error loading assignments: {str(e)}") | |
| def set_import_item_color(self, selected_items, color): | |
| """Set the text color for selected items in the assignments list.""" | |
| for item in selected_items: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == item), None) | |
| if file_path: | |
| self.import_items[file_path]["color"] = color | |
| item.setForeground(QtGui.QColor(color)) | |
| self.status_bar.setText(f"Set color to {color} for {len(selected_items)} item(s)") | |
| def open_session_path_location(self): | |
| """Open the directory of the session path field.""" | |
| session_path = self.session_path_field.text() | |
| if session_path and session_path != "Untitled": | |
| try: | |
| full_path = os.path.join(self.project_dir, session_path) if self.project_dir and not os.path.isabs(session_path) else session_path | |
| path = full_path | |
| if not os.path.isdir(path): | |
| path = os.path.dirname(path) | |
| if not os.path.exists(path): | |
| self.status_bar.setText(f"Directory not found: {path}") | |
| return | |
| os.startfile(path) | |
| self.status_bar.setText(f"Opened directory: {path}") | |
| except Exception as e: | |
| self.status_bar.setText(f"Error opening directory: {str(e)}") | |
| else: | |
| self.status_bar.setText("No valid path to open.") | |
| def navigate_browser_to_session_path(self): | |
| """Navigate the Asset Browser to the folder in the session path field.""" | |
| session_path = self.session_path_field.text() | |
| if session_path and session_path != "Untitled": | |
| full_path = os.path.join(self.project_dir, session_path) if self.project_dir and not os.path.isabs(session_path) else session_path | |
| folder_path = full_path | |
| if not os.path.isdir(full_path): | |
| folder_path = os.path.dirname(full_path) | |
| if not os.path.exists(folder_path): | |
| self.status_bar.setText(f"Folder not found: {folder_path}") | |
| return | |
| self.current_folder = folder_path | |
| self.asset_cache = self.cache_folder_assets(folder_path) | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText(f"Navigated Asset Browser to: {folder_path}") | |
| else: | |
| self.status_bar.setText("No valid path to navigate.") | |
| def navigate_browser_to_current_file(self): | |
| """Navigate the Asset Browser to the folder in the current file field.""" | |
| current_file = self.current_file_field.text() | |
| if current_file and current_file != "Untitled": | |
| full_path = os.path.join(self.project_dir, current_file) if self.project_dir and not os.path.isabs(current_file) else current_file | |
| folder_path = full_path | |
| if not os.path.isdir(full_path): | |
| folder_path = os.path.dirname(full_path) | |
| if not os.path.exists(folder_path): | |
| self.status_bar.setText(f"Folder not found: {folder_path}") | |
| return | |
| self.current_folder = folder_path | |
| self.asset_cache = self.cache_folder_assets(folder_path) | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText(f"Navigated Asset Browser to: {folder_path}") | |
| else: | |
| self.status_bar.setText("No valid path to navigate.") | |
| def on_session_path_double_click(self, event): | |
| """Handle double-click on session path field to navigate Asset Browser.""" | |
| self.navigate_browser_to_session_path() | |
| QtWidgets.QLineEdit.mouseDoubleClickEvent(self.session_path_field, event) | |
| def on_current_file_double_click(self, event): | |
| """Handle double-click on current file field to navigate Asset Browser.""" | |
| self.navigate_browser_to_current_file() | |
| QtWidgets.QLineEdit.mouseDoubleClickEvent(self.current_file_field, event) | |
| def update_delete_prompt_state(self, state): | |
| """Update the disable_delete_prompt state and save it.""" | |
| self.disable_delete_prompt = not bool(state) | |
| self.save_window_state() | |
| def delete_selected_files(self, selected_items): | |
| """Delete selected files with optional confirmation.""" | |
| current_scene = cmds.file(q=True, sn=True) | |
| file_paths = [] | |
| for item in selected_items: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == item.text() or asset == item.text()), None) | |
| if file_path: | |
| clean_file_path = self.clean_file_path(file_path) | |
| if clean_file_path == current_scene: | |
| QtWidgets.QMessageBox.warning( | |
| self, | |
| "Cannot Delete", | |
| f"Cannot delete {os.path.basename(clean_file_path)} as it is the current open session.", | |
| QtWidgets.QMessageBox.Ok | |
| ) | |
| return | |
| file_paths.append(clean_file_path) | |
| if not file_paths: | |
| self.status_bar.setText("No valid files selected to delete.") | |
| return | |
| if not self.disable_delete_prompt: | |
| file_list = "\n".join(os.path.basename(fp) for fp in file_paths) | |
| reply = QtWidgets.QMessageBox.warning( | |
| self, | |
| "Confirm Delete", | |
| f"Are you sure you want to delete the following files?\n{file_list}\nThis action cannot be undone.", | |
| QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, | |
| QtWidgets.QMessageBox.No | |
| ) | |
| if reply != QtWidgets.QMessageBox.Yes: | |
| self.status_bar.setText("Delete cancelled.") | |
| return | |
| deleted_count = 0 | |
| for file_path in file_paths: | |
| try: | |
| os.remove(file_path) | |
| deleted_count += 1 | |
| except OSError as e: | |
| self.status_bar.setText(f"Error deleting {os.path.basename(file_path)}: {str(e)}") | |
| continue | |
| if deleted_count > 0: | |
| self.full_asset_cache = self.cache_assets() | |
| if self.current_folder: | |
| self.asset_cache = self.cache_folder_assets(self.current_folder) | |
| else: | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Deleted {deleted_count} file(s).") | |
| else: | |
| self.status_bar.setText("No files deleted due to errors.") | |
| def rename_file(self, file_path, from_import_list=False): | |
| """Prompt to rename a file and update lists.""" | |
| clean_file_path = self.clean_file_path(file_path) | |
| current_scene = cmds.file(q=True, sn=True) | |
| if clean_file_path == current_scene: | |
| QtWidgets.QMessageBox.warning( | |
| self, | |
| "Cannot Rename", | |
| f"Cannot rename {os.path.basename(clean_file_path)} as it is the current open session.", | |
| QtWidgets.QMessageBox.Ok | |
| ) | |
| self.status_bar.setText("Rename cancelled: File is open session.") | |
| return | |
| old_name = os.path.basename(file_path) | |
| old_ext = os.path.splitext(old_name)[1] | |
| base_name = os.path.splitext(old_name)[0] | |
| new_name, ok = QtWidgets.QInputDialog.getText( | |
| self, | |
| "Rename File", | |
| f"Enter new name for {old_name} (extension {old_ext} will be preserved):", | |
| QtWidgets.QLineEdit.Normal, | |
| base_name | |
| ) | |
| if not ok or not new_name: | |
| self.status_bar.setText("Rename cancelled.") | |
| return | |
| new_name = re.sub(r'[^a-zA-Z0-9_.-]', '_', new_name.strip()) | |
| if not new_name: | |
| self.status_bar.setText("Error: Invalid file name.") | |
| return | |
| new_file_path = os.path.join(os.path.dirname(file_path), f"{new_name}{old_ext}") | |
| if os.path.exists(new_file_path): | |
| self.status_bar.setText(f"Error: File {new_name}{old_ext} already exists.") | |
| return | |
| try: | |
| os.rename(file_path, new_file_path) | |
| self.full_asset_cache = self.cache_assets() | |
| if self.current_folder: | |
| self.asset_cache = self.cache_folder_assets(self.current_folder) | |
| else: | |
| self.asset_cache = self.full_asset_cache[:] | |
| if from_import_list: | |
| if file_path in self.import_items: | |
| item_info = self.import_items.pop(file_path) | |
| display_text = new_file_path if self.show_full_path else os.path.basename(new_file_path) | |
| item_info["item"].setText(display_text) | |
| self.import_items[new_file_path] = item_info | |
| self.update_browser() | |
| self.update_import_list_display() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Renamed {old_name} to {new_name}{old_ext}.") | |
| except OSError as e: | |
| self.status_bar.setText(f"Error renaming {old_name}: {str(e)}") | |
| def on_browser_double_click(self, index): | |
| """Handle double-click to navigate to the directory of the selected file or folder.""" | |
| selected = self.browser_list.selectedItems() | |
| if len(selected) != 1: | |
| self.status_bar.setText("Please select one asset or folder to navigate.") | |
| return | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected[0].text() or asset == selected[0].text()), None) | |
| if not file_path: | |
| self.status_bar.setText("Error: Selected asset not found.") | |
| return | |
| clean_file_path = self.clean_file_path(file_path) | |
| folder_path = os.path.dirname(clean_file_path) if os.path.isfile(clean_file_path) else clean_file_path | |
| if not os.path.exists(folder_path): | |
| self.status_bar.setText(f"Error: Folder not found for {os.path.basename(clean_file_path)}.") | |
| return | |
| self.current_folder = folder_path | |
| self.asset_cache = self.cache_folder_assets(folder_path) | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText(f"Navigated to folder: {folder_path}") | |
| def on_import_list_double_click(self, index): | |
| """Handle double-click on assignments list to navigate Asset Browser to item's directory.""" | |
| selected = self.import_list.selectedItems() | |
| if len(selected) != 1: | |
| self.status_bar.setText("Please select one assignment to navigate.") | |
| return | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == selected[0]), None) | |
| if not file_path: | |
| self.status_bar.setText("Error: Selected assignment not found.") | |
| return | |
| clean_file_path = self.clean_file_path(file_path) | |
| if not os.path.exists(clean_file_path): | |
| self.status_bar.setText(f"Error: File not found: {os.path.basename(clean_file_path)}") | |
| return | |
| folder_path = os.path.dirname(clean_file_path) | |
| if not os.path.exists(folder_path): | |
| self.status_bar.setText(f"Error: Folder not found for {os.path.basename(clean_file_path)}") | |
| return | |
| self.current_folder = folder_path | |
| self.asset_cache = self.cache_folder_assets(folder_path) | |
| self.update_browser() | |
| self.update_directory_indicator() | |
| self.bookmark_dropdown.setCurrentIndex(0) | |
| self.status_bar.setText(f"Navigated to folder: {folder_path}") | |
| def clear_all_import_list(self): | |
| if not self.import_items: | |
| self.status_bar.setText("Assignments list is already empty.") | |
| return | |
| self.import_list.clearSelection() | |
| self.import_list.clear() | |
| self.import_items.clear() | |
| self.update_import_list_display() | |
| self.status_bar.setText("Cleared all assignments from list.") | |
| def update_dir_field(self): | |
| self.dir_field.setText("Please select project directory" if not self.asset_cache else self.project_dir) | |
| def update_current_file_field(self): | |
| """Update the current file field with the current scene's file path, using relative path if possible.""" | |
| current_file = cmds.file(q=True, sn=True) | |
| if current_file and self.project_dir and current_file.startswith(self.project_dir): | |
| relative_path = os.path.relpath(current_file, self.project_dir) | |
| self.current_file_field.setText(relative_path) | |
| else: | |
| self.current_file_field.setText(current_file if current_file else "Untitled") | |
| def update_current_file_field_on_selection(self): | |
| """Update the current file field based on browser list selection, using relative path if possible.""" | |
| selected = self.browser_list.selectedItems() | |
| if len(selected) == 1: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| clean_file_path = self.clean_file_path(file_path) | |
| if self.project_dir and clean_file_path.startswith(self.project_dir): | |
| relative_path = os.path.relpath(clean_file_path, self.project_dir) | |
| self.current_file_field.setText(relative_path) | |
| else: | |
| self.current_file_field.setText(clean_file_path) | |
| return | |
| self.update_current_file_field() | |
| def update_session_path_field(self): | |
| """Update the session path field with the current scene's file path, using relative path if possible.""" | |
| current_file = cmds.file(q=True, sn=True) | |
| if current_file and self.project_dir and current_file.startswith(self.project_dir): | |
| relative_path = os.path.relpath(current_file, self.project_dir) | |
| self.session_path_field.setText(relative_path) | |
| else: | |
| self.session_path_field.setText(current_file if current_file else "Untitled") | |
| def open_current_file_location(self): | |
| """Open the directory of the current file field.""" | |
| current_file = self.current_file_field.text() | |
| if current_file and current_file != "Untitled": | |
| try: | |
| full_path = os.path.join(self.project_dir, current_file) if self.project_dir and not os.path.isabs(current_file) else current_file | |
| directory = os.path.dirname(full_path) | |
| if not os.path.exists(directory): | |
| self.status_bar.setText(f"Directory not found for {os.path.basename(current_file)}.") | |
| return | |
| os.startfile(directory) | |
| self.status_bar.setText(f"Opened directory for {os.path.basename(current_file)}.") | |
| except Exception as e: | |
| self.status_bar.setText(f"Error opening directory: {str(e)}") | |
| else: | |
| self.status_bar.setText("No valid file path to open.") | |
| def update_import_list_display(self): | |
| self.import_list.clear() | |
| import_items = [] | |
| for file_path in self.import_items.keys(): | |
| file_name = os.path.basename(file_path) | |
| name_match = re.match(r'^(.*?)(?:_v\d+)?\.(ma|mb|fbx)$', file_name) | |
| name = name_match.group(1) if name_match else file_name | |
| version_match = re.search(r'_v(\d+)', file_name) | |
| version = version_match.group(1) if version_match else "0" | |
| asset = file_name | |
| display_text = file_path if self.show_full_path else file_name | |
| import_items.append({ | |
| "text": display_text, | |
| "file_path": file_path, | |
| "name": name, | |
| "sort_name": file_name if not self.show_full_path else file_path, | |
| "version": version, | |
| "asset": asset | |
| }) | |
| if self.import_sort_mode == "alphabetical_asc": | |
| import_items.sort(key=lambda x: (x["sort_name"].lower(), x["version"], x["asset"])) | |
| elif self.import_sort_mode == "alphabetical_desc": | |
| import_items.sort(key=lambda x: (x["sort_name"].lower(), x["version"], x["asset"]), reverse=True) | |
| elif self.import_sort_mode == "version": | |
| import_items.sort(key=lambda x: (x["version"], x["name"], x["asset"])) | |
| else: # "asset" | |
| import_items.sort(key=lambda x: (x["asset"], x["name"], x["version"])) | |
| for item_data in import_items: | |
| list_item = QtWidgets.QListWidgetItem(item_data["text"]) | |
| color = self.import_items[item_data["file_path"]].get("color", "white") | |
| list_item.setForeground(QtGui.QColor(color)) | |
| self.import_list.addItem(list_item) | |
| self.import_items[item_data["file_path"]]["item"] = list_item | |
| self.assets_registered_label.setText(f"Asset(s) Registered: {len(self.import_items)}") | |
| self.update_remove_button_state() | |
| def on_import_sort_changed(self, text): | |
| sort_mapping = { | |
| "Alphabetical A-Z": "alphabetical_asc", | |
| "Alphabetical Z-A": "alphabetical_desc", | |
| "Version": "version", | |
| "Asset": "asset" | |
| } | |
| self.import_sort_mode = sort_mapping.get(text, "asset") | |
| self.update_import_list_display() | |
| self.save_window_state() | |
| def remove_from_import_list(self): | |
| selected = self.import_list.selectedItems() | |
| if not selected: | |
| self.status_bar.setText("Please make at least one selection.") | |
| return | |
| for item in selected: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == item), None) | |
| if file_path: | |
| self.import_list.takeItem(self.import_list.row(item)) | |
| del self.import_items[file_path] | |
| self.update_import_list_display() | |
| self.status_bar.setText(f"Removed {len(selected)} assignments from list.") | |
| def add_to_import_list(self): | |
| selected = self.browser_list.selectedItems() | |
| if not selected: | |
| self.status_bar.setText("No items selected to add.") | |
| return | |
| added_count = 0 | |
| for item in selected: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == item.text() or asset == item.text()), None) | |
| if file_path and file_path not in self.import_items: | |
| display_text = file_path if self.show_full_path else os.path.basename(file_path) | |
| list_item = QtWidgets.QListWidgetItem(display_text) | |
| list_item.setForeground(QtGui.QColor("white")) | |
| self.import_list.addItem(list_item) | |
| self.import_items[file_path] = {"item": list_item, "color": "white"} | |
| added_count += 1 | |
| self.update_import_list_display() | |
| self.status_bar.setText(f"Added {added_count} assignments to list.") | |
| def initialize_import_list(self): | |
| self.import_items.clear() | |
| self.import_list.clear() | |
| self.assets_registered_label.setText("Asset(s) Registered: 0") | |
| def update_browser(self): | |
| self.browser_list.clear() | |
| search_input = self.search_bar.text().strip() | |
| displayed_assets = [] | |
| if not search_input: | |
| displayed_assets = self.asset_cache | |
| for asset in displayed_assets: | |
| display_text = asset if self.show_full_path else os.path.basename(asset) | |
| self.browser_list.addItem(display_text) | |
| self.status_bar.setText(f"Showing {len(self.asset_cache)} assets{' in folder: ' + self.current_folder if self.current_folder else ''}.") | |
| else: | |
| search_input = search_input.replace(",", " ") | |
| tokens = [token.strip() for token in search_input.split() if token.strip()] | |
| matches = 0 | |
| for asset in self.asset_cache: | |
| asset_name = os.path.basename(asset).lower() | |
| if all(fnmatch.fnmatch(asset_name, f"*{token}*") for token in tokens): | |
| display_text = asset if self.show_full_path else os.path.basename(asset) | |
| self.browser_list.addItem(display_text) | |
| displayed_assets.append(asset) | |
| matches += 1 | |
| self.status_bar.setText(f"Found {matches} matches for '{search_input}'{' in folder: ' + self.current_folder if self.current_folder else ''}.") | |
| self.update_dir_field() | |
| self.assets_found_label.setText(f"Assets Found: {len(displayed_assets)}") | |
| def toggle_path_display(self): | |
| self.show_full_path = self.path_checkbox.isChecked() | |
| self.update_browser() | |
| self.update_import_list_display() | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| def import_selections(self): | |
| selected = self.import_list.selectedItems() | |
| if not selected: | |
| self.status_bar.setText("No assignments selected to import.") | |
| return | |
| self.import_assets(selected) | |
| def import_all(self): | |
| if not self.import_items: | |
| self.status_bar.setText("No assignments to import.") | |
| return | |
| selected = [self.import_list.item(i) for i in range(self.import_list.count())] | |
| self.import_assets(selected) | |
| def import_assets(self, selected_items): | |
| imported_count = 0 | |
| for index, item in enumerate(selected_items): | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == item), None) | |
| if not file_path: | |
| continue | |
| clean_file_path = self.clean_file_path(file_path) | |
| if not clean_file_path or not os.path.exists(clean_file_path): | |
| self.status_bar.setText(f"Error: {clean_file_path if clean_file_path else 'Unknown file'} not found.") | |
| continue | |
| is_valid, validation_error = self.validate_file(clean_file_path) | |
| if not is_valid: | |
| self.status_bar.setText(f"Error: {os.path.basename(clean_file_path)} - {validation_error}") | |
| continue | |
| try: | |
| file_type = "mayaAscii" if clean_file_path.endswith(".ma") else "mayaBinary" if clean_file_path.endswith(".mb") else "FBX" | |
| cmds.file(clean_file_path, i=True, type=file_type, preserveReferences=False, loadReferenceDepth="all") | |
| imported_count += 1 | |
| try: | |
| mel.eval('editMenuUpdate MayaWindow|mainEditMenu;') | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error updating edit menu: {str(e)}") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error importing {os.path.basename(clean_file_path)}: {str(e)}") | |
| continue | |
| if imported_count > 0: | |
| self.status_bar.setText(f"Imported {imported_count} assignment(s) into scene") | |
| else: | |
| self.status_bar.setText("No assignments imported due to errors.") | |
| if self.clear_after_import: | |
| if len(selected_items) < self.import_list.count(): | |
| for item in selected_items: | |
| file_path = next((path for path, info in self.import_items.items() if info["item"] == item), None) | |
| if file_path: | |
| self.import_list.takeItem(self.import_list.row(item)) | |
| del self.import_items[file_path] | |
| else: | |
| self.clear_all_import_list() | |
| self.update_import_list_display() | |
| def new_scene(self): | |
| """Create a new Maya scene with confirmation if modified.""" | |
| if cmds.file(q=True, modified=True): | |
| reply = QtWidgets.QMessageBox.warning( | |
| self, | |
| "Confirm New Scene", | |
| "The current scene has unsaved changes. Creating a new scene will discard them. Continue?", | |
| QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, | |
| QtWidgets.QMessageBox.No | |
| ) | |
| if reply != QtWidgets.QMessageBox.Yes: | |
| self.status_bar.setText("New scene cancelled.") | |
| return | |
| try: | |
| cmds.file(new=True, force=True) | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.status_bar.setText("Created new scene.") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error creating new scene: {str(e)}") | |
| def save_scene(self): | |
| """Save the current scene to its existing file or prompt for a location, with incremental save if enabled.""" | |
| current_file = cmds.file(q=True, sn=True) | |
| if current_file and os.path.exists(current_file): | |
| if self.incremental_save: | |
| directory = os.path.dirname(current_file) | |
| file_name = os.path.basename(current_file) | |
| ext = os.path.splitext(file_name)[1] | |
| base_name = os.path.splitext(file_name)[0] | |
| version_match = re.search(r'_v(\d+)$', base_name) | |
| if version_match: | |
| version_num = int(version_match.group(1)) | |
| new_version = f"{version_num + 1:03d}" | |
| new_base_name = re.sub(r'_v\d+$', f'_v{new_version}', base_name) | |
| else: | |
| new_base_name = f"{base_name}_v001" | |
| new_version = "001" | |
| new_file = os.path.join(directory, f"{new_base_name}{ext}") | |
| suffix = 0 | |
| while os.path.exists(new_file): | |
| suffix += 1 | |
| new_file = os.path.join(directory, f"{new_base_name}_{suffix}{ext}") | |
| try: | |
| cmds.file(rename=new_file) | |
| file_type = "mayaAscii" if ext == ".ma" else "mayaBinary" | |
| cmds.file(save=True, type=file_type) | |
| self.full_asset_cache = self.cache_assets() | |
| if self.current_folder: | |
| self.asset_cache = self.cache_folder_assets(self.current_folder) | |
| else: | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Saved incrementally to {os.path.basename(new_file)}") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error saving incrementally: {str(e)}") | |
| else: | |
| try: | |
| cmds.file(save=True, type="mayaAscii") | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.status_bar.setText(f"Saved scene to {os.path.basename(current_file)}") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error saving scene: {str(e)}") | |
| else: | |
| self.save_scene_as() | |
| def save_scene_as(self): | |
| """Prompt for a new file path to save the scene.""" | |
| default_dir = self.project_dir if self.current_folder is None else self.current_folder | |
| default_name = "untitled" | |
| default_path = os.path.join(default_dir, f"{default_name}.ma") | |
| file_path, _ = QtWidgets.QFileDialog.getSaveFileName( | |
| self, | |
| "Save Scene As", | |
| default_path, | |
| "Maya ASCII (*.ma);;Maya Binary (*.mb)" | |
| ) | |
| if file_path: | |
| if not file_path.endswith((".ma", ".mb")): | |
| file_path += ".ma" | |
| file_type = "mayaAscii" if file_path.endswith(".ma") else "mayaBinary" | |
| try: | |
| cmds.file(rename=file_path) | |
| cmds.file(save=True, type=file_type) | |
| self.full_asset_cache = self.cache_assets() | |
| if self.current_folder: | |
| self.asset_cache = self.cache_folder_assets(self.current_folder) | |
| else: | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Saved scene as {os.path.basename(file_path)}") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error saving scene as {os.path.basename(file_path)}: {str(e)}") | |
| def version_up_scene(self): | |
| """Version up the current scene by incrementing or adding a version number.""" | |
| current_file = cmds.file(q=True, sn=True) | |
| if not current_file: | |
| self.status_bar.setText("Error: Scene has no file path. Use Save As first.") | |
| return | |
| directory = os.path.dirname(current_file) | |
| file_name = os.path.basename(current_file) | |
| ext = os.path.splitext(file_name)[1] | |
| base_name = os.path.splitext(file_name)[0] | |
| version_match = re.search(r'_v(\d+)$', base_name) | |
| if not version_match: | |
| version_match = re.search(r'(\d+)$', base_name) | |
| if version_match: | |
| version_num = int(version_match.group(1)) | |
| new_version = f"{version_num + 1:03d}" | |
| if '_v' in base_name: | |
| new_base_name = re.sub(r'_v\d+$', f'_v{new_version}', base_name) | |
| else: | |
| new_base_name = re.sub(r'\d+$', new_version, base_name) | |
| else: | |
| new_base_name = f"{base_name}_v001" | |
| new_version = "001" | |
| new_file = os.path.join(directory, f"{new_base_name}{ext}") | |
| suffix = 0 | |
| while os.path.exists(new_file): | |
| suffix += 1 | |
| new_file = os.path.join(directory, f"{new_base_name}_{suffix}{ext}") | |
| try: | |
| cmds.file(rename=new_file) | |
| file_type = "mayaAscii" if ext == ".ma" else "mayaBinary" | |
| cmds.file(save=True, type=file_type) | |
| self.full_asset_cache = self.cache_assets() | |
| if self.current_folder: | |
| self.asset_cache = self.cache_folder_assets(self.current_folder) | |
| else: | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.update_browser() | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Versioned up scene to {os.path.basename(new_file)}") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error versioning up scene: {str(e)}") | |
| def open_scene(self, file_path=None): | |
| selected = self.browser_list.selectedItems() if not file_path else [] | |
| if not file_path and not selected: | |
| self.status_bar.setText("No items selected to open.") | |
| return | |
| if not file_path and len(selected) > 1: | |
| self.status_bar.setText("Please select only one asset to open.") | |
| return | |
| if not file_path: | |
| file_path = next((asset for asset in self.asset_cache if os.path.basename(asset) == selected[0].text() or asset == selected[0].text()), None) | |
| if file_path: | |
| clean_file_path = self.clean_file_path(file_path) | |
| if not os.path.exists(clean_file_path): | |
| self.status_bar.setText(f"Error: {os.path.basename(clean_file_path)} not found.") | |
| return | |
| is_valid, validation_error = self.validate_file(clean_file_path) | |
| if not is_valid: | |
| self.status_bar.setText(f"Error: {os.path.basename(clean_file_path)} - {validation_error}") | |
| return | |
| reply = QtWidgets.QMessageBox.warning( | |
| self, | |
| "Confirm Open Scene", | |
| f"Opening {os.path.basename(clean_file_path)} will replace the current scene. Save your work before proceeding. Continue?", | |
| QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, | |
| QtWidgets.QMessageBox.No | |
| ) | |
| if reply != QtWidgets.QMessageBox.Yes: | |
| self.status_bar.setText("Open scene cancelled.") | |
| return | |
| try: | |
| cmds.file(clean_file_path, open=True, force=True) | |
| try: | |
| mel.eval('editMenuUpdate MayaWindow|mainEditMenu;') | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error updating edit menu: {str(e)}") | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.status_bar.setText(f"Opened scene: {os.path.basename(clean_file_path)}") | |
| except RuntimeError as e: | |
| self.status_bar.setText(f"Error opening {os.path.basename(clean_file_path)}: {str(e)}") | |
| def open_scene_new_session(self, file_path): | |
| clean_file_path = self.clean_file_path(file_path) | |
| if not os.path.exists(clean_file_path): | |
| self.status_bar.setText(f"Error: {os.path.basename(clean_file_path)} not found.") | |
| return | |
| is_valid, validation_error = self.validate_file(clean_file_path) | |
| if not is_valid: | |
| self.status_bar.setText(f"Error: {os.path.basename(clean_file_path)} - {validation_error}") | |
| return | |
| try: | |
| maya_exe = "maya.exe" | |
| subprocess.Popen([maya_exe, clean_file_path], shell=False) | |
| self.status_bar.setText(f"Launched new Maya session with {os.path.basename(clean_file_path)}") | |
| except (subprocess.SubprocessError, FileNotFoundError) as e: | |
| self.status_bar.setText(f"Error launching new Maya session: {str(e)}") | |
| def open_source_location(self, file_path): | |
| """Open the directory of the given file path using the raw path without cleaning.""" | |
| try: | |
| directory = os.path.dirname(file_path) | |
| if not os.path.exists(directory): | |
| self.status_bar.setText(f"Directory not found for {os.path.basename(file_path)}.") | |
| return | |
| os.startfile(directory) | |
| self.status_bar.setText(f"Opened source location for {os.path.basename(file_path)}.") | |
| except Exception as e: | |
| self.status_bar.setText(f"Error opening location for {os.path.basename(file_path)}: {str(e)}") | |
| def change_project_dir(self, new_dir=None): | |
| if not new_dir: | |
| new_dir = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Project Directory", self.project_dir) | |
| if new_dir: | |
| self.project_dir = new_dir | |
| self.dir_field.setText(new_dir) | |
| self.full_asset_cache = self.cache_assets() | |
| self.asset_cache = self.full_asset_cache[:] | |
| self.current_folder = None | |
| self.update_browser() | |
| self.update_import_list_display() | |
| self.update_current_file_field() | |
| self.update_session_path_field() | |
| self.update_directory_indicator() | |
| self.project_history.append({"path": new_dir, "timestamp": time.time()}) | |
| self.save_project_history() | |
| self.status_bar.setText(f"Project directory changed to {new_dir}") | |
| def load_project_history(self): | |
| history_file = os.path.join(os.path.expanduser("~"), "Documents", "maya_json", "project_history.json") | |
| if os.path.exists(history_file): | |
| try: | |
| with open(history_file, 'r') as f: | |
| history = json.load(f) | |
| return [entry for entry in history if isinstance(entry, dict) and "path" in entry and "timestamp" in entry] | |
| except (json.JSONDecodeError, IOError): | |
| return [] | |
| return [] | |
| def save_project_history(self): | |
| """Save project history to JSON file, keeping the 10 most recent entries.""" | |
| history_dir = os.path.join(os.path.expanduser("~"), "Documents", "maya_json") | |
| history_file = os.path.join(history_dir, "project_history.json") | |
| os.makedirs(history_dir, exist_ok=True) | |
| unique_history = {entry["path"]: entry for entry in self.project_history}.values() | |
| self.project_history = sorted(unique_history, key=lambda x: x["timestamp"], reverse=True)[:10] | |
| try: | |
| with open(history_file, 'w') as f: | |
| json.dump(self.project_history, f, indent=4) | |
| except IOError as e: | |
| self.status_bar.setText(f"Error saving project history: {str(e)}") | |
| def load_window_state(self): | |
| """Load window state from JSON file.""" | |
| state_file = os.path.join(os.path.expanduser("~"), "Documents", "maya_json", "window_state.json") | |
| if os.path.exists(state_file): | |
| try: | |
| with open(state_file, 'r') as f: | |
| state = json.load(f) | |
| self.window_width = state.get("window_width", 800) | |
| self.window_height = state.get("window_height", 600) | |
| self.import_list_height = state.get("import_list_height", 200) | |
| self.browser_list_height = state.get("browser_list_height", 240) | |
| self.show_full_path = state.get("show_full_path", False) | |
| self.import_sort_mode = state.get("import_sort_mode", "asset") | |
| self.import_sort_dropdown_text = { | |
| "alphabetical_asc": "Alphabetical A-Z", | |
| "alphabetical_desc": "Alphabetical Z-A", | |
| "version": "Version", | |
| "asset": "Asset" | |
| }.get(self.import_sort_mode, "Asset") | |
| self.clear_after_import = state.get("clear_after_import", False) | |
| self.disable_delete_prompt = state.get("disable_delete_prompt", False) | |
| self.incremental_save = state.get("incremental_save", False) | |
| # REMOVED: Loading of stay_on_top state to enforce always on top | |
| except (json.JSONDecodeError, IOError): | |
| pass | |
| def save_window_state(self): | |
| """Save window state to JSON file.""" | |
| state_dir = os.path.join(os.path.expanduser("~"), "Documents", "maya_json") | |
| state_file = os.path.join(state_dir, "window_state.json") | |
| os.makedirs(state_dir, exist_ok=True) | |
| state = { | |
| "window_width": self.width(), | |
| "window_height": self.height(), | |
| "import_list_height": self.import_list_height, | |
| "browser_list_height": self.browser_list_height, | |
| "show_full_path": self.show_full_path, | |
| "import_sort_mode": self.import_sort_mode, | |
| "clear_after_import": self.clear_after_import, | |
| "disable_delete_prompt": self.disable_delete_prompt, | |
| "incremental_save": self.incremental_save, | |
| # REMOVED: stay_on_top state to enforce always on top | |
| } | |
| try: | |
| with open(state_file, 'w') as f: | |
| json.dump(state, f, indent=4) | |
| self.status_bar.setText("Window state saved.") | |
| except IOError as e: | |
| self.status_bar.setText(f"Error saving window state: {str(e)}") | |
| def closeEvent(self, event): | |
| """Handle window close event and save state.""" | |
| self.save_window_state() | |
| super(AssetFileManagerVV, self).closeEvent(event) | |
| # Example usage to launch the tool | |
| def launch_asset_file_manager(): | |
| """Launch the Asset File Manager in Maya.""" | |
| project_dir = cmds.workspace(q=True, dir=True) or os.path.expanduser("~") | |
| app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv) | |
| window = AssetFileManagerVV(project_dir) | |
| window.show() | |
| app.exec_() | |
| if __name__ == "__main__": | |
| launch_asset_file_manager() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment