aboutsummaryrefslogtreecommitdiff
path: root/Core/Scripts/System/utils.py
diff options
context:
space:
mode:
authorMobileMachine\jeremy <[email protected]>2017-06-06 22:59:03 -0400
committerMobileMachine\jeremy <[email protected]>2017-06-06 22:59:03 -0400
commit24725fa8681f906ab44d80687c09fecc171a2896 (patch)
tree312a601df29aca7f8db9f44082d96ebc7a679138 /Core/Scripts/System/utils.py
parentInitial commit (diff)
downloadartv2-24725fa8681f906ab44d80687c09fecc171a2896.tar.xz
artv2-24725fa8681f906ab44d80687c09fecc171a2896.zip
Initial Submission
First submission of current state of ARTv2. Currently considered to be in Alpha. There are a couple of animation tools not implemented yet, and one module not implemented yet, as well as incomplete documentation.
Diffstat (limited to 'Core/Scripts/System/utils.py')
-rw-r--r--Core/Scripts/System/utils.py1159
1 files changed, 1159 insertions, 0 deletions
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