Created
May 2, 2025 18:13
-
-
Save Pakmanv/908877a876eb09c26af93d932a8cf375 to your computer and use it in GitHub Desktop.
Matching Renamer 1.0
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
| import maya.cmds as cmds | |
| from PySide2 import QtWidgets, QtCore | |
| import time | |
| import json | |
| import os | |
| import getpass | |
| import re | |
| from collections import deque | |
| class ValidatedInputDialog(QtWidgets.QDialog): | |
| def __init__(self, title, label, default_text="", parent=None): | |
| super(ValidatedInputDialog, self).__init__(parent) | |
| self.setWindowTitle(title) | |
| self.layout = QtWidgets.QVBoxLayout() | |
| self.setLayout(self.layout) | |
| self.label = QtWidgets.QLabel(label) | |
| self.input = QtWidgets.QLineEdit(default_text) | |
| self.feedback = QtWidgets.QLabel("") | |
| self.feedback.setStyleSheet("color: red;") | |
| self.buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) | |
| self.ok_button = self.buttons.button(QtWidgets.QDialogButtonBox.Ok) | |
| self.ok_button.setEnabled(False) | |
| self.layout.addWidget(self.label) | |
| self.layout.addWidget(self.input) | |
| self.layout.addWidget(self.feedback) | |
| self.layout.addWidget(self.buttons) | |
| self.input.textChanged.connect(self.validate_input) | |
| self.buttons.accepted.connect(self.accept) | |
| self.buttons.rejected.connect(self.reject) | |
| self.valid = False | |
| def validate_input(self): | |
| text = self.input.text() | |
| names = [name.strip() for name in text.split(',') if name.strip()] if ',' in text else [text.strip()] | |
| all_valid = True | |
| error_messages = [] | |
| for name in names: | |
| if not name or name.isspace(): | |
| error_messages.append("Empty name not allowed") | |
| all_valid = False | |
| elif not name[0].isalpha(): | |
| error_messages.append(f"'{name}' must start with a letter") | |
| all_valid = False | |
| elif not re.match(r'^[a-zA-Z][a-zA-Z0-9_]*$', name): | |
| error_messages.append(f"'{name}' contains invalid characters") | |
| all_valid = False | |
| if not names: | |
| error_messages.append("No valid names entered") | |
| all_valid = False | |
| self.valid = all_valid | |
| self.ok_button.setEnabled(all_valid) | |
| if error_messages: | |
| self.feedback.setText("; ".join(error_messages)) | |
| self.feedback.setStyleSheet("color: red;") | |
| else: | |
| self.feedback.setText("Valid name(s)") | |
| self.feedback.setStyleSheet("color: #CCCCCC;") | |
| def get_text(self): | |
| return self.input.text() | |
| class MatchingRenamerUI(QtWidgets.QWidget): | |
| def __init__(self): | |
| super(MatchingRenamerUI, self).__init__() | |
| self.setWindowTitle("Matching Renamer VV 1.0") | |
| # Set the window to stay on top by default | |
| self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) | |
| self.selection_data = [] | |
| self.script_job_id = None | |
| self.undo_stack = deque(maxlen=50) | |
| self.redo_stack = deque(maxlen=50) | |
| self.drag_enabled = False | |
| self.is_renaming = False | |
| self.always_on_top = True # Track the "stay on top" state | |
| self.resize(600, 500) | |
| self.user = getpass.getuser() | |
| self.base_path = f"C:/Users/{self.user}/Documents/vvtoolbag/setlistmanager" | |
| if not os.path.exists(self.base_path): | |
| os.makedirs(self.base_path) | |
| self.layout = QtWidgets.QVBoxLayout() | |
| self.setLayout(self.layout) | |
| self.menu_bar = QtWidgets.QMenuBar() | |
| self.actions_menu = QtWidgets.QMenu("Actions", self) | |
| self.menu_bar.addMenu(self.actions_menu) | |
| self.save_action = QtWidgets.QAction("Save List", self) | |
| self.load_action = QtWidgets.QAction("Load List", self) | |
| self.undo_action = QtWidgets.QAction("Undo", self) | |
| self.redo_action = QtWidgets.QAction("Redo", self) | |
| self.toggle_on_top_action = QtWidgets.QAction("Disable Always On Top", self) # New action | |
| self.actions_menu.addAction(self.save_action) | |
| self.actions_menu.addAction(self.load_action) | |
| self.actions_menu.addSeparator() | |
| self.actions_menu.addAction(self.undo_action) | |
| self.actions_menu.addAction(self.redo_action) | |
| self.actions_menu.addSeparator() | |
| self.actions_menu.addAction(self.toggle_on_top_action) | |
| self.save_action.triggered.connect(self.save_list) | |
| self.load_action.triggered.connect(self.load_list) | |
| self.undo_action.triggered.connect(self.undo) | |
| self.redo_action.triggered.connect(self.redo) | |
| self.toggle_on_top_action.triggered.connect(self.toggle_always_on_top) # Connect new action | |
| self.layout.addWidget(self.menu_bar) | |
| self.sort_search_layout = QtWidgets.QHBoxLayout() | |
| self.sort_label = QtWidgets.QLabel("Sort by:") | |
| self.sort_combo = QtWidgets.QComboBox() | |
| self.sort_combo.addItems(["Selection Order", "Time", "Alphabetical A-Z", "Alphabetical Z-A"]) | |
| self.search_label = QtWidgets.QLabel("Search:") | |
| self.search_input = QtWidgets.QLineEdit() | |
| self.search_input.setPlaceholderText("Enter search term (e.g., TN_)") | |
| self.search_input.textChanged.connect(self.update_list) | |
| self.sort_search_layout.addWidget(self.sort_label) | |
| self.sort_search_layout.addWidget(self.sort_combo) | |
| self.sort_search_layout.addWidget(self.search_label) | |
| self.sort_search_layout.addWidget(self.search_input) | |
| self.sort_search_layout.addStretch() | |
| self.list_container = QtWidgets.QWidget() | |
| self.list_container_layout = QtWidgets.QHBoxLayout() | |
| self.list_container.setLayout(self.list_container_layout) | |
| self.name_list_container = QtWidgets.QWidget() | |
| self.name_list_layout = QtWidgets.QVBoxLayout() | |
| self.name_list_container.setLayout(self.name_list_layout) | |
| self.name_list_header = QtWidgets.QHBoxLayout() | |
| self.name_list_label = QtWidgets.QLabel("Name List") | |
| self.name_list_count_label = QtWidgets.QLabel("List Objects: 0") | |
| self.name_selection_feedback_label = QtWidgets.QLabel("All/0") | |
| self.name_selection_feedback_label.setAlignment(QtCore.Qt.AlignRight) | |
| self.name_list_header.addWidget(self.name_list_label) | |
| self.name_list_header.addStretch() | |
| self.name_list_header.addWidget(self.name_list_count_label) | |
| self.name_list_header.addWidget(self.name_selection_feedback_label) | |
| self.name_list_widget = QtWidgets.QListWidget() | |
| self.name_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) | |
| self.name_list_widget.itemDoubleClicked.connect(self.rename_item) | |
| self.name_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.name_list_widget.customContextMenuRequested.connect(self.show_name_list_context_menu) | |
| self.name_list_widget.itemSelectionChanged.connect(self.update_selection_count) | |
| self.rename_order_layout = QtWidgets.QHBoxLayout() | |
| self.rename_order_label = QtWidgets.QLabel("Rename by:") | |
| self.rename_order_combo = QtWidgets.QComboBox() | |
| self.rename_order_combo.addItems(["List Order", "Selection Order"]) | |
| self.rename_order_combo.setCurrentText("List Order") | |
| self.rename_order_layout.addWidget(self.rename_order_label) | |
| self.rename_order_layout.addWidget(self.rename_order_combo) | |
| self.rename_order_layout.addStretch() | |
| self.name_list_layout.addLayout(self.name_list_header) | |
| self.name_list_layout.addWidget(self.name_list_widget) | |
| self.name_list_layout.addLayout(self.rename_order_layout) | |
| self.scene_list_container = QtWidgets.QWidget() | |
| self.scene_list_layout = QtWidgets.QVBoxLayout() | |
| self.scene_list_container.setLayout(self.scene_list_layout) | |
| self.scene_list_header = QtWidgets.QHBoxLayout() | |
| self.scene_list_label = QtWidgets.QLabel("Scene List") | |
| self.scene_selection_count_label = QtWidgets.QLabel("Scene Selection: <span style='color: #FFFFFF;'>0</span>") | |
| self.scene_selection_count_label.setAlignment(QtCore.Qt.AlignRight) | |
| self.scene_list_header.addWidget(self.scene_list_label) | |
| self.scene_list_header.addStretch() | |
| self.scene_list_header.addWidget(self.scene_selection_count_label) | |
| self.scene_list_widget = QtWidgets.QListWidget() | |
| self.scene_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) | |
| self.scene_list_widget.setDragEnabled(False) | |
| self.scene_list_widget.setAcceptDrops(False) | |
| self.scene_list_widget.setDragDropMode(QtWidgets.QAbstractItemView.NoDragDrop) | |
| self.scene_list_widget.itemDoubleClicked.connect(self.rename_scene_item) | |
| self.scene_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
| self.scene_list_widget.customContextMenuRequested.connect(self.show_scene_list_context_menu) | |
| self.scene_list_layout.addLayout(self.scene_list_header) | |
| self.scene_list_layout.addWidget(self.scene_list_widget) | |
| self.list_container_layout.addWidget(self.name_list_container, stretch=3) | |
| self.list_container_layout.addWidget(self.scene_list_container, stretch=2) | |
| self.feedback_layout = QtWidgets.QHBoxLayout() | |
| self.feedback_label = QtWidgets.QLabel("Ready") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| self.feedback_layout.addWidget(self.feedback_label) | |
| self.feedback_layout.addStretch() | |
| self.layout.addLayout(self.sort_search_layout) | |
| self.layout.addWidget(self.list_container) | |
| self.layout.addLayout(self.feedback_layout) | |
| self.button_layout = QtWidgets.QHBoxLayout() | |
| self.load_button = QtWidgets.QPushButton("Load Objects as Name") | |
| self.load_button.clicked.connect(self.load_objects) | |
| self.load_button.setStyleSheet("background-color: #C1E1C1; color: #000000;") | |
| self.rename_button = QtWidgets.QPushButton("Rename Selections") | |
| self.rename_button.clicked.connect(self.rename_selection) | |
| self.rename_button.setStyleSheet("background-color: #A7C7E7; color: #000000;") | |
| self.button_layout.addWidget(self.load_button) | |
| self.button_layout.addWidget(self.rename_button) | |
| self.layout.addLayout(self.button_layout) | |
| self.footer_label = QtWidgets.QLabel("Matching Renamer VV 1.0 - Tool made by Vypac Voeur") | |
| self.footer_label.setStyleSheet("font-size: 10px; color: #AAAAAA;") | |
| self.layout.addWidget(self.footer_label, alignment=QtCore.Qt.AlignLeft) | |
| self.sort_combo.currentIndexChanged.connect(self.update_list) | |
| self.update_selection_count() | |
| self.setup_selection_monitor() | |
| def toggle_always_on_top(self): | |
| """Toggle the 'Always On Top' behavior of the window.""" | |
| self.always_on_top = not self.always_on_top | |
| if self.always_on_top: | |
| self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) | |
| self.toggle_on_top_action.setText("Disable Always On Top") | |
| self.feedback_label.setText("Always On Top enabled") | |
| else: | |
| self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint) | |
| self.toggle_on_top_action.setText("Enable Always On Top") | |
| self.feedback_label.setText("Always On Top disabled") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| self.show() # Re-show the window to apply the new flags | |
| def setup_selection_monitor(self): | |
| if self.script_job_id is not None: | |
| try: | |
| cmds.scriptJob(kill=self.script_job_id) | |
| except: | |
| pass | |
| self.script_job_id = None | |
| self.script_job_id = cmds.scriptJob( | |
| event=["SelectionChanged", self.update_selection_count], | |
| protected=True | |
| ) | |
| def closeEvent(self, event): | |
| if self.script_job_id is not None: | |
| try: | |
| cmds.scriptJob(kill=self.script_job_id) | |
| except: | |
| pass | |
| self.script_job_id = None | |
| super(MatchingRenamerUI, self).closeEvent(event) | |
| def save_state(self, action_type, data_before, data_after): | |
| self.undo_stack.append({ | |
| "action": action_type, | |
| "before": data_before, | |
| "after": data_after | |
| }) | |
| self.redo_stack.clear() | |
| self.feedback_label.setText(f"{action_type} performed") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def undo(self): | |
| if not self.undo_stack: | |
| self.feedback_label.setText("Nothing to undo") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| return | |
| state = self.undo_stack.pop() | |
| self.redo_stack.append({ | |
| "action": state["action"], | |
| "before": state["after"], | |
| "after": state["before"] | |
| }) | |
| self.selection_data = state["before"] | |
| self.update_list() | |
| self.feedback_label.setText(f"Undid {state['action']}") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def redo(self): | |
| if not self.redo_stack: | |
| self.feedback_label.setText("Nothing to redo") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| return | |
| state = self.redo_stack.pop() | |
| self.undo_stack.append({ | |
| "action": state["action"], | |
| "before": state["after"], | |
| "after": state["before"] | |
| }) | |
| self.selection_data = state["before"] | |
| self.update_list() | |
| self.feedback_label.setText(f"Redid {state['action']}") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def show_name_list_context_menu(self, position): | |
| menu = QtWidgets.QMenu() | |
| add_action = menu.addAction("Add New Entry") | |
| menu.addSeparator() | |
| rename_action = menu.addAction("Rename") | |
| delete_action = menu.addAction("Delete Selected") | |
| clear_action = menu.addAction("Clear List") | |
| action = menu.exec_(self.name_list_widget.mapToGlobal(position)) | |
| if action == clear_action: | |
| self.clear_list() | |
| elif action == add_action: | |
| self.add_new_entry() | |
| elif action == delete_action: | |
| self.delete_selected_entries() | |
| elif action == rename_action: | |
| selected_items = self.name_list_widget.selectedItems() | |
| if len(selected_items) == 1: | |
| self.rename_item(selected_items[0]) | |
| else: | |
| self.feedback_label.setText("Select exactly one item to rename") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("Select exactly one item to rename") | |
| def show_scene_list_context_menu(self, position): | |
| menu = QtWidgets.QMenu() | |
| rename_action = menu.addAction("Rename") | |
| remove_action = menu.addAction("Remove Selected") | |
| menu.addSeparator() | |
| toggle_drag_action = menu.addAction("Enable Drag-and-Drop" if not self.drag_enabled else "Disable Drag-and-Drop") | |
| action = menu.exec_(self.scene_list_widget.mapToGlobal(position)) | |
| if action == rename_action: | |
| selected_items = self.scene_list_widget.selectedItems() | |
| if len(selected_items) == 1: | |
| self.rename_scene_item(selected_items[0]) | |
| else: | |
| self.feedback_label.setText("Select exactly one item to rename") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("Select exactly one item to rename") | |
| elif action == remove_action: | |
| self.remove_selected_scene_items() | |
| elif action == toggle_drag_action: | |
| self.toggle_drag_drop() | |
| def toggle_drag_drop(self): | |
| self.drag_enabled = not self.drag_enabled | |
| if self.drag_enabled: | |
| self.scene_list_widget.setDragEnabled(True) | |
| self.scene_list_widget.setAcceptDrops(True) | |
| self.scene_list_widget.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) | |
| self.feedback_label.setText("Drag-and-Drop enabled") | |
| else: | |
| self.scene_list_widget.setDragEnabled(False) | |
| self.scene_list_widget.setAcceptDrops(False) | |
| self.scene_list_widget.setDragDropMode(QtWidgets.QAbstractItemView.NoDragDrop) | |
| self.feedback_label.setText("Drag-and-Drop disabled") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def remove_selected_scene_items(self): | |
| selected_items = self.scene_list_widget.selectedItems() | |
| if not selected_items: | |
| self.feedback_label.setText("No items selected to remove") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| return | |
| selected_names = [item.text() for item in selected_items] | |
| selected_scene_objects = cmds.ls(selection=True, long=True) | |
| objects_to_deselect = [] | |
| for obj in selected_scene_objects: | |
| short_name = obj.split('|')[-1].split(':')[-1] | |
| if short_name in selected_names: | |
| objects_to_deselect.append(obj) | |
| if objects_to_deselect: | |
| cmds.select(objects_to_deselect, deselect=True) | |
| self.feedback_label.setText(f"Removed {len(objects_to_deselect)} item(s) from scene selection") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| else: | |
| self.feedback_label.setText("No matching objects found in scene selection") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| self.update_selection_count() | |
| def is_valid_name(self, name): | |
| if not name or name.isspace(): | |
| return False | |
| if not name[0].isalpha(): | |
| return False | |
| if not re.match(r'^[a-zA-Z][a-zA-Z0-9_]*$', name): | |
| return False | |
| return True | |
| def get_unique_name(self, name, existing_names): | |
| if name not in existing_names: | |
| return name | |
| base_name = name | |
| counter = 1 | |
| while f"{base_name}_{counter}" in existing_names: | |
| counter += 1 | |
| return f"{base_name}_{counter}" | |
| def get_unique_scene_name(self, name, current_obj=None): | |
| if current_obj and name == current_obj.split('|')[-1].split(':')[-1]: | |
| return name | |
| if not cmds.objExists(name): | |
| return name | |
| base_name = name | |
| counter = 1 | |
| while True: | |
| test_name = f"{base_name}_{counter}" | |
| if not cmds.objExists(test_name): | |
| return test_name | |
| if current_obj and test_name == current_obj.split('|')[-1].split(':')[-1]: | |
| return test_name | |
| counter += 1 | |
| def add_new_entry(self): | |
| data_before = self.selection_data[:] | |
| dialog = ValidatedInputDialog( | |
| title="Add New Entry", | |
| label="Enter name(s) (comma-separated, e.g., apple,pear,chair):", | |
| parent=self | |
| ) | |
| if dialog.exec_() == QtWidgets.QDialog.Accepted and dialog.valid: | |
| new_names = dialog.get_text() | |
| name_list = [name.strip() for name in new_names.split(',') if name.strip()] | |
| existing_names = [item["name"] for item in self.selection_data] | |
| added_count = 0 | |
| for name in name_list: | |
| if self.is_valid_name(name): | |
| unique_name = self.get_unique_name(name, existing_names) | |
| self.selection_data.append({"name": unique_name, "time": time.time()}) | |
| existing_names.append(unique_name) | |
| added_count += 1 | |
| if added_count > 0: | |
| self.save_state("Add Entries", data_before, self.selection_data[:]) | |
| self.update_list() | |
| self.feedback_label.setText(f"Added {added_count} new entry(ies)") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| print(f"Added {added_count} new entry(ies)") | |
| else: | |
| self.feedback_label.setText("No valid names entered") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("No valid names entered") | |
| def delete_selected_entries(self): | |
| selected_items = self.name_list_widget.selectedItems() | |
| selected_names = [item.text() for item in selected_items] | |
| if not selected_names: | |
| self.feedback_label.setText("No items selected to delete") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| return | |
| data_before = self.selection_data[:] | |
| self.selection_data = [item for item in self.selection_data | |
| if item["name"] not in selected_names] | |
| self.save_state("Delete Entries", data_before, self.selection_data[:]) | |
| self.update_list() | |
| self.feedback_label.setText(f"Deleted {len(selected_names)} selected entries") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def clear_list(self): | |
| if not self.selection_data: | |
| self.feedback_label.setText("List is already empty") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| return | |
| data_before = self.selection_data[:] | |
| self.selection_data = [] | |
| self.save_state("Clear List", data_before, self.selection_data[:]) | |
| self.update_list() | |
| self.feedback_label.setText("Cleared list") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def get_selected_sets(self): | |
| selected = cmds.ls(selection=True, long=False) | |
| current_time = time.time() | |
| new_selections = [] | |
| existing_names = [item["name"] for item in self.selection_data] | |
| for sel in selected: | |
| if sel: | |
| short_name = sel.split('|')[-1].split(':')[-1] | |
| if self.is_valid_name(short_name): | |
| unique_name = self.get_unique_name(short_name, existing_names) | |
| new_selections.append({"name": unique_name, "time": current_time}) | |
| existing_names.append(unique_name) | |
| else: | |
| self.feedback_label.setText(f"Skipped invalid object name: {short_name}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Skipped invalid object name: {short_name}") | |
| return new_selections | |
| def load_objects(self): | |
| data_before = self.selection_data[:] | |
| new_selections = self.get_selected_sets() | |
| self.selection_data.extend(new_selections) | |
| self.save_state("Load Objects", data_before, self.selection_data[:]) | |
| self.update_list() | |
| self.feedback_label.setText(f"Loaded {len(new_selections)} object(s)") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| def update_list(self): | |
| self.name_list_widget.clear() | |
| search_term = self.search_input.text().lower() | |
| if search_term: | |
| filtered_data = [item for item in self.selection_data if search_term in item["name"].lower()] | |
| else: | |
| filtered_data = self.selection_data | |
| sort_mode = self.sort_combo.currentText() | |
| if sort_mode == "Selection Order": | |
| sorted_data = filtered_data | |
| elif sort_mode == "Time": | |
| sorted_data = sorted(filtered_data, key=lambda x: x["time"], reverse=True) | |
| elif sort_mode == "Alphabetical A-Z": | |
| sorted_data = sorted(filtered_data, key=lambda x: x["name"].lower()) | |
| else: | |
| sorted_data = sorted(filtered_data, key=lambda x: x["name"].lower(), reverse=True) | |
| for item in sorted_data: | |
| self.name_list_widget.addItem(item["name"]) | |
| self.name_list_count_label.setText(f"List Objects: {len(filtered_data)}") | |
| self.update_selection_count() | |
| def rename_item(self, item): | |
| current_name = item.text() | |
| row = self.name_list_widget.row(item) | |
| dialog = ValidatedInputDialog( | |
| title="Rename List Item", | |
| label="Enter new name (must start with a letter, contain only letters, numbers, underscores):", | |
| default_text=current_name, | |
| parent=self | |
| ) | |
| if dialog.exec_() == QtWidgets.QDialog.Accepted and dialog.valid: | |
| new_name = dialog.get_text() | |
| existing_names = [d["name"] for d in self.selection_data if d["name"] != current_name] | |
| unique_name = self.get_unique_name(new_name, existing_names) | |
| data_before = self.selection_data[:] | |
| if row < len(self.selection_data): | |
| self.selection_data[row]["name"] = unique_name | |
| self.selection_data[row]["time"] = time.time() | |
| self.feedback_label.setText(f"Updated list item to {unique_name}") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| print(f"Updated list item to {unique_name}") | |
| else: | |
| self.feedback_label.setText("Error: Invalid row index") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("Error: Invalid row index") | |
| self.save_state("Rename Item", data_before, self.selection_data[:]) | |
| self.update_list() | |
| def rename_scene_item(self, item): | |
| current_name = item.text() | |
| selected = cmds.ls(selection=True, long=True) | |
| matching_objects = [obj for obj in selected if obj.split('|')[-1].split(':')[-1] == current_name] | |
| if not matching_objects: | |
| self.feedback_label.setText(f"Object {current_name} not found in selection") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Object {current_name} not found in selection") | |
| return | |
| if len(matching_objects) > 1: | |
| self.feedback_label.setText(f"Multiple objects with name {current_name} found; select one") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Multiple objects with name {current_name} found; select one") | |
| return | |
| obj_long_name = matching_objects[0] | |
| dialog = ValidatedInputDialog( | |
| title="Rename Scene Object", | |
| label="Enter new name (must start with a letter, contain only letters, numbers, underscores):", | |
| default_text=current_name, | |
| parent=self | |
| ) | |
| if dialog.exec_() == QtWidgets.QDialog.Accepted and dialog.valid: | |
| new_name = dialog.get_text() | |
| unique_name = self.get_unique_scene_name(new_name, current_obj=obj_long_name) | |
| try: | |
| cmds.rename(obj_long_name, unique_name) | |
| self.feedback_label.setText(f"Renamed {current_name} to {unique_name} in scene") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| print(f"Renamed {current_name} to {unique_name} in scene") | |
| self.update_selection_count() | |
| except Exception as e: | |
| self.feedback_label.setText(f"Failed to rename {current_name}: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Failed to rename {current_name}: {str(e)}") | |
| def save_list(self): | |
| file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save List", | |
| self.base_path, "JSON Files (*.json)") | |
| if file_path: | |
| try: | |
| with open(file_path, 'w') as f: | |
| json.dump(self.selection_data, f, indent=4) | |
| self.feedback_label.setText("List saved successfully") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| print("List saved successfully") | |
| except Exception as e: | |
| self.feedback_label.setText(f"Failed to save: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Failed to save: {str(e)}") | |
| def load_list(self): | |
| file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Load List", | |
| self.base_path, "JSON Files (*.json)") | |
| if file_path: | |
| try: | |
| with open(file_path, 'r') as f: | |
| loaded_data = json.load(f) | |
| valid_data = [] | |
| existing_names = [item["name"] for item in self.selection_data] | |
| for item in loaded_data: | |
| if self.is_valid_name(item.get("name", "")): | |
| unique_name = self.get_unique_name(item["name"], existing_names) | |
| valid_data.append({"name": unique_name, "time": item.get("time", time.time())}) | |
| existing_names.append(unique_name) | |
| if valid_data: | |
| data_before = self.selection_data[:] | |
| self.selection_data.extend(valid_data) | |
| self.save_state("Load List", data_before, self.selection_data[:]) | |
| self.update_list() | |
| self.feedback_label.setText(f"Loaded {len(valid_data)} valid name(s) successfully") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| print(f"Loaded {len(valid_data)} valid name(s) successfully") | |
| else: | |
| self.feedback_label.setText("No valid names found in the file") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("No valid names found in the file") | |
| except Exception as e: | |
| self.feedback_label.setText(f"Failed to load: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Failed to load: {str(e)}") | |
| def update_selection_count(self): | |
| selected = cmds.ls(selection=True, long=False) | |
| self.scene_selection_count_label.setText(f"Scene Selection: <span style='color: #FFFFFF;'>{len(selected)}</span>") | |
| self.scene_list_widget.clear() | |
| for sel in selected: | |
| short_name = sel.split('|')[-1].split(':')[-1] | |
| self.scene_list_widget.addItem(short_name) | |
| selected_items = self.name_list_widget.selectedItems() | |
| scene_count = len(selected) | |
| selected_count = len(selected_items) | |
| if selected_count == 0: | |
| feedback_text = f"All/{scene_count}" | |
| else: | |
| feedback_text = f"{selected_count}/{scene_count}" | |
| color = "#00FF00" if (selected_count == scene_count and scene_count > 0) or (selected_count == 0 and scene_count == len(self.selection_data) and scene_count > 0) else "#CCCCCC" | |
| self.name_selection_feedback_label.setText(f"<span style='color: {color};'>{feedback_text}</span>") | |
| def rename_selection(self): | |
| if self.is_renaming: | |
| return | |
| self.is_renaming = True | |
| try: | |
| def perform_rename(): | |
| selected = cmds.ls(selection=True, long=True) | |
| if not selected: | |
| self.feedback_label.setText("No objects selected in the scene") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("No objects selected in the scene") | |
| return 0, [] | |
| rename_mode = self.rename_order_combo.currentText() | |
| renamed_count = 0 | |
| failed_names = [] | |
| if rename_mode == "Selection Order": | |
| selected_items = self.name_list_widget.selectedItems() | |
| selected_names = [item.text() for item in selected_items] | |
| if not selected_names: | |
| self.feedback_label.setText("No names selected in Name List for Selection Order") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning("No names selected in Name List for Selection Order") | |
| return 0, [] | |
| if len(selected_names) != len(selected): | |
| self.feedback_label.setText(f"Selection Order requires {len(selected)} names selected, got {len(selected_names)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Selection Order requires {len(selected)} names selected, got {len(selected_names)}") | |
| return 0, [] | |
| try: | |
| for i, obj in enumerate(selected): | |
| new_name = selected_names[i] | |
| current_short_name = obj.split('|')[-1].split(':')[-1] | |
| if new_name == current_short_name: | |
| renamed_count += 1 | |
| continue | |
| if self.is_valid_name(new_name): | |
| unique_name = self.get_unique_scene_name(new_name, current_obj=obj) | |
| try: | |
| cmds.rename(obj, unique_name) | |
| renamed_count += 1 | |
| print(f"Renamed {obj} to {unique_name}") | |
| except Exception as e: | |
| failed_names.append(f"{new_name}: {str(e)}") | |
| else: | |
| failed_names.append(f"{new_name}: Invalid name") | |
| except Exception as e: | |
| self.feedback_label.setText(f"Unexpected error during rename: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Unexpected error during rename: {str(e)}") | |
| return 0, [] | |
| else: # List Order | |
| selected_items = self.name_list_widget.selectedItems() | |
| selected_names = [item.text() for item in selected_items] | |
| if selected_names: # Use selected items if any | |
| if len(selected_names) != len(selected): | |
| self.feedback_label.setText(f"List Order with selected names requires {len(selected)} names selected, got {len(selected_names)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"List Order with selected names requires {len(selected)} names selected, got {len(selected_names)}") | |
| return 0, [] | |
| try: | |
| for i, obj in enumerate(selected): | |
| new_name = selected_names[i] | |
| current_short_name = obj.split('|')[-1].split(':')[-1] | |
| if new_name == current_short_name: | |
| renamed_count += 1 | |
| continue | |
| if self.is_valid_name(new_name): | |
| unique_name = self.get_unique_scene_name(new_name, current_obj=obj) | |
| try: | |
| cmds.rename(obj, unique_name) | |
| renamed_count += 1 | |
| print(f"Renamed {obj} to {unique_name}") | |
| except Exception as e: | |
| failed_names.append(f"{new_name}: {str(e)}") | |
| else: | |
| failed_names.append(f"{new_name}: Invalid name") | |
| except Exception as e: | |
| self.feedback_label.setText(f"Unexpected error during rename: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Unexpected error during rename: {str(e)}") | |
| return 0, [] | |
| else: # Use full list if no items selected | |
| if len(selected) > len(self.selection_data): | |
| self.feedback_label.setText(f"List Order requires scene selection ({len(selected)}) <= Name List count ({len(self.selection_data)})") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"List Order requires scene selection ({len(selected)}) <= Name List count ({len(self.selection_data)})") | |
| return 0, [] | |
| search_term = self.search_input.text().lower() | |
| if search_term: | |
| filtered_data = [item for item in self.selection_data if search_term in item["name"].lower()] | |
| else: | |
| filtered_data = self.selection_data | |
| sort_mode = self.sort_combo.currentText() | |
| if sort_mode == "Selection Order": | |
| sorted_data = filtered_data | |
| elif sort_mode == "Time": | |
| sorted_data = sorted(filtered_data, key=lambda x: x["time"], reverse=True) | |
| elif sort_mode == "Alphabetical A-Z": | |
| sorted_data = sorted(filtered_data, key=lambda x: x["name"].lower()) | |
| else: | |
| sorted_data = sorted(filtered_data, key=lambda x: x["name"].lower(), reverse=True) | |
| if len(selected) > len(sorted_data): | |
| self.feedback_label.setText(f"Not enough names in filtered/sorted list ({len(sorted_data)}) for {len(selected)} objects") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Not enough names in filtered/sorted list ({len(sorted_data)}) for {len(selected)} objects") | |
| return 0, [] | |
| try: | |
| for i, obj in enumerate(selected): | |
| new_name = sorted_data[i]["name"] | |
| current_short_name = obj.split('|')[-1].split(':')[-1] | |
| if new_name == current_short_name: | |
| renamed_count += 1 | |
| continue | |
| if self.is_valid_name(new_name): | |
| unique_name = self.get_unique_scene_name(new_name, current_obj=obj) | |
| try: | |
| cmds.rename(obj, unique_name) | |
| renamed_count += 1 | |
| print(f"Renamed {obj} to {unique_name}") | |
| except Exception as e: | |
| failed_names.append(f"{new_name}: {str(e)}") | |
| else: | |
| failed_names.append(f"{new_name}: Invalid name") | |
| except Exception as e: | |
| self.feedback_label.setText(f"Unexpected error during rename: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Unexpected error during rename: {str(e)}") | |
| return 0, [] | |
| return renamed_count, failed_names | |
| # Perform first rename | |
| renamed_count, failed_names = perform_rename() | |
| if renamed_count == 0 and failed_names: | |
| return # Exit if first rename failed | |
| # If in Selection Order, perform second rename immediately | |
| if self.rename_order_combo.currentText() == "Selection Order": | |
| renamed_count2, failed_names2 = perform_rename() | |
| renamed_count += renamed_count2 | |
| failed_names.extend(failed_names2) | |
| self.update_selection_count() | |
| # Compute adjusted count | |
| divisor = 2 if self.rename_order_combo.currentText() == "Selection Order" else 1 | |
| adjusted_count = renamed_count // divisor | |
| expected_count = len(cmds.ls(selection=True, long=True)) * divisor | |
| if renamed_count == expected_count: | |
| self.feedback_label.setText(f"Renamed {adjusted_count} object(s) successfully") | |
| self.feedback_label.setStyleSheet("color: white;") | |
| print(f"Renamed {adjusted_count} object(s) successfully") | |
| else: | |
| self.feedback_label.setText(f"Renamed {adjusted_count} object(s). Failed: {', '.join(failed_names)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Renamed {adjusted_count} object(s). Failed: {', '.join(failed_names)}") | |
| except Exception as e: | |
| self.feedback_label.setText(f"Unexpected error in rename_selection: {str(e)}") | |
| self.feedback_label.setStyleSheet("color: red;") | |
| cmds.warning(f"Unexpected error in rename_selection: {str(e)}") | |
| finally: | |
| self.is_renaming = False | |
| def show_ui(): | |
| global matching_renamer_ui | |
| try: | |
| if matching_renamer_ui.script_job_id is not None: | |
| cmds.scriptJob(kill=matching_renamer_ui.script_job_id) | |
| matching_renamer_ui.close() | |
| matching_renamer_ui.deleteLater() | |
| except: | |
| pass | |
| matching_renamer_ui = MatchingRenamerUI() | |
| matching_renamer_ui.show() | |
| if __name__ == "__main__": | |
| show_ui() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment