This function could eventually be changed to simply refresh that UI with the new information.

Finds all assets in the scene built with ARTv2, and creates a picker tab for each one in the animation UI.

Gets the current QGraphicsView based on the currently selected character tab, and the currently selected
picker tab of said character.

Adds a tab to the QTabWidget for a given character to add more module control pickers to. These are sometimes
referred to as "canvases".

bypass -- If not creating the "Main" tab or loading a picker from file, a tab name must be entered.

If not creating the "Main" tab or loading a picker from file, a tab name must be entered.

tabName -- If creating the "Main" tab or loading a picker from file, the name given to the tab that
will be created.

If creating the "Main" tab or loading a picker from file, the name given to the tab that

Instantiates ART_SelectControlsUI to bring up the tool for selecting specified rig controls for selected
modules.

Gets any comment boxes that were created on any picker tabs and returns a list of those items.
:param saving: Whether or not this function is being called from savePicker, in which case, it will disable
edits.

A list of lists where the inner list contains the following data for each comment box found:

The QRect of the comment box, which contains the box dimensions and the x/y coordinates.

Creates an instance of ART_AddModuleToCanvas to bring up that tool, passing in modules valid to add.

Instantiates ART_MatchOverRangeUI to bring up the tool for matching different rig type over a frame range for
selected modules.

Matching the IK leg rig controls to the Fk leg rig controls over a frame range of 0-30.

Compares the modules that are on the picker to all modules that make up the character.

unused -- Whether or not we want a list returned of modules that are not on the picker yet.

Whether or not we want a list returned of modules that are not on the picker yet.

used -- Whether or not we want a list returned of modules that are on the picker.

Whether or not we want a list returned of modules that are on the picker.

Returns a list of the modules based on either used or unused args.

Override event that captures a mouse release when in the passed in QGraphicsView and hides the QRubberBand
if it was visible. If in comment box mode, this release event will also create the comment box with the
dimensions and position of the start point of the mouse press, and the QRect from the mouse move.

view -- the QGraphicsView to detect mouse release events and to add comment boxes to.

the QGraphicsView to detect mouse release events and to add comment boxes to.

Creates an instance of the ART_MovePickerToTabUI tool, passing in all valid module pickers that could be moved.

Takes the given module and instantiates it, returning the memory address for the created instance.

Save the picker data to a .picker file. The main picker data is actually gathered by self.getPickerTabs,
self.getComments, and self.getButtonColors.

Mirrors any QGraphicsSimpleTextItems text if the parent picker was mirrored, so that the text is correct.
:param children: List of child items (QGraphicsSimpleTextItems) of a pickerBorderItem or a pickerButton.

Loads a .picker file and builds the picker according to the file data.
:param filename: The path of the picker file to gather data from.

Creates an interface allowing a user to change the background image of a picker with these dimensions: (442 x
600)

Instantiates ART_ImportMotionUI to bring up the tool for importing FBX animation onto the rig.

Instantiates ART_ResetModulesUI to bring up the tool for resetting transformations on a selected modules.
Also known as "zeroing out".

Toggles selection interaction modes within the QGraphicsScene. The three different modes are normal select,
drag select, and comment box mode. This will set the dragMode of the QGraphicsView to the currently selected
state, along with switching icons to show selection status.

Creates an instance of the ART_RemoveModuleFromCanvas tool, passing in modules valid to remove.

Override event that captures a mouse press when in the passed in QGraphicsView and displays the QRubberBand
if in drag select or comment box mode.

Gets joint module picker button colors, which can be edited by the user.

Currently, only the joint module supports users being able to change the button color.

A list of lists where the inner list contains the following data for each joint module found:

Create a new control picker for the given character tab. This will remove the help gif and replace it with a
blank canvas that module pickers can be added to (using ART_AddModuleToCanvas).

bypass -- Whether or not to bypass the QMessageBox confirming the creation of a new picker.
This is used when loading a picker from file. bypass will be set to True.

Whether or not to bypass the QMessageBox confirming the creation of a new picker.

Gets the QGraphicsScene of the current QGraphicsView, which is gotten by calling on self.getCurrentView.

Override event that captures a mouse move when in the passed in QGraphicsView and changes the
displayed size of the QRubberBand based on the origin position and the current position, drawing a QRect (if
in drag select or comment box mode.). It also finds any items inside of that QRect (picker buttons, etc).

view -- the QGraphicsView to detect mouse move events in and check for items in.

the QGraphicsView to detect mouse move events in and check for items in.

Instantiates the class, getting the QSettings, writing the stylesheets, and calling on the method to build
the interface.

Finds and returns all picker item data, like transforms, scale, x/y coordinates, mirrored status, parent tab,
and controlled module.

saving -- Whether or not this function is being called by savePicker, in which case if it is, it will then
make sure that the picker items are no longer editable.

Whether or not this function is being called by savePicker, in which case if it is, it will then

A list of lists, where each inner list has the following data for a picker item:

Name of module picker belongs to (which module controls it interfaces with)

Whether or not the picker is a sub-picker, like fingers or toes.

Makes all picker items editable again, making them selectable, movable, scalable, and rotatable.

Finds all modules on the picker. Used mostly be the load picker function.
:return: Returns a list of picker data that includes for each item, the module the picker is for, the item in
memory, and the nice name.

Opens a file browser to select a valid .png background image and then apply it to the specified QGraphicsScene.

Instantiate the ART_AnimationUI class to build the main interface the animators will interact with.

Instantiates ART_ExportMotionUI to bring up the tool for exporting animation out to various file formats.

Sets the icon of the matchRangeBtn to the next frame in the passed in movie. Note: This was a test function
to see if animated gifs could be used as buttons.

Builds the animation UI that houses the area for control pickers and a sidebar for animation tools.

This class creates the main animation interface that houses the control picker and the buttons for the animation
tools. This is the main interface that animators will interact with.

addModuleToPickerUI() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

backgroundBrowse() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

changeBackground() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

clearBackground() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

closeEvent() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

comparePickerToRig() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

createNewPicker() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

editPicker() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

exportMotion() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

findAllPickerItems() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

findCharacters() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

getButtonColors() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

getComments() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

getCurrentScene() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

getCurrentView() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

getModuleInst() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

getPickerTabs() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

gfxViewMouseMove() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

gfxViewMousePress() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

gfxViewMouseRelease() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

importMotion() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

loadPicker() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

matchOverRange() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

mirrorChildTextItems() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

movePickerToTab() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

pickerTabChange() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

removeModuleFromPickerUI() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

resetRigCtrls() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

savePicker() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

selectAllCtrls() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

setButtonIcon() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

toggleDragState() (Interfaces.ART_AnimationUI.ART_AnimationUI method)

Called on when a tab is changed on a given character's picker, if the remove modules from canvas UI is open,
it will be closed.

Suggested feature request was to have this operate on the rig controls and not just joints. That
would mean storing both controls and joints. The desire behind this was to do all LOD posing in the
rig file, rather than the temp file that gets created for exporting.

Adds a Groupbox with two QListWidgets, where the left shows bones to remove, and the right shows the next
viable parent bone to transfer the weighting to. Both lists can then be edited as well.

Finds the current number of LOD tabs, constructs a label for the new tab, iterating the count by 1,
and calls on createLODpage, passing in that label.

Checks to see if LOD attributes exist on the character node, and if so, builds and populates the UI based on
those settings.

ART_ExportMeshes.addMeshesToLodList(listWidget, meshTree, mainWin, label)

Finds the selected items in addMeshToList_UI's treeWidget, and adds them back to the main LOD page's
listWidget for meshes associated with that LOD.

listWidget -- The list widget to add selected items in the treeWidget to.

meshTree -- The treeWidget from addMeshToList_UI(), whose selection will be queried.

The treeWidget from addMeshToList_UI(), whose selection will be queried.

Finds any child items from selected items in the Skeleton Tree and appends them to the input lists.

fullList -- The full list of all items, including the original selected parent items and any child items

The full list of all items, including the original selected parent items and any child items

itemList -- The instances in memory of the selected items in the Skeleton Tree.

The instances in memory of the selected items in the Skeleton Tree.

item -- The parent item in the Skeleton Tree to check for children

Removes the current tab index from the tabWidget. Also removes and LOD attributes associated with this LOD
from the character node.

Finds all skinned meshes in the scene and adds them as items to the given TreeWidget.

Creates a UI that lists all meshes for selection to assign meshes to a given LOD.

listWidget -- The listWidget on the main LOD page that will list the selected meshes

The listWidget on the main LOD page that will list the selected meshes

Gathers all export data by calling on utils.findExportMeshData, parses the information, and for each LOD in
the list, calls on utils.ExportMesh(), passing in the appropriate data.

Instantiates the class, getting the QSettings, presenting a QMessageBox about saving the current file,
creates a temporary file to do the export work out of (stripping out the rig and removing all connections
from joints), set the model pose, then calls on the UI build.

mainUI -- Instance of the Rig Creator interface, from which this class was called.

Instance of the Rig Creator interface, from which this class was called.

Creates a right-click context menu for the weighting transfer entry widget:

groupBox -- The parent groupBox for the weighting transfer entry.

Gathers the associated meshes for an LOD and stores that information to the character node.

listWidget -- The QListWidget of the LOD tab to search for associated meshes.

Creates a tab (QFrame) for a new LOD, with the UI elements to set the output path for the FBX, to set the
meshes assigned to the LOD, and to launch the bone removal tool for the LOD.

closeable -- Whether this LOD can be removed, thus removing the tab.

would mean getting data for both controls and joints. The desire behind this was to do all LOD posing
in the rig file, rather than the temp file that gets created for exporting.

Gather LOD pose attribute data for the given LOD and set those values on the joints.

Finds the selected items in the Skeleton Tree (for removal) and locates the next viable parent to transfer
weighting to, then adds that bone to the passed in QListWidget.

widget -- The QListWidget to add the bone that will receive weights from removed bones.

The QListWidget to add the bone that will receive weights from removed bones.

autoFind -- Whether or not to auto-locate the next viable parent or use the currently selected item.

Whether or not to auto-locate the next viable parent or use the currently selected item.

Takes the items selected in the skeleton tree (pictured below) and auto-selects and children under the
selected items, as removing the parent will also remove the children of those parents, and then adds all of
those items to the given widget.

widget -- The QListWidget in the weighting transfer entry widget showing bones to remove.

The QListWidget in the weighting transfer entry widget showing bones to remove.

boneWidget -- The QListWidget in the weighting transfer entry widget showing bone who will receive
weights from removed bones.

The QListWidget in the weighting transfer entry widget showing bone who will receive

Gathers all information from the temp file (LOD meshes, bones to remove, etc), opens the export file,
and applies that information to the network node in the export file. Lastly removes the temp file.

Collapses the given groupBox down to 16 pixel high, or restores its original height, given the state.

groupBox -- Which groupBox to operate on and manipulate the height.

Takes the selected items from the left QListWidget in a weighting transfer widget and attempts to remove
them from the list.

Gathers the FBX output path for the LOD and stores that information to the character node.

ART_ExportMeshes.addBoneToList_Accept(tree, listWidget, lod, layout, viewBtn)

Called from the "Save and Close" button of the addBoneToList_UI, clears all items in the main listWidget,
then populates that listWidget with the new bones to remove. Adds all LOD pose info to the character node as
well as weighting transfer info for that lod.

listWidget -- The listWidget on the main LOD page on the bottom right that lists bones to remove.

The listWidget on the main LOD page on the bottom right that lists bones to remove.

layout -- The QVboxLayout to query for weighting transfer entries.

Calls on a fileDialog for the user to browse to an FBX file for saving. Either one that exists, or creating
a new one.

lineEdit -- The QLineEdit whose text to set with the path to the FBX file.

Creates an interface to remove bones from a LOD, transfer weighting of those removed bones to the next viable
parent, and handle LOD posing.

parent -- The instance of the main UI created by ART_ExportMeshes.buildUI()

The instance of the main UI created by ART_ExportMeshes.buildUI()

label -- The label for the window title to show what LOD this interface represents.

The label for the window title to show what LOD this interface represents.

listWidget -- The list widget on the main LOD page that lists all bones being removed.

The list widget on the main LOD page that lists all bones being removed.

Populates the Skeleton Tree (self.tree) by recursively looking for relatives of the given name and making more
QTreeWidgetItems using the passed in name, and parenting under the passed in parent.

name -- The bone name, which will be used to look for children, and also as the text for the treeWidgetItem

The bone name, which will be used to look for children, and also as the text for the treeWidgetItem

parent -- The parent treeWidgetItem that the created item will be a child of.

The parent treeWidgetItem that the created item will be a child of.

Builds the main interface for the tool, which allows additions of LODs, assignment of meshes to a given LOD,
and assignment of bones to remove per LOD (which then opens another interface/tool).

Here is a breakdown image showing which UI elements call on which functions:

Creates the LOD0 Tab, which is a little bit unique from subsequent LOD tabs, as the Add/Remove LOD buttons
are added, and the LOD0 tab can not be removed. It still calls on createLODpage to create the common
elements, but this creates the framework for all of the LOD tabs.

Removes the given groupBox, deleting a weighting transfer entry.

This class is used to export skeletal meshes and skeletal mesh LODs.

The UI has a robust suite of tools for managing LODs, bone removal for LODs, choosing
which meshes are associated with a LOD, where weighting from removed bones will get transferred,
and setting/viewing LOD poses (useful if you wanted to remove finger bones for a LOD, but not have
"paddle hands"

A look at the LOD tools for transferring weighting and managing LOD poses:

__init__() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addBoneToList_Accept() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addBoneToList_UI() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addItemsToWeightXferList() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addMeshLOD() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addMeshToList_UI() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addMeshesToLodList() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

addXferBoneToList() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

browseToFBX() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

closeWin() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

collapseBox() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

createExportMeshesPage() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

createLODpage() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

findTreeChildren() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

lodContextMenu() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

populateRenderMeshes() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

populateUI() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

removeBonesFromList() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

removeLodTab() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

removeTransferEntry() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

resetLodPose() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

saveFilePath() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

saveLodPose() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

saveMeshList() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

viewLodPose() (Interfaces.ART_ExportMeshes.ART_ExportMeshes method)

Queries the joints in the skeleton and get the attribute values to store for the given lod.

This class is used to export FBX animation from the rig to Unreal Engine. It supports morph targets,
custom attribute curves, and pre/post scripts.

It can be found on the animation sidebar with this icon:
.. image:: /images/exportMotionButton.png

__init__() (Interfaces.ART_ExportMotionUI.ART_ExportMotion method)

"""
Author: Jeremy Ernst
"""

from functools import partial

import maya.cmds as cmds

import System.utils as utils
from ThirdParty.Qt import QtGui, QtCore, QtWidgets

class ART_AimMode():
    """
    This class creates a tool that allows a rigger to toggle aim mode for a given selection of modules. Aim mode
    ensures that the parent joints in a joint mover are always aiming at their children.

    This tool can be called from the toolbar on the Rig Creator UI from the following button:
        .. image:: /images/aimModeButton.png

    The full interface looks like this:
        .. image:: /images/aimMode.png

    """

    def __init__(self, mainUI):
        """
        Instantiates the class, getting the QSettings for the tool, and building the Aim Mode interface.

        :param mainUI: The instance of the Rig Creator UI this tool was called from.

        """

        # get the directory path of the tools
        settings = QtCore.QSettings("Epic Games", "ARTv2")
        self.toolsPath = settings.value("toolsPath")
        self.iconsPath = settings.value("iconPath")
        self.mainUI = mainUI

        # build the UI
        self.buildAimModeUI()

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    def buildAimModeUI(self):
        """
        Builds the Aim Mode interface, finding all modules that have the ability to aim, and listing those modules
        as well as their current aim status.

        """

        if cmds.window("ART_AimModeWin", exists=True):
            cmds.deleteUI("ART_AimModeWin", wnd=True)

        # launch a UI to get the name information
        self.aimModeWin = QtWidgets.QMainWindow(self.mainUI)

        # load stylesheet
        styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
        f = open(styleSheetFile, "r")
        self.style = f.read()
        f.close()

        self.aimModeWin.setStyleSheet(self.style)

        # size policies
        mainSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        # create the main widget
        self.aimModeWin_mainWidget = QtWidgets.QWidget()
        self.aimModeWin.setCentralWidget(self.aimModeWin_mainWidget)

        # set qt object name
        self.aimModeWin.setObjectName("ART_AimModeWin")
        self.aimModeWin.setWindowTitle("Aim Mode")

        # create the mainLayout for the rig creator UI
        self.aimModeWin_mainLayout = QtWidgets.QVBoxLayout(self.aimModeWin_mainWidget)
        self.aimModeWin_mainLayout.setContentsMargins(0, 0, 0, 0)

        self.aimModeWin.resize(400, 250)
        self.aimModeWin.setSizePolicy(mainSizePolicy)
        self.aimModeWin.setMinimumSize(QtCore.QSize(400, 250))
        self.aimModeWin.setMaximumSize(QtCore.QSize(400, 250))

        # create the background image
        self.aimModeWin_frame = QtWidgets.QFrame()
        self.aimModeWin_mainLayout.addWidget(self.aimModeWin_frame)

        # create the layout for the widgets
        self.aimModeWin_widgetLayout = QtWidgets.QHBoxLayout(self.aimModeWin_frame)
        self.aimModeWin_widgetLayout.setContentsMargins(5, 5, 5, 5)

        # add the QListWidget Frame
        self.aimModeWin_moduleListFrame = QtWidgets.QFrame()
        self.aimModeWin_moduleListFrame.setMinimumSize(QtCore.QSize(275, 200))
        self.aimModeWin_moduleListFrame.setMaximumSize(QtCore.QSize(275, 200))
        self.aimModeWin_moduleListFrame.setContentsMargins(20, 0, 20, 0)

        # create the list widget
        self.aimModeWin_moduleList = QtWidgets.QListWidget(self.aimModeWin_moduleListFrame)
        self.aimModeWin_widgetLayout.addWidget(self.aimModeWin_moduleListFrame)
        self.aimModeWin_moduleList.setMinimumSize(QtCore.QSize(265, 200))
        self.aimModeWin_moduleList.setMaximumSize(QtCore.QSize(265, 200))
        self.aimModeWin_moduleList.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
        self.aimModeWin_moduleList.setSpacing(3)

        # add the layout for the buttons
        self.aimModeWin_buttonLayoutAll = QtWidgets.QVBoxLayout()
        self.aimModeWin_widgetLayout.addLayout(self.aimModeWin_buttonLayoutAll)
        self.aimModeWin_buttonLayoutAll.setContentsMargins(5, 20, 5, 20)

        # add the selection buttons
        self.aimModeWin_selectionButtonLayout = QtWidgets.QVBoxLayout()
        self.aimModeWin_buttonLayoutAll.addLayout(self.aimModeWin_selectionButtonLayout)
        self.aimModeWin_selectAllButton = QtWidgets.QPushButton("Select All")
        self.aimModeWin_selectAllButton.setMinimumSize(QtCore.QSize(115, 25))
        self.aimModeWin_selectAllButton.setMaximumSize(QtCore.QSize(115, 25))
        self.aimModeWin_selectionButtonLayout.addWidget(self.aimModeWin_selectAllButton)
        self.aimModeWin_selectAllButton.clicked.connect(self.aimModeWin_moduleList.selectAll)
        self.aimModeWin_selectAllButton.setObjectName("blueButton")

        self.aimModeWin_selectNoneButton = QtWidgets.QPushButton("Clear Selection")
        self.aimModeWin_selectNoneButton.setMinimumSize(QtCore.QSize(115, 25))
        self.aimModeWin_selectNoneButton.setMaximumSize(QtCore.QSize(115, 25))
        self.aimModeWin_selectionButtonLayout.addWidget(self.aimModeWin_selectNoneButton)
        self.aimModeWin_selectNoneButton.clicked.connect(self.aimModeWin_moduleList.clearSelection)
        self.aimModeWin_selectNoneButton.setObjectName("blueButton")

        # spacer
        spacerItem = QtWidgets.QSpacerItem(20, 80, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.aimModeWin_selectionButtonLayout.addItem(spacerItem)

        # add the buttons for reset settings and reset transforms
        self.aimModeWin_turnOnAim = QtWidgets.QPushButton("On")
        self.aimModeWin_turnOnAim.setMinimumSize(QtCore.QSize(115, 25))
        self.aimModeWin_turnOnAim.setMaximumSize(QtCore.QSize(115, 25))
        self.aimModeWin_selectionButtonLayout.addWidget(self.aimModeWin_turnOnAim)
        self.aimModeWin_turnOnAim.setToolTip("Turn on Aim Mode for selected modules.")
        self.aimModeWin_turnOnAim.clicked.connect(partial(self.aimModeUI_Toggle, True))
        self.aimModeWin_turnOnAim.setObjectName("blueButton")

        self.aimModeWin_turnOffAim = QtWidgets.QPushButton("Off")
        self.aimModeWin_turnOffAim.setMinimumSize(QtCore.QSize(115, 25))
        self.aimModeWin_turnOffAim.setMaximumSize(QtCore.QSize(115, 25))
        self.aimModeWin_selectionButtonLayout.addWidget(self.aimModeWin_turnOffAim)
        self.aimModeWin_turnOffAim.setToolTip("Turn off Aim Mode for selected modules.")
        self.aimModeWin_turnOffAim.clicked.connect(partial(self.aimModeUI_Toggle, False))
        self.aimModeWin_turnOffAim.setObjectName("blueButton")

        # populate the list widget
        modules = utils.returnRigModules()
        for module in modules:
            # get module name
            moduleName = cmds.getAttr(module + ".moduleName")

            # figure out if the module supports aimMode
            canAim = False
            if cmds.objExists(module + ".canAim"):
                canAim = cmds.getAttr(module + ".canAim")

                # see if it is currently in aimMode
                aimMode = cmds.getAttr(module + ".aimMode")

            # if it does, add it to the listwidget
            if canAim:

                # font
                headerFont = QtGui.QFont()
                headerFont.setPointSize(10)
                headerFont.setBold(True)

                # create the listWidgetItem
                pixmap = QtGui.QPixmap(10, 10)
                pixmap.fill(QtGui.QColor(0, 255, 0))
                icon = QtGui.QIcon(pixmap)

                pixmapOff = QtGui.QPixmap(10, 10)
                pixmapOff.fill(QtGui.QColor(255, 0, 0))
                iconOff = QtGui.QIcon(pixmapOff)

                item = QtWidgets.QListWidgetItem(iconOff, moduleName)
                item.setFont(headerFont)
                item.setTextAlignment(QtCore.Qt.AlignCenter)
                item.setData(QtCore.Qt.UserRole, [icon, iconOff])

                if aimMode:
                    item.setIcon(icon)

                self.aimModeWin_moduleList.addItem(item)

        # show the window
        self.aimModeWin.show()

    def aimModeUI_Toggle(self, state):
        """
        Calls on each selected module's aimMode function and sets the aimMode based on the passed in state.

        :param state: Whether to turn aim mode on or off.

        """

        selected = self.aimModeWin_moduleList.selectedItems()
        items = []

        for each in selected:
            items.append(each.text())
            if state:
                each.setIcon(each.data(QtCore.Qt.UserRole)[0])
            if not state:
                each.setIcon(each.data(QtCore.Qt.UserRole)[1])

        # call on each selected module's aimMode function
        for each in self.mainUI.moduleInstances:
            name = each.name
            if name in items:
                each.aimMode(state)

        # clear selection
        self.aimModeWin_moduleList.clearSelection()

"""
"""

# import statements

import System.interfaceUtils as interfaceUtils

class ART_AddModuleToCanvas(object):
    """
    This tool presents a UI that lists all modules not currently on the animation picker canvas, and allows the user
    to choose modules from the list to add to the animation picker canvas. This is an animator-facing tool.

    .. image:: /images/addModuleToCanvas.png

    """

    def __init__(self, animPickerUI, modulesToAdd, parent=None):
        """
        initialize the class, get the QSettings for the tool, and call on buildUI.

        :param animPickerUI: the instance of the animation picker class that launched this class.
        :param modulesToAdd: A list of the modules to add, which is a list of modules currently not on the animPickerUI

        """
        super(ART_AddModuleToCanvas, self).__init__()
        self.scriptPath = settings.value("scriptPath")
        self.projectPath = settings.value("projectPath")

        self.pickerUI = animPickerUI
        self.modules = []
        self.modulesToAdd = modulesToAdd

        # assign close event
        self.closeEvent = self.closeWin

        self.buildUI()

    def buildUI(self):
        """
        Builds the interface for the tool, showing a list of modules not currently displayed on the picker.

        """

        if cmds.window("pyART_AddToCanvasWIN", exists=True):
            cmds.deleteUI("pyART_AddToCanvasWIN", wnd=True)

        # create the main window
        self.mainWin = QtWidgets.QMainWindow(self.pickerUI)

        self.mainWidget = QtWidgets.QWidget()
        self.mainWin.setCentralWidget(self.mainWidget)

        # create the mainLayout
        self.layout = QtWidgets.QVBoxLayout(self.mainWidget)

        styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/animPicker.qss")
        f.close()

        self.mainWin.setStyleSheet(self.style)

        self.mainWin.setMinimumSize(QtCore.QSize(250, 400))
        self.mainWin.setMaximumSize(QtCore.QSize(250, 400))
        self.mainWin.resize(250, 400)

        self.mainWin.setObjectName("pyART_AddToCanvasWIN")
        self.mainWin.setWindowTitle("Add Module To Canvas")

        # label, listWidget, button
        label = QtWidgets.QLabel("Available Modules:")
        label.setProperty("boldFont", True)
        self.layout.addWidget(label)

        self.moduleList = QtWidgets.QListWidget()
        self.moduleList.setMaximumSize(230, 300)
        self.moduleList.setMinimumSize(230, 300)
        self.layout.addWidget(self.moduleList)

        # add modules to listWidget
        self.addModulesToList()

        # create add button
        button = QtWidgets.QPushButton("Add Selected To Canvas")
        self.layout.addWidget(button)
        button.setObjectName("blueButton")
        button.clicked.connect(self.addSelectedToCanvas)

        # show ui
        self.mainWin.show()

    def addModulesToList(self):
        """
        Take the modules passed into the class, and if the modules do not already exist on the canvas, add them to
        the list widget.

        """

        existing = self.getExistingModules()

        for module in self.modulesToAdd:
            if module not in existing:
                modName = cmds.getAttr(module + ".moduleName")

                # add to listWIdget
                index = self.pickerUI.characterTabs.currentIndex()
                widget = self.pickerUI.characterTabs.widget(index)
                characterNode = widget.property("charNode")

                item = QtWidgets.QListWidgetItem(modName)
                item.setData(QtCore.Qt.UserRole, [module, characterNode])
                self.moduleList.addItem(item)

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    def addSelectedToCanvas(self):
        """
        Takes the selected module in the list widget and adds it to the picker canvas. The picker UI is contained in
        the self.pickerUI.

        """

        selected = self.moduleList.currentItem()
        module = selected.data(QtCore.Qt.UserRole)[0]

        index = self.pickerUI.characterTabs.currentIndex()
        widget = self.pickerUI.characterTabs.widget(index)
        characterNode = widget.property("charNode")

        # get inst
        modType = cmds.getAttr(module + ".moduleType")
        modName = cmds.getAttr(module + ".moduleName")
        mod = __import__("RigModules." + modType, {}, {}, [modType])
        reload(mod)

        # get the class name from that module file (returns RigModules.ART_Root.ART_Root for example)
        moduleClass = getattr(mod, mod.className)

        # find the instance of that module
        moduleInst = moduleClass(self, modName)
        self.modules.append(moduleInst)

        scene = self.getCurrentCanvasTab()

        # find out if charNode has a namespace
        if cmds.objExists(characterNode + ".namespace"):
            namespace = cmds.getAttr(characterNode + ".namespace") + ":"
        else:
            namespace = ""

        # pass in the network node and the namespace
        picker = moduleInst.pickerUI(scene.sceneRect().center(), self.pickerUI, module, namespace)
        scene.addItem(picker[0])
        self.pickerUI.selectionScriptJobs.append(picker[2])

        # =======================================================================
        # #mirror the module's pickerBorderItem if needed
        if picker[1] == True:
            picker[0].setTransformOriginPoint(picker[0].boundingRect().center())
            picker[0].setTransform(QtGui.QTransform(-1.0, 0.0, 0.0, 1.0, picker[0].boundingRect().width() * 2, 0.0))

            children = picker[0].childItems()
            if children is not None:
                self.mirrorChildren(children)

        row = self.moduleList.row(selected)
        self.moduleList.takeItem(row)

    def mirrorChildren(self, children):
        """
        Take the passed in children and mirror the item so the text is not backwards. When a module is added to the
        canvas, if it was a right side module, it will be mirrored. This then unmirrors the text.

        :param children: List of QGraphicsSimpleTextItems that need to be mirrored.

        """

        # for mirroring text on any child items of a pickerBorderItem
        for child in children:
            if type(child) == QtWidgets.QGraphicsSimpleTextItem:
                child.setTransformOriginPoint(child.boundingRect().center())
                child.setTransform(QtGui.QTransform(-1.0, 0.0, 0.0, 1.0, child.boundingRect().width(), 0.0))

            children = child.childItems()

    def getCurrentCanvasTab(self):
        """
        Get the current tab of the current character of the animation picker.
        :return: returns the QGraphicsScene that items can then be added to.

        """
        # get the current tab index and the widget

        children = widget.children()
            if type(child) == QtWidgets.QTabWidget:
                tab = child
                canvasIndex = tab.currentIndex()
                canvasWidget = tab.widget(canvasIndex)

                canvasChildren = canvasWidget.children()
                for canvasChild in canvasChildren:
                    if type(canvasChild) == QtWidgets.QGraphicsView:
                        view = canvasChild
                        scene = view.scene()

        return scene

    def getExistingModules(self):
        """
        Find all existing modules on the canvas and return those modules as a list.
        :return: List of modules whose picker exists on the canvas.

        """

        characterNodeModules = cmds.listConnections(characterNode + ".rigModules")

        namespace = None

        returnData = []

        # get the children of the current tab widget

            # if we find a tab widget, search for the gfxScene
                tab = child
                selectedTab = tab.currentIndex()

                for i in range(tab.count()):
                    tab.setCurrentIndex(i)
                    canvasIndex = tab.currentIndex()
                    canvasWidget = tab.widget(canvasIndex)
                    canvasChildren = canvasWidget.children()

                    for canvasChild in canvasChildren:
                        if type(canvasChild) == QtWidgets.QGraphicsView:
                            view = canvasChild
                            scene = view.scene()

                            # get all items in the gfxScene
                            itemsInScene = scene.items()

                            for item in itemsInScene:
                                # if we find our top level picker item (the borderItem), get it's data
                                if type(item) == interfaceUtils.pickerBorderItem or item.type() == 3:
                                    module = item.data(QtCore.Qt.UserRole)

                                    if namespace is None:
                                        if module not in returnData:
                                            returnData.append(module)
                                    else:
                                        if (namespace + module) not in returnData:
                                            returnData.append(namespace + module)

                tab.setCurrentIndex(selectedTab)

        return returnData

    def closeWin(self, event):
        """
        deletes the UI.

        """

        cmds.deleteUI("pyART_AddToCanvasWIN", wnd=True)

"""
"""

import maya.mel as mel

import System.riggingUtils as riggingUtils

class ART_AddOrRemoveInfluences():
    """
    This class is used to list influences in or out of the current skinCluster, and then remove or add said
    influences given current selection.

    It is called from this button, found after finalizing your setup:
      .. image:: /images/addRemoveInfsButton.png

    This is what the full interface looks like:
      .. image:: /images/addRemoveInfs.png

    """

        """
        Instantiates the class, taking in the instance of the rig creator skin tools interface. Get settings values
        from QSettings. Build the interface.

        :param mainUI: Instance of the skin tools interface.

        """

        self.buildInterface()

    def buildInterface(self):
        """
        Builds the interface for the tool, finding all joints that compose the asset, comparing them to joints in the
        skinCluster, then separating the initial list into joints in the cluster, and joints not in the cluster.

        """

        if cmds.window("ART_addRemoveInfsWin", exists=True):
            cmds.deleteUI("ART_addRemoveInfsWin", wnd=True)

        self.addRemoveInfsWin = QtWidgets.QMainWindow(self.mainUI)

        self.addRemoveInfsWin_mainWidget = QtWidgets.QWidget()
        self.addRemoveInfsWin.setCentralWidget(self.addRemoveInfsWin_mainWidget)

        f.close()

        self.addRemoveInfsWin.setObjectName("ART_addRemoveInfsWin")
        self.addRemoveInfsWin.setWindowTitle("Add/Remove Influences")

        # create the mainLayout for the ui
        self.addRemoveInfsWin_mainLayout = QtWidgets.QVBoxLayout(self.addRemoveInfsWin_mainWidget)
        self.addRemoveInfsWin_mainLayout.setContentsMargins(5, 5, 5, 5)

        self.addRemoveInfsWin.resize(300, 450)
        self.addRemoveInfsWin.setSizePolicy(mainSizePolicy)
        self.addRemoveInfsWin.setMinimumSize(QtCore.QSize(300, 450))
        self.addRemoveInfsWin.setMaximumSize(QtCore.QSize(300, 450))

        self.addRemoveInfsWin_frame = QtWidgets.QFrame()
        self.addRemoveInfsWin_mainLayout.addWidget(self.addRemoveInfsWin_frame)
        self.addRemoveInfsWin_frame.setObjectName("dark")

        # create the main layout for the widgets
        self.addRemoveInfsWin_widgetLayout = QtWidgets.QHBoxLayout(self.addRemoveInfsWin_frame)

        # two layouts needed for the widget layout. left side = vertical layout for filters,
        # search, and list. right layout = vertical layout for buttons
        self.addRemoveInfsWin_leftSideLayout = QtWidgets.QVBoxLayout()
        self.addRemoveInfsWin_widgetLayout.addLayout(self.addRemoveInfsWin_leftSideLayout)

        self.addRemoveInfsWin_rightSideLayout = QtWidgets.QVBoxLayout()
        self.addRemoveInfsWin_widgetLayout.addLayout(self.addRemoveInfsWin_rightSideLayout)

        # left side: filters, search, list
        self.addRemoveInfsWin_filters = QtWidgets.QComboBox()
        self.addRemoveInfsWin_leftSideLayout.addWidget(self.addRemoveInfsWin_filters)
        self.addRemoveInfsWin_filters.addItem("Show Influences In Skin")
        self.addRemoveInfsWin_filters.addItem("Show Influences Not In Skin")
        self.addRemoveInfsWin_filters.currentIndexChanged.connect(partial(self.addOrRemoveInfs_ShowInfsFilter))

        self.addRemoveInfsWin_search = QtWidgets.QLineEdit()
        self.addRemoveInfsWin_leftSideLayout.addWidget(self.addRemoveInfsWin_search)
        self.addRemoveInfsWin_search.setPlaceholderText("Search...")
        self.addRemoveInfsWin_search.textChanged.connect(partial(self.addOrRemoveInfs_Search))

        self.addRemoveInfsWin_infList = QtWidgets.QListWidget()
        self.addRemoveInfsWin_leftSideLayout.addWidget(self.addRemoveInfsWin_infList)
        self.addRemoveInfsWin_infList.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)

        # right side: add button, remove button, prune weights, remove unused button
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setBold(True)

        self.addRemoveInfsWin_refreshSelBtn = QtWidgets.QPushButton("Refresh")
        self.addRemoveInfsWin_rightSideLayout.addWidget(self.addRemoveInfsWin_refreshSelBtn)
        self.addRemoveInfsWin_refreshSelBtn.setMinimumSize(110, 35)
        self.addRemoveInfsWin_refreshSelBtn.setMaximumSize(110, 35)
        self.addRemoveInfsWin_refreshSelBtn.setFont(font)
        self.addRemoveInfsWin_refreshSelBtn.clicked.connect(partial(self.addOrRemoveInfs_RefreshSelection))
        self.addRemoveInfsWin_refreshSelBtn.setObjectName("blueButton")
        self.addRemoveInfsWin_rightSideLayout.addSpacerItem(
            QtWidgets.QSpacerItem(100, 300, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed))

        self.addRemoveInfsWin_addInfBtn = QtWidgets.QPushButton("Add")
        self.addRemoveInfsWin_rightSideLayout.addWidget(self.addRemoveInfsWin_addInfBtn)
        self.addRemoveInfsWin_addInfBtn.setMinimumSize(110, 35)
        self.addRemoveInfsWin_addInfBtn.setMaximumSize(110, 35)
        self.addRemoveInfsWin_addInfBtn.setFont(font)
        self.addRemoveInfsWin_addInfBtn.clicked.connect(partial(self.addOrRemoveInfs_addInf, True, False))
        self.addRemoveInfsWin_addInfBtn.setObjectName("blueButton")

        self.addRemoveInfsWin_removeInfBtn = QtWidgets.QPushButton("Remove")
        self.addRemoveInfsWin_rightSideLayout.addWidget(self.addRemoveInfsWin_removeInfBtn)
        self.addRemoveInfsWin_removeInfBtn.setMinimumSize(110, 35)
        self.addRemoveInfsWin_removeInfBtn.setMaximumSize(110, 35)
        self.addRemoveInfsWin_removeInfBtn.setFont(font)
        self.addRemoveInfsWin_removeInfBtn.clicked.connect(partial(self.addOrRemoveInfs_addInf, False, False))
        self.addRemoveInfsWin_removeInfBtn.setObjectName("blueButton")

        self.addRemoveInfsWin_removeUnusedInfBtn = QtWidgets.QPushButton("Remove Unused")
        self.addRemoveInfsWin_rightSideLayout.addWidget(self.addRemoveInfsWin_removeUnusedInfBtn)
        self.addRemoveInfsWin_removeUnusedInfBtn.setMinimumSize(110, 35)
        self.addRemoveInfsWin_removeUnusedInfBtn.setMaximumSize(110, 35)
        self.addRemoveInfsWin_removeUnusedInfBtn.setFont(font)
        self.addRemoveInfsWin_removeUnusedInfBtn.clicked.connect(partial(self.addOrRemoveInfs_addInf, False, True))
        self.addRemoveInfsWin_removeUnusedInfBtn.setObjectName("blueButton")

        self.addRemoveInfsWin_pruneBtn = QtWidgets.QPushButton("Prune Weights")
        self.addRemoveInfsWin_rightSideLayout.addWidget(self.addRemoveInfsWin_pruneBtn)
        self.addRemoveInfsWin_pruneBtn.setMinimumSize(110, 35)
        self.addRemoveInfsWin_pruneBtn.setMaximumSize(110, 35)
        self.addRemoveInfsWin_pruneBtn.setFont(font)
        self.addRemoveInfsWin_pruneBtn.clicked.connect(partial(self.addOrRemoveInfs_prune))
        self.addRemoveInfsWin_pruneBtn.setObjectName("blueButton")

        # populate infList
        self.addOrRemoveInfs_RefreshSelection()

        # show window
        self.addRemoveInfsWin.show()

    def addOrRemoveInfs_RefreshSelection(self):
        """
        Regenerate the lists comparing all joints of the rig to joints in the skin cluster and joints not in the
        skinCluster. Clear the listWidgets and refresh with new data.

        """

        self.addRemoveInfsWin_infList.clear()

        # get selection, find skin cluster, find influences in skinCluster, and populate listWidget
        selection = cmds.ls(sl=True)
        if len(selection) > 0:
            skinCluster = riggingUtils.findRelatedSkinCluster(selection[0])

            if skinCluster is not None:
                skinInfs = cmds.skinCluster(skinCluster, q=True, inf=True)

                for inf in skinInfs:
                    self.addRemoveInfsWin_infList.addItem(inf)

    def addOrRemoveInfs_ShowInfsFilter(self):
        """
        Change what is displayed in the QListWidget based on the QComboBox setting of which joints to show: those in
        the skinCluster, or those not in the skinCluster.

        """

        currentIndex = self.addRemoveInfsWin_filters.currentIndex()

                # if filter set to show infs in cluster:
                if currentIndex == 0:
                    for each in skinInfs:
                        self.addRemoveInfsWin_infList.addItem(each)

                # if filter set to show non-skinned infs
                if currentIndex == 1:
                    # get full path
                    dagPath = cmds.ls(skinInfs[0], long=True)[0]
                    rootJoint = dagPath.partition("|")[2].partition("|")[0]
                    currentSelection = cmds.ls(sl=True)[0]

                    # get all joints
                    cmds.select(rootJoint, hi=True)
                    skeleton = cmds.ls(sl=True)
                    cmds.select(currentSelection)

                    # compare all joints to infs list
                    for each in skeleton:
                        if each not in skinInfs:
                            self.addRemoveInfsWin_infList.addItem(each)

    def addOrRemoveInfs_Search(self):
        """
        Hides all items in the QListWidget, then compares the QLineEdit search text with each item in the QListWidget,
        and if the search text is found in the text of an item, show that QListWidgetItem.

        """

        searchKey = self.addRemoveInfsWin_search.text()

        # get all items in the joint list
        allItems = []
        for i in range(self.addRemoveInfsWin_infList.count()):
            item = self.addRemoveInfsWin_infList.item(i)
            itemName = item.text()
            allItems.append([item, itemName])

        # hide all items in list
        for item in allItems:
            item[0].setHidden(True)
            item[0].setSelected(False)

        # find items in list with search key and show item
        if searchKey.find("*") == 0:
            matchedItems = self.addRemoveInfsWin_infList.findItems(searchKey, QtCore.Qt.MatchFlag.MatchWildcard)
            for item in matchedItems:
                item.setHidden(False)
                item.setSelected(True)

        else:
            matchedItems = self.addRemoveInfsWin_infList.findItems(searchKey, QtCore.Qt.MatchFlag.MatchContains)

    def addOrRemoveInfs_addInf(self, add, removeUnused):
        """
        Either add, or remove, selected influences from the QListWidget to/from the skinCluster. (the main function)

        :param add: Whether or not to add or remove.
        :param removeUnused: If this flag is set, remove any unweighted influences in the skinCluster.

        """

        # get current selection in scene
        currentSelection = cmds.ls(sl=True)

        # get selected items in infList
        selectedItems = self.addRemoveInfsWin_infList.selectedItems()

        # find the skinCluster

            # removed unused influences
            if removeUnused:
                weightedInfs = cmds.skinCluster(skinCluster, q=True, weightedInfluence=True)
                allInfs = cmds.skinCluster(skinCluster, q=True, inf=True)

                for inf in allInfs:
                    if inf not in weightedInfs:
                        cmds.skinCluster(skinCluster, edit=True, ri=inf)

                cmds.select(currentSelection)
                self.addOrRemoveInfs_RefreshSelection()
                self.addOrRemoveInfs_ShowInfsFilter()
                return

            # add selectedItems to the skinCluster
            for item in selectedItems:
                if add:
                    cmds.skinCluster(skinCluster, edit=True, ai=item.text(), wt=0, lw=True)
                if not add:
                    cmds.skinCluster(skinCluster, edit=True, ri=item.text())

        # refresh list
        cmds.select(currentSelection)
        self.addOrRemoveInfs_ShowInfsFilter()

    def addOrRemoveInfs_prune(self):
        """
        Calls on Maya's built in prune skin weights tool.

        """

        mel.eval("PruneSmallWeightsOptions;")

        # refresh lists

"""
"""

class ART_BoneCounter():
    """
    This class builds a simple interface that allows a user to see what the total bone count of their asset will be
    given the current module settings. It also allows the user to set a bone count target.

    It can be found on the Rig Creator toolbar with this icon:
        .. image:: /images/boneCounterButton.png

    The interface looks like this:
        .. image:: /images/boneCounter.png

    """
        """
        Instantiates this class, getting the QSettings and calling on the method to build the interface for the tool.

        :param mainUI: The instance of the Rig Creator UI from which this class was called.

        .. seealso:: ART_BoneCounter.buildBoneCounterUI

        """

        self.buildBoneCounterUI()

    def buildBoneCounterUI(self):
        """
        Builds the interface for the bone counter tool, which is comprised off a QLineEdit that shows the current
        bone count, a QPushButton to launch another simple UI that allows the user to set a bone count target,
        and QProgressBar that shows a percentage to target bone count.

        .. seealso:: ART_BoneCounter.setBoneCountTarget

        """

        if cmds.window("ART_BoneCounterWin", exists=True):
            cmds.deleteUI("ART_BoneCounterWin", wnd=True)

        self.boneCounterWin = QtWidgets.QMainWindow(self.mainUI)

        self.boneCounterWin_mainWidget = QtWidgets.QWidget()
        self.boneCounterWin.setCentralWidget(self.boneCounterWin_mainWidget)

        self.boneCounterWin.setObjectName("ART_BoneCounterWin")
        self.boneCounterWin.setWindowTitle("Bone Counter")

        self.boneCounterWin_mainLayout = QtWidgets.QVBoxLayout(self.boneCounterWin_mainWidget)
        self.boneCounterWin_mainLayout.setContentsMargins(0, 0, 0, 0)

        self.boneCounterWin.resize(300, 100)
        self.boneCounterWin.setSizePolicy(mainSizePolicy)
        self.boneCounterWin.setMinimumSize(QtCore.QSize(300, 100))
        self.boneCounterWin.setMaximumSize(QtCore.QSize(300, 100))

        headerFont = QtGui.QFont()
        headerFont.setPointSize(8)
        headerFont.setBold(True)

        # load toolbar stylesheet
        f.close()

        # create the bone counter stylesheet
        self.progBarStyle = """
        QProgressBar{
            border: 2px solid black;
            border-radius: 5px;
            text-align: center;
            font: 87 10pt "Arial";
            color: rgb(255,255,255);
        }

        QProgressBar::chunk {
            background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(25,175,255), stop:1 rgb(9,62,98));
            width: 10px;
            margin: 0.5px;
        }
        """

        self.progBarStyleMax = """
            font: 87 10pt "Arial Black";
        }

            background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(255,174,0), stop:1 rgb(30,30,30));
        }
        """

        self.boneCounterWin_frame = QtWidgets.QFrame()
        self.boneCounterWin_mainLayout.addWidget(self.boneCounterWin_frame)
        self.boneCounterWin_frame.setObjectName("mid")
        self.boneCounterWin.setStyleSheet(self.style)

        self.boneCounterWin_widgetLayoutMain = QtWidgets.QVBoxLayout(self.boneCounterWin_frame)
        self.boneCounterWin_widgetLayoutMain.setContentsMargins(5, 5, 5, 5)
        self.boneCounterWin_widgetLayout = QtWidgets.QHBoxLayout()
        self.boneCounterWin_widgetLayoutMain.addLayout(self.boneCounterWin_widgetLayout)

        # label creation
        self.boneCount = QtWidgets.QLabel("Bone Count:      ")
        self.boneCount.setFont(headerFont)
        self.boneCounterWin_widgetLayout.addWidget(self.boneCount)

        self.boneCounter = QtWidgets.QSpinBox()
        self.boneCounter.setReadOnly(True)
        self.boneCounter.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        self.boneCounter.setRange(1, 9999)
        self.boneCounter.setMinimumWidth(60)
        self.boneCounter.setMaximumWidth(60)
        self.boneCounterWin_widgetLayout.addWidget(self.boneCounter)

        # create the button
        self.boneMaxButton = QtWidgets.QPushButton("Set Target")
        self.boneCounterWin_widgetLayout.addWidget(self.boneMaxButton)
        self.boneMaxButton.clicked.connect(partial(self.setBoneCountTarget))
        self.boneMaxButton.setMinimumHeight(30)
        self.boneMaxButton.setStyleSheet(self.style)
        self.boneMaxButton.setObjectName("blueButton")

        # add the progress bar
        self.boneCountBar = QtWidgets.QProgressBar()
        self.boneCounterWin_widgetLayoutMain.addWidget(self.boneCountBar)
        self.boneCountBar.setValue(1)
        self.boneCountBar.setStyleSheet(self.progBarStyle)
        self.boneCountBar.setRange(0, 100)
        self.boneCountBar.setFormat("target = %m")

        # add the max range of the progress bar to the main network node
        if cmds.objExists("ART_Root_Module.target"):
            value = cmds.getAttr("ART_Root_Module.target")
            currentValue = self.boneCountBar.value()
            self.boneCountBar.setMaximum(value)

            if value < currentValue:
                self.boneCountBar.setValue(value)

            if currentValue <= value:
                self.boneCountBar.setStyleSheet(self.progBarStyle)
            if currentValue > value:
                self.boneCountBar.setStyleSheet(self.progBarStyleMax)

        else:
            cmds.addAttr("ART_Root_Module", sn="target", keyable=False)
            cmds.setAttr("ART_Root_Module.target", 100, lock=True)

        # get the current bone count
        allBones = []
            joints = cmds.getAttr(module + ".Created_Bones")
            splitJoints = joints.split("::")

            for bone in splitJoints:
                if bone != "":
                    allBones.append(bone)

        # update the spinBox and progress bar
        self.boneCounter.setValue(len(allBones))
        max = self.boneCountBar.maximum()

        if len(allBones) <= max:
            self.boneCountBar.setValue(len(allBones))
            self.boneCountBar.setStyleSheet(self.progBarStyle)
        if len(allBones) > max:
            self.boneCountBar.setValue(max)
            self.boneCountBar.setStyleSheet(self.progBarStyleMax)

        self.boneCounterWin.show()

    def setBoneCountTarget(self):
        """
        Builds a UI that allows the user to set the target bone count.

            .. image:: /images/boneCountTarget.png

        """

        self.targetWindow = QtWidgets.QMainWindow()

        self.targetWindow_mainWidget = QtWidgets.QWidget()
        self.targetWindow.setCentralWidget(self.targetWindow_mainWidget)

        self.targetWindow.setObjectName("ART_setBoneCountTarget_UI")
        self.targetWindow.setWindowTitle("Bone Count")

        self.targetWindow_topLayout = QtWidgets.QVBoxLayout(self.targetWindow_mainWidget)
        self.targetWindow_mainLayout = QtWidgets.QFormLayout()
        self.targetWindow_topLayout.addLayout(self.targetWindow_mainLayout)

        self.targetWindow_topLayout.setContentsMargins(10, 10, 10, 10)
        self.targetWindow.resize(250, 70)
        self.targetWindow.setSizePolicy(mainSizePolicy)
        self.targetWindow.setMinimumSize(QtCore.QSize(250, 70))
        self.targetWindow.setMaximumSize(QtCore.QSize(250, 70))

        # add label
        label = QtWidgets.QLabel("Enter a target bone count:        ")
        self.targetWindow_mainLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, label)

        # add spinBox
        self.targetWindow_SpinBox = QtWidgets.QSpinBox()
        self.targetWindow_SpinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        self.targetWindow_SpinBox.setRange(1, 9999)
        self.targetWindow_SpinBox.setMinimumWidth(70)
        self.targetWindow_SpinBox.setStyleSheet("background-color: rgb(255,255,255); color: rgb(0,0,0);")
        self.targetWindow_mainLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.targetWindow_SpinBox)
        self.targetWindow_SpinBox.setValue(100)
        self.targetWindow_SpinBox.setObjectName("ART_targetWindowSpinBox")

        # add a confirm button
        self.targetWindow_confirmButton = QtWidgets.QPushButton("Confirm")
        self.targetWindow_topLayout.addWidget(self.targetWindow_confirmButton)

        buttonImage = utils.returnNicePath(self.iconsPath, "System/blue_field_background.png")
        self.targetWindow_confirmButton.setStyleSheet(
            "background-image: url(" + buttonImage + "); background-color: rgb(0,0,0);")
        self.targetWindow_confirmButton.clicked.connect(self.setBoneCountTarget_Confirm)

        self.targetWindow.show()

    def setBoneCountTarget_Confirm(self):
        """
        Take the QSpinBox value for the new target and refresh the main UI with the new data.

        """

        newMax = self.targetWindow_SpinBox.value()
        cmds.deleteUI("ART_setBoneCountTarget_UI", wnd=True)

        value = self.boneCounter.value()
        currentValue = self.boneCountBar.value()
        self.boneCountBar.setMaximum(newMax)

        cmds.setAttr("ART_Root_Module.target", lock=False)
        cmds.setAttr("ART_Root_Module.target", newMax, lock=True)

        if newMax < currentValue:

        if value <= max:
        if value > max:

    def updateBoneCount(self):
        """
        Updates the interface based on new module information. Usually triggered when a module has had its settings
        changed.

        """

        try:
            modules = utils.returnRigModules()
            allBones = []
            for module in modules:
                joints = cmds.getAttr(module + ".Created_Bones")
                splitJoints = joints.split("::")

                for bone in splitJoints:
                    if bone != "":
                        allBones.append(bone)

            # update the spinBox and progress bar
            self.boneCounter.setValue(len(allBones))
            max = self.boneCountBar.maximum()

            if len(allBones) <= max:
                self.boneCountBar.setValue(len(allBones))
            if len(allBones) > max:
                self.boneCountBar.setValue(max)
        except:
            pass

"""
"""

import json
import os

# maya 2016< maya2017> compatability
try:
    import shiboken as shiboken
except:
    import shiboken2 as shiboken

_instances = []

def getMainWindow():
    """
    Get Maya's window as a QWidget and return the item in memory.

    :return: a QWidget of Maya's window

    """

    # get maya's window as QWidget
    import maya.OpenMayaUI as mui
    pointer = mui.MQtUtil.mainWindow()
    # pyside QMainWindow takes in a QWidget rather than QObject
    return shiboken.wrapInstance(long(pointer), QtWidgets.QWidget)

class ART_AnimationUI(QtWidgets.QMainWindow):
    """
    This class creates the main animation interface that houses the control picker and the buttons for the animation
    tools. This is the main interface that animators will interact with.

        .. image:: /images/animationUI.png

    """

    def __init__(self, parent=None):
        """
        Instantiates the class, getting the QSettings, writing the stylesheets, and calling on the method to build
        the interface.

        """

        _instances.append(self)

        super(ART_AnimationUI, self).__init__(parent)

        self.center = QtCore.QPointF(210.000000, 287.500000)
        self.scale = 1.0
        self.selectionScriptJobs = []

        self.closeEvent = self.closeEvent

        # write out qss based on user settings
        stylesheetDir = utils.returnNicePath(self.scriptPath, "Interfaces/StyleSheets/")
        stylesheets = os.listdir(stylesheetDir)

        for sheet in stylesheets:
            interfaceUtils.writeQSS(os.path.join(stylesheetDir, sheet))

        """
        Builds the animation UI that houses the area for control pickers and a sidebar for animation tools.

        """

        self.setCentralWidget(self.mainWidget)

        f.close()

        self.setStyleSheet(self.style)

        self.setMinimumSize(QtCore.QSize(550, 400))
        self.setMaximumSize(QtCore.QSize(550, 750))
        self.resize(550, 750)

        self.setObjectName("pyART_AnimTools_Win")
        self.setWindowTitle("Animation Tools")

        # #create the menu bar
        self.menuBar = QtWidgets.QMenuBar()
        self.menuBar.setMaximumHeight(20)
        self.layout.addWidget(self.menuBar)

        # #create the picker toolbar layout
        self.toolFrame = QtWidgets.QFrame()
        self.toolFrame.setObjectName("dark")
        self.toolFrame.setMinimumHeight(50)
        self.toolFrame.setMaximumHeight(50)

        self.layout.addWidget(self.toolFrame)
        self.toolbarLayout = QtWidgets.QHBoxLayout(self.toolFrame)
        self.toolbarLayout.setDirection(QtWidgets.QBoxLayout.RightToLeft)
        self.toolbarLayout.addStretch(0)
        self.toolbarLayout.setSpacing(4)

        # #picker toolbar buttons

        self.editPickerBtn = QtWidgets.QPushButton("Edit")
        self.editPickerBtn.setMinimumSize(QtCore.QSize(90, 30))
        self.editPickerBtn.setMaximumSize(QtCore.QSize(90, 30))
        self.toolbarLayout.addWidget(self.editPickerBtn)
        self.editPickerBtn.setEnabled(False)
        self.editPickerBtn.setObjectName("blueButton")
        self.editPickerBtn.clicked.connect(self.editPicker)
        self.editPickerBtn.setToolTip("Make module pickers editable (move, scale, rotate)")

        self.createPickerBtn = QtWidgets.QPushButton("Create")
        self.createPickerBtn.setMinimumSize(QtCore.QSize(90, 30))
        self.createPickerBtn.setMaximumSize(QtCore.QSize(90, 30))
        self.createPickerBtn.setObjectName("blueButton")
        self.createPickerBtn.setToolTip("Create a new picker for the given character.")

        self.toolbarLayout.addWidget(self.createPickerBtn)
        self.createPickerBtn.clicked.connect(self.createNewPicker)

        self.removeModBtn = QtWidgets.QPushButton()
        self.removeModBtn.setMinimumSize(QtCore.QSize(30, 30))
        self.removeModBtn.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/removeModule.png"))
        self.removeModBtn.setIconSize(QtCore.QSize(30, 30))
        self.removeModBtn.setIcon(icon)
        self.toolbarLayout.addWidget(self.removeModBtn)
        self.removeModBtn.setEnabled(False)
        self.removeModBtn.setObjectName("toolbar")
        self.removeModBtn.clicked.connect(partial(self.removeModuleFromPickerUI))
        self.removeModBtn.setToolTip("Remove a module's picker from the canvas.")

        self.moveModBtn = QtWidgets.QPushButton()
        self.moveModBtn.setMinimumSize(QtCore.QSize(30, 30))
        self.moveModBtn.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/movePicker.png"))
        self.moveModBtn.setIconSize(QtCore.QSize(30, 30))
        self.moveModBtn.setIcon(icon)
        self.toolbarLayout.addWidget(self.moveModBtn)
        self.moveModBtn.setEnabled(False)
        self.moveModBtn.setObjectName("toolbar")
        self.moveModBtn.clicked.connect(partial(self.movePickerToTab))
        self.moveModBtn.setToolTip("Move a picker from one tab to another.")

        self.moduleListBtn = QtWidgets.QPushButton()
        self.moduleListBtn.setMinimumSize(QtCore.QSize(30, 30))
        self.moduleListBtn.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/moduleList.png"))
        self.moduleListBtn.setIconSize(QtCore.QSize(30, 30))
        self.moduleListBtn.setIcon(icon)
        self.toolbarLayout.addWidget(self.moduleListBtn)
        self.moduleListBtn.setEnabled(False)
        self.moduleListBtn.setObjectName("toolbar")
        self.moduleListBtn.clicked.connect(partial(self.addModuleToPickerUI))
        self.moduleListBtn.setToolTip("Add a module's picker to the current canvas.")

        # background image button
        self.backgroundImgBtn = QtWidgets.QPushButton()
        self.backgroundImgBtn.setMinimumSize(QtCore.QSize(30, 30))
        self.backgroundImgBtn.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/background.png"))
        self.backgroundImgBtn.setIconSize(QtCore.QSize(30, 30))
        self.backgroundImgBtn.setIcon(icon)
        self.toolbarLayout.addWidget(self.backgroundImgBtn)
        self.backgroundImgBtn.setEnabled(False)
        self.backgroundImgBtn.setObjectName("toolbar")
        self.backgroundImgBtn.setToolTip("Change the background of the current picker tab.")
        self.backgroundImgBtn.clicked.connect(partial(self.changeBackground))

        self.toolbarLayout.addSpacerItem(QtWidgets.QSpacerItem(10, 0))

        buttonGrp = QtWidgets.QButtonGroup(self.toolbarLayout)
        buttonGrp.setExclusive(True)

        self.normalSelectButton = QtWidgets.QPushButton()
        self.normalSelectButton.setMinimumSize(QtCore.QSize(30, 30))
        self.normalSelectButton.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/normalSelect_On.png"))
        self.normalSelectButton.setIconSize(QtCore.QSize(30, 30))
        self.normalSelectButton.setIcon(icon)
        self.toolbarLayout.addWidget(self.normalSelectButton)
        self.normalSelectButton.setCheckable(True)
        self.normalSelectButton.setObjectName("toolbar")
        self.normalSelectButton.setChecked(True)
        self.normalSelectButton.setToolTip("Change picker selection mode to normal selection")
        self.normalSelectButton.clicked.connect(self.toggleDragState)

        self.dragSelectButton = QtWidgets.QPushButton()
        self.dragSelectButton.setMinimumSize(QtCore.QSize(30, 30))
        self.dragSelectButton.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/dragSelect.png"))
        self.dragSelectButton.setIconSize(QtCore.QSize(30, 30))
        self.dragSelectButton.setIcon(icon)
        self.toolbarLayout.addWidget(self.dragSelectButton)
        self.dragSelectButton.setCheckable(True)
        self.dragSelectButton.setObjectName("toolbar")
        self.dragSelectButton.setToolTip("Change picker selection mode to drag selection")
        self.dragSelectButton.clicked.connect(self.toggleDragState)

        self.commentButton = QtWidgets.QPushButton()
        self.commentButton.setMinimumSize(QtCore.QSize(30, 30))
        self.commentButton.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/commentMode.png"))
        self.commentButton.setIconSize(QtCore.QSize(30, 30))
        self.commentButton.setIcon(icon)
        self.toolbarLayout.addWidget(self.commentButton)
        self.commentButton.setCheckable(True)
        self.commentButton.setObjectName("toolbar")
        self.commentButton.setToolTip("Add Comment Box Mode")
        self.commentButton.clicked.connect(self.toggleDragState)

        buttonGrp.addButton(self.normalSelectButton)
        buttonGrp.addButton(self.dragSelectButton)
        buttonGrp.addButton(self.commentButton)

        # self.toolbarLayout.addSpacerItem(QtWidgets.QSpacerItem(10, 0))

        self.saveTemplateBtn = QtWidgets.QPushButton()
        self.saveTemplateBtn.setMinimumSize(QtCore.QSize(30, 30))
        self.saveTemplateBtn.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/save.png"))
        self.saveTemplateBtn.setIconSize(QtCore.QSize(30, 30))
        self.saveTemplateBtn.setIcon(icon)
        self.toolbarLayout.addWidget(self.saveTemplateBtn)
        self.saveTemplateBtn.setObjectName("toolbar")
        self.saveTemplateBtn.clicked.connect(self.savePicker)
        self.saveTemplateBtn.setToolTip("Save a picker file")

        self.loadTemplateBtn = QtWidgets.QPushButton()
        self.loadTemplateBtn.setMinimumSize(QtCore.QSize(30, 30))
        self.loadTemplateBtn.setMaximumSize(QtCore.QSize(30, 30))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/load.png"))
        self.loadTemplateBtn.setIconSize(QtCore.QSize(30, 30))
        self.loadTemplateBtn.setIcon(icon)
        self.toolbarLayout.addWidget(self.loadTemplateBtn)
        self.loadTemplateBtn.setObjectName("toolbar")
        self.loadTemplateBtn.clicked.connect(self.loadPicker)
        self.loadTemplateBtn.setToolTip("Load a picker file")

        # #create the horizontal layout, with the left column giving us the picker canvas area,
        # and the right a toolbar for animation tools
        self.mainLayout = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.mainLayout)

        self.mainLeftColumnLayout = QtWidgets.QVBoxLayout()
        self.mainLayout.addLayout(self.mainLeftColumnLayout)

        self.mainRightColumnLayout = QtWidgets.QVBoxLayout()
        self.mainLayout.addLayout(self.mainRightColumnLayout)

        # #left column - picker canvas

        """
        the picker canvas will consist of a tab layout at the top, one per character in scene,
        and in each of those tabs, another tab layout to hold the pages of the picker
        """

        self.pickerMainWidget = QtWidgets.QFrame()
        self.pickerMainWidget.setObjectName("epic")
        self.mainLeftColumnLayout.addWidget(self.pickerMainWidget)

        self.pickerMainWidget.setMinimumSize(470, 700)
        self.pickerMainWidget.setMaximumSize(470, 700)

        self.characterTabs = QtWidgets.QTabWidget(self.pickerMainWidget)
        self.characterTabs.setObjectName("tabVariant")

        self.characterTabs.setMinimumSize(450, 670)
        self.characterTabs.setMaximumSize(450, 670)
        self.characterTabs.setIconSize(QtCore.QSize(75, 75))

        # look for characters in scene to create character tabs
        charactersFound = self.findCharacters()

        if not charactersFound:
            cmds.warning("No characters found in scene.")

        # #right column - toolbar

        self.animToolsFrame = QtWidgets.QFrame()
        self.animToolsFrame.setObjectName("epic2")
        self.mainRightColumnLayout.addWidget(self.animToolsFrame)
        self.animToolsFrame.setMinimumSize(80, 750)
        self.animToolsFrame.setMaximumSize(80, 750)

        self.animToolsLayout = QtWidgets.QVBoxLayout(self.animToolsFrame)

        self.animToolsLayout.addSpacerItem(QtWidgets.QSpacerItem(75, 75))

        # Select Controls
        self.selectCtrlBtn = QtWidgets.QPushButton()
        self.selectCtrlBtn.setMinimumSize(QtCore.QSize(65, 65))
        self.selectCtrlBtn.setMaximumSize(QtCore.QSize(65, 65))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/animSelect.png"))
        self.selectCtrlBtn.setIconSize(QtCore.QSize(65, 65))
        self.selectCtrlBtn.setIcon(icon)
        self.animToolsLayout.addWidget(self.selectCtrlBtn)
        self.selectCtrlBtn.setObjectName("toolbar")
        self.selectCtrlBtn.clicked.connect(self.selectAllCtrls)
        self.selectCtrlBtn.setToolTip("Select All Controls")

        # Export Motion

        self.exportMotionBtn = QtWidgets.QPushButton()
        self.exportMotionBtn.setMinimumSize(QtCore.QSize(65, 65))
        self.exportMotionBtn.setMaximumSize(QtCore.QSize(65, 65))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/export.png"))
        self.exportMotionBtn.setIconSize(QtCore.QSize(65, 65))
        self.exportMotionBtn.setIcon(icon)
        self.animToolsLayout.addWidget(self.exportMotionBtn)
        self.exportMotionBtn.setObjectName("toolbar")
        self.exportMotionBtn.clicked.connect(self.exportMotion)
        self.exportMotionBtn.setToolTip("Export Motion")

        # Import Motion
        self.importMotionBtn = QtWidgets.QPushButton()
        self.importMotionBtn.setMinimumSize(QtCore.QSize(65, 65))
        self.importMotionBtn.setMaximumSize(QtCore.QSize(65, 65))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/import.png"))
        self.importMotionBtn.setIconSize(QtCore.QSize(65, 65))
        self.importMotionBtn.setIcon(icon)
        self.animToolsLayout.addWidget(self.importMotionBtn)
        self.importMotionBtn.setObjectName("toolbar")
        self.importMotionBtn.clicked.connect(self.importMotion)
        self.importMotionBtn.setToolTip("Import Motion")

        # Match Over Frame Range

        movie = QtGui.QMovie(utils.returnNicePath(self.iconsPath, "System/matchRange.gif"))
        movie.setParent(self)
        self.matchRangeBtn = QtWidgets.QPushButton()
        self.matchRangeBtn.setMinimumSize(QtCore.QSize(65, 65))
        self.matchRangeBtn.setMaximumSize(QtCore.QSize(65, 65))
        self.matchRangeBtn.setIconSize(QtCore.QSize(65, 65))
        self.animToolsLayout.addWidget(self.matchRangeBtn)
        self.matchRangeBtn.setObjectName("toolbar")
        self.matchRangeBtn.clicked.connect(self.matchOverRange)
        self.matchRangeBtn.setToolTip("Match Over Frame Range")

        movie.frameChanged.connect(partial(self.setButtonIcon, movie))
        movie.start()

        if movie.loopCount() != -1:
            movie.finished.connect(movie.start())

        # Reset Module
        self.resetModuleBtn = QtWidgets.QPushButton()
        self.resetModuleBtn.setMinimumSize(QtCore.QSize(65, 65))
        self.resetModuleBtn.setMaximumSize(QtCore.QSize(65, 65))
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/animReset.png"))
        self.resetModuleBtn.setIconSize(QtCore.QSize(65, 65))
        self.resetModuleBtn.setIcon(icon)
        self.animToolsLayout.addWidget(self.resetModuleBtn)
        self.resetModuleBtn.setObjectName("toolbar")
        self.resetModuleBtn.clicked.connect(self.resetRigCtrls)
        self.resetModuleBtn.setToolTip("Reset modules tool")

        self.animToolsLayout.addSpacerItem(QtWidgets.QSpacerItem(75, 600))

    def setButtonIcon(self, movie):
        """
        Sets the icon of the matchRangeBtn to the next frame in the passed in movie. Note: This was a test function
        to see if animated gifs could be used as buttons.

        :param movie: The movie whose frame to change.

        """

        self.matchRangeBtn.setIcon(QtGui.QIcon(movie.currentPixmap()))

    def findCharacters(self):
        """
        Finds all assets in the scene built with ARTv2, and creates a picker tab for each one in the animation UI.

        """

        allNodes = cmds.ls(type="network")
        characterNodes = []
        for node in allNodes:
            attrs = cmds.listAttr(node)
            if "rigModules" in attrs:
                characterNodes.append(node)

        if len(characterNodes) == 0:
            return False

        else:
            # go through each node, find the character name, the namespace on the node, and the picker attribute
            for node in characterNodes:
                try:
                    namespace = cmds.getAttr(node + ".namespace")
                except:
                    namespace = cmds.getAttr(node + ".name")

                picker = False

                if cmds.objExists(node + ".pickerFile"):
                    pickerFile = cmds.getAttr(node + ".pickerFile")
                    pickerPath = utils.returnNicePath(self.toolsPath, "Core/Pickers/")

                    picker = True

                # create the tab for the given namespace
                characterWidget = QtWidgets.QWidget()
                characterWidget.setProperty("charNode", node)
                characterWidget.setMinimumSize(442, 720)
                characterWidget.setMaximumSize(442, 720)
                characterWidget.setProperty("namespace", namespace)

                # add the icon found on the node's icon path attribute to the tab
                iconPath = cmds.getAttr(node + ".iconPath")
                iconPath = utils.returnNicePath(self.projectPath, iconPath)
                icon = QtGui.QIcon(iconPath)
                self.characterTabs.addTab(characterWidget, icon, "")
                index = self.characterTabs.indexOf(characterWidget)
                self.characterTabs.setTabToolTip(index, namespace)

                # if a picker file existed, load it. Otherwise, display an image telling the user to either
                # load a file or create a picker
                frame = QtWidgets.QFrame(characterWidget)
                frame.setMinimumSize(442, 720)
                frame.setMaximumSize(442, 720)
                frame.setStyleSheet(
                    "background-image: url(" + utils.returnNicePath(self.iconsPath, "System/noPicker.png") + ");")

                layout = QtWidgets.QVBoxLayout(frame)
                layout.setContentsMargins(20, 150, 20, 241)

                # add help gif
                movie_screen = QtWidgets.QLabel(frame)
                movie_screen.setStyleSheet("background: transparent;")
                layout.addWidget(movie_screen)
                moviePath = utils.returnNicePath(self.iconsPath, "Help/createPicker.gif")
                movie = QtGui.QMovie(moviePath, QtCore.QByteArray())
                movie.setCacheMode(QtGui.QMovie.CacheAll)
                movie.setSpeed(100)
                movie_screen.setMovie(movie)

                movie.start()

                if picker:
                    self.characterTabs.setCurrentIndex(index)
                    self.loadPicker(utils.returnNicePath(pickerPath, pickerFile))

            return True

    def createNewPicker(self, bypass=False):
        """
        Create a new control picker for the given character tab. This will remove the help gif and replace it with a
        blank canvas that module pickers can be added to (using ART_AddModuleToCanvas).

        :param bypass:  Whether or not to bypass the QMessageBox confirming the creation of a new picker.
                        This is used when loading a picker from file. bypass will be set to True.

        :return: returns the QTabWidget for this character's picker.

        .. seealso:: ART_AddModuleToCanvas

        """

        index = self.characterTabs.currentIndex()
        widget = self.characterTabs.widget(index)
        closeWidget = widget.children()

        # get the tab text
        character = self.characterTabs.tabToolTip(index)

        if not bypass:
            # display a message confirming new picker creation for given character
            msgBox = QtWidgets.QMessageBox()
            msgBox.setText("Create a New Picker for " + str(character) + " ?")
            msgBox.setIcon(QtWidgets.QMessageBox.Question)
            msgBox.addButton("Yes", QtWidgets.QMessageBox.YesRole)
            msgBox.addButton("Cancel", QtWidgets.QMessageBox.NoRole)
            ret = msgBox.exec_()

            # if the user selected "Yes", delete the old widget
            if ret == 0:
                for each in closeWidget:
                    each.setParent(None)
                    each.close()
                    self.update()

            if ret == 1:
                return

        else:
            for each in closeWidget:
                each.setParent(None)
                each.close()
                self.update()

        # now add a new tab widget to our character tabs, with a new tab named "Main"
        pickerTabs = QtWidgets.QTabWidget(widget)
        pickerTabs.currentChanged.connect(self.pickerTabChange)

        pickerTabs.setMinimumSize(444, 632)
        pickerTabs.setMaximumSize(444, 632)

        # add 'add tab' button
        addTabBtn = QtWidgets.QPushButton("Add Tab")
        addTabBtn.setObjectName("blueButton")
        addTabBtn.setToolTip("Add Picker Tab")
        addTabBtn.clicked.connect(partial(self.addTab, pickerTabs))
        pickerTabs.setCornerWidget(addTabBtn, QtCore.Qt.TopRightCorner)

        # style sheet
        stylesheet = """
        QTabBar::tab
        {
            background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(19,132,183), stop:1 rgb(30,30,30));
            width: 100px;
            padding-left: -10px;
        }
        QTabBar::tab:selected
        {
            background-color: rgb(14,100,143);
        }
        QTabBar::tab:hover
        {
            background: rgb(19,132,183);
        }
        QTabBar::tab:!selected
        {
            margin-top: 5px;
        }
        QTabWidget::pane
        {
            border-top: 2px solid rgb(19,132,183);
        }
        """

        # create the qWidget for this tab of the picker
        pickerCanvas = QtWidgets.QWidget()
        pickerCanvas.setMinimumSize(442, 600)
        pickerCanvas.setMaximumSize(442, 600)
        pickerTabs.addTab(pickerCanvas, "Main")
        pickerTabs.setStyleSheet(stylesheet)

        # create the layout for this page
        pageLayout = QtWidgets.QVBoxLayout(pickerCanvas)

        # create the graphicsView
        gfxView = QtWidgets.QGraphicsView()
        pageLayout.addWidget(gfxView)

        # create the qgraphicsScene
        gfxScene = QtWidgets.QGraphicsScene()
        gfxScene.setSceneRect(0, 0, 420, 575)
        gfxView.setScene(gfxScene)
        gfxView.setDragMode(QtWidgets.QGraphicsView.NoDrag)

        # set background image
        pixmap = QtGui.QPixmap(utils.returnNicePath(self.iconsPath, "System/canvas.png"))
        gfxItem = gfxScene.addPixmap(pixmap)
        gfxItem.setZValue(-20)

        # show the picker tabs
        pickerTabs.show()

        # enable module list button
        self.backgroundImgBtn.setEnabled(True)
        self.moduleListBtn.setEnabled(True)
        self.removeModBtn.setEnabled(True)
        self.moveModBtn.setEnabled(True)
        self.normalSelectButton.setEnabled(False)
        self.dragSelectButton.setEnabled(False)

            self.addModuleToPickerUI()

        # =======================================================
        # #enable edit button
        self.editPickerBtn.setEnabled(True)

        return pickerTabs

    def toggleDragState(self):
        """
        Toggles selection interaction modes within the QGraphicsScene. The three different modes are normal select,
        drag select, and comment box mode. This will set the dragMode of the QGraphicsView to the currently selected
        state, along with switching icons to show selection status.

        """

        views = []

                tab = child

                            views.append(view)

        for view in views:
            view.mousePressEvent = partial(self.gfxViewMousePress, view)
            view.mouseMoveEvent = partial(self.gfxViewMouseMove, view)
            view.mouseReleaseEvent = partial(self.gfxViewMouseRelease, view)

        # normal singular select mode
        if self.normalSelectButton.isChecked():
            for view in views:
                view.setDragMode(QtWidgets.QGraphicsView.NoDrag)

            icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/normalSelect_On.png"))
            self.normalSelectButton.setIconSize(QtCore.QSize(30, 30))
            self.normalSelectButton.setIcon(icon)

            icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/dragSelect.png"))
            self.dragSelectButton.setIconSize(QtCore.QSize(30, 30))
            self.dragSelectButton.setIcon(icon)

            icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/commentMode.png"))
            self.commentButton.setIconSize(QtCore.QSize(30, 30))
            self.commentButton.setIcon(icon)

        # drag select mode
        if self.dragSelectButton.isChecked():
                view.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)

            icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/normalSelect.png"))

            icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/dragSelect_On.png"))

        # create UE4-style comment box mode
        if self.commentButton.isChecked():

            icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/commentMode_on.png"))

    def gfxViewMousePress(self, view, event):
        """
        Override event that captures a mouse press when in the passed in QGraphicsView and displays the QRubberBand
        if in drag select or comment box mode.

        :param view: the QGraphicsView to detect mouse press events in.

        """

            self.origin = event.pos()
            self.rubberband = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, view)
            self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
            self.rubberband.show()

        QtWidgets.QGraphicsView.mousePressEvent(view, event)

    def gfxViewMouseMove(self, view, event):
        """
        Override event that captures a mouse move when in the passed in QGraphicsView and changes the
        displayed size of the QRubberBand based on the origin position and the current position, drawing a QRect (if
        in drag select or comment box mode.). It also finds any items inside of that QRect (picker buttons, etc).

        :param view: the QGraphicsView to detect mouse move events in and check for items in.

        """

            try:
                self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
                self.itemsInRect = view.items(QtCore.QRect(self.origin, event.pos()).normalized())
            except Exception, e:
                pass

            try:
                pass
        QtWidgets.QGraphicsView.mouseMoveEvent(view, event)

    def gfxViewMouseRelease(self, view, event):
        """
        Override event that captures a mouse release when in the passed in QGraphicsView and hides the QRubberBand
        if it was visible. If in comment box mode, this release event will also create the comment box with the
        dimensions and position of the start point of the mouse press, and the QRect from the mouse move.

        :param view: the QGraphicsView to detect mouse release events and to add comment boxes to.

        """

            self.rubberband.hide()
            try:
                for item in self.itemsInRect:
                    try:
                        if item.classType == "pickerButton":
                            item.mousePressEventCustom(QtGui.QGraphicsSceneMouseEvent())

                    except:
                        pass
            except:
                pass

            geo = self.rubberband.geometry()
            scene = view.scene()
            box = interfaceUtils.commentBoxItem(geo.x(), geo.y(), geo.width(), geo.height(), scene, view, self)
            scene.addItem(box)
            box.setZValue(0)

            self.normalSelectButton.setChecked(True)
            self.toggleDragState()

        QtWidgets.QGraphicsView.mouseReleaseEvent(view, event)

    def addTab(self, tabWidget, bypass=False, tabName=None):
        """
        Adds a tab to the QTabWidget for a given character to add more module control pickers to. These are sometimes
        referred to as "canvases".

        :param tabWidget: The QTabWidget to add a tab to.
        :param bypass: If not creating the "Main" tab or loading a picker from file, a tab name must be entered.
        :param tabName: If creating the "Main" tab or loading a picker from file, the name given to the tab that
                        will be created.

        :return: Index of the created tab in the QTabWidget.
        """

            tabName, ok = QtWidgets.QInputDialog.getText(self, "Tab Name", "Enter Tab Name:")
        else:
            ok = True
            tabName = tabName
        if ok:
            if tabName != "":
                # create the qWidget for this tab of the picker
                pickerCanvas = QtWidgets.QWidget()
                pickerCanvas.setMinimumSize(442, 600)
                pickerCanvas.setMaximumSize(442, 600)
                tabWidget.addTab(pickerCanvas, str(tabName))
                index = tabWidget.indexOf(pickerCanvas)

                # create the layout for this page
                pageLayout = QtWidgets.QVBoxLayout(pickerCanvas)

                # create the graphicsView
                gfxView = QtWidgets.QGraphicsView()
                pageLayout.addWidget(gfxView)

                # create the qgraphicsScene
                gfxScene = QtWidgets.QGraphicsScene()
                gfxScene.setSceneRect(0, 0, 420, 575)
                gfxView.setScene(gfxScene)

                # set background image
                pixmap = QtGui.QPixmap(utils.returnNicePath(self.iconsPath, "System/canvas.png"))
                gfxScene.addPixmap(pixmap)

                return index

    def closeEvent(self, event):
        """
        Closes the interface.

        """

        for inst in _instances:

            if cmds.dockControl("pyART_animToolsDock", q=True, exists=True):
                inst.close()

            else:
                if cmds.window("pyART_AnimTools_Win", exists=True):
                    inst.close()

    def addModuleToPickerUI(self):
        """
        Creates an instance of ART_AddModuleToCanvas to bring up that tool, passing in modules valid to add.

        """

        modulesToAdd = self.comparePickerToRig(True, False)
        import ART_AddModuleToCanvas as am2c
        reload(am2c)
        am2c.ART_AddModuleToCanvas(self, modulesToAdd)

    def removeModuleFromPickerUI(self):
        """
        Creates an instance of the ART_RemoveModuleFromCanvas tool, passing in modules valid to remove.

        """

        modulesToAdd = self.comparePickerToRig(False, True)
        import ART_RemoveModuleFromCanvas as arfc
        reload(arfc)
        arfc.ART_RemoveModuleFromCanvas(self, modulesToAdd)

    def movePickerToTab(self):
        """
        Creates an instance of the ART_MovePickerToTabUI tool, passing in all valid module pickers that could be moved.

        """

        modulesToAdd = self.findAllPickerItems()
        import ART_MovePickerToTabUI as mp2t
        reload(mp2t)
        mp2t.ART_MovePickerToTab(self, modulesToAdd)

    def pickerTabChange(self):
        """
        Called on when a tab is changed on a given character's picker, if the remove modules from canvas UI is open,
        it will be closed.

        .. todo:: This function could eventually be changed to simply refresh that UI with the new information.

        """

            title = cmds.window("pyART_AddToCanvasWIN", q=True, title=True)
            if title == "Remove Module From Canvas":
                cmds.deleteUI("pyART_AddToCanvasWIN", wnd=True)

    def editPicker(self):
        """
        Makes all picker items editable again, making them selectable, movable, scalable, and rotatable.

        """

        data = self.getPickerTabs(False)
        comments = self.getComments()

        for each in data:
            item = each.get("item")
            item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
            item.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
            item.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True)
            item.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)

        for comment in comments:
            item = comment.get("item")

    def savePicker(self):
        """
        Save the picker data to a .picker file. The main picker data is actually gathered by self.getPickerTabs,
        self.getComments, and self.getButtonColors.

        .. seealso:: ART_AnimationUI.getPickerTabs, ART_AnimationUI.getComments, ART_AnimationUI.getButtonColors

        """

        # get info from current character tab
        pickerData = self.getPickerTabs(True)
        commentData = self.getComments(True)
        buttonData = self.getButtonColors()

        jsonData = {}
        jsonData["pickerData"] = pickerData
        jsonData["commentData"] = commentData
        jsonData["buttonData"] = buttonData

        # character name

        if not cmds.objExists(characterNode + ".pickerFile"):
            cmds.addAttr(characterNode, ln="pickerFile", dt="string")

        # ask for the file name to give the picker
        startingDir = utils.returnNicePath(self.toolsPath, "Core/Pickers")
        if not os.path.exists(startingDir):
            os.makedirs(startingDir)

        filename = cmds.fileDialog2(fm=0, okc="Save Picker", dir=startingDir, ff="*.picker")
        if filename is not None:
            # create the picker file
            f = open(filename[0], 'w')

            # dump the data with json
            json.dump(jsonData, f)
            f.close()

            # write picker file location to character node
            niceFileName = utils.returnFriendlyPath(filename[0])
            pickerFile = niceFileName.partition(utils.returnNicePath(self.toolsPath, "Core/Pickers/") + "/")[2]
            cmds.setAttr(characterNode + ":.pickerFile", pickerFile, type="string")

            # disable module list button
            self.backgroundImgBtn.setEnabled(False)
            self.moduleListBtn.setEnabled(False)
            self.removeModBtn.setEnabled(False)
            self.moveModBtn.setEnabled(False)
            self.normalSelectButton.setEnabled(True)
            self.dragSelectButton.setEnabled(True)

    def comparePickerToRig(self, unused, used):
        """
        Compares the modules that are on the picker to all modules that make up the character.

        :param unused: Whether or not we want a list returned of modules that are not on the picker yet.
        :param used: Whether or not we want a list returned of modules that are on the picker.
        :return: Returns a list of the modules based on either used or unused args.

        """

        jsonData = self.getPickerTabs(False)

        rigModules = cmds.listConnections(characterNode + ".rigModules")
        returnModules = []
        pickerModules = []

        for data in jsonData:
            pickerModules.append(data.get("module"))

        if unused:
            for module in rigModules:
                if module not in pickerModules:
                    returnModules.append(module)

        if used:
            returnModules = list(set(pickerModules))

        return returnModules

    def findAllPickerItems(self):
        """
        Finds all modules on the picker. Used mostly be the load picker function.
        :return: Returns a list of picker data that includes for each item, the module the picker is for, the item in
        memory, and the nice name.

        .. seealso:: ART_AnimationUI.loadPicker
        """

                tab = child

                                    niceName = item.data(2)
                                    returnData.append([module, item, niceName])

    def loadPicker(self, filename=None):
        """
        Loads a .picker file and builds the picker according to the file data.
        :param filename: The path of the picker file to gather data from.

        """

        if filename is None:
            startingDir = utils.returnNicePath(self.toolsPath, "Core/Pickers")
            if os.path.exists(startingDir):

                filename = cmds.fileDialog2(fm=1, okc="Load Picker", dir=startingDir, ff="*.picker")
                if filename is not None:
                    filename = filename[0]

        borderItems = []

        if os.path.exists(filename):
            # ===============================================================
            # #load the data
            json_file = open(filename)
            data = json.load(json_file)
            json_file.close()

            # #get character node
            tabIndex = self.characterTabs.currentIndex()
            characterWidget = self.characterTabs.widget(tabIndex)
            characterNode = characterWidget.property("charNode")
            characterNodeModules = cmds.listConnections(characterNode + ".rigModules")

            if cmds.objExists(characterNode + ".namespace"):
                namespace = cmds.getAttr(characterNode + ".namespace") + ":"
            else:
                namespace = cmds.getAttr(characterNode + ".name")

            # create new picker
            tabWidget = self.createNewPicker(True)

            # #create all tabs
            existingTabs = []
            subPickers = []
            for i in range(tabWidget.count()):
                tabName = tabWidget.tabText(i)
                existingTabs.append(tabName)

            # #go through data
            pickerData = data["pickerData"]

            for item in pickerData:
                module = item.get("module")
                moduleNiceName = item.get("module")
                tab = item.get("tab")
                transforms = item.get("transforms")
                subPicker = item.get("subPicker")
                mirrored = item.get("mirrored")
                path = item.get("path")

                if module is not None:
                    # if there is a namespace (if the add character for animation tool was used):
                    if namespace + module in characterNodeModules:
                        module = namespace + module
                    else:
                        namespace = ""

                if subPicker is True:
                    subPickers.append([module, tab, transforms, mirrored, moduleNiceName])

                if tab not in existingTabs:
                    self.addTab(tabWidget, True, tab)
                    existingTabs.append(tab)

                # ===========================================================
                # #add the picker to the correct tab
                buildPicker = False
                tabChildren = tabWidget.children()

                # get a list of the tabs with their names and index
                tabInfo = {}
                for i in range(len(tabChildren)):
                    tabName = tabWidget.tabText(i)
                    tabInfo[tabName] = i

                # now find a matching entry in that list to our tab variable
                index = tabInfo.get(tab)
                if index is None:
                    index = 0
                # set out tab widget to that index
                tabWidget.setCurrentIndex(index)
                if subPicker is False:
                    buildPicker = True

                # #now that the tab is correct, build the picker for the module
                if buildPicker:
                    scene = self.getCurrentScene()

                    try:
                        # create picker for the given module
                        inst = self.getModuleInst(module)
                        pickerData = inst.pickerUI(self.center, self, module, namespace)
                        picker = pickerData[0]
                        scene.addItem(picker)
                        self.selectionScriptJobs.append(pickerData[2])
                        borderItems.append([picker, moduleNiceName, module])

                        # mirror the module's pickerBorderItem if needed
                        if pickerData[1] == True:
                            picker.setTransformOriginPoint(picker.boundingRect().center())
                            picker.setTransform(
                                QtGui.QTransform(-1.0, 0.0, 0.0, 1.0, picker.boundingRect().width() * 2, 0.0))

                            children = picker.childItems()
                            if children is not None:
                                self.mirrorChildTextItems(children)

                        # set transformations on the picker
                        picker.setTransformOriginPoint(picker.boundingRect().center())
                        picker.setScale(transforms[2])

                        # when loading a picker, set data(1) to be the scale factor that was possibly saved out.
                        picker.setData(1, transforms[2])

                        picker.setRotation(transforms[1])

                        picker.setPos(QtCore.QPointF(transforms[0][0], transforms[0][1]))

                        picker.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
                        picker.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, False)
                        picker.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)
                        picker.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, False)

                        # =======================================================
                        # #enable edit button
                        self.editPickerBtn.setEnabled(True)

                        # #disable canvas, move, and remove buttons
                        self.backgroundImgBtn.setEnabled(False)
                        self.moduleListBtn.setEnabled(False)
                        self.removeModBtn.setEnabled(False)
                        self.moveModBtn.setEnabled(False)
                        self.normalSelectButton.setEnabled(True)
                        self.dragSelectButton.setEnabled(True)

                    except Exception, e:
                        cmds.warning(str(e))

                try:
                    if path is not None:

                        widget = tabWidget.widget(index)
                        widgetChildren = widget.children()
                        for child in widgetChildren:
                            if type(child) == QtWidgets.QGraphicsView:
                                gfxView = child

                                if os.path.exists(utils.returnFriendlyPath(path)):

                                    # set background image
                                    pixmap = QtGui.QPixmap()
                                    pixmap.load(utils.returnFriendlyPath(path))
                                    scene = gfxView.scene()
                                    gfxItem = scene.addPixmap(pixmap)
                                    gfxItem.setZValue(-10)
                                    scene.setProperty("customImg", gfxItem)
                                    scene.setProperty("filePath", filename)
                                else:
                                    cmds.warning("Tried to load custom background image: " + str(
                                        path) + ", but file not found on disk.")

                except Exception, e:
                    print e

            # #Handle Sub-Pickers
            if len(subPickers) > 0:
                for info in subPickers:

                    module = info[4]
                    tab = info[1]
                    transforms = info[2]
                    mirrored = info[3]
                    pickerItems = self.findAllPickerItems()

                    # each item in pickerItems has module, item, niceName. use niceName to see if we have a winner

                    for each in pickerItems:
                        pickerModule = each[0]
                        pickerItem = each[1]
                        pickerNiceName = each[2]

                        if pickerModule == module:
                            if pickerNiceName is not None:
                                # now find a matching entry in that list to our tab variable
                                index = tabInfo.get(tab)

                                # set our tab widget to that index
                                tabWidget.setCurrentIndex(index)

                                # find current scene
                                scene = self.getCurrentScene()
                                scene.addItem(pickerItem)

                                # set transformations on the picker
                                pickerItem.setTransformOriginPoint(pickerItem.boundingRect().center())
                                pickerItem.setScale(transforms[2])

                                # when loading a picker, set data(1) to be the scale factor that was possibly saved out.
                                pickerItem.setData(1, transforms[2])

                                pickerItem.setRotation(transforms[1])

                                pickerItem.setPos(QtCore.QPointF(transforms[0][0], transforms[0][1]))

                                if mirrored == -1:
                                    pickerItem.setTransformOriginPoint(pickerItem.boundingRect().center())
                                    pickerItem.setTransform(
                                        QtGui.QTransform(-1.0, 0.0, 0.0, 1.0, scene.sceneRect().width(), 0.0))

                                # lock down pickers
                                pickerItem.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
                                pickerItem.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, False)
                                pickerItem.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)
                                pickerItem.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, False)

            # #Add Comment data
            commentData = data["commentData"]

            for item in commentData:
                rect = item.get("rect")
                color = item.get("color")
                label = item.get("label")

                    index = 0

                view = self.getCurrentView()
                scene = self.getCurrentScene()

                box = interfaceUtils.commentBoxItem(rect[0], rect[1], rect[2], rect[3], scene, view, self)
                box.brush.setColor(QtGui.QColor.fromRgb(color[0], color[1], color[2], color[3]))
                box.textLabel.setPlainText(label)
                scene.addItem(box)

            # #Find button color data
            try:
                buttonData = data["buttonData"]

                for border in borderItems:
                    pickerModule = border[1]
                    picker = border[0]
                    networkNode = border[2]

                    for button in buttonData:
                        module = button.get("module")
                        color = button.get("color")
                        control = button.get("control")

                            # then we have the correct border item parent and now need to get its button
                            childItems = picker.childItems()
                            for child in childItems:
                                if type(child) == interfaceUtils.pickerButton:

                                    # delete the existing scriptJob
                                    scriptJob = picker.data(5)
                                    cmds.scriptJob(kill=scriptJob)
                                    self.selectionScriptJobs.remove(scriptJob)

                                    # set the button color
                                    child.brush.setColor(QtGui.QColor.fromRgb(color[0], color[1], color[2], color[3]))

                                    inst = self.getModuleInst(networkNode)

                                    # create the new scriptJob
                                    newColor = QtGui.QColor.fromRgb(color[0], color[1], color[2], color[3])
                                    scriptJob = cmds.scriptJob(event=["SelectionChanged",
                                                                      partial(inst.selectionScriptJob_animUI,
                                                                              [[child, control, newColor]])],
                                                               kws=True)
                                    picker.setData(5, scriptJob)
                                    self.selectionScriptJobs.append(scriptJob)

                print e

            # #write picker file location to character node
            if not cmds.objExists(characterNode + ".pickerFile"):
                cmds.addAttr(characterNode, ln="pickerFile", dt="string")

            niceFileName = utils.returnFriendlyPath(filename)
            cmds.setAttr(characterNode + ".pickerFile", pickerFile, type="string")

    def mirrorChildTextItems(self, children):
        """
        Mirrors any QGraphicsSimpleTextItems text if the parent picker was mirrored, so that the text is correct.
        :param children: List of child items (QGraphicsSimpleTextItems) of a pickerBorderItem or a pickerButton.

        """

        # for mirroring text on any child items of a pickerBorderItem or a pickerButton

                self.mirrorChildTextItems(children)

    def getCurrentScene(self):
        """
        Gets the QGraphicsScene of the current QGraphicsView, which is gotten by calling on self.getCurrentView.

        :return: Returns the QGraphicsScene under the current QGraphicsView.

        .. seealso:: ART_AnimationUI.getCurrentView

        """

        view = self.getCurrentView()
        scene = view.scene()

    def getCurrentView(self):
        """
        Gets the current QGraphicsView based on the currently selected character tab, and the currently selected
        picker tab of said character.

        :return: Returns the QGraphicsView that is currently active.

        """

        # search through the children of the widget until we find the gfxScene
                tab = child

                        return view

    def getPickerTabs(self, saving):
        """
        Finds and returns all picker item data, like transforms, scale, x/y coordinates, mirrored status, parent tab,
        and controlled module.

        :param saving: Whether or not this function is being called by savePicker, in which case if it is, it will then
                        make sure that the picker items are no longer editable.

        :rtype: A list of lists, where each inner list has the following data for a picker item:
        :return [0]: Name of tab the picker item belongs to
        :return [1]: Picker item transforms
        :return [2]: Name of module picker belongs to (which module controls it interfaces with)
        :return [3]: Whether or not the picker item is mirrored.
        :return [4]: Whether or not the picker is a sub-picker, like fingers or toes.
        :return [5]: And if not saving, the memory address of the picker item.

        """

                tab = child

                                    # item.data(1)could have data that is the scale factor, however, if this is
                                    # a fresh picker, it will not.
                                    scaleFactor = item.data(1)
                                    if scaleFactor is None:
                                        scaleFactor = item.scale

                                    # get position, rotation, and find out if the pickerBorderItem has been mirrored
                                    position = item.pos()
                                    position = [position.x(), position.y()]
                                    rotation = item.rotation()
                                    mirrored = item.transform().m11()
                                    itemData = {}

                                    # add data to dictionary
                                    itemData["tab"] = tab.tabText(canvasIndex)
                                    itemData["transforms"] = [position, rotation, scaleFactor]
                                    itemData["module"] = item.data(QtCore.Qt.UserRole)
                                    itemData["mirrored"] = mirrored

                                    if item.data(2) is None:
                                        itemData["subPicker"] = False
                                    else:
                                        itemData["subPicker"] = True

                                    if not saving:
                                        itemData["item"] = item

                                    if saving:
                                        # set flags to False so borderItem is no longer moveable, selectable, etc.
                                        item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False)
                                        item.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, False)
                                        item.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, False)
                                        item.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, False)

                                    returnData.append(itemData)

                                if type(item) == QtWidgets.QGraphicsPixmapItem:
                                    customImg = scene.property("customImg")
                                    if item == customImg:
                                        filePath = scene.property("filePath")
                                        itemData = {}

                                        itemData["tab"] = tab.tabText(canvasIndex)
                                        itemData["path"] = filePath

                                        returnData.append(itemData)

    def getComments(self, saving=False):
        """
        Gets any comment boxes that were created on any picker tabs and returns a list of those items.
        :param saving: Whether or not this function is being called from savePicker, in which case, it will disable
        edits.

        :rtype: A list of lists where the inner list contains the following data for each comment box found:
        :return [0]: The QRect of the comment box, which contains the box dimensions and the x/y coordinates.
        :return [1]: The color of the comment box
        :return [2]: The name of the tab the comment box is under.
        :return [3]: The text label of the comment box.

        """

                tab = child

                                if type(item) == interfaceUtils.commentBoxItem:

                                    # get comment box position and color
                                    geo = item.boundingRect()
                                    color = item.brush.color().getRgb()
                                    itemData["rect"] = [geo.x(), geo.y(), geo.width(), geo.height()]
                                    itemData["color"] = color

                                    # get comment box text
                                    children = item.childItems()
                                    for each in children:
                                        if type(each) == QtWidgets.QGraphicsTextItem:
                                            label = each.toPlainText()
                                            itemData["label"] = label

    def getButtonColors(self):
        """
        Gets joint module picker button colors, which can be edited by the user.

        .. note:: Currently, only the joint module supports users being able to change the button color.

        :rtype: A list of lists where the inner list contains the following data for each joint module found:
        :return [0]: The name of the module this picker interfaces with.
        :return [1]: The color of the picker button.
        :return [2]: The name of the control this button selects.

        """

                tab = child

                                    childButtons = item.childItems()

                                    # if the picker button is from a leaf module, get color data (only leaf for now)
                                    for child in childButtons:
                                        if type(child) == interfaceUtils.pickerButton:
                                            moduleType = item.data(QtCore.Qt.UserRole)
                                            if "ART_Leaf_Module" in moduleType:

                                                # get button color
                                                color = child.brush.color().getRgb()

                                                # store module name, button color
                                                itemData = {}
                                                itemData["module"] = moduleType
                                                itemData["color"] = color
                                                itemData["control"] = child.object

                                                returnData.append(itemData)

    def getModuleInst(self, module):
        """
        Takes the given module and instantiates it, returning the memory address for the created instance.

        :param module: The name of the module to instantiate.

        :return: The instance of the instantiated module in memory.

        """

        # get the class name from that module file (returns Modules.ART_Root.ART_Root for example)

        return moduleInst

    def changeBackground(self):
        """
        Creates an interface allowing a user to change the background image of a picker with these dimensions: (442 x
        600)

        """

        if cmds.window("pyART_changeBackgroundImg_Win", exists=True):
            cmds.deleteUI("pyART_changeBackgroundImg_Win", wnd=True)

        mainWin = QtWidgets.QMainWindow(self.mainWidget)
        mainWin.setStyleSheet(self.style)

        mainWin.setMinimumSize(QtCore.QSize(400, 200))
        mainWin.setMaximumSize(QtCore.QSize(400, 200))

        mainWin.setObjectName("pyART_changeBackgroundImg_Win")
        mainWin.setWindowTitle("Change Background")

        # create the main layout
        mainWidget = QtWidgets.QFrame()
        mainWin.setCentralWidget(mainWidget)
        mainLayout = QtWidgets.QVBoxLayout(mainWidget)

        # scroll area contents
        scrollContents = QtWidgets.QFrame()
        scrollContents.setStyleSheet("background: transparent;")
        scrollLayout = QtWidgets.QVBoxLayout()
        scrollLayout.setSpacing(5)

        # find tabs. for each tab, create widget for changing background
        tabs = []

            # if we find a tab widget, get tab name and item
                tab = child

                    # find tab's graphics view
                    canvas = tab.widget(i)
                    canvasChildren = canvas.children()
                    for child in canvasChildren:
                        if type(child) == QtWidgets.QGraphicsView:
                            gfxView = child
                            tabs.append([tab, tab.tabText(i), gfxView])

        for i in range(len(tabs)):
            # create the widget for each tab background
            layout = QtWidgets.QHBoxLayout()
            scrollLayout.addLayout(layout)

            # which tab combo box
            tabComboBox = QtWidgets.QComboBox()
            tabComboBox.setMinimumWidth(100)
            tabComboBox.setFixedHeight(35)
            layout.addWidget(tabComboBox)
            tabComboBox.setStyleSheet(self.style)

            tabComboBox.addItem(tabs[i][1])

            # path location
            pathField = QtWidgets.QLineEdit()
            layout.addWidget(pathField)
            pathField.setReadOnly(True)
            pathField.setFixedHeight(35)
            pathField.setStyleSheet(self.style)
            pathField.setMinimumWidth(160)
            pathField.setPlaceholderText("442 x 600 PNG image file..")

            # browse button
            button = QtWidgets.QPushButton()
            layout.addWidget(button)
            button.setMinimumSize(35, 35)
            button.setMaximumSize(35, 35)
            icon = QtGui.QIcon(os.path.join(self.iconsPath, "System/fileBrowse.png"))
            button.setIconSize(QtCore.QSize(30, 30))
            button.setIcon(icon)
            button.setStyleSheet(self.style)
            button.clicked.connect(partial(self.backgroundBrowse, pathField, tabs[i][2]))

            # clear button
            icon = QtGui.QIcon(os.path.join(self.iconsPath, "System/exit.png"))
            button.setToolTip("Clear custom background image, resetting back to default.")
            button.clicked.connect(partial(self.clearBackground, tabs[i][2]))

        # add everything to the scroll Layout
        scrollContents.setLayout(scrollLayout)
        scrollArea = QtWidgets.QScrollArea()
        mainLayout.addWidget(scrollArea)
        scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        scrollArea.setWidgetResizable(False)
        scrollArea.setWidget(scrollContents)

        # show
        mainWin.show()

    def backgroundBrowse(self, field, gfxView):
        """
        Opens a file browser to select a valid .png background image and then apply it to the specified QGraphicsScene.

        :param field: QLineEdit for the path name to be displayed.
        :param gfxView: QGraphicsView to add the background image to.

        """

        startingDir = utils.returnNicePath(self.toolsPath, "Core/Icons")
        if os.path.exists(startingDir):

            filename = cmds.fileDialog2(fm=1, okc="Load Picker", dir=startingDir, ff="*.png")
            if filename is not None:
                filename = filename[0]
                field.setText(utils.returnFriendlyPath(filename))

                pixmap = QtGui.QPixmap()
                pixmap.load(utils.returnFriendlyPath(filename))
                scene = gfxView.scene()
                gfxItem = scene.addPixmap(pixmap)
                gfxItem.setZValue(-10)
                scene.setProperty("customImg", gfxItem)
                scene.setProperty("filePath", filename)

    def clearBackground(self, gfxView):
        """
        Removes the background image from the given QGraphicsView.

        :param gfxView: The QGraphicsView whose background image to remove.

        .. seealso:: ART_AnimationUI.changeBackground

        """

        scene = gfxView.scene()
        items = scene.items()
        customImg = scene.property("customImg")

        for item in items:
            if item == customImg:
                scene.removeItem(item)

    def exportMotion(self):
        """
        Instantiates ART_ExportMotionUI to bring up the tool for exporting animation out to various file formats.

        """

        import ART_ExportMotionUI
        reload(ART_ExportMotionUI)
        ART_ExportMotionUI.ART_ExportMotion(self)

    def importMotion(self):
        """
        Instantiates ART_ImportMotionUI to bring up the tool for importing FBX animation onto the rig.

        """

        import ART_ImportMotionUI
        reload(ART_ImportMotionUI)
        ART_ImportMotionUI.ART_ImportMotion(self)

    def resetRigCtrls(self):
        """
        Instantiates ART_ResetModulesUI to bring up the tool for resetting transformations on a selected modules.
        Also known as "zeroing out".

        """

        import ART_ResetModulesUI
        reload(ART_ResetModulesUI)
        ART_ResetModulesUI.ART_ResetModules(self)

    def selectAllCtrls(self):
        """
        Instantiates ART_SelectControlsUI to bring up the tool for selecting specified rig controls for selected
        modules.

        """

        show = True
        mods = cmds.getModifiers()
        if (mods & 4) > 0:
            show = False

        import ART_SelectControlsUI
        reload(ART_SelectControlsUI)
        ART_SelectControlsUI.ART_SelectControls(self, show)

    def matchOverRange(self):
        """
        Instantiates ART_MatchOverRangeUI to bring up the tool for matching different rig type over a frame range for
        selected modules.

        :Example:

        Matching the IK leg rig controls to the Fk leg rig controls over a frame range of 0-30.

        """

        import ART_MatchOverRangeUI
        reload(ART_MatchOverRangeUI)
        ART_MatchOverRangeUI.ART_MatchOverRange(self)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
def run():
    """
    Instantiate the ART_AnimationUI class to build the main interface the animators will interact with.

    :return: instance of the ART_AnimationUI in memory.

    """

    # create new instance of ART_AnimationUI
    gui = ART_AnimationUI(getMainWindow())

    # Dock Control
    allowedAreas = ["left", "right"]

    try:
        if cmds.dockControl("pyArtv2AnimToolsDock", q=True, exists=True):
            cmds.deleteUI("pyART_AnimTools_Win")
            cmds.deleteUI("pyArtv2AnimToolsDock", control=True)

        dock = cmds.dockControl("pyArtv2AnimToolsDock", area="right", content="pyART_AnimTools_Win",
                                allowedArea=allowedAreas, label="Animation Tools", w=450, h=500)
        cmds.refresh(force=True)
        cmds.dockControl(dock, edit=True, r=True)

    except Exception, e:
        cmds.warning("UI failed to launch.")
        print e

    return gui

# coding=utf-8
"""
"""

import os

try:
except:

    """
    Get Maya’s window as a QWidget and return the item in memory.

    :return: a QWidget of Maya’s window

    """

windowTitle = "Edit Rig"
windowObject = "pyArtEditRigWin"

class ART_EditRigUI(QtWidgets.QMainWindow):
    """
    This class builds a tool that allows a user to Edit a Rig or Add Character for Animation. Both functions use the
    same interface. The title and button text get swapped out depending on which situation has been called for by
    the A.R.T.2.0 menu.

        .. image:: /images/editRig.png

    """

    def __init__(self, edit, add, parent=None):
        """
        Instantiates the class, getting the QSettings and building the interface.

        :param edit: Whether or not the operation is to edit the rig.
        :param add: Whether or not the operation is to add the character for animation.

        """

        super(ART_EditRigUI, self).__init__(parent)
        self.edit = edit
        self.add = add

        self.createUI()

    def createUI(self):
        """
        Builds the UI, listing options for choosing a project and showing all assets belonging to that project for
        edit or add.

        """

        # fonts
        font.setPointSize(12)

        fontSmall = QtGui.QFont()
        fontSmall.setPointSize(9)
        fontSmall.setBold(True)

        f.close()

        # #create the main widget

        self.setObjectName(windowObject)
        if self.edit:
            self.setWindowTitle(windowTitle)
        if self.add:
            self.setWindowTitle("Add Rig For Animation")

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.resize(400, 400)
        self.setSizePolicy(mainSizePolicy)
        self.setMinimumSize(QtCore.QSize(400, 400))
        self.setMaximumSize(QtCore.QSize(400, 400))

        # create the QFrame
        self.frame = QtWidgets.QFrame()
        self.layout.addWidget(self.frame)
        self.widgetLayout = QtWidgets.QHBoxLayout(self.frame)
        self.frame.setObjectName("mid")

        # #create two VBox Layouts to create 2 columns
        self.leftColumn = QtWidgets.QVBoxLayout()
        self.widgetLayout.addLayout(self.leftColumn)

        self.rightColumn = QtWidgets.QVBoxLayout()
        self.widgetLayout.addLayout(self.rightColumn)

        # #left column : project comboBox, group comboBox, listWidget of characters

        self.projectMenu = QtWidgets.QComboBox()
        self.leftColumn.addWidget(self.projectMenu)
        self.projectMenu.setMinimumSize(150, 30)
        self.projectMenu.setMaximumSize(150, 30)
        self.projectMenu.currentIndexChanged.connect(self.populateGroups)

        self.groupMenu = QtWidgets.QComboBox()
        self.leftColumn.addWidget(self.groupMenu)
        self.groupMenu.setMinimumSize(150, 30)
        self.groupMenu.setMaximumSize(150, 30)
        self.groupMenu.currentIndexChanged.connect(self.populateCharacters)

        self.characterList = QtWidgets.QListWidget()
        self.leftColumn.addWidget(self.characterList)
        self.characterList.setMinimumSize(150, 200)
        self.characterList.setMaximumSize(150, 200)
        self.characterList.itemClicked.connect(partial(self.populateIcon))
        self.populateProjects()
        # #right column: icon frame, edit button/add button, close button

        self.characterIcon = QtWidgets.QLabel()
        self.characterIcon.setMinimumSize(200, 200)
        self.characterIcon.setMaximumSize(200, 200)
        self.rightColumn.addWidget(self.characterIcon)

        # default image
        self.defaultPixMap = QtGui.QPixmap(utils.returnNicePath(self.iconsPath, "System/noCharacter.png"))
        self.characterIcon.setPixmap(self.defaultPixMap)

        # if edit:
            self.editButton = QtWidgets.QPushButton("Edit Selected")
            self.editButton.setFont(font)
            self.rightColumn.addWidget(self.editButton)
            self.editButton.setMinimumSize(200, 40)
            self.editButton.setMaximumSize(200, 40)
            self.editButton.clicked.connect(partial(self.editSelected))
            self.editButton.setObjectName("blueButton")

        # if add:
            self.addButton = QtWidgets.QPushButton("Add Selected")
            self.addButton.setFont(font)
            self.rightColumn.addWidget(self.addButton)
            self.addButton.setMinimumSize(200, 40)
            self.addButton.setMaximumSize(200, 40)
            self.addButton.clicked.connect(partial(self.addSelected))
            self.addButton.setObjectName("blueButton")

        self.closeButton = QtWidgets.QPushButton("Close")
        self.closeButton.setFont(font)
        self.rightColumn.addWidget(self.closeButton)
        self.closeButton.setMinimumSize(200, 40)
        self.closeButton.setMaximumSize(200, 40)
        self.closeButton.clicked.connect(partial(self.closeUI))
        self.closeButton.setObjectName("blueButton")

    def populateProjects(self):
        """
        Find all projects on disk (using the project path setting) and add each project to the QComboBox. Then,
        call on populateGroups.

        .. seealso:: ART_EditRigUI.populateGroups()

        """

        # if the project path doesn't exist on disk, create it
        if not os.path.exists(self.projectPath):
            os.makedirs(self.projectPath)

        # get a list of the existing folders in projects
        existingProjects = os.listdir(self.projectPath)
        folders = []

        # find out which returned items are directories
        for each in existingProjects:
            if os.path.isdir(os.path.join(self.projectPath, each)):
                folders.append(each)

        # add each project to the combo box
        self.projectMenu.clear()
        for each in folders:
            self.projectMenu.addItem(each)

        # find selected project and populate groups
        self.populateGroups()

    def populateGroups(self):
        """
        Given the selected project, populate any groups for that project (using the project path from QSettings) and
        add them to the groups QComboBox.

        Then, call on populateCharacters.

        .. seealso:: ART_EditRigUI.populateCharacters()

        """

        selectedProject = self.projectMenu.currentText()
        project = os.path.join(self.projectPath, selectedProject)
        existingGroups = os.listdir(project)

        for each in existingGroups:
            if os.path.isdir(os.path.join(project, each)):

        # otherwise, add each project to the combo box
        self.groupMenu.clear()
        self.groupMenu.addItem(" ")
            self.groupMenu.addItem(each)

        # populate characters
        self.populateCharacters()

    def populateCharacters(self):
        """
        Given the selected project and group, populate the QListWidget with any assets found using that information.
        The project path comes from the QSettings, the group is a subfolder of the project.

        """

        # add project button

        fullPath = os.path.join(self.projectPath, selectedProject)
        selectedGroup = self.groupMenu.currentText()
        if len(selectedGroup) > 1:
            fullPath = os.path.join(fullPath, selectedGroup)

        existingCharacters = os.listdir(fullPath)
        files = []

        for each in existingCharacters:
            if os.path.isfile(os.path.join(fullPath, each)):
                if each.rpartition(".")[2] == "ma":
                    files.append(each)

        self.characterList.clear()

        for each in files:
            item = QtWidgets.QListWidgetItem(each.partition(".ma")[0])
            item.setFont(font)
            self.characterList.addItem(item)

    def populateIcon(self, *args):
        """
        Given the selected character, display the correct icon for that character in the UI.

        """

        # default

        fullPath = utils.returnNicePath(self.projectPath, selectedProject)
            fullPath = utils.returnNicePath(fullPath, selectedGroup)

        selectedCharacter = self.characterList.currentItem().text()
        fullPath = utils.returnNicePath(fullPath, selectedCharacter + ".png")

        if os.path.exists(fullPath):
            pixmap = QtGui.QPixmap(fullPath)
            self.characterIcon.setPixmap(pixmap)

        else:
            self.characterIcon.setPixmap(self.defaultPixMap)

    def editSelected(self, *args):
        """
        Find the selected character, and open that file for edit. The path is constructed using the QSettings info,
        with any group as a subfolder, and lastly the selected asset/character as the last part of the path.

        """

        mayaFile = utils.returnNicePath(fullPath, selectedCharacter + ".ma")
        iconFile = utils.returnNicePath(fullPath, selectedCharacter + ".png")

        if os.path.exists(mayaFile):
            if os.path.exists(iconFile):
                launchUI = False

                # get current file
                currentFile = cmds.file(q=True, sceneName=True)
                if cmds.file(currentFile, q=True, modified=True) == True:
                    proceed = self.unsavedChanges(currentFile)

                    if proceed == 0:
                        cmds.file(save=True, force=True)
                        cmds.file(mayaFile, open=True, prompt=True, options="v=0", ignoreVersion=True, typ="mayaAscii",
                                  f=True)
                        launchUI = True

                    if proceed == 1:
                                  f=True)

                    if proceed == 2:
                        return
                else:
                    cmds.file(mayaFile, open=True, prompt=True, options="v=0", ignoreVersion=True, typ="mayaAscii",
                              f=True)
                    launchUI = True

        if launchUI:
            import Interfaces.ART_RigCreatorUI as ART_RigCreatorUI
            reload(ART_RigCreatorUI)
            ART_RigCreatorUI.createUI()

    def addSelected(self, *args):
        """
        Finds the selected character, and references that file into the currently opened scene. The path is
        constructed using the QSettings info, with any group as a subfolder, and lastly the selected asset/character
        as the last part of the path.

        Also handles namespaces, adding the new namespace to the network node of the added asset, and launches the
        animationUI.

        """

        # reference in the selected character if the file exists on drive

                # find existing namespaces in scene
                namespaces = cmds.namespaceInfo(listOnlyNamespaces=True)

                # reference the rig file
                cmds.file(mayaFile, r=True, type="mayaAscii", loadReferenceDepth="all", namespace=selectedCharacter,
                          options="v=0")

                # clear selection and fit view
                cmds.select(clear=True)
                cmds.viewFit()
                panels = cmds.getPanel(type='modelPanel')

                # turn on smooth shading
                for panel in panels:
                    editor = cmds.modelPanel(panel, q=True, modelEditor=True)
                    cmds.modelEditor(editor, edit=True, displayAppearance="smoothShaded", displayTextures=True,
                                     textures=True)

                # find new namespaces in scene to figure out the namespace that was created upon referencing the
                # character
                newCharacterName = selectedCharacter
                newNamespaces = cmds.namespaceInfo(listOnlyNamespaces=True)

                for name in newNamespaces:
                    if name not in namespaces:
                        newCharacterName = name

                # add an attribute to the rig root (if needed) and set the namespace attr to the newCharacterName
                if cmds.objExists(newCharacterName + ":ART_RIG_ROOT"):
                    if not cmds.objExists(newCharacterName + ":ART_RIG_ROOT.namespace"):
                        cmds.addAttr(newCharacterName + ":ART_RIG_ROOT", ln="namespace", dt="string", keyable=False)

                    cmds.setAttr(newCharacterName + ":ART_RIG_ROOT.namespace", newCharacterName, type="string")

                # delete any interfaces that may be up
                self.closeUI()

                if cmds.dockControl("pyArtRigCreatorDock", q=True, exists=True):
                    if cmds.window("pyArtRigCreatorUi", exists=True):
                        cmds.deleteUI("pyArtRigCreatorUi", wnd=True)
                    cmds.deleteUI("pyArtRigCreatorDock", control=True)

        # launch anim UI

        import Interfaces.ART_AnimationUI as ART_AnimationUI
        ART_AnimationUI.run()

    def unsavedChanges(self, path):
        """
        Displays a message box that warns the user of unsaved file changed and returns their response.

        :return: Returns the user response (Save, Don't Save, Cancel)

        """

        # message box for letting user know current file has unsaved changes
        msgBox = QtWidgets.QMessageBox()
        msgBox.setIcon(QtWidgets.QMessageBox.Warning)
        msgBox.setText("Current File Has Unsaved Changes!")
        msgBox.addButton("Save Changes", QtWidgets.QMessageBox.YesRole)
        msgBox.addButton("Don't Save", QtWidgets.QMessageBox.NoRole)
        msgBox.addButton("Cancel", QtWidgets.QMessageBox.NoRole)
        ret = msgBox.exec_()

        return ret

    def closeUI(self):
        """
        Closes and deletes the interface for the class.

        """

        if cmds.window("pyArtEditRigWin", exists=True):
            cmds.deleteUI("pyArtEditRigWin", wnd=True)

def run():
    """
    Creates an instance of the class for Editing a rig. The ARTv2 menu calls on this function.

    """

    if cmds.window("pyArtEditRigWin", exists=True):
        cmds.deleteUI("pyArtEditRigWin", wnd=True)

    gui = ART_EditRigUI(True, False, getMainWindow())
    gui.show()

def runAdd():
    """
    Creates an instance of the class for adding a rig for animation. The artv2 menu calls this function.

    """

    gui = ART_EditRigUI(False, True, getMainWindow())

"""
"""

import os

class ART_BuildProgress_UI():
    """
    This class kicks off building all of the rigs for the modules. It displays an interface that shows progress and
    displays information about the rig build.

    It exports skin weights, rebuilds the skeleton in rig pose, imports skin weights, runs any pre-script,
    calls on each module's rig building code, sets up rig parenting and hierarchy, and finally runs any post-script.

        .. image:: /images/buildProgress.gif

    """

        """
        Instantiates the class, getting the QSettings and then calling on the function to build the UI for the tool.

        :param mainUI: The instance of the Rig Creator UI that this class was called from.

        .. seealso:: ART_BuildProgressUI.buildUI

        """

        self.rigData = []
        self.warnings = 0
        self.errors = 0

        # images
        self.imageBkgrd = utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/toolbar_background.png"))
        self.imageBtnBkrd = utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/blue_field_background.png"))
        self.frameBackground = utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/field_background.png"))

        if cmds.window("ART_BuildProgressWin", exists=True):
            cmds.deleteUI("ART_BuildProgressWin", wnd=True)

        """
        Builds the interface, which doesn't really have any user-interaction, but is there to display information
        about the progress of the rig build. There are two QProgressBars that show current module build progress and
        total build progress, then a QTextEdit which outputs information about what the build process is currently
        working on.

        After the interface is built, it sets the rig pose on the joints of each module.

        """

        self.mainWin = QtWidgets.QMainWindow(interfaceUtils.getMainWindow())
        self.mainWin.setStyleSheet("background-color: rgb(0, 0, 0);, color: rgb(0,0,0);")

        f.close()

        self.mainWin.setObjectName("ART_BuildProgressWin")
        self.mainWin.setWindowTitle("Build Progress")

        # font

        # set size policy

        self.mainWin.resize(500, 300)
        self.mainWin.setSizePolicy(mainSizePolicy)
        self.mainWin.setMinimumSize(QtCore.QSize(500, 300))
        self.mainWin.setMaximumSize(QtCore.QSize(500, 300))

        # create the QFrame for this page
        self.background = QtWidgets.QFrame()
        self.layout.addWidget(self.background)
        self.mainLayout = QtWidgets.QVBoxLayout(self.background)
        self.background.setObjectName("epic")

        # build the progress bars:

        self.currentTask = QtWidgets.QProgressBar()
        self.mainLayout.addWidget(self.currentTask)
        self.currentTask.setRange(0, 100)
        self.currentTask.setTextVisible(True)
        self.currentTask.setValue(0)

        self.totalProgress = QtWidgets.QProgressBar()
        self.mainLayout.addWidget(self.totalProgress)
        self.totalProgress.setFormat("Total Progress..")
        self.totalProgress.setRange(0, 12)
        self.totalProgress.setTextVisible(True)
        self.totalProgress.setValue(0)

        # detailed information
        self.infoText = QtWidgets.QTextEdit()
        self.mainLayout.addWidget(self.infoText)
        self.infoText.setMinimumHeight(200)
        self.infoText.setMaximumHeight(200)
        self.infoText.setText("Starting Build Process..")
        self.infoText.setReadOnly(True)

        # start build
        self.setRigPose()

    def exportWeights(self):
        """
        Exports all skin weights of meshes that have skinClusters to a .weights file (JSON). It also has
        functionality to deal with morph targets, making sure they are preserved when history on the meshes is deleted.

        .. seealso:: riggingUtils.export_skin_weights()

        """

        self.infoText.append("\n")
        self.infoText.append("|| EXPORTING SKIN WEIGHTS ||")

        # find meshes that are weighted
        weightedMeshes = []
        skinClusters = cmds.ls(type='skinCluster')

        for cluster in skinClusters:
            geometry = cmds.skinCluster(cluster, q=True, g=True)[0]
            geoTransform = cmds.listRelatives(geometry, parent=True)[0]
            weightedMeshes.append([geoTransform, cluster])

        # update progress bar
        numMeshes = len(weightedMeshes)
        self.currentTask.setRange(0, numMeshes)

        # save out weights of meshes
        for mesh in weightedMeshes:
            filePath = utils.returnFriendlyPath(os.path.join(cmds.internalVar(utd=True), mesh[0] + ".WEIGHTS"))

            # export skin weights
            riggingUtils.export_skin_weights(filePath, mesh[0])

            # CHECK FOR MORPH TARGETS
            blendshapeList = []

            # find blendshapes
            skinCluster = riggingUtils.findRelatedSkinCluster(mesh[0])

                blendshapes = cmds.listConnections(skinCluster + ".input", source=True, type="blendShape")
                deleteShapes = []
                if blendshapes is not None:
                    for each in blendshapes:
                        attrs = cmds.listAttr(each, m=True, string="weight")
                        if attrs is not None:
                            for attr in attrs:

                                # if not, manually create shapes by toggling attrs and duplicating mesh
                                if not cmds.objExists(attr):
                                    cmds.setAttr(each + "." + attr, 1)
                                    dupe = cmds.duplicate(mesh[0])[0]

                                    # parent to world
                                    parent = cmds.listRelatives(dupe, parent=True)
                                    if parent is not None:
                                        cmds.parent(dupe, world=True)

                                    # rename the duplicate mesh to the blendshape name
                                    cmds.rename(dupe, attr)
                                    cmds.setAttr(each + "." + attr, 0)
                                    deleteShapes.append(attr)

                            # add the blendshape node name and its attrs to the master blendshape list
                            blendshapeList.append([each, attrs])

            # delete history of meshes
            cmds.delete(mesh[0], ch=True)

            # reapply blendshapes
            for item in blendshapeList:
                bshapeName = item[0]
                shapeList = item[1]

                i = 1
                for shape in shapeList:
                    if cmds.objExists(bshapeName):
                        cmds.blendShape(bshapeName, edit=True, t=(mesh[0], i, shape, 1.0))

                    else:
                        cmds.select([shape, mesh[0]], r=True)
                        cmds.blendShape(name=bshapeName)
                        cmds.select(clear=True)

            try:
                for each in deleteShapes:
                    cmds.delete(each)
            except:
                pass

            # update progress and info
            self.infoText.append("    Exported Skin Weights for " + mesh[0])
            curVal = self.currentTask.value()
            self.currentTask.setValue(curVal + 1)

        # update main progress bar
        self.totalProgress.setValue(1)

        # rebuild the skeleton in rig pose
        self.rebuildSkeleton()

    def setRigPose(self):
        """
        Sets the rig pose on each module's joints. Lastly, calls on ART_BuildProgressUI.exportWeights()

        ..seealso :: ART_BuildProgressUI.exportWeights()

        """

        # set rig pose
        self.infoText.append("Setting Rig Pose..")
        numMods = len(self.mainUI.moduleInstances) - 1

        self.currentTask.setRange(0, numMods)

        for inst in self.mainUI.moduleInstances:
            if inst.name != "root":
                inst.setupForRigPose()
                inst.setReferencePose("rigPose")
                inst.cleanUpRigPose()
                curVal = self.currentTask.value()
                self.currentTask.setValue(curVal + 1)

        self.totalProgress.setValue(2)

        self.exportWeights()

    def rebuildSkeleton(self):
        """
        Rebuilds the skeleton in rig pose, meaning that all joint rotations will be zeroed in rig pose rather than
        model pose. This ensures clean rigging.

        Lastly, calls on ART_BuildProgressUI.importWeights() to reimport weighting back onto the meshes.

        .. seealso:: riggingUtils.buildSkeleton(), ART_BuildProgressUI.importWeights()

        """

        # rebuild the skeleton
        cmds.delete("root")

        self.infoText.append("Rebuilding Skeleton in Rig Pose..")

        # build skeleton from utils
        riggingUtils.buildSkeleton()

        self.totalProgress.setValue(3)

        # import weights
        self.importWeights()

    def importWeights(self):
        """
        Imports skin weights back onto the asset geometry after having rebuilt the skeleton in rig pose. Then calls
        on ART_BuildProgressUI.preScript().

        .. seealso:: ART_BuildProgressUI.preScript()

        """

        meshes = utils.findAllSkinnableGeo()

        self.currentTask.setRange(0, len(meshes))
        self.infoText.append("|| IMPORTING SKIN WEIGHTS ||")

        for mesh in meshes:
            filePath = utils.returnFriendlyPath(os.path.join(cmds.internalVar(utd=True), mesh + ".WEIGHTS"))

            if os.path.exists(filePath):
                riggingUtils.import_skin_weights(filePath, mesh, True)

                # update progress and info
                self.infoText.append("    Imported Skin Weights for " + mesh)

                # remove skin file
                os.remove(filePath)

            else:
                self.infoText.setTextColor(QtGui.QColor(236, 217, 0))
                self.infoText.append("    Could not import weights for " + mesh)
                self.infoText.setTextColor(QtGui.QColor(255, 255, 255))
                self.warnings += 1

        self.totalProgress.setValue(5)

        # call on the prescript
        self.preScript()

        #     def exportMeshes(self):
        #
        #         lodAttrs = utils.getLodData()
        #         exportData = utils.findExportMeshData()
        #
        #         self.currentTask.setRange(0, len(lodAttrs))
        #         self.currentTask.setValue(0)
        #
        #
        #         #save the file
        #         saveFile = cmds.file(q = True, sceneName = True)
        #
        #
        #         try:
        #             cmds.file(save = True)
        #         except Exception, e:
        #             cmds.error("Could not save file. Error: " + str(e))
        #             return
        #
        #
        #         #for each LOD
        #         for each in exportData:
        #             meshValue = each[1]
        #             pathValue = each[0]
        #             boneValue = each[2]
        #             poseData = each[3]
        #             utils.exportMesh(self.mainUI, meshValue, pathValue, boneValue, poseData)
        #
        #             #open the file
        #             cmds.file(saveFile, open = True, force = True)
        #
        #             #update UI
        #             self.infoText.setTextColor(QtGui.QColor(0,255,18))
        #             self.infoText.append("    SUCCESS: FBX file exported.")
        #             self.infoText.append("          " + str(pathValue))
        #             self.infoText.setTextColor(QtGui.QColor(255,255,255))
        #
        #             #update progress bar
        #             curVal = self.currentTask.value()
        #             self.currentTask.setValue(curVal + 1)
        #
        #         #update main progress bar
        #         self.totalProgress.setValue(7)
        #
        #         #run pre-script
        #         self.preScript()

        """
        Keeping this around for now, but currently it is not being used.
        Exporting is now done through edit rig -> Export Skeletal Meshes
        """

    def preScript(self):
        """
        If there is a pre-script to run, this will call on ART_BuildProgressUI.executeScript() to run the pre-script.
        Then it will call on ART_BuildProgressUI.buildRigs() to build each module's rigs.

        .. note:: Pre-Scipts are used if you ever want to do something to your character before the rig gets built.
                  An example usage would be adding IK joints for UE4 in a pre-script, as you don't want or need
                  controls for those IK joints, and setting up those constraints.

        .. seealso:: ART_BuildProgressUI.executeScript(), ART_BuildProgressUI.buildRigs()

        """

        self.infoText.append(" \n")

        # get pre-script path from character node, if it exists
        characterNode = utils.returnCharacterModule()
        if cmds.objExists(characterNode + ".preScriptPath"):
            scriptPath = cmds.getAttr(characterNode + ".preScriptPath")
            self.infoText.append("Executing Pre-Script..")
            self.infoText.append("    " + scriptPath)

            # try to execute the pre-script
            status = self.executeScript(scriptPath)
            if status:
                self.infoText.setTextColor(QtGui.QColor(0, 255, 18))
                self.infoText.append("    SUCCESS: Pre-Script Was Successfully Executed..")

            if not status:
                self.infoText.setTextColor(QtGui.QColor(255, 0, 0))
                self.infoText.append("    FAILED: Pre-Script Was Not Successfully Executed..")
                self.errors += 1
        else:
            self.infoText.setTextColor(QtGui.QColor(255, 255, 0))
            self.infoText.append("No Pre-Script To Run..")
            self.infoText.setTextColor(QtGui.QColor(255, 255, 255))

        self.totalProgress.setValue(8)

        # build rigs
        self.buildRigs()

    def buildRigs(self):
        """
        First, create a driver skeleton which the rig will build upon, then calls on each module's buildCustomRig
        function, which will build the rig for that module, then sets up rig parenting and hiearchy once all modules
        are built.

        Lastly, calls on the function to execute a post script if one was given during publish.

        .. seealso:: riggingUtils.createDriverSkeleton(), ART_BuildProgressUI.postScript()

        """

        self.infoText.append("||  PREPARING TO BUILD CONTROL RIGS..  ||")

        # Update Current Task bar with num modules
        self.currentTask.setRange(0, len(self.mainUI.moduleInstances))

        # create the driver skeleton
        riggingUtils.createDriverSkeleton()

        # Loop through modules, building rigs
            self.infoText.append(" \n")
            self.infoText.append("    Building: " + str(inst.name))

            # build module rigs
            inst.buildRig(self.infoText, self)

            # update progress bar

        # #Setup the rig parenting
        for data in self.rigData:
            createdConstraints = []

            if data[1] == "driver_root":
                pConst = cmds.pointConstraint("offset_anim", data[0], mo=True)[0]
                oConst = cmds.orientConstraint("offset_anim", data[0], mo=True)[0]
                if pConst not in createdConstraints:
                    createdConstraints.append(pConst)
                if oConst not in createdConstraints:
                    createdConstraints.append(oConst)

            else:
                # get the connections of the passed in parent bone
                connections = []
                for connection in cmds.listConnections(data[1], type="constraint"):
                    connections.append(connection)

                for connection in list(set(connections)):
                    constraintType = cmds.nodeType(connection)
                    driveAttrs = []
                    targetWeights = []

                    # get those constraint target attributes for each constraint connection
                    targets = cmds.getAttr(connection + ".target", mi=True)
                    if len(targets) >= 1:
                        for each in targets:
                            # get the names of the constraint targets
                            targetWeights.append(
                                    cmds.listConnections(connection + ".target[" + str(each) + "].targetWeight",
                                                         p=True)[0])
                            for targetWeight in targetWeights:
                                name = targetWeight.rpartition(".")[2]
                                name = name.rpartition("W")[0]
                                if name not in driveAttrs:
                                    driveAttrs.append(name)

                    # Setup constraint between the driveAttrs and the rigData[0] node
                    for attr in driveAttrs:
                        print attr, data[0]
                        if constraintType == "pointConstraint":
                            try:
                                pConst = cmds.pointConstraint(attr, data[0], mo=True)[0]
                                if pConst not in createdConstraints:
                                    createdConstraints.append(pConst)
                            except Exception, e:
                                cmds.warning(str(e))

                        if constraintType == "orientConstraint":
                            try:
                                oConst = cmds.orientConstraint(attr, data[0], mo=True)[0]
                                if oConst not in createdConstraints:
                                    createdConstraints.append(oConst)

                        if constraintType == "parentConstraint":
                            try:
                                paConst = cmds.parentConstraint(attr, data[0], mo=True)[0]
                                if paConst not in createdConstraints:
                                    createdConstraints.append(paConst)

                    # get the names of the constraint targets
                    for const in createdConstraints:
                        constraintAttrs = []
                        targets = cmds.getAttr(const + ".target", mi=True)
                            constraintAttrs.append(
                                    cmds.listConnections(const + ".target[" + str(each) + "].targetWeight", p=True)[0])

                        # setup connections between the parent bone constraints and the newly created constraints
                        if len(constraintAttrs) > 0:
                            for i in range(len(constraintAttrs)):
                                try:
                                    cmds.connectAttr(targetWeights[i], constraintAttrs[i])
                                except:
                                    pass

        # #set the state on the main network node
        if cmds.objExists("ART_RIG_ROOT.state"):
            cmds.setAttr("ART_RIG_ROOT.state", 2)

        # #hide all joints
        joints = cmds.ls(type="joint")
        for joint in joints:
            try:
                cmds.setAttr(joint + ".v", lock=False)
                cmds.setAttr(joint + ".v", 0, lock=True)
            except:
                pass

        # #remove unused skin influences to optimize scene
        mel.eval("removeAllUnusedSkinInfs()")

        # #save the file
        cmds.file(save=True, type="mayaAscii")

        self.totalProgress.setValue(9)

        # #execute post script
        self.postScript()

    def postScript(self):
        """
        If there is a post-script to run, this will call on ART_BuildProgressUI.executeScript() to run the post-script.
        Then it will call on ART_BuildProgressUI.completeBuild() which wraps up the build process and alerts the user
        the build is done.

        .. note:: Post-Scipts are used if you ever want to do something to your character after the rig gets built.
                  An example usage would be adding custom rigging to joints or controls in the scene, setting up custom
                  relationships or set-driven keys, etc.

        .. seealso:: ART_BuildProgressUI.executeScript(), ART_BuildProgressUI.completeBuild()

        """

        if cmds.objExists(characterNode + ".postScriptPath"):
            scriptPath = cmds.getAttr(characterNode + ".postScriptPath")
            self.infoText.append("Executing Post-Script..")

            if status:
                self.infoText.append("    SUCCESS: Post-Script Was Successfully Executed..")

                self.infoText.append("    FAILED: Post-Script Was Not Successfully Executed..")

        else:
            self.infoText.append("No Post-Script To Run..")

        self.totalProgress.setValue(10)

        # capture model pose for rig controls
        self.completeBuild()

    def completeBuild(self):
        """
        Locks down all network nodes, saves the scene, and alerts user that the rig build is complete.

        """

        self.infoText.append("Cleaning Up..")

        # go through each module instance, and lock down the nodes
        cmds.select("rig_grp", hi=True)
        rigNodes = cmds.ls(sl=True)
        numNodes = len(rigNodes)
        self.currentTask.setMaximum(numNodes)

        for node in rigNodes:

            try:
                cmds.lockNode(node, lock=True)

            except:
                pass

        # save scene

        # iterate total progress
        self.totalProgress.setValue(12)

        # add build info
        font.setPointSize(20)

        self.infoText.setFont(font)
        self.infoText.setTextColor(QtGui.QColor(0, 255, 18))
        self.infoText.append("\n\nPUBLISH COMPLETE!")

        font.setBold(False)
        self.infoText.setTextColor(QtGui.QColor(255, 255, 255))

        # get file name
        fileName = cmds.file(q=True, sceneName=True)
        iconPath = cmds.getAttr("ART_RIG_ROOT.iconPath")

        self.infoText.append("Assets Created:    ")
        self.infoText.append("    " + fileName)
        self.infoText.append("    " + iconPath)
        self.infoText.append(str(self.warnings) + " warnings")
        self.infoText.append(str(self.errors) + " errors")

        # tell user build is complete
        msgBox.setIcon(QtWidgets.QMessageBox.Information)
        msgBox.setText("Rig Build Complete!")
        msgBox.addButton("New Scene", QtWidgets.QMessageBox.YesRole)
        msgBox.addButton("Edit Rig", QtWidgets.QMessageBox.NoRole)

        if ret == 1:
            import ART_RigCreatorUI as ART_RigCreatorUI
            cmds.refresh(force=True)
            cmds.dockControl("pyArtRigCreatorDock", e=True, r=True)

        else:

            # if the rigCreatorUI exists delete UI
            if cmds.dockControl("pyArtRigCreatorDock", q=True, exists=True):
                cmds.deleteUI("pyArtRigCreatorUi")
                cmds.deleteUI("pyArtRigCreatorDock", control=True)

            cmds.file(new=True)

    def executeScript(self, scriptPath):
        """
        Takes a given script (mel or python) and runs it.

        :param scriptPath: location of the script to be evaluated and ran.

        :return: Whether or not the execution of the script failed or not.

        .. seealso:: ART_BuildProgressUI.preScript(), ART_BuildProgressUI.postScript()

        """

        sourceType = ""
        status = False

        if scriptPath.find(".py") != -1:
            sourceType = "python"

        if scriptPath.find(".mel") != -1:
            sourceType = "mel"

        # MEL
        if sourceType == "mel":
            try:
                command = ""
                # open the file, and for each line in the file, add it to our command string.
                f = open(scriptPath, 'r')
                lines = f.readlines()
                for line in lines:
                    command += line

                import maya.mel as mel
                mel.eval(command)

                # save the file
                cmds.file(save=True, type="mayaAscii")
                status = True
            except:
                pass

        # PYTHON
        if sourceType == "python":
            try:
                execfile("" + scriptPath + "")

            except:
                pass

        return status

"""

########
Contents
########

|   **Node Traversal Utilities:**
|       :func:`returnRigModules <System.utils.returnRigModules>`
|       :func:`returnCharacterModule <System.utils.returnCharacterModule>`
|       :func:`returnCharacterModules <System.utils.returnCharacterModules>`
|       :func:`returnRigModuleTypes <System.utils.returnRigModuleTypes>`
|       :func:`getViableParents <System.utils.getViableParents>`
|       :func:`deleteChildren <System.utils.deleteChildren>`
|       :func:`find_all_incoming <System.utils.find_all_incoming>`
|
|   **Joint Mover Utilities:**
|       :func:`findAndRenameOutlinerChildren <System.utils.findAndRenameOutlinerChildren>`
|       :func:`findAssociatedMover <System.utils.findAssociatedMover>`
|       :func:`findMoverNodeFromJointName <System.utils.findMoverNodeFromJointName>`
|       :func:`findOffsetMoverFromName <System.utils.findOffsetMoverFromName>`
|       :func:`findGlobalMoverFromName <System.utils.findGlobalMoverFromName>`
|
|   **Mesh Utilities:**
|       :func:`splitMesh <System.utils.splitMesh>`
|       :func:`findAllSkinnableGeo <System.utils.findAllSkinnableGeo>`
|       :func:`getLodData <System.utils.getLodData>`
|       :func:`exportMesh <System.utils.exportMesh>`
|       :func:`findExportMeshData <System.utils.findExportMeshData>`
|
|   **Path Utilities:**
|       :func:`win_path_convert <System.utils.win_path_convert>`
|       :func:`returnFriendlyPath <System.utils.returnFriendlyPath>`
|       :func:`returnNicePath <System.utils.returnNicePath>`
|
|   **Misc. Utilities:**
|       :func:`fitViewAndShade <System.utils.fitViewAndShade>`
|       :func:`fitSelection <System.utils.fitSelection>`
|       :func:`get_mobject <System.utils.get_mobject>`
|       :func:`get_namespace <System.utils.get_namespace>`
|       :func:`remove_namespace <System.utils.remove_namespace>`
|
|

#########
Functions
#########
"""

import math
import os
import re

from maya import OpenMaya, OpenMayaAnim

import interfaceUtils as interfaceUtils
import riggingUtils as riggingUtils
from ThirdParty.Qt import QtWidgets

try:
except:

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
def returnRigModules():
    """
    Look for all network nodes in the scene. Return network nodes that have a .parent attribute.

    :return: list of network nodes that have .parent attr, signifying an ART network node
    """

    modules = []
    networkNodes = cmds.ls(type="network")

    if "ART_Root_Module" in networkNodes:
        modules.append("ART_Root_Module")
        index = networkNodes.index("ART_Root_Module")
        networkNodes.pop(index)

    for node in networkNodes:
        attrs = cmds.listAttr(node)
        if "parent" in attrs:
            modules.append(node)

    return modules

def returnCharacterModule():
    """
    Look for all network nodes in the scene. Return the main network node that all other ART network nodes
    connect to (also known as the character node)

    :return: character node that stores character information.
    """

        if "parent" not in attrs:
            return node

def returnCharacterModules():
    """
    Look for all network nodes in the scene. Return all character network nodes found.

    :return: list of character network nodes which list details about the character.
    """

def returnRigModuleTypes():
    """
    Look for all network nodes in the scene. Get the moduleType attribute value for any valid nodes and
    append that to our list of modules to return, giving us a list of all of the types of modules our character
    has.

    :return: list of the different types of modules in our scene (ART_Arm, ART_Leg, etc)
    """

    modTypes = []

            modType = cmds.getAttr(node + ".moduleType")
            if modType not in modTypes:
                modTypes.append(modType)

    return modTypes

def getViableParents():
    """
    Look for all network nodes in the scene, and search for the Created_Bones attribute, which holds the names
    of the bones a particular module will create given its current settings. Add all created bones from all modules
    to a list to be returned.

    :return: list of all the bone names created by the current modules in the scene.
    """

    # look through the node network and find created bone lists
    modules = returnRigModules()

    bonesList = []
    for module in modules:
        if cmds.objExists(module + ".Created_Bones"):
            bonesList.append(cmds.getAttr(module + ".Created_Bones"))

    # now we have a long string of all of the created bones. we need to split them up into individual items
    parents = []
    for bone in bonesList:
        bones = bone.split("::")

        for bone in reversed(bones):
            if bone != "":
                parents.append(bone)

    # once done, add each one to the comboBox
    return parents

def findMoverNodeFromJointName(networkNodes, jointName, offsetMover=True, globalMover=False):
    """
    Look for the passed in joint name in the Created_Bones attribute of all network nodes in the scene.
    Once found, find the joint's associated offset or global mover.

    :param networkNodes: list of network nodes to search for jointName in Created_Bones attribute
    :param jointName: name of joint whose mover (offset or global) we are trying to find
    :param offsetMover: Whether to return the offset mover
    :param globalMover: Whether to return the global mover
    :return: the name of the associated joint mover for the given joint
    """

    # take the passed in list, and if there is a created_Bones attribute, search it for the jointName
        boneList = []
        if cmds.objExists(node + ".Created_Bones"):
            boneList.append(cmds.getAttr(node + ".Created_Bones"))
        for bone in boneList:
            splitBones = bone.split("::")

            for joint in splitBones:
                if joint == jointName:
                    # get the module name and module class
                    moduleName = cmds.getAttr(node + ".moduleName")
                    moduleClass = cmds.getAttr(node + ".moduleType")

                    if node != "ART_Root_Module":
                        basename = cmds.getAttr(node + ".baseName")
                    else:
                        basename = moduleName

                    # start building up the mover name we need and return it
                    if offsetMover:
                        moverName = jointName + "_mover_offset"
                    if globalMover:
                        moverName = jointName + "_mover"

                    if cmds.objExists(moverName):
                        return moverName

                    else:
                        if offsetMover:
                            moverName = jointName + "_mover_offset"
                        if globalMover:
                            moverName = jointName + "_mover"

                        # comparing basename and moduleName, get prefix and suffix
                        prefix = moduleName.partition(basename)[0]
                        suffix = moduleName.partition(basename)[2]

                        # make sure not partitioning on an empty separator
                        if prefix == "":
                            prefix = "&&"
                        if suffix == "":
                            suffix = "$$"

                        noPrefix = jointName.partition(prefix)[2]
                        if noPrefix != "":
                            moverName = noPrefix
                        noSuffix = moverName.rpartition(suffix)[0]
                        if noSuffix != "":
                            moverName = noSuffix

                        # construct mover name.
                            moverName = moduleName + "_" + moverName

                            if cmds.objExists(moverName):
                                if moverName.find("_mover") != -1:
                                    return moverName
                            else:
                                moverName += "_mover_offset"

                                if cmds.objExists(moverName):

                            else:
                                moverName += "_mover"

def findOffsetMoverFromName(name):
    """
    Find the top-most offset mover of a given module and return it.

    :param name: name of the module whose top-most offset mover we wish to find.
    :return: name of the offset mover of given module name.
    """

    grp = name + "_mover_grp"
    if cmds.objExists(grp):
        topLevelGrp = cmds.listRelatives(grp, children=True)
        if len(topLevelGrp) > 0:
            globalMover = cmds.listRelatives(topLevelGrp[0], children=True, type="transform")
            if len(globalMover) > 0:
                offsetMover = cmds.listRelatives(globalMover[0], children=True, type="transform")
                if len(offsetMover) > 0:
                    return offsetMover[0]

def findGlobalMoverFromName(name):
    """
    Find the top-most global mover of a given module and return it.

    :param name: name of the module whose top-most global mover we wish to find.
    :return: name of the global mover of given module name.
    """

            if topLevelGrp[0].find("mover") != -1:
                if topLevelGrp[0].find("offset") == -1:
                    if topLevelGrp[0].find("grp") == -1:
                        return topLevelGrp[0]

                    else:
                        globalMover = cmds.listRelatives(topLevelGrp[0], children=True, type="transform")

                        if len(globalMover) > 0:
                            return globalMover[0]

            else:
                globalMover = cmds.listRelatives(topLevelGrp[0], children=True, type="transform")

                if len(globalMover) > 0:
                    return globalMover[0]

def fitViewAndShade():
    """
    Focus the camera on what is in the scene, fitting it to the view, then turn on shaded mode.
    """

    # clear selection and fit view
    previousSelection = cmds.ls(sl=True)
    cmds.select(clear=True)
    cmds.viewFit()
    panels = cmds.getPanel(type='modelPanel')

    # turn on smooth shading
    for panel in panels:
        editor = cmds.modelPanel(panel, q=True, modelEditor=True)
        cmds.modelEditor(editor, edit=True, displayAppearance="smoothShaded", backfaceCulling=False)
    if len(previousSelection) > 0:
        cmds.select(previousSelection)

def fitSelection():
    """
    Frame the camera up on the current selection.
    """

    mel.eval("FrameSelected;")
    mel.eval("fitPanel -selected;")

def findAndRenameOutlinerChildren(widgetItem, partitionName, newName):
    """
    Find all child widgets of the passed in widgetItem and set their text to the new module name. Usually invoked
    when a user wants to change the module name.

    :param widgetItem: widget to search and replace the names of the children widgets
    :param partitionName: the original module name
    :param newName: the new module name
    """

    children = widgetItem.childCount()
    for i in range(children):
        child = widgetItem.child(i)
        mover = child.text(0).partition(partitionName)[2]
        child.setText(0, newName + mover)
        findAndRenameOutlinerChildren(child, partitionName, newName)

def deleteChildren(node):
    """
    Delete all children of the passed in transform node.

    :param node: node whose children we want to delete.
    """

    relatives = cmds.listRelatives(node, children=True, f=True)
    if relatives is not None:
        for each in relatives:
            if cmds.nodeType(each) == "transform":
                if cmds.objExists(each):

def findAssociatedMover(joint, module):
    """
    Find the global mover associated with the passed in joint and the passed in network node. Usually invoked from
    riggingUtils when creating a control from a joint mover.

    :param joint: name of the joint whose associated mover you wish to find.
    :param module: name of the module the joint belongs to. Used to figure out if any prefixes or suffixes were used.
    :return: name of global mover
    """

    if module == "ART_Root_Module":
        return "root_mover"

    # figure out the name
    moduleName = cmds.getAttr(module + ".moduleName")
    baseName = cmds.getAttr(module + ".baseName")

    moverName = joint + "_mover"

    if cmds.objExists(moverName):
        return moverName

    else:
        # find prefix/suffix if available
        prefix = moduleName.partition(baseName)[0]
        suffix = moduleName.partition(baseName)[2]
        if prefix == "":
            prefixSeparator = " "
        else:
            prefixSeparator = prefix

        if suffix == "":
            suffixSeparator = " "
        else:
            suffixSeparator = suffix

        # get the bone name (for example, thigh)
            boneName = joint.partition(suffixSeparator)[0]
        else:
            boneName = joint.partition(prefixSeparator)[2].partition(suffixSeparator)[0]

        # get the mover node
        mover = prefix + baseName + suffix + "_" + boneName + "_mover"
        mover = mover.replace(" ", "")

        if cmds.objExists(mover):
            return mover

        else:
            return None

def find_all_incoming(start_nodes, max_depth=None):
    """
    Recursively finds all unique incoming dependencies for the specified node.

    :return: list of dependencies associated with node.
    """

    dependencies = set()
    _find_all_incoming(start_nodes, dependencies, max_depth, 0)
    return list(dependencies)

def _find_all_incoming(start_nodes, dependencies, max_depth, depth):
    """
    """

    if max_depth and depth > max_depth:
        return
    kwargs = dict(s=True, d=False)
    incoming = cmds.listConnections(list(start_nodes), **kwargs)
    if not incoming:
        return
    non_visitied = set(cmds.ls(incoming, l=True)).difference(dependencies)
    dependencies.update(non_visitied)
    if non_visitied:
        _find_all_incoming(non_visitied, dependencies, max_depth, depth + 1)

def get_mobject(name):
    """
    Get's MObject from given name.
    """

    selection_list = OpenMaya.MSelectionList()
    selection_list.add(name)
    mobject = OpenMaya.MObject()
    selection_list.getDependNode(0, mobject)
    return mobject

def get_namespace(name):
    """
    Gets the namespace from the given name.

    :param name: String to extract the namespace from.
    :return: The extracted namespace
    """

    namespace = re.match('[_0-9a-zA-Z]+(?=:)(:[_0-9a-zA-Z]+(?=:))*', name)
    if namespace:
        namespace = '%s:' % str(namespace.group(0))
    else:
        namespace = ''
    return namespace

def remove_namespace(name):
    """
    Removes the namespace from the given name

    :param name: The name with the namespace
    :return: The name without the namesapce
    """

    namespace = get_namespace(name)
        return re.sub('^{0}'.format(namespace), '', name)
    return name

def win_path_convert(path=None):
    """
    Converts back slashes to forward slashes
    """

    separator = os.sep
    if separator != "/":
        path = path.replace(os.sep, "/")
    return path

def exportMesh(mainUI, exportMeshes, exportPath, removeBones, poseDict):
    """
    Take the passed in information and export skeletal meshes to FBX format in the specified output path.
    Invoked from ART_ExportMeshes.py

    :param mainUI: instance of rig creator interface. Used to access rig creator class variables
    :param exportMeshes: list of meshes to export for the given LOD
    :param exportPath: output path of FBX file for the given LOD
    :param removeBones: which bones, if any, to remove for the given LOD
    :param poseDict: transform values for bones to be applied to given LOD before removing bones, in order to bake in a\
    pose.

    Details:

    When exporting a LOD skeletal mesh, there are many steps that need to happen:
        * First, the LOD needs to go back to the model pose. If there are any joints this LOD needs to remove, we must
          save out the skin weight information first.
        * If there is a LOD pose to set, set the pose then while the mesh is still skinned.
        * After setting the LOD pose, search for any connected blendshapes the meshes may have. If there are not true
          copies of the mesh in the scene, manually create new morph target copies by going through each blendshape
          attribute turning them on, one by one, duplicating the mesh each time.
        * After checking for blendshapes, delete history on the meshes. This will bake the LOD pose in.
        * Next, re-apply the blendshapes now that the pose is baked in and import the skin weights.
        * If there were bones to remove, before removing the joints, transfer the weighting of those bones to the bone
          that was specified to transfer weighting to. Usually this is the first valid parent. So if removing all finger
          bones in an LOD, you would likely transfer all finger weights to the hand bone.
        * Once the weighting is transferred, it is then safe to delete the bones that were to be removed.
        * Now, we start building our selection of things to export. First though, we will check for any incoming
          connections to our joints that we want to export, and break those connections. These are usually connections
          to the rig, and we don't want FBX exporting all of that stuff.
        * Build the selection (joints and morphs) and export FBX
    """

    # before starting this function, the file should be saved (by whichever function is calling this function)

    # make sure fbx plugin is loaded
    cmds.loadPlugin("fbxmaya")

    # set FBX settings
    mel.eval("FBXExportSmoothingGroups -v 1")
    mel.eval("FBXExportSmoothMesh -v 0")
    mel.eval("FBXExportTangents -v 1")
    mel.eval("FBXExportUpAxis Z")
    mel.eval("FBXExportCameras -v 0")
    mel.eval("FBXExportConstraints -v 0")
    mel.eval("FBXExportShapes -v 1")
    mel.eval("FBXExportSkins -v 1")

    # Create a progress bar
    exportFileName = os.path.basename(exportPath)
    title = os.path.splitext(exportFileName)[0]

    progBar = interfaceUtils.ProgressBar(title)
    progBar.setTextVisible(True)
    progBar.show()

    # find the value range
    maxVal = 4 + len(exportMeshes)

    if poseDict is not None:
        maxVal += 1
    if removeBones is not None:
        maxVal += len(exportMeshes) * 4

    progBar.setRange(0, maxVal)
    progBar.setValue(0)

    # go to model pose
    progBar.setFormat("Setting Model Pose on Modules..")
    progBar.setValue(progBar.value() + 1)

    for inst in mainUI.moduleInstances:
        try:
            inst.setupForRigPose()
            inst.setReferencePose("modelPose")
            inst.cleanUpRigPose()
        except:
            pass

    # if joints to remove, save out skinning
    import System.riggingUtils as riggingUtils

    weightFiles = []
    if exportMeshes and removeBones or removeBones is not None:

        for mesh in exportMeshes:
            skinCluster = riggingUtils.findRelatedSkinCluster(mesh)

            # find a temporary place to save the weights out to
            fullPath = os.path.join(cmds.internalVar(uwd=True), "artv2")
            fullPath = os.path.join(fullPath, "weights")
            if not os.path.exists(fullPath):
                os.makedirs(fullPath)

            meshCheck = False
            # do a check to make sure we don't have naming conflicts
            if mesh.find("|") != -1:
                shortName = mesh.rpartition("|")[2]
                cmds.select("*" + shortName + "*")
                selection = cmds.ls(sl=True, long=False, transforms=True, shapes=False)
                if len(selection) > 1:
                    string = ""
                    for each in selection:
                        string += each + "\n"

                    # launch a message box informing of the issue
                    msgBox = QtWidgets.QMessageBox()
                    msgBox.setWindowTitle("Naming Conflict")
                    msgBox.setText(
                        "More than one object shares the same name. Names should be unique.\
                        Please fix and try the operation again.")
                    msgBox.setInformativeText(string)
                    msgBox.setIcon(QtWidgets.QMessageBox.Critical)
                    ret = msgBox.exec_()

                    # clean up
                    if ret == QtWidgets.QMessageBox.Ok:
                        for file in weightFiles:
                            os.remove(file)
                    return

                else:
                    meshCheck = True
            else:
                meshCheck = True

            if meshCheck:
                fullPath = os.path.join(fullPath, mesh + ".weights")
                fullPath = returnFriendlyPath(fullPath)

                # add file to our list so we can delete later
                weightFiles.append(fullPath)

                # create skin class instance and save weights to location
                progBar.setFormat("Exporting Skin Weights..")
                progBar.setValue(progBar.value() + 1)
                skin = riggingUtils.export_skin_weights(fullPath, mesh)
                print skin
                skin.exportSkinWeights(fullPath)

    if exportMeshes is None:
        msgBox.setWindowTitle("Export Error")
        msgBox.setText("No meshes assigned to this LOD.")
        msgBox.setIcon(QtWidgets.QMessageBox.Critical)
        msgBox.exec_()
        return

    # if LOD pose exists, set LOD pose (meshes are still weighted)
        progBar.setFormat("Setting LOD Pose..")
        progBar.setValue(progBar.value() + 1)
        for key in poseDict:
            data = poseDict.get(key)
            if cmds.objExists(key):
                for each in data:
                    # translate, rotate, scale
                    if each == data[0]:
                        cmds.setAttr(key + ".translateX", each[0])
                        cmds.setAttr(key + ".translateY", each[1])
                        cmds.setAttr(key + ".translateZ", each[2])

                    if each == data[1]:
                        cmds.setAttr(key + ".rotateX", each[0])
                        cmds.setAttr(key + ".rotateY", each[1])
                        cmds.setAttr(key + ".rotateZ", each[2])

                    if each == data[2]:
                        cmds.setAttr(key + ".scaleX", each[0])
                        cmds.setAttr(key + ".scaleY", each[1])
                        cmds.setAttr(key + ".scaleZ", each[2])

    # delete the history on the meshes after setting the pose

            deleteShapes = []

            blendshapes = cmds.ls(type="blendShape")
            for blendshape in blendshapes:
                geo = cmds.blendShape(blendshape, q=True, geometry=True)
                if geo is not None:
                    geo = cmds.listRelatives(geo, parent=True)
                    if geo is not None:
                        if geo[0] == mesh:
                            attrs = cmds.listAttr(blendshape, m=True, string="weight")
                            if attrs is not None:
                                for attr in attrs:
                                    # if not, manually create shapes by toggling attrs and duplicating mesh
                                    if not cmds.objExists(attr):
                                        cmds.setAttr(blendshape + "." + attr, 1)
                                        dupe = cmds.duplicate(mesh)[0]

                                        # parent to world
                                        parent = cmds.listRelatives(dupe, parent=True)
                                        if parent is not None:
                                            cmds.parent(dupe, world=True)

                                        # rename the duplicate mesh to the blendshape name
                                        cmds.rename(dupe, attr)
                                        cmds.setAttr(blendshape + "." + attr, 0)
                                        deleteShapes.append(attr)

                                # add the blendshape node name and its attrs to the master blendshape list
                                blendshapeList.append([blendshape, attrs])

            # delete history
            cmds.delete(mesh, ch=True)

                i = 1
                        cmds.blendShape(bshapeName, edit=True, t=(mesh, i, shape, 1.0))

                    else:
                        cmds.select([shape, mesh], r=True)

            for each in deleteShapes:
                cmds.delete(each)
        progBar.setFormat("Deleting Mesh History..")

    # go back to model pose
        try:
        except:
            pass

    # import skin weights

            progBar.setFormat("Importing Skin Weights")
            progBar.setValue(progBar.value() + 1)

            fullPath = os.path.join(fullPath, mesh + ".weights")
            fullPath = returnFriendlyPath(fullPath)
            if os.path.exists(fullPath):
                riggingUtils.import_skin_weights(fullPath, mesh, True)

    for f in weightFiles:
        os.remove(f)

    # transfer weighting information
    for mesh in exportMeshes:
        skinCluster = riggingUtils.findRelatedSkinCluster(mesh)

        # prune weights
        cmds.skinPercent(skinCluster, mesh, prw=0.001)

        # remove unused influences
        weightedInfs = cmds.skinCluster(skinCluster, q=True, weightedInfluence=True)
        allInfs = cmds.skinCluster(skinCluster, q=True, inf=True)
        for inf in allInfs:
            if inf not in weightedInfs:
                cmds.skinCluster(skinCluster, edit=True, ri=inf)

        # get mesh influences
        meshInfluences = cmds.skinCluster(skinCluster, q=True, wi=True)

        # transfer weighting
        if removeBones is not None:
            for entry in removeBones:
                transferBone = entry[0]
                deleteBones = entry[1]

                for joint in deleteBones:
                    if joint in meshInfluences:

                        currentInfluences = cmds.skinCluster(skinCluster, q=True, inf=True)
                        if transferBone not in currentInfluences:
                            cmds.skinCluster(skinCluster, e=True, wt=0, ai=transferBone)

                        for x in range(cmds.polyEvaluate(mesh, v=True)):
                            value = cmds.skinPercent(skinCluster, (mesh + ".vtx[" + str(x) + "]"), t=joint, q=True)
                            if value > 0:
                                cmds.skinPercent(skinCluster, (mesh + ".vtx[" + str(x) + "]"),
                                                 tmw=[joint, transferBone])
        progBar.setFormat("Transferring Weighting..")

    # delete joints to remove
        progBar.setFormat("Deleting Chosen Bones..")

        for entry in removeBones:
            deleteBones = entry[1]
            for bone in deleteBones:
                try:
                    cmds.delete(bone)
                    print "Unable to remove joint: " + str(bone)
                    print e

    # handle morph targets
    blendShapeNodes = []
    blendShapeMeshes = []

        progBar.setFormat("Checking for Morph Targets..")

        blendshapes = cmds.ls(type="blendShape")
        for blendshape in blendshapes:
            geo = cmds.blendShape(blendshape, q=True, geometry=True)
            if geo is not None:
                geo = cmds.listRelatives(geo, parent=True)

                    if geo[0] == mesh:
                        blendShapeNodes.append(blendshape)
                        # get blendshapes
                        attrs = cmds.listAttr(blendshape, m=True, string="weight")
                                if cmds.objExists(attr):
                                    blendShapeMeshes.append(attr)

    # before building selection, remove any connections from our skeleton
    progBar.setFormat("Removing Connections on Skeleton..")

    cmds.select("root", hi=True)
    selection = cmds.ls(sl=True)
    for each in selection:
        connT = cmds.connectionInfo(each + ".translate", sourceFromDestination=True)
        if connT != '':
            cmds.disconnectAttr(connT, each + ".translate")
        connR = cmds.connectionInfo(each + ".rotate", sourceFromDestination=True)
        if connR != '':
            cmds.disconnectAttr(connR, each + ".rotate")
        connS = cmds.connectionInfo(each + ".scale", sourceFromDestination=True)
        if connS != '':
            cmds.disconnectAttr(connS, each + ".scale")

    # export (select root, geometry, and any morphs)

    # build selection
    progBar.setFormat("Building Selection For Export..")

        cmds.select(mesh, add=True)
    cmds.select("root", add=True)
    for bShapeMesh in blendShapeMeshes:
        cmds.select(bShapeMesh, add=True)

    progBar.setFormat("FBX Exporting...")
    progBar.setValue(progBar.maximum())

    mel.eval("FBXExport -f \"" + exportPath + "\" -s")

def findExportMeshData():
    """
    Search through the character node LOD attributes to find out what and how to export the LOD.
    This data included the LOD pose, the bones to remove, the meshes for the LOD, the output path
    for the FBX, and the LOD name.

    :return: [output path for FBX, list of meshes to export for LOD, list of bones to remove for LOD,
             pose to put LOD in before removing bones, the LOD number]
    """

    lodAttrs = getLodData()
    characterNode = returnCharacterModule()

    if lodAttrs is not None:
        # get json data from attribute
        lodData = ["_Pose", "_Bones", "_Meshes", "_FilePath", "LOD"]

    returnData = []

    for attr in lodAttrs:

        meshValue = None
        boneValue = None
        pathValue = None
        poseData = None
        lodNumber = None

        for entry in lodData:
            if cmds.objExists(characterNode + "." + attr + entry):
                if entry == "_Bones":
                    try:
                        boneValue = json.loads(cmds.getAttr(characterNode + "." + attr + entry))
                    except:
                        pass
                if entry == "_Meshes":
                    try:
                        meshValue = cmds.listConnections(characterNode + "." + attr + entry)
                    except:
                        cmds.warning("No meshes assigned to this LOD.")
                        pass
                if entry == "_FilePath":
                    pathValue = json.loads(cmds.getAttr(characterNode + "." + attr + entry))
                if entry == "_Pose":
                    try:
                        poseData = json.loads(cmds.getAttr(characterNode + "." + attr + entry))
                    except:
                        pass
            else:
                if entry == "LOD":
                    lodNumber = attr

        # append to return data
        returnData.append([pathValue, meshValue, boneValue, poseData, lodNumber])

    return returnData

def getLodData():
    """
    Search the character node for and LOD related attributes and return those.

    :return: list of attributes on character node that contain LOD export data.
    """

    # get mesh info from character node
    attrs = cmds.listAttr(characterNode, ud=True, string="LOD_*_FilePath")

    lodAttrs = []
    if attrs is not None:
        for attr in attrs:
            lodAttrs.append(attr.partition("_FilePath")[0])

    return lodAttrs

def returnFriendlyPath(path):
    """
    Take the incoming path and replace back slashes with forward slashes

    :param path: directory or file path to replace back slashes in
    :return: a directory or file path with only forward slashes
    """

    nicePath = os.path.normpath(path)
    if nicePath.partition("\\")[2] != "":
        nicePath = nicePath.replace("\\", "/")
    return nicePath

def returnNicePath(toolsPath, imagePath):
    """
    Take the incoming path and file and use os.path.join to create a new file path. Then replace all
    back slashes with forward slashes.

    :param toolsPath: the base directory path
    :param imagePath: the file name to join onto the base path
    :return: a joined path with only forward slashes
    """

    image = os.path.normpath(os.path.join(toolsPath, imagePath))
    if image.partition("\\")[2] != "":
        image = image.replace("\\", "/")
    return image

def splitMesh(mesh, assetName):
    """
    Take the given mesh and break it into chunks based on the influence weights of each bone. For example,
    if the mesh was a leg that was skinned to a thigh bone, a calf bone, and a foot bone, this function will
    split the leg mesh into three new meshes, one for each major influence. This is sometimes known as an
    "anim mesh"

    :param mesh: name of mesh to split up
    :param assetName: name of character or rig (the name given on publish)
    :return: a list of the newly created meshes
    """

    # get mesh's skinCluster
    skinCluster = riggingUtils.findRelatedSkinCluster(mesh)

    # get the influences in that skinCluster
    influences = cmds.skinCluster(skinCluster, q=True, inf=True)

    # create a group if it doesn't exist
    if not cmds.objExists(assetName + "_animMeshGrp"):
        cmds.group(empty=True, name=assetName + "_animMeshGrp")

    newMeshes = []
    # loop through each influence, creating a mesh for that influnece is applicable
    for influence in influences:
        # create a new mesh
        if cmds.objExists(mesh + "_" + influence):
            cmds.warning("Mesh with name: " + mesh + "_" + influence + " already exists. Skipping.")
        else:
            newMesh = cmds.duplicate(mesh, name=mesh + "_" + influence)[0]

            # unlock attrs so we can add a constraint later
            for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
                cmds.setAttr(newMesh + attr, lock=False)

            # if there is only 1 influence, constrain the entire mesh as is
            if len(influences) <= 1:
                cmds.parentConstraint(influence, newMesh, mo=True)

            # otherwise, loop through each influence getting the components affected by that influence
            else:
                verts = []
                notWeighted = []

                for i in range(cmds.polyEvaluate(mesh, v=True)):
                    value = cmds.skinPercent(skinCluster, mesh + ".vtx[" + str(i) + "]", transform=influence, q=True)

                    if value > 0.5:
                        verts.append(newMesh + ".vtx[" + str(i) + "]")

                    else:
                        notWeighted.append(newMesh + ".vtx[" + str(i) + "]")

                # if the amount of non-weighted verts is the same as the number of verts in the mesh, delete the mesh.
                if len(notWeighted) == cmds.polyEvaluate(mesh, v=True):
                    cmds.delete(newMesh)

                if verts:
                    # select all verts
                    cmds.select(newMesh + ".vtx[*]")

                    # Convert the selection to contained faces
                    if len(verts) != cmds.polyEvaluate(mesh, v=True):
                        # unselect the verts we want to keep, convert remaining to faces and delete
                        cmds.select(verts, tgl=True)
                        cmds.select(cmds.polyListComponentConversion(fv=True, tf=True, internal=False))
                        cmds.delete()

                    # constrain mesh to influence, parent mesh to group
                    cmds.parentConstraint(influence, newMesh, mo=True)
                    cmds.parent(newMesh, assetName + "_animMeshGrp")

                    # fill holes, triangulate, and smooth normals
                    cmds.polyCloseBorder(newMesh, ch=False)
                    cmds.polyTriangulate(newMesh, ch=False)
                    cmds.polySoftEdge(newMesh, a=90, ch=False)
                    newMeshes.append(newMesh)

    return (newMeshes)

def findAllSkinnableGeo():
    """
    Find all meshes in the scene, list their parents. If the parent is not part of the joint mover (proxy geo,
    lra representations, or bone representation geo) add it to the list of meshes that can be skinned.

    :return: a list of geometry that is valid for skinning.
    """

    meshes = cmds.ls(type="mesh")
    skinnableGeo = []

    for mesh in meshes:
        parent = cmds.listRelatives(mesh, parent=True, type="transform")[0]
        if parent is not None:
            if parent.find("proxy_geo") == -1:
                if parent.find("lra") == -1:
                    if parent.find("bone_geo") == -1:
                        skinnableGeo.append(parent)
            if parent.find("proxy_geo") != -1:
                # get that parent
                transformParent = cmds.listRelatives(parent, parent=True)
                if transformParent is not None:
                    if transformParent[0].find("skinned") == 0:

    skinnableGeo = set(skinnableGeo)
    return skinnableGeo

def getMayaPyLoc():
    """
    This function finds the location of the mayapy interpreter for batch functions.

    :return: mayapy location
    """

    operatingSystem = cmds.about(operatingSystem=True)
    windows = ["win64", "nt"]
    linux = ["linux", "linux64"]
    mac = ["mac"]

    mayaLoc = None
    mayapy = None

    for key in sorted(os.environ.keys()):
        if key.find("MAYA_LOCATION") != -1:
            mayaLoc = os.environ[key]

    if operatingSystem in windows:
        mayapy = returnFriendlyPath(os.path.join(mayaLoc, "bin"))
        mayapy = returnFriendlyPath(os.path.join(mayapy, "mayapy.exe"))

    else:
        # need to confirm this works on Linux and Mac OS
        mayapy = returnFriendlyPath(os.path.join(mayapy, "mayapy"))

    return mayapy

"""
"""

from ThirdParty.Qt import QtCore, QtWidgets

class ART_BakeOffsets():
    """
    This clas builds a UI that allows the rigger to select modules to bake offset mover values up to the global mover
    controls, making the offset movers no longer offset from their global mover parents. It can be found on the rig
    creator toolbar with the following icon:
        .. image:: /images/bakeOffsetsButton.png

    The full interface looks like this, listing found modules in the scene to select and bake offsets down to.
        .. image:: /images/bakeOffsets.png

    """

        """
        Instantiates the class, getting the QSettings and then calling on ART_BakeOffsetsUI.buildBakeOffsetsUI to
        create the interface.

        :param mainUI: The instance of the Rig Creator UI where this class was called from.

        .. seealso:: ART_BakeOffsetsUI.buildBakeOffsetsUI

        """

        self.buildBakeOffsetsUI()

    def buildBakeOffsetsUI(self):
        """
        Builds the interface, finding all modules and listing them for selection.

        """

        if cmds.window("ART_BakeOffsetsWin", exists=True):
            cmds.deleteUI("ART_BakeOffsetsWin", wnd=True)

        self.bakeOffsetsWin = QtWidgets.QMainWindow(self.mainUI)

        f.close()

        self.bakeOffsetsWin.setStyleSheet(self.style)

        self.bakeOffsetsWin_mainWidget = QtWidgets.QWidget()
        self.bakeOffsetsWin.setCentralWidget(self.bakeOffsetsWin_mainWidget)

        self.bakeOffsetsWin.setObjectName("ART_BakeOffsetsWin")
        self.bakeOffsetsWin.setWindowTitle("Bake Offsets")

        self.bakeOffsetsWin_mainLayout = QtWidgets.QVBoxLayout(self.bakeOffsetsWin_mainWidget)
        self.bakeOffsetsWin_mainLayout.setContentsMargins(0, 0, 0, 0)

        self.bakeOffsetsWin.resize(400, 250)
        self.bakeOffsetsWin.setSizePolicy(mainSizePolicy)
        self.bakeOffsetsWin.setMinimumSize(QtCore.QSize(400, 250))
        self.bakeOffsetsWin.setMaximumSize(QtCore.QSize(400, 250))

        # create the background
        self.bakeOffsetsWin_frame = QtWidgets.QFrame()
        self.bakeOffsetsWin_mainLayout.addWidget(self.bakeOffsetsWin_frame)

        self.bakeOffsetsWin_widgetLayout = QtWidgets.QHBoxLayout(self.bakeOffsetsWin_frame)
        self.bakeOffsetsWin_widgetLayout.setContentsMargins(5, 5, 5, 5)

        self.bakeOffsetsWin_moduleListFrame = QtWidgets.QFrame()
        self.bakeOffsetsWin_moduleListFrame.setMinimumSize(QtCore.QSize(265, 200))
        self.bakeOffsetsWin_moduleListFrame.setMaximumSize(QtCore.QSize(265, 200))
        self.bakeOffsetsWin_moduleListFrame.setContentsMargins(20, 0, 20, 0)

        self.bakeOffsetsWin_moduleList = QtWidgets.QListWidget(self.bakeOffsetsWin_moduleListFrame)
        self.bakeOffsetsWin_widgetLayout.addWidget(self.bakeOffsetsWin_moduleListFrame)
        self.bakeOffsetsWin_moduleList.setMinimumSize(QtCore.QSize(265, 200))
        self.bakeOffsetsWin_moduleList.setMaximumSize(QtCore.QSize(265, 200))
        self.bakeOffsetsWin_moduleList.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
        self.bakeOffsetsWin_moduleList.setSpacing(3)

        self.bakeOffsetsWin_buttonLayoutAll = QtWidgets.QVBoxLayout()
        self.bakeOffsetsWin_widgetLayout.addLayout(self.bakeOffsetsWin_buttonLayoutAll)
        self.bakeOffsetsWin_buttonLayoutAll.setContentsMargins(5, 20, 5, 20)

        self.bakeOffsetsWin_selectionButtonLayout = QtWidgets.QVBoxLayout()
        self.bakeOffsetsWin_buttonLayoutAll.addLayout(self.bakeOffsetsWin_selectionButtonLayout)
        self.bakeOffsetsWin_selectAllButton = QtWidgets.QPushButton("Select All")
        self.bakeOffsetsWin_selectAllButton.setMinimumSize(QtCore.QSize(115, 25))
        self.bakeOffsetsWin_selectAllButton.setMaximumSize(QtCore.QSize(115, 25))
        self.bakeOffsetsWin_selectionButtonLayout.addWidget(self.bakeOffsetsWin_selectAllButton)
        self.bakeOffsetsWin_selectAllButton.clicked.connect(self.bakeOffsetsWin_moduleList.selectAll)
        self.bakeOffsetsWin_selectAllButton.setObjectName("blueButton")

        self.bakeOffsetsWin_selectNoneButton = QtWidgets.QPushButton("Clear Selection")
        self.bakeOffsetsWin_selectNoneButton.setMinimumSize(QtCore.QSize(115, 25))
        self.bakeOffsetsWin_selectNoneButton.setMaximumSize(QtCore.QSize(115, 25))
        self.bakeOffsetsWin_selectionButtonLayout.addWidget(self.bakeOffsetsWin_selectNoneButton)
        self.bakeOffsetsWin_selectNoneButton.clicked.connect(self.bakeOffsetsWin_moduleList.clearSelection)
        self.bakeOffsetsWin_selectNoneButton.setObjectName("blueButton")

        # spacer
        self.bakeOffsetsWin_selectionButtonLayout.addItem(spacerItem)

        self.bakeOffsetsWin_bakeOFfsetsBtn = QtWidgets.QPushButton("Bake Offsets")
        self.bakeOffsetsWin_bakeOFfsetsBtn.setMinimumSize(QtCore.QSize(115, 25))
        self.bakeOffsetsWin_bakeOFfsetsBtn.setMaximumSize(QtCore.QSize(115, 25))
        self.bakeOffsetsWin_selectionButtonLayout.addWidget(self.bakeOffsetsWin_bakeOFfsetsBtn)
        self.bakeOffsetsWin_bakeOFfsetsBtn.setToolTip("Turn on Aim Mode for selected modules.")
        self.bakeOffsetsWin_bakeOFfsetsBtn.clicked.connect(partial(self.bakeOffsets))
        self.bakeOffsetsWin_bakeOFfsetsBtn.setObjectName("blueButton")

            if moduleName != "root":
                self.bakeOffsetsWin_moduleList.addItem(moduleName)

        self.bakeOffsetsWin.show()

    def bakeOffsets(self):
        """
        Tales selected modules listed in the QListWidget and bakes the offset mover values up to the global mover
        parent. This is achieved by creating a locator in the space of the offset mover, zeroing the offset mover
        out, and constraining the global mover to the constraint. Finally, the constraints are removed and the
        locators deleted.

        """

        selected = self.bakeOffsetsWin_moduleList.selectedItems()
        items = []

        constraints = []
        locators = []

        # go through each module and create the locators for the offset movers

                # get movers
                jointMovers = each.returnJointMovers

                # separate mover lists
                globalMovers = jointMovers[0]
                offsetMovers = jointMovers[1]

                # create locators for the offsetMovers, then zero out offset mover
                for mover in offsetMovers:
                    locatorName = mover.partition("_offset")[0] + "_loc"
                    loc = cmds.spaceLocator(name=locatorName)[0]

                    # constrain locator
                    constraint = cmds.parentConstraint(mover, loc)[0]
                    cmds.delete(constraint)

                    # parent locator under a copy of the locatorName
                    parentLoc = cmds.duplicate(loc)[0]
                    cmds.parent(loc, parentLoc)
                    locators.append(parentLoc)

                    for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
                        try:
                            cmds.setAttr(mover + attr, 0)
                        except:
                            pass

        # now, for each global mover, find child groups that are constrained to them

                childGrps = []

                for mover in globalMovers:
                    try:

                        # find the mover group constrained to this mover (if any)
                        connection = cmds.listConnections(mover, type="parentConstraint")[0]
                        grp = cmds.listConnections(connection + ".constraintRotateX")[0]
                        if grp.find("_mover_grp") != -1:
                            childGrps.append([mover, grp])

                        # now take this group and delete the constraints
                        cmds.select(grp)
                        cmds.delete(constraints=True)

                        print e

        # now snap global movers to offset movers

                    if cmds.objExists(mover + "_loc"):
                        constraint = cmds.parentConstraint(mover + "_loc", mover)[0]
                        constraints.append(constraint)

        # now remove all locs
        for const in constraints:
            cmds.delete(const)

        for loc in locators:
            cmds.delete(loc)

        # finally, reconstrain mover_grps to their movers
        for group in childGrps:
            cmds.parentConstraint(group[0], group[1], mo=True)
            cmds.scaleConstraint(group[0], group[1])

        cmds.select(clear=True)

"""
"""

try:
except:

    """
    Returns a pointer to Maya's window as a QWidget.

    """

windowTitle = "Change Module Name"
windowObject = "pyArtChangeModuleNameUi"

class ART_ChangeModuleName_UI(QtWidgets.QMainWindow):
    """
    This class allows the user to change the prefix or suffix or both, of a given module. It is found within the
    skeletonSettingsUI of an individual module in the Rig Creator.

        .. image:: /images/changeModName.png

    """

    def __init__(self, baseName, moduleInst, rigUiInst, prefix, suffix, parent=None):
        """
        Instantiates the class, taking in current module information, and builds the interface.

        :param baseName: The base name of the module, found on the network node attribute.
        :param moduleInst: The instance in memory of the module whose name is to change.
        :param rigUiInst: The instance in memory of the Rig Creator UI from which this class was called.
        :param prefix: The existing prefix of the module name.
        :param suffix: The existing suffix of the module name.

        """

        super(ART_ChangeModuleName_UI, self).__init__(parent)

        # create class variables
        self.baseName = baseName
        self.modInst = moduleInst
        self.rigUiInst = rigUiInst
        self.prefixInc = prefix
        self.suffixInc = suffix

        style = f.read()
        f.close()

        self.setStyleSheet(style)

        self.setWindowTitle(windowTitle)

        self.mainLayout = QtWidgets.QVBoxLayout(self.mainWidget)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)

        self.resize(300, 150)
        self.setMinimumSize(QtCore.QSize(300, 150))
        self.setMaximumSize(QtCore.QSize(300, 150))

        self.mainLayout.addWidget(self.frame)

        self.widgetLayout = QtWidgets.QVBoxLayout(self.frame)

        # create the prefix pair of fields
        self.prefixForm = QtWidgets.QFormLayout()
        self.widgetLayout.addLayout(self.prefixForm)

        self.prefixLabel = QtWidgets.QLabel("Prefix: ")
        self.prefixForm.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.prefixLabel)

        self.prefix = QtWidgets.QLineEdit(self.prefixInc)
        self.prefixForm.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.prefix)

        # hookup signal/slot connection
        self.prefix.textChanged.connect(self.updatePreview)

        # create the suffix pair of fields
        self.suffixForm = QtWidgets.QFormLayout()
        self.widgetLayout.addLayout(self.suffixForm)

        self.suffixLabel = QtWidgets.QLabel("Suffix: ")
        self.suffixForm.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.suffixLabel)

        self.suffix = QtWidgets.QLineEdit(self.suffixInc)
        self.suffixForm.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.suffix)

        self.suffix.textChanged.connect(self.updatePreview)

        # spacer
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.widgetLayout.addItem(spacerItem)

        # realtime preview of final module name
        self.previewForm = QtWidgets.QFormLayout()
        self.widgetLayout.addLayout(self.previewForm)
        self.previewLabel = QtWidgets.QLabel("Preview: ")
        self.previewName = QtWidgets.QLabel(self.prefixInc + self.baseName + self.suffixInc)
        self.previewName.setMinimumSize(QtCore.QSize(200, 20))
        self.previewName.setMaximumSize(QtCore.QSize(200, 20))
        self.previewName.setAlignment(QtCore.Qt.AlignHCenter)
        self.previewForm.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.previewLabel)
        self.previewForm.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.previewName)

        # set preview font
        self.previewName.setFont(font)

        spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.widgetLayout.addItem(spacerItem1)

        # update button
        self.updateBtn = QtWidgets.QPushButton("UPDATE")
        self.widgetLayout.addWidget(self.updateBtn)
        self.updateBtn.setMinimumSize(QtCore.QSize(285, 40))
        self.updateBtn.setMaximumSize(QtCore.QSize(285, 40))
        self.updateBtn.setSizePolicy(mainSizePolicy)
        self.updateBtn.setFont(font)
        self.updateBtn.setObjectName("blueButton")

        # hookup signal/slot on create button
        self.updateBtn.clicked.connect(self.applyModuleNameChange)

    def updatePreview(self):
        """
        Updates the QLabel with the current prefix + basename + suffix, adding in underscores where needed.

        """

        prefix = str(self.prefix.text())
        suffix = str(self.suffix.text())

        string = ""
        if len(prefix) > 0:
            string += prefix + "_"

        string += self.baseName

        if len(suffix) > 0:
            string += "_" + suffix

        self.previewName.setText(string)

    def applyModuleNameChange(self):
        """
        Checks to make sure a module doesn't exist with the new name, and if not, updates naming of the module in a
        multitude of places. Any UI elements, joint movers, attribute values (like .Created_Bones), etc.

        .. note::
            The following things get updated:
                  * QGroupBox label of the SkeletonSettingsUI
                  * Network Node .moduleName attribute
                  * Module Instance variable of self.name gets updated
                  * Created_Bones attribute values
                  * Joint Mover Nodes
                  * Rig Creator Outliner names for module
                  * Selection Script Job for outliner
                  * Any modules' attributes that have a value that matches the old name (like parent module bone)
                  * Any modules that are a mirror of this module, and their mirrorModule attribute value

        """

        # check to see if a module already has that name. If so, return out and do not continue
        validName = False
        msg = "A module with that name already exists. Please enter a unique name for the module"

            name = cmds.getAttr(module + ".moduleName")
            if name == str(self.previewName.text()):
                cmds.confirmDialog(title="Name Exists", message=msg, icon="critical")
                return

        # update groupbox label
        originalName = self.modInst.groupBox.title()
        self.modInst.groupBox.setTitle(str(self.previewName.text()))

        # update network node moduleName attribute
        networkNode = self.modInst.returnNetworkNode
        cmds.setAttr(networkNode + ".moduleName", lock=False)
        cmds.setAttr(networkNode + ".moduleName", str(self.previewName.text()), type="string", lock=True)

        # update self.name for rig module
        self.modInst.name = str(self.previewName.text())

        # update created bones attribute values

            prefix += prefix + "_"
            suffix = "_" + suffix

        createdBones = self.modInst.returnCreatedJoints

        attrString = ""

        for bone in createdBones:
            niceName = bone
            if len(bone) > 1:
                if self.prefixInc != "":
                    niceName = bone.partition(self.prefixInc)[2]
                if self.suffixInc != "":
                    niceName = niceName.partition(self.suffixInc)[0]

                attrString += prefix + niceName + suffix + "::"

        cmds.setAttr(networkNode + ".Created_Bones", lock=False)
        cmds.setAttr(networkNode + ".Created_Bones", attrString, type="string", lock=True)

        # joint mover renaming
        cmds.select(originalName + "_mover_grp", hi=True)
        jointMoverNodes = cmds.ls(sl=True, type="transform")
        constraints = ["pointConstraint", "orientConstraint", "parentConstraint"]

        for node in jointMoverNodes:
            try:
                if cmds.nodeType(node) not in constraints:
                    locked = cmds.lockNode(node, q=True)
                    if locked:
                        cmds.lockNode(node, lock=False)
                    nodeName = node.partition(originalName)[2]
                    newName = self.modInst.name + nodeName
                    cmds.rename(node, newName)

                    if locked:
                        cmds.lockNode(node, lock=True)
                pass

        # update outliner names
        utils.findAndRenameOutlinerChildren(self.modInst.outlinerWidgets[self.modInst.originalName + "_treeModule"],
                                            originalName, self.modInst.name)
        self.modInst.outlinerWidgets[self.modInst.originalName + "_treeModule"].setText(0, self.modInst.name)

        # find module's selection scriptJob and delete it
        jobs = cmds.scriptJob(lj=True)
        for job in jobs:
            compareString = job.partition(":")[0]
            if str(self.modInst.scriptJob) == str(compareString):
                cmds.scriptJob(kill=self.modInst.scriptJob)
                break

        # replace module's outliner control entries with the new control name
        for item in self.modInst.outlinerControls:
            item[1] = item[1].replace(originalName, self.modInst.name)

        # create the selection script job again
        self.modInst.createScriptJob()

        # find any modules using any of the created joints from this module that match the OLD name
        createdJoints = self.modInst.returnCreatedJoints
        modules = self.modInst.getAllModules

        for mod in modules:
            attrs = cmds.listAttr(mod)
            if "parentModuleBone" in attrs:
                value = cmds.getAttr(mod + ".parentModuleBone")
                if value in createdBones:
                    index = createdBones.index(value)

                    # update those modules' network node parentModuleBone attribute
                    cmds.setAttr(mod + ".parentModuleBone", lock=False)
                    cmds.setAttr(mod + ".parentModuleBone", createdJoints[index], type="string", lock=True)

                    # and also those modules' skeletonSettingsUI currentParent label
                    modName = cmds.getAttr(mod + ".moduleName")

                    for each in self.rigUiInst.moduleInstances:
                        if each.networkNode == mod:
                            # find the current groupBox for this module
                            for i in range(self.rigUiInst.moduleSettingsLayout.count()):
                                if type(self.rigUiInst.moduleSettingsLayout.itemAt(i).widget()) == QtWidgets.QGroupBox:
                                    if self.rigUiInst.moduleSettingsLayout.itemAt(i).widget().title() == modName:
                                        self.rigUiInst.moduleSettingsLayout.itemAt(i).widget().setParent(None)

                                        # relaunch the skeleton settings UI with new info
                                        each.skeletonSettings_UI(modName)

        # update mirrorModule field
            if "mirrorModule" in attrs:
                value = cmds.getAttr(mod + ".mirrorModule")

                if value == originalName:
                    cmds.setAttr(mod + ".mirrorModule", lock=False)
                    cmds.setAttr(mod + ".mirrorModule", self.modInst.name, type="string", lock=True)

        # delete the UI
        mayaWindow = interfaceUtils.getMainWindow()
        mayaWindow = mayaWindow.objectName()
        cmds.deleteUI(mayaWindow + "|" + windowObject)

"""
"""

windowTitle = "Add Module"
windowObject = "pyArtAddModuleUi"

class ART_AddModule_UI(QtWidgets.QMainWindow):
    """
    This class builds a UI used by the rig creator, and is called when a user pushed a module button to add to their
    rig. This UI presents options like prefix, suffix, and ability to specify the parent module bone.

    .. image:: /images/addModuleUI.png

    """

    def __init__(self, baseName, className, rigUiInst, parent=None):
        """
        Initialize the class, taking in the base name of the module to be added, the name of the class of the module
        to be added, and the instance of the rig creator UI. Then build the interface for the tool.

        :param baseName: The base name of the module to be added, defined in the module class file at the top.
        :param className: The class name of the module to be added, so we can then initialize that module.
        :param rigUiInst: The instance of the rig creator UI, from which this function was called.

        """

        super(ART_AddModule_UI, self).__init__(parent)

        self.className = className

        f.close()

        self.mainLayout = QtWidgets.QHBoxLayout(self.mainWidget)

        self.setMinimumSize(QtCore.QSize(500, 220))
        self.setMaximumSize(QtCore.QSize(500, 520))

        self.column1layout = QtWidgets.QVBoxLayout(self.frame)
        self.mainLayout.addLayout(self.column1layout)

        self.column2Layout = QtWidgets.QVBoxLayout()
        self.mainLayout.addLayout(self.column2Layout)

        label = QtWidgets.QLabel("Choose Parent Bone")
        label.setFont(font)
        label.setAlignment(QtCore.Qt.AlignCenter)
        self.column2Layout.addWidget(label)

        self.boneSearch = QtWidgets.QLineEdit()
        self.column2Layout.addWidget(self.boneSearch)
        self.boneSearch.setPlaceholderText("Search...")
        self.boneSearch.textChanged.connect(self.searchList)

        self.hierarchyTree = QtWidgets.QListWidget()
        self.column2Layout.addWidget(self.hierarchyTree)

        # add items to listWidget
        parents = utils.getViableParents()
        for bone in parents:
            self.hierarchyTree.addItem(bone)

            if bone == "root":
                index = parents.index(bone)
                self.hierarchyTree.setCurrentRow(index)

        self.column1layout.addLayout(self.prefixForm)

        self.prefix = QtWidgets.QLineEdit()

        self.column1layout.addLayout(self.suffixForm)

        self.suffix = QtWidgets.QLineEdit()

        self.previewLabel = QtWidgets.QLabel("Module Name: ")
        self.column1layout.addWidget(self.previewLabel)

        self.previewName = QtWidgets.QLabel(self.baseName)
        self.previewName.setMinimumSize(QtCore.QSize(255, 25))
        self.previewName.setMaximumSize(QtCore.QSize(255, 25))
        self.column1layout.addWidget(self.previewName)

        self.column1layout.addItem(spacerItem1)

        # special cases (arms and legs)
        specialCaseModules = ["ART_Leg_Standard", "ART_Arm_Standard"]
        if className in specialCaseModules:
            # spacer
            groupBox = QtWidgets.QGroupBox("")
            self.column1layout.addWidget(groupBox)
            layout = QtWidgets.QVBoxLayout(groupBox)

            self.radioButtonLayout = QtWidgets.QHBoxLayout()
            layout.addLayout(self.radioButtonLayout)
            self.rightRadioBtn = QtWidgets.QRadioButton("Right Side")
            self.leftRadioBtn = QtWidgets.QRadioButton("Left Side")
            self.radioButtonLayout.addWidget(self.rightRadioBtn)
            self.radioButtonLayout.addWidget(self.leftRadioBtn)
            self.leftRadioBtn.setChecked(True)

        # spacer
        spacerItem2 = QtWidgets.QSpacerItem(20, 80, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.column1layout.addItem(spacerItem2)

        # create button
        self.createButton = QtWidgets.QPushButton("CREATE")
        self.column1layout.addWidget(self.createButton)
        self.createButton.setMinimumSize(QtCore.QSize(255, 40))
        self.createButton.setMaximumSize(QtCore.QSize(255, 40))
        self.createButton.setSizePolicy(mainSizePolicy)
        self.createButton.setObjectName("blueButton")
        self.createButton.setFont(font)

        self.createButton.clicked.connect(self.createModule)

        self.hierarchyTree.setFocus()

        """
        Read the prefix and suffix QLineEdits, append an underscore, and update the previewName QLabel with the
        prefix, basename, and suffix to show what the final module name will be.

        """

        string = ""

    def createModule(self):
        """
        Instantiate our module class to create with the user specified name, creating the network node, building
        the Skeleton Settings UI for the module, adding the joint mover for that module (importing the joint mover
        file), and adding the joint mover to the outliner.

        """

        mod = __import__("RigModules." + self.className, {}, {}, [self.className])

        jmPath = mod.jointMover

        # find the instance of that module and call on the skeletonSettings_UI function
        userSpecName = str(self.previewName.text())

        # check to see if a module already has that name
            if name == userSpecName:
                cmds.confirmDialog(title="Name Exists",
                                   message="A module with that name already exists. Please enter a unique name.",
                                   icon="critical")
                return

        # call functions to create network node, skeleton settings UI
        moduleInst = moduleClass(self.rigUiInst, userSpecName)
        self.rigUiInst.moduleInstances.append(moduleInst)  # add this instance to the ui's list of module instances
        networkNode = moduleInst.buildNetwork()

        # figure out side
        if self.className in specialCaseModules:
            side = "Left"
            if self.rightRadioBtn.isChecked():
                side = "Right"
                cmds.setAttr(networkNode + ".side", lock=False)
                cmds.setAttr(networkNode + ".side", "Right", type="string", lock=True)

            # build new jmPath name
            jmPath = jmPath.partition(".ma")[0] + "_" + side + ".ma"

        moduleInst.skeletonSettings_UI(userSpecName)
        moduleInst.jointMover_Build(jmPath)
        moduleInst.addJointMoverToOutliner()

        # update the created joints attribute on the network node with the new names

            prefix = prefix + "_"

        createdBones = cmds.getAttr(networkNode + ".Created_Bones")
        createdBones = createdBones.split("::")

                attrString += prefix + bone + suffix + "::"

        # update the self.currentParent label and the parentModuleBone attr on the network node
        parent = (self.hierarchyTree.currentItem().text())
        moduleInst.currentParent.setText(parent)

        cmds.setAttr(networkNode + ".parentModuleBone", lock=False)
        cmds.setAttr(networkNode + ".parentModuleBone", parent, type="string", lock=True)

        # parent the joint mover to the offset mover of the parent
        mover = ""

        if parent == "root":
            mover = "root_mover"

        else:
            # find the parent mover name to parent to
            networkNodes = utils.returnRigModules()
            mover = utils.findMoverNodeFromJointName(networkNodes, parent, False, True)

        if mover is not None:
            cmds.parentConstraint(mover, userSpecName + "_mover_grp", mo=True)
            cmds.scaleConstraint(mover, userSpecName + "_mover_grp")

        # create the connection geo between the two
        globalMover = utils.findGlobalMoverFromName(userSpecName)
        cmds.select(globalMover)
        cmds.setToolTo("moveSuperContext")

        utils.fitViewAndShade()

        cmds.deleteUI(windowObject)

        # obey the current UI visibility toggles
        self.rigUiInst.setMoverVisibility()
        moduleInst.updateBoneCount()
        self.rigUiInst.populateNetworkList()

        # turn on aim mode
        moduleInst.aimMode(True)

    def searchList(self):
        """
        Get the search text from the QLineEdit and search the items in the QListWidget for any matches. Anything that
        does not match will be hidden in the QListWidget.

        """

        searchText = self.boneSearch.text()

        for i in range(self.hierarchyTree.count()):
            lwItem = self.hierarchyTree.item(i)
            if lwItem.text().find(searchText) != -1:
                lwItem.setHidden(False)
            else:
                lwItem.setHidden(True)

"""

===============
File Attributes
    * **icon:** This is the image file (125x75 .png) that gets used in the RigCreatorUI

    * **hoverIcon:** When you hover over the module in the module list, it will swap to this icon
      (background changes to orange). There are .psd template files for these.

    * **search:** These are search terms that are accepted when searching the list of modules in the
      RigCreatorUI

    * **class name:** The name of the class.

    * **jointMover:** The relative path to the joint mover file. Relative to the ARTv2 root directory.

    * **baseName:** The default name the module will get created with. Users can then add a prefix and/or
      suffix to the base name.

    * **rigs:** This is a simple list of what rigs this module can build. This feature isn't implemented yet,
      but the plan is to query this list and present these options to the user for them to select what rigs
      they want to build for the module. Right now, it will build all rigs.

    * **fbxImport:** This is a list that will show the options for the module in the import mocap interface.
      Normally, this list will have at least None and FK.

    * **matchData:** This is a list of options that will be presented for the module in a comboBox in the
      match over frame range interface. First argument is  a bool as to whether the module can or can't
      match. The second arg is a list of strings to display for the match options. For example:
      matchData = [True, ["Match FK to IK", "Match IK to FK"]]

    * **controlTypes:** This is a list of lists, where each item in the main list is a list comprised of the
      name of the attribute that gets added to the network node that contains the control information.
      The second arg in each list entry is a control type, like FK or IK. This is used in the select
      rig controls interface for filtering out which controls on each module you want to select. On
      this module, the values are: controlTypes = [["fkControls", "FK"]], which means that the
      attribute that holds the control info is called fkControls, and those controls are of type FK.

        .. image:: /images/selectRigControls.png

Class
"""
# file imports
import os

from System.ART_RigModule import ART_RigModule

# file attributes
icon = "Modules/head.png"
hoverIcon = "Modules/hover_head.png"
search = "biped:head:neck"
className = "ART_Head"
jointMover = "Core/JointMover/ART_Head_1Neck.ma"
baseName = "Head"
rigs = ["FK"]
fbxImport = ["None", "FK"]
matchData = [False, None]  # This is for matching over frame range options. (Matching between rigs of the module)
controlTypes = [["fkControls", "FK"]]

class ART_Head(ART_RigModule):
    """This class creates the head module"""

    def __init__(self, rigUiInst, moduleUserName):
        """Initiate the class, taking in the instance to the interface and the user specified name.

        :param rigUiInst: This is the rig creator interface instance being passed in.
        :param moduleUserName: This is the name specified by the user on module creation.

        Instantiate the following class variables as well:
            * **self.rigUiInst:** take the passed in interface instance and make it a class var
            * **self.moduleUserName:** take the passed in moduleUserName and make it a class var
            * **self.outlinerWidget:** an empty list that will hold all of the widgets added to the outliner

        Also, read the QSettings to find out where needed paths are.
        """

        self.moduleUserName = moduleUserName
        self.outlinerWidgets = {}

        ART_RigModule.__init__(self, "ART_Head_Module", "ART_Head", moduleUserName)

    def addAttributes(self):
        """
        Add custom attributes this module needs to the network node.

        Always calls on base class function first, then extends with any attributes unique to the class.
        """

        # call the base class method first to hook up our connections to the master module
        ART_RigModule.addAttributes(self)

        # add custom attributes for this specific module
        cmds.addAttr(self.networkNode, sn="Created_Bones", dt="string", keyable=False)
        cmds.setAttr(self.networkNode + ".Created_Bones", "neck_01::head::", type="string", lock=True)

        cmds.addAttr(self.networkNode, sn="baseName", dt="string", keyable=False)
        cmds.setAttr(self.networkNode + ".baseName", baseName, type="string", lock=True)

        cmds.addAttr(self.networkNode, sn="canAim", at="bool", keyable=False)
        cmds.setAttr(self.networkNode + ".canAim", False, lock=True)

        cmds.addAttr(self.networkNode, sn="aimMode", at="bool", keyable=False)
        cmds.setAttr(self.networkNode + ".aimMode", False, lock=True)

        cmds.addAttr(self.networkNode, sn="neckJoints", keyable=False)
        cmds.setAttr(self.networkNode + ".neckJoints", 1, lock=True)

    def skeletonSettings_UI(self, name):
        """
        This is the UI for the module that has all of the configuration settings.

        :param name:  user given name of module (prefix + base_name + suffix)
        :param width: width of the skeleton settings groupBox. 335 usually
        :param height: height of the skeleton settings groupBox.
        :param checkable: Whether or not the groupBox can be collapsed.

        Build the groupBox that contains all of the settings for this module. Parent the groupBox
        into the main skeletonSettingsUI layout.
        Lastly, call on updateSettingsUI to populate the UI based off of the network node values.

        .. image:: /images/skeletonSettings.png

        """
        # width, height, checkable

        networkNode = self.returnNetworkNode

        # groupbox all modules get
        ART_RigModule.skeletonSettings_UI(self, name, 335, 228, True)

        # STANDARD BUTTONS

        # create a VBoxLayout to add to our Groupbox and then add a QFrame for our signal/slot
        self.mainLayout = QtWidgets.QVBoxLayout(self.groupBox)
        self.frame = QtWidgets.QFrame(self.groupBox)
        self.frame.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed))
        self.frame.setMinimumSize(QtCore.QSize(320, 210))
        self.frame.setMaximumSize(QtCore.QSize(320, 210))

        # create layout that is a child of the frame
        self.layout = QtWidgets.QVBoxLayout(self.frame)

        # current parent
        self.currentParentMod = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.currentParentMod)
        self.currentParentLabel = QtWidgets.QLabel("Current Parent: ")
        self.currentParentLabel.setFont(font)
        self.currentParentMod.addWidget(self.currentParentLabel)

        parent = cmds.getAttr(networkNode + ".parentModuleBone")
        self.currentParent = QtWidgets.QLabel(parent)
        self.currentParent.setFont(font)
        self.currentParent.setAlignment(QtCore.Qt.AlignHCenter)
        self.currentParentMod.addWidget(self.currentParent)

        # button layout for name/parent
        self.buttonLayout = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.buttonLayout)
        self.changeNameBtn = QtWidgets.QPushButton("Change Name")
        self.changeParentBtn = QtWidgets.QPushButton("Change Parent")
        self.buttonLayout.addWidget(self.changeNameBtn)
        self.buttonLayout.addWidget(self.changeParentBtn)
        self.changeNameBtn.setObjectName("blueButton")
        self.changeParentBtn.setObjectName("blueButton")

        # bake offsets button
        self.bakeToolsLayout = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.bakeToolsLayout)

        # Bake OFfsets
        self.bakeOffsetsBtn = QtWidgets.QPushButton("Bake Offsets")
        self.bakeOffsetsBtn.setFont(headerFont)
        self.bakeToolsLayout.addWidget(self.bakeOffsetsBtn)
        self.bakeOffsetsBtn.clicked.connect(self.bakeOffsets)
        self.bakeOffsetsBtn.setToolTip("Bake the offset mover values up to the global movers to get them in sync")

        self.bakeOffsetsBtn.setObjectName("blueButton")

        # button signal/slots
        self.changeNameBtn.clicked.connect(partial(self.changeModuleName, baseName, self, self.rigUiInst))
        self.changeParentBtn.clicked.connect(partial(self.changeModuleParent, self, self.rigUiInst))

        # Number of Neck Bones
        self.neckLayout = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.neckLayout)

        self.numNeckBonesLabel = QtWidgets.QLabel("Number of Neck Bones: ")
        self.numNeckBonesLabel.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
        self.numNeckBonesLabel.setMinimumSize(QtCore.QSize(200, 20))
        self.numNeckBonesLabel.setMaximumSize(QtCore.QSize(200, 20))
        self.neckLayout.addWidget((self.numNeckBonesLabel))

        self.numNeck = QtWidgets.QSpinBox()
        self.numNeck.setMaximum(3)
        self.numNeck.setMinimum(1)
        self.numNeck.setMinimumSize(QtCore.QSize(100, 20))
        self.numNeck.setMaximumSize(QtCore.QSize(100, 20))
        self.numNeck.setValue(1)
        self.numNeck.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        self.neckLayout.addWidget(self.numNeck)

        # rebuild button
        spacerItem = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        self.layout.addItem(spacerItem)

        self.applyButton = QtWidgets.QPushButton("Apply Changes")
        self.layout.addWidget(self.applyButton)
        self.applyButton.setFont(headerFont)
        self.applyButton.setMinimumSize(QtCore.QSize(300, 40))
        self.applyButton.setMaximumSize(QtCore.QSize(300, 40))
        self.applyButton.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        self.applyButton.setEnabled(False)
        self.applyButton.clicked.connect(partial(self.applyModuleChanges, self))

        # spinBox & checkbox signal/slots
        self.numNeck.valueChanged.connect(self.toggleButtonState)

        # signal slot for groupbox checkbox
        QtCore.QObject.connect(self.groupBox, QtCore.SIGNAL("toggled(bool)"), self.frame.setVisible)
        self.groupBox.setChecked(False)

        # add custom skeletonUI settings  name, parent, rig types to install, mirror module, etc.
        # add to the rig cretor UI's module settings layout VBoxLayout
        self.rigUiInst.moduleSettingsLayout.addWidget(self.groupBox)

        # Populate the settings UI based on the network node attributes
        self.updateSettingsUI()

    def updateSettingsUI(self):

        """
        Update the skeleton settings UI based on the network node values for this module.
        """

        numNeck = cmds.getAttr(networkNode + ".neckJoints")

        # update UI elements
        self.numNeck.setValue(numNeck)

        # apply changes

    def updateNeck(self, attachedModules, oldNum):

        """
        Take the number of neck bones value and rebuild the joint mover.

        The head module has 3 joint mover files, 1 for each possible neck number. When the number of neck bones is
        changed, the current module has its information stored (placement, etc), the module is then deleted,
        the new joint mover path is constructed and brought in, and lastly it resolves any dependency issues.
        (Like if there was a leaf joint as a child of a neck bone that no longer exists)

        :param attachedModules: self.checkForDependencies()
        :param oldNum: the existing amount  of neck bones prior to the update
        """

        # gather information (current name, current parent, etc)
        name = cmds.getAttr(networkNode + ".moduleName")
        newNum = int(cmds.getAttr(networkNode + ".neckJoints"))

        # call on base class delete
        movers = self.returnJointMovers
        for moverGrp in movers:
            for mover in moverGrp:
                cmds.lockNode(mover, lock=False)

        # store mover positions (movers = [all global, all offset, all geo])
        basePositions = {}

        for each in movers:
            for mover in each:
                attrs = cmds.listAttr(mover, keyable=True)
                attrValues = []
                for attr in attrs:
                    value = cmds.getAttr(mover + "." + attr)
                    attrValues.append([attr, value])
                basePositions[mover] = attrValues

        # delete joint mover
        cmds.delete(self.name + "_mover_grp")

        # build new jmPath name
        jmPath = jointMover.partition(".ma")[0].rpartition("_")[0] + "_" + str(newNum) + "Neck.ma"  # ART_Head_1Neck
        self.jointMover_Build(jmPath)

        # apply base positions
        for key in basePositions:

            mover = key
            attrList = basePositions.get(key)

            for attr in attrList:
                if cmds.objExists(mover):
                    cmds.setAttr(mover + "." + attr[0], attr[1])

        mover = ""

        else:
            mover = utils.findMoverNodeFromJointName(networkNodes, parent)

        # delete the old constraint and create the new one
        if cmds.objExists(self.name + "_mover_grp_parentConstraint*"):
            cmds.delete(self.name + "_mover_grp_parentConstraint*")

            cmds.parentConstraint(mover, self.name + "_mover_grp", mo=True)

        if cmds.objExists(self.name + "_mover_grp_scaleConstraint*"):
            cmds.delete(self.name + "_mover_grp_scaleConstraint*")

            cmds.scaleConstraint(mover, self.name + "_mover_grp", mo=True)

        childMover = utils.findOffsetMoverFromName(name)
        riggingUtils.createBoneConnection(mover, childMover, name)
        self.applyModuleChanges(self)

        # if there were any module dependencies, fix those now.
        if len(attachedModules) > 0:
            elementList = []

            for each in attachedModules:
                elementList.append([each[2], "  -> parent changed from:  ", each[1], "  to:  ", "root\n"])
                currentParent = cmds.listRelatives(each[2] + "_mover_grp", parent=True)[0]
                if currentParent != "root_mover":
                    cmds.parentConstraint("root_mover", each[2] + "_mover_grp", mo=True)
                    cmds.scaleConstraint("root_mover", each[2] + "_mover_grp", mo=True)
                    cmds.setAttr(each[0] + ".parentModuleBone", lock=False)
                    cmds.setAttr(each[0] + ".parentModuleBone", "root", type="string", lock=True)

                    # then, update settings UI for those dependency modules to display new parent info
                    modules = self.getAllModules

                    if each[0] in modules:
                        modName = cmds.getAttr(each[0] + ".moduleName")

                        for modInst in self.rigUiInst.moduleInstances:
                            if modInst.networkNode == each[0]:
                                # find the current groupBox for this module
                                for i in range(self.rigUiInst.moduleSettingsLayout.count()):
                                    if type(self.rigUiInst.moduleSettingsLayout.itemAt(
                                            i).widget()) == QtWidgets.QGroupBox:
                                        if self.rigUiInst.moduleSettingsLayout.itemAt(i).widget().title() == modName:
                                            self.rigUiInst.moduleSettingsLayout.itemAt(i).widget().setParent(None)

                                            # relaunch the skeleton settings UI with new info
                                            modInst.skeletonSettings_UI(modName)

                # create the connection geo between the two
                mover = "root_mover"
                childMover = utils.findOffsetMoverFromName(each[2])
                riggingUtils.createBoneConnection(mover, childMover, each[2])
                each[3].applyModuleChanges(each[3])

            # warn user about changes
            winParent = interfaceUtils.getMainWindow()
            win = interfaceUtils.DialogMessage("Attention!",
                                               "The following modules have had their parent changed due to the change\
                                               in this module's structure:",
                                               elementList, 5, winParent)
            win.show()

    def applyModuleChanges(self, moduleInst):

        """
        Update the scene after the settings are changed in the skeleton settings UI.

        This means also updating the created_bones attr, updating the joint mover if needed,
        running self.updateNeck, updating the outliner, and updating the bone count.

        :param moduleInst: self (usually, but there are cases like templates where an inst on disc is passed in.)
        """

        # get prefix/suffix
        name = self.groupBox.title()
        prefix = name.partition(baseName)[0]
        suffix = name.partition(baseName)[2]

            if prefix.find("_") == -1:
                prefix = prefix + "_"
            if suffix.find("_") == -1:
                suffix = "_" + suffix

        # create list of the new created bones
        joints = []

        # get current neck value
        currentNum = int(cmds.getAttr(networkNode + ".neckJoints"))

        # get new neck value
        uiNeckVal = self.numNeck.value()

        if uiNeckVal != currentNum:
            # update neck value, and call on update neck
            cmds.setAttr(networkNode + ".neckJoints", lock=False)
            cmds.setAttr(networkNode + ".neckJoints", uiNeckVal, lock=True)

            # look for any attached modules
            attachedModules = self.checkForDependencies()
            self.updateNeck(attachedModules, currentNum)

        for i in range(uiNeckVal):
            joints.append(prefix + "neck_0" + str(i + 1) + suffix)

        joints.append(prefix + "head" + suffix)

        # build attrString
        for bone in joints:
            attrString += bone + "::"

        # reset button

        # update outliner
        self.updateOutliner()
        self.updateBoneCount()

    def toggleButtonState(self):

        """Toggle the state of the Apply Changes button."""

        state = self.applyButton.isEnabled()
        if state is False:
            self.applyButton.setEnabled(True)

    def addJointMoverToOutliner(self):

        """
        Add the joint movers for this module to the outliner.

        Depending on the module settings, different joint movers may or may not be added. Also, each "joint" usually
        has three movers: global, offset, and geo. However, not all joints do, so this method is also used to specify
        which joint movers for each joint are added to the outliner.

        .. image:: /images/outliner.png

        """
        index = self.rigUiInst.treeWidget.topLevelItemCount()

        # Add the module to the tree widget in the outliner tab of the rig creator UI
        self.outlinerWidgets[self.name + "_treeModule"] = QtWidgets.QTreeWidgetItem(self.rigUiInst.treeWidget)
        self.rigUiInst.treeWidget.topLevelItem(index).setText(0, self.name)
        foreground = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        self.outlinerWidgets[self.name + "_treeModule"].setForeground(0, foreground)

        # add the neck 01
        self.outlinerWidgets[self.name + "_neck_01"] = QtWidgets.QTreeWidgetItem(
                self.outlinerWidgets[self.name + "_treeModule"])
        self.outlinerWidgets[self.name + "_neck_01"].setText(0, self.name + "_neck_01")
        self.createGlobalMoverButton(self.name + "_neck_01", self.outlinerWidgets[self.name + "_neck_01"],
                                     self.rigUiInst)
        self.createOffsetMoverButton(self.name + "_neck_01", self.outlinerWidgets[self.name + "_neck_01"],
        self.createMeshMoverButton(self.name + "_neck_01", self.outlinerWidgets[self.name + "_neck_01"], self.rigUiInst)

        # add neck 02
        self.outlinerWidgets[self.name + "_neck_02"] = QtWidgets.QTreeWidgetItem(
                self.outlinerWidgets[self.name + "_neck_01"])
        self.outlinerWidgets[self.name + "_neck_02"].setText(0, self.name + "_neck_02")
        self.createGlobalMoverButton(self.name + "_neck_02", self.outlinerWidgets[self.name + "_neck_02"],
        self.createOffsetMoverButton(self.name + "_neck_02", self.outlinerWidgets[self.name + "_neck_02"],
        self.createMeshMoverButton(self.name + "_neck_02", self.outlinerWidgets[self.name + "_neck_02"], self.rigUiInst)

        # add neck 03
        self.outlinerWidgets[self.name + "_neck_03"] = QtWidgets.QTreeWidgetItem(
                self.outlinerWidgets[self.name + "_neck_02"])
        self.outlinerWidgets[self.name + "_neck_03"].setText(0, self.name + "_neck_03")
        self.createGlobalMoverButton(self.name + "_neck_03", self.outlinerWidgets[self.name + "_neck_03"],
        self.createOffsetMoverButton(self.name + "_neck_03", self.outlinerWidgets[self.name + "_neck_03"],
        self.createMeshMoverButton(self.name + "_neck_03", self.outlinerWidgets[self.name + "_neck_03"], self.rigUiInst)

        # add head
        self.outlinerWidgets[self.name + "_head"] = QtWidgets.QTreeWidgetItem(
        self.outlinerWidgets[self.name + "_head"].setText(0, self.name + "_head")
        self.createGlobalMoverButton(self.name + "_head", self.outlinerWidgets[self.name + "_head"], self.rigUiInst)
        self.createOffsetMoverButton(self.name + "_head", self.outlinerWidgets[self.name + "_head"], self.rigUiInst)
        self.createMeshMoverButton(self.name + "_head", self.outlinerWidgets[self.name + "_head"], self.rigUiInst)

        # create selection script job for module
        self.createScriptJob()

        # update based on spinBox values
        self.rigUiInst.treeWidget.expandAll()

    def updateOutliner(self):
        """
        Whenever changes are made to the module settings, update the outliner to show the new or removed movers
        """

        # NECK
        numNeck = self.numNeck.value()
        if numNeck == 1:
            self.outlinerWidgets[self.originalName + "_neck_01"].setHidden(False)
            self.outlinerWidgets[self.originalName + "_neck_02"].setHidden(True)
            self.outlinerWidgets[self.originalName + "_neck_03"].setHidden(True)

        if numNeck == 2:
            self.outlinerWidgets[self.originalName + "_neck_02"].setHidden(False)

        if numNeck == 3:
            self.outlinerWidgets[self.originalName + "_neck_03"].setHidden(False)

    def pinModule(self, state):
        """
        Pin the module in place so the parent does not move the module. Each module has to define how it needs to be
        pinned.
        """
        topLevelMover = self.name + "_neck_01_mover_grp"

        if state:

            loc = cmds.spaceLocator()[0]
            cmds.setAttr(loc + ".v", False, lock=True)
            constraint = cmds.parentConstraint(topLevelMover, loc)[0]
            cmds.delete(constraint)
            const = cmds.parentConstraint(loc, topLevelMover)[0]

            if not cmds.objExists(networkNode + ".pinConstraint"):
                cmds.addAttr(networkNode, ln="pinConstraint", keyable=True, at="message")

            cmds.connectAttr(const + ".message", networkNode + ".pinConstraint")

        if not state:

            connections = cmds.listConnections(networkNode + ".pinConstraint")
            if len(connections) > 0:
                constraint = connections[0]
                cmds.delete(constraint)

    def skinProxyGeo(self):
        """
        Skin the proxy geo brought in by the module. Each module has to define how it wants to skin its proxy geo.
        """

        # get the network node
        baseName = cmds.getAttr(networkNode + ".baseName")

        # get this module's proxy geo meshes
        cmds.select(name + "_mover_grp", hi=True)
        proxyGeoMeshes = []
        for each in selection:
            if each.find("proxy_geo") != -1:
                parent = cmds.listRelatives(each, parent=True)[0]
                if cmds.nodeType(each) == "transform":
                    proxyGeoMeshes.append(each)

        # skin the proxy geo meshes
        for mesh in proxyGeoMeshes:
            dupeMesh = cmds.duplicate(mesh, name="skin_" + mesh)[0]
            cmds.setAttr(dupeMesh + ".overrideEnabled", lock=False)
            cmds.setAttr(dupeMesh + ".overrideDisplayType", 0)

            # create skinned geo group
            if not cmds.objExists("skinned_proxy_geo"):
                cmds.group(empty=True, name="skinned_proxy_geo")

            cmds.parent(dupeMesh, "skinned_proxy_geo")

            boneName = mesh.partition(name + "_")[2]
            boneName = boneName.partition("_proxy_geo")[0]
            joint = prefix + boneName + suffix

            if not cmds.objExists(joint):
                cmds.delete(dupeMesh)

            else:
                cmds.select([dupeMesh, joint])
                cmds.skinCluster(tsb=True, maximumInfluences=1, obeyMaxInfluences=True, bindMethod=0, skinMethod=0,
                                 normalizeWeights=True)

    def buildRigCustom(self, textEdit, uiInst):
        """
        Build the rigs for this module.

        This method defines what rigs are built and how they are built when the asset is published. It posts any build
        info to the passed in textEdit.

        :param textEdit: passed in text edit that the rig build can post updates to
        :param uiInst:  passed in instance of the buildProgressUI
        """

        if textEdit is not None:
            textEdit.append("        Building " + self.name + " Rig..")

        # get the created joints
        parentBone = cmds.getAttr(networkNode + ".parentModuleBone")
        joints = self.returnCreatedJoints

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        # create groups and settings

        # create the rig group
        self.rigGrp = cmds.group(empty=True, name=self.name + "_group")
        constraint = cmds.parentConstraint(parentBone, self.rigGrp)[0]
        cmds.delete(constraint)

        # create the rig settings group
        self.rigSettings = cmds.group(empty=True, name=self.name + "_settings")
        cmds.parent(self.rigSettings, self.rigGrp)
        for attr in (cmds.listAttr(self.rigSettings, keyable=True)):
            cmds.setAttr(self.rigSettings + "." + attr, lock=True, keyable=False)

        # create the ctrl group (what will get the constraint to the parent)
        self.rigCtrlGrp = cmds.group(empty=True, name=self.name + "_ctrl_grp")
        constraint = cmds.parentConstraint(parentBone, self.rigCtrlGrp)[0]

        cmds.parent(self.rigCtrlGrp, self.rigGrp)

        # Build the FK Neck Rig

        fkControls = []
        self.topNode = None

        # get number of neck bones
        neckJoints = []
        for i in range(len(joints) - 1):
            neckJoints.append(joints[i])

        # create FK controls for neck joints, adding space switching to base neck joint
        for joint in neckJoints:
            if joint == joints[0]:
                data = riggingUtils.createControlFromMover(joint, networkNode, True, True)

                fkControl = cmds.rename(data[0], "fk_" + joint + "_anim")
                animGrp = cmds.rename(data[1], "fk_" + joint + "_anim_grp")
                spaceSwitcher = cmds.rename(data[2], "fk_" + joint + "_anim_space_switcher")
                spaceSwitchFollow = cmds.rename(data[3], "fk_" + joint + "_anim_space_switcher_follow")
                self.topNode = spaceSwitchFollow

                fkControls.append([spaceSwitchFollow, fkControl, joint])
                # color the control
                riggingUtils.colorControl(fkControl, 18)

            else:
                data = riggingUtils.createControlFromMover(joint, networkNode, True, False)

                fkControls.append([animGrp, fkControl, joint])

        # create hierarchy
        fkControls.reverse()

        for i in range(len(fkControls)):
            try:
                cmds.parent(fkControls[i][0], fkControls[i + 1][1])
            except IndexError:
                pass

        # Build the FK Head Rig
        headJoint = joints[-1]
        data = riggingUtils.createControlFromMover(headJoint, networkNode, True, True)

        headControl = cmds.rename(data[0], "fk_" + headJoint + "_anim")
        animGrp = cmds.rename(data[1], "fk_" + headJoint + "_anim_grp")
        spaceSwitcher = cmds.rename(data[2], "fk_" + headJoint + "_anim_space_switcher")
        spaceSwitchFollow = cmds.rename(data[3], "fk_" + headJoint + "_anim_space_switcher_follow")

        # color the control
        riggingUtils.colorControl(headControl, 17)

        # parent head to neck
        lastNeck = fkControls[0][1]
        cmds.parent(spaceSwitchFollow, lastNeck)

        fkControls.append([animGrp, headControl, headJoint])

        # parent head rig to rigGrp
        cmds.parent(self.topNode, self.rigCtrlGrp)

        # lock attrs
        for each in fkControls:
            control = each[1]
            if control != headControl:
                for attr in [".scaleX", ".scaleY", ".globalScale", ".visibility"]:
                    cmds.setAttr(control + attr, lock=True, keyable=False)
            else:
                for attr in [".visibility"]:

        # hook up to driver skeleton
            joint = each[2]

            cmds.pointConstraint(control, "driver_" + joint, mo=True)
            cmds.orientConstraint(control, "driver_" + joint)

            # plug master control scale into a new mult node that takes joint.scale into input 1, and master.scale
            # into input 2, and plugs that into driver joint
            if cmds.objExists("master_anim"):
                globalScaleMult = cmds.shadingNode("multiplyDivide", asUtility=True, name=joint + "_globalScale")
                cmds.connectAttr("master_anim.scale", globalScaleMult + ".input1")
                cmds.connectAttr(control + ".scale", globalScaleMult + ".input2")
                riggingUtils.createConstraint(globalScaleMult, "driver_" + joint, "scale", False, 2, 0, "output")
            else:
                riggingUtils.createConstraint(control, "driver_" + joint, "scale", False, 2, 0)

        # add info to module
        fkRigData = []
            fkRigData.append(each[1])

        # add created control info to module
        if not cmds.objExists(networkNode + ".fkControls"):
            cmds.addAttr(networkNode, ln="fkControls", dt="string")
        jsonString = json.dumps(fkRigData)
        cmds.setAttr(networkNode + ".fkControls", jsonString, type="string")

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        # # #            Parent Under Offset Ctrl           # # #
        # parent under offset_anim if it exists(it always should)
        if cmds.objExists("offset_anim"):
            cmds.parent(self.rigGrp, "offset_anim")

        # return data
        try:
            uiInst.rigData.append([self.rigCtrlGrp, "driver_" + parentBone, 1])
        except:
            pass

        # update progress
            textEdit.setTextColor(QtGui.QColor(0, 255, 18))
            textEdit.append("        SUCCESS: FK Build Complete!")
            textEdit.setTextColor(QtGui.QColor(255, 255, 255))

    def pickerUI(self, center, animUI, networkNode, namespace):
        """
        Build the animation picker for the module.

        :param center: the center of the QGraphicsScene
        :param animUI: the instance of the AnimationUI
        :param networkNode: the module's network node
        :param namespace: the namespace of the character

        """

        self.namespace = namespace
        neckJoints = cmds.getAttr(networkNode + ".neckJoints")

        # create qBrushes
        yellowBrush = QtCore.Qt.yellow
        blueBrush = QtGui.QColor(100, 220, 255)
        greenBrush = QtGui.QColor(0, 255, 30)
        clearBrush = QtGui.QBrush(QtCore.Qt.black)
        clearBrush.setStyle(QtCore.Qt.NoBrush)

        # create the picker border item
        if networkNode.find(":") != -1:
            moduleNode = networkNode.partition(":")[2]
        else:
            moduleNode = networkNode

        borderItem = interfaceUtils.pickerBorderItem(center.x() - 50, center.y() - 50, 100, 100, clearBrush, moduleNode)

        # get controls
        fkControls = json.loads(cmds.getAttr(networkNode + ".fkControls"))
        buttonData = []

        # create buttons
        headButton = interfaceUtils.pickerButtonCustom(100, 100,
                                                       [[10, 60], [30, 60], [40, 40], [40, 10], [35, 5], [30, 0],
                                                        [10, 0], [5, 5], [0, 10], [0, 40], [10, 60]], [30, 5],
                                                       namespace + fkControls[0], yellowBrush, borderItem)
        buttonData.append([headButton, namespace + fkControls[0], yellowBrush])

        if neckJoints == 1:
            neck1Button = interfaceUtils.pickerButtonCustom(100, 100,
                                                            [[5, 55], [8, 63], [32, 63], [35, 55], [40, 85], [0, 85],
                                                             [5, 55]], [30, 5], namespace + fkControls[1], blueBrush,
                                                            borderItem)
            buttonData.append([neck1Button, namespace + fkControls[1], blueBrush])

        if neckJoints == 2:
                                                            [[5, 55], [8, 63], [32, 63], [35, 55], [37, 68], [3, 68],

            neck2Button = interfaceUtils.pickerButtonCustom(100, 100, [[3, 70], [37, 70], [40, 80], [0, 80]], [30, 5],
                                                            namespace + fkControls[2], blueBrush, borderItem)
            buttonData.append([neck2Button, namespace + fkControls[2], blueBrush])

        if neckJoints == 3:

            neck2Button = interfaceUtils.pickerButtonCustom(100, 100, [[3, 70], [37, 70], [38, 76], [2, 76]], [30, 5],

            neck3Button = interfaceUtils.pickerButtonCustom(100, 100, [[2, 78], [38, 78], [40, 84], [0, 84]], [30, 5],
                                                            namespace + fkControls[3], blueBrush, borderItem)
            buttonData.append([neck3Button, namespace + fkControls[3], blueBrush])

        # go through button data, adding menu items
        for each in buttonData:
            button = each[0]

            zeroIcon1 = QtGui.QIcon((utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/zeroAll.png"))))
            zeroIcon2 = QtGui.QIcon((utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/zeroSel.png"))))
            selectIcon = QtGui.QIcon((utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/select.png"))))

            button.menu.addAction(selectIcon, "Select All Controls", partial(self.selectRigControls))
            button.menu.addSeparator()

            button.menu.addAction(zeroIcon1, "Zero Out Attrs (All)", partial(self.resetRigControls, True))
            button.menu.addAction(zeroIcon2, "Zero Out Attrs (Sel)", partial(self.resetRigControls, False))

        # #Create scriptJob for selection. Set scriptJob number to borderItem.data(5)
        scriptJob = cmds.scriptJob(event=["SelectionChanged", partial(self.selectionScriptJob_animUI, buttonData)],
                                   kws=True)
        borderItem.setData(5, scriptJob)
        animUI.selectionScriptJobs.append(scriptJob)

        return [borderItem, False, scriptJob]

    def importFBX(self, importMethod, character):
        """
        Import FBX motion onto this module's rig controls.

        :param importMethod: The import method to be used (options defined in the file attributes)
        :param character: the namespace of the character

        Each module has to define what import methods it offers (at the very top of the module file) and then define
        how motion is imported using those methods.
        """

        returnControls = []

        # get basic info of node
        networkNode = self.returnRigNetworkNode
        moduleName = cmds.getAttr(networkNode + ".moduleName")
        baseModuleName = cmds.getAttr(networkNode + ".baseName")

        # find prefix/suffix of module name
        prefixSuffix = moduleName.split(baseModuleName)
        prefix = None
        suffix = None

        if prefixSuffix[0] != '':
            prefix = prefixSuffix[0]
        if prefixSuffix[1] != '':
            suffix = prefixSuffix[1]

        # get joints
        joints = cmds.getAttr(networkNode + ".Created_Bones")
        splitJoints = joints.split("::")
        createdJoints = []

        for bone in splitJoints:
                createdJoints.append(bone)

        # IMPORT FK
        if importMethod == "FK":

            for joint in createdJoints:
                if cmds.objExists(character + ":" + "fk_" + joint + "_anim"):
                    cmds.parentConstraint(joint, character + ":" + "fk_" + joint + "_anim")
                    returnControls.append(character + ":" + "fk_" + joint + "_anim")

        # IMPORT NONE
        if importMethod == "None":
            pass

        return returnControls

"""
"""

try:
except:

    """

    """

windowTitle = "Change Module Parent"
windowObject = "pyArtChangeModuleParentUi"

class ART_ChangeModuleParent_UI(QtWidgets.QMainWindow):
    """
    This class allows the user to change the parent module bone of a given module. It is found within the

        .. image:: /images/changeModParent.png

    """

    def __init__(self, currentParent, moduleInst, rigUiInst, parent=None):
        """

        :param currentParent: The current module parent bone of this module.

        """
        super(ART_ChangeModuleParent_UI, self).__init__(parent)

        self.currentParent = currentParent

        f.close()

        self.setMinimumSize(QtCore.QSize(250, 400))
        self.setMaximumSize(QtCore.QSize(250, 400))

        label = QtWidgets.QLabel("Choose New Parent:")
        self.widgetLayout.addWidget(label)

        self.boneSearch.setPlaceholderText("Search..")
        self.widgetLayout.addWidget(self.boneSearch)
        self.boneList = QtWidgets.QListWidget()
        self.widgetLayout.addWidget(self.boneList)
        self.boneList.setMinimumHeight(200)

        # add items to comboBox
        bones = utils.getViableParents()

        # get our own bones
        modBones = self.modInst.returnCreatedJoints

        for bone in bones:
            if bone not in modBones:
                self.boneList.addItem(bone)
                index = bones.index(bone)
                self.boneList.setCurrentRow(index)

        self.updateBtn.setMinimumSize(QtCore.QSize(230, 40))
        self.updateBtn.setMaximumSize(QtCore.QSize(230, 40))

        self.updateBtn.clicked.connect(self.applyModuleParentChange)

        self.updateBtn.setFocus()

        """
        Reads the text in the QLineEdit and searches the list widget for any items containing the search text,
        hiding all listWidgetItems that do not contain the search text.

        """

        for i in range(self.boneList.count()):
            lwItem = self.boneList.item(i)
            else:

    def applyModuleParentChange(self):
        """
        Gets the new parent from the selected ListWidgetItem text and then checks to make sure the selected parent
        isn't a bone that is part of the module we're trying to change the parent on. Then updates text and attribute
        values where needed.

        .. note::
                  * Current Parent text item in the Skeleton Settings UI
                  * Network Node .parentModuleBone attribute
                  * Constrains nodes based on new parenting relationship

        """

        # get new parent
        newParent = self.boneList.currentItem().text()

        # check to make sure new parent is not in this module's created bones list

        if newParent in createdBones:
            cmds.confirmDialog(title="Error", icon="critical",
                               message="Cannot parent a module to a bone created by the module.")
            return

        # update current parent text
        self.modInst.currentParent.setText(newParent)

        # update network node parentModuleBone attribute
        cmds.setAttr(networkNode + ".parentModuleBone", newParent, type="string", lock=True)

        # delete the existing bone connection and reparent to the new parent and recreate the bone connection
        if cmds.objExists(self.modInst.name + "_parentGrp"):
            cmds.delete(self.modInst.name + "_parentGrp")

            # parent under the new parent
            moverGrp = cmds.getAttr(networkNode + ".moduleName")
            moverGrp = moverGrp + "_mover_grp"

            if newParent == "root":
                offsetMover = "root_mover"

            else:
                networkNodes = utils.returnRigModules()
                mover = utils.findMoverNodeFromJointName(networkNodes, newParent, False, True)
                offsetMover = utils.findMoverNodeFromJointName(networkNodes, newParent)

            # create the new bone representation
            childMover = utils.findOffsetMoverFromName(self.modInst.name)
            riggingUtils.createBoneConnection(offsetMover, childMover, self.modInst.name)

        if cmds.objExists(self.modInst.name + "_mover_grp_parentConstraint*"):
            cmds.delete(self.modInst.name + "_mover_grp_parentConstraint*")

        networkNodes = utils.returnRigModules()
        mover = utils.findMoverNodeFromJointName(networkNodes, newParent, False, True)
            cmds.parentConstraint(mover, self.modInst.name + "_mover_grp", mo=True)

        if cmds.objExists(self.modInst.name + "_mover_grp_scaleConstraint*"):
            cmds.delete(self.modInst.name + "_mover_grp_scaleConstraint*")

            cmds.scaleConstraint(mover, self.modInst.name + "_mover_grp", mo=True)

"""
"""

import os

class ART_DebugRigs():
    """
    This class is used in developing rigs for modules and quickly testing them without having to go
    through the entire build/publish process.

        .. image:: /images/debugRigs.png

    """

        """
        Instantiate the class, getting the QSettings, and building the interface.

        """

        # images

        if cmds.window("ART_DebugRigsWin", exists=True):
            cmds.deleteUI("ART_DebugRigsWin", wnd=True)

        """
        Build the UI, listing all modules in the scene that make up the asset for the user to select and build rigs
        for the selected.

        """

        self.mainWin = QtWidgets.QMainWindow(self.mainUI)

        f.close()

        self.mainWin.setObjectName("ART_DebugRigsWin")
        self.mainWin.setWindowTitle("Build Rigs")

        # font

        self.mainWin.resize(400, 300)
        self.mainWin.setMinimumSize(QtCore.QSize(400, 300))
        self.mainWin.setMaximumSize(QtCore.QSize(400, 300))

        self.background.setObjectName("mid")
        self.mainLayout = QtWidgets.QHBoxLayout(self.background)

        # create the list on the left and add the modules to the list
        self.mainLayout.addWidget(self.moduleList)

        for mod in self.mainUI.moduleInstances:
            item = QtWidgets.QListWidgetItem(mod.name)
            item.setData(QtCore.Qt.UserRole, mod)
            self.moduleList.addItem(item)

        # create our buttons on the right
        self.rightLayout = QtWidgets.QVBoxLayout()
        self.mainLayout.addLayout(self.rightLayout)

        infoText = "This tool is only for testing rigs in development. "
        infoText += "It will leave behind nodes in your scene that you do NOT want to publish with. "
        infoText += "When using this tool, it is advised to open a clean scene to publish your final asset."

        self.info = QtWidgets.QLabel()
        self.rightLayout.addWidget(self.info)
        self.info.setWordWrap(True)
        self.info.setMinimumSize(150, 125)
        self.info.setMaximumSize(150, 125)
        self.info.setText(infoText)

        self.rightLayout.addSpacerItem(
            QtWidgets.QSpacerItem(0, 200, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding))

        self.buildButton = QtWidgets.QPushButton("Build Rigs For Selected")
        self.buildButton.setObjectName("blueButton")
        self.rightLayout.addWidget(self.buildButton)
        self.buildButton.setMinimumSize(150, 40)
        self.buildButton.setMaximumSize(150, 40)
        self.buildButton.clicked.connect(partial(self.buildRigs))

        self.deleteButton = QtWidgets.QPushButton("Remove Selected Rigs")
        self.deleteButton.setObjectName("blueButton")
        self.rightLayout.addWidget(self.deleteButton)
        self.deleteButton.setMinimumSize(150, 40)
        self.deleteButton.setMaximumSize(150, 40)
        self.deleteButton.clicked.connect(partial(self.deleteRig))

        self.rightLayout.addWidget(self.closeButton)
        self.closeButton.setMinimumSize(150, 40)
        self.closeButton.setMaximumSize(150, 40)
        self.closeButton.clicked.connect(partial(self.close))

        """
        Builds the rigs for the selected module by calling on that module's buildRig function.

        """

        data = self.moduleList.currentItem().data(QtCore.Qt.UserRole)

        # call on inst build rigs functions
        if not cmds.objExists("driver_root"):
            riggingUtils.createDriverSkeleton()
        data.buildRig(None, self.mainUI)

    def deleteRig(self):
        """
        Deletes the rigs for the selected module by calling on that module's deleteRig function.

        """

        data.deleteRig()

    def close(self):
        """
        Close the interface and delete the window.

        """

"""

    This is the base class from which all modules are created. When creating a new module,
    your module class should inherit from this base class. Example:

|        class ART_Head(ART_RigModule)

    In the __init__ of your module class, you will want to also run the base class __init__
    at the end of your module class __init__:

|        ART_RigModule.__init__(self, "ART_Head_Module", "ART_Head", moduleUserName)

    This module has two file attributes set by default. Your module class will need many more
    file attributes set. Please see another module as an example.

########
Contents
########

|   **Module Creation Functions:**
|       :func:`addAttributes <System.ART_RigModule.ART_RigModule.addAttributes>`
|       :func:`buildNetwork <System.ART_RigModule.ART_RigModule.buildNetwork>`
|       :func:`createMirrorModule <System.ART_RigModule.ART_RigModule.createMirrorModule>`
|       :func:`jointMover_Build <System.ART_RigModule.ART_RigModule.jointMover_Build>`
|       :func:`skeletonSettings_UI <System.ART_RigModule.ART_RigModule.skeletonSettings_UI>`
|
|   **Module Update Functions:**
|       :func:`applyModuleChanges <System.ART_RigModule.ART_RigModule.applyModuleChanges>`
|       :func:`checkForDependencies <System.ART_RigModule.ART_RigModule.checkForDependencies>`
|       :func:`deleteModule <System.ART_RigModule.ART_RigModule.deleteModule>`
|
|   **Module Settings and Interface Functions:**
|       :func:`changeModuleName <System.ART_RigModule.ART_RigModule.changeModuleName>`
|       :func:`changeModuleParent <System.ART_RigModule.ART_RigModule.changeModuleParent>`
|       :func:`copySettings <System.ART_RigModule.ART_RigModule.copySettings>`
|       :func:`pasteSettings <System.ART_RigModule.ART_RigModule.pasteSettings>`
|       :func:`resetSettings <System.ART_RigModule.ART_RigModule.resetSettings>`
|       :func:`createContextMenu <System.ART_RigModule.ART_RigModule.createContextMenu>`
|       :func:`createMirrorOfModule_UI <System.ART_RigModule.ART_RigModule.createMirrorOfModule_UI>`
|       :func:`createScriptJob <System.ART_RigModule.ART_RigModule.createScriptJob>`
|       :func:`mirrorTransformations <System.ART_RigModule.ART_RigModule.mirrorTransformations>`
|       :func:`mirrorTransformations_Custom <System.ART_RigModule.ART_RigModule.mirrorTransformations_Custom>`
|       :func:`resetTransforms <System.ART_RigModule.ART_RigModule.resetTransforms>`
|       :func:`setMirrorModule <System.ART_RigModule.ART_RigModule.setMirrorModule>`
|       :func:`updateBoneCount <System.ART_RigModule.ART_RigModule.updateBoneCount>`
|       :func:`updatePreview <System.ART_RigModule.ART_RigModule.updatePreview>`
|       :func:`updateSettingsUI <System.ART_RigModule.ART_RigModule.updateSettingsUI>`
|
|   **Module Joint Mover Functions:**
|       :func:`addJointMoverToOutliner <System.ART_RigModule.ART_RigModule.addJointMoverToOutliner>`
|       :func:`aimMode <System.ART_RigModule.ART_RigModule.aimMode>`
|       :func:`aimMode_Setup <System.ART_RigModule.ART_RigModule.aimMode_Setup>`
|       :func:`bakeOffsets <System.ART_RigModule.ART_RigModule.bakeOffsets>`
|       :func:`createGlobalMoverButton <System.ART_RigModule.ART_RigModule.createGlobalMoverButton>`
|       :func:`createMeshMoverButton <System.ART_RigModule.ART_RigModule.createMeshMoverButton>`
|       :func:`createOffsetMoverButton <System.ART_RigModule.ART_RigModule.createOffsetMoverButton>`
|       :func:`pinModule <System.ART_RigModule.ART_RigModule.pinModule>`
|       :func:`selectMover <System.ART_RigModule.ART_RigModule.selectMover>`
|       :func:`selectScriptJob <System.ART_RigModule.ART_RigModule.selectScriptJob>`
|       :func:`toggleShapeVis <System.ART_RigModule.ART_RigModule.toggleShapeVis>`
|       :func:`updateOutliner <System.ART_RigModule.ART_RigModule.updateOutliner>`
|
|   **Module Publish Functions:**
|       :func:`cleanUpRigPose <System.ART_RigModule.ART_RigModule.cleanUpRigPose>`
|       :func:`createRigPoseSliderForJoint <System.ART_RigModule.ART_RigModule.createRigPoseSliderForJoint>`
|       :func:`getReferencePose <System.ART_RigModule.ART_RigModule.getReferencePose>`
|       :func:`matchModelPose <System.ART_RigModule.ART_RigModule.matchModelPose>`
|       :func:`mirrorTransformations_RigPose <System.ART_RigModule.ART_RigModule.mirrorTransformations_RigPose>`
|       :func:`resetRigPose <System.ART_RigModule.ART_RigModule.resetRigPose>`
|       :func:`resetRigPose_Part <System.ART_RigModule.ART_RigModule.resetRigPose_Part>`
|       :func:`rigPose_UI <System.ART_RigModule.ART_RigModule.rigPose_UI>`
|       :func:`setPosePercentage <System.ART_RigModule.ART_RigModule.setPosePercentage>`
|       :func:`setPosePercentage_Part <System.ART_RigModule.ART_RigModule.setPosePercentage_Part>`
|       :func:`setReferencePose <System.ART_RigModule.ART_RigModule.setReferencePose>`
|       :func:`setReferencePoseSlider <System.ART_RigModule.ART_RigModule.setReferencePoseSlider>`
|       :func:`setSkeletonPose <System.ART_RigModule.ART_RigModule.setSkeletonPose>`
|       :func:`setupForRigPose <System.ART_RigModule.ART_RigModule.setupForRigPose>`
|       :func:`setupModelPoseForRig <System.ART_RigModule.ART_RigModule.setupModelPoseForRig>`
|       :func:`skinProxyGeo <System.ART_RigModule.ART_RigModule.skinProxyGeo>`
|       :func:`updateRigPose <System.ART_RigModule.ART_RigModule.updateRigPose>`
|
|   **Module Rig Functions**
|       :func:`buildRig <System.ART_RigModule.ART_RigModule.buildRig>`
|       :func:`buildRigCustom <System.ART_RigModule.ART_RigModule.buildRigCustom>`
|       :func:`deleteRig <System.ART_RigModule.ART_RigModule.deleteRig>`
|       :func:`getControls <System.ART_RigModule.ART_RigModule.getControls>`
|       :func:`importFBX <System.ART_RigModule.ART_RigModule.importFBX>`
|       :func:`importFBX_pre <System.ART_RigModule.ART_RigModule.importFBX_pre>`
|       :func:`resetRigControls <System.ART_RigModule.ART_RigModule.resetRigControls>`
|       :func:`selectRigControls <System.ART_RigModule.ART_RigModule.selectRigControls>`
|       :func:`selectionScriptJob_animUI <System.ART_RigModule.ART_RigModule.selectionScriptJob_animUI>`
|
|   **Module Utility Functions**
|       :func:`getAllModules <System.ART_RigModule.ART_RigModule.getAllModules>`
|       :func:`getModules <System.ART_RigModule.ART_RigModule.getModules>`
|       :func:`removeSkeletalConstraints <System.ART_RigModule.ART_RigModule.removeSkeletalConstraints>`
|       :func:`returnCreatedJoints <System.ART_RigModule.ART_RigModule.returnCreatedJoints>`
|       :func:`returnJointMovers <System.ART_RigModule.ART_RigModule.returnJointMovers>`
|       :func:`returnMirrorModuleInst <System.ART_RigModule.ART_RigModule.returnMirrorModuleInst>`
|       :func:`returnNetworkNode <System.ART_RigModule.ART_RigModule.returnNetworkNode>`
|       :func:`returnPrefixSuffix <System.ART_RigModule.ART_RigModule.returnPrefixSuffix>`
|       :func:`returnRigNetworkNode <System.ART_RigModule.ART_RigModule.returnRigNetworkNode>`
|
|
#########
Class
#########
"""

import os
import traceback

import System.mathUtils as mathUtils

matchData = [False, None]

class ART_RigModule():
    def __init__(self, moduleName, moduleType, userCreatedName):

        :param moduleName: This is the base name of the module, specified in the rig module.
        :param moduleType: This is the name of the module to create (the module class).
        :param userCreatedName: This is the name specified by the user on module creation.

            * **self.modName:** Take the passed in moduleName and make it a class var
            * **self.moduleType:** Take the passed in moduleType and make it a class var
            * **self.rootMod:** The network node of the entire character asset
            * **self.name:** The user created name (prefix +  baseName + suffix) passed in
            * **self.originalName:** Also the user created name, but we want to store what the original name
              was on creation, as the user can rename the module later. This will allow us to make the link between
              what the module's original name was and what the new name is.
            * **self.outlinerControls:** A list of the outliner controls created when adding module joint movers to
              the outliner.

        """

        # set class variables
        self.modName = moduleName
        self.moduleType = moduleType
        self.rootMod = None
        self.name = userCreatedName
        self.originalName = userCreatedName
        self.outlinerControls = []
        self.controlTypes = []

    def buildNetwork(self):
        """
        Build the network node for the module which will store all information needed by the module.
        Then, call on addAttributes to add the required module attributes to the network node.
        """

        # create the network node for our module
        self.networkNode = cmds.createNode("network", name=self.modName)

        # create attributes
        self.addAttributes()

        return self.networkNode

        """
        Add attributes to the network node that all modules need.
        """

        if self.moduleType == None:
            # add attributes specific to this module
            cmds.addAttr(self.networkNode, sn="rigModules", at="message")

        else:
            # add a rigging message attribute
            cmds.addAttr(self.networkNode, sn="parent", at="message")

            # add a module type attribute
            cmds.addAttr(self.networkNode, sn="moduleType", dt="string", keyable=False)
            cmds.setAttr(self.networkNode + ".moduleType", self.moduleType, type="string", lock=True)

            # and a module name attribute (used for skeleton settings UI groupbox label)
            cmds.addAttr(self.networkNode, sn="moduleName", dt="string", keyable=False)
            cmds.setAttr(self.networkNode + ".moduleName", self.name, type="string", lock=True)

            # add attr for parent module that user specfies
            cmds.addAttr(self.networkNode, sn="parentModuleBone", dt="string", keyable=False)

            # add attr for mirror module that user specfies
            cmds.addAttr(self.networkNode, sn="mirrorModule", dt="string", keyable=False)

            cmds.addAttr(self.networkNode, sn="pinned", at="bool", keyable=False)
            cmds.setAttr(self.networkNode + ".pinned", False, lock=True)

            # connect rigModule to root module node
            self.rootMod = self.getModules
            if self.rootMod != None:
                cmds.connectAttr(self.rootMod + ".rigModules", self.networkNode + ".parent")

    def skeletonSettings_UI(self, name, width, height, checkable):
        """
        Build the framework for the skeleton settings that all modules need.

        :param name: user given name of module (prefix + base_name + suffix)
        """

        # add the groupbox for this module with the module name and module settings
        self.groupBox = QtWidgets.QGroupBox(name)
        self.groupBox.setGeometry(QtCore.QRect(0, 0, width, height))
        self.groupBox.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed))

        if not checkable:
            self.groupBox.setMinimumSize(QtCore.QSize(width, height))

        if checkable:
            self.groupBox.setMinimumSize(QtCore.QSize(width, 0))

        self.groupBox.setMaximumSize(QtCore.QSize(width, height))
        self.groupBox.setFlat(True)
        self.groupBox.setCheckable(checkable)

        self.lockButton = QtWidgets.QPushButton()
        self.lockButton.setMinimumSize(QtCore.QSize(20, 20))
        self.lockButton.setMaximumSize(QtCore.QSize(20, 20))

        # load style sheet file
        styleSheetFile = utils.returnNicePath(self.toolsPath,
                                              "Core/Scripts/Interfaces/StyleSheets/skeletonSettings.qss")
        f.close()

        self.groupBox.setStyleSheet(style)

        # set properties for filtering later
        self.groupBox.setObjectName(name)
        self.groupBox.setProperty("name", name)

        # set context menu policy on groupbox
        self.groupBox.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.groupBox.customContextMenuRequested.connect(self.createContextMenu)

    def changeModuleName(self, baseName, moduleInst, rigUiInst):
        """
        Launch the interface that allows users to change the module name.

        :param baseName: The module base name (head, torso, leg, etc)
        :param moduleInst: The specific instance of the module
        :param rigUiInst: The instance of the rig creator interface.

        This will call on a separate class in Core/Interfaces called ART_ChangeModuleNameUI.py
        """

        # when pressing the change name button on the skeleton settings UI (if it exists):

        # delete the UI if it already exists
        if cmds.window(mayaWindow + "|pyArtChangeModuleNameUi", q=True, exists=True):
            cmds.deleteUI(mayaWindow + "|pyArtChangeModuleNameUi")

        # launch a UI for prefix/suffix/preview again
        import Interfaces.ART_ChangeModuleNameUI as ART_ChangeModuleNameUI
        reload(ART_ChangeModuleNameUI)
        inst = ART_ChangeModuleNameUI.ART_ChangeModuleName_UI(baseName, moduleInst, rigUiInst, prefix, suffix,
                                                              interfaceUtils.getMainWindow())
        inst.show()

    def changeModuleParent(self, moduleInst, rigUiInst):
        """
        Launch the interface that allows users to change the module's parent bone.

        This will call on a separate class in Core/Interfaces called ART_ChangeModuleParentUI.py
        """

        # get current parent value
        currentParent = self.currentParent.text()

        # when pressing the change parent button on the skeleton settings UI (if it exists):

        if cmds.window(mayaWindow + "|pyArtChangeModuleParentUi", q=True, exists=True):
            cmds.deleteUI(mayaWindow + "|pyArtChangeModuleParentUi")

        import Interfaces.ART_ChangeModuleParentUI as ART_ChangeModuleParentUI
        reload(ART_ChangeModuleParentUI)
        inst = ART_ChangeModuleParentUI.ART_ChangeModuleParent_UI(currentParent, moduleInst, rigUiInst,
                                                                  interfaceUtils.getMainWindow())

    def setMirrorModule(self, moduleInst, rigUiInst):
        """
        Launch the interface that allows users to change the module's mirror module.
        Meaning, the module that is linked as a mirror of this module. Only modules of the same
        type can be linked as mirrors.

        This will call on a separate class in Core/Interfaces called ART_SetMirrorModuleUI.py
        """

        if cmds.window(mayaWindow + "|pyArtSetMirrorModuleUi", q=True, exists=True):
            cmds.deleteUI(mayaWindow + "|pyArtSetMirrorModuleUi")

        import Interfaces.ART_SetMirrorModuleUI as ART_SetMirrorModuleUI
        reload(ART_SetMirrorModuleUI)
        inst = ART_SetMirrorModuleUI.ART_SetMirrorModule_UI(moduleInst, rigUiInst, interfaceUtils.getMainWindow())

    def jointMover_Build(self, path):
        """
        Import the joint mover file with the given path.

        After importing the module's joint mover file, rename imported nodes to use module name.
        Then, assign existing matching materials to joint mover proxy geometry, deleting the imported
        materials if they were duplicates. Then parent into the main JointMover group. Lastly, hook up
        global scaling on the joint movers.

        :param path: Path of joint mover file to import
        """

        # get the full path for the joint mover file
        fullPath = os.path.join(self.toolsPath, path)

        # import the file
            nodes = cmds.file(fullPath, i=True, iv=True, type="mayaAscii", rnn=True)
            validTypes = ["transform", "joint", "ikHandle"]

            # loop through returned nodes from import, and find the mover_grp, renaming it and all
            # children to have user specified name as prefix
            for node in nodes:
                if node.find("|mover_grp") == 0:
                    children = cmds.listRelatives(node, allDescendents=True, type="transform")
                    moverGrp = node.partition("|")[2]
                    movers = [moverGrp]

                    for child in children:
                        try:
                            if cmds.nodeType(child) in validTypes:
                                movers.append(child)
                        except Exception, e:
                            print e

                    for mover in movers:
                        try:
                            cmds.rename(mover, self.name + "_" + mover)
                            print mover, self.name + "_" + mover
                            print str(e)

                    # exit loop
                    break

            # assign materials if they exist, removing duplicate materials
            materials = [["*_blue_m", "blue_m"], ["*_green_m", "green_m"], ["*_red_m", "red_m"],
                         ["*_white_m", "white_m"], ["*_proxy_shader_tan", "proxy_shader_tan"],
                         ["*_proxy_shader_black", "proxy_shader_black"]]
            deleteMaterials = []
            for material in materials:
                try:
                    # select materials for the joint mover
                    cmds.select(material[0])
                    foundMaterials = cmds.ls(sl=True)

                    # loop through each color material (dupes)
                    for mat in foundMaterials:
                        cmds.hyperShade(objects=mat)
                        assignedGeo = cmds.ls(sl=True)

                        # select the geo and the original material, and assign
                        originalMaterial = material[1]
                        for geo in assignedGeo:
                            cmds.select([geo, originalMaterial])
                            cmds.hyperShade(assign=originalMaterial)

                        # delete the material no longer needed
                        deleteMaterials.append(mat)
                except:
                    pass

            # delete all deleteMaterials
            for mat in deleteMaterials:
                cmds.delete(mat)

            # add to JointMover grp
            if not cmds.objExists("JointMover"):
                cmds.group(empty=True, name="JointMover")

            try:
                cmds.parent("|" + self.name + "_mover_grp", "JointMover")
                print str(e)
            globalMover = utils.findGlobalMoverFromName(self.name)
            cmds.select(globalMover)
            cmds.setToolTo("moveSuperContext")

            # obey visibility toggles
            self.rigUiInst.setMoverVisibility()

            # 1/13/16 Change # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
            # hook up global scale on joint movers
            try:
                movers = self.returnJointMovers

                for each in [movers[0], movers[1]]:
                    for mover in each:
                        if not cmds.objExists(mover + ".globalScale"):
                            try:
                                cmds.aliasAttr("globalScale", mover + ".scaleZ")
                                cmds.connectAttr(mover + ".globalScale", mover + ".scaleX")
                                cmds.connectAttr(mover + ".globalScale", mover + ".scaleY")
                                cmds.setAttr(mover + ".scaleX", keyable=False)
                                cmds.setAttr(mover + ".scaleY", keyable=False)
                            except:
                                pass
                # lock movers
                for each in movers:
                        cmds.lockNode(mover, lock=True)
            except:
                pass

                # 1/13/16 Change # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        else:
            cmds.confirmDialog(title="Joint Mover", message="Could not find associated joint mover file.",
                               icon="critical")

    def selectScriptJob(self, info):
        """
        Change icon color of the given joint mover's button in the outliner to show selection status.

        :param info: This list contains the button object, the joint mover control, and the original color icon.

        If the control given is selected, the icon is switched to a white icon. If it is not selected, the icon
        is set back to the original passed in icon.
        """

        pixmap = QtGui.QPixmap(20, 15)
        pixmap.fill(QtGui.QColor(255, 255, 255))
        whiteIcon = QtGui.QIcon(pixmap)

        # for each item in the passed in info [outliner button, mover control, unselected stylesheet],check if
        # the control is in the selection, and color the button appropriately
        for item in info:
            button = item[0]
            control = item[1]
            icon = item[2]

            selected = cmds.ls(selection=True)
            if control not in selected:
                self.outlinerWidgets[button].setIcon(icon)

            if control in selected:
                self.outlinerWidgets[button].setIcon(whiteIcon)

    def createGlobalMoverButton(self, name, parent, uiInstance):
        """
        Create the button in the outliner for the global mover control of a joint mover.

        :param name: The name of the joint mover control.
        :param parent: The outliner widget the created button will be parented to.
        :param uiInstance: The Rig Creator interface instance.
        """

        part = name.partition(self.name)[2]

        # create the icon
        pixmap.fill(QtGui.QColor("yellow"))
        icon = QtGui.QIcon(pixmap)

        self.outlinerWidgets[name + "_globalMoverBtn"] = QtWidgets.QPushButton(icon, "")
        self.outlinerWidgets[name + "_globalMoverBtn"].setMinimumSize(QtCore.QSize(20, 15))
        self.outlinerWidgets[name + "_globalMoverBtn"].setMaximumSize(QtCore.QSize(20, 15))
        uiInstance.treeWidget.setItemWidget(parent, 1, self.outlinerWidgets[name + "_globalMoverBtn"])

        # connect and add to list
        self.outlinerWidgets[name + "_globalMoverBtn"].clicked.connect(
            partial(self.selectMover, part, True, False, False, self.outlinerWidgets[name + "_globalMoverBtn"]))
        self.outlinerControls.append([name + "_globalMoverBtn", name + "_mover", icon])

    def createOffsetMoverButton(self, name, parent, uiInstance):
        """
        Create the button in the outliner for the offset mover control of a joint mover.

        """

        pixmap.fill(QtGui.QColor(100, 200, 255))

        self.outlinerWidgets[name + "_offsetMoverBtn"] = QtWidgets.QPushButton(icon, "")
        self.outlinerWidgets[name + "_offsetMoverBtn"].setMinimumSize(QtCore.QSize(20, 15))
        self.outlinerWidgets[name + "_offsetMoverBtn"].setMaximumSize(QtCore.QSize(20, 15))
        uiInstance.treeWidget.setItemWidget(parent, 2, self.outlinerWidgets[name + "_offsetMoverBtn"])

        self.outlinerWidgets[name + "_offsetMoverBtn"].clicked.connect(
            partial(self.selectMover, part, False, True, False, self.outlinerWidgets[name + "_offsetMoverBtn"]))
        self.outlinerControls.append([name + "_offsetMoverBtn", name + "_mover_offset", icon])

    def createMeshMoverButton(self, name, parent, uiInstance):
        """
        Create the button in the outliner for the geometry mover control of a joint mover.

        Note: The geometry mover is purely for aesthetics and does not affect the rigging.
        """

        pixmap.fill(QtGui.QColor(255, 176, 176))

        self.outlinerWidgets[name + "_geoMoverBtn"] = QtWidgets.QPushButton(icon, "")
        self.outlinerWidgets[name + "_geoMoverBtn"].setMinimumSize(QtCore.QSize(20, 15))
        self.outlinerWidgets[name + "_geoMoverBtn"].setMaximumSize(QtCore.QSize(20, 15))
        uiInstance.treeWidget.setItemWidget(parent, 3, self.outlinerWidgets[name + "_geoMoverBtn"])

        self.outlinerWidgets[name + "_geoMoverBtn"].clicked.connect(
            partial(self.selectMover, part, False, False, True, self.outlinerWidgets[name + "_geoMoverBtn"]))
        self.outlinerControls.append([name + "_geoMoverBtn", name + "_mover_geo", icon])

    def createScriptJob(self):
        """
        Create the selection script job for the outliner buttons and their associated joint mover controls.

        This function purely creates the script job. The script job function that is run is called self.selectScriptJob.
        """

        self.scriptJob = cmds.scriptJob(
            event=["SelectionChanged", partial(self.selectScriptJob, self.outlinerControls)], runOnce=False,
            parent="pyArtRigCreatorUi", kws=True, per=False)
        self.rigUiInst.scriptJobs.append(self.scriptJob)

    def selectMover(self, part, globalMover, offsetMover, geoMover, button):
        """
        Select the appropriate joint mover control based on the args passed in. Color the associated button white
        to show selection status.

        :param part: The name of the joint mover control.
        :param globalMover: Boolean of whether or not given control is a global mover.
        :param offsetMover: Boolean of whether or not given control is an offset mover.
        :param geoMover:  Boolean of whether or not given control is a mesh mover.
        :param button: The button in the outliner associated with the given mover.
        """

        # select mover and color button
        name = self.name + part

        # get modifiers
        toggle = False
        if (mods & 1) > 0:
            toggle = True

        if globalMover:
            cmds.select(name + "_mover", tgl=toggle)

            selected = cmds.ls(sl=True)
            if name + "_mover" in selected:
                button.setStyleSheet('background-color: rgb(255, 255, 255);')
            else:
                button.setStyleSheet('background-color: rgb(255, 255, 0);')

        if offsetMover:
            cmds.select(name + "_mover_offset", tgl=toggle)

            if name + "_mover_offset" in selected:
                self.outlinerWidgets[name + "_offsetMoverBtn"].setStyleSheet('background-color: rgb(255, 255, 255);')
            else:
                self.outlinerWidgets[name + "_offsetMoverBtn"].setStyleSheet('background-color: rgb(100, 220, 255);')

        if geoMover:
            cmds.select(name + "_mover_geo", tgl=toggle)

            if name + "_mover_geo" in selected:
                self.outlinerWidgets[name + "_geoMoverBtn"].setStyleSheet('background-color: rgb(255, 255, 255);')
            else:
                self.outlinerWidgets[name + "_geoMoverBtn"].setStyleSheet('background-color: rgb(255, 176, 176);')

    def createContextMenu(self, point):
        """
        Create the right-click menu for the module.

        :param point: Point on monitor to spawn the right-click menu.

        Default menu actions created:
            * Copy Settings
            * Paste Settings
            * Reset Settings
            * Delete Module
            * Create Mirror of this Module
            * Mirror Transformations (only if a mirror is linked)
        """

        mirror = cmds.getAttr(networkNode + ".mirrorModule")

        # icons
        icon_copy = QtGui.QIcon(os.path.join(self.iconsPath, "System/copy.png"))
        icon_paste = QtGui.QIcon(os.path.join(self.iconsPath, "System/paste.png"))
        icon_reset = QtGui.QIcon(os.path.join(self.iconsPath, "System/reset.png"))
        icon_delete = QtGui.QIcon(os.path.join(self.iconsPath, "System/delete.png"))
        icon_mirror = QtGui.QIcon(os.path.join(self.iconsPath, "System/mirrorXforms.png"))
        icon_createMirror = QtGui.QIcon(os.path.join(self.iconsPath, "System/createMirror.png"))

        # create the context menu
        if networkNode != "ART_Root_Module":
            self.contextMenu = QtWidgets.QMenu()
            self.contextMenu.addAction(icon_copy, "Copy Settings", self.copySettings)
            self.contextMenu.addAction(icon_paste, "Paste Settings", self.pasteSettings)
            self.contextMenu.addAction(icon_reset, "Reset Settings", self.resetSettings)

            self.contextMenu.addSeparator()
            if mirror != None:
                self.contextMenu.addAction(icon_mirror, "Mirror Transformations to " + mirror,
                                           self.mirrorTransformations)

            self.contextMenu.addAction(icon_createMirror, "Create Mirror of this Module", self.createMirrorOfModule_UI)

            self.contextMenu.addAction(icon_delete, "Delete Module", self.deleteModule)
            self.contextMenu.exec_(self.groupBox.mapToGlobal(point))

    def copySettings(self):
        """
        Copy the values from the network node of the module and store them in a temp file on disk.

        This function is used in the right-click menu of the module on the skeleton settings interface.
        Occasionally, it is called outside of the menu. For example, when creating a mirror of the module,
        the settings are copied for the source module to then be later pasted on the mirror.
        """

        attrs = cmds.listAttr(networkNode, ud=True, hd=True)

        attrData = []
            value = cmds.getAttr(networkNode + "." + attr)
            attrData.append([attr, value])

        # write out attrData to a temp file
        tempDir = cmds.internalVar(userTmpDir=True)
        clipboardFile = os.path.normcase(os.path.join(tempDir, "ART_clipboard.txt"))

        f = open(clipboardFile, 'w')

        # dump the data with json
        json.dump(attrData, f)
        f.close()

    def pasteSettings(self):
        """
        Paste the settings from the temp file on disk to the module's network node.

        After settings are pasted, applyModuleChanges is called to update the joint mover in the scene with
        the latest values. updateSettingsUI is also called to update the outliner.
        """

        # it does this 4 times because for some reason it would not grab everything one time through. Investigate
        for i in range(4):

            tempDir = cmds.internalVar(userTmpDir=True)
            clipboardFile = os.path.normcase(os.path.join(tempDir, "ART_clipboard.txt"))

            if os.path.exists(clipboardFile):
                # load the data
                json_file = open(clipboardFile)
                data = json.load(json_file)
                json_file.close()

                # attempt to paste data if module type is the same
                networkNode = self.returnNetworkNode
                moduleType = cmds.getAttr(networkNode + ".moduleType")
                if moduleType == data[0][1]:

                    for each in data:
                        attr = each[0]
                        value = each[1]

                        try:
                            attrType = str(cmds.getAttr(networkNode + "." + attr, type=True))

                            if attrType != "string":
                                cmds.setAttr(networkNode + "." + attr, lock=False)
                                cmds.setAttr(networkNode + "." + attr, value, lock=True)
                        except:
                            pass

            else:
                cmds.warning("No data in clipboard")

        # relaunch the UI

    def resetSettings(self):
        """
        Reset the settings of the module's network node.

        Occasionally, it is called outside of the menu.

        After settings are reset, applyModuleChanges is called to update the joint mover in the scene with
        """

            networkNode = self.returnNetworkNode
            attrs = cmds.listAttr(networkNode, ud=True)

            for attr in attrs:
                attrType = str(cmds.getAttr(networkNode + "." + attr, type=True))

                if attrType == "double":
                    cmds.setAttr(networkNode + "." + attr, lock=False)
                    cmds.setAttr(networkNode + "." + attr, 0, lock=True)

                if attrType == "bool":
                    cmds.setAttr(networkNode + "." + attr, True, lock=True)

                if attrType == "enum":

    def resetTransforms(self, translate, rotate, scale, name):
        """
        Reset the given attributes of all movers in the module.

        :param translate: Boolean of whether or not to reset translation values.
        :param rotate: Boolean of whether or not to reset the rotation values.
        :param scale: Boolean of whether or not to reset the scale values.
        :param name: The module name. (prefix +  basename + suffix)

        This function is mainly called from ART_ResetModeUI.

        """

        globalMovers = []
        offsetMovers = []
        geoMovers = []

            if each.find("_mover") != -1:
                if each.partition("_mover")[2] == "":
                    globalMovers.append(each)
            if each.find("_mover_offset") != -1:
                if each.partition("_mover_offset")[2] == "":
                    offsetMovers.append(each)
            if each.find("_mover_geo") != -1:
                if each.partition("_mover_geo")[2] == "":
                    geoMovers.append(each)

        for moverList in [globalMovers, offsetMovers, geoMovers]:
            for each in moverList:
                if translate:
                    for attr in [".tx", ".ty", ".tz"]:
                        try:
                            cmds.setAttr(each + attr, 0)
                        except:
                            pass
                if rotate:
                    for attr in [".rx", ".ry", ".rz"]:
                        try:
                        except:
                            pass
                if scale:
                    for attr in [".sx", ".sy", ".sz"]:
                        try:
                            cmds.setAttr(each + attr, 1)
                        except:
                            pass
        if cmds.window("ART_ResetXformsModeWin", exists=True):
            cmds.deleteUI("ART_ResetXformsModeWin", wnd=True)

    def deleteModule(self):
        """
        Delete the module and all associated nodes and interfaces.

        First, this will delete the joint mover, remove the entry from the outliner and the skeleton settings UI.
        Then, it has to deal with any connected modules or mirror modules and resolve any issues there.

        """

        # delete the joint mover

        # remove the entry from the outliner
        index = self.rigUiInst.treeWidget.indexOfTopLevelItem(self.outlinerWidgets[self.name + "_treeModule"])
        self.rigUiInst.treeWidget.takeTopLevelItem(index)

        # remove the groupbox
        self.groupBox.setParent(None)

        # deal with mirror module
        mirrorModule = cmds.getAttr(networkNode + ".mirrorModule")
        if mirrorModule != None:
            if mirrorModule != "None":
                modules = utils.returnRigModules()
                for mod in modules:
                    if modName == mirrorModule:

                        # set the mirrored version
                        cmds.setAttr(mod + ".mirrorModule", lock=False)
                        cmds.setAttr(mod + ".mirrorModule", "None", type="string", lock=True)

                        # get instance of mirror module's class
                        modType = cmds.getAttr(mod + ".moduleType")
                        modName = cmds.getAttr(mod + ".moduleName")
                        module = __import__("RigModules." + modType, {}, {}, [modType])

                        # get the class name from that module file (returns Modules.ART_Root.ART_Root for example)
                        moduleClass = getattr(module, module.className)

                        # find the instance of that module and call on the skeletonSettings_UI function
                        moduleInst = moduleClass(self.rigUiInst, modName)

                        # find the current groupBox for this module
                        for i in range(self.rigUiInst.moduleSettingsLayout.count()):
                            if type(self.rigUiInst.moduleSettingsLayout.itemAt(i).widget()) == QtWidgets.QGroupBox:
                                if self.rigUiInst.moduleSettingsLayout.itemAt(i).widget().title() == modName:
                                    self.rigUiInst.moduleSettingsLayout.itemAt(i).widget().setParent(None)

                                    # relaunch the skeleton settings UI with new info
                                    moduleInst.skeletonSettings_UI(modName)

        # check for any attached modules
        attachedModules = self.checkForDependencies()
        elementList = []

                cmds.parent(each[2] + "_mover_grp", "root_mover")
                cmds.setAttr(each[0] + ".parentModuleBone", lock=False)
                cmds.setAttr(each[0] + ".parentModuleBone", "root", type="string", lock=True)
                each[3].currentParent.setText("root")

        # remove the network node
        cmds.delete(networkNode)

        # delete scriptJob
        cmds.scriptJob(kill=self.scriptJob, force=True)
        self.rigUiInst.moduleInstances.remove(self)

        # warn user about changes
                                               "The following modules have had their parent changed\
                                               due to the change in this module's structure:",

    def checkForDependencies(self):
        """
        This method will check modules for any attached modules or children modules.

        This method is generally called when deleting a module or when changing a module name
        so that any connected modules are updated accordingly.

        :return: attached modules

        """

        # This method will check our module for any attached modules
        modules = self.getAllModules

        attachedMods = []
        instances = {}

        for inst in self.rigUiInst.moduleInstances:
            networkNode = inst.returnNetworkNode
            instances[networkNode] = inst

            parentJoint = cmds.getAttr(module + ".parentModuleBone")
            if parentJoint in joints:
                instance = instances.get(module)
                attachedMods.append([module, parentJoint, moduleName, instance])

        return attachedMods

    def createMirrorOfModule_UI(self):
        """
        This method builds the interface for creating a mirror of a module.

        .. image:: /images/mirrorModule.png

        """

        # copy the settings of the module
        self.copySettings()

        # get basename and classname
        className = cmds.getAttr(networkNode + ".moduleType")

        self.mirrorWindow = QtWidgets.QMainWindow()

        f.close()

        self.mirrorWindow.setStyleSheet(style)

        self.mirrorWindow.setCentralWidget(self.mainWidget)

        self.mirrorWindow.setObjectName("ART_createMirrorModuleUI")
        self.mirrorWindow.setWindowTitle("Create Mirror Module")

        self.mirrorWindow.resize(300, 150)
        self.mirrorWindow.setSizePolicy(mainSizePolicy)
        self.mirrorWindow.setMinimumSize(QtCore.QSize(300, 150))
        self.mirrorWindow.setMaximumSize(QtCore.QSize(300, 150))

        self.prefix.textChanged.connect(partial(self.updatePreview, baseName))

        self.suffix.textChanged.connect(partial(self.updatePreview, baseName))

        # spacer

        self.previewName = QtWidgets.QLabel(baseName)

        self.widgetLayout.addWidget(self.createButton)
        self.createButton.setMinimumSize(QtCore.QSize(285, 40))
        self.createButton.setMaximumSize(QtCore.QSize(285, 40))

        self.createButton.clicked.connect(self.createMirrorModule)

        self.mirrorWindow.show()

    def updatePreview(self, baseName, *args):
        """
        This simple method updates the module preview field(QLineEdit) with the entered prefix and suffix.

        :param baseName: base name of the module (example: arm)

        """

        string = ""

        string += baseName

    def createMirrorModule(self):
        """
        This method creates the mirror of a module and is called from createMirrorOfModule_UI.

        To create the mirror of a module, after a few checks are done, a module of the same type is created first.
        If that module type has a left/right version of a joint mover file, the opposite version is brought in.
        All the normal steps of module creation are then gone through and lastly, mirrorTransformations is called.

        """

        mirrorModule = None
        moduleName = None

                                   message="A module with that name already exists. Please enter a unique name \
                                   for the module",
                return

        # now check the modules that contain the parent bone
            bones = cmds.getAttr(module + ".Created_Bones")
            splitJoints = bones.split("::")
            createdJoints = []

            # create a list of the created bones
                    createdJoints.append(bone)

            # see if the parent bone is in that list
            if parent in createdJoints:
                mirrorModule = cmds.getAttr(module + ".mirrorModule")
                moduleName = cmds.getAttr(module + ".moduleName")

        # if our parent bone's module, has a mirror module, we need to create this new mirror module under that
        # parent instead (if parent = l_thigh, mirror parent should be r_thigh)
        if mirrorModule is not None:
                if modName == mirrorModule:

                    # find the parent's mover (if parent is l_thigh, mover would be l_leg_thigh_mover)
                    networkNodes = utils.returnRigModules()
                    mover = utils.findMoverNodeFromJointName(networkNodes, parent, False, True)

                    # find mirror mover
                    mirrorMover = mover.replace(moduleName, mirrorModule)
                    baseName = cmds.getAttr(module + ".baseName")
                    boneList = cmds.getAttr(module + ".Created_Bones")

                    # now, we need to find the joint from the mirror mover, and once there is a match, the parent\
                    # var now becomes that joint
                    if mirrorMover.find("_mover") != -1:
                        jointName = mirrorMover.partition("_mover")[0]

                        if jointName in boneList:
                            parent = jointName

                        else:
                            # if removing _mover didn't yield a matching joint name, take out the baseName from\
                            # the mover name, and then remove the _mover
                            jointName = jointName.replace(baseName + "_", "")

                            if jointName in boneList:
                                parent = jointName

        # arms and leg exception
        mirrorSide = None
            side = cmds.getAttr(networkNode + ".side")
            if side == "Left":
                mirrorSide = "Right"
            if side == "Right":
                mirrorSide = "Left"

        # create an instance of the module
        mod = __import__("RigModules." + className, {}, {}, [className])

        self.rigUiInst.moduleInstances.append(moduleInst)
        networkNodeInst = moduleInst.buildNetwork()

        # if mirrorSide exists
        if mirrorSide is not None:
            jmPath = jmPath.partition(".ma")[0] + "_" + mirrorSide + ".ma"
            if mirrorSide == "Left":
                cmds.setAttr(networkNodeInst + ".side", lock=False)
                cmds.setAttr(networkNodeInst + ".side", "Left", type="string", lock=True)
            if mirrorSide == "Right":
                cmds.setAttr(networkNodeInst + ".side", "Right", type="string", lock=True)

        # build the settings UI/joint mover/add to outliner

        createdBones = cmds.getAttr(networkNodeInst + ".Created_Bones")

        cmds.setAttr(networkNodeInst + ".Created_Bones", lock=False)
        cmds.setAttr(networkNodeInst + ".Created_Bones", attrString, type="string", lock=True)

        cmds.setAttr(networkNodeInst + ".parentModuleBone", lock=False)
        cmds.setAttr(networkNodeInst + ".parentModuleBone", parent, type="string", lock=True)

        # find the current parent mover and its scale
            offsetMover = "root_mover"

        else:
            offsetMover = utils.findMoverNodeFromJointName(networkNodes, parent)

        childMover = utils.findOffsetMoverFromName(userSpecName)
        riggingUtils.createBoneConnection(offsetMover, childMover, userSpecName)

        moduleInst.pasteSettings()

        # delete UI
        cmds.deleteUI("ART_createMirrorModuleUI", wnd=True)

        # update the mirrorModule setting
        self.mirrorMod.setText(userSpecName)
        moduleInst.mirrorMod.setText(name)

        cmds.setAttr(networkNode + ".mirrorModule", lock=False)
        cmds.setAttr(networkNode + ".mirrorModule", userSpecName, type="string", lock=True)

        cmds.setAttr(networkNodeInst + ".mirrorModule", lock=False)
        cmds.setAttr(networkNodeInst + ".mirrorModule", name, type="string", lock=True)

        # mirror transformations
        self.mirrorTransformations()

    def mirrorTransformations_RigPose(self):
        """
        This method is used when creating rig poses for modules. If a module has a mirror, this method will mirror the
        rig pose transformations to that mirror module.

        """

        # reset mirror module's rig pose
        mirrorModInst = self.returnMirrorModuleInst

        # ensure the mirrorModInst has a UI and is setup for rig pose creation
        mirrorModInst.setupForRigPose()
        # if not cmds.objExists(mirrorModInst.name + "_rigPose"):
        #     mirrorModInst.getReferencePose("rigPose")

        # call on base mirror transformations method
        mirrorModInst.getReferencePose("rigPose")

        # update the rig pose of the mirrorModInst
        mirrorModInst.updateRigPose(mirrorModInst.overallSlider)
        mirrorModInst.cleanUpRigPose()

    def mirrorTransformations(self):
        """
        This method mirrors transformations for the module's mirror module.

        """

        # get the mirror module

        # get mirror module instance and information
        mirrorInst = self.returnMirrorModuleInst

        # turn off aim mode
        mirrorInst.aimMode_Setup(False)

        # turn off coplanar mode IF it exists on the module
        try:
            state = mirrorInst.coplanarBtn.isChecked()
            if state:
                mirrorInst.coplanarBtn.setChecked(False)
                mirrorInst.coplanarMode()
        except:
            pass

        moverTypes = self.returnJointMovers
        for moverType in moverTypes:
            for jointMover in moverType:
                attrs = cmds.listAttr(jointMover, keyable=True)

                    value = cmds.getAttr(jointMover + "." + attr)

                    mirrorMover = jointMover.partition(moduleName)[2]
                    mirrorMover = mirrorModule + mirrorMover
                    mirrorAttrs = ["translateX", "translateY", "translateZ"]

                    if attr in mirrorAttrs:
                        cmds.setAttr(mirrorMover + "." + attr, value * -1)
                    else:
                        cmds.setAttr(mirrorMover + "." + attr, value)

        if len(currentSelection) > 0:
            cmds.select(currentSelection)

        # turn aim mode on
        mirrorInst.aimMode_Setup(True)

        # extend functionality
        self.mirrorTransformations_Custom()

    def rigPose_UI(self, parentWidget):
        """
        This method creates the UI widget that gets parented into the publish UI that handles rig pose creation.

        A slider gets created for the overall module that goes from current pose to ideal rig pose. Then a slider
        gets created for each joint in the module to allow for finer control over the rig pose creation.

        :param parentWidget: the widget the rig pose UI (QFrame) will get parented to

        """

        # Add a QFrame for the widget
        self.rigPoseFrame = QtWidgets.QFrame()
        self.rigPoseFrame.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
        self.rigPoseFrame.setMinimumWidth(310)
        self.rigPoseFrame.setMaximumWidth(310)

        f.close()

        # add the rig pose frame to the stackWidget
        parentWidget.addWidget(self.rigPoseFrame)
        numWidgetsInStack = parentWidget.count()
        parentWidget.setCurrentIndex(numWidgetsInStack - 1)

        # frame styling
        backgroundImg = utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/field_background.png"))
        self.rigPoseFrame.setStyleSheet("background-image: url(" + backgroundImg + ");")

        # create an overall layout
        self.rigPoseLayout = QtWidgets.QVBoxLayout(self.rigPoseFrame)
        label = QtWidgets.QLabel(self.name)
        self.rigPoseLayout.addWidget(label)

        # create a slider for the overall module rig pose
        hboxLayout = QtWidgets.QHBoxLayout()
        self.rigPoseLayout.addLayout(hboxLayout)

        image1 = QtWidgets.QFrame()
        image1.setMinimumSize(30, 30)
        image1.setMaximumSize(30, 30)
        modelPoseImg = utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/modelPose.png"))
        image1.setStyleSheet("background-image: url(" + modelPoseImg + ");")
        hboxLayout.addWidget(image1)
        image1.setToolTip("Model Pose")

        self.overallSlider = QtWidgets.QSlider()
        self.overallSlider.setProperty("name", self.name)
        hboxLayout.addWidget(self.overallSlider)
        self.overallSlider.setOrientation(QtCore.Qt.Horizontal)
        self.overallSlider.setRange(0, 100)
        self.overallSlider.setSingleStep(1)
        self.overallSlider.setTracking(False)

        image2 = QtWidgets.QFrame()
        image2.setMinimumSize(30, 30)
        image2.setMaximumSize(30, 30)
        rigPoseImg = utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/rigPose.png"))
        image2.setStyleSheet("background-image: url(" + rigPoseImg + ");")
        hboxLayout.addWidget(image2)
        image2.setToolTip("Rig Pose")

        # create hboxlayout for resetAll and update Rig Pose buttons
        buttonLayout = QtWidgets.QHBoxLayout()
        self.rigPoseLayout.addLayout(buttonLayout)

        self.rigPoseResetAllBtn = QtWidgets.QPushButton("Reset Rig Pose")
        buttonLayout.addWidget(self.rigPoseResetAllBtn)
        self.rigPoseResetAllBtn.clicked.connect(self.resetRigPose)
        self.rigPoseResetAllBtn.setToolTip("Reset the module to it's initial ideal rig pose.")
        self.rigPoseResetAllBtn.setObjectName("blueButton")
        self.rigPoseResetAllBtn.setStyleSheet(self.style)

        self.rigPoseUpdateAllBtn = QtWidgets.QPushButton("Update Rig Pose")
        self.rigPoseUpdateAllBtn.setStyleSheet(self.style)
        buttonLayout.addWidget(self.rigPoseUpdateAllBtn)
        self.rigPoseUpdateAllBtn.clicked.connect(partial(self.updateRigPose, self.overallSlider))
        self.rigPoseUpdateAllBtn.setToolTip(
            "Update the rig pose if you've done any custom manipulations to the controls.")
        self.rigPoseUpdateAllBtn.setObjectName("blueButton")

        # create a frame for the advanced controls
        self.rigPose_advancedGroup = QtWidgets.QGroupBox("Advanced")
        self.rigPoseLayout.addWidget(self.rigPose_advancedGroup)
        self.rigPose_advancedLayout = QtWidgets.QVBoxLayout(self.rigPose_advancedGroup)

        # create a slider for each created joint

            if cmds.objExists(joint):
                self.createRigPoseSliderForJoint(joint)

            else:
                jointBaseName = joint
                if self.name != baseName:
                    nameData = self.name.split(baseName)

                    if nameData[0] != "":
                        jointBaseName = jointBaseName.partition(nameData[0])[2]
                    if nameData[1] != "":
                        jointBaseName = jointBaseName.partition(nameData[1])[0]

                if cmds.objExists(self.name + "_" + jointBaseName + "_mover"):
                    self.createRigPoseSliderForJoint(joint)

        # create mirror button if applicable
        if cmds.getAttr(networkNode + ".mirrorModule") != "":
            mirrorMod = cmds.getAttr(networkNode + ".mirrorModule")
            if mirrorMod != None:
                self.rigPoseMirrorBtn = QtWidgets.QPushButton("Mirror to: " + mirrorMod)
                self.rigPoseLayout.addWidget(self.rigPoseMirrorBtn)
                self.rigPoseMirrorBtn.clicked.connect(self.mirrorTransformations_RigPose)
                self.rigPoseMirrorBtn.setObjectName("blueButton")
                self.rigPoseMirrorBtn.setStyleSheet(self.style)

        self.rigPoseLayout.addSpacerItem(
            QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding))

    def buildRig(self, textEdit, uiInst):
        """
        This method starts building the rig for a module. It will then call on buildRigCustom, which is implemented
        in each derived module class as an override function.

        :param textEdit: The text edit in the buildProgressUI that we output information to.
        :param uiInst: passed in instance of the buildProgressUI

        """

        # get current nodes in scene
        currentNodes = cmds.ls("*", long=True)
        successfulBuild = True
        errorMessage = ""

        # run the instance build function
        try:
            self.buildRigCustom(textEdit, uiInst)

        except Exception, e:
            successfulBuild = False
            errorMessage = str(traceback.format_exc())

        # get all nodes in scene and compare to original list
        allNodes = cmds.ls("*", long=True)

        newNodes = list(set(allNodes).difference(currentNodes))

        for node in newNodes:
            if not cmds.objExists(node + ".sourceModule"):
                cmds.addAttr(node, ln="sourceModule", dt="string")

            try:
                cmds.setAttr(node + ".sourceModule", self.name, type="string")
            except:
                print node

        if not successfulBuild:
            print "Build Rig Failed: " + str(e)
            print errorMessage
            # self.deleteRig()

        """
        This method is what truly builds the rig for each module. It is implemented in the derived module class.

        """
        pass

        """
        This method deletes all rigging for the module.

        """

        allNodes = cmds.ls("*")
            if cmds.objExists(node + ".sourceModule"):
                cmds.lockNode(node, lock=False)
                source = cmds.getAttr(node + ".sourceModule")
                if source == self.name:
                    try:
                        cmds.delete(node)
                    except:
                        pass

    def mirrorTransformations_Custom(self):
        """
        This method is implemented in the derived module class.
        """

        pass

        """
        """

        pass

        """
        """

        pass

        """
        """

        pass

        """
        """

        pass

        """
        """

        pass

    def aimMode(self, state):
        """
        This method toggles the aim mode state if the module can have aim mode.

        It then calls on each derived module's aimMode_Setup which defines how to setup aim mode for the module.

        """

        cmds.setAttr(networkNode + ".aimMode", lock=False)
        cmds.setAttr(networkNode + ".aimMode", state, lock=True)

        self.aimMode_Setup(state)

    def aimMode_Setup(self, state):
        """
        """

        pass

    def setupModelPoseForRig(self):
        """
        """

        pass

    def matchModelPose(self):
        """

        """

        pass

        """

        """

        pass

        """
        This method bakes any transforms on the offset movers up to the global movers, and then zeroes out the offset
        movers.

        """

        # get movers
        jointMovers = self.returnJointMovers

        # separate mover lists
        globalMovers = jointMovers[0]
        offsetMovers = jointMovers[1]

        # create locators for the offsetMovers, then zero out offset mover
        for mover in offsetMovers:
            locatorName = mover.partition("_offset")[0] + "_loc"
            loc = cmds.spaceLocator(name=locatorName)[0]

            # constrain locator
            constraint = cmds.parentConstraint(mover, loc)[0]

            # parent locator under a copy of the locatorName
            parentLoc = cmds.duplicate(loc)[0]
            cmds.parent(loc, parentLoc)
            locators.append(parentLoc)

                try:
                    cmds.setAttr(mover + attr, 0)
                except:
                    pass

        # snap global movers to locators
        for mover in globalMovers:
            if cmds.objExists(mover + "_loc"):
                constraint = cmds.parentConstraint(mover + "_loc", mover)[0]
                constraints.append(constraint)

        # remove locs

    def setupForRigPose(self):
        """
        This method unhides the movers and constrains the joints to the movers for creating the rig pose.

        If the user wants to create a custom rig pose (instead of using the sliders), this method sets the module
        up for that functionality.

        """

        # unlock joint movers
        cmds.select("JointMover", hi=True)
        jmNodes = cmds.ls(sl=True)
        for node in jmNodes:
            cmds.lockNode(node, lock=False)

        # find the mover shapes and set their visibility
        globalMovers = movers[0]
        shapes = []

                child = cmds.listRelatives(mover, children=True, shapes=True)
                if len(child) > 0:
                    shapes.append(mover + "|" + child[0])

            for shape in shapes:
                cmds.setAttr(shape + ".v", lock=False)
                cmds.setAttr(shape + ".v", 0, lock=True)

        # show global movers
        shapes = []
            child = cmds.listRelatives(mover, children=True, shapes=True)
            if len(child) > 0:
                shapes.append(mover + "|" + child[0])

        for shape in shapes:
            cmds.setAttr(shape + ".v", lock=False)
            cmds.setAttr(shape + ".v", 1, lock=True)

        # unlock mover group for this module and make visible
        cmds.lockNode(self.name + "_mover_grp", lock=False)
        cmds.setAttr(self.name + "_mover_grp.v", lock=False)
        cmds.setAttr(self.name + "_mover_grp.v", 1)

        # hide the proxy geo
        cmds.select(self.name + "_mover_grp", hi=True)
        allNodes = cmds.ls(sl=True)
            if node.find("_proxy_geo") != -1:
                if cmds.nodeType(node) == "mesh":
                    parent = cmds.listRelatives(node, parent=True)[0]
                    cmds.lockNode(parent, lock=False)
                    cmds.setAttr(parent + ".v", lock=False)
                    cmds.setAttr(parent + ".v", 0)
                    cmds.lockNode(parent, lock=True)

        # get the joints created by this module

        # create mover name

            if cmds.objExists(joint + "_mover_offset"):
                cmds.parentConstraint(joint + "_mover_offset", joint)
            else:

                if cmds.objExists(self.name + "_" + jointBaseName + "_mover_offset"):
                    cmds.parentConstraint(self.name + "_" + jointBaseName + "_mover_offset", joint)

        # lock joint movers
            cmds.lockNode(node, lock=True)

    def setSkeletonPose(self, poseType):
        """
        This method constrains the joints to the movers and then stores that pose data for those joints.

        This could be the model pose or the rig pose.

        :param poseType: whether to set the model pose or rig pose for the joints.

        """

            else:

        # set pose
        self.setReferencePose(poseType)

    def removeSkeletalConstraints(self):
        """
        This method removes any constraints on the joints. This tends to get called by removing rigging.

        """

        # get the joints created by this module and remove the constraints

                cmds.select(joint)
                cmds.delete(constraints=True)

            else:

                    cmds.select(joint)
                    cmds.delete(constraints=True)

    def cleanUpRigPose(self):
        """
        This method hides the joint movers and unconstrains the joints from the movers after setting a rig pose.

        """

        # show the proxy geo
                    cmds.setAttr(parent + ".v", 1)

        # unlock mover group for this module and make invisible

        cmds.setAttr(self.name + "_mover_grp.v", 0)

        cmds.setAttr(self.name + "_mover_grp.v", lock=True)
        cmds.lockNode(self.name + "_mover_grp", lock=True)

            else:

    def updateRigPose(self, slider):
        """
        This method updates what the stored rig pose is for a module. The default rig pose tends to be zeroed out
        rotations, but this function can essentially update what the max value on the rig pose slider sets the pose to.

        :param slider: The rig pose slider where the min is the current model pose and the max is the rig pose.

        """

        # get network node

        # get pose data off networkNode
        originalData = json.loads(cmds.getAttr(networkNode + ".rigPose"))
        newPose = []

        for data in originalData:
            moverData = {}
            mover = data.get("mover")
            moverData["mover"] = mover

            if cmds.objExists(mover):
                translate = cmds.getAttr(mover + ".translate")[0]
                rotate = cmds.getAttr(mover + ".rotate")[0]

                moverData["translate"] = translate
                moverData["rotate"] = rotate
                newPose.append(moverData)

        jsonString = json.dumps(newPose)
        cmds.setAttr(networkNode + ".rigPose", jsonString, type="string")
        slider.setValue(0)
        slider.setValue(100)

    def resetRigPose(self):
        """
        This method resets the module rig pose to be the default (zeroed rotations).

        """

        # remove the rigPose attribute on the networkNode
        cmds.deleteAttr(networkNode, at="rigPose")

        # recreate rig pose node with defaults
        self.getReferencePose("rigPose")

        # set slider
        self.overallSlider.setValue(0)
        self.overallSlider.setValue(100)

    def resetRigPose_Part(self, part):
        """
        This method resets the given joint (part) rig pose to be zeroed rotations. This is for the part slider on
        the rig pose UI in the advanced section.

        :param part: The given joint name slider.

        """

        # get the networkNode

        # get the poseData
        poseData = json.loads(cmds.getAttr(networkNode + ".rigPose"))

        # find our part in the pose data
        for data in poseData:
            if mover == part:
                rotate = data.get("rotate")

                try:
                    cmds.setAttr(mover + ".rotate", 0, 0, 0, type="double3")
                    data["rotate"] = (0.0, 0.0, 0.0)
                except:
                    pass

    def getReferencePose(self, poseType, zeroPose=True):
        """
        This method gets the model pose or the rig pose (depending on poseType) and stores that data for the movers.

        :param poseType: Whether or not to get the model pose or rig pose.
        :param zeroPose: Whether or not the default rig pose should be set to zeroed rotations.

        """

        # if rigPose already exists, then do not set values
        if poseType == "rigPose":
            if cmds.objExists(networkNode + "." + poseType):
                return

        # create the pose data attr if needed
        if not cmds.objExists(networkNode + "." + poseType):
            cmds.addAttr(networkNode, sn=poseType, dt="string")

        # create reference pose data dict
        poseData = []

        # loop through each mover, getting the translate and rotate values, creating an attribute on the network node
        # to store those values
        for moverList in [globalMovers, offsetMovers]:
            for mover in moverList:
                moverData = {}
                moverData["mover"] = mover

                for attr in ["translate", "rotate"]:
                    value = cmds.getAttr(mover + "." + attr)[0]

                    if zeroPose:
                        if poseType == "rigPose":
                            if attr == "rotate":
                                value = (0.0, 0.0, 0.0)

                    # add the data to the list
                    moverData[attr] = value

                    # add mover data to the pose data list
                    poseData.append(moverData)

        # dump the pose data list onto the poseType attribute
        jsonString = json.dumps(poseData)
        cmds.setAttr(networkNode + "." + poseType, jsonString, type="string")

    def setReferencePose(self, poseType):
        """
        This method gets the data for the given pose type (rig or model) and sets the movers with those values.

        :param poseType: Whether to set the model pose or the rig pose on the movers.

        """

        # get the pose data from the attribute
        if cmds.objExists(networkNode + "." + poseType):
            poseData = json.loads(cmds.getAttr(networkNode + "." + poseType))

            for data in poseData:
                mover = data.get("mover")
                translate = data.get("translate")

                # if the mover exists, set the values

                    # set translations
                    for i in range(len(translate)):
                        if i == 0:
                            try:
                                cmds.setAttr(mover + ".translateX", translate[i])
                            except:
                                pass
                        if i == 1:
                            try:
                                cmds.setAttr(mover + ".translateY", translate[i])
                            except:
                                pass
                        if i == 2:
                            try:
                                cmds.setAttr(mover + ".translateZ", translate[i])
                            except:
                                pass

                    # set rotations
                    for i in range(len(rotate)):
                        if i == 0:
                            try:
                                cmds.setAttr(mover + ".rotateX", rotate[i])
                            except:
                                pass
                        if i == 1:
                            try:
                                cmds.setAttr(mover + ".rotateY", rotate[i])
                            except:
                                pass
                        if i == 2:
                            try:
                                cmds.setAttr(mover + ".rotateZ", rotate[i])
                            except:
                                pass

    def setReferencePoseSlider(self, part, *args):
        """
        This method takes the slider value of a given part and then calls on setPosePercentage, which will then find
        the values of the model pose and the rig pose and figure out based on the slider percentage what values to
        set on the mover.

        :param part: the joint mover which the slider is controlling.
        :param args: the values from the slider

        """

        percent = float(args[0]) * .01
        self.setPosePercentage(percent, part)

    def setPosePercentage(self, percent, part):
        """
        This method takes the percent from setReferencePoseSlider, gets the values of the model pose and rig pose
        for the given part, then calls on setPosePercentage_Part to find and set the values on the mover that is
        the percentage between model and rig pose.

        Example: If the model pose is a value of 10 and the rig pose is a value of 0, and the slider is at .5, then
        the value to set is 5. (But this is done and found per attribute)

        :param percent: What percent of model and rig pose to set.
        :param part: What joint mover to set the values on.

        """

        # get reference pose attributes
        modelPoseData = json.loads(cmds.getAttr(networkNode + ".modelPose"))
        rigPoseData = json.loads(cmds.getAttr(networkNode + ".rigPose"))

        # get the data for each mover
        for poseData in modelPoseData:

            mover = poseData.get("mover")
            translate = poseData.get("translate")
            rotate = poseData.get("rotate")

            if part != None:
                if part == mover:
                    self.setPosePercentage_Part(percent, mover, modelPoseData, rigPoseData, poseData, translate, rotate)
            else:
                self.setPosePercentage_Part(percent, mover, modelPoseData, rigPoseData, poseData, translate, rotate)

    def setPosePercentage_Part(self, percent, mover, modelPoseData, rigPoseData, poseData, translate, rotate):
        """
        This method takes the data from setPosePercentage and figures out what values to set on the given part (mover).

        :param percent: the percent value of the slider. What percentage of the model and rig pose to use.
        :param mover: the mover to set the values on.
        :param modelPoseData: all of the data for the model pose for this mover.
        :param rigPoseData: all of the data for the rig pose for this mover.
        :param poseData: a list which includes the mover and its translate and rotate values.
        :param translate: the translate values for the model pose
        :param rotate: the rotate values for the model pose

        """

        # get the index of this entry in the rigPoseData list
        index = modelPoseData.index(poseData)

        # get the corresponding rig pose data
        rigData = rigPoseData[index]
        rigTranslate = rigData.get("translate")
        rigRotate = rigData.get("rotate")

        # find percentile between model and rig pose to set on each attribute
        for i in range(len(translate)):
            valueToSet = mathUtils.returnPercentile([translate[i], rigTranslate[i]], percent)
            if i == 0:
                cmds.setAttr(mover + ".translateX", valueToSet)
            if i == 1:
                cmds.setAttr(mover + ".translateY", valueToSet)
            if i == 2:
                cmds.setAttr(mover + ".translateZ", valueToSet)

        for i in range(len(rotate)):
            valueToSet = mathUtils.returnPercentile([rotate[i], rigRotate[i]], percent)
            if i == 0:
                cmds.setAttr(mover + ".rotateX", valueToSet)
            if i == 1:
                cmds.setAttr(mover + ".rotateY", valueToSet)
            if i == 2:
                cmds.setAttr(mover + ".rotateZ", valueToSet)

    def createRigPoseSliderForJoint(self, joint):
        """
        This method creates the rig pose slider widget for the given joint. (This shows up in the advanced section of
        the rig pose UI)

        :param joint: The joint that the slider will control.

        """

        f.close()

        jointName = joint

        if cmds.objExists(joint + "_mover"):
            jointName = joint

        else:

            jointBaseName = joint
            if self.name != baseName:
                nameData = self.name.split(baseName)

                if nameData[0] != "":
                    jointName = jointBaseName.partition(nameData[0])[2]
                if nameData[1] != "":
                    jointName = jointName.partition(nameData[1])[0]

                jointName = self.name + "_" + jointName

            else:

        # create a master vertical layout
        mainLayout = QtWidgets.QVBoxLayout()
        self.rigPose_advancedLayout.addLayout(mainLayout)

        # create a label for the joint
        font.setPointSize(10)
        jointLabel = QtWidgets.QLabel(joint + ":")
        jointLabel.setFont(font)
        mainLayout.addWidget(jointLabel)

        # create layout for slider/button
        layout = QtWidgets.QHBoxLayout()
        mainLayout.addLayout(layout)

        # create slider for joint
        slider = QtWidgets.QSlider()
        layout.addWidget(slider)
        slider.setProperty("name", joint)
        slider.setOrientation(QtCore.Qt.Horizontal)
        slider.setRange(0, 100)
        slider.setSingleStep(1)
        slider.valueChanged.connect(partial(self.setReferencePoseSlider, jointName + "_mover"))
        slider.setTracking(False)
        self.overallSlider.valueChanged.connect(slider.setValue)

        # create reset button
        button = QtWidgets.QPushButton("Reset")
        button.setMinimumWidth(70)
        button.setMaximumWidth(70)
        layout.addWidget(button)
        button.setStyleSheet(self.style)

        button.clicked.connect(partial(self.resetRigPose_Part, jointName + "_mover"))

        """
        This method looks at the create bones attribute of the module and gets the number of bones in that list
        and appends it onto the total bone count for the bone counter interface.

        """

            if self.rigUiInst.boneCounterInst is not None:
                self.rigUiInst.boneCounterInst.updateBoneCount()

    def toggleShapeVis(self, transform, value):
        """
        This method finds the shapes for the passed in transform and toggles the visibility based on the value.

        :param transform: the transform to get the shape nodes from.
        :param value: whether to show or hide the shape nodes.

        """

        if cmds.objExists(transform):
            shape = cmds.listRelatives(transform, shapes=True)
            if shape is not None:
                cmds.setAttr(shape[0] + ".v", lock=False)
                cmds.setAttr(shape[0] + ".v", value)
                cmds.setAttr(shape[0] + ".v", lock=True)

    def selectionScriptJob_animUI(self, buttonData):
        """
        This method is called from a scriptjob anytime a selection is changed. It's sole purpose it to update the button
        color on the anim picker to show if a control is selected or not.

        :param buttonData: pairings of button/control/brush. brush is the original color of the button.

        """

        selection = mel.eval("ls -sl;")
        if selection is None:
            selection = []

        for data in buttonData:
            control = data[1]
            button = data[0]
            brushColor = data[2]

            if control in selection:
                button.brush.setColor(QtCore.Qt.white)
                button.update()

            else:
                button.brush.setColor(brushColor)

    def importFBX_pre(self, importMethod, character):
        """
        This method runs before an fbx is imported onto the control rig. It cuts any keys on the controls and zeroes
        the controls out before importing the fbx (which is called in the derived module class)

        :param importMethod: Whether or not the FBX is getting imported as FK, IK, Both, or None
        :param character: The namespace of the rig.

        """

        if importMethod != "None":
            controls = self.getControls()

            for control in controls:
                cmds.cutKey(character + ":" + control)

            self.resetRigControls(True)

        """
        This method is implemented in the derived module class and defines how mocap is imported onto the rig controls.

        """

        pass

    def resetRigControls(self, resetAll):
        """
        This method zeroes out control attributes. If resetAll is true, then it will zero out all rig controls for
        the module. Otherwise, it will only zero out the selected controls of the module.

        :param resetAll: Whether or not to reset only the selected controls or all controls of the module.

        """

        # get namespace
        characterNode = cmds.listConnections(networkNode + ".parent")[0]
        namespace = cmds.getAttr(characterNode + ".namespace")

        if resetAll:

            # list any attributes on the network node that contain "controls"
            controls = cmds.listAttr(networkNode, st="*Controls")
            # get that data on that attr
                data = json.loads(cmds.getAttr(networkNode + "." + control))

                # reset the attr on each control
                nonZeroAttrs = ["scale", "globalScale", "scaleX", "scaleY", "scaleZ"]

                try:
                        attrs = cmds.listAttr(namespace + ":" + each, keyable=True)
                        for attr in attrs:
                            if attr not in nonZeroAttrs:
                                cmds.setAttr(namespace + ":" + each + "." + attr, 0)
                            else:
                                cmds.setAttr(namespace + ":" + each + "." + attr, 1)
                except:
                    cmds.warning("skipped " + str(control) + ". No valid controls found to reset.")

        if not resetAll:
            nonZeroAttrs = ["scale", "globalScale", "scaleX", "scaleY", "scaleZ"]
            selection = cmds.ls(sl=True)

            for each in selection:
                attrs = cmds.listAttr(each, keyable=True)

                    if attr not in nonZeroAttrs:
                        cmds.setAttr(each + "." + attr, 0)
                    else:
                        cmds.setAttr(each + "." + attr, 1)

    def getControls(self):
        """
        This method returns a list of all the rig controls of the module.

        :return: List of all rig controls in the module.

        """

        if networkNode is None:
            win = interfaceUtils.DialogMessage("Error", "This function does not work without a namespace.", [], 0,
                                               winParent)

        # list any attributes on the network node that contain "controls"
        controls = cmds.listAttr(networkNode, st="*Controls")

        # get that data on that attr
        for control in controls:
            data = json.loads(cmds.getAttr(networkNode + "." + control))

            # reset the attr on each control
            if data is not None:
                    returnControls.append(each)

    def selectRigControls(self):
        """
        This method calls on getControls to return a list of the controls and the selects them.

        """

        controls = self.getControls()

            cmds.select(namespace + ":" + control, add=True)

    # PROPERTIES
    @property
    def getModules(self):
        """
        This method finds the main "character" module that has connections to all of the rig modules

        :return: returns the character node.

        """

        modules = cmds.ls(type="network")
            attrs = cmds.listAttr(module)
                return module

    @property
    def getAllModules(self):
        """
        This method finds all connected rig modules to the main character network node.

        :return: returns a list of the rig modules

        """

        returnMods = []
            if "parent" in attrs:
                returnMods.append(module)

        return returnMods

    @property
    def returnNetworkNode(self):
        """
        This method returns the module's own network node.

        :return: the modules network node

        """

        networkNodes = cmds.ls(type="network")
        for node in networkNodes:
            if "moduleName" in attrs:
                if cmds.getAttr(node + ".moduleName") == self.name:
                    networkNode = node

        return networkNode

    @property
    def returnRigNetworkNode(self):
        """
        This method returns the module's own network node using the namespace on the main character
        network node. This is so that if there are multiple characters in a scene, we know which
        network node for which character we are trying to return.

        :return: returns this module's network node in a scene with references.

        """
        modules = []
                    characterNode = cmds.listConnections(node + ".parent")[0]
                    if cmds.objExists(characterNode + ".namespace"):
                        if cmds.getAttr(characterNode + ".namespace") == self.namespace.partition(":")[0]:
                            networkNode = node
                            return networkNode
                    else:
                        return None

    @property
    def returnClassObject(self):
        return self

    @property
    def returnCreatedJoints(self):
        """
        This method loops through the Created Bones attribute on its network node and returns a list of the
        joints it will create given the current module settings.

        :return: A list of the created bones of the module.

        """

        return createdJoints

    @property
    def returnJointMovers(self):
        """
        This method finds and returns all joint movers for the module.

        :return: a list of all global movers, offset movers, and geo movers for the module.

        """

        # select global movers
        cmds.select(name + "*_mover")
        globalMovers = cmds.ls(sl=True)

        # select offset movers
        cmds.select(name + "*_mover_offset")
        offsetMovers = cmds.ls(sl=True)

        # mesh movers
        cmds.select(name + "*_mover_geo")
        geoMovers = cmds.ls(sl=True)

        return [globalMovers, offsetMovers, geoMovers]

    @property
    def returnMirrorModuleInst(self):
        """
        This method finds and returns the instance of a module's mirror module.

        :return: a pointer in memory to the instance of the mirror module.

        """

        # find instance through rig UI inst
            moduleName = cmds.getAttr(networkNode + ".moduleName")
            if moduleName == mirrorModule:
                return inst

    @property
    def returnPrefixSuffix(self):
        """
        This method splits our module name by the base name and returns the prefix and suffix.

        :return: the user-defined prefix and suffix found by splitting the module name by the base name.

        """

        splitName = self.name.split(baseName)
        if splitName[0] != '':
            prefix = splitName[0]
        if splitName[1] != '':
            suffix = splitName[1]
        return [prefix, suffix]

"""
"""

import os

class ART_ExportMeshes(QtWidgets.QMainWindow):
    """
    This class is used to export skeletal meshes and skeletal mesh LODs.

    The UI has a robust suite of tools for managing LODs, bone removal for LODs, choosing
    which meshes are associated with a LOD, where weighting from removed bones will get transferred,
    and setting/viewing LOD poses (useful if you wanted to remove finger bones for a LOD, but not have
    "paddle hands"

        .. image:: /images/exportMeshes.png

    A look at the LOD tools for transferring weighting and managing LOD poses:

        .. image:: /images/lodTool.png

    """

    def __init__(self, mainUI, parent=None):
        """
        Instantiates the class, getting the QSettings, presenting a QMessageBox about saving the current file,
        creates a temporary file to do the export work out of (stripping out the rig and removing all connections
        from joints), set the model pose, then calls on the UI build.

        :param mainUI: Instance of the Rig Creator interface, from which this class was called.

        .. seealso:: ART_ExportMeshes.buildUI(), ART_ExportMeshes.populateUI()

        """

        super(ART_ExportMeshes, self).__init__(parent)

        if cmds.window("pyART_ExportMeshesWin", exists=True):
            cmds.deleteUI("pyART_ExportMeshesWin", wnd=True)

        # get settings

        # class vars
        self.lodPoseDict = {}

        # get current file
        self.saveFile = cmds.file(q=True, sceneName=True)

        # message box for confirming save action
        msgBax = QtWidgets.QMessageBox()
        msgBax.setText(
            "Please make sure any changes to the current file are saved before continuing. This process will be "
            "creating a temporary file to do all of the exporting from.")
        msgBax.setIcon(QtWidgets.QMessageBox.Warning)
        msgBax.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
        msgBax.setDefaultButton(QtWidgets.QMessageBox.Ok)
        ret = msgBax.exec_()

        if ret == QtWidgets.QMessageBox.Ok:

            # save file as a new temporary file
            self.tempFile = os.path.join(os.path.dirname(self.saveFile), "exportFile.ma")
            cmds.file(rename=self.tempFile)
            cmds.file(save=True)

            # remove all skeleton connections
            cmds.select("root", hi=True)
                connT = cmds.connectionInfo(each + ".translate", sourceFromDestination=True)
                if connT != '':
                    cmds.disconnectAttr(connT, each + ".translate")
                connR = cmds.connectionInfo(each + ".rotate", sourceFromDestination=True)
                if connR != '':
                    cmds.disconnectAttr(connR, each + ".rotate")
                connS = cmds.connectionInfo(each + ".scale", sourceFromDestination=True)
                if connS != '':
                    cmds.disconnectAttr(connS, each + ".scale")

            # remove rig and driver skeleton
            cmds.select("rig_grp", hi=True)

                cmds.lockNode(each, lock=False)

            cmds.delete("rig_grp")
            cmds.delete("driver_root")

            # show all joints
            joints = cmds.ls(sl=True)
            for joint in joints:
                cmds.setAttr(joint + ".v", 1)

            # go to model pose
            for inst in self.mainUI.moduleInstances:
                try:
                    # call on the module's bakeOffsets method
                    inst.setupForRigPose()
                    inst.setReferencePose("modelPose")
                    inst.cleanUpRigPose()
                    print e

            # build UI
            self.buildUI()

            # populate UI
            self.populateUI()

        """
        Gathers all information from the temp file (LOD meshes, bones to remove, etc), opens the export file,
        and applies that information to the network node in the export file. Lastly removes the temp file.

        """

        # save out data
        exportData = utils.findExportMeshData()

        # open original file
        cmds.file(self.saveFile, open=True, force=True)

        # apply data
        lodData = ["_Pose", "_Bones", "_Meshes", "_FilePath"]

        for each in exportData:
            # path, meshes, removeBones, poseData, lodAttr
            for d in lodData:

                # if the attr does not exist, create it
                if not cmds.objExists(characterNode + "." + each[4] + d):
                    if d != "_Meshes":
                        cmds.addAttr(characterNode, ln=each[4] + d, dt="string")
                    else:
                        cmds.addAttr(characterNode, ln=each[4] + d, at="message")

            # set the attr data
            dataString = json.dumps(each[0])
            cmds.setAttr(characterNode + "." + each[4] + "_FilePath", dataString, type="string")

            dataString = json.dumps(each[2])
            cmds.setAttr(characterNode + "." + each[4] + "_Bones", dataString, type="string")

            dataString = json.dumps(each[3])
            cmds.setAttr(characterNode + "." + each[4] + "_Pose", dataString, type="string")

            # first remove all connections
            i = each[4].partition("_")[2]
            connections = cmds.listConnections(characterNode + "." + each[4] + "_Meshes")
            if connections is not None:
                for conn in connections:
                    cmds.disconnectAttr(characterNode + "." + each[4] + "_Meshes", conn + ".lodGroup" + str(i))

            # Add attrs to meshes to connect up to the characterNode LOD meshes attr
            for mesh in each[1]:
                if not cmds.objExists(mesh + ".lodGroup" + str(i)):
                    cmds.addAttr(mesh, sn="lodGroup" + str(i), at="message")

                cmds.connectAttr(characterNode + "." + each[4] + "_Meshes", mesh + ".lodGroup" + str(i))

        # remove temp file
        os.remove(self.tempFile)

        """
        Builds the main interface for the tool, which allows additions of LODs, assignment of meshes to a given LOD,
        and assignment of bones to remove per LOD (which then opens another interface/tool).

        .. seealso:: ART_ExportMeshes.addBoneToList_UI(), ART_ExportMeshes.addMeshToList_UI()
        .. seealso:: ART_ExportMeshes.addMeshLOD(), ART_ExportMeshes.createLODpage()
        .. seealso:: ART_ExportMeshes.createExportMeshesPage(), ART_ExportMeshes.removeLodTab()
        .. seealso:: ART_ExportMeshes.export()

        Here is a breakdown image showing which UI elements call on which functions:

        .. image:: /images/exportMeshesBreakout.png

        """

        f.close()

        self.mainWidget.setStyleSheet(self.style)

        self.setObjectName("pyART_ExportMeshesWin")
        self.setWindowTitle("Export Skeletal Meshes")

        # font

        self.resize(600, 400)
        self.setMinimumSize(QtCore.QSize(600, 400))
        self.setMaximumSize(QtCore.QSize(600, 400))

        # build pages
        self.createExportMeshesPage()

        self.show()

    def createExportMeshesPage(self):
        """
        Creates the LOD0 Tab, which is a little bit unique from subsequent LOD tabs, as the Add/Remove LOD buttons
        are added, and the LOD0 tab can not be removed. It still calls on createLODpage to create the common
        elements, but this creates the framework for all of the LOD tabs.

        .. seealso:: ART_ExportMeshes.createLODpage()

        """

        self.exportMeshPage = QtWidgets.QFrame()
        self.exportMeshPage.setStyleSheet(self.style)
        self.exportMeshPage.setMinimumSize(560, 250)
        self.layout.addWidget(self.exportMeshPage)
        self.emPageMainLayout = QtWidgets.QVBoxLayout(self.exportMeshPage)
        self.exportMeshPage.setObjectName("dark")

        # add Tab Widget
        # tab stylesheet (tab stylesheet via QSS doesn't seem to work for some reason
        {
            background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(90,90,90), stop:1 rgb(30,30,30));
            width: 180px;
            font: 8pt;
        }
        {
            font: bold 10pt;
        }
        {
            background: rgb(132,95,16);
        }
        {
        }
        {
            border: 2px solid rgb(0,0,0);
        }
        """

        self.emPageTabs = QtWidgets.QTabWidget()
        self.emPageTabs.setStyleSheet(stylesheet)
        self.emPageMainLayout.addWidget(self.emPageTabs)

        self.emPageTabBar = QtWidgets.QTabBar()
        self.emPageTabs.setTabBar(self.emPageTabBar)
        self.emPageTabBar.setContentsMargins(0, 0, 0, 0)

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        # LOD0 tab
        self.createLODpage("LOD_0", False)

        cornerWidget = QtWidgets.QWidget()
        cornerLayout = QtWidgets.QHBoxLayout(cornerWidget)
        cornerLayout.setContentsMargins(0, 0, 0, 10)

        # remove lod button
        self.removeMeshLodBtn = QtWidgets.QPushButton("   -   ")
        self.removeMeshLodBtn.setObjectName("blueButton")
        self.removeMeshLodBtn.setMinimumHeight(20)
        self.removeMeshLodBtn.setToolTip("Remove Mesh LOD (current tab)")
        cornerLayout.addWidget(self.removeMeshLodBtn)
        self.removeMeshLodBtn.clicked.connect(self.removeLodTab)

        # add lod button
        self.addMeshLodBtn = QtWidgets.QPushButton("   +   ")
        self.addMeshLodBtn.setObjectName("blueButton")
        self.addMeshLodBtn.setMinimumHeight(20)
        self.addMeshLodBtn.setToolTip("Add Mesh LOD")
        cornerLayout.addWidget(self.addMeshLodBtn)
        self.addMeshLodBtn.clicked.connect(partial(self.addMeshLOD))

        self.emPageTabs.setCornerWidget(cornerWidget)

        # add continue/back buttons
        buttonlayout = QtWidgets.QHBoxLayout()
        self.emPageMainLayout.addLayout(buttonlayout)

        spacerItem = QtWidgets.QSpacerItem(300, 0)
        buttonlayout.addSpacerItem(spacerItem)

        self.emPage_continueButton = QtWidgets.QPushButton("Export")
        buttonlayout.addWidget(self.emPage_continueButton)
        self.emPage_continueButton.setFont(font)
        self.emPage_continueButton.setMinimumSize(250, 50)
        self.emPage_continueButton.setMaximumSize(250, 50)
        self.emPage_continueButton.clicked.connect(partial(self.export))
        self.emPage_continueButton.setObjectName("blueButton")

    def createLODpage(self, label, closeable):
        """
        Creates a tab (QFrame) for a new LOD, with the UI elements to set the output path for the FBX, to set the
        meshes assigned to the LOD, and to launch the bone removal tool for the LOD.

        :param label: The text label for the tab (LOD_#)
        :param closeable: Whether this LOD can be removed, thus removing the tab.

        """

        lodTab = QtWidgets.QFrame()
        lodTab.setStyleSheet(self.style)
        lodTab.setObjectName("mid")

        self.emPageTabs.addTab(lodTab, label)

        # horizontal layout
        mainLayout = QtWidgets.QHBoxLayout(lodTab)

        # left side column
        leftSide = QtWidgets.QVBoxLayout()
        mainLayout.addLayout(leftSide)

        # label
        meshLayout = QtWidgets.QHBoxLayout()
        leftSide.addLayout(meshLayout)
        layoutLabel = QtWidgets.QLabel("Meshes For " + label + ":")
        meshLayout.addWidget(layoutLabel)
        layoutLabel.setStyleSheet('background: transparent')

        chooseBtn = QtWidgets.QPushButton("Choose Meshes")
        chooseBtn.setObjectName("blueButton")
        meshLayout.addWidget(chooseBtn)

        # render mesh list
        meshList = QtWidgets.QListWidget()
        meshList.setToolTip("Select the meshes you want to include in this mesh LOD.")
        meshList.setProperty("listType", "mesh")
        leftSide.addWidget(meshList)
        meshList.setToolTip("List of meshes to include for this LOD")
        meshList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)

        chooseBtn.clicked.connect(partial(self.addMeshToList_UI, self, label, meshList))

        # right side column
        rightLayout = QtWidgets.QVBoxLayout()
        mainLayout.addLayout(rightLayout)

        # horizontal layout for line edit and button
        pathLayout = QtWidgets.QHBoxLayout()
        rightLayout.addLayout(pathLayout)

        lineEdit = QtWidgets.QLineEdit()
        pathLayout.addWidget(lineEdit)
        lineEdit.setMinimumHeight(35)
        lineEdit.setMaximumHeight(35)
        lineEdit.setPlaceholderText("FBX Export Path...")
        lineEdit.setToolTip("Define the file name and file location for this LOD FBX file.")
        lineEdit.textChanged.connect(partial(self.saveFilePath, lineEdit, label))

        browseBtn = QtWidgets.QPushButton()
        browseBtn.setMinimumSize(35, 35)
        browseBtn.setMaximumSize(35, 35)
        pathLayout.addWidget(browseBtn)
        icon = QtGui.QIcon(os.path.join(self.iconsPath, "System/fileBrowse.png"))
        browseBtn.setIconSize(QtCore.QSize(30, 30))
        browseBtn.setIcon(icon)
        browseBtn.clicked.connect(partial(self.browseToFBX, lineEdit))

        # label + add button
        removeBonesLayout = QtWidgets.QHBoxLayout()
        rightLayout.addLayout(removeBonesLayout)

        layoutLabel = QtWidgets.QLabel("Bones to remove for " + label + ":")
        removeBonesLayout.addWidget(layoutLabel)
        layoutLabel.setStyleSheet('background: transparent;')

        removeBtn = QtWidgets.QPushButton("+")
        removeBtn.setMinimumSize(35, 35)
        removeBtn.setMaximumSize(35, 35)
        removeBonesLayout.addWidget(removeBtn)
        removeBtn.setObjectName("blueButton")

        # bone list
        boneList = QtWidgets.QListWidget()
        boneList.setProperty("listType", "bone")
        rightLayout.addWidget(boneList)
        boneList.setToolTip("List of joints to remove from this LOD")
        boneList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        boneList.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)

        # signals/slots
        removeBtn.clicked.connect(partial(self.addBoneToList_UI, self, label, boneList))

    def addBoneToList_UI(self, parent, label, listWidget):
        """
        Creates an interface to remove bones from a LOD, transfer weighting of those removed bones to the next viable
        parent, and handle LOD posing.

        :param parent: The instance of the main UI created by ART_ExportMeshes.buildUI()
        :param label: The label for the window title to show what LOD this interface represents.
        :param listWidget: The list widget on the main LOD page that lists all bones being removed.

        .. seealso::  ART_ExportMeshes.addBoneToList_Accept(), ART_ExportMeshes.addWeightingTransferEntry()
        .. seealso::  ART_ExportMeshes.viewLodPose(), ART_ExportMeshes.resetLodPose(), ART_ExportMeshes.resetLodPose()

        """

        mainWin = QtWidgets.QMainWindow(parent)

        mainWidget.setObjectName("mid")
        mainWin.setMinimumSize(725, 525)
        mainWin.setMaximumSize(725, 525)

        mainWin.setObjectName("ART_AddBoneToLODlistWin")
        mainWin.setWindowTitle(label)

        # font

        layout = QtWidgets.QVBoxLayout(mainWidget)
        mainLayout = QtWidgets.QHBoxLayout()
        layout.addLayout(mainLayout)

        # treeWidget
        self.tree = QtWidgets.QTreeWidget()
        self.tree.setObjectName("light")
        mainLayout.addWidget(self.tree)
        self.tree.headerItem().setText(0, "")
        self.tree.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        self.tree.setMinimumSize(QtCore.QSize(260, 470))
        self.tree.setMaximumSize(QtCore.QSize(260, 470))

        self.tree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.tree.header().setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
        self.tree.headerItem().setText(0, "Skeleton Tree")
        self.tree.setColumnWidth(0, 260)
        self.tree.setIndentation(10)

        # right side

        # Weighting Transfer
        xferGroupBox = QtWidgets.QGroupBox("Weighting Transfer")
        rightLayout.addWidget(xferGroupBox)
        xferGroupBox.setMinimumSize(QtCore.QSize(425, 330))
        xferGroupBox.setMaximumSize(QtCore.QSize(425, 330))
        xferGroupBox.setObjectName("light")

        # vboxLayout--hbox button layout for spacer/button, vboxlayout/scroll area for entries
        weightingXferLayout = QtWidgets.QVBoxLayout(xferGroupBox)
        rightLayout.addLayout(weightingXferLayout)

        weightingXferLayout.addLayout(buttonLayout)
        buttonLayout.addSpacerItem(
            QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed))

        addEntryButton = QtWidgets.QPushButton("  Add Entry  ")
        addEntryButton.setObjectName("blueButton")
        buttonLayout.addWidget(addEntryButton)

        # ScrollArea
        weightingXferLayout.addLayout(scrollLayout)

        scrollLayout.addWidget(scrollArea)

        scrollContents.setObjectName("dark")

        entriesLayout = QtWidgets.QVBoxLayout(scrollContents)
        scrollArea.setWidgetResizable(True)

        entriesLayout.addSpacerItem(

        # LOD Pose
        lodPoseGroupBox = QtWidgets.QGroupBox("LOD Posing")
        rightLayout.addWidget(lodPoseGroupBox)
        lodPoseGroupBox.setMinimumSize(QtCore.QSize(425, 75))
        lodPoseGroupBox.setMaximumSize(QtCore.QSize(425, 75))
        lodPoseGroupBox.setObjectName("light")

        lodPoseLayout = QtWidgets.QHBoxLayout(lodPoseGroupBox)

        viewPoseBtn = QtWidgets.QPushButton("View LOD Pose")
        viewPoseBtn.setCheckable(True)
        lodPoseLayout.addWidget(viewPoseBtn)
        viewPoseBtn.clicked.connect(partial(self.viewLodPose, label, viewPoseBtn))

        resetPoseBtn = QtWidgets.QPushButton("Reset LOD Pose")
        lodPoseLayout.addWidget(resetPoseBtn)
        resetPoseBtn.setObjectName("blueButton")
        resetPoseBtn.clicked.connect(partial(self.resetLodPose, label))

        savePoseBtn = QtWidgets.QPushButton("Save LOD Pose")
        lodPoseLayout.addWidget(savePoseBtn)
        savePoseBtn.setObjectName("blueButton")
        savePoseBtn.clicked.connect(partial(self.saveLodPose, label))

        # button
        addButton = QtWidgets.QPushButton("Save and Close")
        addButton.setMinimumHeight(40)
        addButton.setMaximumHeight(40)
        rightLayout.addWidget(addButton)
        addButton.clicked.connect(
            partial(self.addBoneToList_Accept, self.tree, listWidget, label, entriesLayout, viewPoseBtn))
        addButton.setObjectName("blueButton")

        addEntryButton.clicked.connect(partial(self.addWeightingTransferEntry, entriesLayout))

        # joints
        cmds.select("root", hi=True)
        joints = cmds.ls(sl=True, type="joint")

            parent = cmds.listRelatives(joint, parent=True)

            if parent is None:
                item = QtWidgets.QTreeWidgetItem(self.tree)
                item.setText(0, joint)
                item.setBackground(0, QtCore.Qt.NoBrush)

                children = cmds.listRelatives(joint, children=True, type="joint")
                for child in children:
                    self.addBoneToListUI_addChildren(child, item)

        # show
        self.tree.expandAll()

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        # populate ui automatically if the settings exist
        attrs = cmds.listAttr(characterNode, ud=True, string="LOD_*_Bones")
        lodAttrs = []

        if attrs is not None:
                lodAttrs.append(attr.partition("_Bones")[0])

        if lodAttrs is not None:

            # get json data from attribute
            lodData = ["_Bones"]
            boneValues = []

            for attr in lodAttrs:
                if attr == label:
                    for entry in lodData:
                        if cmds.objExists(characterNode + "." + attr + entry):
                            if entry == "_Bones":
                                boneValues = json.loads(cmds.getAttr(characterNode + "." + attr + entry))

            for item in boneValues:
                bones = item[1]
                xferBone = item[0]

                # create an entry (need to return back the layouts needed for the next part
                entryData = self.addWeightingTransferEntry(entriesLayout)
                self.tree.clearSelection()
                for each in bones:
                    item = self.tree.findItems(each, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
                    if item is not None:
                        item = item[0]
                        item.setSelected(True)
                self.addItemsToWeightXferList(entryData[0], entryData[1])

                item = self.tree.findItems(xferBone, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
                if item is not None:
                    item = item[0]
                    item.setSelected(True)
                    self.addXferBoneToList(entryData[1], False)

    def addWeightingTransferEntry(self, layout):
        """
        Adds a Groupbox with two QListWidgets, where the left shows bones to remove, and the right shows the next
        viable parent bone to transfer the weighting to. Both lists can then be edited as well.

        This is what that would like like:

            .. image:: /images/xferEntry.png

        :param layout: The QVboxLayout to add the QGroupbox to.

        :return: returns both QListWidgets (in memory)

        .. seealso:: ART_ExportMeshes.addItemsToWeightXferList(), ART_ExportMeshes.removeBonesFromList()

        """

        groupbox = QtWidgets.QGroupBox()
        layout.insertWidget(0, groupbox)
        groupbox.setMaximumSize(QtCore.QSize(370, 140))
        groupbox.setCheckable(True)
        groupbox.setFlat(True)

        f.close()
        groupbox.setStyleSheet(style)

        mainLayout = QtWidgets.QHBoxLayout(groupbox)

        groupbox.toggled.connect(partial(self.collapseBox, groupbox))

        removeBonesList = QtWidgets.QListWidget()
        mainLayout.addWidget(removeBonesList)
        removeBonesList.setMinimumSize(QtCore.QSize(150, 122))
        removeBonesList.setMaximumSize(QtCore.QSize(150, 122))
        removeBonesList.setProperty("list", "remove")
        removeBonesList.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)

        removeBonesList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        removeBonesList.customContextMenuRequested.connect(partial(self.lodContextMenu, removeBonesList, groupbox))

        buttonLayout = QtWidgets.QVBoxLayout()
        mainLayout.addLayout(buttonLayout)

        addButton = QtWidgets.QPushButton(" + ")
        buttonLayout.addWidget(addButton)

        remButton = QtWidgets.QPushButton(" - ")
        buttonLayout.addWidget(remButton)
        remButton.setObjectName("blueButton")

        xferBoneList = QtWidgets.QListWidget()
        mainLayout.addWidget(xferBoneList)
        xferBoneList.setMinimumSize(QtCore.QSize(120, 122))
        xferBoneList.setMaximumSize(QtCore.QSize(120, 122))
        xferBoneList.setProperty("list", "xfer")

        setButton = QtWidgets.QPushButton("<-")
        mainLayout.addWidget(setButton)
        setButton.setObjectName("blueButton")
        setButton.clicked.connect(partial(self.addXferBoneToList, xferBoneList, False))

        addButton.clicked.connect(partial(self.addItemsToWeightXferList, removeBonesList, xferBoneList))
        remButton.clicked.connect(partial(self.removeBonesFromList, removeBonesList))

        return [removeBonesList, xferBoneList]

    def collapseBox(self, groupBox, *args):
        """
        Collapses the given groupBox down to 16 pixel high, or restores its original height, given the state.

        :param groupBox: Which groupBox to operate on and manipulate the height.
        :param args: What the state is of the groupBox checkBox.

        """

        if args[0]:
            groupBox.setMaximumSize(QtCore.QSize(370, 140))
            groupBox.setMinimumSize(QtCore.QSize(370, 140))
        else:
            groupBox.setMaximumSize(QtCore.QSize(370, 16))
            groupBox.setMinimumSize(QtCore.QSize(370, 16))

    def addMeshLOD(self):
        """
        Finds the current number of LOD tabs, constructs a label for the new tab, iterating the count by 1,
        and calls on createLODpage, passing in that label.

        """

        # get current count of tabs
        numTabs = self.emPageTabs.count()
        label = "LOD_" + str(numTabs)

        self.createLODpage(label, True)

    def addMeshToList_UI(self, parent, label, listWidget):
        """
        Creates a UI that lists all meshes for selection to assign meshes to a given LOD.

            .. image:: /images/addMeshesUI.png

        :param parent: The UI instance to parent this interface to
        :param label: The LOD text label for this interface's window title.
        :param listWidget: The listWidget on the main LOD page that will list the selected meshes

            .. image:: /images/meshList.png

        .. seealso:: ART_ExportMeshes.populateRenderMeshes(), ART_ExportMeshes.addMeshesToLodList()

        """

        mainWin.setMinimumSize(300, 525)
        mainWin.setMaximumSize(300, 525)

        mainWin.setObjectName("ART_AddMeshesToLODlistWin")

        # font

        meshTree = QtWidgets.QTreeWidget()
        meshTree.setObjectName("light")
        mainLayout.addWidget(meshTree)
        meshTree.headerItem().setText(0, "")
        meshTree.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        meshTree.setMinimumSize(QtCore.QSize(260, 470))
        meshTree.setMaximumSize(QtCore.QSize(260, 470))

        meshTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        meshTree.header().setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
        meshTree.headerItem().setText(0, "Geometry")
        meshTree.setColumnWidth(0, 260)
        meshTree.setIndentation(10)

        chooseBtn = QtWidgets.QPushButton("Save and Close")
        layout.addWidget(chooseBtn)
        chooseBtn.clicked.connect(partial(self.addMeshesToLodList, listWidget, meshTree, mainWin, label))

        # populate tree
        self.populateRenderMeshes(meshTree)

        # if items in listWidget, select those items in the tree
        for i in range(listWidget.count()):
            item = listWidget.item(i).text()
            matches = meshTree.findItems(item, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
            for match in matches:
                match.setSelected(True)

    def addMeshesToLodList(self, listWidget, meshTree, mainWin, label):
        """
        Finds the selected items in addMeshToList_UI's treeWidget, and adds them back to the main LOD page's
        listWidget for meshes associated with that LOD.

        :param listWidget: The list widget to add selected items in the treeWidget to.
        :param meshTree: The treeWidget from addMeshToList_UI(), whose selection will be queried.
        :param mainWin: The window instance from addMeshToList_UI()
        :param label: The name of the LOD tab

        .. seealso:: ART_ExportMeshes.saveMeshList()

        """

        # add to list widget
        selected = meshTree.selectedItems()
        listWidget.clear()
            icon = QtGui.QIcon(utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/mesh.png")))
            listWidgetItem = QtWidgets.QListWidgetItem(icon, each.text(0))
            listWidget.addItem(listWidgetItem)

        # hook up connections
        self.saveMeshList(listWidget, label)

        # close window
        mainWin.close()
        mainWin.deleteLater()

    def saveLodPose(self, lod):
        """
        Queries the joints in the skeleton and get the attribute values to store for the given lod.

        :param lod: the text label of the LOD to operate on.

        .. todo:: Suggested feature request was to have this operate on the rig controls and not just joints. That
                  would mean storing both controls and joints. The desire behind this was to do all LOD posing in the
                  rig file, rather than the temp file that gets created for exporting.

        """

        # get all joints

        # go through each one, and find the created bones for that modules
            if module != "ART_Root_Module":

                        createdJoints.append(bone)

        dataDict = {}
        for each in createdJoints:
            translateData = cmds.getAttr(each + ".translate")[0]
            translate = []
            for d in translateData:
                translate.append(float('{:.3f}'.format(d)))

            rotateData = cmds.getAttr(each + ".rotate")[0]
            rotate = []
            for r in rotateData:
                rotate.append(float('{:.3f}'.format(r)))

            scaleData = cmds.getAttr(each + ".scale")[0]
            scale = []
            for s in scaleData:
                scale.append(float('{:.3f}'.format(s)))

            dataDict[str(each)] = [translate, rotate, scale]

        self.lodPoseDict[lod] = dataDict

            try:
                # set the model pose again (un-altered)
                inst.setReferencePose("modelPose")
                print e

    def viewLodPose(self, lod, button):
        """
        Gather LOD pose attribute data for the given LOD and set those values on the joints.

        :param lod: The text label for the LOD to operate on.
        :param button: the "View LOD Pose" button instance

                  would mean getting data for both controls and joints. The desire behind this was to do all LOD posing
                  in the rig file, rather than the temp file that gets created for exporting.

        """

        if button.isChecked():
            try:
                for key in self.lodPoseDict[lod]:
                    data = self.lodPoseDict[lod].get(key)
                    if cmds.objExists(key):
                        for each in data:
                            # translate, rotate, scale
                            if each == data[0]:
                                cmds.setAttr(key + ".translateX", each[0])
                                cmds.setAttr(key + ".translateY", each[1])
                                cmds.setAttr(key + ".translateZ", each[2])

                            if each == data[1]:
                                cmds.setAttr(key + ".rotateX", each[0])
                                cmds.setAttr(key + ".rotateY", each[1])
                                cmds.setAttr(key + ".rotateZ", each[2])

                            if each == data[2]:
                                cmds.setAttr(key + ".scaleX", each[0])
                                cmds.setAttr(key + ".scaleY", each[1])
                                cmds.setAttr(key + ".scaleZ", each[2])

            except:
                try:
                    characterNode = utils.returnCharacterModule()
                    if cmds.objExists(characterNode + "." + lod + "_Pose"):
                        dict = json.loads(cmds.getAttr(characterNode + "." + lod + "_Pose"))
                        for key in dict:
                            data = dict.get(key)
                            for each in data:
                                # translate, rotate, scale
                                if each == data[0]:
                                    cmds.setAttr(key + ".translateX", each[0])
                                    cmds.setAttr(key + ".translateY", each[1])
                                    cmds.setAttr(key + ".translateZ", each[2])

                                if each == data[1]:
                                    cmds.setAttr(key + ".rotateX", each[0])
                                    cmds.setAttr(key + ".rotateY", each[1])
                                    cmds.setAttr(key + ".rotateZ", each[2])

                                if each == data[2]:
                                    cmds.setAttr(key + ".scaleX", each[0])
                                    cmds.setAttr(key + ".scaleY", each[1])
                                    cmds.setAttr(key + ".scaleZ", each[2])
                    print e

        else:

                try:
                    print e

    def resetLodPose(self, lod):
        """
        Resets the LOD pose for the given LOD to the model pose.

        :param lod: the LOD text label to operate on.

        """

            try:
                # call on the module's bakeOffsets method
                print e

        self.saveLodPose(lod)

    def lodContextMenu(self, widget, groupBox, point):
        """
        Creates a right-click context menu for the weighting transfer entry widget:

        :param widget: The parent widget the context menu will spawn from.
        :param groupBox: The parent groupBox for the weighting transfer entry.
        :param point: Where on the parent widget to spawn the context menu.

        """

        style = """
        QMenu {
                background-color: rgb(60,60,60);
                border: 1px solid black;
            }

        QMenu::item {
            background-color: transparent;
            }

        QMenu::item:selected {
                background-color: rgb(255,174,0);
                color: black;
            }
            """

        menu = QtWidgets.QMenu(widget)
        menu.setStyleSheet(style)
        menu.addAction("Clear Selection", widget.clearSelection)
        menu.addAction("Select All", widget.selectAll)
        menu.addAction("Remove This Entry", partial(self.removeTransferEntry, groupBox))

        menu.exec_(widget.mapToGlobal(point))

    def removeTransferEntry(self, groupBox):
        """
        Removes the given groupBox, deleting a weighting transfer entry.

        :param groupBox: Which groupBox to remove.

        """

        groupBox.close()
        groupBox.deleteLater()

    def removeLodTab(self):
        """
        Removes the current tab index from the tabWidget. Also removes and LOD attributes associated with this LOD
        from the character node.

        """

        currentTab = self.emPageTabs.currentIndex()
        tabText = self.emPageTabs.tabText(currentTab)

        if currentTab != 0:
            self.emPageTabs.removeTab(currentTab)

            # remove LOD attrs
            characterNode = utils.returnCharacterModule()
            attrs = cmds.listAttr(characterNode, ud=True, string=tabText + "*")
            if attrs is not None:
                    cmds.deleteAttr(characterNode, at=attr)

        else:
            msgBox.setText("Cannot remove LOD 0")
            msgBox.setIcon(QtWidgets.QMessageBox.Critical)
            msgBox.exec_()

    def browseToFBX(self, lineEdit):
        """
        Calls on a fileDialog for the user to browse to an FBX file for saving. Either one that exists, or creating
        a new one.

        :param lineEdit: The QLineEdit whose text to set with the path to the FBX file.

        """

        fileName = cmds.fileDialog2(fm=0, dir=self.projectPath, ff="*.fbx")
        fileName = utils.returnFriendlyPath(fileName[0])
        lineEdit.setText(fileName)

    def addItemsToWeightXferList(self, widget, boneWidget):
        """
        Takes the items selected in the skeleton tree (pictured below) and auto-selects and children under the
        selected items, as removing the parent will also remove the children of those parents, and then adds all of
        those items to the given widget.

            .. image:: /images/boneTree.png

        :param widget: The QListWidget in the weighting transfer entry widget showing bones to remove.
        :param boneWidget: The QListWidget in the weighting transfer entry widget showing bone who will receive
                           weights from removed bones.

                            .. image:: /images/xferEntry.png

        .. seealso:: ART_ExportMeshes.addXferBoneToList()

        """

        # get selected items in self.tree
        selected = self.tree.selectedItems()

        fullList = []
        itemList = []
            self.findTreeChildren(fullList, itemList, each)

        for item in itemList:
            item.setSelected(True)

        for each in fullList:
            widget.addItem(each)

        self.addXferBoneToList(boneWidget)

    def findTreeChildren(self, fullList, itemList, item):
        """
        Finds any child items from selected items in the Skeleton Tree and appends them to the input lists.

        :param fullList: The full list of all items, including the original selected parent items and any child items
        :param itemList: The instances in memory of the selected items in the Skeleton Tree.
        :param item: The parent item in the Skeleton Tree to check for children

        .. seealso:: ART_ExportMeshes.addItemsToWeightXferList()

        """

        fullList.append(item.text(0))
        itemList.append(item)
        for i in range(item.childCount()):
            self.findTreeChildren(fullList, itemList, item.child(i))

    def addXferBoneToList(self, widget, autoFind=True):
        """
        Finds the selected items in the Skeleton Tree (for removal) and locates the next viable parent to transfer
        weighting to, then adds that bone to the passed in QListWidget.

        :param widget: The QListWidget to add the bone that will receive weights from removed bones.
        :param autoFind: Whether or not to auto-locate the next viable parent or use the currently selected item.

        """

        if autoFind:
            # find selected items, find the first item parent that is not selected
            selected = self.tree.selectedItems()

            viableParent = None
            for each in selected:
                if each.parent().isSelected() is False:
                    viableParent = each.parent().text(0)
            widget.clear()
            widget.addItem(viableParent)

        else:

            if len(selected) > 1:

                msgBox = QtWidgets.QMessageBox()
                msgBox.setText("Only 1 bone can be selected to transfer weighting to.")
                msgBox.setIcon(QtWidgets.QMessageBox.Critical)
                msgBox.exec_()

            else:
                widget.clear()
                widget.addItem(selected[0].text(0))

    def removeBonesFromList(self, widget):
        """
        Takes the selected items from the left QListWidget in a weighting transfer widget and attempts to remove
        them from the list.

        :param widget: The QListWidget to check for selected items in.

        """

        selected = widget.selectedItems()

        # find the text of each selected item and list items in the main skeleton tree that match that text
            text = each.text()
            match = self.tree.findItems(text, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)

            # check to make sure (if there was a match) that the parent of that item is not also being removed.
            if len(match) > 0:
                parent = match[0].parent()

                parentMatch = widget.findItems(parent.text(0), QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive)
                if len(parentMatch) == 0:

                    row = widget.row(each)
                    widget.takeItem(row)

                else:
                        "Parent of the joint is also being removed. Cannot complete request unless parent joint "
                        "is not removed.")
                    msgBox.exec_()

            else:
                msgBox.setText("Was not able to find a matching entry in the skeleton tree.")

                return

    def addBoneToListUI_addChildren(self, name, parent):
        """
        Populates the Skeleton Tree (self.tree) by recursively looking for relatives of the given name and making more
        QTreeWidgetItems using the passed in name, and parenting under the passed in parent.

        :param name: The bone name, which will be used to look for children, and also as the text for the treeWidgetItem
        :param parent: The parent treeWidgetItem that the created item will be a child of.

        """

        item = QtWidgets.QTreeWidgetItem(parent)
        item.setText(0, name)
        item.setBackground(0, QtCore.Qt.NoBrush)

        children = cmds.listRelatives(name, children=True, type="joint")
        if children:
            for child in children:
                self.addBoneToListUI_addChildren(child, item)

    def addBoneToList_Accept(self, tree, listWidget, lod, layout, viewBtn):
        """
        Called from the "Save and Close" button of the addBoneToList_UI, clears all items in the main listWidget,
        then populates that listWidget with the new bones to remove. Adds all LOD pose info to the character node as
        well as weighting transfer info for that lod.

        :param listWidget: The listWidget on the main LOD page on the bottom right that lists bones to remove.
        :param lod: The LOD text (LOD_#) to operate on.
        :param layout: The QVboxLayout to query for weighting transfer entries.
        :param viewBtn: The "View LOD Pose" button instance

        """

        # find items already in listWidget

        # get the character node

        # add info to characterNode
        if not cmds.objExists(characterNode + "." + lod + "_Pose"):
            cmds.addAttr(characterNode, ln=lod + "_Pose", dt="string")

        if not cmds.objExists(characterNode + "." + lod + "_Bones"):
            cmds.addAttr(characterNode, ln=lod + "_Bones", dt="string")

        # gather weight transfer info and pose info
        lodXferList = []

        for i in range(layout.count()):
            if type(layout.itemAt(i).widget()) == QtWidgets.QGroupBox:
                children = layout.itemAt(i).widget().children()
                data = []
                removeBones = []
                for each in children:
                    if type(each) == QtWidgets.QListWidget:

                        # [[list of bones], xferbone]
                        if each.property("list") == "remove":

                            for x in range(each.count()):
                                item = each.item(x)
                                if item.text() not in removeBones:
                                    removeBones.append(item.text())

                        elif each.property("list") == "xfer":
                            for y in range(each.count()):
                                item = each.item(y)
                                data.append(item.text())
                                data.append(removeBones)
                lodXferList.append(data)

        for item in lodXferList:
            bones = item[1]

            for bone in bones:
                icon = QtGui.QIcon(utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/boneDisplay.png")))
                listWidgetItem = QtWidgets.QListWidgetItem(icon, str(bone))
                listWidget.addItem(listWidgetItem)

        string = json.dumps(lodXferList)
        cmds.setAttr(characterNode + "." + lod + "_Bones", string, type="string")

        # Pose Info
        try:
            poseString = json.dumps(self.lodPoseDict[lod])
            cmds.setAttr(characterNode + "." + lod + "_Pose", poseString, type="string")
        except:
            print "no LOD pose given. Using model pose."

        # set viewPoseBtn to unchecked
        viewBtn.setChecked(False)
        self.viewLodPose(lod, viewBtn)

        # remove UI
        if cmds.window("ART_AddBoneToLODlistWin", exists=True):
            cmds.deleteUI("ART_AddBoneToLODlistWin", wnd=True)

    def populateRenderMeshes(self, tree):
        """
        Finds all skinned meshes in the scene and adds them as items to the given TreeWidget.

        .. image:: /images/addMeshesUI.png

        :param tree: The QTreeWidget to add found meshes to

        """

        # find all skinned meshes in scene
        renderMeshes = []

            renderMeshes.append(geoTransform)

        geoParents = []
        worldGeo = []
        meshDict = {}

        for geo in renderMeshes:
            parent = cmds.listRelatives(geo, parent=True)
            if parent is not None:
                parent = parent[0]

                if parent not in geoParents:
                    geoParents.append(parent)

            else:
                worldGeo.append(geo)

        for each in geoParents:
            children = cmds.listRelatives(each)
            meshDict[each] = children

        for key in meshDict.keys():
            item = QtWidgets.QTreeWidgetItem(tree)
            item.setText(0, key)
            item.setBackground(0, QtCore.Qt.NoBrush)

            children = meshDict.get(key)
                icon = QtGui.QIcon(utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/mesh.png")))
                childItem = QtWidgets.QTreeWidgetItem(item)
                childItem.setText(0, child)
                childItem.setBackground(0, QtCore.Qt.NoBrush)
                childItem.setIcon(0, icon)

        for geo in worldGeo:
            geoItem = QtWidgets.QTreeWidgetItem(tree)
            geoItem.setText(0, geo)
            geoItem.setBackground(0, QtCore.Qt.NoBrush)
            geoItem.setIcon(0, icon)

        tree.expandAll()

    def populateUI(self):
        """
        Checks to see if LOD attributes exist on the character node, and if so, builds and populates the UI based on
        those settings.

        """

        attrs = cmds.listAttr(characterNode, ud=True, string="LOD_*_FilePath")
        createdTabs = []
                lodAttrs.append(attr.partition("_FilePath")[0])

            lodData = ["_Pose", "_Bones", "_Meshes", "_FilePath"]
            meshValue = None
            boneValue = None
            pathValue = None

                for entry in lodData:
                    if cmds.objExists(characterNode + "." + attr + entry):
                        if entry == "_Bones":
                            boneValue = json.loads(cmds.getAttr(characterNode + "." + attr + entry))
                        if entry == "_Meshes":
                            meshValue = cmds.listConnections(characterNode + "." + attr + entry)
                        if entry == "_FilePath":
                            pathValue = json.loads(cmds.getAttr(characterNode + "." + attr + entry))

                # compare number of LOD attrs to number of current tabs. If needed, add more tabs for the lods
                numTabs = self.emPageTabs.count()

                if numTabs < len(lodAttrs):
                    for i in range(numTabs):
                        tab = self.emPageTabs.widget(i)
                        tabText = self.emPageTabs.tabText(i)

                        if attr != tabText:
                            if attr not in createdTabs:
                                self.createLODpage(attr, True)
                                createdTabs.append(attr)

                # find the associated tab and set the data
                for i in range(self.emPageTabs.count()):
                    tab = self.emPageTabs.widget(i)
                    tabText = self.emPageTabs.tabText(i)

                    if tabText == attr:
                        # tab children
                        lists = lineEdits = tab.findChildren(QtWidgets.QListWidget)
                        lineEdits = tab.findChildren(QtWidgets.QLineEdit)

                        # set meshes and bones for this tab
                        for list in lists:
                            property = list.property("listType")

                            if property == "mesh":
                                if meshValue is not None:
                                    for mesh in meshValue:
                                        icon = QtGui.QIcon(
                                            utils.returnFriendlyPath(os.path.join(self.iconsPath, "System/mesh.png")))
                                        listWidgetItem = QtWidgets.QListWidgetItem(icon, mesh)
                                        list.addItem(listWidgetItem)

                            if property == "bone":
                                if boneValue is not None:
                                    for item in boneValue:
                                        bones = item[1]
                                        for bone in bones:
                                            icon = QtGui.QIcon(utils.returnFriendlyPath(
                                                os.path.join(self.iconsPath, "System/boneDisplay.png")))
                                            listWidgetItem = QtWidgets.QListWidgetItem(icon, bone)
                                            list.addItem(listWidgetItem)

                        # set file path
                        if os.path.exists(os.path.dirname(pathValue)):
                            lineEdits[0].setText(pathValue)

    def saveFilePath(self, lineEdit, tabText, *args):
        """
        Gathers the FBX output path for the LOD and stores that information to the character node.

        :param lineEdit: The QLineEdit which stores the output path text.
        :param tabText: The LOD text to operate on (LOD_#)

        """

        # get file path
        path = lineEdit.text()
        dataString = json.dumps(path)

        if not cmds.objExists(characterNode + "." + tabText + "_FilePath"):
            cmds.addAttr(characterNode, ln=tabText + "_FilePath", dt="string")

        cmds.setAttr(characterNode + "." + tabText + "_FilePath", dataString, type="string")

    def saveMeshList(self, listWidget, tabText):
        """
        Gathers the associated meshes for an LOD and stores that information to the character node.

        :param listWidget: The QListWidget of the LOD tab to search for associated meshes.

        """

        if not cmds.objExists(characterNode + "." + tabText + "_Meshes"):
            cmds.addAttr(characterNode, ln=tabText + "_Meshes", at="message")

        i = tabText.partition("_")[2]

        # first remove all connections
        connections = cmds.listConnections(characterNode + "." + tabText + "_Meshes")
        if connections is not None:
            for conn in connections:
                cmds.disconnectAttr(characterNode + "." + tabText + "_Meshes", conn + ".lodGroup" + str(i))

        # Add attrs to meshes to connect up to the characterNode LOD meshes attr
        meshList = []
        for x in range(listWidget.count()):
            meshList.append(listWidget.item(x).text())

        for mesh in meshList:
            if not cmds.objExists(mesh + ".lodGroup" + str(i)):
                cmds.addAttr(mesh, sn="lodGroup" + str(i), at="message")

            cmds.connectAttr(characterNode + "." + tabText + "_Meshes", mesh + ".lodGroup" + str(i))

    def export(self):
        """
        Gathers all export data by calling on utils.findExportMeshData, parses the information, and for each LOD in
        the list, calls on utils.ExportMesh(), passing in the appropriate data.

        .. seealso:: utils.ExportMesh(), utils.findExportMeshData()

        """

            if not os.path.exists(os.path.dirname(each[0])):
                msgBox.setText("There are LODs with no valid file path to export to. Aborting.")
                return
            if each[1] is None:
                msgBox.setText("There are LODs that have no meshes associated with them. Aborting.")
                return

        # save the file
        saveFile = cmds.file(q=True, sceneName=True)
        try:
            cmds.error("Could not save file. Error: " + str(e))
            return

        # export
        fileData = []
            meshValue = each[1]
            pathValue = each[0]
            boneValue = each[2]
            poseData = each[3]

            utils.exportMesh(self.mainUI, meshValue, pathValue, boneValue, poseData)
            fileData.append(pathValue)

            # open the file
            cmds.file(saveFile, open=True, force=True)

        # report and close
        self.close()

        msgBox.setText("Export Complete!")

        string = ""
        for each in fileData:
            string += each + "\n"
        msgBox.setDetailedText(string)

"""
"""

import os
import subprocess
import tempfile

class ART_ExportMotion(object):
    """
    This class is used to export FBX animation from the rig to Unreal Engine. It supports morph targets,
    custom attribute curves, and pre/post scripts.

    It can be found on the animation sidebar with this icon:
        .. image:: /images/exportMotionButton.png

    .. todo:: Add the ability to export alembic and animation curve data.

    """

    def __init__(self, animPickerUI, parent=None):

        super(ART_ExportMotion, self).__init__()

        if cmds.window("pyART_ExportMotionWIN", exists=True):
            cmds.deleteUI("pyART_ExportMotionWIN", wnd=True)

        self.mainWin.resizeEvent = self.windowResized

        f.close()

        self.mainWin.setMinimumSize(QtCore.QSize(470, 500))
        self.mainWin.setMaximumSize(QtCore.QSize(470, 900))
        self.mainWin.resize(470, 500)

        self.mainWin.setObjectName("pyART_ExportMotionWIN")
        self.mainWin.setWindowTitle("Export Motion")

        # tabs
        self.exportTabs = QtWidgets.QTabWidget()
        self.layout.addWidget(self.exportTabs)

        {
        }
        {
        }
        {
        }
        {
        }
        {
            border-left: 2px solid rgb(19,132,183);
            border-right: 2px solid rgb(19,132,183);
            border-bottom: 2px solid rgb(19,132,183);
        }
        """

        stylesheet2 = """
        {
            width: 140px;
        }
        {
        }
        {
        }
        {
        }
        {
            border-top: 2px solid rgb(255,175,25);
            border-left: 0px solid rgb(19,132,183);
            border-right: 0px solid rgb(19,132,183);
            border-bottom: 2px solid rgb(255,175,25);
        }
        """

        self.exportTabs.setStyleSheet(stylesheet)

        # FBX Tab
        self.fbxExportTab = QtWidgets.QWidget()
        self.exportTabs.addTab(self.fbxExportTab, "FBX")

        # ABC Tab
        self.abcExportTab = QtWidgets.QWidget()
        self.exportTabs.addTab(self.abcExportTab, "ABC")

        # Anim Curve Tab
        self.animExportTab = QtWidgets.QWidget()
        self.exportTabs.addTab(self.animExportTab, "Animation")

        # #FBX TAB
        self.fbxTabLayoutFrame = QtWidgets.QFrame(self.fbxExportTab)
        self.fbxTabLayoutFrame.setObjectName("dark")
        self.fbxTabLayoutFrame.setMinimumSize(450, 410)
        self.fbxTabLayoutFrame.setMaximumSize(450, 900)
        self.fbxTabLayoutFrame.setStyleSheet(self.style)

        self.fbxTabLayout = QtWidgets.QVBoxLayout(self.fbxTabLayoutFrame)

        # FBX Export Tabs
        self.fbxTabs = QtWidgets.QTabWidget()
        self.fbxTabLayout.addWidget(self.fbxTabs)

        self.fbxTabs.setStyleSheet(stylesheet2)

        # Settings Tab
        self.exportSettings = QtWidgets.QWidget()
        self.fbxTabs.addTab(self.exportSettings, "Export Settings")
        self.settingsTabLayout = QtWidgets.QVBoxLayout(self.exportSettings)

        self.sequencesTab = QtWidgets.QWidget()
        self.fbxTabs.addTab(self.sequencesTab, "Sequences")
        self.sequenceTabLayout = QtWidgets.QVBoxLayout(self.sequencesTab)

        # # Export Settings
        self.exportSettings = QtWidgets.QFrame()
        self.exportSettings.setObjectName("dark")
        self.exportSettings.setMinimumSize(QtCore.QSize(415, 330))
        self.exportSettings.setMaximumSize(QtCore.QSize(415, 900))
        self.settingsTabLayout.addWidget(self.exportSettings)

        self.settingsLayout = QtWidgets.QVBoxLayout(self.exportSettings)

        # export meshes checkbox
        self.exportMeshCB = QtWidgets.QCheckBox("Export Meshes")
        self.settingsLayout.addWidget(self.exportMeshCB)
        self.exportMeshCB.setChecked(True)

        # horizontal layout for morphs and custom attr curves
        self.settings_cb_layout = QtWidgets.QHBoxLayout()
        self.settingsLayout.addLayout(self.settings_cb_layout)

        # export morphs and custom attr curves checkboxes
        self.exportMorphsCB = QtWidgets.QCheckBox("Export Morph Targets")
        self.settings_cb_layout.addWidget(self.exportMorphsCB)
        self.exportMorphsCB.setChecked(True)

        self.exportCustomAttrsCB = QtWidgets.QCheckBox("Export Custom Attribute Curves")
        self.settings_cb_layout.addWidget(self.exportCustomAttrsCB)
        self.exportCustomAttrsCB.setChecked(True)

        # horizontal layout for list widgets (morphs and custom attrs)
        self.settings_list_layout = QtWidgets.QHBoxLayout()
        self.settingsLayout.addLayout(self.settings_list_layout)

        # list widgets for listing morphs and custom attr curves
        self.exportMorphList = QtWidgets.QListWidget()
        self.exportMorphList.setMinimumSize(QtCore.QSize(185, 150))
        self.exportMorphList.setMaximumSize(QtCore.QSize(185, 150))
        self.settings_list_layout.addWidget(self.exportMorphList)
        self.exportMorphList.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)

        self.exportCurveList = QtWidgets.QListWidget()
        self.exportCurveList.setMinimumSize(QtCore.QSize(185, 150))
        self.exportCurveList.setMaximumSize(QtCore.QSize(185, 150))
        self.settings_list_layout.addWidget(self.exportCurveList)
        self.exportCurveList.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)

        # signal slots for checkboxes and lists
        self.exportMorphsCB.stateChanged.connect(partial(self.disableWidget, self.exportMorphList, self.exportMorphsCB))
        self.exportCustomAttrsCB.stateChanged.connect(
            partial(self.disableWidget, self.exportCurveList, self.exportCustomAttrsCB))
        self.exportMorphsCB.stateChanged.connect(self.exportMeshCB.setChecked)
        self.exportMeshCB.stateChanged.connect(self.exportMorphsCB.setChecked)

        # horizontal layout for pre-script
        self.preScript_layout = QtWidgets.QHBoxLayout()
        self.settingsLayout.addLayout(self.preScript_layout)

        # pre-script checkbox, lineEdit, and button
        self.preScriptCB = QtWidgets.QCheckBox("Pre-Script: ")
        self.preScript_layout.addWidget(self.preScriptCB)

        self.preScript_path = QtWidgets.QLineEdit()
        self.preScript_layout.addWidget(self.preScript_path)

        ps_browseBtn = QtWidgets.QPushButton()
        ps_browseBtn.setMinimumSize(25, 25)
        ps_browseBtn.setMaximumSize(25, 25)
        icon = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/fileBrowse.png"))
        ps_browseBtn.setIconSize(QtCore.QSize(25, 25))
        ps_browseBtn.setIcon(icon)
        ps_browseBtn.clicked.connect(partial(self.fileBrowse_script, self.preScript_path, self.preScriptCB))
        self.preScript_layout.addWidget(ps_browseBtn)

        # horizontal layout for post-script
        self.postScript_layout = QtWidgets.QHBoxLayout()
        self.settingsLayout.addLayout(self.postScript_layout)

        self.postScriptCB = QtWidgets.QCheckBox("Post-Script: ")
        self.postScript_layout.addWidget(self.postScriptCB)

        self.postScript_path = QtWidgets.QLineEdit()
        self.postScript_layout.addWidget(self.postScript_path)

        pps_browseBtn = QtWidgets.QPushButton()
        pps_browseBtn.setMinimumSize(25, 25)
        pps_browseBtn.setMaximumSize(25, 25)
        pps_browseBtn.setIconSize(QtCore.QSize(25, 25))
        pps_browseBtn.setIcon(icon)
        pps_browseBtn.clicked.connect(partial(self.fileBrowse_script, self.postScript_path, self.postScriptCB))
        self.postScript_layout.addWidget(pps_browseBtn)

        # save settings button
        button = QtWidgets.QPushButton("Save Export Settings")
        self.settingsLayout.addWidget(button)
        button.clicked.connect(self.fbx_saveExportData)

        # spacer
        self.settingsLayout.addSpacerItem(

        # # FBX Sequences

        # # Add Sequence
        self.addFbxAnimSequence = QtWidgets.QPushButton("Add Sequence")
        self.addFbxAnimSequence.setMinimumSize(QtCore.QSize(415, 50))
        self.addFbxAnimSequence.setMaximumSize(QtCore.QSize(415, 50))
        self.addFbxAnimSequence.setObjectName("blueButton")
        self.addFbxAnimSequence.clicked.connect(partial(self.fbx_addSequence))
        self.sequenceTabLayout.addWidget(self.addFbxAnimSequence)

        # #Main Export Section
        self.fbxAnimSequenceFrame = QtWidgets.QFrame()
        self.fbxAnimSequenceFrame.setMinimumWidth(415)
        self.fbxAnimSequenceFrame.setMaximumWidth(415)
        scrollSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
        self.fbxAnimSequenceFrame.setSizePolicy(scrollSizePolicy)
        self.fbxAnimSequenceFrame.setObjectName("dark")

        self.fbxMainScroll = QtWidgets.QScrollArea()
        self.sequenceTabLayout.addWidget(self.fbxMainScroll)
        self.fbxMainScroll.setMinimumSize(QtCore.QSize(415, 280))
        self.fbxMainScroll.setMaximumSize(QtCore.QSize(415, 900))
        self.fbxMainScroll.setWidgetResizable(True)
        self.fbxMainScroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.fbxMainScroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.fbxMainScroll.setWidget(self.fbxAnimSequenceFrame)

        self.fbxSequenceLayout = QtWidgets.QVBoxLayout(self.fbxAnimSequenceFrame)
        self.fbxSequenceLayout.addSpacerItem(

        # spacer
        self.sequenceTabLayout.addSpacerItem(

        # #export button
        self.doFbxExportBtn = QtWidgets.QPushButton("Export")
        self.fbxTabLayout.addWidget(self.doFbxExportBtn)
        self.doFbxExportBtn.setObjectName("blueButton")
        self.doFbxExportBtn.setMinimumSize(QtCore.QSize(430, 40))
        self.doFbxExportBtn.setMaximumSize(QtCore.QSize(430, 40))
        self.doFbxExportBtn.clicked.connect(self.fbx_export)

        self.fbxTabs.setCurrentIndex(1)

        # find morphs
        self.findMorphs()

        # find custom curves
        self.findCustomCurves()

        # populate UI
        self.fbx_populateUI()

    def fbx_checkExportMesh(self):

        if self.exportMorphsCB.isChecked():
            if not self.exportMeshCB.isChecked():
                self.exportMeshCB.setChecked(True)
                self.exportMeshCB.setEnabled(False)

        else:
            self.exportMeshCB.setEnabled(True)

    def fbx_populateUI(self, refresh=False, *args):

        # remove existing animation sequences
        widgetsToRemove = []

        for i in range(self.fbxSequenceLayout.count()):
            child = self.fbxSequenceLayout.itemAt(i)
            if child is not None:
                if type(child.widget()) == QtWidgets.QGroupBox:
                    widgetsToRemove.append(child.widget())

        for widget in widgetsToRemove:
            self.fbx_removeAnimSequence(widget)

        # get characters in scene
        characters = []
        characterInfo = self.findCharacters()
        for info in characterInfo:
            characters.append(info[0])

        # check character nodes for fbxAnimData
        for currentChar in characters:
            # loop through data, adding sequences and setting settings
            if cmds.objExists(currentChar + ":ART_RIG_ROOT.fbxAnimData"):
                fbxData = json.loads(cmds.getAttr(currentChar + ":ART_RIG_ROOT.fbxAnimData"))

                # each entry in the fbxData list is a sequence with all the needed information
                for data in fbxData:

                    # first, set export settings
                    self.exportMeshCB.setChecked(data[0])
                    self.exportMorphsCB.setChecked(data[1])
                    self.exportCustomAttrsCB.setChecked(data[2])

                    # select morphs and curves to export in the lists if they exist
                    for i in range(self.exportMorphList.count()):
                        bShape = self.exportMorphList.item(i)
                        text = bShape.text()
                        if text in data[3]:
                            bShape.setSelected(True)

                    for i in range(self.exportCurveList.count()):
                        cCurve = self.exportCurveList.item(i)
                        text = cCurve.text()
                        if text in data[4]:
                            cCurve.setSelected(True)

                    # set pre/post script info
                    self.preScriptCB.setChecked(data[5][0])
                    self.preScript_path.setText(data[5][1])

                    self.postScriptCB.setChecked(data[6][0])
                    self.postScript_path.setText(data[6][1])

                    # add anim sequence
                    self.fbx_addSequence(data[7])

    def fbx_addSequence(self, data=None):

        # get number of children of fbxSequenceLayout
        children = self.fbxSequenceLayout.count()
        index = children - 1

        # contained groupBox for each sequence
        groupBox = QtWidgets.QGroupBox()
        groupBox.setCheckable(True)
        groupBox.setMaximumHeight(260)
        groupBox.setMaximumWidth(380)
        self.fbxSequenceLayout.insertWidget(index, groupBox)

        groupBox.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        groupBox.customContextMenuRequested.connect(partial(self.fbx_createContextMenu, groupBox))

        # add frame layout to groupbox
        frameLayout = QtWidgets.QVBoxLayout(groupBox)

        # add frame to groupbox
        frame = QtWidgets.QFrame()
        frame.setObjectName("light")
        frameLayout.addWidget(frame)

        vLayout = QtWidgets.QVBoxLayout(frame)
        vLayout.setObjectName("vLayout")

        QtCore.QObject.connect(groupBox, QtCore.SIGNAL("toggled(bool)"), frame.setVisible)
        groupBox.setChecked(True)

        # #portrait and character combo box
        characterLayout = QtWidgets.QHBoxLayout()
        characterLayout.setObjectName("charLayout")
        vLayout.addLayout(characterLayout)

        portrait = QtWidgets.QLabel()
        portrait.setObjectName("charPortrait")
        portrait.setMinimumSize(QtCore.QSize(30, 30))
        portrait.setMaximumSize(QtCore.QSize(30, 30))
        characterLayout.addWidget(portrait)

        characterComboBox = QtWidgets.QComboBox()
        characterComboBox.setObjectName("charComboBox")
        characterComboBox.setMinimumHeight(30)
        characterComboBox.setMaximumHeight(30)
        characterLayout.addWidget(characterComboBox)

        # populate combo box
        characters = self.findCharacters()
        for character in characters:
            characterName = character[0]
            characterComboBox.addItem(characterName)
        self.updateIcon(characterComboBox, portrait, characters)
        characterComboBox.currentIndexChanged.connect(partial(self.updateIcon, characterComboBox, portrait, characters))

        # #Checkbox, path, and browse button
        pathLayout.setObjectName("pathLayout")
        vLayout.addLayout(pathLayout)

        checkBox = QtWidgets.QCheckBox()
        checkBox.setObjectName("exportCheckBox")
        checkBox.setChecked(True)
        pathLayout.addWidget(checkBox)

        pathField = QtWidgets.QLineEdit()
        pathField.setObjectName("exportPath")
        pathLayout.addWidget(pathField)
        pathField.setMinimumWidth(200)

        browseBtn.setMinimumSize(25, 25)
        browseBtn.setMaximumSize(25, 25)
        browseBtn.setIconSize(QtCore.QSize(25, 25))
        browseBtn.clicked.connect(partial(self.fileBrowse_export, pathField))

        # #frame range, and frame rate
        optionLayout = QtWidgets.QHBoxLayout()
        vLayout.addLayout(optionLayout)
        optionLayout.setObjectName("optionLayout")

        label1 = QtWidgets.QLabel("Start Frame: ")
        optionLayout.addWidget(label1)
        label1.setStyleSheet("background: transparent;")

        startFrame = QtWidgets.QSpinBox()
        startFrame.setObjectName("startFrame")
        optionLayout.addWidget(startFrame)
        startFrame.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        startFrame.setRange(-1000, 10000)

        label2 = QtWidgets.QLabel(" End Frame: ")
        optionLayout.addWidget(label2)
        label2.setStyleSheet("background: transparent;")
        label2.setAlignment(QtCore.Qt.AlignCenter)

        endFrame = QtWidgets.QSpinBox()
        endFrame.setObjectName("endFrame")
        optionLayout.addWidget(endFrame)
        endFrame.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        endFrame.setRange(-1000, 10000)

        # set frame range by default based on current timeline
        start = cmds.playbackOptions(q=True, ast=True)
        end = cmds.playbackOptions(q=True, aet=True)
        startFrame.setValue(start)
        endFrame.setValue(end)

        frameRate = QtWidgets.QComboBox()
        frameRate.setObjectName("frameRate")
        optionLayout.addWidget(frameRate)
        frameRate.hide()

        # add items to frame rate
        frameRate.addItem("ntsc")
        frameRate.addItem("ntscf")
        frameRate.addItem("film")

        # set the FPS to the current scene setting
        fps = cmds.currentUnit(q=True, time=True)
        if fps == "film":
            frameRate.setCurrentIndex(2)
        if fps == "ntsc":
            frameRate.setCurrentIndex(0)
        if fps == "ntscf":
            frameRate.setCurrentIndex(1)

        # #advanced options
        advancedGroup = QtWidgets.QGroupBox("Advanced Settings")
        vLayout.addWidget(advancedGroup)
        advancedGroup.setCheckable(True)

        advancedLayout = QtWidgets.QVBoxLayout(advancedGroup)
        advancedFrame = QtWidgets.QFrame()
        advancedLayout.addWidget(advancedFrame)
        advancedFrameLayout = QtWidgets.QVBoxLayout(advancedFrame)

        # #rotation interpolation
        interpLayout = QtWidgets.QHBoxLayout()
        advancedFrameLayout.addLayout(interpLayout)

        label3 = QtWidgets.QLabel("Rotation Interpolation: ")
        interpLayout.addWidget(label3)
        label3.setStyleSheet("background: transparent;")

        interpCombo = QtWidgets.QComboBox()
        interpLayout.addWidget(interpCombo)
        interpCombo.setObjectName("rotInterp")
        interpCombo.setMinimumWidth(150)

        interpCombo.addItem("Quaternion Slerp")
        interpCombo.addItem("Independent Euler-Angle")

        # #sample rate and root options
        rateRootLayout = QtWidgets.QHBoxLayout()
        advancedFrameLayout.addLayout(rateRootLayout)

        label4 = QtWidgets.QLabel("Sample Rate: ")
        rateRootLayout.addWidget(label4)

        sampleRate = QtWidgets.QDoubleSpinBox()
        sampleRate.setObjectName("sampleRate")
        rateRootLayout.addWidget(sampleRate)
        sampleRate.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        sampleRate.setRange(0, 1)
        sampleRate.setSingleStep(0.1)
        sampleRate.setValue(1.00)

        rootComboBox = QtWidgets.QComboBox()
        rootComboBox.setObjectName("rootExportOptions")
        rootComboBox.addItem("Export Root Animation")
        rootComboBox.addItem("Zero Root")
        rootComboBox.addItem("Zero Root, Keep World Space")
        rateRootLayout.addWidget(rootComboBox)

        QtCore.QObject.connect(advancedGroup, QtCore.SIGNAL("toggled(bool)"), advancedFrame.setVisible)
        advancedGroup.setChecked(False)

        # signal slot for groupbox title
        characterComboBox.currentIndexChanged.connect(partial(self.fbx_updateTitle, groupBox))
        pathField.textChanged.connect(partial(self.fbx_updateTitle, groupBox))
        startFrame.valueChanged.connect(partial(self.fbx_updateTitle, groupBox))
        endFrame.valueChanged.connect(partial(self.fbx_updateTitle, groupBox))
        checkBox.stateChanged.connect(partial(self.fbx_updateTitle, groupBox))

        # create groupbox title
        self.fbx_updateTitle(groupBox)

        # set data if coming from duplicate call
        if data:

            # set character combo box
            for i in range(characterComboBox.count()):
                text = characterComboBox.itemText(i)
                if text == data[0]:
                    characterComboBox.setCurrentIndex(i)

            # set export checkbox
            checkBox.setChecked(data[1])

            # set export path
            pathField.setText(data[2])

            # set start frame
            startFrame.setValue(data[3])

            # set end frame
            endFrame.setValue(data[4])

            # set FPS
            for i in range(frameRate.count()):
                text = frameRate.itemText(i)
                if text == data[5]:
                    frameRate.setCurrentIndex(i)

            # set rotation interpolation
            for i in range(interpCombo.count()):
                text = interpCombo.itemText(i)
                if text == data[6]:
                    interpCombo.setCurrentIndex(i)

            # set sample rate
            sampleRate.setValue(data[7])

            # set root export
            for i in range(rootComboBox.count()):
                text = rootComboBox.itemText(i)
                if text == data[8]:
                    rootComboBox.setCurrentIndex(i)

    def fbx_createContextMenu(self, widget, point):

        # icons
        icon_delete = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/delete.png"))
        icon_duplicate = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/duplicate.png"))
        icon_collapse = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/upArrow.png"))
        icon_expand = QtGui.QIcon(utils.returnNicePath(self.iconsPath, "System/downArrow.png"))

        contextMenu = QtWidgets.QMenu()
        contextMenu.addAction(icon_delete, "Remove Sequence", partial(self.fbx_removeAnimSequence, widget))
        contextMenu.addAction(icon_duplicate, "Duplicate Sequence", partial(self.fbx_duplicateSequence, widget))
        contextMenu.addAction(icon_expand, "Expand All Sequences", partial(self.fbx_expandAllSequences, True))
        contextMenu.addAction(icon_collapse, "Collapse All Sequences", partial(self.fbx_expandAllSequences, False))
        contextMenu.exec_(widget.mapToGlobal(point))

    def fbx_removeAnimSequence(self, widget):

        widget.setParent(None)
        widget.close()
        self.fbx_saveExportData()

    def fbx_duplicateSequence(self, widget):

        sequenceData = self.fbx_getSequenceInfo(widget)
        self.fbx_addSequence(sequenceData)

    def fileBrowse_export(self, widget):

        try:
            path = cmds.fileDialog2(fm=0, okc="Export", dir=self.projectPath, ff="*.fbx")
            nicePath = utils.returnFriendlyPath(path[0])
            widget.setText(nicePath)
        except:
            pass

    def fileBrowse_script(self, widget, checkbox=None):

        try:
            path = cmds.fileDialog2(fm=1, okc="Accept", dir=self.projectPath, ff="*.py;;*.mel")

            if checkbox is not None:
                checkbox.setChecked(True)
        except:
            pass

    def fbx_export(self):

        # save settings
        characterData = self.fbx_saveExportData()

        # find mayapy interpreter location
        mayapy = utils.getMayaPyLoc()

        msgBax.setText("Please make sure any changes to the current file are saved before continuing.\
        This process will be creating a temporary file to do all of the exporting from.")

            # save copy of scene to temp location
            sourceFile = cmds.file(q=True, sceneName=True)
            filePath = os.path.dirname(sourceFile)
            tempFile = os.path.join(filePath, "export_TEMP.ma")

            cmds.file(rename=tempFile)
            cmds.file(save=True, type="mayaAscii", force=True)

            # pass tempFile and characterData to mayapy instance for processing
            if os.path.exists(mayapy):
                script = utils.returnFriendlyPath(os.path.join(self.toolsPath, "Core\Scripts\System\ART_FbxExport.py"))

                # create a temp file with the json data
                with tempfile.NamedTemporaryFile(delete=False) as temp:
                    json.dump(characterData, temp)
                    temp.close()

                # create a log file
                stdoutFile = os.path.join(filePath, "export_log.txt")
                out = file(stdoutFile, 'w')

                # open mayapy, passing in export file and character data
                subprocess.Popen(mayapy + ' ' + "\"" + script + "\"" + ' ' + "\"" + tempFile + "\"" + ' ' +
                                 "\"" + temp.name + "\"", stdout=out, stderr=out)

                # close the output file (for logging)
                out.close()

            else:
                msgBox.setText("mayapy executable not found. Currently not implemented for mac and linux.")
                msgBox.setIcon(QtWidgets.QMessageBox.Error)
                msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
                msgBox.setDefaultButton(QtWidgets.QMessageBox.Ok)

            # reopen the original file
            cmds.file(sourceFile, open=True, force=True)

    def findMorphs(self):

        # clear list
        self.exportMorphList.clear()
        characterMeshes = []

        # get all characters

            currentCharacter = character[0]

            # get meshes off of character node
            if cmds.objExists(currentCharacter + ":ART_RIG_ROOT"):
                if cmds.objExists(currentCharacter + ":ART_RIG_ROOT.LOD_0_Meshes"):
                    characterMeshes = cmds.listConnections(currentCharacter + ":ART_RIG_ROOT.LOD_0_Meshes")

            # get skinClusters in scene and query their connections
            skins = cmds.ls(type="skinCluster")

            for skin in skins:
                shapeInfo = cmds.listConnections(skin, c=True, type="blendShape", et=True)
                mesh = cmds.listConnections(skin, type="mesh")

                if mesh is not None:
                    # confirm that the blendshape found belongs to one of our character meshes. Then add to list
                    if mesh[0] in characterMeshes:
                        if shapeInfo is not None:
                            for info in shapeInfo:
                                if cmds.nodeType(info) == "blendShape":
                                    item = QtWidgets.QListWidgetItem(info)
                                    self.exportMorphList.addItem(item)
                                    item.setSelected(False)

    def findCustomCurves(self):

            rootBone = currentCharacter + ":root"
            attrs = cmds.listAttr(rootBone, keyable=True)

            standardAttrs = ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ",
                             "scaleX", "scaleY", "scaleZ", "visibility"]

                if attr not in standardAttrs:
                    item = QtWidgets.QListWidgetItem(currentCharacter + ":" + attr)
                    self.exportCurveList.addItem(item)
                    item.setSelected(False)

        characterInfo = []

        # go through each node, find the character name, the namespace on the node, and the picker attribute
        for node in characterNodes:
            try:
                namespace = cmds.getAttr(node + ".namespace")
            except:
                namespace = cmds.getAttr(node + ".name")

            # add the icon found on the node's icon path attribute to the tab
            iconPath = cmds.getAttr(node + ".iconPath")
            iconPath = utils.returnNicePath(self.projectPath, iconPath)

            characterInfo.append([namespace, iconPath])

        return characterInfo

    def updateIcon(self, comboBox, label, characterInfo, *args):

        # get current selection of combo box
        characterName = comboBox.currentText()

        # loop through characterInfo, find matching characterName, and get icon path
        for each in characterInfo:
            if characterName == each[0]:
                iconPath = each[1]
                img = QtGui.QImage(iconPath)
                pixmap = QtGui.QPixmap(img.scaledToWidth(30))
                label.setPixmap(pixmap)
                label.show()

    def windowResized(self, event):

        currentSize = self.mainWin.size()
        height = currentSize.height()

        self.fbxTabLayoutFrame.resize(450, height - 50)

        width = self.fbxTabs.size()
        width = width.width()
        self.fbxTabs.resize(width, height - 50)
        self.fbxMainScroll.setMinimumSize(415, height - 220)

    def disableWidget(self, widget, checkbox, *args):

        state = checkbox.isChecked()
        if state:
            widget.setEnabled(True)
            for i in range(widget.count()):
                item = widget.item(i)
        else:
            widget.setEnabled(False)
                item.setHidden(True)

    def fbx_updateTitle(self, groupBox, *args):

        # get info from interface
        data = []
        children = groupBox.children()
        for each in children:
            if type(each) == QtWidgets.QFrame:
                contents = each.children()

                for child in contents:
                    objectName = child.objectName()

                    if objectName == "charComboBox":
                        char = child.currentText()
                        data.append(char)

                    if objectName == "exportCheckBox":
                        value = child.isChecked()
                        data.append(value)

                    if objectName == "exportPath":
                        path = child.text()
                        filename = os.path.basename(path)
                        data.append(filename.partition(".")[0])

                    if objectName == "startFrame":
                        startFrame = child.value()
                        data.append(startFrame)

                    if objectName == "endFrame":
                        endFrame = child.value()
                        data.append(endFrame)

        titleString = ""
        if data[1]:
            titleString += data[0] + ", "
            titleString += data[2] + ", "
            titleString += "["
            titleString += str(data[3]) + ": "
            titleString += str(data[4]) + "]"
        else:
            titleString += "Not Exporting.."
        groupBox.setTitle(titleString)

    def fbx_saveExportData(self):

        exportData = []

        # get main export settings
        exportMesh = self.exportMeshCB.isChecked()
        exportMorph = self.exportMorphsCB.isChecked()
        exportCurve = self.exportCustomAttrsCB.isChecked()

        exportData.append(exportMesh)
        exportData.append(exportMorph)
        exportData.append(exportCurve)

        # get selected morphs
        morphs = []
        for i in range(self.exportMorphList.count()):
            item = self.exportMorphList.item(i)
            if item.isSelected():
                morphs.append(item.text())

        # get selected curves
        curves = []
        for i in range(self.exportCurveList.count()):
            item = self.exportCurveList.item(i)
                curves.append(item.text())

        # pre and post script
        preScript = self.preScriptCB.isChecked()
        preScript_path = self.preScript_path.text()

        postScript = self.postScriptCB.isChecked()
        postScript_path = self.postScript_path.text()

        exportData.append(morphs)
        exportData.append(curves)
        exportData.append([preScript, preScript_path])
        exportData.append([postScript, postScript_path])

        # get fbx sequences and settings
        characterData = {}
            if type(child.widget()) == QtWidgets.QGroupBox:
                data = []
                sequenceData = self.fbx_getSequenceInfo(child.widget())
                data.extend(exportData)
                data.append(sequenceData)

                if sequenceData[0] not in characterData:
                    characterData[sequenceData[0]] = [data]
                else:
                    currentData = characterData.get(sequenceData[0])
                    currentData.append(data)
                    characterData[sequenceData[0]] = currentData

        # loop through each key (character) in the dictionary, and write its data to the network node
        for each in characterData:
            data = characterData.get(each)

            # Add that data to the character node
            networkNode = each + ":ART_RIG_ROOT"
            if not cmds.objExists(networkNode + ".fbxAnimData"):
                cmds.addAttr(networkNode, ln="fbxAnimData", dt="string")

            cmds.setAttr(networkNode + ".fbxAnimData", json.dumps(data), type="string")

        return characterData

    def fbx_getSequenceInfo(self, groupBox, *args):

        data = []

                        data.append(path)

                    if objectName == "frameRate":
                        fps = child.currentText()
                        data.append(fps)

                    if type(child) == QtWidgets.QGroupBox:
                        subChildren = child.children()

                        for sub in subChildren:
                            if type(sub) == QtWidgets.QFrame:
                                advancedChildren = sub.children()
                                for advancedChild in advancedChildren:
                                    advancedObj = advancedChild.objectName()

                                    if advancedObj == "sampleRate":
                                        rate = advancedChild.value()
                                        data.append(rate)

                                    if advancedObj == "rotInterp":
                                        interp = advancedChild.currentText()
                                        data.append(interp)

                                    if advancedObj == "rootExportOptions":
                                        root = advancedChild.currentText()
                                        data.append(root)

        return data

    def fbx_expandAllSequences(self, state):

            data = []
                child.widget().setChecked(state)

buildBoneCounterUI() (Interfaces.ART_BoneCounter.ART_BoneCounter method)

setBoneCountTarget() (Interfaces.ART_BoneCounter.ART_BoneCounter method)

setBoneCountTarget_Confirm() (Interfaces.ART_BoneCounter.ART_BoneCounter method)

updateBoneCount() (Interfaces.ART_BoneCounter.ART_BoneCounter method)

aimModeUI_Toggle() (Interfaces.ART_AimModeUI.ART_AimMode method)

ART_ChangeModuleName_UI (class in Interfaces.ART_ChangeModuleNameUI)

__init__() (Interfaces.ART_ChangeModuleNameUI.ART_ChangeModuleName_UI method)

populateCharacters() (Interfaces.ART_EditRigUI.ART_EditRigUI method)

populateGroups() (Interfaces.ART_EditRigUI.ART_EditRigUI method)

populateProjects() (Interfaces.ART_EditRigUI.ART_EditRigUI method)

unsavedChanges() (Interfaces.ART_EditRigUI.ART_EditRigUI method)

ART_AddModuleToCanvas (class in Interfaces.ART_AddModuleToCanvas)

__init__() (Interfaces.ART_AddModuleToCanvas.ART_AddModuleToCanvas method)

buildUI() (Interfaces.ART_AddModuleToCanvas.ART_AddModuleToCanvas method)

closeWin() (Interfaces.ART_AddModuleToCanvas.ART_AddModuleToCanvas method)

mirrorChildren() (Interfaces.ART_AddModuleToCanvas.ART_AddModuleToCanvas method)

__init__() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

buildRigs() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

buildUI() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

completeBuild() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

executeScript() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

exportWeights() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

importWeights() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

postScript() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

preScript() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

rebuildSkeleton() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

setRigPose() (Interfaces.ART_BuildProgressUI.ART_BuildProgress_UI method)

__init__() (Interfaces.ART_BakeOffsetsUI.ART_BakeOffsets method)

bakeOffsets() (Interfaces.ART_BakeOffsetsUI.ART_BakeOffsets method)

buildBakeOffsetsUI() (Interfaces.ART_BakeOffsetsUI.ART_BakeOffsets method)

ART_AddOrRemoveInfluences (class in Interfaces.ART_AddOrRemoveInfluences)

createModule() (Interfaces.ART_AddModuleUI.ART_AddModule_UI method)

searchList() (Interfaces.ART_AddModuleUI.ART_AddModule_UI method)

updatePreview() (Interfaces.ART_AddModuleUI.ART_AddModule_UI method)

ART_ChangeModuleParent_UI (class in Interfaces.ART_ChangeModuleParentUI)

addJointMoverToOutliner() (System.ART_RigModule.ART_RigModule method)

applyModuleChanges() (System.ART_RigModule.ART_RigModule method)

changeModuleParent() (System.ART_RigModule.ART_RigModule method)

checkForDependencies() (System.ART_RigModule.ART_RigModule method)

createGlobalMoverButton() (System.ART_RigModule.ART_RigModule method)

createMeshMoverButton() (System.ART_RigModule.ART_RigModule method)

createMirrorModule() (System.ART_RigModule.ART_RigModule method)

createMirrorOfModule_UI() (System.ART_RigModule.ART_RigModule method)

createOffsetMoverButton() (System.ART_RigModule.ART_RigModule method)

createRigPoseSliderForJoint() (System.ART_RigModule.ART_RigModule method)

mirrorTransformations() (System.ART_RigModule.ART_RigModule method)

mirrorTransformations_Custom() (System.ART_RigModule.ART_RigModule method)

mirrorTransformations_RigPose() (System.ART_RigModule.ART_RigModule method)

removeSkeletalConstraints() (System.ART_RigModule.ART_RigModule method)

returnClassObject (System.ART_RigModule.ART_RigModule attribute)

returnCreatedJoints (System.ART_RigModule.ART_RigModule attribute)

returnJointMovers (System.ART_RigModule.ART_RigModule attribute)

returnMirrorModuleInst (System.ART_RigModule.ART_RigModule attribute)

returnNetworkNode (System.ART_RigModule.ART_RigModule attribute)

returnPrefixSuffix (System.ART_RigModule.ART_RigModule attribute)

returnRigNetworkNode (System.ART_RigModule.ART_RigModule attribute)

selectionScriptJob_animUI() (System.ART_RigModule.ART_RigModule method)

setPosePercentage_Part() (System.ART_RigModule.ART_RigModule method)

setReferencePoseSlider() (System.ART_RigModule.ART_RigModule method)

setupModelPoseForRig() (System.ART_RigModule.ART_RigModule method)

skeletonSettings_UI() (System.ART_RigModule.ART_RigModule method)