From 2434da2b68df5564cb6d6fddc024539d316bea3e Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Fri, 22 Jul 2022 19:44:30 -0700 Subject: [PATCH 01/18] Minor PEP8 Cleanup --- python-scripts/gt_path_manager.py | 501 +++++++++++++++--------------- 1 file changed, 252 insertions(+), 249 deletions(-) diff --git a/python-scripts/gt_path_manager.py b/python-scripts/gt_path_manager.py index 1f694455..479e8d23 100644 --- a/python-scripts/gt_path_manager.py +++ b/python-scripts/gt_path_manager.py @@ -28,6 +28,9 @@ Changed to semantic versioning Removed unused imports Fixed "Refresh" button width + + 1.2.2 - 2022-07-22 + Minor PEP8 Cleanup Todo: Add support for Goalem Nodes @@ -50,35 +53,33 @@ from PySide2 import QtWidgets from shiboken2 import wrapInstance -import maya.OpenMaya as om -import maya.OpenMayaUI as omui +import maya.OpenMaya as OpenMaya +import maya.OpenMayaUI as OpenMayaUI import maya.cmds as cmds -import sys +import logging import os import re # Script Name -script_name = "GT Path Manager" +script_name = "GT Path Manager" # Version -script_version = '1.2.1' +script_version = '1.2.2' -# Python Version -python_version = sys.version_info.major +# Logging Setup +logging.basicConfig() +logger = logging.getLogger("gt_path_manager") +logger.setLevel(logging.INFO) def maya_main_window(): """ Return the Maya main window widget as a Python object """ - main_window_ptr = omui.MQtUtil.mainWindow() - - if python_version == 3: - return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) - else: - return wrapInstance(long(main_window_ptr), QtWidgets.QWidget) - - + main_window_ptr = OpenMayaUI.MQtUtil.mainWindow() + return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) + + def list_reference_pairs(): """ Returns all references and their paths. Used to get a reference path when the file is not found. @@ -88,8 +89,8 @@ def list_reference_pairs(): reference_list (list): A list of pairs, containing reference name and reference path """ - it = om.MItDependencyNodes(om.MFn.kReference) - ref_nodes = om.MObjectArray() + it = OpenMaya.MItDependencyNodes(OpenMaya.MFn.kReference) + ref_nodes = OpenMaya.MObjectArray() while not it.isDone(): ref_nodes.append(it.thisNode()) it.next() @@ -97,43 +98,53 @@ def list_reference_pairs(): ref_pairs = [] for i in range(ref_nodes.length()): try: - ref = ref_nodes.__getitem__(i) - mfn_ref = om.MFnReference(ref) + ref = ref_nodes.__getitem__(i) + mfn_ref = OpenMaya.MFnReference(ref) ref_pairs.append([mfn_ref.absoluteName(), mfn_ref.fileName(False, False, False)]) - except: - pass + except Exception as e: + logger.debug(str(e)) return ref_pairs - - + + class GTPathManagerDialog(QtWidgets.QDialog): """ Main GT Path Manager Class """ ATTR_ROLE = QtCore.Qt.UserRole VALUE_ROLE = QtCore.Qt.UserRole + 1 - + def __init__(self, parent=maya_main_window()): """ Create main dialog, set title and run other UI calls """ super(GTPathManagerDialog, self).__init__(parent) + self.search_path_label = None + self.search_replace_btn = None + self.refresh_btn = None + self.start_repair_btn = None + self.help_btn = None + self.title_label = None + self.filepath_le = None + self.select_dir_path_btn = None + self.table_wdg = None + self.only_files_cb = None + self.setWindowTitle(script_name + ' - (v' + str(script_version) + ')') self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint) self.setMinimumWidth(700) self.resize(self.width() + 250, 500) - + # Set Icon self.setWindowIcon(QtGui.QIcon(':/annotation.png')) - + # Setup Window Content and Signals self.create_widgets() self.create_layout() self.create_connections() - + # Remove Focus from Line Edit self.setFocus() # Initial Table Refresh self.refresh_table() - def create_widgets(self): """ Create Widgets """ # Title @@ -142,88 +153,86 @@ def create_widgets(self): border: 0px solid rgb(93, 93, 93); \ color: rgb(255, 255, 255);\ font: bold 12px; \ - padding: 5px;') + padding: 5px;') self.help_btn = QtWidgets.QPushButton('Help') - self.help_btn.setStyleSheet('color: rgb(255, 255, 255); font: bold 12px;') - + self.help_btn.setStyleSheet('color: rgb(255, 255, 255); font: bold 12px;') + # Search Path self.search_path_label = QtWidgets.QLabel("Search Path: ") self.filepath_le = QtWidgets.QLineEdit() self.filepath_le.setPlaceholderText('Path to a Directory') - + self.filepath_le.setMinimumSize(QtCore.QSize(380, 0)) self.select_dir_path_btn = QtWidgets.QPushButton() self.select_dir_path_btn.setIcon(QtGui.QIcon(':fileOpen.png')) self.select_dir_path_btn.setToolTip('Select Directory') - + self.table_wdg = QtWidgets.QTableWidget() self.table_wdg.setColumnCount(4) self.table_wdg.setColumnWidth(0, 22) self.table_wdg.setColumnWidth(1, 80) - self.table_wdg.setColumnWidth(3, 280) + self.table_wdg.setColumnWidth(3, 280) header_view = self.table_wdg.horizontalHeader() header_view.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) - + self.table_wdg.setHorizontalHeaderLabels(["", "Node", "Node Type", "Path"]) self.refresh_btn = QtWidgets.QPushButton("Refresh") self.refresh_btn.setFixedWidth(75) self.start_repair_btn = QtWidgets.QPushButton("Auto Path Repair") - #self.start_repair_btn.setFixedWidth(120) self.search_replace_btn = QtWidgets.QPushButton("Search and Replace") - + self.only_files_cb = QtWidgets.QCheckBox("Only File Nodes") - def create_layout(self): """ Layout """ # Build File Path Layout - file_path_layout = QtWidgets.QHBoxLayout() + file_path_layout = QtWidgets.QHBoxLayout() file_path_layout.addWidget(self.search_path_label) file_path_layout.addWidget(self.filepath_le) file_path_layout.addWidget(self.select_dir_path_btn) - + # Build Title Layout title_layout = QtWidgets.QHBoxLayout() title_layout.setSpacing(0) - title_layout.addWidget(self.title_label,5) + title_layout.addWidget(self.title_label, 5) title_layout.addWidget(self.help_btn) # Bottom Left Buttons (Search Path) button_layout = QtWidgets.QHBoxLayout() button_layout.addLayout(file_path_layout) - + # Bottom Right Buttons (Main Buttons) button_layout.setSpacing(2) button_layout.addStretch() - #button_layout.addWidget(self.only_files_cb) + # button_layout.addWidget(self.only_files_cb) button_layout.addWidget(self.start_repair_btn) button_layout.addWidget(self.search_replace_btn) button_layout.addWidget(self.refresh_btn) - + # Build Main Layout main_layout = QtWidgets.QVBoxLayout(self) main_layout.addLayout(title_layout) - main_layout.setContentsMargins(15, 15, 15, 11) # Make Margins Uniform LTRB + main_layout.setContentsMargins(15, 15, 15, 11) # Make Margins Uniform LTRB main_layout.addWidget(self.table_wdg) main_layout.addLayout(button_layout) - + def create_connections(self): """ Create Connections """ self.refresh_btn.clicked.connect(self.refresh_table) self.table_wdg.cellChanged.connect(self.on_cell_changed) self.table_wdg.cellClicked.connect(self.select_clicked_item) - + # Auto Path Repair Btn self.start_repair_btn.clicked.connect(self.start_attempt_repair) - + self.help_btn.clicked.connect(self.build_gui_help_path_manager) self.search_replace_btn.clicked.connect(self.build_gui_search_replace_path_manager) - + self.select_dir_path_btn.clicked.connect(self.show_dir_select_dialog) - + def show_dir_select_dialog(self): """ Invoke open file dialog so the user can select a search directory (Populate filepath_le with user input) """ multiple_filters = "Directories Only (.donotshowfiles)" @@ -233,14 +242,13 @@ def show_dir_select_dialog(self): if file_path: self.filepath_le.setText(file_path[0]) - def set_cell_changed_connection_enabled(self, enabled): - """ To turn on and off the connection so it doesn't update unnecessarily """ + """ To turn on and off the connection, so it doesn't update unnecessarily """ if enabled: self.table_wdg.cellChanged.connect(self.on_cell_changed) else: self.table_wdg.cellChanged.disconnect(self.on_cell_changed) - + def select_clicked_item(self, row): """ Executed when clicking on a table item, it tries to select the node clicked @@ -250,22 +258,19 @@ def select_clicked_item(self, row): try: if cmds.objExists(node_name): cmds.select(node_name) - except: - pass - + except Exception as e: + logger.debug(str(e)) def showEvent(self, e): """ Cause it to refresh when opening. I might have to change this for heavy projects """ super(GTPathManagerDialog, self).showEvent(e) - self.refresh_table - + self.refresh_table def keyPressEvent(self, e): """ Key presses should not be passed to the parent """ super(GTPathManagerDialog, self).keyPressEvent(e) e.accept() - def get_path_items(self, obj): """ Get a tuple containing file_path, is_valid_path, obj_type, obj_icon, obj_attr @@ -275,7 +280,7 @@ def get_path_items(self, obj): Returns: file_path (string): The path extracted from the object. - is_valid_path (bool): Whether or not the file exists in the system (or directory). + is_valid_path (bool): Whether the file exists in the system (or directory). obj_type (string): Type of object. E.g. "file". obj_icon (string): Icon path for the Node Type cell. obj_attr (string): Attribute used to get/set the new path. @@ -286,21 +291,21 @@ def get_path_items(self, obj): obj_icon = '' obj_attr = '' is_dir = False - + try: # Common Types - if obj_type == 'file': + if obj_type == 'file': obj_icon = ':file.svg' obj_type = obj_type.capitalize() obj_attr = '.fileTextureName' file_path = cmds.getAttr(obj + obj_attr) - + elif obj_type == 'audio': obj_icon = ':audio.svg' obj_type = obj_type.capitalize() obj_attr = '.filename' file_path = cmds.getAttr(obj + obj_attr) - + elif obj_type == 'cacheFile': obj_icon = ':cachedPlayback.png' obj_type = 'Cache File' @@ -308,20 +313,20 @@ def get_path_items(self, obj): path_no_file = cmds.getAttr(obj + obj_attr) or '' file_path = path_no_file + '/' + cmds.getAttr(obj + '.cacheName') + '.xml' file_path = file_path.replace('//', '/') - + elif obj_type == 'AlembicNode': obj_icon = ':enableAllCaches.png' obj_type = 'Alembic File' obj_attr = '.abc_File' file_path = cmds.getAttr(obj + obj_attr) - + elif obj_type == 'BifMeshImportNode': obj_icon = ':bifrostContainer.svg' obj_type = 'Bifrost Cache' obj_attr = '.bifMeshDirectory' is_dir = True file_path = cmds.getAttr(obj + obj_attr) - + elif obj_type == 'gpuCache': obj_icon = ':importCache.png' obj_type = 'GPU Cache' @@ -334,19 +339,19 @@ def get_path_items(self, obj): obj_type = 'aiPhotometricLight' obj_attr = '.aiFilename' file_path = cmds.getAttr(obj + obj_attr) - - elif obj_type == 'aiStandIn': + + elif obj_type == 'aiStandIn': obj_icon = ':envCube.svg' obj_type = 'aiStandIn' obj_attr = '.dso' file_path = cmds.getAttr(obj + obj_attr) - - elif obj_type == 'aiVolume': + + elif obj_type == 'aiVolume': obj_icon = ':cube.png' obj_type = 'aiVolume' obj_attr = '.filename' file_path = cmds.getAttr(obj + obj_attr) - + # Redshift elif obj_type == 'RedshiftProxyMesh': obj_icon = ':envCube.svg' @@ -377,21 +382,21 @@ def get_path_items(self, obj): obj_type = 'rsIESLight' obj_attr = '.profile' file_path = cmds.getAttr(obj + obj_attr) - + # MASH elif obj_type == 'MASH_Audio': obj_icon = ':audio.svg' obj_type = 'MASH Audio' obj_attr = '.filename' file_path = cmds.getAttr(obj + obj_attr) - + # Image Plane elif obj_type == 'imagePlane': obj_icon = ':imagePlane.svg' obj_type = 'Image Plane' obj_attr = '.imageName' file_path = cmds.getAttr(obj + obj_attr) - + # References elif obj_type == 'reference': obj_icon = ':reference.png' @@ -413,18 +418,18 @@ def get_path_items(self, obj): r_file = 'Unknown' print(e) file_path = r_file - + is_valid_path = os.path.isfile(file_path) if is_dir: is_valid_path = os.path.isdir(file_path) - - return (file_path, is_valid_path, obj_type, obj_icon, obj_attr) - except: - return (file_path, False, obj_type, obj_icon, obj_attr) + + return file_path, is_valid_path, obj_type, obj_icon, obj_attr + except Exception as e: + logger.debug(str(e)) + return file_path, False, obj_type, obj_icon, obj_attr else: return None - def refresh_table(self, is_repair_attempt=False, is_search_replace=False): """ Main Refresh Function @@ -441,25 +446,25 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): if os.path.isdir(search_dir): is_search_dir_valid = True else: - cmds.warning('The search directory doesn\'t exist. Please select a valid path and try again.') - + cmds.warning("The search directory doesn't exist. Please select a valid path and try again.") + self.set_cell_changed_connection_enabled(False) # So it doesn't update it unnecessarily - + self.table_wdg.setRowCount(0) # Remove all rows # Used to detect installed plugins node_types = cmds.ls(nodeTypes=True) - + # Common Nodes file_nodes = cmds.ls(type='file') path_nodes = file_nodes - + # Available Types available_node_types = ['audio', 'cacheFile', 'AlembicNode', 'gpuCache', 'BifMeshImportNode', 'RedshiftProxyMesh', 'RedshiftVolumeShape', 'RedshiftNormalMap', 'RedshiftDomeLight', 'RedshiftIESLight', 'MASH_Audio', 'aiPhotometricLight', 'aiStandIn', 'aiVolume', 'imagePlane'] - + # Add Types for Loaded Plugins path_node_types = [] for obj_type in available_node_types: @@ -471,22 +476,22 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): try: nodes_list = cmds.ls(type=node_type) path_nodes += nodes_list - except: - pass - + except Exception as e: + logger.debug(str(e)) + # Add References refs = cmds.ls(rf=True) path_nodes += refs # Populate Table for i in range(len(path_nodes)): - + # ################ Start Directory Search ################ # if is_repair_attempt and is_search_dir_valid: + progress_bar_name = 'Searching' try: # (path, is_path_valid, node_type_string, icon, node_attr) file_items = self.get_path_items(path_nodes[i]) - progress_bar_name = 'Searching' query_path = file_items[0] initial_result = os.path.exists(query_path) query_path = query_path.replace('\\', '/') # Format it - The main Query @@ -494,7 +499,7 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): accept_dir = False is_udim_file = False is_image_sequence = False - + # Check if using UDIMs or Image Sequences if file_items[2] == 'File': try: @@ -510,8 +515,8 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): uv_tiling_mode) query_path = udim_file_pattern is_udim_file = True - except: - pass + except Exception as e: + logger.debug(str(e)) # Handle desired folder (instead of file) if file_items[2] == 'Bifrost Cache': @@ -523,66 +528,67 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): is_found = False # If common locations are available try them first - if (initial_result != True) and (len(common_locations) != 0): + if initial_result is not True and (len(common_locations) != 0): for loc in common_locations: - formatted_path = loc.replace("\\","/") + formatted_path = loc.replace("\\", "/") formatted_path = formatted_path[::-1] formatted_path = formatted_path.split("/", 1)[-1] formatted_path = formatted_path[::-1] - common_path_result = os.path.exists(formatted_path + "/" + desired_file) - if common_path_result == True: - resolved_path = (formatted_path + "/" + desired_file).replace('/','\\') - #print(path_nodes[i] + ' found using known location.') # Debugging - self.set_attr_enhanced(path_nodes[i], file_items[4], resolved_path) + common_path_result = os.path.exists(formatted_path + "/" + desired_file) + if common_path_result is True: + resolved_path = (formatted_path + "/" + desired_file).replace('/', '\\') + # print(path_nodes[i] + ' found using known location.') # Debugging + self.set_attr_enhanced(path_nodes[i], file_items[4], resolved_path) is_found = True - + # Full Search/Walk - if (initial_result != True) and (is_found == False): - search_count = 0 # How many folders to look into (walk) for the progress bar + if initial_result is not True and is_found is False: + search_count = 0 # How many folders to look into (walk) for the progress bar # Generates the file names in a directory tree by walking the tree either top-b or b-top for path in os.walk(search_dir): - search_count += 1 + logger.debug(str(path)) + search_count += 1 resolved_path = query_path # make_progress_bar(name, maxVal) - Max value is the number of folders self.make_progress_bar(progress_bar_name, search_count) # root_dir_path, sub_dirs, files in os.walk(my_dir) for path, dirs, files in os.walk(search_dir): - self.move_progress_bar(progress_bar_name, 1) - path = path.replace('/','\\') - + self.move_progress_bar(progress_bar_name, 1) + path = path.replace('/', '\\') + # Handle Files if desired_file in files: resolved_path = (path + '\\' + desired_file).replace('/', '\\') - common_locations.append(resolved_path) + common_locations.append(resolved_path) is_found = True - + # Handle Folders (instead of files) if accept_dir and desired_file in dirs: resolved_path = (path + '\\' + desired_file).replace('/', '\\') - common_locations.append(resolved_path) + common_locations.append(resolved_path) is_found = True - + # Handle UDIMs - if is_udim_file and is_found == False: + if is_udim_file and is_found is False: file_name = os.path.splitext(desired_file)[0].replace('', '') extension = os.path.splitext(desired_file)[1] - pattern = re.compile(file_name + '\\d\\d\\d\\d' + extension) - + pattern = re.compile(file_name + '\\d\\d\\d\\d' + extension) + first_found_file = '' - + if any(pattern.match(line) for line in files): lines_to_log = [line for line in files if pattern.match(line)] first_found_file = lines_to_log[0] if first_found_file != '': - resolved_path = (path + '\\' + first_found_file).replace('/','\\') + resolved_path = (path + '\\' + first_found_file).replace('/', '\\') if os.path.exists(resolved_path): common_locations.append(resolved_path) is_found = True - + # Handle Image sequences - if is_image_sequence and is_found == False: + if is_image_sequence and is_found is False: file_name = os.path.splitext(desired_file)[0].replace('', '').replace('', '') extension = os.path.splitext(desired_file)[1] @@ -595,17 +601,18 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): first_found_file = lines_to_log[0] if first_found_file != '': - resolved_path = (path + '\\' + first_found_file).replace('/','\\') + resolved_path = (path + '\\' + first_found_file).replace('/', '\\') if os.path.exists(resolved_path): common_locations.append(resolved_path) is_found = True if is_found: - #print(path_nodes[i] + ' has a valid path.') # Debugging + # print(path_nodes[i] + ' has a valid path.') # Debugging self.set_attr_enhanced(path_nodes[i], file_items[4], resolved_path) - self.kill_progress_window(progress_bar_name) # Kill progress bar - except: - self.kill_progress_window(progress_bar_name) - # ################ End Directory Search ################ # + self.kill_progress_window(progress_bar_name) # Kill progress bar + except Exception as e: + logger.debug(str(e)) + self.kill_progress_window(progress_bar_name) + # ################ End Directory Search ################ # # Search and Replace if is_search_replace: @@ -615,49 +622,49 @@ def refresh_table(self, is_repair_attempt=False, is_search_replace=False): new_path = old_path.replace(self.search_string, self.replace_string) # (path, is_path_valid, node_type_string, icon, node_attr) self.set_attr_enhanced(path_nodes[i], file_items[4], new_path) - except: - pass - + except Exception as e: + logger.debug(str(e)) + # Refresh Table file_items = self.get_path_items(path_nodes[i]) self.table_wdg.insertRow(i) - - self.table_wdg.setFocusPolicy(QtCore.Qt.NoFocus) # No highlight - + + self.table_wdg.setFocusPolicy(QtCore.Qt.NoFocus) # No highlight + self.insert_item(i, 1, path_nodes[i], None, path_nodes[i]) - - if file_items: # (path, is_path_valid, node_type_string, icon, node_attr) + + if file_items: # (path, is_path_valid, node_type_string, icon, node_attr) if file_items[1]: self.insert_item(i, 2, file_items[2], None, cmds.objectType(path_nodes[i]), - icon_path=file_items[3], editable=False ) + icon_path=file_items[3], editable=False) self.insert_icon(i, 0, ':confirm.png') else: self.insert_item(i, 2, file_items[2], None, cmds.objectType(path_nodes[i]), - icon_path=file_items[3], editable=False ) + icon_path=file_items[3], editable=False) self.insert_icon(i, 0, ':error.png') self.insert_item(i, 3, file_items[0], file_items[4], file_items[0]) - + self.set_cell_changed_connection_enabled(True) - + def insert_item(self, row, column, node_name, attr, value, icon_path='', editable=True, centered=True): item = QtWidgets.QTableWidgetItem(node_name) # item.setBackgroundColor(QtGui.QColor(255,0,0, 10)) Make the background of the cells green/red? self.set_item_value(item, value) self.set_item_attr(item, attr) - + if icon_path != '': item.setIcon(QtGui.QIcon(icon_path)) - + if centered: item.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - + if not editable: item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) - - self.table_wdg.setItem(row, column, item) - - def insert_icon(self, row, column, icon_path): + + self.table_wdg.setItem(row, column, item) + + def insert_icon(self, row, column, icon_path): item = QtWidgets.QWidget() label = QtWidgets.QLabel() label.setScaledContents(True) @@ -670,37 +677,37 @@ def insert_icon(self, row, column, icon_path): layout.setAlignment(QtCore.Qt.AlignHCenter) layout.setContentsMargins(0, 5, 0, 5) item.setLayout(layout) - + self.table_wdg.setCellWidget(row, column, item) def set_item_text(self, item, text): item.setText(text) - + def get_item_text(self, item): return item.text() - + def set_item_attr(self, item, attr): item.setData(self.ATTR_ROLE, attr) - + def get_item_attr(self, item): return item.data(self.ATTR_ROLE) - + def set_item_value(self, item, value): item.setData(self.VALUE_ROLE, value) - + def get_item_value(self, item): return item.data(self.VALUE_ROLE) - + def on_cell_changed(self, row, column): self.set_cell_changed_connection_enabled(False) - + item = self.table_wdg.item(row, column) if column == 1: self.rename(item) if column == 3: self.repath(item) - + self.set_cell_changed_connection_enabled(True) def rename(self, item): @@ -711,19 +718,19 @@ def rename(self, item): if actual_new_name != new_name: self.set_item_text(item, actual_new_name) self.set_item_value(item, actual_new_name) - + def repath(self, item): old_path = self.get_item_value(item) new_path = self.get_item_text(item) attr_to_change = self.get_item_attr(item) - + object_name = self.get_item_value(self.table_wdg.item(item.row(), 1)) if old_path != new_path: try: is_valid_path = os.path.isfile(new_path) complex_output = self.set_attr_enhanced(object_name, attr_to_change, new_path) - - if complex_output != None and complex_output == False: + + if complex_output is not None and complex_output is False: self.set_item_value(item, old_path) self.set_item_text(item, old_path) is_valid_path = os.path.isfile(old_path) @@ -734,14 +741,14 @@ def repath(self, item): self.insert_icon(item.row(), 0, ':confirm.png') else: self.insert_icon(item.row(), 0, ':error.png') - + self.set_cell_changed_connection_enabled(True) self.refresh_table() except Exception as e: self.set_item_value(item, old_path) self.set_cell_changed_connection_enabled(True) self.refresh_table() - raise e + raise e def set_attr_enhanced(self, obj, attribute, new_value): """ @@ -752,49 +759,52 @@ def set_attr_enhanced(self, obj, attribute, new_value): attribute (string): Name of the attribute to set. E.g. ".cacheFile" new_value (string): New value to update """ - #print(obj + ' ' + attribute + ' ' + new_value) # Debugging - + # print(obj + ' ' + attribute + ' ' + new_value) # Debugging + if cmds.objExists(obj): obj_type = cmds.objectType(obj) or '' else: obj_type = '' - + complex_types = ['cacheFile', 'reference'] - + if obj_type not in complex_types: - cmds.setAttr( obj + attribute , new_value, type='string') + cmds.setAttr(obj + attribute, new_value, type='string') else: if obj_type == 'cacheFile': - format_path = os.path.splitext(new_value)[0].replace("\\","/") + format_path = os.path.splitext(new_value)[0].replace("\\", "/") file_name = format_path.split('/')[-1] format_path_no_file = format_path[::-1].split("/", 1)[-1][::-1] - + try: - if os.path.isfile(format_path_no_file + '/' + file_name.replace('.xml','') + '.xml'): + if os.path.isfile(format_path_no_file + '/' + file_name.replace('.xml', '') + '.xml'): cmds.setAttr(obj + '.cachePath', format_path_no_file, type='string') cmds.setAttr(obj + '.cacheName', file_name, type='string') return True else: return False - except: + except Exception as e: + logger.debug(str(e)) return False - + if obj_type == 'reference': not_skipped = True try: - cmds.referenceQuery(obj,isLoaded=True) - except: + cmds.referenceQuery(obj, isLoaded=True) + except Exception as e: + logger.debug(str(e)) not_skipped = False - + if not_skipped: if os.path.isfile(new_value): try: cmds.file(new_value, loadReference=obj) - except: + except Exception as e: + logger.debug(str(e)) return False else: cmds.warning('Provided reference path : "' + - new_value + '" doesn\'t lead to a valid file. Previous path was retained.') + new_value + "\" doesn't lead to a valid file. Previous path was retained.") else: cmds.warning('Reference file inaccessible.') @@ -802,7 +812,6 @@ def start_attempt_repair(self): """ Runs refresh function while searching for files """ self.refresh_table(is_repair_attempt=True) - def make_progress_bar(self, prog_win_name, max_value): """ Create Progress Window @@ -812,15 +821,15 @@ def make_progress_bar(self, prog_win_name, max_value): max_value (int): The maximum or "ending" value of the progress indicator. """ - if(cmds.window(prog_win_name, q=1, ex=1)): + if cmds.window(prog_win_name, q=1, ex=1): cmds.deleteUI(prog_win_name) - if(cmds.windowPref(prog_win_name, q=1, ex=1)): + if cmds.windowPref(prog_win_name, q=1, ex=1): cmds.windowPref(prog_win_name, r=1) prog_window = cmds.window(prog_win_name, title=prog_win_name, widthHeight=(300, 50)) cmds.columnLayout(p=prog_win_name) - progress_control = cmds.progressBar(prog_win_name + '_progress', maxValue=max_value, width=300, height=50) - cmds.showWindow( prog_window ) + cmds.progressBar(prog_win_name + '_progress', maxValue=max_value, width=300, height=50) + cmds.showWindow(prog_window) def move_progress_bar(self, prog_win_name, step_size): cmds.progressBar(prog_win_name + '_progress', edit=True, step=step_size) @@ -828,35 +837,35 @@ def move_progress_bar(self, prog_win_name, step_size): def kill_progress_window(self, prog_win_name): """ Close progress window in case it exists - + Args: prog_win_name (string): Name of the window """ - if(cmds.window(prog_win_name, q=1, ex=1)): + if cmds.window(prog_win_name, q=1, ex=1): cmds.deleteUI(prog_win_name) - if(cmds.windowPref(prog_win_name, q=1, ex=1)): + if cmds.windowPref(prog_win_name, q=1, ex=1): cmds.windowPref(prog_win_name, r=1) - + def build_gui_help_path_manager(self): """ Creates the Help GUI for GT Path Manager """ window_name = "build_gui_help_path_manager" if cmds.window(window_name, exists=True): cmds.deleteUI(window_name, window=True) - cmds.window(window_name, title= script_name + " Help", mnb=False, mxb=False, s=True) - cmds.window(window_name, e=True, s=True, wh=[1,1]) + cmds.window(window_name, title=script_name + " Help", mnb=False, mxb=False, s=True) + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + main_column = cmds.columnLayout(p=window_name) - main_column = cmds.columnLayout(p= window_name) - # Title Text - cmds.separator(h=12, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p=main_column) # Window Size Adjustment - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) # Title Column - cmds.text(script_name + " Help", bgc=[.4,.4,.4], fn="boldLabelFont", align="center") - cmds.separator(h=10, style='none', p=main_column) # Empty Space + cmds.separator(h=12, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p=main_column) # Window Size Adjustment + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) # Title Column + cmds.text(script_name + " Help", bgc=[.4, .4, .4], fn="boldLabelFont", align="center") + cmds.separator(h=10, style='none', p=main_column) # Empty Space # Body ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1,10)], p=main_column) + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) # cmds.text(l='Script for managing paths', align="center") # cmds.separator(h=15, style='none') # Empty Space @@ -865,11 +874,11 @@ def build_gui_help_path_manager(self): cmds.separator(h=10, style='none') # Empty Space cmds.text(l='You can select the node listed by clicking on it or \nchange its name or path by double ' 'clicking the cell.', align="center") - + cmds.separator(h=10, style='none') # Empty Space cmds.text(l='The icon on the left describes the validity of the path.\nIf the file or directory is found in ' 'the system it shows\n a green confirm icon otherwise it shows a red icon.', align="center") - + cmds.separator(h=10, style='none') # Empty Space cmds.text(l='Auto Path Repair', align="center", font='boldLabelFont') cmds.text(l='This function walks through the folders under the\nprovided directory looking for missing files. ' @@ -883,122 +892,116 @@ def build_gui_help_path_manager(self): cmds.separator(h=10, style='none') # Empty Space cmds.text(l='Search Path', align="center", font='boldLabelFont') cmds.text(l='A directory path used when looking for missing files.', align="center") - + cmds.separator(h=15, style='none') # Empty Space - cmds.rowColumnLayout(nc=2, cw=[(1, 140),(2, 140)], cs=[(1,10),(2, 0)], p=main_column) + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p=main_column) cmds.text('Guilherme Trevisan ') cmds.text(l='TrevisanGMW@gmail.com', hl=True, highlightColor=[1, 1, 1]) - cmds.rowColumnLayout(nc=2, cw=[(1, 140),(2, 140)], cs=[(1,10),(2, 0)], p=main_column) + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p=main_column) cmds.separator(h=10, style='none') # Empty Space cmds.text(l='Github', hl=True, highlightColor=[1, 1, 1]) cmds.separator(h=7, style='none') # Empty Space - + # Close Button - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1,10)], p=main_column) - + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) + cmds.separator(h=5, style='none') cmds.button(l='OK', h=30, c=lambda args: close_help_gui()) cmds.separator(h=8, style='none') - + # Show and Lock Window cmds.showWindow(window_name) cmds.window(window_name, e=True, s=False) - + # Set Window Icon - qw = omui.MQtUtil.findWindow(window_name) - if python_version == 3: - widget = wrapInstance(int(qw), QtWidgets.QWidget) - else: - widget = wrapInstance(long(qw), QtWidgets.QWidget) + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QtWidgets.QWidget) icon = QtGui.QIcon(':/question.png') widget.setWindowIcon(icon) - + def close_help_gui(): """ Closes Help UI in case it's opened. """ if cmds.window(window_name, exists=True): cmds.deleteUI(window_name, window=True) - def build_gui_search_replace_path_manager(self): """ Creates the GUI for Searching and Replacing Paths """ window_name = "build_gui_search_replace_path_manager" if cmds.window(window_name, exists=True): cmds.deleteUI(window_name, window=True) - cmds.window(window_name, title= 'Search and Replace', mnb=False, mxb=False, s=True) - cmds.window(window_name, e=True, s=True, wh=[1,1]) + cmds.window(window_name, title='Search and Replace', mnb=False, mxb=False, s=True) + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + main_column = cmds.columnLayout(p=window_name) - main_column = cmds.columnLayout(p= window_name) - # Body - cmds.separator(h=12, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1,10)], p=main_column) + cmds.separator(h=12, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) cmds.text(l='This will search and replace strings in your paths', align="center") - cmds.separator(h=12, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p=main_column) # Window Size Adjustment - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) # Title Column - cmds.text('Search for:', bgc=[.4,.4,.4], fn="boldLabelFont", align="center") - search_txtfield = cmds.textField(placeholderText='Type search here') - cmds.separator(h=10, style='none') # Empty Space - cmds.text('Replace with:', bgc=[.4,.4,.4], fn="boldLabelFont", align="center") - replace_txtfield = cmds.textField(placeholderText='Type replace here') - + cmds.separator(h=12, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p=main_column) # Window Size Adjustment + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p=main_column) # Title Column + cmds.text('Search for:', bgc=[.4, .4, .4], fn="boldLabelFont", align="center") + search_txt_field = cmds.textField(placeholderText='Type search here') + cmds.separator(h=10, style='none') # Empty Space + cmds.text('Replace with:', bgc=[.4, .4, .4], fn="boldLabelFont", align="center") + replace_txt_field = cmds.textField(placeholderText='Type replace here') + # Close Button cmds.separator(h=5, style='none') - cmds.rowColumnLayout(nc=2, cw=[(1, 148),(2, 148)], cs=[(1,10),(2,4)], p=main_column) - + cmds.rowColumnLayout(nc=2, cw=[(1, 148), (2, 148)], cs=[(1, 10), (2, 4)], p=main_column) + # Apply Button cmds.button(l='Search and Replace', h=30, c=lambda args: apply_search_replace()) - - #cmds.separator(h=10, style='none') + cmds.button(l='Cancel', h=30, c=lambda args: close_snr_gui()) cmds.separator(h=8, style='none') - + # Show and Lock Window cmds.showWindow(window_name) cmds.window(window_name, e=True, s=False) - + # Set Window Icon - qw = omui.MQtUtil.findWindow(window_name) - if python_version == 3: - widget = wrapInstance(int(qw), QtWidgets.QWidget) - else: - widget = wrapInstance(long(qw), QtWidgets.QWidget) + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QtWidgets.QWidget) icon = QtGui.QIcon(':/search.png') widget.setWindowIcon(icon) - + def apply_search_replace(): """ Runs Search and Replace Function """ - self.search_string = cmds.textField(search_txtfield, q=True, text=True) - self.replace_string = cmds.textField(replace_txtfield, q=True, text=True) - + self.search_string = cmds.textField(search_txt_field, q=True, text=True) + self.replace_string = cmds.textField(replace_txt_field, q=True, text=True) + if self.search_string != '': try: gt_path_manager_dialog.show() - except: - pass + except Exception as e: + logger.debug(str(e)) self.refresh_table(is_search_replace=True) if cmds.window(window_name, exists=True): cmds.deleteUI(window_name, window=True) else: cmds.warning('"Search for" string can\'t be empty.') - + def close_snr_gui(): """ Closes Search and Replace GUI in case it's opened. """ if cmds.window(window_name, exists=True): cmds.deleteUI(window_name, window=True) - + + def try_to_close_gt_path_manager(): """ Attempts to close GT Path Manager """ try: - gt_path_manager_dialog.close() # pylint: disable=E0601 + gt_path_manager_dialog.close() # pylint: disable=E0601 gt_path_manager_dialog.deleteLater() - except: - pass + except Exception as e: + logger.debug(str(e)) + # Build GUI if __name__ == "__main__": try_to_close_gt_path_manager() gt_path_manager_dialog = GTPathManagerDialog() - gt_path_manager_dialog.show() \ No newline at end of file + gt_path_manager_dialog.show() From d7992b01242ca69a692b58cd66ea3f1caa2302a1 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Fri, 22 Jul 2022 19:44:42 -0700 Subject: [PATCH 02/18] Added GUI and logger --- python-scripts/gt_attributes_to_python.py | 203 +++++++++++++++++++++- 1 file changed, 197 insertions(+), 6 deletions(-) diff --git a/python-scripts/gt_attributes_to_python.py b/python-scripts/gt_attributes_to_python.py index 12dbfa1f..58521129 100644 --- a/python-scripts/gt_attributes_to_python.py +++ b/python-scripts/gt_attributes_to_python.py @@ -9,22 +9,49 @@ Added option to strip zeroes Added auto conversion of "-0"s into "0"s for clarity - Todo: - Create GUI - Add Logger + 0.0.4 - 2022-07-22 + Added GUI + Added logger + + TODO: + Add options + Create "Extra User-Defined Attributes" function """ +from maya import OpenMayaUI as OpenMayaUI import maya.cmds as cmds +import logging +import sys + +try: + from shiboken2 import wrapInstance +except ImportError: + from shiboken import wrapInstance + +try: + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QWidget +except ImportError: + from PySide.QtGui import QIcon, QWidget + +# Logging Setup +logging.basicConfig() +logger = logging.getLogger("gt_shape_extract_state") +logger.setLevel(logging.INFO) # Script Name script_name = 'GT Attributes to Python' # Version: -script_version = "0.0.3" +script_version = "0.0.4" DIMENSIONS = ['x', 'y', 'z'] DEFAULT_CHANNELS = ['t', 'r', 's'] +# if __name__ == '__main__': +# # default_attr_to_python(None, use_loop=True) +# attr_to_list(None, separate_channels=False) + def attr_to_list(obj_list, printing=True, decimal_place=2, separate_channels=False, strip_zeroes=True): """ @@ -156,6 +183,170 @@ def default_attr_to_python(obj_list, printing=True, use_loop=False, decimal_plac return output +# Function for the "Run Code" button +def run_output_code(out): + try: + exec(out) + except Exception as e: + cmds.warning("Something is wrong with your code!") + cmds.warning(e) + + +# Main Form ============================================================================ +def build_gui_attr_to_python(): + window_name = "build_gui_attr_to_python" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name) + + # Main GUI Start Here ================================================================================= + window_gui_attr_to_python = cmds.window(window_name, title=script_name + ' (v' + script_version + ')', + titleBar=True, mnb=False, mxb=False, sizeable=True) + + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + content_main = cmds.columnLayout(adj=True) + + # Title + title_bgc_color = (.4, .4, .4) + cmds.separator(h=10, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)], p=content_main) # Window Size Adjustment + cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 325), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], + p=content_main) # Title Column + cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space + cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") + cmds.button(l="Help", bgc=title_bgc_color, c=lambda x: build_gui_help_attr_to_python()) + cmds.separator(h=10, style='none', p=content_main) # Empty Space + + # Body ==================== + cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)], p=content_main) + + cmds.rowColumnLayout(nc=2, cw=[(1, 190), (2, 190)], cs=[(1, 10), (2, 5)], p=content_main) + + cmds.button(l="Extract Default Attributes to \"setAttr\"", bgc=(.6, .6, .6), + c=lambda x: _btn_extract_attr(attr_type='default')) + cmds.button(l="Extract Default Attributes to List", bgc=(.6, .6, .6), + c=lambda x: _btn_extract_attr(attr_type='list')) + cmds.separator(h=5, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 386)], cs=[(1, 10)], p=content_main) + cmds.button(l="Extract User-Defined Attributes", bgc=(.6, .6, .6), + c=lambda x: _btn_extract_attr(attr_type='user'), en=0) + cmds.separator(h=10, style='none') # Empty Space + cmds.separator(h=10, style='none', p=content_main) # Empty Space + cmds.separator(h=10, p=content_main) + + # Bottom ==================== + cmds.rowColumnLayout(nc=1, cw=[(1, 390)], cs=[(1, 10)], p=content_main) + cmds.text(label='Output Python Code') + output_python = cmds.scrollField(editable=True, wordWrap=True) + cmds.separator(h=10, style='none') # Empty Space + cmds.button(l="Run Code", c=lambda x: run_output_code(cmds.scrollField(output_python, query=True, text=True))) + cmds.separator(h=10, style='none') # Empty Space + + def _btn_extract_attr(attr_type='default'): + selection = cmds.ls(selection=True) or [] + + if len(selection) == 0: + cmds.warning('Make sure you selected at least one curve and try again.') + return + + if attr_type == 'list': + output_python_command = attr_to_list(selection, printing=False, decimal_place=2, + separate_channels=False, strip_zeroes=True) + elif attr_type == 'user': + output_python_command = 'User-defined output placeholder' # TODO + else: + output_python_command = default_attr_to_python(selection, printing=False, use_loop=False, + decimal_place=2, strip_zeroes=True) + if len(output_python_command) == 0: + cmds.warning('Make sure you selected at least one object and try again.') + return + + if output_python_command.startswith('\n'): + output_python_command = output_python_command[1:] + + print(output_python_command) + if len(selection) == 1: + sys.stdout.write('Attributes for "' + str(selection[0] + '" extracted. ' + '(Output to Script Editor and GUI)')) + else: + sys.stdout.write('Attributes for ' + str(len(selection)) + ' extracted. ' + '(Output to Script Editor and GUI)') + cmds.scrollField(output_python, e=True, ip=1, it='') # Bring Back to the Top + cmds.scrollField(output_python, edit=True, wordWrap=True, text='', sl=True) + cmds.scrollField(output_python, edit=True, wordWrap=True, text=output_python_command, sl=True) + cmds.setFocus(output_python) + + # Show and Lock Window + cmds.showWindow(window_gui_attr_to_python) + cmds.window(window_name, e=True, s=False) + + # Set Window Icon + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QWidget) + icon = QIcon(':/arcLengthDimension.svg') + widget.setWindowIcon(icon) + + # Main GUI Ends Here ================================================================================= + + +# Creates Help GUI +def build_gui_help_attr_to_python(): + window_name = "build_gui_help_attr_to_python" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + cmds.window(window_name, title=script_name + " Help", mnb=False, mxb=False, s=True) + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + cmds.columnLayout("main_column", p=window_name) + + # Title Text + cmds.separator(h=12, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p="main_column") # Window Size Adjustment + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") # Title Column + cmds.text(script_name + " Help", bgc=[.4, .4, .4], fn="boldLabelFont", align="center") + cmds.separator(h=10, style='none', p="main_column") # Empty Space + + # Body ==================== + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") + cmds.text(l='This script generates the Python code necessary to recreate\na curve shape state', align="left") + cmds.separator(h=10, style='none') # Empty Space + cmds.text(l='"Extract Default Attributes to \"setAttr\"" button:', align="left", fn="boldLabelFont") + cmds.text(l='Outputs the python code necessary to set the TRS \n(Translate, Rotate, and Scale) attributes ' + 'back to their\ncurrent value.', align="left") + cmds.separator(h=10, style='none') # Empty Space + cmds.text(l='Run Code:', align="left", fn="boldLabelFont") + cmds.text(l='Attempts to run the code (or anything written) inside ', align="left") + cmds.text(l='"Output Python Curve" box', align="left") + cmds.separator(h=15, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") + cmds.text('Guilherme Trevisan ') + cmds.text(l='TrevisanGMW@gmail.com', hl=True, highlightColor=[1, 1, 1]) + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") + cmds.separator(h=15, style='none') # Empty Space + cmds.text(l='Github', hl=True, highlightColor=[1, 1, 1]) + cmds.separator(h=7, style='none') # Empty Space + + # Close Button + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") + cmds.separator(h=10, style='none') + cmds.button(l='OK', h=30, c=lambda args: close_help_gui()) + cmds.separator(h=8, style='none') + + # Show and Lock Window + cmds.showWindow(window_name) + cmds.window(window_name, e=True, s=False) + + # Set Window Icon + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QWidget) + icon = QIcon(':/question.png') + widget.setWindowIcon(icon) + + def close_help_gui(): + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + if __name__ == '__main__': - # default_attr_to_python(None, use_loop=True) - attr_to_list(None, separate_channels=False) + build_gui_attr_to_python() From 36fa2f5dd6079fd215cd8f095311c7bdec08083d Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Fri, 22 Jul 2022 21:26:38 -0700 Subject: [PATCH 03/18] Added attributes to python script --- mel-scripts/gt_tools_menu.mel | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mel-scripts/gt_tools_menu.mel b/mel-scripts/gt_tools_menu.mel index 394e99fb..a757b0aa 100644 --- a/mel-scripts/gt_tools_menu.mel +++ b/mel-scripts/gt_tools_menu.mel @@ -166,6 +166,9 @@ // 2.1.0 - 2022-07-19 // Added Render Calculator // +// 2.2.0 - 2022-07-19 +// Added Attributes to Python +// //---------------------------------------------------------------------------- // Globals @@ -253,13 +256,18 @@ menuItem -l "Tools" -sm true -to true -image "toolSettings.png"; -ann ("Script for getting and setting translate and rotate world space data.") -image "buttonManip.svg" ; + menuItem + -l ("Attributes to Python") + -c ("python(\"gt_tools.execute_script('gt_attributes_to_python', 'build_gui_attr_to_python')\");") + -ann ("Converts attributes into Python code. TRS Channels or User-defined.") + -image "attributes.png" ; + menuItem -l ("Render Checklist") -c ("python(\"gt_tools.execute_script('gt_render_checklist', 'build_gui_gt_render_checklist')\");") -ann ("Performs a series of checks to detect common issues that are often accidently ignored/unnoticed.") -image "checkboxOn.png" ; - setParent -menu ".." ; From 7352eb161f2d5c710730724213d2c8a6e26d3b79 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Fri, 22 Jul 2022 21:26:44 -0700 Subject: [PATCH 04/18] Updated version --- python-scripts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-scripts/__init__.py b/python-scripts/__init__.py index f41334fd..20aff23c 100644 --- a/python-scripts/__init__.py +++ b/python-scripts/__init__.py @@ -6,7 +6,7 @@ import os # Global Vars -PACKAGE_VERSION = "2.1.3" +PACKAGE_VERSION = "2.2.0" # Initial Setup - Add path and initialize logger if __name__ != '__main__': From 89cc00f7262b4867f0c71404b25c6ca96a94c063 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Fri, 22 Jul 2022 21:26:57 -0700 Subject: [PATCH 05/18] Added user-defined extraction --- python-scripts/gt_attributes_to_python.py | 89 +++++++++++++++++++---- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/python-scripts/gt_attributes_to_python.py b/python-scripts/gt_attributes_to_python.py index 58521129..c04b902e 100644 --- a/python-scripts/gt_attributes_to_python.py +++ b/python-scripts/gt_attributes_to_python.py @@ -13,9 +13,12 @@ Added GUI Added logger + 0.0.5 - 2022-07-22 + Increased the size of the UI + Added "Extract User-Defined Attributes" function + TODO: Add options - Create "Extra User-Defined Attributes" function """ from maya import OpenMayaUI as OpenMayaUI @@ -43,7 +46,7 @@ script_name = 'GT Attributes to Python' # Version: -script_version = "0.0.4" +script_version = "0.0.5" DIMENSIONS = ['x', 'y', 'z'] DEFAULT_CHANNELS = ['t', 'r', 's'] @@ -183,6 +186,59 @@ def default_attr_to_python(obj_list, printing=True, use_loop=False, decimal_plac return output +def user_attr_to_python(obj_list, printing=True, decimal_place=2, strip_zeroes=True): + """ + Returns a string + Args: + obj_list (list, none): List objects to extract the transform from (if empty, it will try to use selection) + printing (optional, bool): If active, the function will print the values to the script editor + decimal_place (optional, int): How precise you want the extracted values to be (formats the float it gets) + strip_zeroes (optional, bool): If active, it will remove unnecessary zeroes (e.g. 0.0 -> 0) + + Returns: + Python code with extracted transform values + + """ + if not obj_list: + obj_list = cmds.ls(selection=True) + if not obj_list: + return + + output = '' + if printing: + output += ('#' * 80) + + for obj in obj_list: + output += '\n# User-Defined Attribute Data for "' + obj + '":\n' + data = {} + attributes = cmds.listAttr(obj, userDefined=True) or [] + if not attributes: + output += '# No user-defined attributes found on this object.\n' + else: + for attr in attributes: # TRS + # not cmds.getAttr(obj + '.' + attr, lock=True) # TODO Check if locked + attr_type = cmds.getAttr(obj + '.' + attr, typ=True) + value = cmds.getAttr(obj + '.' + attr) + if attr_type == 'double3': + pass + elif attr_type == 'string': + output += 'cmds.setAttr("' + obj + '.' + attr + '", "' + str(value) + '", typ="string")\n' + else: + output += 'cmds.setAttr("' + obj + '.' + attr + '", ' + str(value) + ')\n' + + # Return / Print + if printing: + output += ('#' * 80) + if output.replace('#', ''): + print(output) + return output + else: + print('No data found. Make sure your selection at least one object with user-defined attributes.') + return None + else: + return output + + # Function for the "Run Code" button def run_output_code(out): try: @@ -209,8 +265,8 @@ def build_gui_attr_to_python(): # Title title_bgc_color = (.4, .4, .4) cmds.separator(h=10, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)], p=content_main) # Window Size Adjustment - cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 325), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], + cmds.rowColumnLayout(nc=1, cw=[(1, 500)], cs=[(1, 10)], p=content_main) # Window Size Adjustment + cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 430), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], p=content_main) # Title Column cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") @@ -218,26 +274,28 @@ def build_gui_attr_to_python(): cmds.separator(h=10, style='none', p=content_main) # Empty Space # Body ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 500)], cs=[(1, 10)], p=content_main) - cmds.rowColumnLayout(nc=2, cw=[(1, 190), (2, 190)], cs=[(1, 10), (2, 5)], p=content_main) + default_attr_button_cw = 243 + cmds.rowColumnLayout(nc=2, cw=[(1, default_attr_button_cw), (2, default_attr_button_cw)], + cs=[(1, 10), (2, 5)], p=content_main) cmds.button(l="Extract Default Attributes to \"setAttr\"", bgc=(.6, .6, .6), c=lambda x: _btn_extract_attr(attr_type='default')) cmds.button(l="Extract Default Attributes to List", bgc=(.6, .6, .6), c=lambda x: _btn_extract_attr(attr_type='list')) cmds.separator(h=5, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 386)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 10)], p=content_main) cmds.button(l="Extract User-Defined Attributes", bgc=(.6, .6, .6), - c=lambda x: _btn_extract_attr(attr_type='user'), en=0) + c=lambda x: _btn_extract_attr(attr_type='user')) cmds.separator(h=10, style='none') # Empty Space cmds.separator(h=10, style='none', p=content_main) # Empty Space cmds.separator(h=10, p=content_main) # Bottom ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 390)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 10)], p=content_main) cmds.text(label='Output Python Code') - output_python = cmds.scrollField(editable=True, wordWrap=True) + output_python = cmds.scrollField(editable=True, wordWrap=True, height=200) cmds.separator(h=10, style='none') # Empty Space cmds.button(l="Run Code", c=lambda x: run_output_code(cmds.scrollField(output_python, query=True, text=True))) cmds.separator(h=10, style='none') # Empty Space @@ -246,14 +304,15 @@ def _btn_extract_attr(attr_type='default'): selection = cmds.ls(selection=True) or [] if len(selection) == 0: - cmds.warning('Make sure you selected at least one curve and try again.') + cmds.warning('Make sure you selected at least one object and try again.') return if attr_type == 'list': output_python_command = attr_to_list(selection, printing=False, decimal_place=2, separate_channels=False, strip_zeroes=True) elif attr_type == 'user': - output_python_command = 'User-defined output placeholder' # TODO + output_python_command = user_attr_to_python(selection, printing=False, + decimal_place=2, strip_zeroes=True) else: output_python_command = default_attr_to_python(selection, printing=False, use_loop=False, decimal_place=2, strip_zeroes=True) @@ -269,8 +328,8 @@ def _btn_extract_attr(attr_type='default'): sys.stdout.write('Attributes for "' + str(selection[0] + '" extracted. ' '(Output to Script Editor and GUI)')) else: - sys.stdout.write('Attributes for ' + str(len(selection)) + ' extracted. ' - '(Output to Script Editor and GUI)') + sys.stdout.write('Attributes extracted for ' + str(len(selection)) + ' objects. ' + '(Output to Script Editor and GUI)') cmds.scrollField(output_python, e=True, ip=1, it='') # Bring Back to the Top cmds.scrollField(output_python, edit=True, wordWrap=True, text='', sl=True) cmds.scrollField(output_python, edit=True, wordWrap=True, text=output_python_command, sl=True) @@ -283,7 +342,7 @@ def _btn_extract_attr(attr_type='default'): # Set Window Icon qw = OpenMayaUI.MQtUtil.findWindow(window_name) widget = wrapInstance(int(qw), QWidget) - icon = QIcon(':/arcLengthDimension.svg') + icon = QIcon(':/attributes.png') widget.setWindowIcon(icon) # Main GUI Ends Here ================================================================================= From 157c186da0be17e503beab73d33ac75e29c72068 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Fri, 22 Jul 2022 21:34:10 -0700 Subject: [PATCH 06/18] Increased the size of the output window --- python-scripts/gt_extract_bound_joints.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python-scripts/gt_extract_bound_joints.py b/python-scripts/gt_extract_bound_joints.py index 68e621fc..a6d01d65 100644 --- a/python-scripts/gt_extract_bound_joints.py +++ b/python-scripts/gt_extract_bound_joints.py @@ -17,6 +17,9 @@ 1.0.1 - 2022-07-20 Updated help menu +1.0.2 - 2022-07-22 +Increased the size of the output window + Todo: Add Transfer functions Add save as set button @@ -46,7 +49,7 @@ script_name = "GT - Extract Bound Joints" # Version -script_version = "1.0.0" +script_version = "1.0.2" # Settings extract_joints_settings = {'filter_non_existent': True, @@ -104,7 +107,7 @@ def build_gui_extract_bound_joints(): # Bottom ==================== cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 10)], p=content_main) cmds.text(label='Output - Selection Command:') - output_python = cmds.scrollField(editable=True, wordWrap=True) + output_python = cmds.scrollField(editable=True, wordWrap=True, height=200) cmds.separator(h=10, style='none') # Empty Space cmds.button(l="Run Code", c=lambda x: run_output_code(cmds.scrollField(output_python, query=True, text=True))) cmds.separator(h=10, style='none') # Empty Space From 45f7b9588e1180a5962858b68f83b60d78de01d5 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 10:09:08 -0700 Subject: [PATCH 07/18] Updated script name and window size --- python-scripts/gt_shape_curve_to_python.py | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/python-scripts/gt_shape_curve_to_python.py b/python-scripts/gt_shape_curve_to_python.py index 739d2083..e2eddbf9 100644 --- a/python-scripts/gt_shape_curve_to_python.py +++ b/python-scripts/gt_shape_curve_to_python.py @@ -30,6 +30,10 @@ Added patch version PEP8 General cleanup + 1.6.2 - 2022-07-14 + Updated script name + Increased the size of the output window + """ import maya.cmds as cmds @@ -54,10 +58,10 @@ logger.setLevel(logging.INFO) # Script Name -script_name = "GT - Generate Python Curve" +script_name = "GT - Extract Python Curve" # Version: -script_version = "1.6.1" +script_version = "1.6.2" # Default Settings close_curve = False @@ -91,8 +95,8 @@ def build_gui_py_curve(): # Title title_bgc_color = (.4, .4, .4) cmds.separator(h=10, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 270)], cs=[(1, 10)], p=content_main) # Window Size Adjustment - cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 200), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], + cmds.rowColumnLayout(nc=1, cw=[(1, 500)], cs=[(1, 10)], p=content_main) # Window Size Adjustment + cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 430), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], p=content_main) # Title Column cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") @@ -100,23 +104,23 @@ def build_gui_py_curve(): cmds.separator(h=10, style='none', p=content_main) # Empty Space # Body ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 470)], cs=[(1, 10)], p=content_main) - cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)]) + cmds.rowColumnLayout(nc=1, cw=[(1, 460)], cs=[(1, 70)]) - settings = cmds.checkBoxGrp(columnWidth2=[150, 1], numberOfCheckBoxes=2, + settings = cmds.checkBoxGrp(columnWidth2=[260, 1], numberOfCheckBoxes=2, label1='Add import \"maya.cmds\" ', label2="Force Open", v1=add_import, v2=close_curve) - cmds.rowColumnLayout(nc=1, cw=[(1, 230)], cs=[(1, 0)]) + cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 10)], p=content_main) cmds.separator(h=10, style='none') # Empty Space - cmds.button(l="Generate", bgc=(.6, .6, .6), c=lambda x: generate_python_curve()) + cmds.button(l="Extract Curve to Python", bgc=(.6, .6, .6), c=lambda x: generate_python_curve()) cmds.separator(h=10, style='none', p=content_main) # Empty Space cmds.separator(h=10, p=content_main) # Bottom ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 10)], p=content_main) cmds.text(label='Output Python Curve') - output_python = cmds.scrollField(editable=True, wordWrap=True) + output_python = cmds.scrollField(editable=True, wordWrap=True, height=200) cmds.separator(h=10, style='none') # Empty Space cmds.button(l="Run Code", c=lambda x: run_output_code(cmds.scrollField(output_python, query=True, text=True))) cmds.separator(h=10, style='none') # Empty Space From d86fcee78623366aa28697ef2d37c1ec5377e8cd Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 10:18:10 -0700 Subject: [PATCH 08/18] Added logger --- python-scripts/gt_rigger_biped_logic.py | 118 +++++++----------------- 1 file changed, 32 insertions(+), 86 deletions(-) diff --git a/python-scripts/gt_rigger_biped_logic.py b/python-scripts/gt_rigger_biped_logic.py index 69275c32..6135a1d7 100644 --- a/python-scripts/gt_rigger_biped_logic.py +++ b/python-scripts/gt_rigger_biped_logic.py @@ -280,6 +280,10 @@ 1.9.13 - 2022-06-30 Added "biped_proxy_pose" stored as a string to main_ctrl + 1.9.14 - 2022-07-23 + Added logging + Some PEP8 Cleanup + TODO Biped Rigger: Transfer scale information from ik spine limit spine to spines Add option to leave all lock translation attributes off @@ -293,10 +297,16 @@ from gt_rigger_data import * from gt_shape_offset import offset_curve_shape import maya.cmds as cmds +import logging import random import json import re +# Logging Setup +logging.basicConfig() +logger = logging.getLogger("gt_rigger_biped_logic") +logger.setLevel(logging.INFO) + def create_proxy(data_biped): """ @@ -1665,7 +1675,8 @@ def create_proxy(data_biped): cmds.select(d=True) unique_message = '<' + str(random.random()) + '>' cmds.inViewMessage( - amg=unique_message + 'Proxy was created!', + amg=unique_message + '' + 'Proxy was created!', pos='botLeft', fade=True, alpha=.9) @@ -1697,12 +1708,12 @@ def orient_to_target(obj_name, target, orient_offset=(0, 0, 0), proxy_obj=None, Parameters: obj_name (string): Name of the object to orient (usually a joint) target (string): Name of the target object (usually the element that will be the child of "obj") - orient_offset (tuple): A tuple containing three 32b floats, used as a rotate offset to change the + orient_offset (tuple): A tuple containing three 32b floats, used as a rotation offset to change the result orientation. proxy_obj (string): The name of the proxy element (used as extra rotation input) aim_vec (tuple): A tuple of floats used for the aim vector of the aim constraint. Default: (1, 0, 0) up_vec (tuple): A tuple of floats used for the up vector of the aim constraint. Default: (0, -1, 0) - brute_force (bool): Creates up and and dir points to determine orientation (Uses proxy object) + brute_force (bool): Creates up and dir points to determine orientation (Uses proxy object) """ if proxy_obj: cmds.delete(cmds.orientConstraint(proxy_obj, obj_name, offset=(0, 0, 0))) @@ -1750,52 +1761,6 @@ def orient_offset(obj_name, orient_offset=(0, 0, 0), apply=True): cmds.setAttr(obj_name + '.rotateZ', orient_offset[2]) cmds.makeIdentity(obj_name, apply=True, rotate=True) - # def create_simple_fk_control(jnt_name, scale_offset, create_offset_grp=True): - # """ - # Creates a simple fk control. Used to quickly iterate through the creation of the finger controls - # - # Parameters: - # jnt_name (string): Name of the joint that will be controlled - # scale_offset (float): The scale offset applied to the control before freezing it - # create_offset_grp (bool): Whether or not an offset group will be created - # Returns: - # control_name_and_group (tuple): The name of the generated control and the name of its ctrl group - # - # """ - # fk_ctrl = cmds.curve(name=jnt_name.replace(JNT_SUFFIX, '') + CTRL_SUFFIX, - # p=[[0.0, 0.0, 0.0], [0.0, 0.897, 0.0], [0.033, 0.901, 0.0], [0.064, 0.914, 0.0], - # [0.091, 0.935, 0.0], [0.111, 0.961, 0.0], [0.124, 0.992, 0.0], [0.128, 1.025, 0.0], - # [0.0, 1.025, 0.0], [0.0, 0.897, 0.0], [-0.033, 0.901, 0.0], [-0.064, 0.914, 0.0], - # [-0.091, 0.935, 0.0], [-0.111, 0.961, 0.0], [-0.124, 0.992, 0.0], [-0.128, 1.025, 0.0], - # [-0.124, 1.058, 0.0], [-0.111, 1.089, 0.0], [-0.091, 1.116, 0.0], [-0.064, 1.136, 0.0], - # [-0.033, 1.149, 0.0], [0.0, 1.153, 0.0], [0.033, 1.149, 0.0], [0.064, 1.136, 0.0], - # [0.091, 1.116, 0.0], [0.111, 1.089, 0.0], [0.124, 1.058, 0.0], [0.128, 1.025, 0.0], - # [-0.128, 1.025, 0.0], [0.0, 1.025, 0.0], [0.0, 1.153, 0.0]], d=1) - # fk_ctrl_grp = cmds.group(name=fk_ctrl + GRP_SUFFIX.capitalize(), empty=True, world=True) - # - # fk_ctrl_offset_grp = '' - # if create_offset_grp: - # fk_ctrl_offset_grp = cmds.group(name=fk_ctrl + 'Offset' + GRP_SUFFIX.capitalize(), empty=True, world=True) - # cmds.parent(fk_ctrl, fk_ctrl_offset_grp) - # cmds.parent(fk_ctrl_offset_grp, fk_ctrl_grp) - # else: - # cmds.parent(fk_ctrl, fk_ctrl_grp) - # - # cmds.setAttr(fk_ctrl + '.scaleX', scale_offset) - # cmds.setAttr(fk_ctrl + '.scaleY', scale_offset) - # cmds.setAttr(fk_ctrl + '.scaleZ', scale_offset) - # cmds.makeIdentity(fk_ctrl, apply=True, scale=True) - # - # cmds.delete(cmds.parentConstraint(jnt_name, fk_ctrl_grp)) - # if 'left_' in jnt_name: - # change_viewport_color(fk_ctrl, LEFT_CTRL_COLOR) - # elif 'right_' in jnt_name: - # change_viewport_color(fk_ctrl, RIGHT_CTRL_COLOR) - # - # for shape in cmds.listRelatives(fk_ctrl, s=True, f=True) or []: - # cmds.rename(shape, '{0}Shape'.format(fk_ctrl)) - # - # return fk_ctrl, fk_ctrl_grp, fk_ctrl_offset_grp def remove_numbers(string): """ @@ -2221,22 +2186,6 @@ def remove_numbers(string): cmds.parent(rig_joints.get('right_forearm_jnt'), rig_joints.get('right_elbow_jnt')) change_viewport_color(rig_joints.get('right_forearm_jnt'), (1, 1, 0)) - # # Left Eye Orient - # temp_transform = cmds.group(empty=True, world=True, name=biped_data.elements.get('left_eye_proxy_crv') + '_orient_target') - # cmds.delete(cmds.parentConstraint(biped_data.elements.get('left_eye_proxy_crv'), temp_transform)) - # cmds.parent(temp_transform, biped_data.elements.get('left_eye_proxy_crv')) - # cmds.setAttr(temp_transform + '.tz', 1) - # orient_to_target(rig_joints.get('left_eye_jnt'), temp_transform, (0,0,0), biped_data.elements.get('left_eye_proxy_crv'))#, (-1,0,0)) - # cmds.delete(temp_transform) - - # # Right Eye Orient - # temp_transform = cmds.group(empty=True, world=True, name=biped_data.elements.get('right_eye_proxy_crv') + '_orient_target') - # cmds.delete(cmds.parentConstraint(biped_data.elements.get('right_eye_proxy_crv'), temp_transform)) - # cmds.parent(temp_transform, biped_data.elements.get('right_eye_proxy_crv')) - # cmds.setAttr(temp_transform + '.tz', 1) - # orient_to_target(rig_joints.get('right_eye_jnt'), temp_transform, (0,0,0), biped_data.elements.get('right_eye_proxy_crv'))#, (-1,0,0)) - # cmds.delete(temp_transform) - # ###### Create Organization Groups ###### # Create Skeleton Group skeleton_grp = cmds.group(name=('skeleton_' + GRP_SUFFIX), empty=True, world=True) @@ -2632,8 +2581,8 @@ def remove_numbers(string): for shape in cmds.listRelatives(main_ctrl, s=True, f=True) or []: try: cmds.setAttr(shape + '.lineWidth', 3) - except: - pass + except Exception as e: + logger.debug(str(e)) change_viewport_color(main_ctrl, (1, 0.171, 0.448)) main_ctrl_grp = cmds.group(name=main_ctrl + GRP_SUFFIX.capitalize(), empty=True, world=True) @@ -3563,7 +3512,7 @@ def remove_numbers(string): niceName='Rotate Order') cmds.connectAttr(left_foot_ik_ctrl + '.rotationOrder', left_foot_ik_ctrl + '.rotateOrder', f=True) - ################# Right Leg FK ################# + # ################# Right Leg FK ################# # Calculate Scale Offset right_leg_scale_offset = 0 @@ -3955,7 +3904,7 @@ def remove_numbers(string): change_viewport_color(left_wrist_ctrl, LEFT_CTRL_COLOR) cmds.parent(left_wrist_ctrl_grp, left_elbow_ctrl) - ################# Left Fingers FK ################# + # ################# Left Fingers FK ################# # Left Fingers Parent left_hand_grp = cmds.group(name='left_hand_' + GRP_SUFFIX, empty=True, world=True) cmds.delete(cmds.parentConstraint(rig_joints.get('left_wrist_jnt'), left_hand_grp)) @@ -4297,7 +4246,7 @@ def remove_numbers(string): change_viewport_color(right_wrist_ctrl, RIGHT_CTRL_COLOR) cmds.parent(right_wrist_ctrl_grp, right_elbow_ctrl) - ################# Right Fingers FK ################# + # ################# Right Fingers FK ################# # Right Fingers Parent right_hand_grp = cmds.group(name='right_hand_' + GRP_SUFFIX, empty=True, world=True) cmds.delete(cmds.parentConstraint(rig_joints.get('right_wrist_jnt'), right_hand_grp)) @@ -5144,7 +5093,7 @@ def remove_numbers(string): change_viewport_color(right_heel_roll_ctrl, RIGHT_CTRL_COLOR) cmds.parent(right_heel_roll_ctrl_grp, right_foot_offset_data_grp) - ####### Left Finger Automation Controls ####### + # ####### Left Finger Automation Controls ####### # Left Fingers left_fingers_ctrl_a = cmds.curve(name='left_fingers_' + CTRL_SUFFIX, p=[[0.0, 0.127, -0.509], [0.047, 0.194, -0.474], [0.079, 0.237, -0.449], @@ -6264,22 +6213,19 @@ def remove_numbers(string): left_ik_finger_chains = [] for finger in ['thumb', 'index', 'middle', 'ring', 'pinky']: left_ik_finger_jnts = [] + finger_name = rig_joints.get('left_' + finger + '01_jnt').replace(JNT_SUFFIX, 'ik_' + JNT_SUFFIX) left_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('left_' + finger + '01_jnt'), - name=rig_joints.get('left_' + finger + '01_jnt').replace(JNT_SUFFIX, - 'ik_' + JNT_SUFFIX), - po=True)) + name=finger_name, po=True)) + finger_name = rig_joints.get('left_' + finger + '02_jnt').replace(JNT_SUFFIX, 'ik_' + JNT_SUFFIX) left_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('left_' + finger + '02_jnt'), - name=rig_joints.get('left_' + finger + '02_jnt').replace(JNT_SUFFIX, - 'ik_' + JNT_SUFFIX), - po=True)) + name=finger_name, po=True)) + finger_name = rig_joints.get('left_' + finger + '03_jnt').replace(JNT_SUFFIX, 'ik_' + JNT_SUFFIX) left_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('left_' + finger + '03_jnt'), - name=rig_joints.get('left_' + finger + '03_jnt').replace(JNT_SUFFIX, - 'ik_' + JNT_SUFFIX), - po=True)) + name=finger_name, po=True)) + finger_name = rig_joints.get('left_' + finger + '04_jnt') + finger_name = finger_name.replace('end' + JNT_SUFFIX.capitalize(), 'ik_' + 'end' + JNT_SUFFIX.capitalize()) left_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('left_' + finger + '04_jnt'), - name=rig_joints.get('left_' + finger + '04_jnt').replace( - 'end' + JNT_SUFFIX.capitalize(), - 'ik_' + 'end' + JNT_SUFFIX.capitalize()), po=True)) + name=finger_name, po=True)) left_ik_finger_chains.append(left_ik_finger_jnts) ik_finger_handles = [] @@ -7056,7 +7002,7 @@ def remove_numbers(string): cmds.setAttr(right_fingers_ctrl + '.minScaleZLimitEnable', 1) cmds.setAttr(right_fingers_ctrl + '.maxScaleZLimitEnable', 1) - # A list of tuples of tuples 1:[thumb, index...], 2:(f_01, f_02, f_03), 3:(finger_ctrl, ctrl_grp, ctrl_offset)1 + # A list of tuples of tuples 1:[thumb, index...], 2:(f_01, f_02, f_03), 3:(finger_ctrl, ctrl_grp, ctrl_offset) for obj in right_fingers_list: finger_name = remove_numbers(obj[0][0].replace(CTRL_SUFFIX, '')) ctrl_offset = obj[0][2] @@ -7652,7 +7598,7 @@ def remove_numbers(string): setup_shape_switch(left_foot_ik_ctrl, attr='controlShape', shape_names=['box', 'flat', 'pin'], shape_enum=['Box', 'Flat', 'Pin']) - # Left Foot In-Between Offset + # Left Foot In-Between # Offset ctrl was created earlier when creating the original ctrl cmds.setAttr(left_foot_offset_ik_ctrl + '.scaleX', .9) cmds.setAttr(left_foot_offset_ik_ctrl + '.scaleY', .9) @@ -7998,7 +7944,7 @@ def remove_numbers(string): setup_shape_switch(right_foot_ik_ctrl, attr='controlShape', shape_names=['box', 'flat', 'pin'], shape_enum=['Box', 'Flat', 'Pin']) - # Right Foot In-Between Offset + # Right Foot In-Between # Offset ctrl was created earlier when creating the original ctrl cmds.setAttr(right_foot_offset_ik_ctrl + '.scaleX', .9) cmds.setAttr(right_foot_offset_ik_ctrl + '.scaleY', .9) From a3555d4d5e42df851d66ef80bc0e8094c6ccc413 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 10:52:31 -0700 Subject: [PATCH 09/18] PEP8 Cleanup --- python-scripts/gt_rigger_biped_logic.py | 68 ++++++++++++------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/python-scripts/gt_rigger_biped_logic.py b/python-scripts/gt_rigger_biped_logic.py index 6135a1d7..5ed8fa19 100644 --- a/python-scripts/gt_rigger_biped_logic.py +++ b/python-scripts/gt_rigger_biped_logic.py @@ -107,7 +107,7 @@ Created a custom help window that takes strings as help inputs to display it to the user 1.7.7 - 2021-10-21 - Changed the behaviour for when creating a real-time skeleton so it overwrites the original skeleton + Changed the behavior for when creating a real-time skeleton so it overwrites the original skeleton 1.7.8 - 2021-10-24 Added aim lines to pole vectors and eye controls @@ -310,8 +310,8 @@ def create_proxy(data_biped): """ - Creates a proxy (guide) skeleton used to later generate entire rig - + Creates a proxy (guide) skeleton used to later generate entire rig + Args: data_biped (GTBipedRiggerData) : Object containing naming and settings for the proxy creation """ @@ -1751,7 +1751,7 @@ def orient_offset(obj_name, orient_offset=(0, 0, 0), apply=True): Args: obj_name (string): Name of the object to orient (usually a joint) - orient_offset (tuple): A tuple containing three 32b floats, used as a rotate offset to change + orient_offset (tuple): A tuple containing three 32b floats, used as a rotation offset to change the result orientation. apply (optional, bool): Whether to execute the function """ @@ -2011,7 +2011,7 @@ def remove_numbers(string): if 'endJnt' in joint or rig_joints.get('right_toe_jnt') in joint or rig_joints.get('left_toe_jnt') in joint: cmds.setAttr(joint + '.radius', .6 * joint_scale_offset) add_node_note(joint, - 'This is an end joint. This means that this joint shouldn\'t be an influence when skinning.') + "This is an end joint. This means that this joint shouldn't be an influence when skinning.") change_outliner_color(joint, (1, 0, 0)) change_viewport_color(joint, (1, 0, 0)) # Eye Joints @@ -2288,10 +2288,9 @@ def remove_numbers(string): cmds.parent(left_wrist_fk_jnt, left_elbow_fk_jnt) # Right Arms FK/IK + right_clavicle_switch_jnt = rig_joints.get('right_clavicle_jnt').replace(JNT_SUFFIX, 'switch_' + JNT_SUFFIX) right_clavicle_switch_jnt = cmds.duplicate(rig_joints.get('right_clavicle_jnt'), - name=rig_joints.get('right_clavicle_jnt').replace(JNT_SUFFIX, - 'switch_' + JNT_SUFFIX), - parentOnly=True)[0] + name=right_clavicle_switch_jnt, parentOnly=True)[0] cmds.setAttr(right_clavicle_switch_jnt + '.radius', ik_jnt_scale) change_viewport_color(right_clavicle_switch_jnt, ikfk_jnt_color) cmds.parent(right_clavicle_switch_jnt, skeleton_grp) @@ -5184,7 +5183,7 @@ def remove_numbers(string): cmds.setAttr(left_fingers_abduction_ctrl[0] + '.overrideEnabled', 1) cmds.setAttr(left_fingers_abduction_ctrl[0] + '.overrideDisplayType', 1) - # ############ Left Fingers Control Behaviour Attributes ############ + # ############ Left Fingers Control Behavior Attributes ############ cmds.addAttr(left_fingers_ctrl, ln=CUSTOM_ATTR_SEPARATOR, at='enum', en='-------------:', keyable=True) cmds.setAttr(left_fingers_ctrl + '.' + CUSTOM_ATTR_SEPARATOR, lock=True) @@ -5339,7 +5338,7 @@ def remove_numbers(string): cmds.setAttr(right_fingers_abduction_ctrl[0] + '.overrideEnabled', 1) cmds.setAttr(right_fingers_abduction_ctrl[0] + '.overrideDisplayType', 1) - # ########### Right Fingers Control Behaviour Attributes ############ + # ########### Right Fingers Control Behavior Attributes ############ cmds.addAttr(right_fingers_ctrl, ln=CUSTOM_ATTR_SEPARATOR, at='enum', en='-------------:', keyable=True) cmds.setAttr(right_fingers_ctrl + '.' + CUSTOM_ATTR_SEPARATOR, lock=True) @@ -5450,9 +5449,9 @@ def remove_numbers(string): cmds.connectAttr(ribbon_sur + '.worldMatrix', follicle + '.inputWorldMatrix', force=True) # Connect transforms to follicle (so it knows where it is) cmds.connectAttr(follicle + '.outTranslate', follicle_transform + '.translate', - force=True) # Connects follicleShape position to its transform (default behaviour) + force=True) # Connects follicleShape position to its transform (default behavior) cmds.connectAttr(follicle + '.outRotate', follicle_transform + '.rotate', - force=True) # Connects follicleShape rotate to its transform (default behaviour) + force=True) # Connects follicleShape rotate to its transform (default behavior) follicle_transform = cmds.rename(follicle_transform, follicle_data) cmds.parent(follicle_transform, spine_ik_grp) spine_follicles[follicle_data] = follicle_transform @@ -6307,17 +6306,17 @@ def remove_numbers(string): limit_condition_node = cmds.createNode('condition', name=finger_name + 'limit') multiply_node = cmds.createNode('multiplyDivide', name=finger_name + MULTIPLY_SUFFIX) - attribute_fist_pose_long = finger_name.replace('left_', '').replace('right_', '').replace('_', - '') + 'FistPoseLimit' - attribute_fist_pose_nice = 'Fist Pose Limit ' + finger_name.replace('left_', '').replace('right_', '').replace( - '_', '').capitalize() + attribute_fist_pose_long = finger_name.replace('left_', '').replace('right_', '') + attribute_fist_pose_long = attribute_fist_pose_long.replace('_', '') + 'FistPoseLimit' + attribute_fist_pose_nice = finger_name.replace('left_', '').replace('right_', '').replace('_', '').capitalize() + attribute_fist_pose_nice = 'Fist Pose Limit ' + attribute_fist_pose_nice cmds.addAttr(left_fingers_ctrl, ln=attribute_fist_pose_long, at='double', k=True, niceName=attribute_fist_pose_nice) cmds.setAttr(left_fingers_ctrl + '.' + attribute_fist_pose_long, -90) attribute_long_name = finger_name.replace('left_', '').replace('right_', '').replace('_', '') + 'Multiplier' - attribute_nice_name = 'Rot Multiplier ' + finger_name.replace('left_', '').replace('right_', '').replace('_', - '').capitalize() + attribute_nice_name = finger_name.replace('left_', '').replace('right_', '').replace('_', '').capitalize() + attribute_nice_name = 'Rot Multiplier ' + attribute_nice_name cmds.addAttr(left_fingers_ctrl, ln=attribute_long_name, at='double', k=True, niceName=attribute_nice_name) # Set Default Values @@ -6789,22 +6788,19 @@ def remove_numbers(string): right_ik_finger_chains = [] for finger in ['thumb', 'index', 'middle', 'ring', 'pinky']: right_ik_finger_jnts = [] + finger_name = rig_joints.get('right_' + finger + '01_jnt').replace(JNT_SUFFIX, 'ik_' + JNT_SUFFIX) right_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('right_' + finger + '01_jnt'), - name=rig_joints.get('right_' + finger + '01_jnt').replace(JNT_SUFFIX, - 'ik_' + JNT_SUFFIX), - po=True)) + name=finger_name, po=True)) + finger_name = rig_joints.get('right_' + finger + '02_jnt').replace(JNT_SUFFIX, 'ik_' + JNT_SUFFIX) right_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('right_' + finger + '02_jnt'), - name=rig_joints.get('right_' + finger + '02_jnt').replace(JNT_SUFFIX, - 'ik_' + JNT_SUFFIX), - po=True)) + name=finger_name, po=True)) + finger_name = rig_joints.get('right_' + finger + '03_jnt').replace(JNT_SUFFIX, 'ik_' + JNT_SUFFIX) right_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('right_' + finger + '03_jnt'), - name=rig_joints.get('right_' + finger + '03_jnt').replace(JNT_SUFFIX, - 'ik_' + JNT_SUFFIX), - po=True)) + name=finger_name, po=True)) + finger_name = rig_joints.get('right_' + finger + '04_jnt') + finger_name = finger_name.replace('end' + JNT_SUFFIX.capitalize(), 'ik_' + 'end' + JNT_SUFFIX.capitalize()) right_ik_finger_jnts.append(cmds.duplicate(rig_joints.get('right_' + finger + '04_jnt'), - name=rig_joints.get('right_' + finger + '04_jnt').replace( - 'end' + JNT_SUFFIX.capitalize(), - 'ik_' + 'end' + JNT_SUFFIX.capitalize()), po=True)) + name=finger_name, po=True)) right_ik_finger_chains.append(right_ik_finger_jnts) ik_finger_handles = [] @@ -6888,15 +6884,15 @@ def remove_numbers(string): attribute_fist_pose_long = finger_name.replace('right_', '').replace('right_', '').replace('_', '') + 'FistPoseLimit' - attribute_fist_pose_nice = 'Fist Pose Limit ' + finger_name.replace('right_', '').replace('right_', '').replace( - '_', '').capitalize() + attribute_fist_pose_nice = finger_name.replace('right_', '').replace('right_', '').replace('_', '').capitalize() + attribute_fist_pose_nice = 'Fist Pose Limit ' + attribute_fist_pose_nice cmds.addAttr(right_fingers_ctrl, ln=attribute_fist_pose_long, at='double', k=True, niceName=attribute_fist_pose_nice) cmds.setAttr(right_fingers_ctrl + '.' + attribute_fist_pose_long, -90) attribute_long_name = finger_name.replace('right_', '').replace('right_', '').replace('_', '') + 'Multiplier' - attribute_nice_name = 'Rot Multiplier ' + finger_name.replace('right_', '').replace('right_', '').replace('_', - '').capitalize() + attribute_nice_name = finger_name.replace('right_', '').replace('right_', '').replace('_', '').capitalize() + attribute_nice_name = 'Rot Multiplier ' + attribute_nice_name cmds.addAttr(right_fingers_ctrl, ln=attribute_long_name, at='double', k=True, niceName=attribute_nice_name) # Set Default Values @@ -10258,8 +10254,8 @@ def remove_numbers(string): all_jnts = cmds.ls(type='joint') for jnt in all_jnts: cmds.setAttr(jnt + ".displayLocalAxis", 1) - except: - pass + except Exception as e: + logger.debug(str(e)) if debugging_show_fk_fingers: cmds.setAttr(left_fingers_ctrl + '.showFkFingerCtrls', 1) From ea07c9f3fa043422af221f73b2d24d4d080a0636 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 10:54:57 -0700 Subject: [PATCH 10/18] Fixed a typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e7fff1f2..24be2dc6 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ In case you need/want to manually install the scripts. It's also a pretty straig
  • How do I update GT Tools to a new version?
    A: Simply uninstall and install it again.
  • What do I do if I have multiple "userSetup.mel" files? One inside "maya/####/scripts" and another one inside "maya/scripts"
    A: The "userSetup.mel" file gets executed when you open Maya, but Maya supports only one file. In case you have two files it will give priority to the file located inside "maya/####/scripts", so manage your initialization commands there.
  • -
  • Where are the other scripts you had in this repository?
    A: I moved all other scripts that are not part of GT Tools to another reposity. Here is the link: TrevisanGMW/maya-scripts
  • +
  • Where are the other scripts you had in this repository?
    A: I moved all other scripts that are not part of GT Tools to another repository. Here is the link: TrevisanGMW/maya-scripts

Contributors

From 48df6a6aa20f5b61cefebc9dd52c4aebeebae857 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 11:01:12 -0700 Subject: [PATCH 11/18] Increased the size of the main window --- python-scripts/gt_shape_extract_state.py | 35 +++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/python-scripts/gt_shape_extract_state.py b/python-scripts/gt_shape_extract_state.py index 508b7742..ab72bc16 100644 --- a/python-scripts/gt_shape_extract_state.py +++ b/python-scripts/gt_shape_extract_state.py @@ -2,16 +2,20 @@ GT Extract Shape State - Outputs the python code containing the current shape data for the selected curves github.com/TrevisanGMW/gt-tools - 2021-10-01 -v1.0.0 - 2021-10-01 -Initial Release + 1.0.0 - 2021-10-01 + Initial Release -v1.1.0 - 2022-03-16 -Added GUI and checks -Added option to print or just return it + 1.1.0 - 2022-03-16 + Added GUI and checks + Added option to print or just return it + + 1.2.0 - 2022-07-14 + Added GUI + Added logger + + 1.2.1 - 2022-07-23 + Increased the size of the main window -v1.2.0 - 2022-07-14 -Added GUI -Added logger """ from maya import OpenMayaUI as OpenMayaUI import maya.cmds as cmds @@ -38,7 +42,7 @@ script_name = "GT - Extract Shape State" # Version -script_version = "1.2.0" +script_version = "1.2.1" def extract_python_curve_shape(curve_transforms, printing=False): @@ -118,8 +122,8 @@ def build_gui_curve_shape_state(): # Title title_bgc_color = (.4, .4, .4) cmds.separator(h=10, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)], p=content_main) # Window Size Adjustment - cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 325), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], + cmds.rowColumnLayout(nc=1, cw=[(1, 500)], cs=[(1, 10)], p=content_main) # Window Size Adjustment + cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 430), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], p=content_main) # Title Column cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") @@ -127,18 +131,17 @@ def build_gui_curve_shape_state(): cmds.separator(h=10, style='none', p=content_main) # Empty Space # Body ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)], p=content_main) - cmds.rowColumnLayout(nc=1, cw=[(1, 400)], cs=[(1, 10)]) - cmds.rowColumnLayout(nc=1, cw=[(1, 370)], cs=[(1, 0)]) + cmds.rowColumnLayout(nc=1, cw=[(1, 500)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 0)]) cmds.separator(h=10, style='none') # Empty Space cmds.button(l="Extract State", bgc=(.6, .6, .6), c=lambda x: _btn_extract_python_curve_shape()) cmds.separator(h=10, style='none', p=content_main) # Empty Space cmds.separator(h=10, p=content_main) # Bottom ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 390)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 490)], cs=[(1, 10)], p=content_main) cmds.text(label='Output Python Curve') - output_python = cmds.scrollField(editable=True, wordWrap=True) + output_python = cmds.scrollField(editable=True, wordWrap=True, height=200) cmds.separator(h=10, style='none') # Empty Space cmds.button(l="Run Code", c=lambda x: run_output_code(cmds.scrollField(output_python, query=True, text=True))) cmds.separator(h=10, style='none') # Empty Space From 8c9a065aa66d9d9541ca20133eca9856db84f393 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 22:08:15 -0700 Subject: [PATCH 12/18] Added "Morphing Attributes" --- mel-scripts/gt_tools_menu.mel | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mel-scripts/gt_tools_menu.mel b/mel-scripts/gt_tools_menu.mel index a757b0aa..3151495a 100644 --- a/mel-scripts/gt_tools_menu.mel +++ b/mel-scripts/gt_tools_menu.mel @@ -167,7 +167,8 @@ // Added Render Calculator // // 2.2.0 - 2022-07-19 -// Added Attributes to Python +// Added "Attributes to Python" +// Added "Morphing Attributes" // //---------------------------------------------------------------------------- @@ -404,6 +405,12 @@ menuItem -l "Rigging" -sm true -to true -image "kinReroot.png"; -ann ("Automated solution for connecting multiple attributes.") -image "hsRearrange.png"; + menuItem + -l ("Morphing Attributes") + -c ("python(\"gt_tools.execute_script('gt_attributes_to_python', 'build_gui_morphing_attributes')\");") + -ann ("Creates attributes to drive selected blend shapes.") + -image "blendShape.png"; + menuItem -l ("Mirror Cluster Tool") -c ("python(\"gt_tools.execute_script('gt_mirror_cluster_tool', 'build_gui_mirror_cluster_tool')\");") @@ -436,7 +443,7 @@ menuItem -l "Rigging" -sm true -to true -image "kinReroot.png"; menuItem -l ("Add Sine Attributes") - -c ("python(\"gt_tools.execute_script('gt_add_sine_attributes', 'build_gui_add_sine_attr')\");") + -c ("python(\"gt_tools.execute_script('gt_sine_attributes', 'build_gui_add_sine_attr')\");") -ann ("Create Sine function without using third-party plugins or expressions.") -image "sineCurveProfile.png" ; From b776d7aeacba053f906a540e1131947f19d83483 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 22:09:56 -0700 Subject: [PATCH 13/18] Renamed scripts --- python-scripts/gt_blends_to_attributes.py | 91 --- python-scripts/gt_morphing_attributes.py | 429 +++++++++++ ...ne_attributes.py => gt_sine_attributes.py} | 686 +++++++++--------- 3 files changed, 772 insertions(+), 434 deletions(-) delete mode 100644 python-scripts/gt_blends_to_attributes.py create mode 100644 python-scripts/gt_morphing_attributes.py rename python-scripts/{gt_add_sine_attributes.py => gt_sine_attributes.py} (97%) diff --git a/python-scripts/gt_blends_to_attributes.py b/python-scripts/gt_blends_to_attributes.py deleted file mode 100644 index 07f3dfb9..00000000 --- a/python-scripts/gt_blends_to_attributes.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -GT Blends to Attributes -github.com/TrevisanGMW/gt-tools - 2022-03-17 - -0.0.1 - 2022-03-17 -Created core functions -""" -import maya.cmds as cmds - -selection_source = cmds.ls(selection=True)[0] # First selected object - Geo with BS -selection_target = cmds.ls(selection=True)[1] # Second selected object - Curve -history = cmds.listHistory(selection_source) -blendshape_node = cmds.ls(history, type='blendShape')[0] -blendshape_names = cmds.listAttr(blendshape_node + '.w', m=True) - -modify_range = True -attribute_range_min = 0 -attribute_range_max = 10 -blend_range_min = 0 -blend_range_max = 1 - -ignore_connected = True -add_separator_attribute = True -custom_separator_attr = '' -method = 'includes' - -undesired_filter_strings = ['corrective'] -desired_blends = [] -desired_filter_strings = ['jaw', 'cheek', 'mouth', 'nose'] -filtered_blends = [] - -# Find desired blends -for target in blendshape_names: - for desired_filter_string in desired_filter_strings: - if method == 'includes': - if desired_filter_string in target: - filtered_blends.append(target) - elif method == 'startswith': - if target.startswith(desired_filter_string): - filtered_blends.append(target) - elif method == 'endswith': - if target.endswith(desired_filter_string): - filtered_blends.append(target) - -if len(desired_filter_strings) == 0: # If filter empty, use everything - filtered_blends = blendshape_names - - -if ignore_connected: # Pre-ignore connected blends - accessible_blends = [] - for blend in filtered_blends: - connections = cmds.listConnections(blendshape_node + '.' + blend, destination=False, plugs=True) or [] - if len(connections) == 0: - accessible_blends.append(blend) -else: - accessible_blends = filtered_blends - -# Remove undesired blends from list -undesired_blends = [] -for target in desired_blends: - for undesired_string in undesired_filter_strings: - if undesired_string in target: - undesired_blends.append(target) -for blend in accessible_blends: - if blend not in undesired_blends: - desired_blends.append(blend) - -# Separator Attribute -if len(desired_blends) != 0 and add_separator_attribute: - separator_attr = 'blends' - if custom_separator_attr: - separator_attr = custom_separator_attr - cmds.addAttr(selection_target, ln=separator_attr, at='enum', en='-------------:', keyable=True) - cmds.setAttr(selection_target + '.' + separator_attr, e=True, lock=True) - -# Create Blend Drivers -desired_blends.sort() -for target in desired_blends: - if modify_range: - cmds.addAttr(selection_target, ln=target, at='double', k=True, - maxValue=attribute_range_max, minValue=attribute_range_min) - remap_node = cmds.createNode('remapValue', name='remap_bs_' + target) - cmds.setAttr(remap_node + '.inputMax', attribute_range_max) - cmds.setAttr(remap_node + '.inputMin', attribute_range_min) - cmds.setAttr(remap_node + '.outputMax', blend_range_max) - cmds.setAttr(remap_node + '.outputMin', blend_range_min) - cmds.connectAttr(selection_target + '.' + target, remap_node + '.inputValue') - cmds.connectAttr(remap_node + '.outValue', blendshape_node + '.' + target, force=True) - else: - cmds.addAttr(selection_target, ln=target, at='double', k=True, maxValue=1, minValue=0) - cmds.connectAttr(selection_target + '.' + target, blendshape_node + '.' + target, force=True) diff --git a/python-scripts/gt_morphing_attributes.py b/python-scripts/gt_morphing_attributes.py new file mode 100644 index 00000000..5ce3b665 --- /dev/null +++ b/python-scripts/gt_morphing_attributes.py @@ -0,0 +1,429 @@ +""" +GT Blends to Attributes +github.com/TrevisanGMW/gt-tools - 2022-03-17 + +0.0.1 - 2022-03-17 +Create core function + +0.0.2 - 2022-07-23 +Create GUI + +""" +try: + from shiboken2 import wrapInstance +except ImportError: + from shiboken import wrapInstance + +try: + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QWidget +except ImportError: + from PySide.QtGui import QIcon, QWidget + +from maya import OpenMayaUI as OpenMayaUI +import maya.cmds as cmds +import logging +import random +import sys + +# Logging Setup +logging.basicConfig() +logger = logging.getLogger("gt_blends_to_attributes") +logger.setLevel(logging.INFO) + +# Script Name +script_name = "GT - Add Morphing Attributes" + +# Version: +script_version = "0.0.2" + +# Settings +gt_blends_to_attr_settings = {'blend_mesh': '', + 'blend_node': '', + 'attr_holder': '', + } + + +def blends_to_attr(): + selection_source = cmds.ls(selection=True)[0] # First selected object - Geo with BS + selection_target = cmds.ls(selection=True)[1] # Second selected object - Curve + history = cmds.listHistory(selection_source) + blendshape_node = cmds.ls(history, type='blendShape')[0] + blendshape_names = cmds.listAttr(blendshape_node + '.w', m=True) + + modify_range = True + attribute_range_min = 0 + attribute_range_max = 10 + blend_range_min = 0 + blend_range_max = 1 + + ignore_connected = True + add_separator_attribute = True + custom_separator_attr = '' + method = 'includes' + + undesired_filter_strings = ['corrective'] + desired_blends = [] + desired_filter_strings = ['jaw', 'cheek', 'mouth', 'nose'] + filtered_blends = [] + + # Find desired blends + for target in blendshape_names: + for desired_filter_string in desired_filter_strings: + if method == 'includes': + if desired_filter_string in target: + filtered_blends.append(target) + elif method == 'startswith': + if target.startswith(desired_filter_string): + filtered_blends.append(target) + elif method == 'endswith': + if target.endswith(desired_filter_string): + filtered_blends.append(target) + + if len(desired_filter_strings) == 0: # If filter empty, use everything + filtered_blends = blendshape_names + + if ignore_connected: # Pre-ignore connected blends + accessible_blends = [] + for blend in filtered_blends: + connections = cmds.listConnections(blendshape_node + '.' + blend, destination=False, plugs=True) or [] + if len(connections) == 0: + accessible_blends.append(blend) + else: + accessible_blends = filtered_blends + + # Remove undesired blends from list + undesired_blends = [] + for target in desired_blends: + for undesired_string in undesired_filter_strings: + if undesired_string in target: + undesired_blends.append(target) + for blend in accessible_blends: + if blend not in undesired_blends: + desired_blends.append(blend) + + # Separator Attribute + if len(desired_blends) != 0 and add_separator_attribute: + separator_attr = 'blends' + if custom_separator_attr: + separator_attr = custom_separator_attr + cmds.addAttr(selection_target, ln=separator_attr, at='enum', en='-------------:', keyable=True) + cmds.setAttr(selection_target + '.' + separator_attr, e=True, lock=True) + + # Create Blend Drivers + desired_blends.sort() + for target in desired_blends: + if modify_range: + cmds.addAttr(selection_target, ln=target, at='double', k=True, + maxValue=attribute_range_max, minValue=attribute_range_min) + remap_node = cmds.createNode('remapValue', name='remap_bs_' + target) + cmds.setAttr(remap_node + '.inputMax', attribute_range_max) + cmds.setAttr(remap_node + '.inputMin', attribute_range_min) + cmds.setAttr(remap_node + '.outputMax', blend_range_max) + cmds.setAttr(remap_node + '.outputMin', blend_range_min) + cmds.connectAttr(selection_target + '.' + target, remap_node + '.inputValue') + cmds.connectAttr(remap_node + '.outValue', blendshape_node + '.' + target, force=True) + else: + cmds.addAttr(selection_target, ln=target, at='double', k=True, maxValue=1, minValue=0) + cmds.connectAttr(selection_target + '.' + target, blendshape_node + '.' + target, force=True) + + +def build_gui_morphing_attributes(): + + def select_blend_shape_node(): + error_message = "Unable to locate blend shape node. Please try again." + blend_node = cmds.textScrollList(blend_nodes_scroll_list, q=True, selectItem=True) or [] + if blend_node: + if cmds.objExists(blend_node[0]): + sys.stdout.write('"' + str(blend_node[0]) + '" will be used when creating attributes.') + gt_blends_to_attr_settings['blend_node'] = blend_node[0] + else: + cmds.warning(error_message) + gt_blends_to_attr_settings['blend_node'] = '' + else: + cmds.warning(error_message) + gt_blends_to_attr_settings['blend_node'] = '' + + def object_load_handler(operation): + """ + Function to handle load buttons. It updates the UI to reflect the loaded data. + + Args: + operation (str): String to determine function ("blend_mesh" or "attr_holder") + """ + def failed_to_load_source(failed_message="Failed to Load"): + cmds.button(source_object_status, l=failed_message, e=True, bgc=(1, .4, .4), w=130) + cmds.textScrollList(blend_nodes_scroll_list, e=True, removeAll=True) + gt_blends_to_attr_settings['blend_mesh'] = '' + + def failed_to_load_target(failed_message="Failed to Load"): + cmds.button(attr_holder_status, l=failed_message, e=True, bgc=(1, .4, .4), w=130) + gt_blends_to_attr_settings['attr_holder'] = '' + + # Blend Mesh + if operation == 'blend_mesh': + current_selection = cmds.ls(selection=True) or [] + if not current_selection: + cmds.warning("Nothing selected. Please select a mesh try again.") + failed_to_load_source() + return + + if len(current_selection) > 1: + cmds.warning("You selected more than one source object! Please select only one object and try again.") + failed_to_load_source() + return + + if cmds.objExists(current_selection[0]): + history = cmds.listHistory(current_selection[0]) + blendshape_nodes = cmds.ls(history, type='blendShape') or [] + if not blendshape_nodes: + cmds.warning("Unable to find blend shape nodes on the selected object.") + failed_to_load_source() + return + else: + gt_blends_to_attr_settings['blend_mesh'] = current_selection[0] + cmds.button(source_object_status, l=gt_blends_to_attr_settings.get('blend_mesh'), + e=True, bgc=(.6, .8, .6), w=130) + cmds.textScrollList(blend_nodes_scroll_list, e=True, removeAll=True) + cmds.textScrollList(blend_nodes_scroll_list, e=True, append=blendshape_nodes) + + # Attr Holder + if operation == 'attr_holder': + current_selection = cmds.ls(selection=True) + if len(current_selection) == 0: + cmds.warning("Nothing selected.") + failed_to_load_target() + return + elif len(current_selection) > 1: + cmds.warning("You selected more than one object! Please select only one") + failed_to_load_target() + return + elif cmds.objExists(current_selection[0]): + gt_blends_to_attr_settings['attr_holder'] = current_selection[0] + cmds.button(attr_holder_status, l=gt_blends_to_attr_settings.get('attr_holder'), e=True, + bgc=(.6, .8, .6), w=130) + else: + cmds.warning("Something went wrong, make sure you selected just one object and try again.") + + def validate_operation(): + """ Checks elements one last time before running the script """ + print("validate then run") + # is_valid = False + # stretchy_name = None + # attr_holder = None + # + # stretchy_prefix = cmds.textField(stretchy_system_prefix, q=True, text=True).replace(' ', '') + # + # # Name + # if stretchy_prefix != '': + # stretchy_name = stretchy_prefix + # + # # ikHandle + # if gt_blends_to_attr_settings.get('ik_handle') == '': + # cmds.warning('Please load an ikHandle first before running the script.') + # is_valid = False + # else: + # if cmds.objExists(gt_blends_to_attr_settings.get('ik_handle')): + # is_valid = True + # else: + # cmds.warning('"' + str(gt_blends_to_attr_settings.get('ik_handle')) + + # "\" couldn't be located. " + # "Make sure you didn't rename or deleted the object after loading it") + # + # # Attribute Holder + # if is_valid: + # if gt_blends_to_attr_settings.get('attr_holder') != '': + # if cmds.objExists(gt_blends_to_attr_settings.get('attr_holder')): + # attr_holder = gt_blends_to_attr_settings.get('attr_holder') + # else: + # cmds.warning('"' + str(gt_blends_to_attr_settings.get('attr_holder')) + + # "\" couldn't be located. " + # "Make sure you didn't rename or deleted the object after loading it. " + # "A simpler version of the stretchy system was created.") + # else: + # sys.stdout.write( + # 'An attribute holder was not provided. A simpler version of the stretchy system was created.') + # + # # Run Script + # if is_valid: + # if stretchy_name: + # make_stretchy_ik(gt_blends_to_attr_settings.get('ik_handle'), stretchy_name=stretchy_name, + # attribute_holder=attr_holder) + # else: + # make_stretchy_ik(gt_blends_to_attr_settings.get('ik_handle'), stretchy_name='temp', + # attribute_holder=attr_holder) + + window_name = "build_gui_morphing_attributes" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name) + + # Build UI + window_gui_blends_to_attr = cmds.window(window_name, title=script_name + ' (v' + script_version + ')', + titleBar=True, mnb=False, mxb=False, sizeable=True) + + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + content_main = cmds.columnLayout(adj=True) + + # Title Text + title_bgc_color = (.4, .4, .4) + cmds.separator(h=10, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 270)], cs=[(1, 10)], p=content_main) # Window Size Adjustment + cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 200), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], + p=content_main) # Title Column + cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space + cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") + cmds.button(l="Help", bgc=title_bgc_color, c=lambda x: build_gui_help_morphing_attr()) + cmds.separator(h=5, style='none') # Empty Space + + # Body ==================== + body_column = cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + + cmds.separator(h=5, style='none') # Empty Space + # + # cmds.text('Text Here:') + # stretchy_system_prefix = cmds.textField(text='', pht='Text Here (Optional)') + + # cmds.separator(h=10, style='none') # Empty Space + cmds.text('1. Deformed Mesh (Source):') + cmds.separator(h=5, style='none') # Empty Space + + cmds.rowColumnLayout(nc=2, cw=[(1, 129), (2, 130)], cs=[(1, 10)], p=content_main) + cmds.button(l="Load Morphing Object", c=lambda x: object_load_handler("blend_mesh"), w=130) + source_object_status = cmds.button(l="Not loaded yet", bgc=(.2, .2, .2), w=130, + c=lambda x: select_existing_object(gt_blends_to_attr_settings.get('blend_mesh'))) + + cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + cmds.separator(h=5, style='none') # Empty Space + cmds.text('Blend Shape Nodes:', font="smallPlainLabelFont") + blend_nodes_scroll_list = cmds.textScrollList(numberOfRows=8, allowMultiSelection=False, height=70, + selectCommand=select_blend_shape_node) + + cmds.separator(h=5, style='none') # Empty Space + cmds.text('2. Attribute Holder (Target):') + cmds.separator(h=5, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 129), (2, 130)], cs=[(1, 10)], p=content_main) + + cmds.button(l="Load Attribute Holder", c=lambda x: object_load_handler("attr_holder"), w=130) + attr_holder_status = cmds.button(l="Not loaded yet", bgc=(.2, .2, .2), w=130, + c=lambda x: select_existing_object( + gt_blends_to_attr_settings.get('attr_holder'))) + + cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + + cmds.separator(h=7, style='none') # Empty Space + cmds.separator(h=5) + cmds.separator(h=7, style='none') # Empty Space + + cmds.button(l="Create Morphing Attributes", bgc=(.6, .6, .6), c=lambda x: validate_operation()) + cmds.separator(h=10, style='none') # Empty Space + + # Show and Lock Window + cmds.showWindow(window_gui_blends_to_attr) + cmds.window(window_name, e=True, s=False) + + # Set Window Icon + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QWidget) + icon = QIcon(':/ikSCsolver.svg') + widget.setWindowIcon(icon) + + # Remove the focus from the textfield and give it to the window + cmds.setFocus(window_name) + + +# Creates Help GUI +def build_gui_help_morphing_attr(): + """ Creates GUI for Make Stretchy IK """ + window_name = "build_gui_help_morphing_attr" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + cmds.window(window_name, title=script_name + " Help", mnb=False, mxb=False, s=True) + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + cmds.columnLayout("main_column", p=window_name) + + # Title Text + cmds.separator(h=12, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p="main_column") # Window Size Adjustment + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") # Title Column + cmds.text(script_name + " Help", bgc=[.4, .4, .4], fn="boldLabelFont", align="center") + cmds.separator(h=10, style='none', p="main_column") # Empty Space + + # Body ==================== + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") + cmds.text(l='Help Place Holder', align="center") + cmds.separator(h=5, style='none') # Empty Space + + cmds.separator(h=15, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") + cmds.text('Guilherme Trevisan ') + cmds.text(l='TrevisanGMW@gmail.com', hl=True, highlightColor=[1, 1, 1]) + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") + cmds.separator(h=15, style='none') # Empty Space + cmds.text(l='Github', hl=True, highlightColor=[1, 1, 1]) + cmds.separator(h=7, style='none') # Empty Space + + # Close Button + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") + cmds.separator(h=10, style='none') + cmds.button(l='OK', h=30, c=lambda args: close_help_gui()) + cmds.separator(h=8, style='none') + + # Show and Lock Window + cmds.showWindow(window_name) + cmds.window(window_name, e=True, s=False) + + # Set Window Icon + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QWidget) + icon = QIcon(':/question.png') + widget.setWindowIcon(icon) + + def close_help_gui(): + """ Closes Help Window """ + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + +def select_existing_object(obj): + """ + Selects an object in case it exists + + Args: + obj (str): Object it will try to select + + """ + if obj != '': + if cmds.objExists(obj): + cmds.select(obj) + unique_message = '<' + str(random.random()) + '>' + cmds.inViewMessage(amg=unique_message + '' + str( + obj) + ' selected.', pos='botLeft', fade=True, alpha=.9) + else: + cmds.warning('"' + str( + obj) + "\" couldn't be selected. Make sure you didn't rename or deleted the object after loading it") + else: + cmds.warning('Nothing loaded. Please load an object before attempting to select it.') + + +def change_outliner_color(obj, rgb_color=(1, 1, 1)): + """ + Sets the outliner color for the selected object + + Args: + obj (str): Name (path) of the object to change. + rgb_color (tuple): RGB Color to change it to + + """ + if cmds.objExists(obj) and cmds.getAttr(obj + '.useOutlinerColor', lock=True) is False: + cmds.setAttr(obj + '.useOutlinerColor', 1) + cmds.setAttr(obj + '.outlinerColorR', rgb_color[0]) + cmds.setAttr(obj + '.outlinerColorG', rgb_color[1]) + cmds.setAttr(obj + '.outlinerColorB', rgb_color[2]) + + +# Build UI +if __name__ == '__main__': + build_gui_morphing_attributes() diff --git a/python-scripts/gt_add_sine_attributes.py b/python-scripts/gt_sine_attributes.py similarity index 97% rename from python-scripts/gt_add_sine_attributes.py rename to python-scripts/gt_sine_attributes.py index 3d76c0f7..e44e6cf3 100644 --- a/python-scripts/gt_add_sine_attributes.py +++ b/python-scripts/gt_sine_attributes.py @@ -1,343 +1,343 @@ -""" - Create Sine output attributes without using third-party plugins or expressions. - github.com/TrevisanGMW/gt-tools - 2021-01-25 - - 1.0 - 2021-01-25 - Initial Release - - 1.1 - 2021-05-10 - Made script compatible with Python 3 (Maya 2022+) - - 1.1.1 - 2021-06-30 - Added patch to version - General cleanup - -""" -try: - from shiboken2 import wrapInstance -except ImportError: - from shiboken import wrapInstance - -try: - from PySide2.QtGui import QIcon - from PySide2.QtWidgets import QWidget -except ImportError: - from PySide.QtGui import QIcon, QWidget - -from maya import OpenMayaUI -import maya.cmds as cmds -import re - -# Script Name -script_name = "GT - Add Sine Attributes" - -# Version: -script_version = "1.1.1" - - -# Main Form ============================================================================ -def build_gui_add_sine_attr(): - window_name = "build_gui_add_sine_attr" - if cmds.window(window_name, exists=True): - cmds.deleteUI(window_name) - - # Main GUI Start Here ================================================================================= - - # Build UI - window_add_sine_attr = cmds.window(window_name, title=script_name + ' (v' + script_version + ')', - titleBar=True, mnb=False, mxb=False, sizeable=True) - - cmds.window(window_name, e=True, s=True, wh=[1, 1]) - - content_main = cmds.columnLayout(adj=True) - - # Title Text - title_bgc_color = (.4, .4, .4) - cmds.separator(h=10, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 270)], cs=[(1, 10)], p=content_main) # Window Size Adjustment - cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 200), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], - p=content_main) # Title Column - cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space - cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") - cmds.button(l="Help", bgc=title_bgc_color, c=lambda x: build_gui_help_add_sine_attr()) - cmds.separator(h=5, style='none') # Empty Space - - # Body ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) - - cmds.text(l='Select attribute holder first, then run script.', align="center") - cmds.separator(h=10, style='none') # Empty Space - - cmds.text('Sine Attributes Prefix:') - stretchy_system_prefix = cmds.textField(text='', pht='Sine Attributes Prefix (Optional)') - - cmds.separator(h=5, style='none') # Empty Space - cmds.rowColumnLayout(nc=2, cw=[(1, 115), (2, 150)], cs=[(1, 10)], p=content_main) - - add_abs_output_checkbox = cmds.checkBox(label='Add Abs Output') - add_prefix_nn_checkbox = cmds.checkBox(label='Add Prefix to Nice Name', value=True) - - cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) - - cmds.separator(h=5, style='none') # Empty Space - - cmds.separator(h=5) - cmds.separator(h=7, style='none') # Empty Space - - cmds.button(l="Add Sine Attributes", bgc=(.6, .6, .6), c=lambda x: validate_operation()) - cmds.separator(h=10, style='none') # Empty Space - - # Show and Lock Window - cmds.showWindow(window_add_sine_attr) - cmds.window(window_name, e=True, s=False) - - # Set Window Icon - qw = OpenMayaUI.MQtUtil.findWindow(window_name) - widget = wrapInstance(int(qw), QWidget) - - icon = QIcon(':/sineCurveProfile.png') - widget.setWindowIcon(icon) - - # Remove the focus from the textfield and give it to the window - cmds.setFocus(window_name) - - # Main GUI Ends Here ================================================================================= - - def validate_operation(): - """ Checks elements one last time before running the script """ - - add_abs_output_value = cmds.checkBox(add_abs_output_checkbox, q=True, value=True) - add_prefix_nn_value = cmds.checkBox(add_prefix_nn_checkbox, q=True, value=True) - - stretchy_prefix = cmds.textField(stretchy_system_prefix, q=True, text=True).replace(' ', '') - - selection = cmds.ls(selection=True) or [] - if len(selection) > 0: - target = selection[0] - is_valid = True - else: - cmds.warning('Please select a target object to be the attribute holder.') - is_valid = False - target = '' - - # Name - if stretchy_prefix != '': - stretchy_name = stretchy_prefix - else: - stretchy_name = 'sine' - - if is_valid: - current_attributes = cmds.listAttr(target, r=True, s=True, userDefined=True) or [] - - possible_conflicts = [stretchy_name + 'Time', - stretchy_name + 'Amplitude', - stretchy_name + 'Frequency', - stretchy_name + 'Offset', - stretchy_name + 'Output', - stretchy_name + 'Tick', - stretchy_name + 'AbsOutput', - ] - - for conflict in possible_conflicts: - for attr in current_attributes: - if attr == conflict: - is_valid = False - - if not is_valid: - cmds.warning('The object selected has conflicting attributes. ' - 'Please change the prefix or select another object.') - - # Run Script - if is_valid: - if stretchy_name: - add_sine_attributes(target, sine_prefix=stretchy_name, tick_source_attr='time1.outTime', - hide_unkeyable=False, add_absolute_output=add_abs_output_value, - nice_name_prefix=add_prefix_nn_value) - cmds.select(target, r=True) - else: - add_sine_attributes(target, sine_prefix=stretchy_name, tick_source_attr='time1.outTime', - hide_unkeyable=False, add_absolute_output=add_abs_output_value, - nice_name_prefix=add_prefix_nn_value) - cmds.select(target, r=True) - - -# Creates Help GUI -def build_gui_help_add_sine_attr(): - """ Creates GUI for Make Stretchy IK """ - window_name = "build_gui_help_add_sine_attr" - if cmds.window(window_name, exists=True): - cmds.deleteUI(window_name, window=True) - - cmds.window(window_name, title=script_name + " Help", mnb=False, mxb=False, s=True) - cmds.window(window_name, e=True, s=True, wh=[1, 1]) - - cmds.columnLayout("main_column", p=window_name) - - # Title Text - cmds.separator(h=12, style='none') # Empty Space - cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p="main_column") # Window Size Adjustment - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") # Title Column - cmds.text(script_name + " Help", bgc=[.4, .4, .4], fn="boldLabelFont", align="center") - cmds.separator(h=10, style='none', p="main_column") # Empty Space - - # Body ==================== - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") - cmds.text(l='Create Sine attributes without using\nthird-party plugins or expressions.', align="center") - cmds.separator(h=5, style='none') # Empty Space - cmds.text(l='Select and object, then click on "Add Sine Attributes"', align="center") - cmds.separator(h=10, style='none') # Empty Space - - cmds.text(l='Sine Attributes:', align='center', font='boldLabelFont') - cmds.text(l='Time: Multiplier for the time input (tick)', align="center") - cmds.text(l='Amplitude: Wave amplitude (how high it gets)', align="center") - cmds.text(l='Frequency: Wave frequency (how often it happens)', align="center") - cmds.text(l='Offset: Value added after calculation, offset.', align="center") - cmds.text(l='Tick: Time as seen by the sine system.', align="center") - cmds.text(l='Output: Result of the sine operation.', align="center") - cmds.text(l='Abs Output: Absolute output. (no negative values)', align="center") - cmds.separator(h=10, style='none') # Empty Space - - cmds.separator(h=15, style='none') # Empty Space - cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") - cmds.text('Guilherme Trevisan ') - cmds.text(l='TrevisanGMW@gmail.com', hl=True, highlightColor=[1, 1, 1]) - cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") - cmds.separator(h=15, style='none') # Empty Space - cmds.text(l='Github', hl=True, highlightColor=[1, 1, 1]) - cmds.separator(h=7, style='none') # Empty Space - - # Close Button - cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") - cmds.separator(h=10, style='none') - cmds.button(l='OK', h=30, c=lambda args: close_help_gui()) - cmds.separator(h=8, style='none') - - # Show and Lock Window - cmds.showWindow(window_name) - cmds.window(window_name, e=True, s=False) - - # Set Window Icon - qw = OpenMayaUI.MQtUtil.findWindow(window_name) - widget = wrapInstance(int(qw), QWidget) - icon = QIcon(':/question.png') - widget.setWindowIcon(icon) - - def close_help_gui(): - """ Closes Help Window """ - if cmds.window(window_name, exists=True): - cmds.deleteUI(window_name, window=True) - - -def add_sine_attributes(obj, sine_prefix='sine', tick_source_attr='time1.outTime', hide_unkeyable=True, - add_absolute_output=False, nice_name_prefix=True): - """ - Create Sine functions without using third-party plugins or expressions - - Args: - obj (string): Name of the object - sine_prefix (string): Prefix given to the name of the attributes (default is "sine") - tick_source_attr (string): Name of the attribute used as the source for time. It uses the default "time1" - node if nothing else is specified - hide_unkeyable (bool): Hides the tick and output attributes - add_absolute_output (bool): Also creates an output version that gives only positive numbers much like the abs() - expression - nice_name_prefix (bool): Add prefix or not - - Returns: - sine_output_attrs (list): A string with the name of the object and the name of the sine output attribute. - E.g. "pSphere1.sineOutput" - In case an absolute output is added, it will be the second object in the list. - E.g. ["pSphere1.sineOutput", "pSphere1.sineAbsOutput"] - If add_absolute_output is False the second attribute is None - """ - # Load Required Plugins - required_plugin = 'quatNodes' - if not cmds.pluginInfo(required_plugin, q=True, loaded=True): - cmds.loadPlugin(required_plugin, qt=False) - - # Set Variables - influence_suffix = 'Time' - amplitude_suffix = 'Amplitude' - frequency_suffix = 'Frequency' - offset_suffix = 'Offset' - output_suffix = 'Output' - tick_suffix = 'Tick' - abs_suffix = 'AbsOutput' - - influence_attr = sine_prefix + influence_suffix - amplitude_attr = sine_prefix + amplitude_suffix - frequency_attr = sine_prefix + frequency_suffix - offset_attr = sine_prefix + offset_suffix - output_attr = sine_prefix + output_suffix - tick_attr = sine_prefix + tick_suffix - abs_attr = sine_prefix + abs_suffix - - # Create Nodes - mdl_node = cmds.createNode('multDoubleLinear', name=obj + '_multDoubleLiner') - quat_node = cmds.createNode('eulerToQuat', name=obj + '_eulerToQuat') - multiply_node = cmds.createNode('multiplyDivide', name=obj + '_amplitude_multiply') - sum_node = cmds.createNode('plusMinusAverage', name=obj + '_offset_sum') - influence_multiply_node = cmds.createNode('multiplyDivide', name=obj + '_influence_multiply') - - # Add Attributes - if nice_name_prefix: - cmds.addAttr(obj, ln=influence_attr, at='double', k=True, maxValue=1, minValue=0) - cmds.addAttr(obj, ln=amplitude_attr, at='double', k=True) - cmds.addAttr(obj, ln=frequency_attr, at='double', k=True) - cmds.addAttr(obj, ln=offset_attr, at='double', k=True) - cmds.addAttr(obj, ln=tick_attr, at='double', k=True) - cmds.addAttr(obj, ln=output_attr, at='double', k=True) - if add_absolute_output: - cmds.addAttr(obj, ln=abs_attr, at='double', k=True) - else: - cmds.addAttr(obj, ln=influence_attr, at='double', k=True, maxValue=1, minValue=0, nn=influence_suffix) - cmds.addAttr(obj, ln=amplitude_attr, at='double', k=True, nn=amplitude_suffix) - cmds.addAttr(obj, ln=frequency_attr, at='double', k=True, nn=frequency_suffix) - cmds.addAttr(obj, ln=offset_attr, at='double', k=True, nn=offset_suffix) - cmds.addAttr(obj, ln=tick_attr, at='double', k=True, nn=tick_suffix) - cmds.addAttr(obj, ln=output_attr, at='double', k=True, nn=output_suffix) - if add_absolute_output: - cmds.addAttr(obj, ln=abs_attr, at='double', k=True, nn=re.sub(r'(\w)([A-Z])', r'\1 \2', abs_suffix)) - - cmds.setAttr(obj + '.' + influence_attr, 1) - cmds.setAttr(obj + '.' + amplitude_attr, 1) - cmds.setAttr(obj + '.' + frequency_attr, 10) - - if hide_unkeyable: - cmds.setAttr(obj + '.' + tick_attr, k=False) - cmds.setAttr(obj + '.' + output_attr, k=False) - if add_absolute_output and hide_unkeyable: - cmds.setAttr(obj + '.' + abs_attr, k=False) - - cmds.connectAttr(tick_source_attr, influence_multiply_node + '.input1X') - cmds.connectAttr(influence_multiply_node + '.outputX', obj + '.' + tick_attr) - cmds.connectAttr(obj + '.' + influence_attr, influence_multiply_node + '.input2X') - - cmds.connectAttr(obj + '.' + amplitude_attr, multiply_node + '.input2X') - cmds.connectAttr(obj + '.' + frequency_attr, mdl_node + '.input1') - cmds.connectAttr(obj + '.' + tick_attr, mdl_node + '.input2') - cmds.connectAttr(obj + '.' + offset_attr, sum_node + '.input1D[0]') - cmds.connectAttr(mdl_node + '.output', quat_node + '.inputRotateX') - - cmds.connectAttr(quat_node + '.outputQuatX', multiply_node + '.input1X') - cmds.connectAttr(multiply_node + '.outputX', sum_node + '.input1D[1]') - cmds.connectAttr(sum_node + '.output1D', obj + '.' + output_attr) - - if add_absolute_output: # abs() - squared_node = cmds.createNode('multiplyDivide', name=obj + '_abs_squared') - reverse_squared_node = cmds.createNode('multiplyDivide', name=obj + '_reverseAbs_multiply') - cmds.setAttr(squared_node + '.operation', 3) # Power - cmds.setAttr(reverse_squared_node + '.operation', 3) # Power - cmds.setAttr(squared_node + '.input2X', 2) - cmds.setAttr(reverse_squared_node + '.input2X', .5) - cmds.connectAttr(obj + '.' + output_attr, squared_node + '.input1X') - cmds.connectAttr(squared_node + '.outputX', reverse_squared_node + '.input1X') - cmds.connectAttr(reverse_squared_node + '.outputX', obj + '.' + abs_attr) - return [(obj + '.' + output_attr), (obj + '.' + abs_attr)] - else: - return [(obj + '.' + output_attr), None] - - -# Build UI -if __name__ == '__main__': - build_gui_add_sine_attr() +""" + Create Sine output attributes without using third-party plugins or expressions. + github.com/TrevisanGMW/gt-tools - 2021-01-25 + + 1.0 - 2021-01-25 + Initial Release + + 1.1 - 2021-05-10 + Made script compatible with Python 3 (Maya 2022+) + + 1.1.1 - 2021-06-30 + Added patch to version + General cleanup + +""" +try: + from shiboken2 import wrapInstance +except ImportError: + from shiboken import wrapInstance + +try: + from PySide2.QtGui import QIcon + from PySide2.QtWidgets import QWidget +except ImportError: + from PySide.QtGui import QIcon, QWidget + +from maya import OpenMayaUI +import maya.cmds as cmds +import re + +# Script Name +script_name = "GT - Add Sine Attributes" + +# Version: +script_version = "1.1.1" + + +# Main Form ============================================================================ +def build_gui_add_sine_attr(): + window_name = "build_gui_add_sine_attr" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name) + + # Main GUI Start Here ================================================================================= + + # Build UI + window_add_sine_attr = cmds.window(window_name, title=script_name + ' (v' + script_version + ')', + titleBar=True, mnb=False, mxb=False, sizeable=True) + + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + content_main = cmds.columnLayout(adj=True) + + # Title Text + title_bgc_color = (.4, .4, .4) + cmds.separator(h=10, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 270)], cs=[(1, 10)], p=content_main) # Window Size Adjustment + cmds.rowColumnLayout(nc=3, cw=[(1, 10), (2, 200), (3, 50)], cs=[(1, 10), (2, 0), (3, 0)], + p=content_main) # Title Column + cmds.text(" ", bgc=title_bgc_color) # Tiny Empty Green Space + cmds.text(script_name, bgc=title_bgc_color, fn="boldLabelFont", align="left") + cmds.button(l="Help", bgc=title_bgc_color, c=lambda x: build_gui_help_add_sine_attr()) + cmds.separator(h=5, style='none') # Empty Space + + # Body ==================== + cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + + cmds.text(l='Select attribute holder first, then run script.', align="center") + cmds.separator(h=10, style='none') # Empty Space + + cmds.text('Sine Attributes Prefix:') + stretchy_system_prefix = cmds.textField(text='', pht='Sine Attributes Prefix (Optional)') + + cmds.separator(h=5, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 115), (2, 150)], cs=[(1, 10)], p=content_main) + + add_abs_output_checkbox = cmds.checkBox(label='Add Abs Output') + add_prefix_nn_checkbox = cmds.checkBox(label='Add Prefix to Nice Name', value=True) + + cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + + cmds.separator(h=5, style='none') # Empty Space + + cmds.separator(h=5) + cmds.separator(h=7, style='none') # Empty Space + + cmds.button(l="Add Sine Attributes", bgc=(.6, .6, .6), c=lambda x: validate_operation()) + cmds.separator(h=10, style='none') # Empty Space + + # Show and Lock Window + cmds.showWindow(window_add_sine_attr) + cmds.window(window_name, e=True, s=False) + + # Set Window Icon + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QWidget) + + icon = QIcon(':/sineCurveProfile.png') + widget.setWindowIcon(icon) + + # Remove the focus from the textfield and give it to the window + cmds.setFocus(window_name) + + # Main GUI Ends Here ================================================================================= + + def validate_operation(): + """ Checks elements one last time before running the script """ + + add_abs_output_value = cmds.checkBox(add_abs_output_checkbox, q=True, value=True) + add_prefix_nn_value = cmds.checkBox(add_prefix_nn_checkbox, q=True, value=True) + + stretchy_prefix = cmds.textField(stretchy_system_prefix, q=True, text=True).replace(' ', '') + + selection = cmds.ls(selection=True) or [] + if len(selection) > 0: + target = selection[0] + is_valid = True + else: + cmds.warning('Please select a target object to be the attribute holder.') + is_valid = False + target = '' + + # Name + if stretchy_prefix != '': + stretchy_name = stretchy_prefix + else: + stretchy_name = 'sine' + + if is_valid: + current_attributes = cmds.listAttr(target, r=True, s=True, userDefined=True) or [] + + possible_conflicts = [stretchy_name + 'Time', + stretchy_name + 'Amplitude', + stretchy_name + 'Frequency', + stretchy_name + 'Offset', + stretchy_name + 'Output', + stretchy_name + 'Tick', + stretchy_name + 'AbsOutput', + ] + + for conflict in possible_conflicts: + for attr in current_attributes: + if attr == conflict: + is_valid = False + + if not is_valid: + cmds.warning('The object selected has conflicting attributes. ' + 'Please change the prefix or select another object.') + + # Run Script + if is_valid: + if stretchy_name: + add_sine_attributes(target, sine_prefix=stretchy_name, tick_source_attr='time1.outTime', + hide_unkeyable=False, add_absolute_output=add_abs_output_value, + nice_name_prefix=add_prefix_nn_value) + cmds.select(target, r=True) + else: + add_sine_attributes(target, sine_prefix=stretchy_name, tick_source_attr='time1.outTime', + hide_unkeyable=False, add_absolute_output=add_abs_output_value, + nice_name_prefix=add_prefix_nn_value) + cmds.select(target, r=True) + + +# Creates Help GUI +def build_gui_help_add_sine_attr(): + """ Creates GUI for Make Stretchy IK """ + window_name = "build_gui_help_add_sine_attr" + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + cmds.window(window_name, title=script_name + " Help", mnb=False, mxb=False, s=True) + cmds.window(window_name, e=True, s=True, wh=[1, 1]) + + cmds.columnLayout("main_column", p=window_name) + + # Title Text + cmds.separator(h=12, style='none') # Empty Space + cmds.rowColumnLayout(nc=1, cw=[(1, 310)], cs=[(1, 10)], p="main_column") # Window Size Adjustment + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") # Title Column + cmds.text(script_name + " Help", bgc=[.4, .4, .4], fn="boldLabelFont", align="center") + cmds.separator(h=10, style='none', p="main_column") # Empty Space + + # Body ==================== + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") + cmds.text(l='Create Sine attributes without using\nthird-party plugins or expressions.', align="center") + cmds.separator(h=5, style='none') # Empty Space + cmds.text(l='Select and object, then click on "Add Sine Attributes"', align="center") + cmds.separator(h=10, style='none') # Empty Space + + cmds.text(l='Sine Attributes:', align='center', font='boldLabelFont') + cmds.text(l='Time: Multiplier for the time input (tick)', align="center") + cmds.text(l='Amplitude: Wave amplitude (how high it gets)', align="center") + cmds.text(l='Frequency: Wave frequency (how often it happens)', align="center") + cmds.text(l='Offset: Value added after calculation, offset.', align="center") + cmds.text(l='Tick: Time as seen by the sine system.', align="center") + cmds.text(l='Output: Result of the sine operation.', align="center") + cmds.text(l='Abs Output: Absolute output. (no negative values)', align="center") + cmds.separator(h=10, style='none') # Empty Space + + cmds.separator(h=15, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") + cmds.text('Guilherme Trevisan ') + cmds.text(l='TrevisanGMW@gmail.com', hl=True, highlightColor=[1, 1, 1]) + cmds.rowColumnLayout(nc=2, cw=[(1, 140), (2, 140)], cs=[(1, 10), (2, 0)], p="main_column") + cmds.separator(h=15, style='none') # Empty Space + cmds.text(l='Github', hl=True, highlightColor=[1, 1, 1]) + cmds.separator(h=7, style='none') # Empty Space + + # Close Button + cmds.rowColumnLayout(nc=1, cw=[(1, 300)], cs=[(1, 10)], p="main_column") + cmds.separator(h=10, style='none') + cmds.button(l='OK', h=30, c=lambda args: close_help_gui()) + cmds.separator(h=8, style='none') + + # Show and Lock Window + cmds.showWindow(window_name) + cmds.window(window_name, e=True, s=False) + + # Set Window Icon + qw = OpenMayaUI.MQtUtil.findWindow(window_name) + widget = wrapInstance(int(qw), QWidget) + icon = QIcon(':/question.png') + widget.setWindowIcon(icon) + + def close_help_gui(): + """ Closes Help Window """ + if cmds.window(window_name, exists=True): + cmds.deleteUI(window_name, window=True) + + +def add_sine_attributes(obj, sine_prefix='sine', tick_source_attr='time1.outTime', hide_unkeyable=True, + add_absolute_output=False, nice_name_prefix=True): + """ + Create Sine functions without using third-party plugins or expressions + + Args: + obj (string): Name of the object + sine_prefix (string): Prefix given to the name of the attributes (default is "sine") + tick_source_attr (string): Name of the attribute used as the source for time. It uses the default "time1" + node if nothing else is specified + hide_unkeyable (bool): Hides the tick and output attributes + add_absolute_output (bool): Also creates an output version that gives only positive numbers much like the abs() + expression + nice_name_prefix (bool): Add prefix or not + + Returns: + sine_output_attrs (list): A string with the name of the object and the name of the sine output attribute. + E.g. "pSphere1.sineOutput" + In case an absolute output is added, it will be the second object in the list. + E.g. ["pSphere1.sineOutput", "pSphere1.sineAbsOutput"] + If add_absolute_output is False the second attribute is None + """ + # Load Required Plugins + required_plugin = 'quatNodes' + if not cmds.pluginInfo(required_plugin, q=True, loaded=True): + cmds.loadPlugin(required_plugin, qt=False) + + # Set Variables + influence_suffix = 'Time' + amplitude_suffix = 'Amplitude' + frequency_suffix = 'Frequency' + offset_suffix = 'Offset' + output_suffix = 'Output' + tick_suffix = 'Tick' + abs_suffix = 'AbsOutput' + + influence_attr = sine_prefix + influence_suffix + amplitude_attr = sine_prefix + amplitude_suffix + frequency_attr = sine_prefix + frequency_suffix + offset_attr = sine_prefix + offset_suffix + output_attr = sine_prefix + output_suffix + tick_attr = sine_prefix + tick_suffix + abs_attr = sine_prefix + abs_suffix + + # Create Nodes + mdl_node = cmds.createNode('multDoubleLinear', name=obj + '_multDoubleLiner') + quat_node = cmds.createNode('eulerToQuat', name=obj + '_eulerToQuat') + multiply_node = cmds.createNode('multiplyDivide', name=obj + '_amplitude_multiply') + sum_node = cmds.createNode('plusMinusAverage', name=obj + '_offset_sum') + influence_multiply_node = cmds.createNode('multiplyDivide', name=obj + '_influence_multiply') + + # Add Attributes + if nice_name_prefix: + cmds.addAttr(obj, ln=influence_attr, at='double', k=True, maxValue=1, minValue=0) + cmds.addAttr(obj, ln=amplitude_attr, at='double', k=True) + cmds.addAttr(obj, ln=frequency_attr, at='double', k=True) + cmds.addAttr(obj, ln=offset_attr, at='double', k=True) + cmds.addAttr(obj, ln=tick_attr, at='double', k=True) + cmds.addAttr(obj, ln=output_attr, at='double', k=True) + if add_absolute_output: + cmds.addAttr(obj, ln=abs_attr, at='double', k=True) + else: + cmds.addAttr(obj, ln=influence_attr, at='double', k=True, maxValue=1, minValue=0, nn=influence_suffix) + cmds.addAttr(obj, ln=amplitude_attr, at='double', k=True, nn=amplitude_suffix) + cmds.addAttr(obj, ln=frequency_attr, at='double', k=True, nn=frequency_suffix) + cmds.addAttr(obj, ln=offset_attr, at='double', k=True, nn=offset_suffix) + cmds.addAttr(obj, ln=tick_attr, at='double', k=True, nn=tick_suffix) + cmds.addAttr(obj, ln=output_attr, at='double', k=True, nn=output_suffix) + if add_absolute_output: + cmds.addAttr(obj, ln=abs_attr, at='double', k=True, nn=re.sub(r'(\w)([A-Z])', r'\1 \2', abs_suffix)) + + cmds.setAttr(obj + '.' + influence_attr, 1) + cmds.setAttr(obj + '.' + amplitude_attr, 1) + cmds.setAttr(obj + '.' + frequency_attr, 10) + + if hide_unkeyable: + cmds.setAttr(obj + '.' + tick_attr, k=False) + cmds.setAttr(obj + '.' + output_attr, k=False) + if add_absolute_output and hide_unkeyable: + cmds.setAttr(obj + '.' + abs_attr, k=False) + + cmds.connectAttr(tick_source_attr, influence_multiply_node + '.input1X') + cmds.connectAttr(influence_multiply_node + '.outputX', obj + '.' + tick_attr) + cmds.connectAttr(obj + '.' + influence_attr, influence_multiply_node + '.input2X') + + cmds.connectAttr(obj + '.' + amplitude_attr, multiply_node + '.input2X') + cmds.connectAttr(obj + '.' + frequency_attr, mdl_node + '.input1') + cmds.connectAttr(obj + '.' + tick_attr, mdl_node + '.input2') + cmds.connectAttr(obj + '.' + offset_attr, sum_node + '.input1D[0]') + cmds.connectAttr(mdl_node + '.output', quat_node + '.inputRotateX') + + cmds.connectAttr(quat_node + '.outputQuatX', multiply_node + '.input1X') + cmds.connectAttr(multiply_node + '.outputX', sum_node + '.input1D[1]') + cmds.connectAttr(sum_node + '.output1D', obj + '.' + output_attr) + + if add_absolute_output: # abs() + squared_node = cmds.createNode('multiplyDivide', name=obj + '_abs_squared') + reverse_squared_node = cmds.createNode('multiplyDivide', name=obj + '_reverseAbs_multiply') + cmds.setAttr(squared_node + '.operation', 3) # Power + cmds.setAttr(reverse_squared_node + '.operation', 3) # Power + cmds.setAttr(squared_node + '.input2X', 2) + cmds.setAttr(reverse_squared_node + '.input2X', .5) + cmds.connectAttr(obj + '.' + output_attr, squared_node + '.input1X') + cmds.connectAttr(squared_node + '.outputX', reverse_squared_node + '.input1X') + cmds.connectAttr(reverse_squared_node + '.outputX', obj + '.' + abs_attr) + return [(obj + '.' + output_attr), (obj + '.' + abs_attr)] + else: + return [(obj + '.' + output_attr), None] + + +# Build UI +if __name__ == '__main__': + build_gui_add_sine_attr() From 9aefb2ed3f4445413fdf3c7fc106e38d15e663fc Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 22:24:08 -0700 Subject: [PATCH 14/18] Added basic validation --- python-scripts/gt_morphing_attributes.py | 126 ++++++++++------------- 1 file changed, 55 insertions(+), 71 deletions(-) diff --git a/python-scripts/gt_morphing_attributes.py b/python-scripts/gt_morphing_attributes.py index 5ce3b665..aeac023a 100644 --- a/python-scripts/gt_morphing_attributes.py +++ b/python-scripts/gt_morphing_attributes.py @@ -38,17 +38,16 @@ script_version = "0.0.2" # Settings -gt_blends_to_attr_settings = {'blend_mesh': '', +morphing_attr_settings = {'morphing_obj': '', 'blend_node': '', 'attr_holder': '', - } + } -def blends_to_attr(): - selection_source = cmds.ls(selection=True)[0] # First selected object - Geo with BS - selection_target = cmds.ls(selection=True)[1] # Second selected object - Curve - history = cmds.listHistory(selection_source) - blendshape_node = cmds.ls(history, type='blendShape')[0] +def blends_to_attr(morphing_obj, blend_node, attr_holder): + logger.debug(str(morphing_obj)) + selection_target = attr_holder + blendshape_node = blend_node blendshape_names = cmds.listAttr(blendshape_node + '.w', m=True) modify_range = True @@ -136,32 +135,32 @@ def select_blend_shape_node(): if blend_node: if cmds.objExists(blend_node[0]): sys.stdout.write('"' + str(blend_node[0]) + '" will be used when creating attributes.') - gt_blends_to_attr_settings['blend_node'] = blend_node[0] + morphing_attr_settings['blend_node'] = blend_node[0] else: cmds.warning(error_message) - gt_blends_to_attr_settings['blend_node'] = '' + morphing_attr_settings['blend_node'] = '' else: cmds.warning(error_message) - gt_blends_to_attr_settings['blend_node'] = '' + morphing_attr_settings['blend_node'] = '' def object_load_handler(operation): """ Function to handle load buttons. It updates the UI to reflect the loaded data. Args: - operation (str): String to determine function ("blend_mesh" or "attr_holder") + operation (str): String to determine function ("morphing_obj" or "attr_holder") """ def failed_to_load_source(failed_message="Failed to Load"): cmds.button(source_object_status, l=failed_message, e=True, bgc=(1, .4, .4), w=130) cmds.textScrollList(blend_nodes_scroll_list, e=True, removeAll=True) - gt_blends_to_attr_settings['blend_mesh'] = '' + morphing_attr_settings['morphing_obj'] = '' def failed_to_load_target(failed_message="Failed to Load"): cmds.button(attr_holder_status, l=failed_message, e=True, bgc=(1, .4, .4), w=130) - gt_blends_to_attr_settings['attr_holder'] = '' + morphing_attr_settings['attr_holder'] = '' # Blend Mesh - if operation == 'blend_mesh': + if operation == 'morphing_obj': current_selection = cmds.ls(selection=True) or [] if not current_selection: cmds.warning("Nothing selected. Please select a mesh try again.") @@ -181,8 +180,8 @@ def failed_to_load_target(failed_message="Failed to Load"): failed_to_load_source() return else: - gt_blends_to_attr_settings['blend_mesh'] = current_selection[0] - cmds.button(source_object_status, l=gt_blends_to_attr_settings.get('blend_mesh'), + morphing_attr_settings['morphing_obj'] = current_selection[0] + cmds.button(source_object_status, l=morphing_attr_settings.get('morphing_obj'), e=True, bgc=(.6, .8, .6), w=130) cmds.textScrollList(blend_nodes_scroll_list, e=True, removeAll=True) cmds.textScrollList(blend_nodes_scroll_list, e=True, append=blendshape_nodes) @@ -199,59 +198,48 @@ def failed_to_load_target(failed_message="Failed to Load"): failed_to_load_target() return elif cmds.objExists(current_selection[0]): - gt_blends_to_attr_settings['attr_holder'] = current_selection[0] - cmds.button(attr_holder_status, l=gt_blends_to_attr_settings.get('attr_holder'), e=True, + morphing_attr_settings['attr_holder'] = current_selection[0] + cmds.button(attr_holder_status, l=morphing_attr_settings.get('attr_holder'), e=True, bgc=(.6, .8, .6), w=130) else: cmds.warning("Something went wrong, make sure you selected just one object and try again.") def validate_operation(): """ Checks elements one last time before running the script """ - print("validate then run") - # is_valid = False - # stretchy_name = None - # attr_holder = None - # - # stretchy_prefix = cmds.textField(stretchy_system_prefix, q=True, text=True).replace(' ', '') - # - # # Name - # if stretchy_prefix != '': - # stretchy_name = stretchy_prefix - # - # # ikHandle - # if gt_blends_to_attr_settings.get('ik_handle') == '': - # cmds.warning('Please load an ikHandle first before running the script.') - # is_valid = False - # else: - # if cmds.objExists(gt_blends_to_attr_settings.get('ik_handle')): - # is_valid = True - # else: - # cmds.warning('"' + str(gt_blends_to_attr_settings.get('ik_handle')) + - # "\" couldn't be located. " - # "Make sure you didn't rename or deleted the object after loading it") - # - # # Attribute Holder - # if is_valid: - # if gt_blends_to_attr_settings.get('attr_holder') != '': - # if cmds.objExists(gt_blends_to_attr_settings.get('attr_holder')): - # attr_holder = gt_blends_to_attr_settings.get('attr_holder') - # else: - # cmds.warning('"' + str(gt_blends_to_attr_settings.get('attr_holder')) + - # "\" couldn't be located. " - # "Make sure you didn't rename or deleted the object after loading it. " - # "A simpler version of the stretchy system was created.") - # else: - # sys.stdout.write( - # 'An attribute holder was not provided. A simpler version of the stretchy system was created.') - # + + # Morphing Object + morphing_obj = morphing_attr_settings.get('morphing_obj') + if morphing_obj: + if not cmds.objExists(morphing_obj): + cmds.warning('Unable to locate morphing object. Please try loading the object again.') + return + else: + cmds.warning('Missing morphing object. Make sure you loaded an object and try again.') + return + + # Attribute Holder + attr_holder = morphing_attr_settings.get('attr_holder') + if attr_holder: + if not cmds.objExists(attr_holder): + cmds.warning('Unable to locate attribute holder. Please try loading the object again.') + return + else: + cmds.warning('Missing attribute holder. Make sure you loaded an object and try again.') + return + + # Blend Shape Node + blend_node = morphing_attr_settings.get('blend_node') + if blend_node: + if not cmds.objExists(blend_node): + cmds.warning('Unable to blend shape node. Please try loading the object again.') + return + else: + cmds.warning('Select a blend shape node to be used as source.') + return + # # Run Script - # if is_valid: - # if stretchy_name: - # make_stretchy_ik(gt_blends_to_attr_settings.get('ik_handle'), stretchy_name=stretchy_name, - # attribute_holder=attr_holder) - # else: - # make_stretchy_ik(gt_blends_to_attr_settings.get('ik_handle'), stretchy_name='temp', - # attribute_holder=attr_holder) + logger.debug('Main Function Called') + # blends_to_attr(morphing_obj, blend_node, attr_holder) window_name = "build_gui_morphing_attributes" if cmds.window(window_name, exists=True): @@ -277,21 +265,16 @@ def validate_operation(): cmds.separator(h=5, style='none') # Empty Space # Body ==================== - body_column = cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) cmds.separator(h=5, style='none') # Empty Space - # - # cmds.text('Text Here:') - # stretchy_system_prefix = cmds.textField(text='', pht='Text Here (Optional)') - - # cmds.separator(h=10, style='none') # Empty Space cmds.text('1. Deformed Mesh (Source):') cmds.separator(h=5, style='none') # Empty Space cmds.rowColumnLayout(nc=2, cw=[(1, 129), (2, 130)], cs=[(1, 10)], p=content_main) - cmds.button(l="Load Morphing Object", c=lambda x: object_load_handler("blend_mesh"), w=130) + cmds.button(l="Load Morphing Object", c=lambda x: object_load_handler("morphing_obj"), w=130) source_object_status = cmds.button(l="Not loaded yet", bgc=(.2, .2, .2), w=130, - c=lambda x: select_existing_object(gt_blends_to_attr_settings.get('blend_mesh'))) + c=lambda x: select_existing_object(morphing_attr_settings.get('morphing_obj'))) cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) cmds.separator(h=5, style='none') # Empty Space @@ -307,7 +290,7 @@ def validate_operation(): cmds.button(l="Load Attribute Holder", c=lambda x: object_load_handler("attr_holder"), w=130) attr_holder_status = cmds.button(l="Not loaded yet", bgc=(.2, .2, .2), w=130, c=lambda x: select_existing_object( - gt_blends_to_attr_settings.get('attr_holder'))) + morphing_attr_settings.get('attr_holder'))) cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) @@ -426,4 +409,5 @@ def change_outliner_color(obj, rgb_color=(1, 1, 1)): # Build UI if __name__ == '__main__': + logger.setLevel(logging.DEBUG) build_gui_morphing_attributes() From f2876e2ece70ab4a468e2a892e0c656428dc325b Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sat, 23 Jul 2022 23:18:04 -0700 Subject: [PATCH 15/18] Added settings (#79) --- python-scripts/gt_morphing_attributes.py | 115 +++++++++++++++++++++-- 1 file changed, 109 insertions(+), 6 deletions(-) diff --git a/python-scripts/gt_morphing_attributes.py b/python-scripts/gt_morphing_attributes.py index aeac023a..7f3a71bc 100644 --- a/python-scripts/gt_morphing_attributes.py +++ b/python-scripts/gt_morphing_attributes.py @@ -8,6 +8,13 @@ 0.0.2 - 2022-07-23 Create GUI +0.0.3 - 2022-07-23 +Added settings + +TODO: + Create filter logic + Connect core function + """ try: from shiboken2 import wrapInstance @@ -35,12 +42,22 @@ script_name = "GT - Add Morphing Attributes" # Version: -script_version = "0.0.2" +script_version = "0.0.3" # Settings morphing_attr_settings = {'morphing_obj': '', - 'blend_node': '', - 'attr_holder': '', + 'blend_node': '', + 'attr_holder': '', + 'filter_string': '', + 'filter_type': 'includes', + 'filter_undesired': False, + 'modify_range': True, + 'new_range_min': 0, + 'new_range_max': 10, + 'old_range_min': 0, + 'old_range_max': 1, + 'ignore_connected': True, + 'add_separator': True, } @@ -129,6 +146,57 @@ def blends_to_attr(morphing_obj, blend_node, attr_holder): def build_gui_morphing_attributes(): + def update_settings(*args): + logger.debug(str(args)) + filter_string = cmds.textField(filter_textfield, q=True, text=True) + filter_option_string = str(cmds.optionMenu(filter_option, q=True, value=True)) + + ignore_connected_value = cmds.checkBox(ignore_connected_chk, q=True, + value=morphing_attr_settings.get('ignore_connected')) + add_separator_value = cmds.checkBox(add_separator_chk, q=True, + value=morphing_attr_settings.get('add_separator')) + modify_range_value = cmds.checkBox(modify_range_chk, q=True, + value=morphing_attr_settings.get('modify_range')) + filter_undesired_value = cmds.checkBox(filter_undesired_chk, q=True, + value=morphing_attr_settings.get('filter_undesired')) + + old_min_int = cmds.intField(old_min_int_field, q=True, value=True) + old_max_int = cmds.intField(old_max_int_field, q=True, value=True) + new_min_int = cmds.intField(new_min_int_field, q=True, value=True) + new_max_int = cmds.intField(new_max_int_field, q=True, value=True) + + if modify_range_value: + cmds.rowColumnLayout(range_column, e=True, en=True) + else: + cmds.rowColumnLayout(range_column, e=True, en=False) + + if filter_undesired_value: + cmds.textField(filter_textfield, e=True, pht='Undesired Filter (Optional)') + else: + cmds.textField(filter_textfield, e=True, pht='Desired Filter (Optional)') + + morphing_attr_settings['modify_range'] = modify_range_value + morphing_attr_settings['ignore_connected'] = ignore_connected_value + morphing_attr_settings['add_separator'] = add_separator_value + morphing_attr_settings['filter_string'] = filter_string + morphing_attr_settings['filter_undesired'] = filter_undesired_value + morphing_attr_settings['filter_type'] = filter_option_string.replace(' ', '').lower() + morphing_attr_settings['old_range_min'] = old_min_int + morphing_attr_settings['old_range_max'] = old_max_int + morphing_attr_settings['new_range_min'] = new_min_int + morphing_attr_settings['new_range_max'] = new_max_int + + logger.debug('modify_range: ' + str(morphing_attr_settings.get('modify_range'))) + logger.debug('ignore_connected: ' + str(morphing_attr_settings.get('ignore_connected'))) + logger.debug('add_separator: ' + str(morphing_attr_settings.get('add_separator'))) + logger.debug('filter_string: ' + str(morphing_attr_settings.get('filter_string'))) + logger.debug('filter_undesired: ' + str(morphing_attr_settings.get('filter_undesired'))) + logger.debug('filter_type: ' + str(morphing_attr_settings.get('filter_type'))) + logger.debug('old_range_min: ' + str(morphing_attr_settings.get('old_range_min'))) + logger.debug('old_range_max: ' + str(morphing_attr_settings.get('old_range_max'))) + logger.debug('new_range_min: ' + str(morphing_attr_settings.get('new_range_min'))) + logger.debug('new_range_max: ' + str(morphing_attr_settings.get('new_range_max'))) + def select_blend_shape_node(): error_message = "Unable to locate blend shape node. Please try again." blend_node = cmds.textScrollList(blend_nodes_scroll_list, q=True, selectItem=True) or [] @@ -239,7 +307,7 @@ def validate_operation(): # # Run Script logger.debug('Main Function Called') - # blends_to_attr(morphing_obj, blend_node, attr_holder) + blends_to_attr(morphing_obj, blend_node, attr_holder) window_name = "build_gui_morphing_attributes" if cmds.window(window_name, exists=True): @@ -264,9 +332,8 @@ def validate_operation(): cmds.button(l="Help", bgc=title_bgc_color, c=lambda x: build_gui_help_morphing_attr()) cmds.separator(h=5, style='none') # Empty Space - # Body ==================== + # 1. Deformed Mesh (Source) ------------------------------------------ cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) - cmds.separator(h=5, style='none') # Empty Space cmds.text('1. Deformed Mesh (Source):') cmds.separator(h=5, style='none') # Empty Space @@ -282,6 +349,7 @@ def validate_operation(): blend_nodes_scroll_list = cmds.textScrollList(numberOfRows=8, allowMultiSelection=False, height=70, selectCommand=select_blend_shape_node) + # 2. Attribute Holder (Target) ------------------------------------------ cmds.separator(h=5, style='none') # Empty Space cmds.text('2. Attribute Holder (Target):') cmds.separator(h=5, style='none') # Empty Space @@ -294,6 +362,41 @@ def validate_operation(): cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) + # 3. Settings and Filters ------------------------------------------ + cmds.separator(h=7, style='none') # Empty Space + cmds.separator(h=5) + cmds.separator(h=7, style='none') # Empty Space + cmds.text("3. Settings and Filters") + cmds.separator(h=7, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 165)], cs=[(1, 10), (2, 5)], p=content_main) + filter_textfield = cmds.textField(text='', pht='Desired Filter (Optional)', cc=update_settings) + filter_option = cmds.optionMenu(label='', cc=update_settings) + cmds.menuItem(label='Includes') + cmds.menuItem(label='Starts With') + cmds.menuItem(label='Ends With') + cmds.separator(h=10, style='none') # Empty Space + + cmds.rowColumnLayout(nc=2, cw=[(1, 120)], cs=[(1, 30), (2, 5)], p=content_main) + ignore_connected_chk = cmds.checkBox("Ignore Connected", cc=update_settings, value=True) + add_separator_chk = cmds.checkBox("Add Separator", cc=update_settings, value=True) + cmds.separator(h=7, style='none') # Empty Space + + cmds.rowColumnLayout(nc=2, cw=[(1, 120)], cs=[(1, 30), (2, 5)], p=content_main) + modify_range_chk = cmds.checkBox("Modify Range", cc=update_settings, value=True) + filter_undesired_chk = cmds.checkBox("Filter Undesired", cc=update_settings) + cmds.separator(h=10, style='none') # Empty Space + + range_column = cmds.rowColumnLayout(nc=4, cw=[(1, 50)], cs=[(1, 30), (2, 5), (3, 30), (4, 5)], p=content_main) + cmds.text("Old Min:") + old_min_int_field = cmds.intField(width=30, value=morphing_attr_settings.get('old_range_min'), cc=update_settings) + cmds.text("Old Max:") + old_max_int_field = cmds.intField(width=30, value=morphing_attr_settings.get('old_range_max'), cc=update_settings) + cmds.text("New Min:") + new_min_int_field = cmds.intField(width=30, value=morphing_attr_settings.get('new_range_min'), cc=update_settings) + cmds.text("New Max:") + new_max_int_field = cmds.intField(width=30, value=morphing_attr_settings.get('new_range_max'), cc=update_settings) + + cmds.rowColumnLayout(nc=1, cw=[(1, 260)], cs=[(1, 10)], p=content_main) cmds.separator(h=7, style='none') # Empty Space cmds.separator(h=5) cmds.separator(h=7, style='none') # Empty Space From 8cbf3616b6b230ef9b1bd63d4daafa9264a16f59 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sun, 24 Jul 2022 13:38:08 -0700 Subject: [PATCH 16/18] Connect GUI to main function --- python-scripts/gt_morphing_attributes.py | 245 ++++++++++++++--------- 1 file changed, 155 insertions(+), 90 deletions(-) diff --git a/python-scripts/gt_morphing_attributes.py b/python-scripts/gt_morphing_attributes.py index 7f3a71bc..92e7c9e0 100644 --- a/python-scripts/gt_morphing_attributes.py +++ b/python-scripts/gt_morphing_attributes.py @@ -11,9 +11,14 @@ 0.0.3 - 2022-07-23 Added settings +1.0.0 - 2022-07-24 +Connected UI and main function +Connected Settings +Added filter logic +Added separated text field for undesired filter + TODO: - Create filter logic - Connect core function + Add options to not sort targets """ try: @@ -42,15 +47,17 @@ script_name = "GT - Add Morphing Attributes" # Version: -script_version = "0.0.3" +script_version = "1.0.0" # Settings morphing_attr_settings = {'morphing_obj': '', 'blend_node': '', 'attr_holder': '', - 'filter_string': '', - 'filter_type': 'includes', - 'filter_undesired': False, + 'desired_filter_string': '', + 'undesired_filter_string': '', + 'desired_filter_type': 'includes', + 'undesired_filter_type': 'includes', + 'ignore_case': True, 'modify_range': True, 'new_range_min': 0, 'new_range_max': 10, @@ -58,98 +65,119 @@ 'old_range_max': 1, 'ignore_connected': True, 'add_separator': True, + 'sort_targets': True, } -def blends_to_attr(morphing_obj, blend_node, attr_holder): - logger.debug(str(morphing_obj)) - selection_target = attr_holder - blendshape_node = blend_node - blendshape_names = cmds.listAttr(blendshape_node + '.w', m=True) - - modify_range = True - attribute_range_min = 0 - attribute_range_max = 10 - blend_range_min = 0 - blend_range_max = 1 - - ignore_connected = True - add_separator_attribute = True +def blends_to_attr(blend_node, attr_holder, undesired_filter_strings, desired_filter_strings, + desired_method='includes', undesired_method='includes', sort_targets=True, + ignore_connected=True, add_separator=True, ignore_case=True, + modify_range=True, old_min=0, old_max=1, new_min=0, new_max=10): custom_separator_attr = '' - method = 'includes' - - undesired_filter_strings = ['corrective'] - desired_blends = [] - desired_filter_strings = ['jaw', 'cheek', 'mouth', 'nose'] + blendshape_names = cmds.listAttr(blend_node + '.w', m=True) filtered_blends = [] # Find desired blends for target in blendshape_names: for desired_filter_string in desired_filter_strings: - if method == 'includes': - if desired_filter_string in target: + target_compare = target + string_compare = desired_filter_string + if ignore_case: + target_compare = target_compare.lower() + string_compare = string_compare.lower() + if desired_method == 'includes': + if string_compare in target_compare: filtered_blends.append(target) - elif method == 'startswith': - if target.startswith(desired_filter_string): + elif desired_method == 'startswith': + if target_compare.startswith(string_compare): filtered_blends.append(target) - elif method == 'endswith': - if target.endswith(desired_filter_string): + elif desired_method == 'endswith': + if target_compare.endswith(string_compare): filtered_blends.append(target) if len(desired_filter_strings) == 0: # If filter empty, use everything filtered_blends = blendshape_names + accessible_blends = [] if ignore_connected: # Pre-ignore connected blends - accessible_blends = [] for blend in filtered_blends: - connections = cmds.listConnections(blendshape_node + '.' + blend, destination=False, plugs=True) or [] + connections = cmds.listConnections(blend_node + '.' + blend, destination=False, plugs=True) or [] if len(connections) == 0: accessible_blends.append(blend) else: accessible_blends = filtered_blends - # Remove undesired blends from list + # Find desired blends + accessible_and_desired_blends = [] undesired_blends = [] - for target in desired_blends: - for undesired_string in undesired_filter_strings: - if undesired_string in target: - undesired_blends.append(target) + for target in accessible_blends: + for undesired_filter_string in undesired_filter_strings: + target_compare = target + string_compare = undesired_filter_string + if ignore_case: + target_compare = target_compare.lower() + string_compare = string_compare.lower() + if undesired_method == 'includes': + if string_compare in target_compare: + undesired_blends.append(target) + elif undesired_method == 'startswith': + if target_compare.startswith(string_compare): + undesired_blends.append(target) + elif undesired_method == 'endswith': + if target_compare.endswith(string_compare): + undesired_blends.append(target) + for blend in accessible_blends: if blend not in undesired_blends: - desired_blends.append(blend) + accessible_and_desired_blends.append(blend) # Separator Attribute - if len(desired_blends) != 0 and add_separator_attribute: - separator_attr = 'blends' + current_attributes = cmds.listAttr(attr_holder, userDefined=True) or [] + separator_attr = 'blends' + if separator_attr in current_attributes or custom_separator_attr in current_attributes: + add_separator = False + + if len(accessible_and_desired_blends) != 0 and add_separator: if custom_separator_attr: separator_attr = custom_separator_attr - cmds.addAttr(selection_target, ln=separator_attr, at='enum', en='-------------:', keyable=True) - cmds.setAttr(selection_target + '.' + separator_attr, e=True, lock=True) + cmds.addAttr(attr_holder, ln=separator_attr, at='enum', en='-------------:', keyable=True) + cmds.setAttr(attr_holder + '.' + separator_attr, e=True, lock=True) # Create Blend Drivers - desired_blends.sort() - for target in desired_blends: + if sort_targets: + accessible_and_desired_blends.sort() + for target in accessible_and_desired_blends: if modify_range: - cmds.addAttr(selection_target, ln=target, at='double', k=True, - maxValue=attribute_range_max, minValue=attribute_range_min) - remap_node = cmds.createNode('remapValue', name='remap_bs_' + target) - cmds.setAttr(remap_node + '.inputMax', attribute_range_max) - cmds.setAttr(remap_node + '.inputMin', attribute_range_min) - cmds.setAttr(remap_node + '.outputMax', blend_range_max) - cmds.setAttr(remap_node + '.outputMin', blend_range_min) - cmds.connectAttr(selection_target + '.' + target, remap_node + '.inputValue') - cmds.connectAttr(remap_node + '.outValue', blendshape_node + '.' + target, force=True) + if target not in current_attributes: + cmds.addAttr(attr_holder, ln=target, at='double', k=True, + maxValue=new_max, minValue=new_min) + else: + cmds.warning('"' + target + '" already existed on attribute holder. ' + 'Please check if no previous connections were lost.') + remap_node = cmds.createNode('remapValue', name='remap_target_' + target) + cmds.setAttr(remap_node + '.inputMax', new_max) + cmds.setAttr(remap_node + '.inputMin', new_min) + cmds.setAttr(remap_node + '.outputMax', old_max) + cmds.setAttr(remap_node + '.outputMin', old_min) + cmds.connectAttr(attr_holder + '.' + target, remap_node + '.inputValue') + cmds.connectAttr(remap_node + '.outValue', blend_node + '.' + target, force=True) else: - cmds.addAttr(selection_target, ln=target, at='double', k=True, maxValue=1, minValue=0) - cmds.connectAttr(selection_target + '.' + target, blendshape_node + '.' + target, force=True) + if target not in current_attributes: + cmds.addAttr(attr_holder, ln=target, at='double', k=True, maxValue=1, minValue=0) + else: + cmds.warning('"' + target + '" already existed on attribute holder. ' + 'Please check if no previous connections were lost') + cmds.connectAttr(attr_holder + '.' + target, blend_node + '.' + target, force=True) def build_gui_morphing_attributes(): def update_settings(*args): logger.debug(str(args)) - filter_string = cmds.textField(filter_textfield, q=True, text=True) - filter_option_string = str(cmds.optionMenu(filter_option, q=True, value=True)) + desired_filter_string = cmds.textField(desired_filter_textfield, q=True, text=True) + undesired_filter_string = cmds.textField(undesired_filter_textfield, q=True, text=True) + desired_filter_option_string = str(cmds.optionMenu(desired_filter_option, q=True, value=True)) + undesired_filter_option_string = str(cmds.optionMenu(undesired_filter_option, q=True, value=True)) ignore_connected_value = cmds.checkBox(ignore_connected_chk, q=True, value=morphing_attr_settings.get('ignore_connected')) @@ -157,8 +185,8 @@ def update_settings(*args): value=morphing_attr_settings.get('add_separator')) modify_range_value = cmds.checkBox(modify_range_chk, q=True, value=morphing_attr_settings.get('modify_range')) - filter_undesired_value = cmds.checkBox(filter_undesired_chk, q=True, - value=morphing_attr_settings.get('filter_undesired')) + ignore_case_value = cmds.checkBox(ignore_case_chk, q=True, + value=morphing_attr_settings.get('ignore_case')) old_min_int = cmds.intField(old_min_int_field, q=True, value=True) old_max_int = cmds.intField(old_max_int_field, q=True, value=True) @@ -170,17 +198,14 @@ def update_settings(*args): else: cmds.rowColumnLayout(range_column, e=True, en=False) - if filter_undesired_value: - cmds.textField(filter_textfield, e=True, pht='Undesired Filter (Optional)') - else: - cmds.textField(filter_textfield, e=True, pht='Desired Filter (Optional)') - morphing_attr_settings['modify_range'] = modify_range_value morphing_attr_settings['ignore_connected'] = ignore_connected_value morphing_attr_settings['add_separator'] = add_separator_value - morphing_attr_settings['filter_string'] = filter_string - morphing_attr_settings['filter_undesired'] = filter_undesired_value - morphing_attr_settings['filter_type'] = filter_option_string.replace(' ', '').lower() + morphing_attr_settings['desired_filter_string'] = desired_filter_string + morphing_attr_settings['undesired_filter_string'] = undesired_filter_string + morphing_attr_settings['ignore_case'] = ignore_case_value + morphing_attr_settings['desired_filter_type'] = desired_filter_option_string.replace(' ', '').lower() + morphing_attr_settings['undesired_filter_type'] = undesired_filter_option_string.replace(' ', '').lower() morphing_attr_settings['old_range_min'] = old_min_int morphing_attr_settings['old_range_max'] = old_max_int morphing_attr_settings['new_range_min'] = new_min_int @@ -189,9 +214,11 @@ def update_settings(*args): logger.debug('modify_range: ' + str(morphing_attr_settings.get('modify_range'))) logger.debug('ignore_connected: ' + str(morphing_attr_settings.get('ignore_connected'))) logger.debug('add_separator: ' + str(morphing_attr_settings.get('add_separator'))) - logger.debug('filter_string: ' + str(morphing_attr_settings.get('filter_string'))) - logger.debug('filter_undesired: ' + str(morphing_attr_settings.get('filter_undesired'))) - logger.debug('filter_type: ' + str(morphing_attr_settings.get('filter_type'))) + logger.debug('desired_filter_string: ' + str(morphing_attr_settings.get('desired_filter_string'))) + logger.debug('undesired_filter_string: ' + str(morphing_attr_settings.get('undesired_filter_string'))) + logger.debug('ignore_case: ' + str(morphing_attr_settings.get('ignore_case'))) + logger.debug('desired_filter_type: ' + str(morphing_attr_settings.get('desired_filter_type'))) + logger.debug('undesired_filter_type: ' + str(morphing_attr_settings.get('undesired_filter_type'))) logger.debug('old_range_min: ' + str(morphing_attr_settings.get('old_range_min'))) logger.debug('old_range_max: ' + str(morphing_attr_settings.get('old_range_max'))) logger.debug('new_range_min: ' + str(morphing_attr_settings.get('new_range_min'))) @@ -275,15 +302,17 @@ def failed_to_load_target(failed_message="Failed to Load"): def validate_operation(): """ Checks elements one last time before running the script """ - # Morphing Object - morphing_obj = morphing_attr_settings.get('morphing_obj') - if morphing_obj: - if not cmds.objExists(morphing_obj): - cmds.warning('Unable to locate morphing object. Please try loading the object again.') - return - else: - cmds.warning('Missing morphing object. Make sure you loaded an object and try again.') - return + # # Morphing Object + # morphing_obj = morphing_attr_settings.get('morphing_obj') + # if morphing_obj: + # if not cmds.objExists(morphing_obj): + # cmds.warning('Unable to locate morphing object. Please try loading the object again.') + # return + # else: + # cmds.warning('Missing morphing object. Make sure you loaded an object and try again.') + # return + + update_settings() # Attribute Holder attr_holder = morphing_attr_settings.get('attr_holder') @@ -307,7 +336,27 @@ def validate_operation(): # # Run Script logger.debug('Main Function Called') - blends_to_attr(morphing_obj, blend_node, attr_holder) + undesired_strings = morphing_attr_settings.get('undesired_filter_string').replace(' ', '') + if undesired_strings: + undesired_strings = undesired_strings.split(',') + else: + undesired_strings = [] + desired_strings = morphing_attr_settings.get('desired_filter_string').replace(' ', '') + if desired_strings: + desired_strings = desired_strings.split(',') + else: + desired_strings = [] + blends_to_attr(blend_node, attr_holder, undesired_strings, desired_strings, + desired_method=morphing_attr_settings.get('desired_filter_type'), + undesired_method=morphing_attr_settings.get('undesired_filter_type'), + ignore_connected=morphing_attr_settings.get('ignore_connected'), + add_separator=morphing_attr_settings.get('add_separator'), + ignore_case=morphing_attr_settings.get('ignore_case'), + modify_range=morphing_attr_settings.get('modify_range'), + old_min=morphing_attr_settings.get('old_range_min'), + old_max=morphing_attr_settings.get('old_range_max'), + new_min=morphing_attr_settings.get('new_range_min'), + new_max=morphing_attr_settings.get('new_range_max')) window_name = "build_gui_morphing_attributes" if cmds.window(window_name, exists=True): @@ -369,21 +418,32 @@ def validate_operation(): cmds.text("3. Settings and Filters") cmds.separator(h=7, style='none') # Empty Space cmds.rowColumnLayout(nc=2, cw=[(1, 165)], cs=[(1, 10), (2, 5)], p=content_main) - filter_textfield = cmds.textField(text='', pht='Desired Filter (Optional)', cc=update_settings) - filter_option = cmds.optionMenu(label='', cc=update_settings) + desired_filter_textfield = cmds.textField(text='', pht='Desired Filter (Optional)', cc=update_settings) + desired_filter_option = cmds.optionMenu(label='', cc=update_settings) + cmds.menuItem(label='Includes') + cmds.menuItem(label='Starts With') + cmds.menuItem(label='Ends With') + cmds.separator(h=10, style='none') # Empty Space + cmds.rowColumnLayout(nc=2, cw=[(1, 165)], cs=[(1, 10), (2, 5)], p=content_main) + undesired_filter_textfield = cmds.textField(text='', pht='Undesired Filter (Optional)', cc=update_settings) + undesired_filter_option = cmds.optionMenu(label='', cc=update_settings) cmds.menuItem(label='Includes') cmds.menuItem(label='Starts With') cmds.menuItem(label='Ends With') cmds.separator(h=10, style='none') # Empty Space - cmds.rowColumnLayout(nc=2, cw=[(1, 120)], cs=[(1, 30), (2, 5)], p=content_main) - ignore_connected_chk = cmds.checkBox("Ignore Connected", cc=update_settings, value=True) - add_separator_chk = cmds.checkBox("Add Separator", cc=update_settings, value=True) + cmds.rowColumnLayout(nc=2, cw=[(1, 110)], cs=[(1, 30), (2, 5)], p=content_main) + ignore_connected_chk = cmds.checkBox("Ignore Connected", cc=update_settings, + value=morphing_attr_settings.get('ignore_connected')) + ignore_case_chk = cmds.checkBox("Ignore Filter Case", cc=update_settings, + value=morphing_attr_settings.get('ignore_case')) cmds.separator(h=7, style='none') # Empty Space - cmds.rowColumnLayout(nc=2, cw=[(1, 120)], cs=[(1, 30), (2, 5)], p=content_main) - modify_range_chk = cmds.checkBox("Modify Range", cc=update_settings, value=True) - filter_undesired_chk = cmds.checkBox("Filter Undesired", cc=update_settings) + cmds.rowColumnLayout(nc=2, cw=[(1, 110)], cs=[(1, 30), (2, 5)], p=content_main) + modify_range_chk = cmds.checkBox("Modify Range", cc=update_settings, + value=morphing_attr_settings.get('modify_range')) + add_separator_chk = cmds.checkBox("Add Separator", cc=update_settings, + value=morphing_attr_settings.get('add_separator')) cmds.separator(h=10, style='none') # Empty Space range_column = cmds.rowColumnLayout(nc=4, cw=[(1, 50)], cs=[(1, 30), (2, 5), (3, 30), (4, 5)], p=content_main) @@ -512,5 +572,10 @@ def change_outliner_color(obj, rgb_color=(1, 1, 1)): # Build UI if __name__ == '__main__': - logger.setLevel(logging.DEBUG) + debugging = False + if debugging: + logger.setLevel(logging.DEBUG) + morphing_attr_settings['morphing_obj'] = 'source_obj' + morphing_attr_settings['attr_holder'] = 'target_obj' + morphing_attr_settings['blend_node'] = 'blendShape1' build_gui_morphing_attributes() From 163fb22d7a15ac5a72c175695594d1923bd24214 Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sun, 24 Jul 2022 13:58:28 -0700 Subject: [PATCH 17/18] Fixed morphing attributes call --- mel-scripts/gt_tools_menu.mel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mel-scripts/gt_tools_menu.mel b/mel-scripts/gt_tools_menu.mel index 3151495a..782dc07c 100644 --- a/mel-scripts/gt_tools_menu.mel +++ b/mel-scripts/gt_tools_menu.mel @@ -407,7 +407,7 @@ menuItem -l "Rigging" -sm true -to true -image "kinReroot.png"; menuItem -l ("Morphing Attributes") - -c ("python(\"gt_tools.execute_script('gt_attributes_to_python', 'build_gui_morphing_attributes')\");") + -c ("python(\"gt_tools.execute_script('gt_morphing_attributes', 'build_gui_morphing_attributes')\");") -ann ("Creates attributes to drive selected blend shapes.") -image "blendShape.png"; From aaf72fb930b3e5b1a16dc134f13acd25652e264a Mon Sep 17 00:00:00 2001 From: TrevisanGMW Date: Sun, 24 Jul 2022 13:58:51 -0700 Subject: [PATCH 18/18] Added undo chunk and inView feedback --- python-scripts/gt_morphing_attributes.py | 103 ++++++++++++++++------- 1 file changed, 72 insertions(+), 31 deletions(-) diff --git a/python-scripts/gt_morphing_attributes.py b/python-scripts/gt_morphing_attributes.py index 92e7c9e0..f4cbfdc2 100644 --- a/python-scripts/gt_morphing_attributes.py +++ b/python-scripts/gt_morphing_attributes.py @@ -17,9 +17,17 @@ Added filter logic Added separated text field for undesired filter +1.0.1 - 2022-07-24 +Added undo chunk +Changed remap node name +Kept original selection after operation +Added inView feedback +Added some docs + TODO: Add options to not sort targets - + Add help + Add docs """ try: from shiboken2 import wrapInstance @@ -47,7 +55,7 @@ script_name = "GT - Add Morphing Attributes" # Version: -script_version = "1.0.0" +script_version = "1.0.1" # Settings morphing_attr_settings = {'morphing_obj': '', @@ -69,10 +77,32 @@ } -def blends_to_attr(blend_node, attr_holder, undesired_filter_strings, desired_filter_strings, +def blends_to_attr(blend_node, attr_holder, desired_filter_strings, undesired_filter_strings, desired_method='includes', undesired_method='includes', sort_targets=True, ignore_connected=True, add_separator=True, ignore_case=True, modify_range=True, old_min=0, old_max=1, new_min=0, new_max=10): + """ + + Args: + blend_node (string): Blend shape node used to extract desired morphing targets + attr_holder (string): Object to receive attributes (usually a control curve) + desired_filter_strings (list): A list of desired strings (targets with these strings will be added) + undesired_filter_strings (list): A list of undesired strings (targets with these strings will be ignored) + desired_method (string): Method using during filtering "includes", "startswith" or "endswith" + undesired_method (string): Method using during filtering "includes", "startswith" or "endswith" + sort_targets: If it should sort the list or use the original order + ignore_connected: If it should ignore morphing targets that already have an incoming connection + add_separator: Added a locked attribute used as a separator + ignore_case: If it should ignore the capitalization of the strings when filtering + modify_range: If it should remap the range of the target and attribute values + old_min: old minimum value (usually, 0) + old_max: old maximum value (usually, 1) + new_min: new minimum value (usually, 0) + new_max: new maximum value (usually, 10) + + Returns: + created_attributes (list) + """ custom_separator_attr = '' blendshape_names = cmds.listAttr(blend_node + '.w', m=True) filtered_blends = [] @@ -154,7 +184,7 @@ def blends_to_attr(blend_node, attr_holder, undesired_filter_strings, desired_fi else: cmds.warning('"' + target + '" already existed on attribute holder. ' 'Please check if no previous connections were lost.') - remap_node = cmds.createNode('remapValue', name='remap_target_' + target) + remap_node = cmds.createNode('remapValue', name='remap_morphing_' + target) cmds.setAttr(remap_node + '.inputMax', new_max) cmds.setAttr(remap_node + '.inputMin', new_min) cmds.setAttr(remap_node + '.outputMax', old_max) @@ -169,9 +199,10 @@ def blends_to_attr(blend_node, attr_holder, undesired_filter_strings, desired_fi 'Please check if no previous connections were lost') cmds.connectAttr(attr_holder + '.' + target, blend_node + '.' + target, force=True) + return accessible_and_desired_blends -def build_gui_morphing_attributes(): +def build_gui_morphing_attributes(): def update_settings(*args): logger.debug(str(args)) desired_filter_string = cmds.textField(desired_filter_textfield, q=True, text=True) @@ -301,17 +332,6 @@ def failed_to_load_target(failed_message="Failed to Load"): def validate_operation(): """ Checks elements one last time before running the script """ - - # # Morphing Object - # morphing_obj = morphing_attr_settings.get('morphing_obj') - # if morphing_obj: - # if not cmds.objExists(morphing_obj): - # cmds.warning('Unable to locate morphing object. Please try loading the object again.') - # return - # else: - # cmds.warning('Missing morphing object. Make sure you loaded an object and try again.') - # return - update_settings() # Attribute Holder @@ -319,20 +339,20 @@ def validate_operation(): if attr_holder: if not cmds.objExists(attr_holder): cmds.warning('Unable to locate attribute holder. Please try loading the object again.') - return + return False else: cmds.warning('Missing attribute holder. Make sure you loaded an object and try again.') - return + return False # Blend Shape Node blend_node = morphing_attr_settings.get('blend_node') if blend_node: if not cmds.objExists(blend_node): cmds.warning('Unable to blend shape node. Please try loading the object again.') - return + return False else: cmds.warning('Select a blend shape node to be used as source.') - return + return False # # Run Script logger.debug('Main Function Called') @@ -346,17 +366,38 @@ def validate_operation(): desired_strings = desired_strings.split(',') else: desired_strings = [] - blends_to_attr(blend_node, attr_holder, undesired_strings, desired_strings, - desired_method=morphing_attr_settings.get('desired_filter_type'), - undesired_method=morphing_attr_settings.get('undesired_filter_type'), - ignore_connected=morphing_attr_settings.get('ignore_connected'), - add_separator=morphing_attr_settings.get('add_separator'), - ignore_case=morphing_attr_settings.get('ignore_case'), - modify_range=morphing_attr_settings.get('modify_range'), - old_min=morphing_attr_settings.get('old_range_min'), - old_max=morphing_attr_settings.get('old_range_max'), - new_min=morphing_attr_settings.get('new_range_min'), - new_max=morphing_attr_settings.get('new_range_max')) + + current_selection = cmds.ls(selection=True) + cmds.undoInfo(openChunk=True, chunkName=script_name) # Start undo chunk + try: + blend_attr_list = blends_to_attr(blend_node, attr_holder, desired_strings, undesired_strings, + desired_method=morphing_attr_settings.get('desired_filter_type'), + undesired_method=morphing_attr_settings.get('undesired_filter_type'), + ignore_connected=morphing_attr_settings.get('ignore_connected'), + add_separator=morphing_attr_settings.get('add_separator'), + ignore_case=morphing_attr_settings.get('ignore_case'), + modify_range=morphing_attr_settings.get('modify_range'), + old_min=morphing_attr_settings.get('old_range_min'), + old_max=morphing_attr_settings.get('old_range_max'), + new_min=morphing_attr_settings.get('new_range_min'), + new_max=morphing_attr_settings.get('new_range_max')) + + message = '' + str(len(blend_attr_list)) + message += ' ' + is_plural = 'morphing attributes were' + if len(blend_attr_list) == 1: + is_plural = 'morphing attribute was' + message += is_plural + ' created/connected.' + + cmds.inViewMessage(amg=message, pos='botLeft', fade=True, alpha=.9) + sys.stdout.write(str(len(blend_attr_list)) + ' ' + is_plural + ' created/connected.') + return True + except Exception as e: + logger.debug(str(e)) + return False + finally: + cmds.undoInfo(closeChunk=True, chunkName=script_name) + cmds.select(current_selection) window_name = "build_gui_morphing_attributes" if cmds.window(window_name, exists=True):