#!/usr/bin/env python # -*- coding: utf-8 -*- from maya import cmds import maya.api.OpenMaya as om import maya.mel as mel #creating a class for global variables used in different functions. class globalVar: def __init__(self): self.new_mesh = None self.ogmesh = None self.newmesh = None self.bone_List = None self.rig_root = None self.namespace_flag = None self.sel1 = None self.selection_list = None self.dag_path = None self.mfn_mesh1 = None self.pp = None self.length = None self.Q = None self.selected = None self.b = None self.bx = None self.by = None self.bz = None self.BonePoint = None self.R = None self.distList = None self.distance = None self.minDist = None self.minDistID = None self.pointMin = None self.M1X = None self.M1Z = None self.M1Y = None self.sel2 = None self.mfn_mesh2 = None self.points2 = None self.M2X = None self.M2Z = None self.M2Y = None self.offsetX = None self.offsetY = None self.offsetZ = None class MyGlobalVar: def __init__(self): self.verticeDict = None #Class to get geo Information from selection to pass along during weight transfer using dictionary class DictClass: def geoInfo(self, vtx=0, geo=0, shape=0, skinC=0): # Returns a list of requested object strings returnValues = [] selVTX = [x for x in cmds.ls(sl=1, fl=1) if ".vtx" in x] if len(selVTX) == 0: # geo can be of bool/int type or of string type. if type(geo) == int or type(geo) == bool: selGEO = cmds.ls(sl=1, objectsOnly=1)[0] elif type(geo) == str or type(geo) == str: selGEO = geo geoShape = cmds.listRelatives(selGEO, shapes=1)[0] # Deformed shapes occur when reference geometry has a deformer applied to it that is then cached # the additional section will take out the namespace of the shapefile (if it exists) and try to # apply the deform syntax on it. if ":" in geoShape: # the colon : deliminates namespace references deformShape = geoShape.partition(":")[2] + "Deformed" if len(cmds.ls(deformShape)) != 0: geoShape = deformShape print("deformed shape found: " + geoShape) else: geoShape = selVTX[0].partition(".")[0] + "Shape" deformTest = geoShape.partition(":")[2] + "Deformed" if len(deformTest) != 0: geoShape = deformTest print("deformed shape found on selected vertices: " + geoShape) for x in range( len(selVTX) ): selVTX[x] = ( selVTX[x].replace(".","ShapeDeformed.") ).partition(":")[2] selGEO = cmds.listRelatives(geoShape, p=1)[0] print(geoShape + " | " + selGEO) if vtx == 1: if len(selVTX) != 0: # if vertices are already selected, then we can take that list whole-sale. returnValues.append(selVTX) else: vtxIndexList = ["{0}.vtx[{1}]".format(geoShape, x) for x in cmds.getAttr ( geoShape + ".vrts", multiIndices=True)] returnValues.append(vtxIndexList) if geo == 1 or geo == True or type(geo) == str or type(geo) == str: returnValues.append(selGEO) if shape == 1: returnValues.append(geoShape) if skinC == 1: skinClusterNM = [x for x in cmds.listHistory(geoShape) if cmds.nodeType(x) == "skinCluster" ][0] returnValues.append(skinClusterNM) return returnValues def getVertexWeights(self, vertexList=[], skinCluster="", thresholdValue=0.001): if len(vertexList) != 0 and skinCluster != "": mgv.verticeDict = {} for vtx in vertexList: influenceVals = cmds.skinPercent(skinCluster, vtx, q=1, v=1, ib=thresholdValue) influenceNames = cmds.skinPercent(skinCluster, vtx, transform=None, q=1, ib=thresholdValue) mgv.verticeDict[vtx] = list(zip(influenceNames, influenceVals)) return mgv.verticeDict else: cmds.error("No Vertices or SkinCluster passed.") #instancing this class name into an variable to call variables inside of functions mgv = MyGlobalVar() utils = DictClass() gv = globalVar() #creating a wrapper to store UI values and pass it to the corresponding feilds def button_wrapper(fn, *args, **kwargs): def wrapped(_): fn(*args, **kwargs) return wrapped #passing arguments as values to the variables from UI def store_og(txt_field): ui_sel = cmds.ls(sl=True) cmds.textField(txt_field, edit=True, text= ui_sel[0]) gv.ogmesh gv.ogmesh= ui_sel[0] return gv.ogmesh #passing arguments as values to the variables from UI def store_new(txt_field): ui_sel = cmds.ls(sl=True) cmds.textField(txt_field, edit=True, text= ui_sel[0]) gv.new_mesh gv.new_mesh = ui_sel[0] return gv.new_mesh #passing arguments as values to the variables from UI def store_joint(txt_field): ui_sel = cmds.ls(sl=True) cmds.textField(txt_field, edit=True, text= ui_sel[0]) gv.rig_root gv.rig_root = ui_sel[0] return gv.rig_root gv.namespace_flag = "True" #Main function to align the skeleton def toolkit(_): #setting skin cluster node to 0 before everything history = cmds.listHistory(gv.ogmesh) skinCluster_node = None for node in history: node_type = cmds.nodeType(node) if node_type == "skinCluster": skinCluster_node = node break if skinCluster_node: cmds.setAttr("{}.envelope".format(skinCluster_node), 0) #Alright lets go cmds.select( clear=True ) def OG_Mesh_func(): gv.sel1 = cmds.ls(gv.ogmesh) # get the dag path gv.selection_list = om.MSelectionList() gv.selection_list.add(gv.sel1[0]) gv.dag_path = gv.selection_list.getDagPath(0) # creating Mfn Mesh gv.mfn_mesh1 = om.MFnMesh(gv.dag_path) #get the full number of vertex in mesh for loop gv.pp = gv.mfn_mesh1.getPoints() gv.length = len(gv.pp) #looping to get vertex coordinates of all points in first mesh gv.Q = 0 while gv.Q < gv.length: gv.points1 = gv.mfn_mesh1.getPoint(gv.Q, space=om.MSpace.kWorld) gv.Q = gv.Q+1 def Get_Bone_Data_func(): gv.selected = cmds.select(gv.bone) gv.b = cmds.xform(gv.selected,q=1,ws=1,t=1) gv.bx = gv.b[0] gv.by = gv.b[1] gv.bz = gv.b[2] gv.BonePoint = om.MPoint(gv.b) #loop to get distances of each verts from the selected bone gv.R = 0 gv.distList = [] while gv.R < gv.length: gv.distance = gv.BonePoint.distanceTo(gv.mfn_mesh1.getPoint(gv.R, space=om.MSpace.kWorld)) gv.distList.append(gv.distance) gv.R = gv.R + 1 #find the min distance vertex ID gv.minDist = (min(gv.distList)) gv.minDistID = (gv.distList.index(min(gv.distList))) #giving the M1 variables the coordinates of the closest vertex to the bone gv.pointMin = gv.mfn_mesh1.getPoint(gv.minDistID, space=om.MSpace.kWorld) gv.M1X = gv.pointMin.x gv.M1Z = gv.pointMin.z gv.M1Y = gv.pointMin.y def New_mesh_func(): # get the mesh vertex position gv.sel2 = cmds.ls(gv.newmesh) # get the dag path gv.selection_list = om.MSelectionList() gv.selection_list.add(gv.sel2[0]) gv.dag_path = gv.selection_list.getDagPath(0) # creating Mfn Mesh gv.mfn_mesh2 = om.MFnMesh(gv.dag_path) #creating M2 variables to store coordinate of the closest vertex in the new mesh gv.points2 = gv.mfn_mesh2.getPoint(gv.minDistID, space=om.MSpace.kWorld) gv.M2X = gv.points2.x gv.M2Z = gv.points2.z gv.M2Y = gv.points2.y def vertex_offset_func(): gv.offsetX = gv.M2X - (gv.M1X - gv.bx) gv.offsetY = gv.M2Y - (gv.M1Y - gv.by) gv.offsetZ = gv.M2Z - (gv.M1Z - gv.bz) def Set_Bone_Data_func(): selected = cmds.select(gv.bone) #cmds.xform(selected,ws=1,t=(offsetX, offsetY, offsetZ)) cmds.move(gv.offsetX, gv.offsetY, gv.offsetZ, absolute=True, ws=True, pcp=True) def select_loop_bones(): cmds.select(gv.new_mesh) gv.newmesh = cmds.ls( selection=True ) #selecting the bone cmds.select(gv.rig_root, hierarchy=False) children_joints = cmds.listRelatives(allDescendents=True, type='joint') cmds.select(children_joints, add=True) gv.bone_List = cmds.ls( selection=True ) cmds.select( clear=True ) #Starting bone loop gv.boneID = 0 while gv.boneID < len(gv.bone_List): gv.bone = gv.bone_List[gv.boneID] #run OG_Mesh file OG_Mesh_func() #run Get_Bone_Data file Get_Bone_Data_func() #run New_Mesh file New_mesh_func() #run Vertex_Offset file vertex_offset_func() #run Set_Bone_data file Set_Bone_Data_func() gv.boneID = gv.boneID+1 select_loop_bones() #Bind New skin cmds.select( clear=True ) cmds.select(gv.rig_root, hierarchy=True) root_heirarchy = cmds.ls(sl=True) for node in root_heirarchy: #print(node) node_type = cmds.nodeType(node) if node_type == "joint": rootjoint_node = node break #now bind skinning cmds.select( clear=True ) cmds.select(rootjoint_node) cmds.select(gv.new_mesh, add=True) #cmds.bindSkin(toAll=True, byClosestPoint=True, colorJoints=True) mel.eval('newSkinCluster " -bindMethod 0 -normalizeWeights 1 -weightDistribution 0 -mi 3 -dr 4 -rui false , multipleBindPose, 1";') cmds.select( clear=True ) cmds.confirmDialog( title='Skeleton Fitted', message='Skeleton has been moved', button=['Alright'] ) #Function to copy skin weights using maya inbuilt UV space def copySkinWeightsUV(_): # transferring skin weights using UV space cmds.select(gv.ogmesh) cmds.select(gv.new_mesh, add=True) mel.eval('copySkinWeights -noMirror -surfaceAssociation closestPoint -uvSpace UVChannel_1 UVChannel_1 -influenceAssociation closestJoint -influenceAssociation oneToOne -influenceAssociation oneToOne;') cmds.select( clear=True ) cmds.confirmDialog( title='Skin Weights Copied', message='Skin Weights Copied', button=['Dismiss'] ) #Copying skin weights using vertex ID def exportWeightsdiceat(*args): cmds.select(clear=True) cmds.select(gv.ogmesh) geoData = utils.geoInfo(vtx=1, geo=1, skinC=1) selVTX = geoData[0] skinClusterNM = geoData[2] thV = 0.001 # dictionary to hold all the vertice & relationships mgv.verticeDict = utils.getVertexWeights(vertexList=selVTX, skinCluster=skinClusterNM, thresholdValue=thV) cmds.select(clear=True) cmds.select(gv.new_mesh) selectGeoData = utils.geoInfo(geo=1, skinC=1) geoName = selectGeoData[0] skinClusterNM = selectGeoData[1] if len(mgv.verticeDict) > 0: for key in list(mgv.verticeDict.keys()): newKey = str(gv.new_mesh)+"."+key.split("Shape.")[1] try: cmds.skinPercent(skinClusterNM, newKey, tv=mgv.verticeDict[key], zri=1) except: cmds.error("Something went wrong with the skinning") print("{0} vertices were set to specificed values.".format(len(list(mgv.verticeDict.keys())))) ## else: cmds.error("Dict was empty ") cmds.confirmDialog( title='Skin Weights Copied', message='Skin Weights Copied Using Dictionary', button=['Dismiss'] ) #Creating UI def run(*args): if cmds.window("RevBS", q=1, exists=1) == True: cmds.deleteUI("RevBS") #main Window cmds.window("RevBS", title="Rev's Rig Tool v2.0", width=450, height=650, sizeable=False) cmds.columnLayout("AllLayout", width=450, height=650, parent="RevBS") #first column - Title cmds.columnLayout("TitleColumn", width=450, height=50, parent="AllLayout") cmds.text(label="TRANSFER RIGGED SKELETON TO SCALED OR MODIFIED MESH", width=450, height=50, align="center", fn="boldLabelFont") #selecting the mesh UI cmds.columnLayout("col_selectmesh", parent="AllLayout") cmds.text(label="PART I", width=450, height=30, backgroundColor=[0.274, 0.619, 0.920], align="center") cmds.text(label="Select and click corresponding buttons.", width=450, height=30, align="left") #each of the select mesh rows cmds.rowLayout("row_selectmesh1", numberOfColumns=2, parent="col_selectmesh") headmeshTXT = cmds.textField("headmeshTXT", backgroundColor=[0.1, 0.1, 0.1], editable=False, width=300, height=28, p="row_selectmesh1") cmds.button("selectHeadBtn", label="<< Original Mesh", width = 150, height=26, command=button_wrapper(store_og, headmeshTXT)) #each of the select mesh rows cmds.rowLayout("row_selectmesh2", numberOfColumns=2, parent="col_selectmesh") teethmeshTXT= cmds.textField("teethmeshTXT",backgroundColor=[0.1, 0.1, 0.1], editable=False, width=300, height=28, p="row_selectmesh2") cmds.button("selectTeethBtn", label="<< Modified Mesh", width = 150, height=26, command=button_wrapper(store_new, teethmeshTXT)) #each of the select mesh rows cmds.rowLayout("row_selectmesh3", numberOfColumns=2, parent="col_selectmesh") teethmeshTXT= cmds.textField("jointTXT",backgroundColor=[0.1, 0.1, 0.1], editable=False, width=300, height=28, p="row_selectmesh3") cmds.button("selectJointBtn", label="<< Root Joint", width = 150, height=26, command=button_wrapper(store_joint, teethmeshTXT)) #Fit skeleton button cmds.columnLayout("col_getbs", parent="AllLayout") cmds.text(label="IF ALL IS SELECTED, THEN RUN THIS", width=450, height=30, align="center") cmds.rowLayout("row_get_bs", numberOfColumns=3, width=450, height=30, parent="col_getbs") cmds.text(label=" ", width=112, height=5, align="center") #empty space to align the button in middle cmds.button(width=225, label="FIT THE SKELETON", backgroundColor=[0.80, 0.65, 0.0], align="center", command= toolkit) cmds.text(label=" ", width=112, height=5, align="center") #empty space to align the button in middle #Part II cmds.columnLayout("col_skin", parent="AllLayout") cmds.text(label="PART II", width=450, height=30, backgroundColor=[0.274, 0.619, 0.920], align="center") cmds.text(label="ONCE FITTED, SKIN USING EITHER BUTTON", width=450, height=30, align="center") cmds.rowLayout("row_skin", numberOfColumns=2, width=450, height=50, parent="col_skin") cmds.button(label="UV Method", backgroundColor=[0.80, 0.65, 0.0], align="left", width = 225, height=26, command= copySkinWeightsUV) cmds.button(label="Dictionary Method (Slow)", backgroundColor=[0.80, 0.65, 0.0], align="center", width = 225, height=26, command= exportWeightsdiceat) cmds.columnLayout("col_note", parent="AllLayout") cmds.text(label=" ", width=450, height=30, align="left") cmds.text(label=" NOTE: The old mesh and new mesh should have the same topology and vertex ID", width=450, height=30, align="left", bgc=[0.79, 0.45, 0.45]) cmds.text(label=" ", width=450, height=5, align="left") cmds.text(label=" UV METHOD: Quick but needs same UV (same sets as well) without overlap", width=450, height=30, align="left", bgc=[0.79, 0.45, 0.45]) cmds.text(label=" ", width=450, height=5, align="left") cmds.text(label=" DICTIONARY METHOD: Very SLOW but accurate, independent of UV (RECOMMENDED)", width=450, height=30, align="left", bgc=[0.79, 0.45, 0.45]) cmds.text(label=" ", width=450, height=5, align="left") cmds.text(label="Written by Rev O'Conner | Go to www.revoconner.com", width=450, height=50, align="center") cmds.text(label="Non Commercial Use Only", width=450, height=15, align="center", fn="boldLabelFont") cmds.separator(h=50,p="col_getbs") cmds.showWindow("RevBS") if __name__ == "__main__": run()