diff --git a/README.md b/README.md
index e7fff1f2..24be2dc6 100644
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ In case you need/want to manually install the scripts. It's also a pretty straig
- How do I update GT Tools to a new version?
A: Simply uninstall and install it again.
- What do I do if I have multiple "userSetup.mel" files? One inside "maya/####/scripts" and another one inside "maya/scripts"
A: The "userSetup.mel" file gets executed when you open Maya, but Maya supports only one file. In case you have two files it will give priority to the file located inside "maya/####/scripts", so manage your initialization commands there.
- - Where are the other scripts you had in this repository?
A: I moved all other scripts that are not part of GT Tools to another reposity. Here is the link: TrevisanGMW/maya-scripts
+ - Where are the other scripts you had in this repository?
A: I moved all other scripts that are not part of GT Tools to another repository. Here is the link: TrevisanGMW/maya-scripts
Contributors
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()