diff options
| author | MobileMachine\jeremy <[email protected]> | 2017-06-06 22:59:03 -0400 |
|---|---|---|
| committer | MobileMachine\jeremy <[email protected]> | 2017-06-06 22:59:03 -0400 |
| commit | 24725fa8681f906ab44d80687c09fecc171a2896 (patch) | |
| tree | 312a601df29aca7f8db9f44082d96ebc7a679138 /Core/Scripts/System | |
| parent | Initial commit (diff) | |
| download | artv2-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.py | 392 | ||||
| -rw-r--r-- | Core/Scripts/System/ART_Reporter.py | 246 | ||||
| -rw-r--r-- | Core/Scripts/System/ART_RigModule.py | 3012 | ||||
| -rw-r--r-- | Core/Scripts/System/ART_Settings.py | 295 | ||||
| -rw-r--r-- | Core/Scripts/System/ART_StripFbxNamespace.py | 40 | ||||
| -rw-r--r-- | Core/Scripts/System/ART_Updater.py | 600 | ||||
| -rw-r--r-- | Core/Scripts/System/__init__.py | 0 | ||||
| -rw-r--r-- | Core/Scripts/System/git_utils.py | 78 | ||||
| -rw-r--r-- | Core/Scripts/System/interfaceUtils.py | 645 | ||||
| -rw-r--r-- | Core/Scripts/System/mathUtils.py | 207 | ||||
| -rw-r--r-- | Core/Scripts/System/riggingUtils.py | 1448 | ||||
| -rw-r--r-- | Core/Scripts/System/utils.py | 1159 |
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 |