aboutsummaryrefslogtreecommitdiff
path: root/Core/Scripts/System
diff options
context:
space:
mode:
authorMobileMachine\jeremy <[email protected]>2017-06-06 22:59:03 -0400
committerMobileMachine\jeremy <[email protected]>2017-06-06 22:59:03 -0400
commit24725fa8681f906ab44d80687c09fecc171a2896 (patch)
tree312a601df29aca7f8db9f44082d96ebc7a679138 /Core/Scripts/System
parentInitial commit (diff)
downloadartv2-24725fa8681f906ab44d80687c09fecc171a2896.tar.xz
artv2-24725fa8681f906ab44d80687c09fecc171a2896.zip
Initial Submission
First submission of current state of ARTv2. Currently considered to be in Alpha. There are a couple of animation tools not implemented yet, and one module not implemented yet, as well as incomplete documentation.
Diffstat (limited to 'Core/Scripts/System')
-rw-r--r--Core/Scripts/System/ART_FbxExport.py392
-rw-r--r--Core/Scripts/System/ART_Reporter.py246
-rw-r--r--Core/Scripts/System/ART_RigModule.py3012
-rw-r--r--Core/Scripts/System/ART_Settings.py295
-rw-r--r--Core/Scripts/System/ART_StripFbxNamespace.py40
-rw-r--r--Core/Scripts/System/ART_Updater.py600
-rw-r--r--Core/Scripts/System/__init__.py0
-rw-r--r--Core/Scripts/System/git_utils.py78
-rw-r--r--Core/Scripts/System/interfaceUtils.py645
-rw-r--r--Core/Scripts/System/mathUtils.py207
-rw-r--r--Core/Scripts/System/riggingUtils.py1448
-rw-r--r--Core/Scripts/System/utils.py1159
12 files changed, 8122 insertions, 0 deletions
diff --git a/Core/Scripts/System/ART_FbxExport.py b/Core/Scripts/System/ART_FbxExport.py
new file mode 100644
index 0000000..d2745b5
--- /dev/null
+++ b/Core/Scripts/System/ART_FbxExport.py
@@ -0,0 +1,392 @@
+import sys
+import os
+import json
+import maya.standalone as std
+import maya.cmds as cmds
+
+
+std.initialize(name='python')
+
+# set up axis to z
+cmds.upAxis(ax='z')
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def fbx_export():
+
+ # log some basic info about incoming data
+ sys.stdout.write("Opening File:\n")
+ sys.stdout.write(str(sys.argv[1]))
+ sys.stdout.write("Export Data File:\n\n")
+ sys.stdout.write(str(sys.argv[2]))
+ sys.stdout.write("\n\n")
+
+ # open the maya file
+ cmds.file(str(sys.argv[1]), open=True, ignoreVersion=True, force=True, prompt=False)
+
+ # load data back into dict format
+ f = open(str(sys.argv[2]), 'r')
+ sys.stdout.write(str(f))
+ data = json.load(f)
+ f.close()
+
+ # the data is a dictionary of characters and their export information. That data might look like:
+ # character: [[seq1 data], [seq2 data]]
+ # we need to go through each character's data, go through each sequence, and export that sequence out
+ for character in data:
+ sequences = data.get(character)
+ sys.stdout.write("\n\n")
+ sys.stdout.write(str(sequences))
+ sys.stdout.write("\n")
+ sys.stdout.write(str(len(sequences)))
+ sys.stdout.write("\n")
+
+ # sequence data as follows [0-6]:
+ # Export Meshes? Export Morphs? Export Attrs? Which Morphs? Which Attrs? Pre Script? Path? Post Script? Path?
+ # Sequence data [7] as follows:
+ # [character, export?, fbxPath, start, end, fps, rotation interp, sample rate, root export]
+
+ # loop through each sequence in the sequence data
+ for sequence in sequences:
+ exportMesh = sequence[0]
+ exportMorphs = sequence[1]
+ exportAttrs = sequence[2]
+ morphTargets = sequence[3]
+ customAttrs = sequence[4]
+ preScript = sequence[5]
+ postScript = sequence[6]
+ sequenceInfo = sequence[7]
+
+ # PRE-SCRIPT
+ if preScript[0]:
+ sys.stdout.write("\nExecuting Pre-Script...\n")
+ status = executeScript(preScript[1])
+
+ if status:
+ sys.stdout.write("\nPre-Script successfully executed!\n")
+ else:
+ sys.stdout.write("\nPre-Script NOT successfully executed :( \n")
+
+ # if this sequence is marked for export
+ if sequenceInfo[1] == True:
+
+ euler = False
+ quat = True
+
+ # get the outputPath
+ filePath = sequenceInfo[2]
+
+ # get the start and end frame
+ startFrame = sequenceInfo[3]
+ endFrame = sequenceInfo[4]
+
+ # get the fps
+ fps = sequenceInfo[5]
+ sys.stdout.write("\n\n" + str(fps) + "\n\n")
+ cmds.currentUnit(time=str(fps))
+ cmds.refresh()
+
+ # get the rotation interpolation
+ interp = sequenceInfo[6]
+ if interp == "Quaternion Slerp":
+ euler = False
+ quat = True
+ if interp == "Independent Euler-Angle":
+ euler = True
+ quat = False
+
+ # set the fbx export flags
+ setExportFlags(startFrame, endFrame, euler, quat)
+
+ # get the sample rate
+ sample = sequenceInfo[7]
+
+ # get the root export option
+ rootExport = sequenceInfo[8]
+
+ # build selection of what to export
+ toExport = []
+
+ # EXPORT MESHES
+ if exportMesh:
+ # get meshes
+ if cmds.objExists(character + ":ART_RIG_ROOT"):
+ if cmds.objExists(character + ":ART_RIG_ROOT.LOD_0_Meshes"):
+ meshes = cmds.listConnections(character + ":ART_RIG_ROOT.LOD_0_Meshes")
+
+ for mesh in meshes:
+ toExport.append(mesh)
+
+ # EXPORT MORPHS
+ if exportMorphs:
+ for each in morphTargets:
+ if cmds.objExists(each):
+ if (each.partition(":")[0]) == character:
+ conns = cmds.listConnections(each, type="mesh")
+ if conns is not None:
+ toExport.extend(conns)
+
+ # SKELETON
+ skeleton = [character + ":root"]
+ skeleton.extend(reversed(cmds.listRelatives(character + ":root", type='joint', allDescendents=True)))
+ toExport.append(skeleton[0])
+
+ # bake skeleton and blendshapes (using sample rate)
+ cmds.select(skeleton)
+ if exportMorphs:
+ cmds.select(morphTargets, add=True)
+
+ # bake down animation onto skeleton and blendshapes
+ cmds.bakeResults(simulation=True, sb=sample, time=(startFrame, endFrame))
+
+ # run euler filter and fix tangents
+ cmds.select(skeleton)
+ cmds.filterCurve()
+ cmds.selectKey()
+ cmds.keyTangent(itt="linear", ott="linear")
+
+ # deal with custom attrs (deleting if user chose not to export)
+ standardAttrs = ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ",
+ "scaleX", "scaleY", "scaleZ", "visibility"]
+ if exportAttrs:
+ available_attrs = cmds.listAttr(skeleton[0], keyable=True)
+ for attr in available_attrs:
+ if attr not in standardAttrs:
+ if (character + ":" + attr) not in customAttrs:
+ sys.stdout.write("\n\n")
+ sys.stdout.write("Removing Attr:")
+ sys.stdout.write(str(attr))
+ sys.stdout.write("\n\n")
+
+ # remove the attribute from the root
+ cmds.deleteAttr(skeleton[0], at=attr)
+
+ # Root Export Options
+ sys.stdout.write("\n\n" + str(rootExport) + "\n\n")
+ if rootExport != "Export Root Animation":
+ if rootExport == "Zero Root":
+ sys.stdout.write("\nZeroing Out Root Animation\n")
+ cmds.cutKey(skeleton[0])
+ attrs = ["translate", "rotate", "scale"]
+ for attr in attrs:
+ try:
+ cmds.disconnectAttr(character + ":driver_root." + attr, character + ":root." + attr)
+ except Exception, e:
+ sys.stdout.write("\n" + str(e) + "\n")
+
+ for zeroAttr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
+ cmds.setAttr(character + ":root" + zeroAttr, 0)
+
+ if rootExport == "Zero Root, Keep World Space":
+ sys.stdout.write("\nZeroing Out Root Animation, Keeping World Space on rest of rig\n")
+ # first, find children that need to be locked in place and create a locator for each
+ rootConnections = cmds.listRelatives(skeleton[0], children=True, type="joint")
+ lockNodes = []
+ locators = []
+
+ for conn in rootConnections:
+ lockNodes.append(conn)
+ constraints = []
+
+ for lockNode in lockNodes:
+ loc = cmds.spaceLocator(name=lockNode + "_loc")[0]
+ locators.append(loc)
+ constraint = cmds.parentConstraint(lockNode, loc)[0]
+ constraints.append(constraint)
+
+ sys.stdout.write("Locking down " + lockNode + " to zero out root.")
+ sys.stdout.write("\n")
+
+ # then bake the locators
+ cmds.select(clear=True)
+ for lockNode in lockNodes:
+ cmds.select(lockNode + "_loc", add=True)
+
+ cmds.bakeResults(simulation=True, sb=sample, time=(float(startFrame), float(endFrame)))
+ cmds.delete(constraints)
+
+ # reverse the constraints so the bones are constrained to the locator
+ boneConstraints = []
+ for lockNode in lockNodes:
+ con = cmds.parentConstraint(lockNode + "_loc", lockNode)[0]
+ boneConstraints.append(con)
+
+ # disconnect attrs on root bone
+ attrs = ["translate", "rotate", "scale"]
+ for attr in attrs:
+ try:
+ cmds.disconnectAttr(character + ":driver_root." + attr, character + ":root." + attr)
+ except Exception, e:
+ sys.stdout.write("\n" + str(e) + "\n")
+
+ # cut keys on the root bone
+ cmds.cutKey(skeleton[0])
+ cmds.setAttr(skeleton[0] + ".tx", 0)
+ cmds.setAttr(skeleton[0] + ".ty", 0)
+ cmds.setAttr(skeleton[0] + ".tz", 0)
+ cmds.setAttr(skeleton[0] + ".rx", 0)
+ cmds.setAttr(skeleton[0] + ".ry", 0)
+ cmds.setAttr(skeleton[0] + ".rz", 0)
+
+ # bake bones now in world space
+ cmds.select(clear=True)
+ for lockNode in lockNodes:
+ cmds.select(lockNode, add=True)
+
+ cmds.bakeResults(simulation=True, sb=sample, time=(float(startFrame), float(endFrame)))
+ cmds.delete(boneConstraints)
+
+ # run an euler filter
+ cmds.select(skeleton)
+ cmds.filterCurve()
+
+ # POST - SCRIPT
+ if postScript[0]:
+ sys.stdout.write("\nExecuting Post Script...\n")
+
+ status = executeScript(postScript[1])
+
+ if status:
+ sys.stdout.write("\nPost-Script successfully executed!\n")
+ else:
+ sys.stdout.write("\nPost-Script NOT successfully executed :( \n")
+
+ # EXPORT FBX
+ cmds.select(toExport)
+
+ try:
+ mel.eval("FBXExport -f \"" + filePath + "\" -s")
+ except:
+ cmds.warning("no path specified..")
+
+ # close mayapy
+ os.remove(str(sys.argv[2]))
+ std.uninitialize()
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def setExportFlags(startFrame, endFrame, euler=False, quat=True):
+
+ # in 2015, if oneClick isn't loaded, it will throw up an error
+ try:
+ cmds.loadPlugin("OneClick.mll")
+ sys.stdout.write("Loaded OneClick plugin.")
+ sys.stdout.write("\n")
+ except Exception, e:
+ sys.stderr.write("unable to load OneClick plugin.")
+ sys.stderr.write("\n")
+
+ try:
+ cmds.loadPlugin("fbxmaya.mll")
+ sys.stdout.write("Loaded FBX plugin.")
+ sys.stdout.write("\n")
+ except Exception, e:
+ sys.stderr.write("unable to load FBX plugin.")
+ sys.stderr.write("\n")
+
+ # Mesh
+ mel.eval("FBXExportSmoothingGroups -v true")
+ mel.eval("FBXExportHardEdges -v false")
+ mel.eval("FBXExportTangents -v false")
+ mel.eval("FBXExportInstances -v false")
+ mel.eval("FBXExportInAscii -v true")
+ mel.eval("FBXExportSmoothMesh -v false")
+
+ # Animation
+ mel.eval("FBXExportBakeComplexAnimation -v true")
+ mel.eval("FBXExportBakeComplexStart -v " + str(startFrame))
+ mel.eval("FBXExportBakeComplexEnd -v " + str(endFrame))
+ mel.eval("FBXExportReferencedAssetsContent -v true")
+ mel.eval("FBXExportBakeComplexStep -v 1")
+ mel.eval("FBXExportUseSceneName -v false")
+ mel.eval("FBXExportFileVersion -v FBX201400")
+
+ if euler:
+ mel.eval("FBXExportQuaternion -v euler")
+
+ if quat:
+ mel.eval("FBXExportQuaternion -v quaternion")
+
+ mel.eval("FBXExportShapes -v true")
+ mel.eval("FBXExportSkins -v true")
+ mel.eval("FBXExportUpAxis z")
+
+ # garbage we don't want
+ # Constraints
+ mel.eval("FBXExportConstraints -v false")
+
+ # Cameras
+ mel.eval("FBXExportCameras -v false")
+
+ # Lights
+ mel.eval("FBXExportLights -v false")
+
+ # Embed Media
+ mel.eval("FBXExportEmbeddedTextures -v false")
+
+ # Connections
+ mel.eval("FBXExportInputConnections -v false")
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+def executeScript(scriptPath):
+
+ 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 + "")
+
+ # save the file
+ cmds.file(save=True, type="mayaAscii")
+ status = True
+ except:
+ pass
+
+ return status
+
+
+fbx_export()
diff --git a/Core/Scripts/System/ART_Reporter.py b/Core/Scripts/System/ART_Reporter.py
new file mode 100644
index 0000000..ed062a4
--- /dev/null
+++ b/Core/Scripts/System/ART_Reporter.py
@@ -0,0 +1,246 @@
+from ThirdParty.Qt import QtGui, QtCore, QtWidgets
+import maya.cmds as cmds
+import utils
+import System.interfaceUtils as interfaceUtils
+import System.git_utils as git
+reload(git)
+
+
+
+windowTitle = "ARTv2: Report an Issue"
+windowObject = "pyArtReporterWin"
+
+
+class ART_Reporter(QtWidgets.QMainWindow):
+
+ def __init__(self, parent = None):
+
+ super(ART_Reporter, self).__init__(parent)
+
+ #get the directory path of the tools
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ self.toolsPath = settings.value("toolsPath")
+ self.iconsPath = settings.value("iconPath")
+ self.scriptPath = settings.value("scriptPath")
+ self.projPath = settings.value("projectPath")
+
+
+ #build the UI
+ self.buildSettingsUi()
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def buildSettingsUi(self):
+
+ #fonts
+ self.font = QtGui.QFont()
+ self.font.setPointSize(10)
+ self.font.setBold(False)
+
+ self.fontSmall = QtGui.QFont()
+ self.fontSmall .setPointSize(9)
+ self.fontSmall .setBold(False)
+
+ self.titleFont = QtGui.QFont()
+ self.titleFont.setPointSize(40)
+ self.titleFont.setBold(True)
+
+
+ #load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ self.style = f.read()
+ f.close()
+
+
+ self.setStyleSheet(self.style)
+
+ #size policies
+ mainSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+
+ #create the main widget
+ self.mainWidget = QtWidgets.QWidget()
+ self.mainWidget.setStyleSheet(self.style)
+ self.mainWidget.setStyleSheet("background-color: rgb(0, 0, 0);, color: rgb(0,0,0);")
+ self.setCentralWidget(self.mainWidget)
+
+ #set qt object name
+ self.setObjectName(windowObject)
+ self.setWindowTitle(windowTitle)
+ self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+
+ #create the mainLayout for the rig creator UI
+ self.layout = QtWidgets.QVBoxLayout(self.mainWidget)
+
+ self.resize(300, 600)
+ self.setSizePolicy(mainSizePolicy)
+ self.setMinimumSize(QtCore.QSize( 300, 600 ))
+ self.setMaximumSize(QtCore.QSize( 300, 600 ))
+
+ #create the QFrame
+ self.frame = QtWidgets.QFrame()
+ self.frame.setObjectName("epic")
+ self.layout.addWidget(self.frame)
+ self.widgetLayout = QtWidgets.QVBoxLayout(self.frame)
+
+
+ #Title of Issue
+ self.titleLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.titleLayout)
+
+ titleLabel = QtWidgets.QLabel("Title: ")
+ self.titleLayout.addWidget(titleLabel)
+
+ self.issueTitle = QtWidgets.QLineEdit()
+ self.issueTitle.setPlaceholderText("Title of Issue")
+ self.titleLayout.addWidget(self.issueTitle)
+ self.issueTitle.setMinimumWidth(200)
+ self.issueTitle.setMaximumWidth(200)
+
+ #Type of Issue (from labels)
+ self.labelLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.labelLayout)
+
+ typeLabel = QtWidgets.QLabel("Issue Type: ")
+ self.labelLayout.addWidget(typeLabel)
+
+ self.issueType = QtWidgets.QComboBox()
+ self.labelLayout.addWidget(self.issueType)
+ self.issueType.setMinimumWidth(200)
+ self.issueType.setMaximumWidth(200)
+
+
+
+ #Information
+ summaryLabel = QtWidgets.QLabel("Summary: ")
+ self.widgetLayout.addWidget(summaryLabel)
+
+ infoText = QtWidgets.QTextEdit()
+ infoText.setReadOnly(True)
+ infoText.setEnabled(False)
+ self.widgetLayout.addWidget(infoText)
+ infoText.setMinimumHeight(60)
+ infoText.setMaximumHeight(60)
+ infoText.setTextColor(QtGui.QColor(120,120,120))
+ infoText.append("(Please include any errors and stacktrace if applicable. Also include any reproduction steps if possible.)")
+
+ self.issueInfo = QtWidgets.QTextEdit()
+ self.widgetLayout.addWidget(self.issueInfo)
+ self.issueInfo.setObjectName("light")
+ #Create Issue
+ self.createIssueBtn = QtWidgets.QPushButton("Create Issue")
+ self.createIssueBtn.setObjectName("blueButton")
+ self.widgetLayout.addWidget(self.createIssueBtn)
+ self.createIssueBtn.clicked.connect(self.createIssue)
+
+ self.credentials = git.getGitCreds()
+ if self.credentials == None:
+ git.gitCredsUI(self)
+ self.credentials = git.getGitCreds()
+
+ self.getLabels()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def getLabels(self):
+
+ labels = self.githubInfo("label")
+ ignoreLabel = ["wontfix", "duplicate", "invalid"]
+ if labels != None:
+ for label in labels:
+ if label.name not in ignoreLabel:
+ self.issueType.addItem(label.name)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def createIssue(self):
+
+ title = self.issueTitle.text()
+ issueType = self.issueType.currentText()
+
+ body = "User: " + str(self.credentials[0]) + "\n"
+ body += "Maya Version: " + str(cmds.about(iv = True)) + "\n"
+ body += "OS: " + str(cmds.about(os = True)) + "\n" + "\n"
+ body += self.issueInfo.toPlainText()
+
+ repo = self.githubInfo("repo")
+ issueCreated = False
+ try:
+ issue = repo.create_issue(title, body)
+ issue.set_labels(issueType)
+ issueCreated = True
+
+ except Exception, e:
+ cmds.warning("unable to create issue. Error: " + str(e))
+ self.close()
+ return
+
+ if issueCreated:
+ msgBox = QtWidgets.QMessageBox()
+ msgBox.setText("Your issue has been created")
+ msgBox.setDetailedText("To view your issue, please visit:\nhttps://github.com/epicernst/Test/issues")
+ ret = msgBox.exec_()
+ self.close()
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def githubInfo(self, type):
+
+ #github section
+ # try:
+ from ThirdParty.github import Github
+ # except:
+ # cmds.warning("unable to import github module. You will not be able to create an issue")
+ # self.close()
+ # return
+
+ repoOwner = "epicernst"
+ try:
+ g = Github(self.credentials[0], self.credentials[1])
+ repo = g.get_user(repoOwner).get_repo("Test")
+ labels = repo.get_labels()
+
+ if type == "repo":
+ return repo
+ if type == "label":
+ return labels
+ except:
+ return None
+ pass
+
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def run():
+
+ if cmds.window("pyArtReporterWin", exists = True):
+ cmds.deleteUI("pyArtReporterWin", wnd = True)
+
+ gui = ART_Reporter(interfaceUtils.getMainWindow())
+ gui.show() \ No newline at end of file
diff --git a/Core/Scripts/System/ART_RigModule.py b/Core/Scripts/System/ART_RigModule.py
new file mode 100644
index 0000000..fe0f385
--- /dev/null
+++ b/Core/Scripts/System/ART_RigModule.py
@@ -0,0 +1,3012 @@
+"""
+Author: Jeremy Ernst
+
+ 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
+#########
+"""
+
+# file imports
+import json
+import os
+import traceback
+from functools import partial
+
+import System.interfaceUtils as interfaceUtils
+import System.riggingUtils as riggingUtils
+import System.utils as utils
+import System.mathUtils as mathUtils
+import maya.cmds as cmds
+import maya.mel as mel
+from ThirdParty.Qt import QtGui, QtCore, QtWidgets
+
+# file attributes
+fbxImport = ["None", "FK"]
+matchData = [False, None]
+
+
+class ART_RigModule():
+ def __init__(self, moduleName, moduleType, userCreatedName):
+ """Initiate the class, taking in the instance to the interface and the user specified name.
+
+ :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.
+
+ Instantiate the following class variables as well:
+ * **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.
+
+ Also, read the QSettings to find out where needed paths are.
+ """
+
+ # set class variables
+ self.modName = moduleName
+ self.moduleType = moduleType
+ self.rootMod = None
+ self.name = userCreatedName
+ self.originalName = userCreatedName
+ self.outlinerControls = []
+ self.controlTypes = []
+
+ # get the directory path of the tools
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ self.toolsPath = settings.value("toolsPath")
+ self.iconsPath = settings.value("iconPath")
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def addAttributes(self):
+ """
+ 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)
+ :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.
+ """
+
+ # 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 = open(styleSheetFile, "r")
+ style = f.read()
+ 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
+ """
+
+ # get prefix/suffix
+ name = self.groupBox.title()
+ prefix = name.partition(baseName)[0]
+ suffix = name.partition(baseName)[2]
+
+ # when pressing the change name button on the skeleton settings UI (if it exists):
+
+ # delete the UI if it already exists
+ mayaWindow = interfaceUtils.getMainWindow()
+ mayaWindow = mayaWindow.objectName()
+ 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.
+
+ :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_ChangeModuleParentUI.py
+ """
+
+ # get current parent value
+ currentParent = self.currentParent.text()
+
+ # when pressing the change parent button on the skeleton settings UI (if it exists):
+
+ # delete the UI if it already exists
+ mayaWindow = interfaceUtils.getMainWindow()
+ mayaWindow = mayaWindow.objectName()
+ if cmds.window(mayaWindow + "|pyArtChangeModuleParentUi", q=True, exists=True):
+ cmds.deleteUI(mayaWindow + "|pyArtChangeModuleParentUi")
+
+ # launch a UI for prefix/suffix/preview again
+ import Interfaces.ART_ChangeModuleParentUI as ART_ChangeModuleParentUI
+ reload(ART_ChangeModuleParentUI)
+ inst = ART_ChangeModuleParentUI.ART_ChangeModuleParent_UI(currentParent, moduleInst, rigUiInst,
+ interfaceUtils.getMainWindow())
+ inst.show()
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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.
+
+ :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_SetMirrorModuleUI.py
+ """
+
+ # delete the UI if it already exists
+ mayaWindow = interfaceUtils.getMainWindow()
+ mayaWindow = mayaWindow.objectName()
+ if cmds.window(mayaWindow + "|pyArtSetMirrorModuleUi", q=True, exists=True):
+ cmds.deleteUI(mayaWindow + "|pyArtSetMirrorModuleUi")
+
+ # launch a UI for prefix/suffix/preview again
+ import Interfaces.ART_SetMirrorModuleUI as ART_SetMirrorModuleUI
+ reload(ART_SetMirrorModuleUI)
+ inst = ART_SetMirrorModuleUI.ART_SetMirrorModule_UI(moduleInst, rigUiInst, interfaceUtils.getMainWindow())
+ inst.show()
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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
+ if os.path.exists(fullPath):
+ 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)
+ except Exception, e:
+ 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
+ cmds.refresh(force=True)
+ if not cmds.objExists("JointMover"):
+ cmds.group(empty=True, name="JointMover")
+
+ try:
+ cmds.parent("|" + self.name + "_mover_grp", "JointMover")
+ except Exception, e:
+ 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:
+ for mover in each:
+ 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 = QtGui.QPixmap(20, 15)
+ pixmap.fill(QtGui.QColor("yellow"))
+ icon = QtGui.QIcon(pixmap)
+
+ # create the button
+ 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.
+
+ :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 = QtGui.QPixmap(20, 15)
+ pixmap.fill(QtGui.QColor(100, 200, 255))
+ icon = QtGui.QIcon(pixmap)
+
+ # create the button
+ 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"])
+
+ # connect and add to list
+ 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.
+
+ :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.
+
+ Note: The geometry mover is purely for aesthetics and does not affect the rigging.
+ """
+
+ part = name.partition(self.name)[2]
+
+ # create the icon
+ pixmap = QtGui.QPixmap(20, 15)
+ pixmap.fill(QtGui.QColor(255, 176, 176))
+ icon = QtGui.QIcon(pixmap)
+
+ # create the button
+ 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"])
+
+ # connect and add to list
+ 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
+ mods = cmds.getModifiers()
+ 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)
+
+ selected = cmds.ls(sl=True)
+ 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)
+
+ selected = cmds.ls(sl=True)
+ 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)
+ """
+
+ networkNode = self.returnNetworkNode
+ 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.addSeparator()
+
+ 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.
+ """
+
+ networkNode = self.returnNetworkNode
+ attrs = cmds.listAttr(networkNode, ud=True, hd=True)
+
+ attrData = []
+ for attr in attrs:
+ 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.
+
+ 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.
+
+ 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
+ self.updateSettingsUI()
+ self.applyModuleChanges(self)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def resetSettings(self):
+ """
+ Reset the settings of the module's network node.
+
+ 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.
+
+ After settings are reset, 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):
+
+ 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, lock=False)
+ cmds.setAttr(networkNode + "." + attr, True, lock=True)
+
+ if attrType == "enum":
+ cmds.setAttr(networkNode + "." + attr, lock=False)
+ cmds.setAttr(networkNode + "." + attr, 0, lock=True)
+
+ # relaunch the UI
+ self.updateSettingsUI()
+ self.applyModuleChanges(self)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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.
+
+ """
+
+ cmds.select(name + "_mover_grp", hi=True)
+ selection = cmds.ls(sl=True)
+
+ globalMovers = []
+ offsetMovers = []
+ geoMovers = []
+
+ for each in selection:
+ 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)
+
+ cmds.select(clear=True)
+
+ 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:
+ cmds.setAttr(each + attr, 0)
+ 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
+ movers = self.returnJointMovers
+
+ for moverGrp in movers:
+ for mover in moverGrp:
+ cmds.lockNode(mover, lock=False)
+
+ cmds.delete(self.name + "_mover_grp")
+
+ # 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
+ networkNode = self.returnNetworkNode
+ mirrorModule = cmds.getAttr(networkNode + ".mirrorModule")
+ if mirrorModule != None:
+ if mirrorModule != "None":
+ modules = utils.returnRigModules()
+ for mod in modules:
+ modName = cmds.getAttr(mod + ".moduleName")
+ 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 = []
+ if len(attachedModules) > 0:
+
+ for each in attachedModules:
+ elementList.append([each[2], " -> parent changed from: ", each[1], " to: ", "root\n"])
+ 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")
+ mover = "root_mover"
+
+ # create the connection geo between the two
+ childMover = utils.findOffsetMoverFromName(each[2])
+ riggingUtils.createBoneConnection(mover, childMover, each[2])
+ each[3].applyModuleChanges(each[3])
+ cmds.select(clear=True)
+
+ # remove the network node
+ cmds.delete(networkNode)
+
+ # delete scriptJob
+ cmds.scriptJob(kill=self.scriptJob, force=True)
+ self.updateBoneCount()
+ self.rigUiInst.moduleInstances.remove(self)
+
+ # warn user about changes
+ if len(attachedModules) > 0:
+ 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 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
+ joints = self.returnCreatedJoints
+
+ attachedMods = []
+ instances = {}
+
+ for inst in self.rigUiInst.moduleInstances:
+ networkNode = inst.returnNetworkNode
+ instances[networkNode] = inst
+
+ for module in modules:
+ parentJoint = cmds.getAttr(module + ".parentModuleBone")
+ moduleName = cmds.getAttr(module + ".moduleName")
+ 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
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+ className = cmds.getAttr(networkNode + ".moduleType")
+
+ # launch a UI to get the name information
+ self.mirrorWindow = QtWidgets.QMainWindow()
+
+ # load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ style = f.read()
+ f.close()
+
+ self.mirrorWindow.setStyleSheet(style)
+
+ # size policies
+ mainSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+
+ # create the main widget
+ self.mainWidget = QtWidgets.QWidget()
+ self.mirrorWindow.setCentralWidget(self.mainWidget)
+
+ # set qt object name
+ self.mirrorWindow.setObjectName("ART_createMirrorModuleUI")
+ self.mirrorWindow.setWindowTitle("Create Mirror Module")
+
+ # create the mainLayout for the rig creator UI
+ self.mainLayout = QtWidgets.QVBoxLayout(self.mainWidget)
+ self.mainLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.mirrorWindow.resize(300, 150)
+ self.mirrorWindow.setSizePolicy(mainSizePolicy)
+ self.mirrorWindow.setMinimumSize(QtCore.QSize(300, 150))
+ self.mirrorWindow.setMaximumSize(QtCore.QSize(300, 150))
+
+ # create the background image
+ self.frame = QtWidgets.QFrame()
+ self.mainLayout.addWidget(self.frame)
+
+ # create the layout for the widgets
+ 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.prefixForm.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.prefix)
+
+ # hookup signal/slot connection
+ self.prefix.textChanged.connect(partial(self.updatePreview, baseName))
+
+ # 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.suffixForm.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.suffix)
+
+ # hookup signal/slot connection
+ self.suffix.textChanged.connect(partial(self.updatePreview, baseName))
+
+ # 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(baseName)
+ 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
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ self.previewName.setFont(font)
+
+ spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.widgetLayout.addItem(spacerItem1)
+
+ # create button
+ self.createButton = QtWidgets.QPushButton("CREATE")
+ self.createButton.setObjectName("blueButton")
+ self.widgetLayout.addWidget(self.createButton)
+ self.createButton.setMinimumSize(QtCore.QSize(285, 40))
+ self.createButton.setMaximumSize(QtCore.QSize(285, 40))
+ self.createButton.setSizePolicy(mainSizePolicy)
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ self.createButton.setFont(font)
+
+ # hookup signal/slot on create button
+ self.createButton.clicked.connect(self.createMirrorModule)
+
+ # show the window
+ 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)
+
+ """
+
+ prefix = str(self.prefix.text())
+ suffix = str(self.suffix.text())
+
+ string = ""
+ if len(prefix) > 0:
+ string += prefix + "_"
+
+ string += baseName
+
+ if len(suffix) > 0:
+ string += "_" + suffix
+
+ self.previewName.setText(string)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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.
+
+ """
+
+ userSpecName = str(self.previewName.text())
+ networkNode = self.returnNetworkNode
+ parent = cmds.getAttr(networkNode + ".parentModuleBone")
+ className = cmds.getAttr(networkNode + ".moduleType")
+
+ # check to see if a module already has that name
+ modules = utils.returnRigModules()
+ mirrorModule = None
+ moduleName = None
+
+ for module in modules:
+ name = cmds.getAttr(module + ".moduleName")
+ if name == userSpecName:
+ cmds.confirmDialog(title="Name Exists",
+ message="A module with that name already exists. Please enter a unique name \
+ for the module",
+ icon="critical")
+ return
+
+ # now check the modules that contain the parent bone
+ for module in modules:
+ bones = cmds.getAttr(module + ".Created_Bones")
+ splitJoints = bones.split("::")
+ createdJoints = []
+
+ # create a list of the created bones
+ for bone in splitJoints:
+ if bone != "":
+ 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:
+ for module in modules:
+ modName = cmds.getAttr(module + ".moduleName")
+ 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
+ specialCaseModules = ["ART_Leg_Standard", "ART_Arm_Standard"]
+ if className in specialCaseModules:
+ 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])
+
+ # get the class name from that module file (returns Modules.ART_Root.ART_Root for example)
+ moduleClass = getattr(mod, mod.className)
+ jmPath = mod.jointMover
+
+ # call functions to create network node, skeleton settings UI
+ moduleInst = moduleClass(self.rigUiInst, userSpecName)
+ 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", lock=False)
+ cmds.setAttr(networkNodeInst + ".side", "Right", type="string", lock=True)
+
+ # build the settings UI/joint mover/add to outliner
+ moduleInst.skeletonSettings_UI(userSpecName)
+ moduleInst.jointMover_Build(jmPath)
+ moduleInst.addJointMoverToOutliner()
+
+ # update the created joints attribute on the network node with the new names
+ prefix = str(self.prefix.text())
+ suffix = str(self.suffix.text())
+
+ if len(prefix) > 0:
+ if prefix.find("_") == -1:
+ prefix = prefix + "_"
+ if len(suffix) > 0:
+ if suffix.find("_") == -1:
+ suffix = "_" + suffix
+
+ createdBones = cmds.getAttr(networkNodeInst + ".Created_Bones")
+ createdBones = createdBones.split("::")
+
+ attrString = ""
+ for bone in createdBones:
+ if len(bone) > 1:
+ attrString += prefix + bone + suffix + "::"
+
+ cmds.setAttr(networkNodeInst + ".Created_Bones", lock=False)
+ cmds.setAttr(networkNodeInst + ".Created_Bones", attrString, type="string", lock=True)
+
+ # update the self.currentParent label and the parentModuleBone attr on the network node
+ moduleInst.currentParent.setText(parent)
+
+ cmds.setAttr(networkNodeInst + ".parentModuleBone", lock=False)
+ cmds.setAttr(networkNodeInst + ".parentModuleBone", parent, type="string", lock=True)
+
+ # find the current parent mover and its scale
+ if parent == "root":
+ mover = "root_mover"
+ offsetMover = "root_mover"
+
+ else:
+ # find the parent mover name to parent to
+ networkNodes = utils.returnRigModules()
+ mover = utils.findMoverNodeFromJointName(networkNodes, parent, False, True)
+ offsetMover = utils.findMoverNodeFromJointName(networkNodes, parent)
+
+ 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
+ childMover = utils.findOffsetMoverFromName(userSpecName)
+ riggingUtils.createBoneConnection(offsetMover, childMover, userSpecName)
+
+ globalMover = utils.findGlobalMoverFromName(userSpecName)
+ cmds.select(globalMover)
+ cmds.setToolTo("moveSuperContext")
+
+ utils.fitViewAndShade()
+ cmds.refresh(force=True)
+ moduleInst.pasteSettings()
+ moduleInst.aimMode(True)
+
+ # delete UI
+ cmds.deleteUI("ART_createMirrorModuleUI", wnd=True)
+
+ # update the mirrorModule setting
+ self.mirrorMod.setText(userSpecName)
+ name = cmds.getAttr(networkNode + ".moduleName")
+ 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()
+
+ self.rigUiInst.populateNetworkList()
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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
+ self.mirrorTransformations()
+ 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.
+
+ """
+
+ currentSelection = cmds.ls(sl=True)
+
+ # get the mirror module
+ networkNode = self.returnNetworkNode
+ mirrorModule = cmds.getAttr(networkNode + ".mirrorModule")
+ moduleName = cmds.getAttr(networkNode + ".moduleName")
+ parent = cmds.getAttr(networkNode + ".parentModuleBone")
+
+ # 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)
+
+ for attr in attrs:
+ 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)
+
+ cmds.select(clear=True)
+ 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)
+
+ # load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ self.style = f.read()
+ 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
+ joints = self.returnCreatedJoints
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+
+ for joint in joints:
+
+ 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()
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def buildRigCustom(self, textEdit, uiInst):
+ """
+ This method is what truly builds the rig for each module. It is implemented in the derived module class.
+
+ :param textEdit: The text edit in the buildProgressUI that we output information to.
+ :param uiInst: passed in instance of the buildProgressUI
+
+ """
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def deleteRig(self):
+ """
+ This method deletes all rigging for the module.
+
+ """
+
+ allNodes = cmds.ls("*")
+ for node in allNodes:
+ 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
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def updateSettingsUI(self):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def applyModuleChanges(self, moduleInst):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def addJointMoverToOutliner(self):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def updateOutliner(self):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def skinProxyGeo(self):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ 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.
+
+ """
+
+ networkNode = self.returnNetworkNode
+ cmds.setAttr(networkNode + ".aimMode", lock=False)
+ cmds.setAttr(networkNode + ".aimMode", state, lock=True)
+
+ self.aimMode_Setup(state)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def aimMode_Setup(self, state):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def setupModelPoseForRig(self):
+ """
+ This method is implemented in the derived module class.
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def matchModelPose(self):
+ """
+ This method is implemented in the derived module class.
+
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def pinModule(self, state):
+ """
+ This method is implemented in the derived module class.
+
+ """
+
+ pass
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def bakeOffsets(self):
+ """
+ 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]
+ constraints = []
+ locators = []
+
+ # 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 mover in offsetMovers:
+ for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
+ 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
+ for const in constraints:
+ cmds.delete(const)
+
+ for loc in locators:
+ cmds.delete(loc)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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
+ movers = self.returnJointMovers
+ globalMovers = movers[0]
+ shapes = []
+
+ for each in movers:
+ for mover in each:
+ 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 = []
+ for mover in globalMovers:
+ 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)
+ for node in allNodes:
+ 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
+ joints = self.returnCreatedJoints
+
+ # create mover name
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+
+ for joint in joints:
+
+ if cmds.objExists(joint + "_mover_offset"):
+ cmds.parentConstraint(joint + "_mover_offset", 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_offset"):
+ cmds.parentConstraint(self.name + "_" + jointBaseName + "_mover_offset", joint)
+
+ # lock joint movers
+ cmds.select("JointMover", hi=True)
+ jmNodes = cmds.ls(sl=True)
+ for node in jmNodes:
+ 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.
+
+ """
+ # get the joints created by this module
+ joints = self.returnCreatedJoints
+
+ # create mover name
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+
+ for joint in joints:
+ if cmds.objExists(joint + "_mover_offset"):
+ cmds.parentConstraint(joint + "_mover_offset", 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_offset"):
+ cmds.parentConstraint(self.name + "_" + jointBaseName + "_mover_offset", joint)
+
+ # 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
+ joints = self.returnCreatedJoints
+
+ # create mover name
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+
+ for joint in joints:
+ if cmds.objExists(joint + "_mover_offset"):
+ cmds.select(joint)
+ cmds.delete(constraints=True)
+
+ 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_offset"):
+ 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.select(self.name + "_mover_grp", hi=True)
+ allNodes = cmds.ls(sl=True)
+ for node in allNodes:
+ 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", 1)
+ cmds.lockNode(parent, lock=True)
+
+ # unlock mover group for this module and make invisible
+ cmds.lockNode(self.name + "_mover_grp", lock=False)
+ cmds.setAttr(self.name + "_mover_grp.v", lock=False)
+
+ 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)
+
+ # get the joints created by this module and remove the constraints
+ joints = self.returnCreatedJoints
+
+ # create mover name
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+
+ for joint in joints:
+ if cmds.objExists(joint + "_mover_offset"):
+ cmds.select(joint)
+ cmds.delete(constraints=True)
+
+ 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_offset"):
+ cmds.select(joint)
+ cmds.delete(constraints=True)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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
+ networkNode = self.returnNetworkNode
+
+ # 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).
+
+ """
+
+ # get the network node
+ networkNode = self.returnNetworkNode
+
+ # 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
+ networkNode = self.returnNetworkNode
+
+ # get the poseData
+ poseData = json.loads(cmds.getAttr(networkNode + ".rigPose"))
+
+ # find our part in the pose data
+ for data in poseData:
+ mover = data.get("mover")
+ 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.
+
+ """
+
+ # get movers
+ jointMovers = self.returnJointMovers
+
+ # separate mover lists
+ globalMovers = jointMovers[0]
+ offsetMovers = jointMovers[1]
+
+ # get the network node
+ networkNode = self.returnNetworkNode
+
+ # 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 network node
+ networkNode = self.returnNetworkNode
+
+ # 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")
+ rotate = data.get("rotate")
+
+ # if the mover exists, set the values
+ if cmds.objExists(mover):
+
+ # 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 network node
+ networkNode = self.returnNetworkNode
+
+ # 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).
+
+ 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: 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.
+
+ """
+
+ # load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ self.style = f.read()
+ f.close()
+
+ # create mover name
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+
+ 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:
+ jointName = self.name + "_" + jointName
+
+ # create a master vertical layout
+ mainLayout = QtWidgets.QVBoxLayout()
+ self.rigPose_advancedLayout.addLayout(mainLayout)
+
+ # create a label for the joint
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+ 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.setObjectName("blueButton")
+ button.setStyleSheet(self.style)
+
+ button.clicked.connect(partial(self.resetRigPose_Part, jointName + "_mover"))
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def updateBoneCount(self):
+ """
+ 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 cmds.window("ART_BoneCounterWin", exists=True):
+ 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)
+ button.update()
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ 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)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def importFBX(self, importMethod, character):
+ """
+ This method is implemented in the derived module class and defines how mocap is imported onto the rig controls.
+
+ :param importMethod: Whether or not the FBX is getting imported as FK, IK, Both, or None
+ :param character: The namespace of the rig.
+
+ """
+
+ 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
+ networkNode = self.returnRigNetworkNode
+ 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
+ for control in controls:
+ data = json.loads(cmds.getAttr(networkNode + "." + control))
+
+ # reset the attr on each control
+ nonZeroAttrs = ["scale", "globalScale", "scaleX", "scaleY", "scaleZ"]
+
+ try:
+ for each in data:
+ 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)
+
+ for attr in attrs:
+ 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.
+
+ """
+
+ # get namespace
+ networkNode = self.returnRigNetworkNode
+ if networkNode is None:
+ winParent = interfaceUtils.getMainWindow()
+ win = interfaceUtils.DialogMessage("Error", "This function does not work without a namespace.", [], 0,
+ winParent)
+ win.show()
+ return None
+
+ # list any attributes on the network node that contain "controls"
+ controls = cmds.listAttr(networkNode, st="*Controls")
+
+ returnControls = []
+
+ # 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:
+ for each in data:
+ returnControls.append(each)
+
+ return returnControls
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def selectRigControls(self):
+ """
+ This method calls on getControls to return a list of the controls and the selects them.
+
+ """
+
+ controls = self.getControls()
+
+ # get namespace
+ networkNode = self.returnRigNetworkNode
+ characterNode = cmds.listConnections(networkNode + ".parent")[0]
+ namespace = cmds.getAttr(characterNode + ".namespace")
+
+ for control in controls:
+ 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")
+ for module in modules:
+ attrs = cmds.listAttr(module)
+ if "rigModules" in attrs:
+ 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
+
+ """
+
+ modules = cmds.ls(type="network")
+ returnMods = []
+ for module in modules:
+ attrs = cmds.listAttr(module)
+ 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:
+ attrs = cmds.listAttr(node)
+ 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 = []
+ networkNodes = cmds.ls(type="network")
+ for node in networkNodes:
+ attrs = cmds.listAttr(node)
+ if "moduleName" in attrs:
+ if cmds.getAttr(node + ".moduleName") == self.name:
+ 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.
+
+ """
+
+ networkNode = self.returnNetworkNode
+ joints = cmds.getAttr(networkNode + ".Created_Bones")
+
+ splitJoints = joints.split("::")
+ createdJoints = []
+
+ for bone in splitJoints:
+ if bone != "":
+ createdJoints.append(bone)
+
+ 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.
+
+ """
+
+ name = self.groupBox.title()
+
+ # 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.
+
+ """
+
+ # get network node
+ networkNode = self.returnNetworkNode
+ mirrorModule = cmds.getAttr(networkNode + ".mirrorModule")
+
+ # find instance through rig UI inst
+ for inst in self.rigUiInst.moduleInstances:
+ networkNode = inst.returnNetworkNode
+ 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.
+
+ """
+
+ prefix = None
+ suffix = None
+
+ networkNode = self.returnNetworkNode
+ baseName = cmds.getAttr(networkNode + ".baseName")
+ splitName = self.name.split(baseName)
+ if splitName[0] != '':
+ prefix = splitName[0]
+ if splitName[1] != '':
+ suffix = splitName[1]
+ return [prefix, suffix]
diff --git a/Core/Scripts/System/ART_Settings.py b/Core/Scripts/System/ART_Settings.py
new file mode 100644
index 0000000..ecc23d2
--- /dev/null
+++ b/Core/Scripts/System/ART_Settings.py
@@ -0,0 +1,295 @@
+from ThirdParty.Qt import QtGui, QtCore, QtWidgets
+
+#maya 2016< maya2017> compatability
+try:
+ import shiboken as shiboken
+except:
+ import shiboken2 as shiboken
+
+from functools import partial
+import maya.cmds as cmds
+import os, json
+import System.utils as utils
+
+
+
+
+def getMainWindow():
+ 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)
+
+
+
+windowTitle = "ART_Settings"
+windowObject = "pyArtSettingsWin"
+
+
+
+class ART_Settings(QtWidgets.QMainWindow):
+
+ def __init__(self, parent = None):
+
+ super(ART_Settings, self).__init__(parent)
+
+ #get the directory path of the tools
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ self.toolsPath = settings.value("toolsPath")
+ self.scriptPath = settings.value("scriptPath")
+ self.iconsPath = settings.value("iconPath")
+ self.projPath = settings.value("projectPath")
+
+ #build the UI
+ self.buildSettingsUi()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def buildSettingsUi(self):
+
+ #fonts
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setBold(True)
+
+ fontSmall = QtGui.QFont()
+ fontSmall.setPointSize(9)
+ fontSmall.setBold(True)
+
+
+ #images
+ frameBackground = os.path.normcase(os.path.join(self.iconsPath, "System/field_background.png"))
+ if frameBackground.partition("\\")[2] != "":
+ frameBackground = frameBackground.replace("\\", "/")
+
+ imageBkgrd = os.path.normcase(os.path.join(self.iconsPath, "System/toolbar_background.png"))
+ if imageBkgrd.partition("\\")[2] != "":
+ imageBkgrd = imageBkgrd.replace("\\", "/")
+
+ imageBtnBkrd = os.path.normcase(os.path.join(self.iconsPath, "System/blue_field_background.png"))
+ if imageBtnBkrd.partition("\\")[2] != "":
+ imageBtnBkrd = imageBtnBkrd.replace("\\", "/")
+
+
+
+ #size policies
+ mainSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+
+ #create the main widget
+ self.mainWidget = QtWidgets.QWidget()
+ self.mainWidget.setStyleSheet("background-color: rgb(0, 0, 0);, color: rgb(0,0,0);")
+ self.setCentralWidget(self.mainWidget)
+
+ #set qt object name
+ self.setObjectName(windowObject)
+ self.setWindowTitle(windowTitle)
+ self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+
+ #create the mainLayout for the rig creator UI
+ self.layout = QtWidgets.QVBoxLayout(self.mainWidget)
+
+ self.resize(600, 260)
+ self.setSizePolicy(mainSizePolicy)
+ self.setMinimumSize(QtCore.QSize( 600, 260 ))
+ self.setMaximumSize(QtCore.QSize( 600, 260 ))
+
+ #create the QFrame
+ self.frame = QtWidgets.QFrame()
+ self.layout.addWidget(self.frame)
+ self.widgetLayout = QtWidgets.QVBoxLayout(self.frame)
+
+ #info page styling
+ self.frame.setStyleSheet("background-image: url(" + imageBkgrd + ");")
+
+
+ #MayaTools/Core : Sccipts, icons, jointmover, etc
+ #MayaTools/Projects: actual project files (animation rigs, thumbnails, poses, etc)
+
+ #location
+ self.locationLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.locationLayout)
+
+ #location -> label
+ label = QtWidgets.QLabel("Tools Location: ")
+ self.locationLayout.addWidget(label)
+ label.setFont(font)
+ label.setMinimumWidth(150)
+
+ #location -> line edit
+ path = utils.returnFriendlyPath(self.toolsPath)
+ self.locationPath = QtWidgets.QLineEdit(path)
+ self.locationLayout.addWidget(self.locationPath)
+
+
+
+ self.locationPath.setStyleSheet("background-image: url(" + frameBackground + "); background-color: rgb(25,175,255);")
+ self.locationPath.setMinimumHeight(35)
+
+ #location -> browse button
+ self.locationBrowse = QtWidgets.QPushButton()
+ self.locationLayout.addWidget(self.locationBrowse)
+
+ self.locationBrowse.setMinimumSize(35,35)
+ self.locationBrowse.setMaximumSize(35, 35)
+ btnBackground = utils.returnNicePath(self.iconsPath, "System/fileBrowse.png")
+ self.locationBrowse.setStyleSheet("background-image: url(" + btnBackground + ");")
+ self.locationBrowse.clicked.connect(partial(self.browse, self.locationPath))
+
+
+ #scripts folder
+ self.scriptsLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.scriptsLayout)
+
+ #scripts -> label
+ label = QtWidgets.QLabel("Scripts: ")
+ self.scriptsLayout.addWidget(label)
+ label.setFont(fontSmall)
+ label.setMinimumWidth(150)
+
+ #scripts -> line edit
+ path = utils.returnFriendlyPath(self.scriptPath)
+ self.scriptsPath = QtWidgets.QLineEdit(path)
+ self.scriptsLayout.addWidget(self.scriptsPath)
+
+ self.scriptsPath.setStyleSheet("background-image: url(" + frameBackground + "); background-color: rgb(25,175,255);")
+ self.scriptsPath.setMinimumHeight(35)
+
+ #scripts -> browse button
+ self.scriptsBrowse = QtWidgets.QPushButton()
+ self.scriptsLayout.addWidget(self.scriptsBrowse)
+
+ self.scriptsBrowse.setMinimumSize(35,35)
+ self.scriptsBrowse.setMaximumSize(35, 35)
+ self.scriptsBrowse.setStyleSheet("background-image: url(" + btnBackground + ");")
+ self.scriptsBrowse.clicked.connect(partial(self.browse, self.scriptsPath))
+
+ #icons folder
+ self.iconsLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.iconsLayout)
+
+ #icons -> label
+ label = QtWidgets.QLabel("Icons: ")
+ self.iconsLayout.addWidget(label)
+ label.setFont(fontSmall)
+ label.setMinimumWidth(150)
+
+ #icons -> line edit
+ path = utils.returnFriendlyPath(self.iconsPath)
+ self.iconPath = QtWidgets.QLineEdit(path)
+ self.iconsLayout.addWidget(self.iconPath)
+
+ self.iconPath.setStyleSheet("background-image: url(" + frameBackground + "); background-color: rgb(25,175,255);")
+ self.iconPath.setMinimumHeight(35)
+
+ #icons -> browse button
+ self.iconsBrowse = QtWidgets.QPushButton()
+ self.iconsLayout.addWidget(self.iconsBrowse)
+
+ self.iconsBrowse.setMinimumSize(35,35)
+ self.iconsBrowse.setMaximumSize(35, 35)
+ self.iconsBrowse.setStyleSheet("background-image: url(" + btnBackground + ");")
+ self.iconsBrowse.clicked.connect(partial(self.browse, self.iconsPath))
+
+ #projects folder
+ self.projectsLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.projectsLayout)
+
+ #projects -> label
+ label = QtWidgets.QLabel("Projects: ")
+ self.projectsLayout.addWidget(label)
+ label.setFont(fontSmall)
+ label.setMinimumWidth(150)
+
+ #projects -> line edit
+ path = utils.returnFriendlyPath(self.projPath)
+ self.projectsPath = QtWidgets.QLineEdit(path)
+ self.projectsLayout.addWidget(self.projectsPath)
+
+ self.projectsPath.setStyleSheet("background-image: url(" + frameBackground + "); background-color: rgb(25,175,255);")
+ self.projectsPath.setMinimumHeight(35)
+
+ #projects -> browse button
+ self.projectsBrowse = QtWidgets.QPushButton()
+ self.projectsLayout.addWidget(self.projectsBrowse)
+
+ self.projectsBrowse.setMinimumSize(35,35)
+ self.projectsBrowse.setMaximumSize(35, 35)
+ self.projectsBrowse.setStyleSheet("background-image: url(" + btnBackground + ");")
+ self.projectsBrowse.clicked.connect(partial(self.browse, self.projectsPath))
+
+ #Save button
+ self.saveChangesBtn = QtWidgets.QPushButton("Save Changes")
+ self.widgetLayout.addWidget(self.saveChangesBtn)
+ self.saveChangesBtn.setFont(font)
+ self.saveChangesBtn.setMinimumHeight(35)
+ self.saveChangesBtn.setStyleSheet("background-image: url(" + imageBtnBkrd + ");background-color: rgb(25, 175, 255);")
+ self.saveChangesBtn.clicked.connect(partial(self.saveSettings))
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def browse(self, lineEdit):
+
+ try:
+ newPath = cmds.fileDialog2(dir = self.toolsPath, fm = 3)[0]
+ newPath = utils.returnFriendlyPath(newPath)
+ lineEdit.setText(newPath)
+
+ except:
+ pass #in case user cancels on Maya's browse dialog
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def saveSettings(self):
+
+
+ #get data from ui
+ mayaToolsDir = self.locationPath.text()
+ scriptDir = self.scriptsPath.text()
+ iconsDir = self.iconPath.text()
+ projectsDir = self.projectsPath.text()
+
+ #save data
+
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ settings.setValue("toolsPath", mayaToolsDir)
+ settings.setValue("scriptPath", scriptDir)
+ settings.setValue("iconPath", iconsDir)
+ settings.setValue("projectPath", projectsDir)
+
+
+ #Give message regarding data being saved, but it won't take effect until Maya is restarted.
+ cmds.confirmDialog(title = "Settings Saved", message = "Please close Maya and reopen in order to have these settings take effect.")
+
+ #close UI
+ if cmds.window("pyArtSettingsWin", exists = True):
+ cmds.deleteUI("pyArtSettingsWin", wnd = True)
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def run():
+
+ if cmds.window("pyArtSettingsWin", exists = True):
+ cmds.deleteUI("pyArtSettingsWin", wnd = True)
+
+ gui = ART_Settings(getMainWindow())
+ gui.show()
+
diff --git a/Core/Scripts/System/ART_StripFbxNamespace.py b/Core/Scripts/System/ART_StripFbxNamespace.py
new file mode 100644
index 0000000..f223c58
--- /dev/null
+++ b/Core/Scripts/System/ART_StripFbxNamespace.py
@@ -0,0 +1,40 @@
+import sys
+import maya.standalone as std
+std.initialize(name = 'python')
+import maya.cmds as cmds
+import maya.mel as mel
+filename = sys.argv[1]
+
+def stripNamespace(filename):
+
+ try:
+ #open the file
+ cmds.loadPlugin("fbxmaya.mll")
+ string = "FBXImportMode -v \"add\";"
+ string += "FBXImport -file \"" + filename + "\""
+ string += "FBXImportFillTimeline -v true"
+ mel.eval(string)
+
+
+ #remove the namespace
+ cmds.namespace(setNamespace = "::")
+ currentNamespaces = cmds.namespaceInfo(listOnlyNamespaces = True)
+
+ restricted = ['UI', 'shared']
+
+ for namespace in currentNamespaces:
+ if namespace not in restricted:
+ cmds.namespace(mv = (':' + namespace, ':'), force = True)
+ cmds.namespace(removeNamespace = namespace)
+
+ #re-export the file
+ mel.eval("FBXExport -f \""+ filename +"\"")
+
+ #exit
+ std.uninitialize()
+
+ except Exception, e:
+ sys.stderr.write(str(e))
+ sys.exit(-1)
+
+stripNamespace(filename) \ No newline at end of file
diff --git a/Core/Scripts/System/ART_Updater.py b/Core/Scripts/System/ART_Updater.py
new file mode 100644
index 0000000..5cc4b61
--- /dev/null
+++ b/Core/Scripts/System/ART_Updater.py
@@ -0,0 +1,600 @@
+from ThirdParty.Qt import QtGui, QtCore, QtWidgets
+import maya.cmds as cmds
+import os, json
+import utils, math, traceback, urllib2, zipfile
+import shutil, errno, stat, base64
+import System.git_utils as git
+
+#maya 2016< 2017> compatability
+try:
+ import shiboken as shiboken
+except:
+ import shiboken2 as shiboken
+
+
+
+def getMainWindow():
+ 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)
+
+
+
+windowTitle = "ARTv2: Check For Updates"
+windowObject = "pyArtUpdaterWin"
+
+
+
+class ART_Updater(QtWidgets.QMainWindow):
+
+ def __init__(self, parent = None):
+
+ super(ART_Updater, self).__init__(parent)
+
+ #get the directory path of the tools
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+
+ self.toolsPath = settings.value("toolsPath")
+ self.scriptPath = settings.value("scriptPath")
+ self.iconsPath = settings.value("iconPath")
+ self.projPath = settings.value("projectPath")
+
+ #get github credentials
+ self.credentials = git.getGitCreds()
+ if self.credentials == None:
+ git.gitCredsUI(self)
+ self.credentials = git.getGitCreds()
+
+
+
+ #build the UI
+ self.buildSettingsUi()
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def buildSettingsUi(self):
+
+ #fonts
+ self.font = QtGui.QFont()
+ self.font.setPointSize(10)
+ self.font.setBold(False)
+
+ self.fontSmall = QtGui.QFont()
+ self.fontSmall .setPointSize(9)
+ self.fontSmall .setBold(False)
+
+ self.titleFont = QtGui.QFont()
+ self.titleFont.setPointSize(40)
+ self.titleFont.setBold(True)
+
+
+ #images
+ frameBackground = os.path.normcase(os.path.join(self.iconsPath, "System/field_background.png"))
+ if frameBackground.partition("\\")[2] != "":
+ frameBackground = frameBackground.replace("\\", "/")
+
+ imageBkgrd = os.path.normcase(os.path.join(self.iconsPath, "System/toolbar_background.png"))
+ if imageBkgrd.partition("\\")[2] != "":
+ imageBkgrd = imageBkgrd.replace("\\", "/")
+
+ imageBtnBkrd = os.path.normcase(os.path.join(self.iconsPath, "System/blue_field_background.png"))
+ if imageBtnBkrd.partition("\\")[2] != "":
+ imageBtnBkrd = imageBtnBkrd.replace("\\", "/")
+
+ #load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ self.style = f.read()
+ f.close()
+
+
+ self.setStyleSheet(self.style)
+
+ #size policies
+ mainSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+
+ #create the main widget
+ self.mainWidget = QtWidgets.QWidget()
+ self.mainWidget.setStyleSheet(self.style)
+ self.mainWidget.setStyleSheet("background-color: rgb(0, 0, 0);, color: rgb(0,0,0);")
+ self.setCentralWidget(self.mainWidget)
+
+ #set qt object name
+ self.setObjectName(windowObject)
+ self.setWindowTitle(windowTitle)
+ self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+
+ #create the mainLayout for the rig creator UI
+ self.layout = QtWidgets.QVBoxLayout(self.mainWidget)
+
+ self.resize(600, 300)
+ self.setSizePolicy(mainSizePolicy)
+ self.setMinimumSize(QtCore.QSize( 600, 300 ))
+ self.setMaximumSize(QtCore.QSize( 600, 300 ))
+
+
+ #create the QFrame
+ self.frame = QtWidgets.QFrame()
+ self.layout.addWidget(self.frame)
+ self.widgetLayout = QtWidgets.QVBoxLayout(self.frame)
+
+ #info page styling
+ self.frame.setStyleSheet("background-image: url(" + imageBkgrd + ");")
+
+
+ #detailed information
+ self.infoText = QtWidgets.QTextEdit()
+ self.infoText.acceptRichText()
+ self.infoText.setStyleSheet("background-color: rgb(120,120,120); background-image: url(" + frameBackground + ");")
+ self.widgetLayout.addWidget(self.infoText)
+ self.infoText.setMinimumSize(QtCore.QSize(550,170))
+ self.infoText.setMaximumSize(QtCore.QSize(550,170))
+ self.infoText.setReadOnly(True)
+ self.infoText.setAutoFormatting(QtWidgets.QTextEdit.AutoBulletList)
+ self.infoText.setLineWrapMode(QtWidgets.QTextEdit.WidgetWidth)
+
+
+ #progress bar
+ self.progressBar = QtWidgets.QProgressBar()
+ self.progressBar.setStyleSheet(self.style)
+ self.progressBar.setMinimumSize(QtCore.QSize(550, 25))
+ self.progressBar.setMaximumSize(QtCore.QSize(550, 25))
+ self.widgetLayout.addWidget(self.progressBar)
+
+ #button bar
+ self.buttonLayout = QtWidgets.QHBoxLayout()
+ self.widgetLayout.addLayout(self.buttonLayout)
+
+ self.cancelButton = QtWidgets.QPushButton("Close")
+ self.buttonLayout.addWidget(self.cancelButton)
+ self.cancelButton.setStyleSheet(self.style)
+ self.cancelButton.setObjectName("blueButton")
+ self.cancelButton.clicked.connect(self.cancel)
+
+ self.updateButton = QtWidgets.QPushButton("Update")
+ self.buttonLayout.addWidget(self.updateButton)
+ self.updateButton.setStyleSheet(self.style)
+ self.updateButton.setObjectName("blueButton")
+ self.updateButton.clicked.connect(self.downloadUpdates)
+
+ if self.credentials != None:
+ self.getInfo()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def getInfo(self):
+
+ #need to eventually swap this with the real file
+ request = urllib2.Request("https://raw.githubusercontent.com/epicernst/Test/master/ARTv2_VersionInfo.json")
+ base64String = base64.encodestring('%s:%s' % (self.credentials[0], self.credentials[1])).replace('\n', '')
+ request.add_header("Authorization", "Basic %s" % base64String)
+ try:
+ result = urllib2.urlopen(request)
+ except Exception, e:
+ self.infoText.setTextColor(QtGui.QColor(249,241,12))
+ self.infoText.append(str(e))
+ self.infoText.append("Your Github login credentials may be invalid or you do not have access to this repo.\n\n")
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ settings.remove("gitUser")
+ settings.remove("gitPass")
+
+ return
+
+
+ content = json.loads(result.read())
+ versions = content.get("versions")
+
+ newFeatures = []
+ majorFixes = []
+ minorFixes = []
+
+ for version in versions:
+ data = versions.get(version)
+ for key in data:
+ if key == "New":
+ info = data.get(key)
+ for each in info:
+ newFeatures.append(each)
+
+ if key == "Critical":
+ info = data.get(key)
+ for each in info:
+ majorFixes.append(each)
+
+ if key == "Minor":
+ info = data.get(key)
+ for each in info:
+ minorFixes.append(each)
+
+
+ #Compare local version to latest
+ latestVersion = content.get("latest version")
+ localVersion = self.checkLocalVersion()
+
+ if float(latestVersion) > float(localVersion):
+ self.infoText.append("You are not up to date!\n")
+ self.infoText.append("Latest Version: " + str(content.get("latest version")))
+ self.infoText.append("Local Version: " + str(localVersion))
+
+ self.infoText.append("\n")
+
+
+ #release notes
+ self.infoText.setFont(self.titleFont)
+ self.infoText.setTextColor(QtGui.QColor(48,255,0))
+ self.infoText.setAlignment(QtCore.Qt.AlignCenter)
+ self.infoText.append("|| NEW FEATURES ||")
+
+ self.infoText.setFont(self.fontSmall)
+ self.infoText.setAlignment(QtCore.Qt.AlignLeft)
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ for feature in newFeatures:
+ self.infoText.append(" *" + str(feature))
+
+ self.infoText.append("\n")
+ self.infoText.setFont(self.titleFont)
+ self.infoText.setTextColor(QtGui.QColor(249,168,12))
+ self.infoText.setAlignment(QtCore.Qt.AlignCenter)
+ self.infoText.append("|| MAJOR FIXES ||")
+
+ self.infoText.setFont(self.fontSmall)
+ self.infoText.setAlignment(QtCore.Qt.AlignLeft)
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ for fix in majorFixes:
+ self.infoText.append(" *" + str(fix))
+
+ self.infoText.append("\n")
+ self.infoText.setFont(self.titleFont)
+ self.infoText.setTextColor(QtGui.QColor(249,241,12))
+ self.infoText.setAlignment(QtCore.Qt.AlignCenter)
+ self.infoText.append("|| MINOR FIXES ||")
+
+ self.infoText.setFont(self.fontSmall)
+ self.infoText.setAlignment(QtCore.Qt.AlignLeft)
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ for each in minorFixes:
+ self.infoText.append(" *" + str(each))
+
+ self.infoText.moveCursor(QtGui.QTextCursor.Start)
+
+ else:
+ self.infoText.append("You are up-to-date!")
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def checkLocalVersion(self):
+
+ mayaModDir = os.environ["home"] + "/maya/modules/"
+ if cmds.about(os = True) == "mac":
+ mayaModDir = os.environ["home"] + "/Library/Preferences/Autodesk/maya/modules/"
+
+ modName = "ARTv2.mod"
+ modFile = mayaModDir + modName
+
+ modFileObj = file(modFile, mode='r')
+ lines = modFileObj.readlines()
+ modFileObj.close()
+
+ localVersion = float(lines[0].split(" ")[2])
+
+ return localVersion
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def checkLatestVersion(self):
+
+ info = git.getGitCreds()
+ if info != None:
+ user = info[0]
+ password = info[1]
+
+ #need to eventually swap this with the real file
+ request = urllib2.Request("https://raw.githubusercontent.com/epicernst/Test/master/ARTv2_VersionInfo.json")
+ base64String = base64.encodestring('%s:%s' % (user, password)).replace('\n', '')
+ request.add_header("Authorization", "Basic %s" % base64String)
+ result = urllib2.urlopen(request)
+ content = json.loads(result.read())
+
+ latestVersion = content.get("latest version")
+ return latestVersion
+
+ else:
+ self.invalidCreds()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def updateLocalVersion(self):
+
+ info = git.getGitCreds()
+ if info != None:
+ mayaModDir = cmds.internalVar(uad = True)
+ mayaModDir = os.path.join(mayaModDir, "modules")
+ mayaModDir = utils.returnFriendlyPath(os.path.join(mayaModDir, "ARTv2.mod"))
+
+ if os.path.exists(mayaModDir):
+ f = open(mayaModDir, 'r')
+ line = f.readline()
+ f.close()
+
+ if line.find("+ ARTv2 ") != -1:
+ version = line.partition("+ ARTv2 ")[2].partition(" ")[0]
+ latest = self.checkLatestVersion()
+ newline = line.replace(str(version), str(latest))
+
+ f = open(mayaModDir, 'w')
+ f.write(newline)
+ f.close()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def findReadOnlyFiles(self, fullPath, listOfFiles):
+
+ #list file contents
+ contents = os.listdir(fullPath)
+ for each in contents:
+ if os.path.isfile(os.path.join(fullPath, each)):
+ fileAttr = os.stat(os.path.join(fullPath, each)).st_mode
+ if not fileAttr & stat.S_IWRITE:
+ try:
+ os.chmod(os.path.join(fullPath, each), stat.S_IWRITE)
+ except Exception, e:
+ listOfFiles.append([each, e])
+ if os.path.isdir(os.path.join(fullPath, each)):
+ self.findReadOnlyFiles(os.path.join(fullPath, each), listOfFiles)
+
+ return listOfFiles
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def downloadUpdates(self):
+
+ info = git.getGitCreds()
+ if info != None:
+ user = info[0]
+ password = info[1]
+
+ else:
+ self.invalidCreds()
+ return
+
+ base64String = base64.encodestring('%s:%s' % (user, password)).replace('\n', '')
+ opener = urllib2.build_opener()
+ opener.addheaders = [("Authorization", "Basic %s" % base64String)]
+ response = opener.open("https://github.com/epicernst/Test/archive/master.zip")
+
+ zipContent = response.read()
+
+ filename = os.path.basename("https://github.com/epicernst/Test/blob/master/master.zip")
+ path = os.environ["home"]
+ filePath = os.path.join(path, filename)
+
+ with open(filePath, 'w') as f:
+ f.write(zipContent)
+
+
+ masterDir = os.path.dirname(filePath)
+ mayaToolsZip = masterDir
+
+
+ with zipfile.ZipFile(filePath, 'r') as zfile:
+ for name in zfile.namelist():
+ if name.find(".zip") != -1:
+ mayaToolsZip = os.path.join(mayaToolsZip, name)
+ zfile.extractall(masterDir)
+
+
+
+ baseToolsDir = os.path.dirname(self.toolsPath)
+ wholeCopy = False
+
+ fileIssues = []
+ with zipfile.ZipFile(mayaToolsZip) as zf:
+ removeDirs = ["MayaTools/Core/Scripts/", "MayaTools/Core/Icons/", "MayaTools/Core/JointMover/", "MayaTools/plug-ins/"]
+
+ #set progress bar range
+ self.progressBar.setMaximum(len(removeDirs) + 1)
+ self.progressBar.setValue(0)
+
+ for dir in removeDirs:
+ fullPath = os.path.normpath(os.path.join(baseToolsDir, dir))
+
+ #list file contents
+ readOnlyFiles = self.findReadOnlyFiles(fullPath, fileIssues)
+
+ #if readOnlyFiles is empty
+ if len(readOnlyFiles) == 0:
+
+ #make a back-up of local versions
+ self.infoText.setTextColor(QtGui.QColor(0,0,0))
+ self.infoText.append("\n########################################################\n")
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ self.infoText.append("Creating Backup of current version.. " + str(dir))
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+
+ versionNumber = self.checkLocalVersion()
+ backupDir = os.path.join(os.path.dirname(self.toolsPath), "ARTv2/Backups")
+ backupDir = os.path.normpath(os.path.join(backupDir, str(versionNumber)))
+ printDir = backupDir
+ backupDir = utils.returnFriendlyPath(backupDir)
+ if not os.path.exists(backupDir):
+ os.makedirs(backupDir)
+
+ fullPath = utils.returnFriendlyPath(fullPath)
+
+ try:
+ shutil.move(fullPath, backupDir)
+ self.infoText.append(" Backups created in " + str(printDir))
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+ except Exception, e:
+ self.infoText.setTextColor(QtGui.QColor(249,168,12))
+ self.infoText.append(str(e))
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ wholeCopy = True
+
+ #extract zip file directory to original location
+ if wholeCopy == False:
+ for name in zf.namelist():
+ for each in removeDirs:
+ if name.find(each)!= -1:
+ #extract directly to the base location
+ try:
+ zf.extract(name, baseToolsDir)
+
+ except Exception, e:
+ self.infoText.setTextColor(QtGui.QColor(249,168,12))
+ self.infoText.append(str(e))
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ wholeCopy = True
+ self.infoText.append(" Extracted updated files to " + str(dir))
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+
+ #report on operations
+ value = self.progressBar.value()
+ self.progressBar.setValue(value + 1)
+
+
+
+
+ #if readOnlyFiles is not empty
+ else:
+ wholeCopy = True
+ if len(readOnlyFiles) > 0:
+ self.infoText.append("The following files were marked as read-only and could not be updated:")
+ for file in readOnlyFiles:
+ self.infoText.append(" " + str(file))
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+
+ if wholeCopy:
+ #report issues in UI
+ self.infoText.setTextColor(QtGui.QColor(249,168,12))
+ self.infoText.append("Could not apply updates automatically.")
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+
+
+ #extract all to an Update folder
+ version = self.checkLatestVersion()
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+ updateDir = os.path.join(self.toolsPath, "Update_" + str(version))
+ if not os.path.exists(updateDir):
+ os.makedirs(updateDir)
+
+ self.infoText.append("Extracting updated files to:\n " + str(updateDir))
+ try:
+ zf.extractall(updateDir)
+ except Exception, e:
+ self.infoText.setTextColor(QtGui.QColor(249,168,12))
+ self.infoText.append("Operation Failed")
+ self.infoText.append(str(e))
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+
+ #report on operation
+ self.infoText.append("Update Extracted. Since the automatic operation failed, you will need to manually integrate and apply the updates.")
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+
+ else:
+ self.infoText.setTextColor(QtGui.QColor(48,255,0))
+ self.infoText.append("\n\nUpdate Operation Completed!")
+ self.infoText.append("You must restart Maya to have updates applied.")
+ self.infoText.moveCursor(QtGui.QTextCursor.End)
+
+
+ #delete zipFile
+ try:
+ os.remove(filePath)
+
+ for each in os.listdir(os.path.dirname(mayaToolsZip)):
+ path = os.path.dirname(mayaToolsZip)
+ path = os.path.join(path, each)
+ os.remove(path)
+
+ os.chmod(os.path.dirname(mayaToolsZip), stat.S_IWRITE)
+ shutil.rmtree(os.path.dirname(mayaToolsZip))
+
+ except Exception, e:
+ self.infoText.append("Unable to clean up temporary files..")
+ self.infoText.append(str(e))
+
+ value = self.progressBar.value()
+ self.progressBar.setValue(value + 1)
+
+ #update .mod file with latest version #
+ self.updateLocalVersion()
+
+
+
+
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def cancel(self):
+ self.close()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def invalidCreds(self):
+
+ self.infoText.setTextColor(QtGui.QColor(249,241,12))
+ self.infoText.append("You have either not setup your Github credentials under Settings, or your github account is not linked with your Epic Games account.")
+ self.infoText.append("For more information on linking your github and Epic Games accounts, see:\n")
+ self.infoText.append("https://www.unrealengine.com/ue4-on-github")
+ self.infoText.setTextColor(QtGui.QColor(255,255,255))
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def run():
+
+ if cmds.window("pyArtUpdaterWin", exists = True):
+ cmds.deleteUI("pyArtUpdaterWin", wnd = True)
+
+ gui = ART_Updater(getMainWindow())
+ gui.show() \ No newline at end of file
diff --git a/Core/Scripts/System/__init__.py b/Core/Scripts/System/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Core/Scripts/System/__init__.py
diff --git a/Core/Scripts/System/git_utils.py b/Core/Scripts/System/git_utils.py
new file mode 100644
index 0000000..e24c769
--- /dev/null
+++ b/Core/Scripts/System/git_utils.py
@@ -0,0 +1,78 @@
+"""
+Author: Jeremy Ernst
+
+This module has utility functions for dealing with github interactions.
+"""
+
+from functools import partial
+
+from ThirdParty.Qt import QtCore, QtWidgets
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def getGitCreds():
+ """
+ Get the github credentials stored in the QSettings
+
+ :return: github username and password
+ """
+
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+
+ user = settings.value("gitUser")
+ password = settings.value("gitPass")
+
+ if user is not None and password is not None:
+ return [user, password]
+ else:
+ return None
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def gitWriteCreds(username, password, ui):
+ """
+ Set the QSettings values for the username and password with the supplied information.
+
+ :param username: user-entered github username
+ :param password: user-entered github password
+ :param ui: instance of UI where use enters above information
+ """
+
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ settings.setValue("gitUser", username.text())
+ settings.setValue("gitPass", password.text())
+ ui.close()
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def gitCredsUI(parent):
+ """
+ Create an interface that allows user to enter github username and password.
+
+ :param parent: interface that this interface will be a child of.
+ """
+
+ credsDialog = QtWidgets.QDialog(parent)
+ credsDialog.setWindowTitle("Github Credentials")
+ credsDialog.setMinimumSize(QtCore.QSize(200, 120))
+ credsDialog.setMaximumSize(QtCore.QSize(200, 120))
+
+ layout = QtWidgets.QVBoxLayout(credsDialog)
+ userName = QtWidgets.QLineEdit()
+ userName.setPlaceholderText("Github User Name..")
+ layout.addWidget(userName)
+
+ password = QtWidgets.QLineEdit()
+ password.setPlaceholderText("Github Password..")
+ layout.addWidget(password)
+ password.setEchoMode(QtWidgets.QLineEdit.Password)
+
+ confirmButton = QtWidgets.QPushButton("Confirm")
+ layout.addWidget(confirmButton)
+ confirmButton.setObjectName("blueButton")
+ confirmButton.clicked.connect(partial(gitWriteCreds, userName, password, credsDialog))
+
+ credsDialog.exec_()
diff --git a/Core/Scripts/System/interfaceUtils.py b/Core/Scripts/System/interfaceUtils.py
new file mode 100644
index 0000000..575a86b
--- /dev/null
+++ b/Core/Scripts/System/interfaceUtils.py
@@ -0,0 +1,645 @@
+# standard imports
+import os
+from stat import S_IWUSR, S_IREAD
+
+import maya.cmds as cmds
+
+import utils
+from ThirdParty.Qt import QtGui, QtCore, QtWidgets
+
+# maya 2016< maya2017> compatability
+try:
+ import shiboken as shiboken
+except:
+ import shiboken2 as shiboken
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def getMainWindow():
+ 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)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def writeQSS(filePath):
+ # this function takes the qss file given, and finds and replaces any image path URLs using the user's settings for
+ # the icons path and changes the file on disk
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ scriptsPath = settings.value("scriptPath")
+ scriptsPath = utils.returnFriendlyPath(scriptsPath)
+ iconPath = settings.value("iconPath")
+ iconPath = utils.returnFriendlyPath(iconPath)
+
+ f = open(filePath, "r")
+ lines = f.readlines()
+ f.close()
+
+ newLines = []
+ for line in lines:
+ if line.find("url(") != -1:
+ oldPath = line.partition("(")[2].rpartition("/")[0]
+ replacePath = utils.returnNicePath(iconPath, "System")
+
+ newLine = line.replace(oldPath, replacePath)
+ newLines.append(newLine)
+ else:
+ newLines.append(line)
+
+ os.chmod(filePath, S_IWUSR | S_IREAD)
+ f = open(filePath, "w")
+ for line in newLines:
+ f.write(line)
+ f.close()
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def addTextToButton(text, parent, centered=True, top=False, bottom=False):
+ text = QtWidgets.QGraphicsSimpleTextItem(text, parent)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setPointSize(12)
+
+ text.setFont(font)
+ textPos = parent.boundingRect().center()
+ textRect = text.boundingRect()
+ parentRect = parent.boundingRect()
+
+ if centered:
+ text.setPos(textPos.x() - textRect.width() / 2, textPos.y() - textRect.height() / 2)
+
+ if top:
+ text.setPos(textPos.x() - textRect.width() / 2, textPos.y() - (parentRect.height() / 2 + textRect.height()))
+
+ if bottom:
+ text.setPos(textPos.x() - textRect.width() / 2, textPos.y() + (parentRect.height() / 2))
+
+ return text
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class progressDialog(object):
+ '''
+ range is a tuple (min,max)
+ example:
+ myBar = progressDialog((0,100000), label="Exporting weights")
+ for i in range(0,100000):
+ myBar.setValue(i)
+ '''
+
+ def __init__(self, range, label='Doin Stuff..', freq=10):
+ self.rangeMin, self.rangeMax, self.freq = range[0], range[1], freq
+ self.bar = QtWidgets.QProgressDialog(label, None, self.rangeMin, self.rangeMax)
+ self.bar.setWindowModality(QtCore.Qt.WindowModal)
+ self.bar.autoClose()
+
+ def setValue(self, val):
+ self.bar.show()
+ QtWidgets.QApplication.processEvents()
+ if val % self.freq == 0 or (val + 1) == self.rangeMax:
+ self.bar.setValue(val + 1)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class ProgressBar(QtWidgets.QProgressBar):
+ def __init__(self, title, parent=None):
+ super(ProgressBar, self).__init__()
+
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ self.toolsPath = settings.value("toolsPath")
+
+ # load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ self.style = f.read()
+ f.close()
+
+ self.setStyleSheet(self.style)
+ self.setWindowTitle(title)
+ self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowTitleHint | QtCore.Qt.CustomizeWindowHint)
+
+ self.setMinimumSize(QtCore.QSize(400, 40))
+ self.setMaximumSize(QtCore.QSize(400, 40))
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class commentBoxItem(QtWidgets.QGraphicsRectItem):
+ def __init__(self, x, y, w, h, scene, view, animUI):
+
+ super(commentBoxItem, self).__init__(x, y, w, h)
+
+ self.brush = QtGui.QBrush(QtGui.QColor(60, 60, 60, 125))
+ self.brushColor = self.brush.color()
+ self.width = w
+ self.height = h
+ self.x = x
+ self.y = y
+ self.scale = 1
+ self.menu = QtWidgets.QMenu()
+ self.scene = scene
+ self.view = view
+ self.animUI = animUI
+
+ self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
+
+ # add items to context menu
+ self.menu.addAction("Change Color", self.changeBoxColor)
+ self.menu.addAction("Rename", self.changeLabelText)
+ self.menu.addAction("Remove Comment Box", self.deleteCommentBox)
+
+ # add text
+ self.textLabel = QtWidgets.QGraphicsTextItem("Comment Box", self, scene)
+ self.textLabel.setPos(x, y - 20)
+ self.textLabel.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
+ # self.textLabel.setTextInteractionFlags(QtCore.Qt.TextEditable)
+
+ self.classType = "comment"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def boundingRect(self):
+ rect = QtCore.QRectF(self.x, self.y, self.width, self.height)
+ return rect
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def paint(self, painter, option, widget):
+ rec = self.boundingRect()
+
+ self.blackPen = QtGui.QPen(QtCore.Qt.black)
+ self.blackPen.setWidth(0)
+ painter.setPen(self.blackPen)
+ painter.fillRect(rec, self.brush)
+ painter.drawRect(rec)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def contextMenuEvent(self, event):
+ self.menu.exec_(event.screenPos())
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def changeBoxColor(self):
+
+ # launch a color dialog to get a new color
+ newColor = QtGui.QColorDialog.getColor()
+ newColor.setAlpha(100)
+ self.brush.setColor(newColor)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def changeLabelText(self):
+
+ text = QtWidgets.QInputDialog.getText(self.scene.parent(), "Comment Box", "Enter Label Text:")
+ if text:
+ self.textLabel.setPlainText(text[0])
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def deleteCommentBox(self):
+
+ self.scene.removeItem(self)
+ self.animUI.rubberband.hide()
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class pickerBorderItem(QtWidgets.QGraphicsRectItem):
+ def __init__(self, x, y, w, h, brush, moduleName, niceName=None):
+
+ super(pickerBorderItem, self).__init__(x, y, w, h)
+
+ self.brush = brush
+ self.brushColor = brush.color()
+ self.width = w
+ self.height = h
+ self.x = x
+ self.y = y
+ self.scale = 1
+
+ self.mouseDown = False
+
+ self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
+ self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
+ self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
+ self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges)
+
+ self.setData(QtCore.Qt.UserRole, moduleName)
+ self.setData(2, niceName)
+ self.classType = "border"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def boundingRect(self):
+ rect = QtCore.QRectF(self.x, self.y, self.width, self.height)
+ return rect
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def paint(self, painter, option, widget):
+ rec = self.boundingRect()
+
+ blackPen = QtGui.QPen(QtCore.Qt.transparent)
+ blackPen.setWidth(0)
+ blackPen.setStyle(QtCore.Qt.DotLine)
+ painter.setPen(blackPen)
+
+ flags = self.flags()
+ if flags & QtWidgets.QGraphicsItem.ItemIsMovable:
+ blackPen = QtGui.QPen(QtCore.Qt.black)
+ blackPen.setWidth(0)
+ blackPen.setStyle(QtCore.Qt.DotLine)
+ painter.setPen(blackPen)
+
+ if self.isSelected():
+ blackPen = QtGui.QPen(QtCore.Qt.white)
+ blackPen.setWidth(0)
+ blackPen.setStyle(QtCore.Qt.DotLine)
+ painter.setPen(blackPen)
+
+ painter.fillRect(rec, self.brush)
+ painter.drawRect(rec)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def wheelEvent(self, event):
+
+ # only if the focusable flag is set to true, do we continue
+ flags = self.flags()
+ if flags & QtWidgets.QGraphicsItem.ItemIsFocusable:
+
+ self.scale = self.data(1)
+ if self.scale is None:
+ self.scale = 1
+ scale = float(event.delta() / 8.0)
+ self.scale = float((scale / 15.0) / 10) + self.scale
+ self.setData(1, self.scale)
+
+ self.setTransformOriginPoint(self.boundingRect().center())
+ self.setScale(self.scale)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def keyPressEvent(self, event):
+
+ self.setTransformOriginPoint(self.boundingRect().center())
+
+ if event.key() == QtCore.Qt.Key_Left:
+ self.setRotation(self.rotation() - 10)
+
+ if event.key() == QtCore.Qt.Key_Right:
+ self.setRotation(self.rotation() + 10)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class pickerButton(QtWidgets.QGraphicsItem):
+ def __init__(self, width, height, relativePos, controlObj, brush, parent=None):
+
+ super(pickerButton, self).__init__(parent)
+
+ self.parentItem().setZValue(1)
+ self.setZValue(2)
+
+ self.brush = QtGui.QBrush(brush)
+ self.brushColor = brush
+
+ self.width = width
+ self.height = height
+ self.relativePos = relativePos
+ self.object = controlObj
+
+ self.setPos(self.parentItem().boundingRect().topLeft())
+ self.setPos(self.pos().x() + self.relativePos[0], self.pos().y() + self.relativePos[1])
+ self.menu = QtWidgets.QMenu()
+
+ self.classType = "pickerButton"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def boundingRect(self):
+ rect = QtCore.QRectF(0, 0, self.width, self.height)
+ return rect
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def paint(self, painter, option, widget):
+ rec = self.boundingRect()
+ painter.fillRect(rec, self.brush)
+ painter.drawRect(rec)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def mousePressEvent(self, event):
+
+ mods = cmds.getModifiers()
+ if (mods & 1) > 0:
+ cmds.select(self.object, tgl=True)
+ if (mods & 1) == 0:
+ cmds.select(self.object)
+
+ if self.object in cmds.ls(sl=True):
+
+ self.brush.setColor(QtCore.Qt.white)
+
+ else:
+ self.brush.setColor(self.brushColor)
+
+ self.update()
+ QtWidgets.QGraphicsItem.mousePressEvent(self, event)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def mousePressEventCustom(self, event):
+
+ cmds.select(self.object, tgl=True)
+ self.brush.setColor(self.brushColor)
+ self.update()
+ QtWidgets.QGraphicsItem.mousePressEvent(self, event)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def mouseMoveEvent(self, event):
+ print "mouse move event"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def dragMoveEvent(self, event):
+ print "drag move event"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def hoverMoveEvent(self, event):
+ print "hover move event"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def contextMenuEvent(self, event):
+ self.menu.exec_(event.screenPos())
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class pickerButtonCustom(QtWidgets.QGraphicsPolygonItem):
+ def __init__(self, width, height, pointArray, relativePos, controlObj, brush, parent=None):
+
+ super(pickerButtonCustom, self).__init__(parent)
+
+ self.parentItem().setZValue(1)
+ self.setZValue(2)
+
+ self.brush = QtGui.QBrush(brush)
+ self.brushColor = brush
+ self.pointArray = pointArray
+ self.poly = self.createPolygon()
+ self.setPolygon(self.poly)
+
+ # position item
+ self.relativePos = relativePos
+ self.object = controlObj
+ self.setPos(self.parentItem().boundingRect().topLeft())
+ self.setPos(self.pos().x() + self.relativePos[0], self.pos().y() + self.relativePos[1])
+
+ # create menu
+ self.menu = QtWidgets.QMenu()
+ self.classType = "pickerButton"
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def createPolygon(self):
+ polygon = QtGui.QPolygonF()
+ for each in self.pointArray:
+ polygon.append(QtCore.QPointF(each[0], each[1]))
+ return polygon
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def paint(self, painter, option, widget):
+ painter.setBrush(self.brush)
+ painter.drawPolygon(self.polygon())
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def mousePressEvent(self, event):
+
+ mods = cmds.getModifiers()
+ if (mods & 1) > 0:
+ cmds.select(self.object, tgl=True)
+ if (mods & 1) == 0:
+ cmds.select(self.object)
+
+ if self.object in cmds.ls(sl=True):
+
+ self.brush.setColor(QtCore.Qt.white)
+
+ else:
+ self.brush.setColor(self.brushColor)
+
+ self.update()
+ QtWidgets.QGraphicsPolygonItem.mousePressEvent(self, event)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def mousePressEventCustom(self, event):
+
+ cmds.select(self.object, tgl=True)
+ self.brush.setColor(self.brushColor)
+ self.update()
+ QtWidgets.QGraphicsItem.mousePressEvent(self, event)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def contextMenuEvent(self, event):
+ self.menu.exec_(event.screenPos())
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class pickerButtonAll(QtWidgets.QGraphicsItem):
+ def __init__(self, width, height, relativePos, controlObjects, brush, parent=None):
+
+ super(pickerButtonAll, self).__init__(parent)
+
+ self.parentItem().setZValue(1)
+ self.setZValue(2)
+
+ self.brush = QtGui.QBrush(brush)
+ self.brushColor = brush
+
+ self.width = width
+ self.height = height
+ self.relativePos = relativePos
+ self.objects = controlObjects
+
+ self.setPos(self.parentItem().boundingRect().topLeft())
+ self.setPos(self.pos().x() + self.relativePos[0], self.pos().y() + self.relativePos[1])
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def boundingRect(self):
+ rect = QtCore.QRectF(0, 0, self.width, self.height)
+ return rect
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def paint(self, painter, option, widget):
+ rec = self.boundingRect()
+ painter.fillRect(rec, self.brush)
+ painter.drawRect(rec)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def mousePressEvent(self, event):
+
+ mods = cmds.getModifiers()
+ if (mods & 1) > 0:
+ for obj in self.objects:
+ cmds.select(obj, add=True)
+
+ if (mods & 1) == 0:
+ cmds.select(clear=True)
+ for obj in self.objects:
+ cmds.select(obj, tgl=True)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class DialogMessage(QtWidgets.QMainWindow):
+ def __init__(self, title, message, elementList, elementSize, parent=None):
+ super(DialogMessage, self).__init__(parent)
+
+ # get the directory path of the
+ settings = QtCore.QSettings("Epic Games", "ARTv2")
+ self.toolsPath = settings.value("toolsPath")
+ self.iconsPath = settings.value("iconPath")
+
+ # load stylesheet
+ styleSheetFile = utils.returnNicePath(self.toolsPath, "Core/Scripts/Interfaces/StyleSheets/mainScheme.qss")
+ f = open(styleSheetFile, "r")
+ style = f.read()
+ f.close()
+
+ self.setStyleSheet(style)
+
+ # size policies
+ mainSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+
+ # create the main widget
+ self.mainWidget = QtWidgets.QWidget()
+ self.setCentralWidget(self.mainWidget)
+
+ # set qt object name
+ self.setObjectName("pyART_customDialogMessageWin")
+ self.setWindowTitle(title)
+
+ # create the mainLayout for the rig creator UI
+ self.mainLayout = QtWidgets.QVBoxLayout(self.mainWidget)
+ self.mainLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.resize(300, 200)
+ self.setSizePolicy(mainSizePolicy)
+ self.setMinimumSize(QtCore.QSize(300, 200))
+ self.setMaximumSize(QtCore.QSize(300, 200))
+
+ # create the background image
+ self.frame = QtWidgets.QFrame()
+ self.mainLayout.addWidget(self.frame)
+
+ # create the layout for the widgets
+ self.widgetLayout = QtWidgets.QVBoxLayout(self.frame)
+
+ # add the message to the layout
+ self.messageArea = QtWidgets.QTextEdit()
+ self.messageArea.setReadOnly(True)
+ self.widgetLayout.addWidget(self.messageArea)
+
+ self.messageArea.setTextColor(QtGui.QColor(236, 217, 0))
+ self.messageArea.append(message + "\n\n")
+
+ string = ""
+ for each in elementList:
+ for i in range(elementSize):
+ string += each[i] + " "
+
+ self.messageArea.setTextColor(QtGui.QColor(255, 255, 255))
+ self.messageArea.append(string)
+
+ # add the OK button
+ self.confirmButton = QtWidgets.QPushButton("OK")
+ self.confirmButton.setObjectName("blueButton")
+ self.widgetLayout.addWidget(self.confirmButton)
+ self.confirmButton.clicked.connect(self.closeWindow)
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ def closeWindow(self):
+
+ cmds.deleteUI("pyART_customDialogMessageWin", wnd=True)
diff --git a/Core/Scripts/System/mathUtils.py b/Core/Scripts/System/mathUtils.py
new file mode 100644
index 0000000..d2230ae
--- /dev/null
+++ b/Core/Scripts/System/mathUtils.py
@@ -0,0 +1,207 @@
+"""
+Math utilities
+2015, Epic Games
+"""
+
+import math
+
+import maya.api.OpenMaya as om
+import maya.cmds as cmds
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# CLASSES
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+class KDTreeNode():
+ def __init__(self, point, left, right):
+ self.point = point
+ self.left = left
+ self.right = right
+
+ def is_leaf(self):
+ return (self.left is None and self.right is None)
+
+
+class KDTreeNeighbours():
+ """ Internal structure used in nearest-neighbours search.
+ """
+
+ def __init__(self, query_point, t):
+ self.query_point = query_point
+ self.t = t # neighbours wanted
+ self.largest_distance = 0 # squared
+ self.current_best = []
+
+ def calculate_largest(self):
+ if self.t >= len(self.current_best):
+ self.largest_distance = self.current_best[-1][1]
+ else:
+ self.largest_distance = self.current_best[self.t - 1][1]
+
+ def add(self, point):
+ sd = square_distance(point, self.query_point)
+ # run through current_best, try to find appropriate place
+ for i, e in enumerate(self.current_best):
+ if i == self.t:
+ return # enough neighbours, this one is farther, let's forget it
+ if e[1] > sd:
+ self.current_best.insert(i, [point, sd])
+ self.calculate_largest()
+ return
+ # append it to the end otherwise
+ self.current_best.append([point, sd])
+ self.calculate_largest()
+
+ def get_best(self):
+ return [element[0] for element in self.current_best[:self.t]]
+
+
+class KDTree():
+ """ KDTree implementation built from http://en.wikipedia.org/wiki/K-d_tree as a starting point
+
+ Example usage:
+ from kdtree import KDTree
+
+ tree = KDTree.construct_from_data(data)
+ nearest = tree.query(point, t=4) # find nearest 4 points
+ """
+
+ def __init__(self, data):
+ def build_kdtree(point_list, depth):
+ if not point_list:
+ return None
+
+ # check that all points share the same dimensions
+ dim = len(point_list[0])
+ for point in point_list:
+ if len(point) != dim:
+ print 'KDTREE: point', point, 'does not have', dim, 'dimensions.'
+
+ # select axis based on depth modulo tested dimension
+ axis = depth % dim
+
+ # sort point list
+ point_list.sort(key=lambda point: point[axis])
+ # choose the median
+ median = len(point_list) / 2
+
+ # create node and recursively construct subtrees
+ node = KDTreeNode(point=point_list[median],
+ left=build_kdtree(point_list[0:median], depth + 1),
+ right=build_kdtree(point_list[median + 1:], depth + 1))
+ return node
+
+ self.root_node = build_kdtree(data, depth=0)
+
+ @staticmethod
+ def construct_from_data(data):
+ tree = KDTree(data)
+ return tree
+
+ def query(self, query_point, t=1, debug=1):
+ stats = {'nodes_visited': 0, 'far_search': 0, 'leafs_reached': 0}
+
+ def nn_search(node, query_point, t, depth, best_neighbours):
+ if node is None:
+ return
+
+ stats['nodes_visited'] += 1
+
+ # if we have reached a leaf, let's add to current best neighbours,
+ # (if it's better than the worst one or if there is not enough neighbours)
+ if node.is_leaf():
+ # statistics['leafs_reached'] += 1
+ best_neighbours.add(node.point)
+ return
+
+ # this node is no leaf
+
+ # select dimension for comparison (based on current depth)
+ axis = depth % len(query_point)
+
+ # figure out which subtree to search
+ near_subtree = None # near subtree
+ far_subtree = None # far subtree (perhaps we'll have to traverse it as well)
+
+ # compare query_point and point of current node in selected dimension
+ # and figure out which subtree is farther than the other
+ if query_point[axis] < node.point[axis]:
+ near_subtree = node.left
+ far_subtree = node.right
+ else:
+ near_subtree = node.right
+ far_subtree = node.left
+
+ # recursively search through the tree until a leaf is found
+ nn_search(near_subtree, query_point, t, depth + 1, best_neighbours)
+
+ # while unwinding the recursion, check if the current node
+ # is closer to query point than the current best,
+ # also, until t points have been found, search radius is infinity
+ best_neighbours.add(node.point)
+
+ # check whether there could be any points on the other side of the
+ # splitting plane that are closer to the query point than the current best
+ if (node.point[axis] - query_point[axis]) ** 2 < best_neighbours.largest_distance:
+ # statistics['far_search'] += 1
+ nn_search(far_subtree, query_point, t, depth + 1, best_neighbours)
+
+ return
+
+ # if there's no tree, there's no neighbors
+ if self.root_node is not None:
+ neighbours = KDTreeNeighbours(query_point, t)
+ nn_search(self.root_node, query_point, t, depth=0, best_neighbours=neighbours)
+ result = neighbours.get_best()
+ else:
+ result = []
+
+ # print statistics
+ return result
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# METHODS
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+def square_distance(self, pointA, pointB):
+ # squared euclidean distance
+ distance = 0
+ dimensions = len(pointA) # assumes both points have the same dimensions
+ for dimension in range(dimensions):
+ distance += (pointA[dimension] - pointB[dimension]) ** 2
+ return distance
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def getAngleBetween(object1, object2):
+ point1 = cmds.xform(object1, t=True, q=True, ws=True)
+ vector1 = om.MVector(point1)
+
+ point2 = cmds.xform(object2, t=True, q=True, ws=True)
+ vector2 = om.MVector(point2)
+
+ dotProduct = vector1.normal() * vector2.normal()
+ angle = math.acos(dotProduct) * 180 / math.pi
+ return angle
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def returnPercentile(incomingRange, percent, key=lambda x: x):
+ floor = math.floor(percent)
+ ceil = math.ceil(percent)
+
+ if percent == 1:
+ return incomingRange[1]
+
+ if percent == 0:
+ return incomingRange[0]
+
+ d0 = key(incomingRange[int(floor)] * (ceil - percent))
+ d1 = key(incomingRange[int(ceil)] * (percent - floor))
+
+ return d0 + d1
diff --git a/Core/Scripts/System/riggingUtils.py b/Core/Scripts/System/riggingUtils.py
new file mode 100644
index 0000000..6bce000
--- /dev/null
+++ b/Core/Scripts/System/riggingUtils.py
@@ -0,0 +1,1448 @@
+"""
+Author: Jeremy Ernst
+
+This module is a collection of utility functions for generic rigging and skinning tasks.
+There are also classes in the module that deal with saving and loading skin weight files.
+"""
+
+import json
+import os
+import time
+
+import maya.cmds as cmds
+from maya import OpenMaya, OpenMayaAnim
+
+import System.mathUtils as mathUtils
+import utils as utils
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# GLOBALS
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ATTRIBUTES = ['skinningMethod', 'normalizeWeights', 'dropoffRate',
+ 'maintainMaxInfluences', 'maxInfluences','bindMethod',
+ 'useComponents', 'normalizeWeights', 'weightDistribution',
+ 'heatmapFalloff']
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# FUNCTIONS (STAND-ALONE)
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def getScaleFactor():
+ # ===========================================================================
+ # #find meshes
+ # ===========================================================================
+ 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)
+
+ # scale factor of 1 = 180cm
+ if len(weightedMeshes) > 0:
+ scale = cmds.exactWorldBoundingBox(weightedMeshes, ii=True)[5]
+ scaleFactor = scale / 180
+ else:
+ scaleFactor = 1
+
+ return scaleFactor
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createDriverSkeleton():
+ # Original Author: Jeremy Ernst
+
+ # ===========================================================================
+ # #duplicate the entire skeleton
+ # ===========================================================================
+ duplicateSkel = cmds.duplicate("root", rc=True)[0]
+ cmds.select("root", hi=True)
+ joints = cmds.ls(sl=True)
+
+ cmds.select(duplicateSkel, hi=True)
+ dupeJoints = cmds.ls(sl=True)
+
+ # ===========================================================================
+ # #rename the duplicate joints
+ # ===========================================================================
+ driverJoints = []
+ for i in range(int(len(dupeJoints))):
+
+ if cmds.objExists(dupeJoints[i]):
+ driverJoint = cmds.rename(dupeJoints[i], "driver_" + joints[i])
+ driverJoints.append(driverJoint)
+
+ # ===========================================================================
+ # #create a direct connection between the driver and the export joints
+ # ===========================================================================
+ for joint in driverJoints:
+ exportJoint = joint.partition("driver_")[2]
+ cmds.connectAttr(joint + ".translate", exportJoint + ".translate")
+ cmds.connectAttr(joint + ".rotate", exportJoint + ".rotate")
+ cmds.connectAttr(joint + ".scale", exportJoint + ".scale")
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def buildSkeleton():
+ # get all of the rig modules in the scene
+ modules = utils.returnRigModules()
+
+ # go through each one, and find the created bones for that modules
+ createdJoints = []
+ for module in modules:
+ if module != "ART_Root_Module":
+ joints = cmds.getAttr(module + ".Created_Bones")
+ splitJoints = joints.split("::")
+
+ for bone in splitJoints:
+ if bone != "":
+ # create the joint
+ if cmds.objExists(bone):
+ cmds.warning(
+ "Object with name: " + bone + " already exists. Renaming object with _renamed suffix.")
+ cmds.rename(bone, bone + "_renamed")
+ cmds.select(clear=True)
+ newJoint = cmds.joint(name=str(bone))
+ cmds.select(clear=True)
+
+ # find the LRA control
+ moduleName = cmds.getAttr(module + ".moduleName")
+ baseName = cmds.getAttr(module + ".baseName")
+
+ # 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)
+ if prefix == "":
+ boneName = bone.partition(suffixSeparator)[0]
+ else:
+ boneName = bone.partition(prefixSeparator)[2].partition(suffixSeparator)[0]
+
+ # get the lra node
+ lra = prefix + baseName + suffix + "_" + boneName + "_lra"
+ lra = lra.replace(" ", "")
+
+ if not cmds.objExists(lra):
+ lra = prefix + baseName + suffix + "_lra"
+ lra = lra.replace(" ", "")
+
+ # position bone at lra
+ constraint = cmds.parentConstraint(lra, newJoint)[0]
+ cmds.delete(constraint)
+
+ # find parent joint and append data to list
+ if bone != splitJoints[0]:
+ relatives = str(cmds.listRelatives(lra, fullPath=True))
+ relatives = relatives.split("|")
+ relatives = relatives[::-1]
+ for relative in relatives:
+ searchKey = lra.partition("_lra")[0]
+
+ if relative.find(searchKey) == -1:
+ parentBoneName = relative.partition(moduleName + "_")[2].partition("_mover")[0]
+ parent = prefix + parentBoneName + suffix
+ createdJoints.append([bone, parent])
+ break
+ else:
+ parent = cmds.getAttr(module + ".parentModuleBone")
+ createdJoints.append([bone, parent])
+
+ # if this is the root module, it's a bit simpler
+ else:
+ jointName = cmds.getAttr(module + ".Created_Bones")
+ # create the joint
+ if cmds.objExists(jointName):
+ cmds.warning(
+ "Object with name: " + jointName + " already exists. Renaming object with _renamed suffix.")
+ cmds.rename(jointName, jointName + "_renamed")
+
+ cmds.select(clear=True)
+ cmds.joint(name=str(jointName))
+ cmds.select(clear=True)
+
+ # go through the data and setup the hierarchy now that all bones have been created
+ for joint in createdJoints:
+ cmds.parent(joint[0], joint[1])
+
+ # freeze rotation transformations
+ cmds.makeIdentity("root", t=False, r=True, s=False, apply=True)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createBoneConnection(mover, childMover, userSpecName):
+ if cmds.objExists(userSpecName + "_parentGrp"):
+ cmds.delete(userSpecName + "_parentGrp")
+
+ grp = cmds.group(empty=True, name=userSpecName + "_parentGrp")
+ cmds.parent(grp, userSpecName + "_bone_representations")
+
+ crv = cmds.curve(d=1, p=[(0, 0, 0), (0, 20, 0)], k=[0, 1], name=mover + "_to_" + childMover + "_connector_geo")
+ cmds.parent(crv, grp)
+
+ cmds.select(crv + ".cv[0]")
+ topCluster = cmds.cluster(name=mover + "_to_" + childMover + "_top_cluster")[1]
+ cmds.select(crv + ".cv[1]")
+ botCluster = cmds.cluster(name=mover + "_to_" + childMover + "_end_cluster")[1]
+ cmds.pointConstraint(mover, topCluster)
+ cmds.pointConstraint(childMover, botCluster)
+
+ cmds.setAttr(topCluster + ".v", False, lock=True)
+ cmds.setAttr(botCluster + ".v", False, lock=True)
+ cmds.setAttr(crv + ".overrideEnabled", 1)
+ cmds.setAttr(crv + ".overrideColor", 1)
+ cmds.setAttr(crv + ".overrideDisplayType", 2)
+
+ cmds.parent(topCluster, grp)
+ cmds.parent(botCluster, grp)
+ cmds.setAttr(crv + ".inheritsTransform", 0)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createFkRig(joints, networkNode, numRigs, slot):
+ # Original Author: Jeremy Ernst
+
+ # lists
+ fkControls = []
+
+ # ===========================================================================
+ # #take the list of incoming joints and find each joint's mover
+ # ===========================================================================
+ for joint in joints:
+ globalMover = utils.findAssociatedMover(joint, networkNode)
+
+ # =======================================================================
+ # #if a mover is found, duplicate it and unparent the duplicate
+ # =======================================================================
+ if globalMover != None:
+ dupe = cmds.duplicate(globalMover, rr=True)[0]
+ utils.deleteChildren(dupe)
+ parent = cmds.listRelatives(dupe, parent=True)
+ if parent != None:
+ cmds.parent(dupe, world=True)
+
+ # turn on visiblity of the control
+ cmds.setAttr(dupe + ".v", 1)
+
+ # ensure pivot is correct
+ piv = cmds.xform(joint, q=True, ws=True, rp=True)
+ cmds.xform(dupe, ws=True, rp=piv)
+
+ # rename the control
+ fkControl = cmds.rename(dupe, "fk_" + joint + "_anim")
+ fkControls.append([joint, fkControl])
+
+ # create a group for the control
+ controlGrp = cmds.group(empty=True, name="fk_" + joint + "_anim_grp")
+ constraint = cmds.parentConstraint(joint, controlGrp)[0]
+ cmds.delete(constraint)
+
+ # parent the control under the controlGrp
+ cmds.parent(fkControl, controlGrp)
+
+ # freeze transformations on the control
+ cmds.makeIdentity(fkControl, t=1, r=1, s=1, apply=True)
+
+ # color the control
+ colorControl(fkControl)
+
+ returnData = None
+ createdControls = []
+
+ # ===========================================================================
+ # #go through the fk controls data and get the joint parent to create the hierarchy
+ # ===========================================================================
+ for data in fkControls:
+ joint = data[0]
+ control = data[1]
+ createdControls.append(control)
+
+ # =======================================================================
+ # #parent the anim group to the parent control
+ # =======================================================================
+ parent = cmds.listRelatives(joint, parent=True)
+ if parent != None:
+ group = control + "_grp"
+ parentControl = "fk_" + parent[0] + "_anim"
+ if parent[0] in joints:
+ if cmds.objExists(parentControl):
+ cmds.parent(group, parentControl)
+
+ else:
+ returnData = group
+
+ # =======================================================================
+ # #lastly, connect controls up to blender nodes to drive driver joints
+ # =======================================================================
+ 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")
+ createConstraint(globalScaleMult, "driver_" + joint, "scale", False, numRigs, slot, "output")
+ else:
+ createConstraint(control, "driver_" + joint, "scale", False, numRigs, slot)
+
+ return [returnData, createdControls]
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createCounterTwistRig(joints, name, networkNode, startJoint, endJoint, parentGrp, offset=[0, 0, 0]):
+ # Original Author: Jeremy Ernst
+
+ # Usage: create a counter twist rig using the incoming joints
+ if len(joints) == 0:
+ return
+
+ # data lists
+ createdGroups = []
+ createdControls = []
+
+ # ===========================================================================
+ # #create the manual controls for the twist joints
+ # ===========================================================================
+ for i in range(len(joints)):
+
+ # driven group
+ group = cmds.group(empty=True, name=joints[i] + "_driven_grp")
+
+ constraint = cmds.parentConstraint(joints[i], group)[0]
+ cmds.delete(constraint)
+
+ # anim group
+ animGrp = cmds.duplicate(group, po=True, name=joints[i] + "_anim_grp")[0]
+ cmds.parent(group, animGrp)
+ createdGroups.append(animGrp)
+
+ # create the twist control
+ twistCtrl = createControl("arrow", 1.5, joints[i] + "_anim")
+ createdControls.append(twistCtrl)
+ constraint = cmds.pointConstraint(joints[i], twistCtrl)[0]
+ cmds.delete(constraint)
+ cmds.parent(twistCtrl, group)
+ colorControl(twistCtrl)
+
+ cmds.setAttr(twistCtrl + ".rx", offset[0])
+ cmds.setAttr(twistCtrl + ".ry", offset[1])
+ cmds.setAttr(twistCtrl + ".rz", offset[2])
+ cmds.setAttr(twistCtrl + ".v", lock=True, keyable=False)
+
+ cmds.makeIdentity(twistCtrl, t=1, r=1, s=1, apply=True)
+
+ # constrain the driver joint to the twist control
+ cmds.parentConstraint(twistCtrl, "driver_" + joints[i])
+
+ # 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=twistCtrl + "_globalScale")
+ cmds.connectAttr("master_anim.scale", globalScaleMult + ".input1")
+ cmds.connectAttr(twistCtrl + ".scale", globalScaleMult + ".input2")
+ createConstraint(globalScaleMult, "driver_" + joints[i], "scale", False, 2, 1, "output")
+ else:
+ createConstraint(twistCtrl, "driver_" + joints[i], "scale", False, 2, 1)
+
+ # ===========================================================================
+ # #create the ik twist joint chain
+ # ===========================================================================
+ ikTwistUpper = cmds.createNode("joint", name=name + "_counter_twist_ik")
+ ikTwistLower = cmds.createNode("joint", name=name + "_counter_twist_ik_end")
+
+ cmds.parent(ikTwistLower, ikTwistUpper)
+
+ # parent under the driver startJoint
+ cmds.parent(ikTwistUpper, "driver_" + startJoint)
+ constraint = cmds.parentConstraint("driver_" + startJoint, ikTwistUpper)[0]
+ cmds.delete(constraint)
+ constraint = cmds.parentConstraint("driver_" + endJoint, ikTwistLower)[0]
+ cmds.delete(constraint)
+
+ # add rp ik to those joints
+ twistIK = cmds.ikHandle(sol="ikRPsolver", name=name + "_counter_twist_ikHandle", sj=ikTwistUpper, ee=ikTwistLower)[
+ 0]
+ cmds.parent(twistIK, "driver_" + startJoint)
+
+ ikWorldPos = cmds.xform(twistIK, q=True, ws=True, t=True)[0]
+ if ikWorldPos < 0:
+ cmds.setAttr(twistIK + ".twist", 180)
+
+ # create ik pole vector
+ twistVector = cmds.spaceLocator(name=name + "_counter_twist_PoleVector")[0]
+ constraint = cmds.parentConstraint("driver_" + startJoint, twistVector)[0]
+ cmds.delete(constraint)
+ cmds.parent(twistVector, "driver_" + startJoint)
+
+ # create pole vector constraint
+ cmds.poleVectorConstraint(twistVector, twistIK)
+
+ # ===========================================================================
+ # #create roll locator (locator that actually will drive the roll amount)
+ # ===========================================================================
+ rollLoc = cmds.spaceLocator(name=name + "_counter_twist_tracker")[0]
+ cmds.setAttr(rollLoc + ".v", 0, lock=True)
+ constraint = cmds.parentConstraint("driver_" + startJoint, rollLoc)[0]
+ cmds.delete(constraint)
+
+ cmds.parent(rollLoc, name + "_group")
+ cmds.makeIdentity(rollLoc, t=0, r=1, s=0, apply=True)
+ cmds.orientConstraint("driver_" + startJoint, rollLoc, skip=["y", "z"])
+
+ # Group up and parent into rig
+ twistGrp = cmds.group(empty=True, name=name + "_counter_twist_grp")
+ constraint = cmds.parentConstraint(startJoint, twistGrp)[0]
+ cmds.delete(constraint)
+
+ for group in createdGroups:
+ cmds.parent(group, twistGrp)
+
+ # ===========================================================================
+ # #add twist grp to parent group and add all anim groups to twist group
+ # ===========================================================================
+ cmds.parent(twistGrp, parentGrp)
+
+ # constrain the group to the startJoint so it follows with the action of the limb
+ cmds.parentConstraint("driver_" + startJoint, twistGrp, mo=True)
+
+ # ===========================================================================
+ # #hook up multiply divide nodes
+ # ===========================================================================
+
+ # first one takes rollLoc rotateX and multiplies it by -1 to get the counter
+ counterMultNode = cmds.shadingNode("multiplyDivide", asUtility=True, name=name + "_counter_twist_counterNode")
+ cmds.connectAttr(rollLoc + ".rotateX", counterMultNode + ".input1X")
+ cmds.setAttr(counterMultNode + ".input2X", -1)
+
+ # second one takes the output of the counterMultNode and multiplies it with the twist amount
+ increment = float(1.0) / float(len(joints))
+ defaultValue = 1
+ for i in range(len(joints)):
+ attrName = joints[i] + "_twistAmt"
+ grpName = joints[i] + "_driven_grp"
+ cmds.addAttr(name + "_settings", ln=attrName, dv=defaultValue, keyable=True)
+ defaultValue -= increment
+
+ # multNode creation
+ rollMultNode = cmds.shadingNode("multiplyDivide", asUtility=True, name=joints[i] + "_rollNode")
+ cmds.connectAttr(counterMultNode + ".outputX", rollMultNode + ".input1X")
+ cmds.connectAttr(name + "_settings." + attrName, rollMultNode + ".input2X")
+
+ # connect output of roll node to driven group
+ cmds.connectAttr(rollMultNode + ".outputX", grpName + ".rotateX")
+
+ # add attr on rig settings node for manual twist control visibility
+ cmds.addAttr(name + "_settings", longName=(startJoint + "_twistCtrlVis"), at='bool', dv=0, keyable=True)
+ cmds.connectAttr(name + "_settings." + startJoint + "_twistCtrlVis", twistGrp + ".v")
+
+ return createdControls
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createTwistRig(joints, name, networkNode, startJoint, endJoint, parentGrp, offset=[0, -90, 0]):
+ # Original Author: Jeremy Ernst
+
+ # Usage: create a standard twist rig using the incoming joints
+ defaultValue = .75
+ createdControls = []
+
+ if len(joints) >= 1:
+ # create our roll group
+ rollGrp = cmds.group(empty=True, name=name + "_" + startJoint + "_twist_grp")
+ cmds.parent(rollGrp, parentGrp)
+ cmds.parentConstraint("driver_" + startJoint, rollGrp)
+
+ for i in range(len(joints)):
+
+ # =======================================================================
+ # #create the twist ctrl group
+ # =======================================================================
+ twistCtrlGrp = cmds.group(empty=True, name=joints[i] + "_twist_ctrl_grp")
+ constraint = cmds.parentConstraint("driver_" + joints[i], twistCtrlGrp)[0]
+ cmds.delete(constraint)
+
+ cmds.parent(twistCtrlGrp, rollGrp)
+
+ # =======================================================================
+ # #create the driven twist group
+ # =======================================================================
+ twistDrivenGrp = cmds.duplicate(twistCtrlGrp, po=True, name=joints[i] + "_twist_driven_grp")[0]
+ cmds.parent(twistDrivenGrp, twistCtrlGrp)
+
+ # =======================================================================
+ # #create the manual twist control
+ # =======================================================================
+ twistCtrl = createControl("arrow", 1, joints[i] + "_anim")
+ createdControls.append(twistCtrl)
+ cmds.makeIdentity(twistCtrl, t=0, r=1, s=0, apply=True)
+ constraint = cmds.parentConstraint(twistCtrlGrp, twistCtrl)[0]
+ cmds.delete(constraint)
+
+ cmds.setAttr(twistCtrl + ".rx", offset[0])
+ cmds.setAttr(twistCtrl + ".ry", offset[1])
+ cmds.setAttr(twistCtrl + ".rz", offset[2])
+ cmds.setAttr(twistCtrl + ".v", lock=True, keyable=False)
+
+ cmds.parent(twistCtrl, twistDrivenGrp)
+ cmds.makeIdentity(twistCtrl, t=1, r=1, s=1, apply=True)
+ colorControl(twistCtrl)
+
+ # =======================================================================
+ # #drive the twist
+ # =======================================================================
+ cmds.addAttr(name + "_settings", ln=joints[i] + "_twistAmt", dv=defaultValue, keyable=True)
+ twistMultNode = cmds.shadingNode("multiplyDivide", asUtility=True, name=joints[i] + "_mult_node")
+ cmds.connectAttr("driver_" + endJoint + ".rx", twistMultNode + ".input1X")
+ cmds.connectAttr(name + "_settings." + joints[i] + "_twistAmt", twistMultNode + ".input2X")
+ cmds.connectAttr(twistMultNode + ".outputX", twistDrivenGrp + ".rx")
+
+ # constrain the driver joint to the twist control
+ cmds.parentConstraint(twistCtrl, "driver_" + joints[i])
+ cmds.scaleConstraint(twistCtrl, "driver_" + joints[i])
+
+ # 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=twistCtrl + "_globalScale")
+ cmds.connectAttr("master_anim.scale", globalScaleMult + ".input1")
+ cmds.connectAttr(twistCtrl + ".scale", globalScaleMult + ".input2")
+ createConstraint(globalScaleMult, "driver_" + joints[i], "scale", False, 2, 1, "output")
+ else:
+ createConstraint(twistCtrl, "driver_" + joints[i], "scale", False, 2, 1)
+
+ # increment the default value
+ increment = float(0.75) / float(len(joints))
+ defaultValue -= increment
+
+ # add attr on rig settings node for manual twist control visibility
+ if len(joints) >= 1:
+ cmds.addAttr(name + "_settings", longName=(startJoint + "_twistCtrlVis"), at='bool', dv=0, keyable=True)
+ cmds.connectAttr(name + "_settings." + startJoint + "_twistCtrlVis", rollGrp + ".v")
+
+ return createdControls
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createControlFromMover(joint, networkNode, orientToJoint, buildSpaceSwitch):
+ # ===========================================================================
+ # #take the list of incoming joints and find each joint's mover
+ # ===========================================================================
+ globalMover = utils.findAssociatedMover(joint, networkNode)
+
+ # ===========================================================================
+ # #if a mover is found, duplicate it and unparent the duplicate
+ # ===========================================================================
+ if globalMover is not None:
+ control = cmds.duplicate(globalMover, rr=True)[0]
+ utils.deleteChildren(control)
+ parent = cmds.listRelatives(control, parent=True)
+ if parent is not None:
+ cmds.parent(control, world=True)
+
+ # turn on visiblity of the control
+ cmds.setAttr(control + ".v", 1)
+
+ for attr in [".translateX", ".translateY", ".translateZ", ".rotateX", ".rotateY", ".rotateZ", ".scaleX",
+ ".scaleY", ".scaleZ"]:
+ cmds.setAttr(control + attr, lock=False, keyable=True)
+
+ # ensure pivot is correct
+ piv = cmds.xform(joint, q=True, ws=True, rp=True)
+ cmds.xform(control, ws=True, rp=piv)
+
+ # create a group for the control
+ controlGrp = cmds.group(empty=True, name=control + "_grp")
+ if orientToJoint:
+ constraint = cmds.parentConstraint(joint, controlGrp)[0]
+ else:
+ constraint = cmds.pointConstraint(joint, controlGrp)[0]
+
+ cmds.delete(constraint)
+
+ # parent the control under the controlGrp
+ cmds.parent(control, controlGrp)
+
+ # freeze transformations on the control
+ cmds.makeIdentity(control, t=1, r=1, s=1, apply=True)
+
+ # =======================================================================
+ # #buildSpaceSwitch nodes if needed
+ # =======================================================================
+ if buildSpaceSwitch:
+ spaceSwitchFollow = cmds.duplicate(controlGrp, po=True, name=control + "_space_switcher_follow")[0]
+ spaceSwitch = cmds.duplicate(controlGrp, po=True, name=control + "_space_switcher")[0]
+
+ utils.deleteChildren(spaceSwitchFollow)
+ utils.deleteChildren(spaceSwitch)
+
+ cmds.parent(spaceSwitch, spaceSwitchFollow)
+ cmds.parent(controlGrp, spaceSwitch)
+
+ return [control, controlGrp, spaceSwitch, spaceSwitchFollow]
+
+ else:
+ return [control, controlGrp]
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createConstraint(target, destination, attr, maintainOffset, numRigs, slot, customAttr=None):
+ # Original Author: Jeremy Ernst
+
+ # ===========================================================================
+ # #takes the incoming destination and hooks it up to the source using a ramp node
+ # ===========================================================================
+ if not cmds.objExists(destination + "_" + attr + "_ramp_node"):
+ cmds.shadingNode("ramp", asTexture=True, name=destination + "_" + attr + "_ramp_node")
+ cmds.setAttr(destination + "_" + attr + "_ramp_node.type", 1)
+
+ # ===========================================================================
+ # #if maintainOffset is passed in, store current destination attr values in an add node as an offset
+ # ===========================================================================
+ if maintainOffset:
+ if not cmds.objExists(destination + "_" + attr + "_offset_node_" + str(slot)):
+ cmds.shadingNode("plusMinusAverage", asUtility=True,
+ name=destination + "_" + attr + "_offset_node_" + str(slot))
+
+ offsetValues = cmds.getAttr(destination + "." + attr)[0]
+ cmds.setAttr(destination + "_" + attr + "_offset_node_" + str(slot) + ".input3D[0]", offsetValues[0],
+ offsetValues[1], offsetValues[2], type="double3")
+
+ # ===========================================================================
+ # #if maintainOffset is True, passed in the target attribute into the add node to add with the offset values
+ # ===========================================================================
+ if maintainOffset:
+ cmds.connectAttr(target + "." + attr, destination + "_" + attr + "_offset_node_" + str(slot) + ".input3D[1]")
+ cmds.connectAttr(destination + "_" + attr + "_offset_node_" + str(slot) + ".output3D",
+ destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].color")
+
+ # ===========================================================================
+ # #otherwise, connect target + .attr to the ramp
+ # ===========================================================================
+ else:
+ if customAttr is None:
+ cmds.connectAttr(target + "." + attr,
+ destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].color")
+ else:
+ cmds.connectAttr(target + "." + customAttr,
+ destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].color")
+
+ # ===========================================================================
+ # #get the point position on the ramp for this slot.
+ # ===========================================================================
+ try:
+ pointPos = 1 / (numRigs - 1) * slot
+ except:
+ pointPos = 0
+
+ cmds.setAttr(destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].position", pointPos)
+
+ # ===========================================================================
+ # #make the output connection if it doesn't yet exist
+ # ===========================================================================
+ rampExists = cmds.connectionInfo(destination + "." + attr, isDestination=True)
+ if not rampExists:
+ cmds.connectAttr(destination + "_" + attr + "_ramp_node.outColor", destination + "." + attr)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def coPlanarModeSnap(instance, snapTarget, snapMover, ikJoints, orientMovers, orientCtrl, skipAttrs):
+ # snap the incoming snapMover to snapTarget using the input attribute
+ cmds.undoInfo(openChunk=True)
+ cmds.cycleCheck(e=False)
+
+ # get current aimState
+ for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
+ cmds.setAttr(snapMover + attr, lock=False)
+
+ constraint = cmds.pointConstraint(snapTarget, snapMover, skip=skipAttrs)[0]
+ cmds.delete(constraint)
+ cmds.cycleCheck(e=True)
+
+ for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
+ cmds.setAttr(snapMover + attr, lock=True)
+
+ for i in range(len(ikJoints)):
+ connection = cmds.connectionInfo(orientMovers[i] + ".rotateX", sourceFromDestination=True)
+ aimConstraint = connection.rpartition(".")[0]
+
+ for attr in [[".rx", ".offsetX"]]:
+ value = cmds.getAttr(orientCtrl + attr[0])
+ cmds.setAttr(aimConstraint + attr[1], value)
+
+ cmds.undoInfo(closeChunk=True)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createControl(controlType, size, name, useScaleFactor=True):
+ if useScaleFactor:
+ scale = getScaleFactor()
+ else:
+ scale = 1
+
+ if controlType == "circle":
+ control = cmds.circle(c=(0, 0, 0), sw=360, r=size * scale, d=3, name=name)[0]
+
+ if controlType == "square":
+ control = cmds.circle(c=(0, 0, 0), s=4, sw=360, r=size * scale, d=1, name=name)[0]
+ cmds.setAttr(control + ".rz", 45)
+
+ if controlType == "arrow":
+ control = cmds.curve(name=name, d=1,
+ p=[(0, -45, 0), (5, -45, 0), (5, -62, 0), (10, -62, 0), (0, -72, 0), (-10, -62, 0),
+ (-5, -62, 0), (-5, -45, 0), (0, -45, 0)])
+ cmds.xform(control, cp=True)
+ bounds = cmds.exactWorldBoundingBox(control)
+ length = abs(bounds[1] - bounds[4]) / 2
+ cmds.xform(control, r=True, rp=(0, length, 0), sp=(0, length, 0))
+ cmds.move(0, 0, 0, rpr=True)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "arrowOnBall":
+ control = cmds.curve(name=name, d=1, p=[(0.80718, 0.830576, 8.022739), (0.80718, 4.219206, 7.146586),
+ (0.80718, 6.317059, 5.70073), (2.830981, 6.317059, 5.70073),
+ (0, 8.422749, 2.94335), (-2.830981, 6.317059, 5.70073),
+ (-0.80718, 6.317059, 5.70073), (-0.80718, 4.219352, 7.146486),
+ (-0.80718, 0.830576, 8.022739), (-4.187851, 0.830576, 7.158003),
+ (-6.310271, 0.830576, 5.705409), (-6.317059, 2.830981, 5.7007),
+ (-8.422749, 0, 2.94335), (-6.317059, -2.830981, 5.70073),
+ (-6.317059, -0.830576, 5.70073), (-4.225134, -0.830576, 7.142501),
+ (-0.827872, -0.830576, 8.017446), (-0.80718, -4.176512, 7.160965),
+ (-0.80718, -6.317059, 5.70073), (-2.830981, -6.317059, 5.70073),
+ (0, -8.422749, 2.94335), (2.830981, -6.317059, 5.70073),
+ (0.80718, -6.317059, 5.70073), (0.80718, -4.21137, 7.151987),
+ (0.80718, -0.830576, 8.022739), (4.183345, -0.830576, 7.159155),
+ (6.317059, -0.830576, 5.70073), (6.317059, -2.830981, 5.70073),
+ (8.422749, 0, 2.94335), (6.317059, 2.830981, 5.70073),
+ (6.317059, 0.830576, 5.70073), (4.263245, 0.830576, 7.116234),
+ (0.80718, 0.830576, 8.022739)])
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "semiCircle":
+ control = cmds.curve(name=name, d=3,
+ p=[(0, 0, 0), (7, 0, 0), (8, 0, 0), (5, 4, 0), (0, 5, 0), (-5, 4, 0), (-8, 0, 0),
+ (-7, 0, 0), (0, 0, 0)])
+ cmds.xform(control, ws=True, t=(0, 5, 0))
+ cmds.xform(control, ws=True, piv=(0, 0, 0))
+ cmds.makeIdentity(control, t=1, apply=True)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "pin":
+ control = cmds.curve(name=name, d=1, p=[(12, 0, 0), (0, 0, 0), (-12, -12, 0), (-12, 12, 0), (0, 0, 0)])
+ cmds.xform(control, ws=True, piv=[12, 0, 0])
+ cmds.setAttr(control + ".scaleY", .5)
+ cmds.makeIdentity(control, t=1, apply=True)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "sphere":
+ points = [(0, 0, 1), (0, 0.5, 0.866), (0, 0.866025, 0.5), (0, 1, 0), (0, 0.866025, -0.5), (0, 0.5, -0.866025),
+ (0, 0, -1), (0, -0.5, -0.866025), (0, -0.866025, -0.5), (0, -1, 0), (0, -0.866025, 0.5),
+ (0, -0.5, 0.866025), (0, 0, 1), (0.707107, 0, 0.707107), (1, 0, 0), (0.707107, 0, -0.707107),
+ (0, 0, -1), (-0.707107, 0, -0.707107), (-1, 0, 0), (-0.866025, 0.5, 0), (-0.5, 0.866025, 0),
+ (0, 1, 0), (0.5, 0.866025, 0), (0.866025, 0.5, 0), (1, 0, 0), (0.866025, -0.5, 0),
+ (0.5, -0.866025, 0), (0, -1, 0), (-0.5, -0.866025, 0), (-0.866025, -0.5, 0), (-1, 0, 0),
+ (-0.707107, 0, 0.707107), (0, 0, 1)]
+ control = cmds.curve(name=name, d=1, p=points)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ return control
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def colorControl(control, value=False):
+ # unlock overrides
+ cmds.setAttr(control + ".overrideEnabled", 1)
+
+ if value == False:
+ # get the world position of the control
+ worldPos = cmds.xform(control, q=True, ws=True, t=True)
+ if worldPos[0] > 0:
+ cmds.setAttr(control + ".overrideColor", 6)
+ else:
+ cmds.setAttr(control + ".overrideColor", 13)
+
+ else:
+ cmds.setAttr(control + ".overrideColor", value)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def fixSkinWeights():
+ # get selection
+ selection = cmds.ls(sl=True)
+ newSelection = []
+
+ # loop through selection getting the shapes and meshes
+ for each in selection:
+ shapes = cmds.listRelatives(each, shapes=True, pa=True)
+ meshes = cmds.ls(shapes, type="mesh")
+
+ # duplicate the object
+ duplicateObject = cmds.duplicate(each, rr=True)
+ cmds.delete(duplicateObject[0], ch=True)
+
+ newShapes = cmds.listRelatives(duplicateObject[0], shapes=True, pa=True)
+ newMeshes = cmds.ls(newShapes, type="mesh")
+
+ # find skinCluster of original object
+ skinCluster = findRelatedSkinCluster(each)
+
+ # find bones in skinCluster
+ bones = cmds.listConnections(skinCluster + ".matrix", d=True)
+ maxInfs = cmds.skinCluster(skinCluster, q=True, mi=True)
+ newSkin = cmds.skinCluster(bones, duplicateObject[0], mi=maxInfs, dr=4)[0]
+ cmds.copySkinWeights(ss=skinCluster, ds=newSkin, nm=True)
+
+ # rename
+ cmds.delete(each)
+ newObj = cmds.rename(duplicateObject, each)
+ newSelection.append(newObj)
+
+ cmds.select(newSelection)
+
+
+def export_skin_weights(file_path=None, geometry=None):
+ """Exports out skin weight from selected geometry"""
+ data = list()
+ # error handling
+ if not file_path:
+ return OpenMaya.MGlobal_displayError("No file path given.")
+ if not geometry:
+ geometry = _geometry_check(geometry)
+ if not geometry:
+ return OpenMaya.MGlobal_displayError("No valid geometry.")
+
+ # build up skin data
+ skin_clusters = find_skin_clusters(geometry)
+ if not skin_clusters:
+ skin_message = "No skin clusters found on {0}.".format(geometry)
+ OpenMaya.MGlobal_displayWarning(skin_message)
+ for skin_cluster in skin_clusters:
+ skin_data_init = SkinData(skin_cluster)
+ skin_data = skin_data_init.gather_data()
+ data.append(skin_data)
+ args = [skin_data_init.skin_cluster, file_path]
+ export_message = "SkinCluster: {0} has " \
+ "been exported to {1}.".format(*args)
+ OpenMaya.MGlobal_displayInfo(export_message)
+
+ # dump data
+ file_path = utils.win_path_convert(file_path)
+ data = json.dumps(data, sort_keys=True, ensure_ascii=True, indent=2)
+ fobj = open(file_path, 'wb')
+ fobj.write(data)
+ fobj.close()
+
+
+def find_skin_clusters(nodes):
+ """Uses all incoming to search relatives to
+ find associated skinCluster.
+ @PARAMS:
+ nodes: list
+ """
+ skin_clusters = list()
+ if not isinstance(nodes, list):
+ nodes = [nodes]
+ relatives = cmds.listRelatives(nodes, ad=True, path=True)
+ all_incoming = utils.find_all_incoming(relatives)
+ for node in all_incoming:
+ if cmds.nodeType(node) == "skinCluster":
+ if node not in skin_clusters:
+ skin_clusters.append(node)
+ return skin_clusters
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def findRelatedSkinCluster(object):
+ 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]
+
+ dagPath = cmds.ls(geoTransform, long=True)[0]
+
+ if geoTransform == object:
+ return cluster
+ elif dagPath == object:
+ return cluster
+
+
+def import_skin_weights(file_path=None, geometry=None, remove_unused=None):
+ # error handling
+ if not file_path:
+ return OpenMaya.MGlobal_displayError("No file path given.")
+ if not geometry:
+ return
+
+ # load data
+ if not os.path.exists(file_path):
+ path_message = "Could not find {0} file.".format(file_path)
+ return OpenMaya.MGlobal_displayWarning(path_message)
+
+ fobj = open(file_path, "rb")
+ data = json.load(fobj)
+
+ # check verts
+ vert_check = _vert_check(data, geometry)
+ if not vert_check:
+ return
+
+ # import skin weights
+ _import_skin_weights(data, geometry, file_path, remove_unused)
+
+
+def _import_skin_weights(data, geometry, file_path, remove_unused=None):
+ # loop through skin data
+ for skin_data in data:
+ geometry = skin_data["shape"]
+ if not cmds.objExists(geometry):
+ continue
+
+ skin_clusters = find_skin_clusters(geometry)
+ if skin_clusters:
+ skin_cluster = SkinData(skin_clusters[0])
+ skin_cluster.set_data(skin_data)
+ else:
+ # TODO: make joint remapper, Chris has a setup for this already
+ skin_cluster = _create_new_skin_cluster(skin_data, geometry)
+ if not skin_cluster:
+ continue
+ skin_cluster[0].set_data(skin_data)
+ if remove_unused:
+ if skin_clusters:
+ _remove_unused_influences(skin_clusters[0])
+ else:
+ _remove_unused_influences(skin_cluster[1])
+ OpenMaya.MGlobal_displayInfo("Imported {0}".format(file_path))
+
+
+def _vert_check(data, geometry):
+ # check vertex count
+ for skin_data in data:
+ geometry = skin_data["shape"]
+ vert_count = cmds.polyEvaluate(geometry, vertex=True)
+ import_vert_count = len(skin_data["blendWeights"])
+ if vert_count != import_vert_count:
+ vert_message = "The vert count does not match for this geometry: " + str(geometry)
+ return OpenMaya.MGlobal_displayError(vert_message)
+ return True
+
+
+def _create_new_skin_cluster(skin_data, geometry):
+ # check joints
+ joints = skin_data["weights"].keys()
+ unused_joints = list()
+ scene_joints = set([utils.remove_namespace(joint) for joint \
+ in cmds.ls(type="joint")])
+ for joint in joints:
+ if not joint in scene_joints:
+ unused_joints.append(joint)
+ # TODO: make joint remapper, Chris has a setup for this already
+ if unused_joints and not scene_joints:
+ return
+
+ skin_cluster = cmds.skinCluster(joints, geometry, tsb=True, nw=2,
+ n=skin_data["skinCluster"])[0]
+ return (SkinData(skin_cluster), skin_cluster)
+
+
+def _remove_unused_influences(skin_cluster):
+ influences_to_remove = list()
+ weighted_influences = cmds.skinCluster(skin_cluster, q=True, wi=True)
+ final_transforms = cmds.skinCluster(skin_cluster, q=True, inf=True)
+ for influence in final_transforms:
+ if influence not in weighted_influences:
+ influences_to_remove.append(influence)
+ for influence in influences_to_remove:
+ cmds.skinCluster(skin_cluster, e=True, ri=influence)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# CLASSES
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class SkinWeights(object):
+ '''
+ Takes in a .WEIGHTS file from disk, an existing skinCluster, or a skinned mesh as inputs
+ If qtProgbar == 'internal' then it will generate a modal progbar
+ '''
+
+ def __init__(self, skinFile=None, skinCluster=None, mesh=None):
+ self.skin = None
+ self.vertices = None
+ self.skinFile = None
+ self.mesh = None
+ self.uvSet = None
+ self.numVerts = None
+ self.skinDict = None
+ self.joints = []
+ self.applied = False
+
+ # TODO: check that the file is really on disk
+ if skinFile:
+ if os.path.exists(skinFile):
+ self.skinDict = self.importSkinFile(skinFile)
+ self.skinFile = skinFile
+ else:
+ cmds.warning('riggingUtils.SkinWeights: cannot find file on disk: ' + skinFile)
+ if skinCluster:
+ if cmds.objExists(skinCluster):
+ self.skin = skinCluster
+ self.mesh = cmds.listConnections(skinCluster + '.outputGeometry')[0]
+ else:
+ cmds.warning('riggingUtils.SkinWeights: cannot find skinCluster: ' + skinCluster)
+ if mesh:
+ if cmds.objExists(mesh):
+ self.mesh = mesh
+ sk = findRelatedSkinCluster(mesh)
+ if sk:
+ self.skin = sk
+ else:
+ cmds.warning('riggingUtils.SkinWeights: mesh has no skinCluster: ' + mesh)
+ else:
+ cmds.warning('riggingUtils.SkinWeights: Cannot find mesh: ' + mesh)
+
+ def exportSkinWeights(self, filePath, qtProgbar=None, freq=1):
+ time1 = time.time()
+ vertPoints = cmds.getAttr(self.mesh + '.vtx[*]')
+
+ f = {}
+ header = {}
+ vtxDict = {}
+ skin = findRelatedSkinCluster(self.mesh)
+
+ numVerts = cmds.polyEvaluate(self.mesh, vertex=1)
+
+ # fill header
+ header['mesh'] = self.mesh
+ header['skinCluster'] = skin
+ header['numVerts'] = numVerts
+ header['uvSet'] = cmds.polyUVSet(self.mesh, currentUVSet=1, q=1)[0]
+
+ # fill vtxDict
+ if qtProgbar == 'internal':
+ from System.interfaceUtils import progressDialog
+ qtProgbar = progressDialog((0, numVerts), label="Exporting skin weights...")
+ elif qtProgbar:
+ qtProgbar.setRange(0, numVerts)
+ qtProgbar.setValue(0)
+ for vtx in range(0, numVerts):
+ vtxDict[vtx] = {}
+ vtxDict[vtx]['world'] = tuple(cmds.pointPosition(self.mesh + '.vtx[' + str(vtx) + ']', w=1))
+ vtxDict[vtx]['local'] = tuple(cmds.pointPosition(self.mesh + '.vtx[' + str(vtx) + ']', l=1))
+ # we will loop all the skinning influences of this vertex and record their names and values
+ vtxDict[vtx]['skinning'] = []
+
+ joints = cmds.skinPercent(skin, self.mesh + '.vtx[' + str(vtx) + ']', q=1, t=None)
+ influence_value = cmds.skinPercent(skin, self.mesh + '.vtx[' + str(vtx) + ']', q=True, v=True)
+
+ for jnt, val in zip(joints, influence_value):
+ if val > 0:
+ vtxDict[vtx]['skinning'].append([jnt, val])
+
+ uv = cmds.polyListComponentConversion(self.mesh + ".vtx[" + str(vtx) + "]", fv=True, tuv=True)
+ vtxDict[vtx]['uv'] = cmds.polyEditUV(uv, q=True)
+ if qtProgbar:
+ qtProgbar.setValue(vtx)
+
+ f['header'] = header
+ f['vtxDict'] = vtxDict
+ wFile = file(filePath, mode='w')
+ wFile.write(json.dumps(f, wFile, indent=4))
+ wFile.close()
+
+ # TODO: validate this
+ time2 = time.time()
+ print 'exportSkinWeights: Weights saved to: %s in %0.3f sec' % (filePath, (time2 - time1))
+ return True
+
+ def importSkinFile(self, filePath):
+ import json
+ if os.path.exists(filePath):
+ f = open(filePath, 'r')
+ self.skinDict = json.load(f)
+ f.close()
+
+ self.skin = self.skinDict['header']['skinCluster']
+ self.mesh = self.skinDict['header']['mesh']
+ self.uvSet = self.skinDict['header']['uvSet']
+ self.numVerts = self.skinDict['header']['numVerts']
+ self.vertices = self.skinDict['vtxDict']
+
+ for vtx in self.skinDict['vtxDict']:
+ # build joint list
+ for inf in self.skinDict['vtxDict'][vtx]['skinning']:
+ if inf[0] not in self.joints:
+ self.joints.append(inf[0])
+
+ # This validates that the influence joints exist
+ def verifiedInfluences(self, joints):
+ jointsInScene = []
+ for jnt in joints:
+ if cmds.objExists(jnt):
+ jointsInScene.append(jnt)
+ else:
+ cmds.warning('SKIN WEIGHT IMPORT: Cannot find joint that matches', jnt, 'in the current scene.')
+ return jointsInScene
+
+ def square_distance(self, pointA, pointB):
+ # squared euclidean distance
+ distance = 0
+ dimensions = len(pointA) # assumes both points have the same dimensions
+ for dimension in range(dimensions):
+ distance += (pointA[dimension] - pointB[dimension]) ** 2
+ return distance
+
+ # Toss the UV list to a point array
+ def uvListToPointArray(self, uvPoints):
+ returnArray = []
+ if len(uvPoints) > 2:
+ for i in range(0, len(uvPoints), 2):
+ newPos = [uvPoints[i], uvPoints[i + 1]]
+ if len(newPos) != 2:
+ cmds.error(newPos + ' not 2d!')
+ returnArray.append(newPos)
+ else:
+ returnArray.append(uvPoints)
+ return returnArray
+
+ def applySkinWeights(self, mesh, applyBy='Vertex Order', killSkin=0, debug=0, qtProgbar=None):
+ self.skin = findRelatedSkinCluster(mesh)
+
+ # remove existing skin cluster
+ if self.skin and killSkin or not self.skin:
+ if killSkin:
+ bindposes = cmds.listConnections(self.skin + '.bindPose')
+ bindposes.append(self.skin)
+ for obj in bindposes:
+ cmds.delete(obj)
+ bind = self.verifiedInfluences(self.joints)
+ bind.append(mesh)
+ sel = cmds.ls(sl=1)
+ cmds.select(cl=1)
+ cmds.select(bind)
+ self.skin = cmds.skinCluster(skinMethod=0, name=(mesh.split('|')[-1] + "_skinCluster"))[0]
+ cmds.select(sel)
+
+ if self.skin:
+ # build kdTree if required
+ vtxDict = {}
+ vtxList = []
+ vtxTree = None
+
+ # parse weight file
+ if applyBy == 'World Position':
+ for vtx in self.vertices:
+ pos = self.vertices[vtx]['world']
+ vtxDict[str(pos)] = vtx
+ vtxList.append(pos)
+ start = time.time()
+ vtxTree = mathUtils.KDTree.construct_from_data(vtxList)
+ elapsed = (time.time() - start)
+ if applyBy == 'Local Position':
+ for vtx in self.vertices:
+ pos = self.vertices[vtx]['local']
+ vtxDict[str(pos)] = vtx
+ vtxList.append(pos)
+ start = time.time()
+ vtxTree = mathUtils.KDTree.construct_from_data(vtxList)
+ elapsed = (time.time() - start)
+ if applyBy == 'UV Position':
+ for vtx in self.vertices:
+ pos = self.vertices[vtx]['uv']
+ # When one vtx has multiple UV locations, we add it to the mapping table once for each location
+ if len(pos) > 2:
+ for i in range(0, len(pos), 2):
+ newPos = [pos[i], pos[i + 1]]
+ if debug:
+ print 'NEWPOS:', newPos
+ if len(newPos) > 2:
+ cmds.error(newPos + ' not 2d!')
+ vtxDict[str(newPos)] = vtx
+ vtxList.append(newPos)
+
+ else:
+ vtxDict[str(pos)] = vtx
+ vtxList.append(pos)
+ if debug:
+ print 'VTXLIST:', vtxList
+ print 'VTXDICT:', vtxDict
+ start = time.time()
+ vtxTree = mathUtils.KDTree.construct_from_data(vtxList)
+ elapsed = (time.time() - start)
+
+ verts = cmds.polyEvaluate(mesh, vertex=1)
+ # progress bar
+ if qtProgbar == 'internal':
+ from System.interfaceUtils import progressDialog
+ qtProgbar = progressDialog((0, verts), label="Importing skin weights...")
+ elif qtProgbar:
+ qtProgbar.setRange(0, verts)
+ qtProgbar.setValue(0)
+
+ # set the weights
+ try:
+ cmds.undoInfo(openChunk=True)
+ time1 = time.time()
+ for vtx in range(0, verts):
+ if applyBy == 'Vertex Order':
+ # TODO: check if numVerts match
+ cmds.skinPercent(self.skin, mesh + ".vtx[" + str(vtx) + "]",
+ transformValue=self.vertices[str(vtx)]['skinning'])
+ if applyBy == 'World Position':
+ if vtxTree:
+ vtxPos = \
+ vtxTree.query(tuple(cmds.pointPosition(mesh + '.vtx[' + str(vtx) + ']', w=1)), t=1)[0]
+ vtxMaya = mesh + '.vtx[' + str(vtx) + ']'
+ if debug:
+ print 'Original vertex', vtxDict[str(vtxPos)], 'maps to new vtx', vtxMaya
+ cmds.skinPercent(self.skin, vtxMaya,
+ transformValue=self.vertices[vtxDict[str(vtxPos)]]['skinning'])
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No existing kdTree built.')
+ if applyBy == 'Local Position':
+ if vtxTree:
+ vtxPos = vtxTree.query(tuple(cmds.pointPosition(mesh + '.vtx[' + str(vtx) + ']')), t=1)[0]
+ vtxMaya = mesh + '.vtx[' + str(vtx) + ']'
+ if debug:
+ print 'Original vertex', vtxDict[str(vtxPos)], 'maps to new vtx', vtxMaya
+ cmds.skinPercent(self.skin, vtxMaya,
+ transformValue=self.vertices[vtxDict[str(vtxPos)]]['skinning'])
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No existing kdTree built.')
+ if applyBy == 'UV Space':
+ if vtxTree:
+ uvPoint = cmds.polyEditUV(
+ cmds.polyListComponentConversion(mesh + ".vtx[" + str(vtx) + "]", fv=True,
+ tuv=True),
+ q=1)
+ pos = self.uvListToPointArray(uvPoint)
+ vtxPos = None
+
+ if debug:
+ print 'uvPoint:', uvPoint
+ print 'pos:', pos
+ # check for multiple uv points assoc with the vert
+ if len(pos) > 1:
+ distance = None
+ vtxPos = None
+ for p in pos:
+ if debug:
+ print pos
+ print p
+ closest = vtxTree.query(tuple(p), t=1)[0]
+ dist = self.square_distance(p, closest)
+ if not distance:
+ distance = dist
+ vtxPos = closest
+ else:
+ if dist < distance:
+ distance = dist
+ vtxPos = closest
+
+ else:
+ vtxPos = vtxTree.query(tuple(pos[0]), t=1)[0]
+ vtxMaya = mesh + '.vtx[' + str(vtx) + ']'
+ if debug:
+ print 'vtxPos:', str(vtxPos)
+ print 'Original UV vertex', vtxDict[str(vtxPos)], 'maps to new UV vtx', vtxMaya
+ print 'SKINNING:', self.vertices[vtxDict[str(vtxPos)]]['skinning'], vtxDict[str(vtxPos)]
+ cmds.skinPercent(self.skin, vtxMaya,
+ transformValue=self.vertices[vtxDict[str(vtxPos)]]['skinning'])
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No existing kdTree built.')
+
+ # Update the progressbar, but only every 100th vertex
+ if qtProgbar:
+ if vtx % 100 == 0 or (vtx + 1) == verts:
+ qtProgbar.setValue(vtx + 1)
+ time2 = time.time()
+ print 'importSkinWeights: Weights loaded from %s in %0.3f sec' % (self.skinFile, (time2 - time1))
+ except Exception as e:
+ print e
+ finally:
+ cmds.undoInfo(closeChunk=True)
+
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No skinCluster found on: ' + mesh)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class SkinData(object):
+ def __init__(self, skin_cluster):
+
+ # globals/data
+ self.skin_cluster = skin_cluster
+ deformer = cmds.deformer(skin_cluster, q=True, g=True)[0]
+ self.shape = cmds.listRelatives(deformer, parent=True, path=True)[0]
+ self.mobject = utils.get_mobject(self.skin_cluster)
+ self.skin_set = OpenMayaAnim.MFnSkinCluster(self.mobject)
+ self.data = {
+ "weights": dict(),
+ "blendWeights": list(),
+ "skinCluster": self.skin_cluster,
+ "shape": self.shape
+ }
+
+ def gather_data(self):
+
+ # get incluence and blend weight data
+ dag_path, mobject = self.get_skin_dag_path_and_mobject()
+ self.get_influence_weights(dag_path, mobject)
+ self.get_blend_weights(dag_path, mobject)
+
+ # add in attribute data
+ for attribute in ATTRIBUTES:
+ self.data[attribute] = cmds.getAttr("{0}.{1}". \
+ format(self.skin_cluster,
+ attribute))
+ return self.data
+
+ def get_skin_dag_path_and_mobject(self):
+ function_set = OpenMaya.MFnSet(self.skin_set.deformerSet())
+ selection_list = OpenMaya.MSelectionList()
+ function_set.getMembers(selection_list, False)
+ dag_path = OpenMaya.MDagPath()
+ mobject = OpenMaya.MObject()
+ selection_list.getDagPath(0, dag_path, mobject)
+ return dag_path, mobject
+
+ def get_influence_weights(self, dag_path, mobject):
+ weights = self._get_weights(dag_path, mobject)
+
+ influence_paths = OpenMaya.MDagPathArray()
+ influence_count = self.skin_set.influenceObjects(influence_paths)
+ components_per_influence = weights.length() / influence_count
+ for count in xrange(influence_paths.length()):
+ name = influence_paths[count].partialPathName()
+ name = utils.remove_namespace(name)
+ weight_data = [weights[influence * influence_count + count] \
+ for influence in xrange(components_per_influence)]
+ self.data["weights"][name] = weight_data
+
+ def _get_weights(self, dag_path, mobject):
+ """Where the API magic happens."""
+ weights = OpenMaya.MDoubleArray()
+ util = OpenMaya.MScriptUtil()
+ util.createFromInt(0)
+ pointer = util.asUintPtr()
+
+ # magic call
+ self.skin_set.getWeights(dag_path, mobject, weights, pointer);
+ return weights
+
+ def get_blend_weights(self, dag_path, mobject):
+ return self._get_blend_weights(dag_path, mobject)
+
+ def _get_blend_weights(self, dag_path, mobject):
+ weights = OpenMaya.MDoubleArray()
+
+ # magic call
+ self.skin_set.getBlendWeights(dag_path, mobject, weights)
+ blend_data = [weights[blend_weight] for \
+ blend_weight in xrange(weights.length())]
+ self.data["blendWeights"] = blend_data
+
+ def set_data(self, data):
+ """Final point for importing weights. Sets and applies influences
+ and blend weight values.
+ @PARAMS:
+ data: dict()
+ """
+ self.data = data
+ dag_path, mobject = self.get_skin_dag_path_and_mobject()
+ self.set_influence_weights(dag_path, mobject)
+ self.set_blend_weights(dag_path, mobject)
+
+ # set skinCluster Attributes
+ for attribute in ATTRIBUTES:
+ cmds.setAttr('{0}.{1}'.format(self.skin_cluster, attribute),
+ self.data[attribute])
+
+ def set_influence_weights(self, dag_path, mobject):
+ weights = self._get_weights(dag_path, mobject)
+ influence_paths = OpenMaya.MDagPathArray()
+ influence_count = self.skin_set.influenceObjects(influence_paths)
+ components_per_influence = weights.length() / influence_count
+
+ # influences
+ unused_influences = list()
+ influences = [influence_paths[inf_count].partialPathName() for \
+ inf_count in xrange(influence_paths.length())]
+
+ # build influences/weights
+ for imported_influence, imported_weights in self.data['weights'].items():
+ for inf_count in xrange(influence_paths.length()):
+ influence_name = influence_paths[inf_count].partialPathName()
+ influence_name = utils.remove_namespace(influence_name)
+ if influence_name == imported_influence:
+ # set the weights
+ for count in xrange(components_per_influence):
+ weights.set(imported_weights[count],
+ count * influence_count + inf_count)
+ influences.remove(influence_name)
+ break
+ else:
+ unused_influences.append(imported_influence)
+
+ # TODO: make joint remapper
+ if unused_influences and influences:
+ OpenMaya.MGlobal_displayWarning("Make a joint remapper, Aaron!")
+
+ # set influences
+ influence_array = OpenMaya.MIntArray(influence_count)
+ for count in xrange(influence_count):
+ influence_array.set(count, count)
+ # set weights
+ self.skin_set.setWeights(dag_path, mobject, influence_array, weights, False)
+
+ def set_blend_weights(self, dag_path, mobject):
+ blend_weights = OpenMaya.MDoubleArray(len(self.data['blendWeights']))
+ for influence, weight in enumerate(self.data['blendWeights']):
+ blend_weights.set(weight, influence)
+ self.skin_set.setBlendWeights(dag_path, mobject, blend_weights)
diff --git a/Core/Scripts/System/utils.py b/Core/Scripts/System/utils.py
new file mode 100644
index 0000000..fa8dbc2
--- /dev/null
+++ b/Core/Scripts/System/utils.py
@@ -0,0 +1,1159 @@
+"""
+Author: Jeremy Ernst
+
+########
+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 json
+import math
+import os
+import re
+
+import maya.cmds as cmds
+import maya.mel as mel
+from maya import OpenMaya, OpenMayaAnim
+
+import interfaceUtils as interfaceUtils
+import riggingUtils as riggingUtils
+from ThirdParty.Qt import QtWidgets
+
+# maya 2016< maya2017> compatability
+try:
+ import shiboken as shiboken
+except:
+ import shiboken2 as shiboken
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+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.
+ """
+
+ networkNodes = cmds.ls(type="network")
+ for node in networkNodes:
+ attrs = cmds.listAttr(node)
+
+ 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.
+ """
+
+ modules = []
+ networkNodes = cmds.ls(type="network")
+ for node in networkNodes:
+ attrs = cmds.listAttr(node)
+
+ if "parent" not in attrs:
+ modules.append(node)
+
+ return modules
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+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 = []
+ networkNodes = cmds.ls(type="network")
+ for node in networkNodes:
+ attrs = cmds.listAttr(node)
+
+ if "parent" in attrs:
+ 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
+ for node in networkNodes:
+ 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.
+ if offsetMover:
+ moverName = moduleName + "_" + moverName
+
+ if cmds.objExists(moverName):
+ if moverName.find("_mover") != -1:
+ return moverName
+ else:
+ moverName += "_mover_offset"
+
+ if cmds.objExists(moverName):
+ return moverName
+
+ if globalMover:
+ moverName = moduleName + "_" + moverName
+
+ if cmds.objExists(moverName):
+ if moverName.find("_mover") != -1:
+ return moverName
+ else:
+ moverName += "_mover"
+
+ if cmds.objExists(moverName):
+ return moverName
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+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.
+ """
+
+ grp = name + "_mover_grp"
+
+ if cmds.objExists(grp):
+ topLevelGrp = cmds.listRelatives(grp, children=True)
+
+ if len(topLevelGrp) > 0:
+ 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):
+ cmds.delete(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)
+ if prefix == "":
+ 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):
+ """
+ Recursively finds all unique incoming dependencies for the specified node.
+ """
+
+ 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)
+ if namespace:
+ 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)
+ progBar.show()
+
+ # 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 = QtWidgets.QMessageBox()
+ 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)
+ if poseDict is not None:
+ 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
+ if exportMeshes and removeBones or removeBones is not None:
+ for mesh in exportMeshes:
+
+ blendshapeList = []
+ deleteShapes = []
+
+ # find blendshapes
+ 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)
+
+ # 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, i, shape, 1.0))
+
+ else:
+ cmds.select([shape, mesh], r=True)
+ cmds.blendShape(name=bshapeName)
+ cmds.select(clear=True)
+
+ for each in deleteShapes:
+ cmds.delete(each)
+ progBar.setFormat("Deleting Mesh History..")
+ progBar.setValue(progBar.value() + 1)
+
+ # go back to model pose
+ for inst in mainUI.moduleInstances:
+ try:
+ inst.setupForRigPose()
+ inst.setReferencePose("modelPose")
+ inst.cleanUpRigPose()
+ except:
+ pass
+
+ # import skin weights
+ if exportMeshes and removeBones or removeBones is not None:
+
+ for mesh in exportMeshes:
+ progBar.setFormat("Importing Skin Weights")
+ progBar.setValue(progBar.value() + 1)
+
+ fullPath = os.path.join(cmds.internalVar(uwd=True), "artv2")
+ fullPath = os.path.join(fullPath, "weights")
+ 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..")
+ progBar.setValue(progBar.value() + 1)
+
+ # delete joints to remove
+ if removeBones is not None:
+ progBar.setFormat("Deleting Chosen Bones..")
+ progBar.setValue(progBar.value() + 1)
+
+ for entry in removeBones:
+ deleteBones = entry[1]
+ for bone in deleteBones:
+ try:
+ cmds.delete(bone)
+ except Exception, e:
+ print "Unable to remove joint: " + str(bone)
+ print e
+
+ # handle morph targets
+ blendShapeNodes = []
+ blendShapeMeshes = []
+
+ for mesh in exportMeshes:
+ progBar.setFormat("Checking for Morph Targets..")
+ progBar.setValue(progBar.value() + 1)
+
+ 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:
+ blendShapeNodes.append(blendshape)
+ # get blendshapes
+ attrs = cmds.listAttr(blendshape, m=True, string="weight")
+ if attrs is not None:
+ for attr in attrs:
+ if cmds.objExists(attr):
+ blendShapeMeshes.append(attr)
+
+ # before building selection, remove any connections from our skeleton
+ progBar.setFormat("Removing Connections on Skeleton..")
+ progBar.setValue(progBar.value() + 1)
+
+ 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)
+ cmds.select(clear=True)
+
+ # build selection
+ progBar.setFormat("Building Selection For Export..")
+ progBar.setValue(progBar.value() + 1)
+
+ for mesh in exportMeshes:
+ 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())
+
+ selection = cmds.ls(sl=True)
+ 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
+ characterNode = returnCharacterModule()
+ 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.append(parent)
+
+ 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(mayaLoc, "bin"))
+ mayapy = returnFriendlyPath(os.path.join(mayapy, "mayapy"))
+
+
+ return mayapy