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

Contributors

diff --git a/mel-scripts/gt_tools_menu.mel b/mel-scripts/gt_tools_menu.mel index 394e99fb..782dc07c 100644 --- a/mel-scripts/gt_tools_menu.mel +++ b/mel-scripts/gt_tools_menu.mel @@ -166,6 +166,10 @@ // 2.1.0 - 2022-07-19 // Added Render Calculator // +// 2.2.0 - 2022-07-19 +// Added "Attributes to Python" +// Added "Morphing Attributes" +// //---------------------------------------------------------------------------- // Globals @@ -253,13 +257,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 ".." ; @@ -396,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_morphing_attributes', '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')\");") @@ -428,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" ; 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__': diff --git a/python-scripts/gt_attributes_to_python.py b/python-scripts/gt_attributes_to_python.py index 12dbfa1f..c04b902e 100644 --- a/python-scripts/gt_attributes_to_python.py +++ b/python-scripts/gt_attributes_to_python.py @@ -9,22 +9,52 @@ 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 + + 0.0.5 - 2022-07-22 + Increased the size of the UI + Added "Extract User-Defined Attributes" function + + TODO: + Add options """ +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.5" 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 +186,226 @@ 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: + 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, 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") + 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, 500)], cs=[(1, 10)], 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, 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')) + 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, 490)], cs=[(1, 10)], p=content_main) + cmds.text(label='Output Python Code') + 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 + + 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 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_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) + 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 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) + 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(':/attributes.png') + 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() 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_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 diff --git a/python-scripts/gt_morphing_attributes.py b/python-scripts/gt_morphing_attributes.py new file mode 100644 index 00000000..f4cbfdc2 --- /dev/null +++ b/python-scripts/gt_morphing_attributes.py @@ -0,0 +1,622 @@ +""" +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 + +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 + +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 +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 = "1.0.1" + +# Settings +morphing_attr_settings = {'morphing_obj': '', + 'blend_node': '', + 'attr_holder': '', + '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, + 'old_range_min': 0, + 'old_range_max': 1, + 'ignore_connected': True, + 'add_separator': True, + 'sort_targets': True, + } + + +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 = [] + + # Find desired blends + for target in blendshape_names: + for desired_filter_string in desired_filter_strings: + 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 desired_method == 'startswith': + if target_compare.startswith(string_compare): + filtered_blends.append(target) + 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 + for blend in filtered_blends: + connections = cmds.listConnections(blend_node + '.' + blend, destination=False, plugs=True) or [] + if len(connections) == 0: + accessible_blends.append(blend) + else: + accessible_blends = filtered_blends + + # Find desired blends + accessible_and_desired_blends = [] + undesired_blends = [] + 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: + accessible_and_desired_blends.append(blend) + + # Separator Attribute + 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(attr_holder, ln=separator_attr, at='enum', en='-------------:', keyable=True) + cmds.setAttr(attr_holder + '.' + separator_attr, e=True, lock=True) + + # Create Blend Drivers + if sort_targets: + accessible_and_desired_blends.sort() + for target in accessible_and_desired_blends: + if modify_range: + 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_morphing_' + 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: + 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) + + return accessible_and_desired_blends + + +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) + 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')) + 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')) + 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) + 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) + + 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['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 + 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('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'))) + 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 [] + if blend_node: + if cmds.objExists(blend_node[0]): + sys.stdout.write('"' + str(blend_node[0]) + '" will be used when creating attributes.') + morphing_attr_settings['blend_node'] = blend_node[0] + else: + cmds.warning(error_message) + morphing_attr_settings['blend_node'] = '' + else: + cmds.warning(error_message) + 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 ("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) + 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) + morphing_attr_settings['attr_holder'] = '' + + # 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.") + 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: + 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) + + # 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]): + 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 """ + update_settings() + + # 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 False + else: + cmds.warning('Missing attribute holder. Make sure you loaded an object and try again.') + 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 False + else: + cmds.warning('Select a blend shape node to be used as source.') + return False + + # # Run Script + logger.debug('Main Function Called') + 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 = [] + + 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): + 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 + + # 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 + + 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("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(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 + cmds.text('Blend Shape Nodes:', font="smallPlainLabelFont") + 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 + 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( + morphing_attr_settings.get('attr_holder'))) + + 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) + 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, 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, 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) + 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 + + 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__': + 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() 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() diff --git a/python-scripts/gt_rigger_biped_logic.py b/python-scripts/gt_rigger_biped_logic.py index 69275c32..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 @@ -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,15 +297,21 @@ 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): """ - 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 """ @@ -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))) @@ -1740,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 """ @@ -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): """ @@ -2046,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 @@ -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) @@ -2339,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) @@ -2632,8 +2580,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 +3511,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 +3903,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 +4245,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 +5092,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], @@ -5235,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) @@ -5390,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) @@ -5501,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 @@ -6264,22 +6212,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 = [] @@ -6361,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 @@ -6843,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 = [] @@ -6942,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 @@ -7056,7 +6998,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 +7594,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 +7940,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) @@ -10312,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) 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 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 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()