Last active
May 9, 2024 10:16
-
-
Save aobond2/0a1e73068e28f0a13efb5fd74b3a7ca5 to your computer and use it in GitHub Desktop.
Validate and export rig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Maya/QT UI template | |
Maya 2023 | |
""" | |
import maya.cmds as cmds | |
import maya.mel as mel | |
from maya import OpenMayaUI as omui | |
from shiboken2 import wrapInstance | |
from PySide2 import QtUiTools, QtCore, QtGui, QtWidgets | |
from functools import partial # optional, for passing args during signal function calls | |
import sys | |
import pymel.core as pm | |
import os, inspect | |
import fnmatch | |
from PySide2.QtGui import QColor | |
class RigExport(QtWidgets.QWidget): | |
""" | |
Create a default tool window. | |
""" | |
window = None | |
def __init__(self, parent = None): | |
""" | |
Initialize class. | |
""" | |
super(RigExport, self).__init__(parent = parent) | |
self.setWindowFlags(QtCore.Qt.Window) | |
# Get script location | |
current_file = inspect.getfile(inspect.currentframe()) | |
currentPath = os.path.dirname(os.path.abspath(current_file)) | |
# Get the UI | |
self.widgetPath = str(currentPath) + "\RigExport.ui" | |
self.widget = QtUiTools.QUiLoader().load(self.widgetPath) | |
self.widget.setParent(self) | |
# set initial window size | |
self.resize(400, 650) | |
# Variables | |
# TODO: change this based on texture format we use | |
self.textureFormat = ".TGA" | |
self.ARMSSuffix = "_ARMS" | |
self.normalSuffix = "_N" | |
self.difuseSuffix = "_D" | |
self.rootName = "root" | |
self.boneInfluenceLimit = 5 | |
self.vertexWrongArray = [] | |
self.meshBoneInfluenceOverLimit = [] | |
self.tagNonExport = "NonExport" | |
self.tagMeshPart = "MeshPart" | |
self.extraAttributeName = "Tags" | |
self.meshPartTagAttributeName = "BodyPart" | |
self.exportArray = [] | |
self.mayaFilePath = cmds.file(q=True, sceneName=True) | |
self.uassetPrefix = "SK_" | |
self.uassetSuffix= "_01" | |
self.materialClean = False | |
self.failArray = [] | |
self.passString = "Pass" | |
self.warningString = "Warning" | |
self.failString = "Fail" | |
self.errorArray = [] | |
self.jointSCWrongArray = [] | |
self.melScriptPath = "C:/dev/Seraph/MayaPlugins/scripts/RigExporter/MeshBabylonExport.mel" | |
self.animationMelScriptPath = "C:/dev/Seraph/MayaPlugins/scripts/RigExporter/AnimationBabylonExport.mel" | |
self.noTextureArray = [] | |
# TODO: Change this | |
self.filePath = "C:/test/" | |
self.meshPartArray = [ | |
"Head", | |
"Hair", | |
"HandL", | |
"HandR", | |
"LegsBase", | |
"LegsUpper", | |
"MainBody", | |
"Shoes", | |
"TorsoBase", | |
"TorsoMid", | |
"TorsoUpper", | |
"Mouth", | |
"Hat", | |
"Glasses", | |
"Eyes" | |
] | |
# locate UI widgets/ reference here | |
self.btn_close = self.widget.findChild(QtWidgets.QPushButton, 'pushButton') | |
self.btn_validate = self.widget.findChild(QtWidgets.QPushButton, 'ValidateButton') | |
self.btn_export = self.widget.findChild(QtWidgets.QPushButton, 'ExportButton') | |
self.btn_cleanUpMaterial = self.widget.findChild(QtWidgets.QPushButton, 'MaterialCleanUpButton') | |
self.btn_addTag = self.widget.findChild(QtWidgets.QPushButton, 'addTagButton') | |
self.btn_meshPartTag = self.widget.findChild(QtWidgets.QPushButton, 'meshPartTagButton') | |
self.btn_deleteTag = self.widget.findChild(QtWidgets.QPushButton, 'deleteTagsButton') | |
self.label_extraMaterial = self.widget.findChild(QtWidgets.QLabel, 'ExtraMaterialLabel') | |
self.label_bindPose = self.widget.findChild(QtWidgets.QLabel, 'BindPoseLabel') | |
self.label_vertexInfluence = self.widget.findChild(QtWidgets.QLabel, 'VertexInfluenceLabel') | |
self.label_currentTag = self.widget.findChild(QtWidgets.QLabel, 'currentTagLabel') | |
self.label_skinCluster = self.widget.findChild(QtWidgets.QLabel, 'SkinClusterLabel') | |
self.graphic_materialCheck = self.widget.findChild(QtWidgets.QGraphicsView, 'MaterialStatus') | |
self.graphic_bindPoseCheck = self.widget.findChild(QtWidgets.QGraphicsView, 'BindPoseStatus') | |
self.graphic_vertexInfluenceCheck = self.widget.findChild(QtWidgets.QGraphicsView, 'VertexInfluenceStatus') | |
self.graphic_skinClusterCheck = self.widget.findChild(QtWidgets.QGraphicsView, 'SkinClusterStatus') | |
self.graphic_skinCluster02Check = self.widget.findChild(QtWidgets.QGraphicsView, 'SkinCluster02Status') | |
self.comboBox_Tags = self.widget.findChild(QtWidgets.QComboBox, 'tagsComboBox') | |
self.comboBox_meshPartTags = self.widget.findChild(QtWidgets.QComboBox, 'meshPartTagsComboBox') | |
self.maxBoneSpinBox = self.widget.findChild(QtWidgets.QSpinBox, 'MaxBoneSpinBox') | |
self.materialTextBrowser = self.widget.findChild(QtWidgets.QTextBrowser, 'MaterialTextBrowser') | |
self.bindPoseTextBrowser = self.widget.findChild(QtWidgets.QTextBrowser, 'BindPoseTextBrowser') | |
self.vertexInfluenceTextBrowser = self.widget.findChild(QtWidgets.QTextBrowser, 'VertexInfluenceTextBrowser') | |
self.skinClusterTextBrowser = self.widget.findChild(QtWidgets.QTextBrowser, 'SkinClusterTextBrowser') | |
self.skinCluster02TextBrowser = self.widget.findChild(QtWidgets.QTextBrowser, 'SkinClusterTextBrowser_2') | |
self.exportTextBrowser = self.widget.findChild(QtWidgets.QTextBrowser, 'ExportTextBrowser') | |
self.btn_zone = self.widget.findChild(QtWidgets.QPushButton, 'ZoneButton') | |
self.btn_skeletalMeshGLBExport = self.widget.findChild(QtWidgets.QPushButton, 'ExportSetButton') | |
self.btn_animationGLBExport = self.widget.findChild(QtWidgets.QPushButton, 'ExportGLBButton') | |
self.btn_batchGLBExport = self.widget.findChild(QtWidgets.QPushButton, 'BatchExportGLBButton') | |
self.text_animationName = self.widget.findChild(QtWidgets.QTextEdit, 'AnimationNameTextEdit') | |
self.btn_textureFix = self.widget.findChild(QtWidgets.QPushButton, 'TextureFixButton') | |
self.btn_render = self.widget.findChild(QtWidgets.QPushButton, 'renderButton') | |
self.btn_batchRender = self.widget.findChild(QtWidgets.QPushButton, 'batchRenderButton') | |
self.btn_variantSkeletalMeshGLBExport = self.widget.findChild(QtWidgets.QPushButton, 'ExportTextureVariantButton') | |
self.btn_updateTags = self.widget.findChild(QtWidgets.QPushButton, 'updateTagsButton') | |
# assign functionality to buttons | |
self.btn_validate.clicked.connect(self.clickedValidate) | |
self.btn_export.clicked.connect(self.exportMeshes) | |
self.btn_cleanUpMaterial.clicked.connect(self.cleanUpMaterial) | |
self.btn_addTag.clicked.connect(self.addTag) | |
self.btn_meshPartTag.clicked.connect(self.meshPartTag) | |
self.btn_deleteTag.clicked.connect(self.deleteTags) | |
self.btn_zone.clicked.connect(self.zoneProcess) | |
self.btn_skeletalMeshGLBExport.clicked.connect(self.skeletalMeshGLBExport) | |
self.btn_animationGLBExport.clicked.connect(self.animationGLBExport) | |
self.btn_batchGLBExport.clicked.connect(self.batchGLBExportWithTags) | |
self.btn_textureFix.clicked.connect(self.textureFix) | |
self.btn_render.clicked.connect(self.doRender) | |
self.btn_batchRender.clicked.connect(self.doBatchRender) | |
self.btn_variantSkeletalMeshGLBExport.clicked.connect(self.variantSkeletalMeshGLBExport) | |
self.btn_updateTags.clicked.connect(self.updateBodyPartTags) | |
# Set some button to disable | |
self.btn_export.setEnabled(False) | |
self.btn_cleanUpMaterial.setEnabled(False) | |
self.btn_zone.setEnabled(False) | |
self.removeSelectionCallback() | |
self.addSelectionCallback() | |
self.updateTagComboBox() | |
def updateTagComboBox(self): | |
self.comboBox_meshPartTags.clear() | |
self.comboBox_meshPartTags.addItems(self.meshPartArray) | |
def addSelectionCallback(self): | |
global selectionChangedCallback | |
selectionChangedCallback = pm.scriptJob(event=['SelectionChanged', self.selectLabel]) | |
def removeSelectionCallback(self): | |
try: | |
global selectionChangedCallback | |
if pm.scriptJob(exists=selectionChangedCallback): | |
pm.scriptJob(kill=selectionChangedCallback) | |
except: | |
print ("no selection callback") | |
def selectLabel(self): | |
selectedObjects = cmds.ls(sl=True) | |
self.label_currentTag.setText(str(selectedObjects)) | |
def meshPartTag(self): | |
print ("MeshPart") | |
allTagsFromQT = self.comboBox_meshPartTags | |
currentTagInQT = allTagsFromQT.currentIndex() | |
selectedObject = cmds.ls(selection=True) | |
neededTags = self.makeMeshPartEnumArray() | |
enumOptions = ':'.join(neededTags) | |
# Add custom attribute | |
for obj in selectedObject: | |
if not (cmds.attributeQuery(self.meshPartTagAttributeName, node=obj, exists=True)): | |
cmds.addAttr(obj, longName=self.meshPartTagAttributeName, attributeType='enum', enumName = enumOptions) | |
# Set attribute default value | |
cmds.setAttr('{}.{}'.format(obj, self.meshPartTagAttributeName), currentTagInQT) | |
def getObjectCenter(self, inputMesh): | |
bb = cmds.exactWorldBoundingBox(inputMesh) | |
center = [(bb[0] + bb[3]) / 2, (bb[1] + bb[4]) / 2, (bb[2] + bb[5]) / 2] | |
distance = max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2]) | |
return (center, distance) | |
def addLights(self): | |
print ("Add lights") | |
#ambientLight = cmds.ambientLight(intensity=15) | |
#ambientLight = pm.createNode('aiSkyDomeLight', name='aiSkyDomeLight1') | |
ambientLight = cmds.shadingNode('aiSkyDomeLight', name='aiSkyDomeLight1', asLight = True) | |
cmds.setAttr(ambientLight + '.intensity', 1.5) | |
cmds.setAttr(ambientLight + '.camera', 0) | |
directionalLight = cmds.directionalLight(rotation=(-52, -50, 0), intensity = 5, rgb = (0.85, 1, 1)) | |
#backLight = cmds.directionalLight(rotation=(-15, 170, 0), intensity = 10, rgb = (1, 0, 0)) | |
#backLight = cmds.directionalLight(rotation=(165, -0.7, 182), intensity = 10, rgb = (1, 0, 0)) | |
backLight = cmds.shadingNode('directionalLight', name='backLight', asLight = True) | |
cmds.setAttr(backLight + '.intensity', 10) | |
cmds.setAttr(backLight + '.rotateX', 165) | |
cmds.setAttr(backLight + '.rotateY', 33) | |
cmds.setAttr(backLight + '.rotateZ', 182) | |
''' | |
cmds.setAttr(f"{directionalLight}.useDepthMapShadows", 1) | |
cmds.setAttr(f"{directionalLight}.dmapResolution", 1024) | |
cmds.setAttr(f"{directionalLight}.dmapFilterSize", 10) | |
cmds.setAttr(f"{backLight}.useDepthMapShadows", 1) | |
cmds.setAttr(f"{backLight}.dmapResolution", 1024) | |
cmds.setAttr(f"{backLight}.dmapFilterSize", 5) | |
''' | |
return ambientLight, directionalLight, backLight | |
def getRenderFileName(self): | |
currentFilePath = cmds.file(q=True, sn=True) | |
currentFileName = os.path.basename(currentFilePath) | |
# Get body type from the file name, example = SK_BodyA_Body_01.ma | |
bodyType = currentFileName.split("_")[1] | |
characterFolderSplitter = "Characters" | |
characterFolderAppend = "Characters/Thumbnails/" | |
currentFilePath = (currentFilePath.split(characterFolderSplitter)[0]) | |
currentFilePath = currentFilePath + characterFolderAppend | |
# Get current selection name | |
selectedObjects = cmds.ls(sl=True) | |
fileName = bodyType + '_' + str(selectedObjects[0]) | |
filePath = currentFilePath + fileName | |
print (filePath) | |
return filePath | |
def setRenderSettings(self): | |
# TODO: expose this | |
cmds.setAttr("defaultResolution.width", 512) | |
cmds.setAttr("defaultResolution.height", 512) | |
cmds.setAttr("defaultResolution.deviceAspectRatio", 1) | |
# TODO: Save to file | |
filePath = self.getRenderFileName() | |
cmds.setAttr("defaultRenderGlobals.imageFormat", 32) | |
cmds.setAttr("hardwareRenderingGlobals.multiSampleEnable", 1) | |
cmds.setAttr("defaultRenderGlobals.imageFilePrefix", filePath, type="string") | |
# Set renderer to hardware | |
#cmds.setAttr('defaultRenderGlobals.currentRenderer', 'mayaHardware2', type='string') | |
cmds.setAttr('defaultRenderGlobals.currentRenderer', 'arnold', type='string') | |
cmds.setAttr("defaultArnoldDriver.ai_translator", "png", type="string") | |
def doBatchRender(self): | |
print ("Batch render") | |
self.unhideAllMeshes() | |
currentFile = self.getCurrentFile() | |
self.checkTag() | |
exportGLB = True | |
# Export all MeshPart | |
# Need to set 2nd argument to True for batch render | |
self.exportMeshParts(exportGLB, True) | |
def doRender(self): | |
# Set render settings here | |
self.setRenderSettings() | |
selectedObjects = cmds.ls(selection=True) | |
print ("-------------------------") | |
print (selectedObjects) | |
center, distance = self.getObjectCenter(selectedObjects[0]) | |
camera_position = [center[0], center[1], center[2] + distance] | |
# Set to render only selected | |
mel.eval("optionVar -intValue renderViewRenderSelectedObj on;") | |
# Create new camera | |
newCamera = cmds.camera(displayResolution=True) | |
cameraShape = newCamera[1] | |
# Set the camera's rotation | |
cmds.xform(newCamera, rotation=[-12, 20, 0]) | |
# Set the camera aperture to correspond to a 1.00 aspect ratio | |
cmds.setAttr(cameraShape + '.horizontalFilmAperture', 1) | |
cmds.setAttr(cameraShape + '.verticalFilmAperture', 1) | |
# Disable other renderable cameras | |
for cam in cmds.ls(type="camera"): | |
if cam != cameraShape: | |
cmds.setAttr(cam + ".renderable", False) | |
# Enable new camera | |
cmds.setAttr(cameraShape + ".renderable", True) | |
cmds.setAttr(cameraShape + ".filmFit", 3) | |
cmds.lookThru(cameraShape) | |
# Set camera position | |
cmds.camera(cameraShape, edit=True, centerOfInterest=distance, position=camera_position) | |
cmds.camera(cameraShape, e=True, aspectRatio=1.00) | |
cmds.viewFit(cameraShape, selectedObjects, f=1) | |
# Add lights | |
ambientLight, directionalLight, backLight = self.addLights() | |
cmds.select(clear=True) | |
for obj in selectedObjects: | |
cmds.select(obj) | |
self.getObjectCenter(obj) | |
pm.runtime.RenderIntoNewWindow() | |
# Do Render | |
#newImage = cmds.render(newCamera, x=512, y=512) | |
# Delete camera | |
if cmds.objExists(cameraShape): | |
cmds.delete(cameraShape) | |
cmds.delete(newCamera[0]) | |
#''' | |
# Delete lights and its transform | |
try: | |
dlRelatives = cmds.listRelatives(directionalLight, parent=True) | |
cmds.delete(dlRelatives, directionalLight) | |
alRelatives = cmds.listRelatives(ambientLight, parent=True) | |
cmds.delete(alRelatives, ambientLight) | |
blRelatives = cmds.listRelatives(backLight, parent=True) | |
cmds.delete(blRelatives, backLight) | |
except: | |
print ("No lights") | |
#''' | |
def processTexture(self): | |
# Get the material of selected object | |
theNodes = cmds.ls(sl = True, dag = True, s = True) | |
shadeEng = cmds.listConnections(theNodes, type = "shadingEngine") | |
materials = cmds.ls(cmds.listConnections(shadeEng), materials = True) | |
for material in materials: | |
textureNode = cmds.listConnections(material, type='file')[0] | |
# Get the filename from the texture node | |
currentTextureFilename = cmds.getAttr("%s.fileTextureName" % textureNode) | |
# Get new path | |
newTexturePath = str(self.findCorrectTexturePath(currentTextureFilename)) | |
# Update path with new path | |
cmds.setAttr("%s.fileTextureName" % textureNode, newTexturePath, type="string") | |
# Convert material to aiStandardMaterial for exporting GLB | |
self.convertMaterialToaiStandardSurface(shadeEng, material, theNodes, len(materials)) | |
def findCorrectTexturePath(self, textureName): | |
currentFilePath = os.path.dirname(cmds.file(q=True, sn=True)) | |
characterFolderSplitter = "Characters" | |
characterFolderAppend = "Characters\Textures" | |
currentFilePath = (currentFilePath.split(characterFolderSplitter)[0]) | |
targetFileName = textureName.split("/")[-1] | |
# Check texture folder for matching name | |
#textureFolder = "C:\dev\M2Main\Source\Engine\M2Content\Characters\Textures" | |
textureFolder = currentFilePath + characterFolderAppend | |
for dirpath, dirnames, filenames in os.walk(textureFolder): | |
for filename in filenames: | |
if fnmatch.fnmatch(filename, targetFileName): | |
targetFileName = (os.path.join(dirpath, filename)) | |
for dirpath, dirnames, filenames in os.walk(dirpath): | |
for filename in filenames: | |
if fnmatch.fnmatch(filename, targetFileName): | |
targetFileName = (os.path.join(dirpath, filename)) | |
newPath = targetFileName | |
return (newPath) | |
def textureFix(self): | |
self.processAllMeshesForTexture() | |
# TODO: Remove this later | |
self.getFBXExportLocation() | |
def has_multiple_materials(self, object_name): | |
# Get all shading groups connected to the object | |
shading_groups = cmds.listConnections(object_name, type='shadingEngine') | |
if not shading_groups: | |
return False | |
# Get all unique materials connected to the shading groups | |
materials = set() | |
for sg in shading_groups: | |
materials.update(cmds.listConnections(sg + '.surfaceShader')) | |
# Check if there are multiple materials | |
return len(materials) > 1 | |
def processAllMeshesForTexture(self): | |
objects = cmds.ls(dag = True, exactType="mesh") | |
for obj in objects: | |
try: | |
cmds.select(obj) | |
self.processTexture() | |
except: | |
print("------------------") | |
def getFBXExportLocation(self): | |
fbxFolderName = "FBX" | |
currentFilePath = os.getcwd() | |
fbxPath = (currentFilePath.split("MA")[0]) + fbxFolderName | |
print ("-------------") | |
print (fbxPath) | |
def zoneProcess(self): | |
print ("Zone Process") | |
groups, meshes = self.getMeshesAndGroups() | |
for m in meshes: | |
print ("---------------------- mesh = " + str(m)) | |
self.boneInfluenceLimit = self.getMaxBoneValue() | |
skin_Cluster = cmds.ls(cmds.listHistory(m), type='skinCluster') | |
if not skin_Cluster: | |
return | |
#bones = cmds.skinCluster(skin_Cluster[0], query=True, influence=True) | |
# Get all vertices | |
vertices = cmds.ls(m + ".vtx[*]", flatten=True) | |
# Auto unwrap the mesh | |
# TODO: Do this in UV2 instead | |
cmds.polyAutoProjection(m, lm=0, pb=0, ibd=1, cm=0, l=2, sc=1) | |
for vertex in vertices: | |
boneInfluence = [] | |
vertexIndex = self.getVertexIndex(vertex) | |
influences = cmds.skinPercent(skin_Cluster[0], vertex, query=True, value=True) | |
boneNames = pm.skinCluster(skin_Cluster[0], query=True, influence=True) | |
for boneName, influence in zip(boneNames, influences): | |
if influence > 0.0: | |
#print("Bone {}: {}, vertex: {}".format(boneName, influence, vertexIndex)) | |
boneInfluence.append(boneName) | |
mainBone = boneInfluence[0] | |
# TODO: Change this later, testing simple unwrap and moving uv | |
self.moveVertexUV(m, vertexIndex, vertex, mainBone) | |
# TODO: Remove this return | |
return | |
def moveVertexUV(self, meshName, vertexIndex, vertex, boneName): | |
#print ("Moving vertex") | |
print (boneName) | |
# Scale down UV to fit the division | |
uvScale = 0.03 | |
newU = self.UV2Position(self.parseBoneName(boneName)) | |
cmds.polyEditUV(vertex, u=newU, v=newU, scaleU = uvScale, scaleV = uvScale) | |
def parseBoneName(self, boneNameInput): | |
# removing uneeded string from boneName | |
boneNameInput = str(boneNameInput) | |
boneNameOutput = boneNameInput.split("'")[0] | |
return boneNameOutput | |
def UV2Position(self, boneName): | |
# Hardcode position here based on bone name. Use Dictionary? | |
# Magic number UV | |
boneUVDict = { | |
"pelvis": 1 | |
} | |
try: | |
newU = (boneUVDict[boneName]) /32 | |
except: | |
newU = 1 | |
return newU | |
def getVertexIndex(self, vertexInput): | |
# Simple string operation to get vertex index | |
return int(vertexInput.split("[")[1].split("]")[0]) | |
def clickedValidate(self): | |
print("--------- Validate") | |
self.extraMaterialCheck() | |
self.bindPoseCheck() | |
self.vertexInfluenceLimitCheck() | |
self.skinClusterCheck() | |
self.jointSkinClusterCheck() | |
# Enable export button | |
if len(self.errorArray) == 0: | |
self.btn_export.setEnabled(True) | |
self.btn_cleanUpMaterial.setEnabled(True) | |
def getSkinClustersPerJoint(self, joint_name): | |
skinClusterList = [] | |
# Get all the deformers connected to the joint | |
deformers = cmds.listConnections(joint_name, type='skinCluster') | |
for d in deformers: | |
if d not in skinClusterList: | |
skinClusterList.append(d) | |
return skinClusterList | |
def get_skin_clusters_in_scene(self): | |
skin_clusters = [] | |
all_meshes = cmds.ls(type='mesh') # Get a list of all mesh nodes in the scene | |
for mesh in all_meshes: | |
skin_cluster = cmds.ls(cmds.listHistory(mesh), type='skinCluster') # Find skin clusters in the history of the mesh | |
if skin_cluster: | |
skin_clusters.extend(skin_cluster) | |
return skin_clusters | |
def getSkinClusterFromJoints(self): | |
# Function to get list of skincluster in joints | |
jointName = self.rootName | |
allSkinClusters = cmds.ls(type='skinCluster') | |
skinClusters = [] | |
for sc in allSkinClusters: | |
influences = cmds.skinCluster(sc, query=True, influence=True) | |
if jointName in influences or any(child in influences for child in cmds.listRelatives(jointName, children=True, type='joint')): | |
skinClusters.append(sc) | |
return skinClusters | |
def getMaxBoneValue(self): | |
maxBones = self.maxBoneSpinBox.value() | |
return maxBones | |
def getMeshesAndGroups(self): | |
meshes = cmds.listRelatives(cmds.ls(geometry=True), parent=True, fullPath=True) or [] | |
allTransforms = cmds.ls(type='transform') | |
# Get transform with children and dont have a shape node as one of its children, that's "Group" in Maya | |
groups = [node for node in allTransforms if cmds.listRelatives(node, children=True) and not cmds.listRelatives(node, shapes=True)] | |
return groups, meshes | |
def checkTag(self): | |
print ("Check tag") | |
# Get all objects that's MeshPart, in this case have extra attributes with index 1 | |
groups, meshes = self.getMeshesAndGroups() | |
# Check groups for tags | |
for g in groups: | |
try: | |
allAttr = cmds.listAttr(g, userDefined = True) | |
for a in allAttr: | |
if (a == self.extraAttributeName): | |
enumValues = cmds.getAttr(f"{g}.{a}") | |
if enumValues == 1 and (g not in self.exportArray): | |
self.exportArray.append(g) | |
except: | |
continue | |
# Now check meshes, TODO: this probably can be done in 1 go | |
for m in meshes: | |
try: | |
allAttr = cmds.listAttr(m, userDefined = True) | |
for x in allAttr: | |
if (x == self.extraAttributeName): | |
enumValues = cmds.getAttr(f"{m}.{x}") | |
if enumValues == 1 and (m not in self.exportArray): | |
self.exportArray.append(m) | |
except: | |
continue | |
# VALIDATION PART | |
def compareMaterial(self): | |
objectWithMat = set() | |
allMat = set(cmds.ls(mat=True)) | |
for m in allMat: | |
connectedObjects = cmds.listConnections(m, type="shape") | |
if connectedObjects: | |
objectWithMat.update(connectedObjects) | |
unusedMaterial = allMat - objectWithMat | |
return (unusedMaterial) | |
def extraMaterialCheck(self): | |
self.compareMaterial() | |
print ("--------- Check extra material") | |
unusedMaterial = self.compareMaterial() | |
materialText = self.label_extraMaterial.text() | |
if (len(unusedMaterial) == 0) or (self.materialClean == True): | |
self.updateStatusGraphic(self.graphic_materialCheck, self.passString) | |
self.updateValidationResult(self.passString, self.materialTextBrowser) | |
else: | |
self.updateStatusGraphic(self.graphic_materialCheck, self.warningString) | |
self.updateValidationResult("Click CleanUp to remove unused materials", self.materialTextBrowser) | |
def updateStatusGraphic(self, graphic, status): | |
# Function to change color of the graphic | |
if status == "Pass": | |
color = QColor(0, 255, 0) | |
if status == "Warning": | |
color = QColor(255, 255, 0) | |
if status == "Fail": | |
color = QColor(255, 0, 0) | |
self.errorArray.append(graphic) | |
background_color = "background-color: rgba({},{},{},{})".format( | |
color.red(), color.green(), color.blue(), color.alphaF() | |
) | |
graphic.setStyleSheet(background_color) | |
def cleanUpMaterial(self): | |
print ("--------- Clean up material") | |
# TODO: Update the function subtract | |
#unusedMaterial = self.compareMaterial() | |
#for u in unusedMaterial: | |
# cmds.delete(u) | |
self.materialClean = True | |
mel.eval('MLdeleteUnused') | |
self.updateStatusGraphic(self.graphic_materialCheck, self.passString) | |
self.updateValidationResult(self.passString, self.materialTextBrowser) | |
def bindPoseCheck(self): | |
print("--------- Bind pose check") | |
# Get a list of all bind poses in the scene. | |
bindPoseArray = [] | |
bind_poses = cmds.ls(type='dagPose') | |
skeleton_bind_poses = [pose for pose in bind_poses if self.rootName in cmds.dagPose(pose, query=True, members=True)] | |
bindPoseNumber = (len(skeleton_bind_poses)) | |
# Check children bind pose | |
rootChildren = self.getRootChildren() | |
for r in rootChildren: | |
splitString = r.split('|')[-1] | |
childBindPoses = [pose for pose in bind_poses if splitString in cmds.dagPose(pose, query=True, members=True)] | |
if childBindPoses not in bindPoseArray: | |
bindPoseArray.append(childBindPoses) | |
if bindPoseNumber == 1 and (len(bindPoseArray) == 1): | |
self.updateStatusGraphic(self.graphic_bindPoseCheck, self.passString) | |
self.updateValidationResult(self.passString, self.bindPoseTextBrowser) | |
else: | |
self.updateStatusGraphic(self.graphic_bindPoseCheck, self.failString) | |
if bindPoseNumber > 1: | |
self.updateValidationResult("There are more than 1 bind pose", self.materialTextBrowser) | |
def getHierarchyRecursive(self, node): | |
children = cmds.listRelatives(node, children=True, fullPath=True) or [] | |
result = [] | |
for child in children: | |
result.append(child) | |
result.extend(self.getHierarchyRecursive(child)) | |
return result | |
def getRootChildren(self): | |
rootChild = self.getHierarchyRecursive(self.rootName) | |
return rootChild | |
def checkBoneInfluence(self, mesh): | |
self.boneInfluenceLimit = self.getMaxBoneValue() | |
skin_Cluster = cmds.ls(cmds.listHistory(mesh), type='skinCluster') | |
if not skin_Cluster: | |
return | |
# Get all vertices | |
vertices = cmds.ls(mesh + ".vtx[*]", flatten=True) | |
for vertex in vertices: | |
influences = cmds.skinPercent(skin_Cluster[0], vertex, query=True, value=True) | |
# Check if vertex have more then bone influences limit | |
if len([weight for weight in influences if weight > 0]) > self.boneInfluenceLimit: | |
print ("Vertex {} in mesh {} has more than {} bone influences.".format(vertex, mesh, self.boneInfluenceLimit)) | |
self.vertexWrongArray.append(vertex) | |
if mesh not in self.meshBoneInfluenceOverLimit: | |
self.meshBoneInfluenceOverLimit.append(mesh) | |
def vertexInfluenceLimitCheck(self): | |
print("--------- Vertex influence check") | |
allMeshes = cmds.ls(type='mesh') | |
for mesh in allMeshes: | |
self.checkBoneInfluence(mesh) | |
if len(self.vertexWrongArray) > 0: | |
print ("FAIL") | |
self.updateStatusGraphic(self.graphic_vertexInfluenceCheck, self.warningString) | |
self.updateValidationResult("These meshes have vertex that over the influence limit: \n\n" + str(self.meshBoneInfluenceOverLimit), self.vertexInfluenceTextBrowser) | |
else: | |
print ("SUCCESS") | |
self.updateStatusGraphic(self.graphic_vertexInfluenceCheck, self.passString) | |
self.updateValidationResult(self.passString, self.vertexInfluenceTextBrowser) | |
def hasSkinCluster(self, mesh): | |
skinClusters = cmds.listConnections(mesh, type='skinCluster') | |
return skinClusters is not None and len(skinClusters) > 0 | |
def findMeshesWithNoSkinClusters(self): | |
allMeshes = cmds.ls(type='mesh') | |
meshesWithNoSkinClusterArray = [] | |
for mesh in allMeshes: | |
if not self.hasSkinCluster(mesh) and not ("ShapeOrig" in mesh): # Ignore ShapeOrig | |
meshesWithNoSkinClusterArray.append(mesh) | |
return meshesWithNoSkinClusterArray | |
def skinClusterCheck(self): | |
# This is called skinnedMesh check in GUI instead of skin cluster | |
print ("--------- Skinned Mesh check") | |
noSkinCluster = self.findMeshesWithNoSkinClusters() | |
rootSkinCluster = self.getSkinClusterFromJoints() # Get all skin clusters in root | |
meshesSkinCluster = self.get_skin_clusters_in_scene() # Get meshes skincluster | |
if not noSkinCluster: | |
print ("All meshes hace skin cluster") | |
self.updateStatusGraphic(self.graphic_skinClusterCheck, self.passString) | |
else: | |
errorMessage = ("There are meshes with no skin cluster: \n\n" + str(noSkinCluster)) | |
print (errorMessage) | |
self.updateStatusGraphic(self.graphic_skinClusterCheck, self.warningString) | |
for m in noSkinCluster: | |
print (m) | |
self.updateValidationResult(errorMessage, self.skinClusterTextBrowser) | |
if not(set(meshesSkinCluster) == set(rootSkinCluster)): | |
self.updateStatusGraphic(self.graphic_skinClusterCheck, self.failString) | |
diff = set(meshesSkinCluster).symmetric_difference(set(rootSkinCluster)) | |
print ("There are skin cluster difference between meshes and joints") | |
print (diff) | |
self.updateValidationResult("There are skin cluster difference between meshes and joints", self.skinClusterTextBrowser) | |
def jointSkinClusterCheck(self): | |
meshesSkinCluster = self.get_skin_clusters_in_scene() # Get meshes skincluster | |
rootJoint = None | |
# Get root joint | |
allJoints = cmds.ls(type="joint") | |
# Find the joint with no parent (the root joint) | |
for joint in allJoints: | |
if not cmds.listRelatives(joint, parent=True): | |
rootJoint = joint | |
self.recursiveJointCheck(rootJoint, meshesSkinCluster) | |
if len(self.jointSCWrongArray) > 0: | |
self.updateStatusGraphic(self.graphic_skinCluster02Check, self.failString) | |
self.updateValidationResult(self.jointSCWrongArray, self.skinCluster02TextBrowser) | |
else: | |
self.updateStatusGraphic(self.graphic_skinCluster02Check, self.passString) | |
self.updateValidationResult(self.passString, self.skinCluster02TextBrowser) | |
def recursiveJointCheck(self, joint, meshesSC): | |
children = cmds.listRelatives(joint, children=True, type="joint") or [] | |
for child in children: | |
if child: | |
jointSCList = self.getSkinClustersPerJoint(child) | |
# Compare the list | |
if not(set(jointSCList) == set(meshesSC)): | |
self.jointSCWrongArray.append(child) | |
# Continue down hierarchy | |
self.recursiveJointCheck(child, meshesSC) | |
def updateValidationResult(self, text, textBrowser): | |
textBrowser.setPlainText(text) | |
def getCharacterName(self): | |
characterName = self.mayaFilePath.split("/")[-1] | |
characterName = characterName.split(".")[0] # Remove '.fbx' | |
characterName = characterName.split("_")[1] | |
return characterName | |
def getExportedObjects(self, meshPart): | |
cmds.select(clear=True) | |
objectToExport = ["root"] | |
objectToExport.append(meshPart) | |
# Select the relevant object and 'Root' | |
for o in objectToExport: | |
if cmds.objExists(o): | |
cmds.select(o, add=True) | |
return objectToExport | |
def fbxExportOptions(self): | |
fbxOptions = { | |
's': True, # Selection Only | |
'es': True, # Embed Textures | |
'force': True, # Overwrite if file exists | |
'ems': True, # Export Edges | |
'wn': True, # World Normal | |
'et': True, # Tangents and Binormals | |
'b': False, # Smoothing Groups | |
'm': True, # Smooth Mesh | |
'l': False, # Light | |
'd': False, # Cameras | |
'animation': False, # Animation | |
} | |
return fbxOptions | |
def getParentGroup(self,obj): | |
parentGroup = cmds.ls(obj, long=True)[0].split("|")[1:-1] | |
parentGroup.reverse() | |
return (parentGroup[0]).split("_")[0] if parentGroup else None | |
def unhideAllMeshes(self): | |
allObjects = cmds.ls() | |
for obj in allObjects: | |
try: | |
cmds.setAttr(obj + ".visibility", True) | |
except: | |
continue | |
def exportMeshParts(self, GLBExport, ThumbnailExport): | |
print ("EXPORTING") | |
self.exportTextBrowser.clear() | |
fbxFolder = "/FBX" | |
glbFolder = "/GLB" | |
bodyPartTag = "" | |
# Get character name | |
char = self.getCharacterName() | |
fileDirectory = os.path.dirname(self.mayaFilePath) | |
fileDirectory = os.path.dirname(fileDirectory) # Up one folder | |
if GLBExport == False: | |
fileDirectory += fbxFolder | |
else: | |
fileDirectory += glbFolder | |
# Get mesh part folder from tag in the mesh | |
# Export path | |
exportOptions = self.fbxExportOptions() | |
fileNames = "" | |
for meshPart in self.exportArray: | |
currentExportArray = [] | |
try: | |
parentGroup = self.getParentGroup(meshPart) | |
bodyPartTag = self.getBodyPartTag(meshPart) | |
if ThumbnailExport == False: | |
currentExportArray = self.getExportedObjects(meshPart) | |
if ThumbnailExport == True: | |
cmds.select(clear=True) | |
meshPart = meshPart.split("|")[-1] | |
currentExportArray.append(meshPart) | |
for o in currentExportArray: | |
print (meshPart) | |
print (o) | |
if cmds.objExists(o): | |
cmds.select(o, add=True) | |
print (currentExportArray) | |
filePath = fileName = fileDirectory + "/" + bodyPartTag | |
# Create export folder if not exist | |
if not os.path.exists(filePath): | |
os.makedirs(filePath) | |
fileName = fileDirectory + "/" + bodyPartTag +"/" + self.uassetPrefix + char + "_" | |
if ThumbnailExport == False: | |
# Concatenate 2nd value, because the first 1 is always 'Root' | |
#currentFileName = fileName + (exportArray[1].split("|")[-1]) | |
# TODO: Sort this parent group naming | |
#currentFileName = fileName + parentGroup + "_" + (exportArray[1].split("|")[-1]) | |
# TODO: Removed parent group name from Alfonso's request | |
currentFileName = fileName + (currentExportArray[1].split("|")[-1]) | |
if ThumbnailExport == True: | |
currentFileName = fileName + (currentExportArray[0].split("|")[-1]) | |
fileNames += ("Export success on: " + (currentFileName + "\n" + "\n")) | |
# Export and parse export options | |
# FBX EXPORT | |
if ThumbnailExport == False: | |
if GLBExport == False: | |
cmds.file(currentFileName, force=True, options="".join(f"{key}={value}" for key, value in exportOptions.items()), type="FBX export", pr=True, es=True) | |
# GLB Export | |
if GLBExport == True: | |
mel.eval('source "{}"; MeshExport "{}";'.format(self.melScriptPath, currentFileName)) | |
if ThumbnailExport == True: | |
print ("Do Batch Render") | |
self.doRender() | |
except: | |
# Display error when fail export | |
failString = "Export fail on: " + str(meshPart) | |
fileNames += failString | |
# Update text browser | |
self.updateValidationResult(fileNames, self.exportTextBrowser) | |
def getBodyPartTag(self, bodyPart): | |
# TODO: GET VALUE OF BODY PART HERE, THIS IS NEEDED FOR SETTING EXPORT FOLDER OF MESH/BODY PART | |
allAttr = cmds.listAttr(bodyPart, userDefined = True) | |
for a in allAttr: | |
if (a == self.meshPartTagAttributeName): | |
enumValues = cmds.getAttr(f"{bodyPart}.{a}") | |
bodyPartTag = self.meshPartArray[enumValues] | |
return bodyPartTag | |
def updateBodyPartTags(self): | |
allTagsFromQT = self.comboBox_meshPartTags | |
neededTags = self.makeMeshPartEnumArray() | |
enumOptions = ':'.join(neededTags) | |
currentTagInQT = allTagsFromQT.currentIndex() | |
# Get all object with extra attributes BodyPart | |
# Compare the member of body part enum in object to the one in code | |
# If different, update | |
self.checkTag() | |
for obj in self.exportArray: | |
allAttr = cmds.listAttr(obj, userDefined = True) | |
for a in allAttr: | |
if (a == self.meshPartTagAttributeName): | |
enumValues = cmds.getAttr(f"{obj}.{a}") | |
enumMembers = cmds.attributeQuery(a, node=obj, listEnum=True)[0].split(':') | |
currentBodyPartTag = (enumMembers[enumValues]) | |
newIndex = (self.meshPartArray.index(currentBodyPartTag)) | |
self.updateArrayWithOrder(self.meshPartArray, enumMembers) | |
self.reorderArray(self.meshPartArray, enumMembers) | |
# Delete body part attibute | |
cmds.deleteAttr(obj + '.' + a) | |
# Add custom attribute | |
if not (cmds.attributeQuery(self.meshPartTagAttributeName, node=obj, exists=True)): | |
cmds.addAttr(obj, longName=self.meshPartTagAttributeName, attributeType='enum', enumName = enumOptions) | |
# Set attribute default value | |
cmds.setAttr('{}.{}'.format(obj, self.meshPartTagAttributeName), newIndex) | |
def updateArrayWithOrder(self, meshPartArray, objectTagArray): | |
objectTagArraySet = set (objectTagArray) | |
for item in meshPartArray: | |
if item not in objectTagArraySet: | |
objectTagArray.append(item) | |
def reorderArray(self, arr1, arr2): | |
indexMap = {elem: index for index, elem in enumerate(arr1)} | |
arr2.sort(key=lambda x:indexMap.get(x, len(arr1))) | |
# EXPORT PART | |
def exportMeshes(self): | |
print ("Export Meshes") | |
self.unhideAllMeshes() | |
# Get all MeshPart objects | |
self.checkTag() | |
exportGLB = False | |
# Export all MeshPart | |
self.exportMeshParts(exportGLB, False) | |
# TAGS PART | |
def makeEnumArray(self): | |
tagsEnumArray = [] | |
tagsEnumArray.append(self.tagNonExport) | |
tagsEnumArray.append(self.tagMeshPart) | |
return tagsEnumArray | |
def makeMeshPartEnumArray(self): | |
meshPartEnumArray = [] | |
for i in self.meshPartArray: | |
meshPartEnumArray.append(i) | |
return meshPartEnumArray | |
def addTag(self): | |
print ("Add tag") | |
allTagsFromQT = self.comboBox_Tags | |
currentTagInQT = allTagsFromQT.currentIndex() | |
selectedObject = cmds.ls(selection=True) | |
neededTags = self.makeEnumArray() | |
enumOptions = ':'.join(neededTags) | |
# Add custom attribute | |
for obj in selectedObject: | |
if not (cmds.attributeQuery(self.extraAttributeName, node=obj, exists=True)): | |
cmds.addAttr(obj, longName=self.extraAttributeName, attributeType='enum', enumName = enumOptions) | |
# Set attribute default value | |
cmds.setAttr('{}.{}'.format(obj, self.extraAttributeName), currentTagInQT) | |
def deleteTags(self): | |
print ("Delete tags") | |
selectedObject = cmds.ls(selection=True) | |
for o in selectedObject: | |
customAttributes = cmds.listAttr(o, userDefined=True) | |
try: | |
for attr in customAttributes: | |
# Delete the custom attribute | |
cmds.deleteAttr(o, at=attr) | |
except: | |
print ("no attribute") | |
def getCurrentFile(self): | |
currentFile = cmds.file(q=True, sceneName=True) | |
noExt, _ = os.path.splitext(currentFile) | |
return noExt | |
# GLTF EXPORT RELATED | |
def convertMaterialToaiStandardSurface(self, shadeEng, materialInput, obj, matNumber): | |
if cmds.nodeType(materialInput) != 'aiStandardSurface': | |
aiMaterial = cmds.shadingNode('aiStandardSurface', asShader=True, name='{}_aiStandardSurface'.format(materialInput)) | |
# Transfer attributes from existing material to new aiStandardSurface material | |
self.transferAttributes(materialInput, aiMaterial) | |
# Transfer textures from existing material to new aiStandardSurface material | |
self.transferTextures(materialInput, aiMaterial) | |
# Assign aiMAterial | |
if matNumber == 1: | |
self.assignaiMaterial(obj, aiMaterial, materialInput) | |
elif matNumber > 1: | |
self.replaceMaterial(obj, shadeEng, materialInput, aiMaterial) | |
# Find normal and ARMS texture | |
normalTexture, armsTexture, difuseTexture = self.getNormalAndARMS(aiMaterial) | |
# Swap textures to PNG if not already using one | |
normalTexture, armsTexture, difuseTexture = self.swapTextureToPNG(normalTexture, armsTexture, difuseTexture) | |
# Assign normal and ARMS texture to aiMaterial | |
self.assignNormalARMSTexture(normalTexture, armsTexture, aiMaterial, difuseTexture) | |
else: | |
print ("Object already using aiMat") | |
def assignIndividualTextures(self,filePath, attribute, aiMaterial): | |
if os.path.isfile(filePath): | |
fileNode = cmds.shadingNode("file", asTexture=True, name=f"{attribute}MapInput") | |
cmds.setAttr(f"{fileNode}.fileTextureName", filePath, type='string') | |
cmds.connectAttr(f"{fileNode}.outColor", f"{aiMaterial}.{attribute}") | |
def assignNormalARMSTexture(self, normal, arms, aiMaterial, difuse): | |
# Assign Normal | |
self.assignIndividualTextures(normal, "normalCamera", aiMaterial) | |
# Assign ARMS | |
if os.path.isfile(arms): | |
# Make file node to load the texture | |
armsFileNode = cmds.shadingNode("file", asTexture=True, name="armsMapInput") | |
# Set the file texture name | |
cmds.setAttr(f"{armsFileNode}.fileTextureName", arms, type="string") | |
# Connect G to roughness, B to metalness | |
cmds.connectAttr(f"{armsFileNode}.outColorG", f"{aiMaterial}.specularRoughness") | |
cmds.connectAttr(f"{armsFileNode}.outColorB", f"{aiMaterial}.metalness") | |
# Assign Difuse | |
# Disconnect existing baseColor first | |
if os.path.isfile(difuse): | |
connections = cmds.listConnections(aiMaterial + ".baseColor", source=True, destination=False, plugs=True) | |
if connections: | |
# Disconnect each connection | |
for connection in connections: | |
cmds.disconnectAttr(connection, aiMaterial + ".baseColor") | |
self.assignIndividualTextures(difuse, "baseColor", aiMaterial) | |
def getNormalAndARMS(self, materialInput): | |
existingTexture = self.getTexturesFromAiMaterial(materialInput) | |
normal, arms, difuse = self.getImageName(existingTexture[0]) | |
return normal, arms, difuse | |
def getTexturesFromAiMaterial(self, aiMaterialNode): | |
textures = [] | |
connections = cmds.listConnections(aiMaterialNode, source=True, destination=False, plugs=True) | |
if connections: | |
for connection in connections: | |
if cmds.nodeType(connection) == "file": | |
textureNode = connection | |
textures.append(textureNode) | |
return textures | |
def getImageName(self, textureNode): | |
textureNode = (textureNode.split('.outColor')[0]) | |
fileTexturePath = cmds.getAttr(textureNode + ".fileTextureName") | |
textureFolder = os.path.dirname(fileTexturePath) | |
textureName = os.path.splitext(os.path.basename(fileTexturePath))[0] | |
# define suffixes | |
suffixes = { | |
'Normal': self.normalSuffix, | |
'Arms': self.ARMSSuffix, | |
'Difuse': self.difuseSuffix | |
} | |
# Generate texture paths | |
texturePaths = {} | |
for key, suffix in suffixes.items(): | |
modifiedTextureName = ('_'.join(textureName.split('_')[:-1])) + suffix | |
texturePaths[key] = os.path.join(textureFolder, modifiedTextureName + self.textureFormat) | |
return texturePaths['Normal'], texturePaths['Arms'], texturePaths['Difuse'] | |
def swapTextureToPNG(self, *textures): | |
newTextures = [] | |
for texture in textures: | |
if texture.lower().endswith('.tga'): | |
pngTexture = os.path.splitext(texture)[0] + '.png' | |
if os.path.exists(pngTexture): | |
texture = pngTexture | |
newTextures.append(texture) | |
return tuple(newTextures) | |
def replaceMaterial(self, obj, shadingEngine, materialInput, aiMaterial): | |
for se in shadingEngine: | |
materials = cmds.ls(cmds.listConnections(se), materials=True) | |
if materialInput in materials: | |
cmds.disconnectAttr('%s.outColor' % materialInput, '%s.surfaceShader' % se) | |
# Connect new mat | |
cmds.connectAttr('%s.outColor' % aiMaterial, '%s.surfaceShader' % se, force=True) | |
def assignaiMaterial(self, obj, aiMaterial, materialInput): | |
cmds.select(obj) | |
cmds.hyperShade(assign=aiMaterial) | |
# Delete old material | |
cmds.delete(materialInput) | |
def transferAttributes(self, sourceMaterial, destinationMaterial): | |
attributes = cmds.listAttr(sourceMaterial, userDefined=True, scalar=True) | |
if attributes: | |
for attr in attributes: | |
value = cmds.getAttr(sourceMaterial + '.' + attr) | |
cmds.setAttr(destinationMaterial + '.' + attr, value) | |
def transferTextures(self, sourceMaterial, destinationMaterial): | |
fileNodes = cmds.ls(cmds.listHistory(sourceMaterial), type='file') | |
if fileNodes: | |
for fileNode in fileNodes: | |
texturePath = cmds.getAttr(fileNode + '.fileTextureName') | |
newFileNode = cmds.shadingNode('file', asTexture=True) | |
cmds.setAttr(newFileNode + '.fileTextureName', texturePath, type="string") | |
cmds.connectAttr(newFileNode + '.outColor', destinationMaterial + '.baseColor', force=True) | |
def skeletalMeshGLBExport(self): | |
print ("Skeletal mesh export") | |
currentFile = self.getCurrentFile() | |
self.checkTag() | |
exportGLB = True | |
# Activate export button | |
mel.eval('source "{}"; MeshExport "{}";'.format(self.melScriptPath, currentFile)) | |
def variantSkeletalMeshGLBExport(self): | |
textureColourArray = [ | |
'Red', | |
'Brown', | |
'Gray', | |
'Purple', | |
'Black', | |
'White', | |
'Pink', | |
'Green', | |
'Cosmic', | |
'Enlightened', | |
'Glitch', | |
'Jade' | |
] | |
print ("Variant skeletal mesh export") | |
# Get the material of selected object | |
selectedObject = cmds.ls(selection=True) | |
bodyName = (selectedObject[0].split("_")[1]) | |
theNodes = cmds.ls(sl = True, dag = True, s = True) | |
shadeEng = cmds.listConnections(theNodes, type = "shadingEngine") | |
materials = cmds.ls(cmds.listConnections(shadeEng), materials = True) | |
header = '' | |
for material in materials: | |
textureNode = cmds.listConnections(material, type='file')[0] | |
# Get the filename from the texture node | |
currentTextureFilename = cmds.getAttr("%s.fileTextureName" % textureNode) | |
#currentTextureFilename = currentTextureFilename.lower() | |
if 'png' in currentTextureFilename: | |
header = (currentTextureFilename.split('_color.png')[0]) | |
header = "_".join(header.split("_")[:-1]) | |
if 'tga' in currentTextureFilename: | |
header = (currentTextureFilename.split('_color.tga')[0]) | |
header = "_".join(header.split("_")[:-1]) | |
texturesFolder = ("/".join(header.split("/")[:-1])) + "/" | |
# TODO: Get all path of textures | |
for texture in textureColourArray: | |
try: | |
# GLB EXPORT STUFF | |
currentFile = "".join((self.getCurrentFile()).split('_01')[:-1]) | |
currentFileFolder = "".join((self.getCurrentFile()).split('MA')[:-1]) | |
currentFileName = os.path.basename(self.getCurrentFile()) | |
needForTexture = currentFile | |
GLBFolder = currentFileFolder + 'GLB/MainBody/' | |
currentFile = GLBFolder + currentFileName + '_' + bodyName | |
currentFile = currentFile + '_' + texture + '_01' | |
print (currentFile) | |
# TEXTURES STUFF | |
#newTexturePath = texturesFolder + 'MoonBirds_Body_' + bodyName + '_' + texture + '_color.png' | |
newTexturePath = "/".join(needForTexture.split('/')[:-3]) + '/Textures/MoonBirds_Body/MoonBirds_Body_' | |
newTexturePath = newTexturePath + bodyName + '_' + texture + '_color.png' | |
if not os.path.exists(newTexturePath): | |
print ("NOT EXIST") | |
print (newTexturePath) | |
# Update path with new path | |
cmds.setAttr("%s.fileTextureName" % textureNode, newTexturePath, type="string") | |
# Do export with Babylon Exporter | |
self.checkTag() | |
mel.eval('source "{}"; MeshExport "{}";'.format(self.melScriptPath, currentFile)) | |
except: | |
continue | |
def batchGLBExportWithTags(self): | |
self.unhideAllMeshes() | |
currentFile = self.getCurrentFile() | |
self.checkTag() | |
exportGLB = True | |
# Export all MeshPart | |
self.exportMeshParts(exportGLB, False) | |
def animationGLBExport(self): | |
print ("Animation export") | |
currentFile = self.getCurrentFile() | |
# Activate export button | |
animName = self.text_animationName.toPlainText() | |
currentFile = (currentFile + '_' + animName) | |
mel.eval('source "{}"; AnimationExport "{}";'.format(self.animationMelScriptPath, currentFile)) | |
# GENERIC FUNCTIONALITY | |
def resizeEvent(self, event): | |
# Called on automatically generated resize event | |
self.widget.resize(self.width(), self.height()) | |
def closeWindow(self): | |
# Close window | |
print ('closing window') | |
self.removeSelectionCallback() | |
self.destroy() | |
def openWindow(): | |
""" | |
ID Maya and attach tool window. | |
""" | |
# Maya uses this so it should always return True | |
if QtWidgets.QApplication.instance(): | |
# Id any current instances of tool and destroy | |
for win in (QtWidgets.QApplication.allWindows()): | |
if 'RigExporterWindow' in win.objectName(): # update this name to match name below | |
win.destroy() | |
#QtWidgets.QApplication(sys.argv) | |
mayaMainWindowPtr = omui.MQtUtil.mainWindow() | |
mayaMainWindow = wrapInstance(int(mayaMainWindowPtr), QtWidgets.QWidget) | |
RigExport.window = RigExport(parent = mayaMainWindow) | |
RigExport.window.setObjectName('RigExporterWindow') # code above uses this to ID any existing windows | |
RigExport.window.setWindowTitle('Rig Exporter') | |
RigExport.window.show() | |
openWindow() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment